mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-02 15:09:33 -05:00
Merge branch 'main' into lunny/fix_webhook
This commit is contained in:
commit
1125967fa0
@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@ -321,6 +322,11 @@ func valuesUser(m map[int64]*user_model.User) []*user_model.User {
|
||||
return values
|
||||
}
|
||||
|
||||
// newMigrationOriginalUser creates and returns a fake user for external user
|
||||
func newMigrationOriginalUser(name string) *user_model.User {
|
||||
return &user_model.User{ID: 0, Name: name, LowerName: strings.ToLower(name)}
|
||||
}
|
||||
|
||||
// LoadUsers loads reactions' all users
|
||||
func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) {
|
||||
if len(list) == 0 {
|
||||
@ -338,7 +344,7 @@ func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Reposit
|
||||
|
||||
for _, reaction := range list {
|
||||
if reaction.OriginalAuthor != "" {
|
||||
reaction.User = user_model.NewReplaceUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
|
||||
reaction.User = newMigrationOriginalUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
|
||||
} else if user, ok := userMaps[reaction.UserID]; ok {
|
||||
reaction.User = user
|
||||
} else {
|
||||
|
@ -38,27 +38,30 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
|
||||
|
||||
u.Avatar = avatars.HashEmail(seed)
|
||||
|
||||
// Don't share the images so that we can delete them easily
|
||||
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
if err := png.Encode(w, img); err != nil {
|
||||
log.Error("Encode: %v", err)
|
||||
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
|
||||
if err != nil {
|
||||
// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
|
||||
// Don't share the images so that we can delete them easily
|
||||
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
if err := png.Encode(w, img); err != nil {
|
||||
log.Error("Encode: %v", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("New random avatar created: %d", u.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
|
||||
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
||||
if u.IsGhost() {
|
||||
if u.IsGhost() || u.IsGiteaActions() {
|
||||
return avatars.DefaultAvatarLink()
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,19 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUserAvatarLink(t *testing.T) {
|
||||
@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
|
||||
link = u.AvatarLink(db.DefaultContext)
|
||||
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||
}
|
||||
|
||||
func TestUserAvatarGenerate(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
var err error
|
||||
tmpDir := t.TempDir()
|
||||
storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
|
||||
require.NoError(t, err)
|
||||
|
||||
u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
|
||||
|
||||
// there was no avatar, generate a new one
|
||||
assert.Empty(t, u.Avatar)
|
||||
err = GenerateRandomAvatar(db.DefaultContext, u)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, u.Avatar)
|
||||
|
||||
// make sure the generated one exists
|
||||
oldAvatarPath := u.CustomAvatarRelativePath()
|
||||
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
|
||||
require.NoError(t, err)
|
||||
// and try to change its content
|
||||
_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
|
||||
require.NoError(t, err)
|
||||
|
||||
// try to generate again
|
||||
err = GenerateRandomAvatar(db.DefaultContext, u)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
|
||||
f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
content, _ := io.ReadAll(f)
|
||||
assert.Equal(t, "abcd", string(content))
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -153,8 +152,6 @@ func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
// ValidateEmail check if email is a valid & allowed address
|
||||
func ValidateEmail(email string) error {
|
||||
if err := validateEmailBasic(email); err != nil {
|
||||
@ -514,7 +511,7 @@ func validateEmailBasic(email string) error {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
||||
if !emailRegexp.MatchString(email) {
|
||||
if !globalVars().emailRegexp.MatchString(email) {
|
||||
return ErrEmailCharIsNotSupported{email}
|
||||
}
|
||||
|
||||
@ -545,3 +542,13 @@ func IsEmailDomainAllowed(email string) bool {
|
||||
|
||||
return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email)
|
||||
}
|
||||
|
||||
func GetActivatedEmailAddresses(ctx context.Context, uid int64) ([]string, error) {
|
||||
emails := make([]string, 0, 2)
|
||||
if err := db.GetEngine(ctx).Table("email_address").Select("email").
|
||||
Where("uid=? AND is_activated=?", uid, true).Asc("id").
|
||||
Find(&emails); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return emails, nil
|
||||
}
|
||||
|
@ -11,9 +11,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ErrOpenIDNotExist openid is not known
|
||||
var ErrOpenIDNotExist = util.NewNotExistErrorf("OpenID is unknown")
|
||||
|
||||
// UserOpenID is the list of all OpenID identities of a user.
|
||||
// Since this is a middle table, name it OpenID is not suitable, so we ignore the lint here
|
||||
type UserOpenID struct { //revive:disable-line:exported
|
||||
@ -99,7 +96,7 @@ func DeleteUserOpenID(ctx context.Context, openid *UserOpenID) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
} else if deleted != 1 {
|
||||
return ErrOpenIDNotExist
|
||||
return util.NewNotExistErrorf("OpenID is unknown")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
@ -213,7 +214,7 @@ func (u *User) GetPlaceholderEmail() string {
|
||||
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
|
||||
}
|
||||
|
||||
// GetEmail returns an noreply email, if the user has set to keep his
|
||||
// GetEmail returns a noreply email, if the user has set to keep his
|
||||
// email address private, otherwise the primary email address.
|
||||
func (u *User) GetEmail() string {
|
||||
if u.KeepEmailPrivate {
|
||||
@ -417,19 +418,9 @@ func (u *User) DisplayName() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
var emailToReplacer = strings.NewReplacer(
|
||||
"\n", "",
|
||||
"\r", "",
|
||||
"<", "",
|
||||
">", "",
|
||||
",", "",
|
||||
":", "",
|
||||
";", "",
|
||||
)
|
||||
|
||||
// EmailTo returns a string suitable to be put into a e-mail `To:` header.
|
||||
func (u *User) EmailTo() string {
|
||||
sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName())
|
||||
sanitizedDisplayName := globalVars().emailToReplacer.Replace(u.DisplayName())
|
||||
|
||||
// should be an edge case but nice to have
|
||||
if sanitizedDisplayName == u.Email {
|
||||
@ -526,28 +517,58 @@ func GetUserSalt() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Returns a 32 bytes long string.
|
||||
// Returns a 32-byte long string.
|
||||
return hex.EncodeToString(rBytes), nil
|
||||
}
|
||||
|
||||
// Note: The set of characters here can safely expand without a breaking change,
|
||||
// but characters removed from this set can cause user account linking to break
|
||||
var (
|
||||
customCharsReplacement = strings.NewReplacer("Æ", "AE")
|
||||
removeCharsRE = regexp.MustCompile("['`´]")
|
||||
transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||
replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
|
||||
)
|
||||
type globalVarsStruct struct {
|
||||
customCharsReplacement *strings.Replacer
|
||||
removeCharsRE *regexp.Regexp
|
||||
transformDiacritics transform.Transformer
|
||||
replaceCharsHyphenRE *regexp.Regexp
|
||||
emailToReplacer *strings.Replacer
|
||||
emailRegexp *regexp.Regexp
|
||||
systemUserNewFuncs map[int64]func() *User
|
||||
}
|
||||
|
||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
return &globalVarsStruct{
|
||||
// Note: The set of characters here can safely expand without a breaking change,
|
||||
// but characters removed from this set can cause user account linking to break
|
||||
customCharsReplacement: strings.NewReplacer("Æ", "AE"),
|
||||
|
||||
removeCharsRE: regexp.MustCompile("['`´]"),
|
||||
transformDiacritics: transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC),
|
||||
replaceCharsHyphenRE: regexp.MustCompile(`[\s~+]`),
|
||||
|
||||
emailToReplacer: strings.NewReplacer(
|
||||
"\n", "",
|
||||
"\r", "",
|
||||
"<", "",
|
||||
">", "",
|
||||
",", "",
|
||||
":", "",
|
||||
";", "",
|
||||
),
|
||||
emailRegexp: regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"),
|
||||
|
||||
systemUserNewFuncs: map[int64]func() *User{
|
||||
GhostUserID: NewGhostUser,
|
||||
ActionsUserID: NewActionsUser,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters.
|
||||
// It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character
|
||||
func NormalizeUserName(s string) (string, error) {
|
||||
vars := globalVars()
|
||||
s, _, _ = strings.Cut(s, "@")
|
||||
strDiacriticsRemoved, n, err := transform.String(transformDiacritics, customCharsReplacement.Replace(s))
|
||||
strDiacriticsRemoved, n, err := transform.String(vars.transformDiacritics, vars.customCharsReplacement.Replace(s))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n)
|
||||
}
|
||||
return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
|
||||
return vars.replaceCharsHyphenRE.ReplaceAllLiteralString(vars.removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
|
||||
}
|
||||
|
||||
var (
|
||||
@ -963,30 +984,28 @@ func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
|
||||
return users, err
|
||||
}
|
||||
|
||||
// GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
|
||||
// GetPossibleUserByID returns the user if id > 0 or returns system user if id < 0
|
||||
func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
|
||||
switch id {
|
||||
case GhostUserID:
|
||||
return NewGhostUser(), nil
|
||||
case ActionsUserID:
|
||||
return NewActionsUser(), nil
|
||||
case 0:
|
||||
if id < 0 {
|
||||
if newFunc, ok := globalVars().systemUserNewFuncs[id]; ok {
|
||||
return newFunc(), nil
|
||||
}
|
||||
return nil, ErrUserNotExist{UID: id}
|
||||
} else if id == 0 {
|
||||
return nil, ErrUserNotExist{}
|
||||
default:
|
||||
return GetUserByID(ctx, id)
|
||||
}
|
||||
return GetUserByID(ctx, id)
|
||||
}
|
||||
|
||||
// GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0
|
||||
// GetPossibleUserByIDs returns the users if id > 0 or returns system users if id < 0
|
||||
func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
|
||||
uniqueIDs := container.SetOf(ids...)
|
||||
users := make([]*User, 0, len(ids))
|
||||
_ = uniqueIDs.Remove(0)
|
||||
if uniqueIDs.Remove(GhostUserID) {
|
||||
users = append(users, NewGhostUser())
|
||||
}
|
||||
if uniqueIDs.Remove(ActionsUserID) {
|
||||
users = append(users, NewActionsUser())
|
||||
for systemUID, newFunc := range globalVars().systemUserNewFuncs {
|
||||
if uniqueIDs.Remove(systemUID) {
|
||||
users = append(users, newFunc())
|
||||
}
|
||||
}
|
||||
res, err := GetUserByIDs(ctx, uniqueIDs.Values())
|
||||
if err != nil {
|
||||
@ -996,7 +1015,7 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetUserByNameCtx returns user by given name.
|
||||
// GetUserByName returns user by given name.
|
||||
func GetUserByName(ctx context.Context, name string) (*User, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, ErrUserNotExist{Name: name}
|
||||
@ -1027,8 +1046,8 @@ func GetUserEmailsByNames(ctx context.Context, names []string) []string {
|
||||
return mails
|
||||
}
|
||||
|
||||
// GetMaileableUsersByIDs gets users from ids, but only if they can receive mails
|
||||
func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) {
|
||||
// GetMailableUsersByIDs gets users from ids, but only if they can receive mails
|
||||
func GetMailableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@ -1053,17 +1072,6 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([
|
||||
Find(&ous)
|
||||
}
|
||||
|
||||
// GetUserNamesByIDs returns usernames for all resolved users from a list of Ids.
|
||||
func GetUserNamesByIDs(ctx context.Context, ids []int64) ([]string, error) {
|
||||
unames := make([]string, 0, len(ids))
|
||||
err := db.GetEngine(ctx).In("id", ids).
|
||||
Table("user").
|
||||
Asc("name").
|
||||
Cols("name").
|
||||
Find(&unames)
|
||||
return unames, err
|
||||
}
|
||||
|
||||
// GetUserNameByID returns username for the id
|
||||
func GetUserNameByID(ctx context.Context, id int64) (string, error) {
|
||||
var name string
|
||||
|
@ -10,9 +10,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
GhostUserID = -1
|
||||
GhostUserName = "Ghost"
|
||||
GhostUserLowerName = "ghost"
|
||||
GhostUserID = -1
|
||||
GhostUserName = "Ghost"
|
||||
)
|
||||
|
||||
// NewGhostUser creates and returns a fake user for someone has deleted their account.
|
||||
@ -20,10 +19,14 @@ func NewGhostUser() *User {
|
||||
return &User{
|
||||
ID: GhostUserID,
|
||||
Name: GhostUserName,
|
||||
LowerName: GhostUserLowerName,
|
||||
LowerName: strings.ToLower(GhostUserName),
|
||||
}
|
||||
}
|
||||
|
||||
func IsGhostUserName(name string) bool {
|
||||
return strings.EqualFold(name, GhostUserName)
|
||||
}
|
||||
|
||||
// IsGhost check if user is fake user for a deleted account
|
||||
func (u *User) IsGhost() bool {
|
||||
if u == nil {
|
||||
@ -32,22 +35,16 @@ func (u *User) IsGhost() bool {
|
||||
return u.ID == GhostUserID && u.Name == GhostUserName
|
||||
}
|
||||
|
||||
// NewReplaceUser creates and returns a fake user for external user
|
||||
func NewReplaceUser(name string) *User {
|
||||
return &User{
|
||||
ID: 0,
|
||||
Name: name,
|
||||
LowerName: strings.ToLower(name),
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
ActionsUserID = -2
|
||||
ActionsUserName = "gitea-actions"
|
||||
ActionsFullName = "Gitea Actions"
|
||||
ActionsEmail = "teabot@gitea.io"
|
||||
ActionsUserID = -2
|
||||
ActionsUserName = "gitea-actions"
|
||||
ActionsUserEmail = "teabot@gitea.io"
|
||||
)
|
||||
|
||||
func IsGiteaActionsUserName(name string) bool {
|
||||
return strings.EqualFold(name, ActionsUserName)
|
||||
}
|
||||
|
||||
// NewActionsUser creates and returns a fake user for running the actions.
|
||||
func NewActionsUser() *User {
|
||||
return &User{
|
||||
@ -55,8 +52,8 @@ func NewActionsUser() *User {
|
||||
Name: ActionsUserName,
|
||||
LowerName: ActionsUserName,
|
||||
IsActive: true,
|
||||
FullName: ActionsFullName,
|
||||
Email: ActionsEmail,
|
||||
FullName: "Gitea Actions",
|
||||
Email: ActionsUserEmail,
|
||||
KeepEmailPrivate: true,
|
||||
LoginName: ActionsUserName,
|
||||
Type: UserTypeIndividual,
|
||||
@ -65,6 +62,16 @@ func NewActionsUser() *User {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) IsActions() bool {
|
||||
func (u *User) IsGiteaActions() bool {
|
||||
return u != nil && u.ID == ActionsUserID
|
||||
}
|
||||
|
||||
func GetSystemUserByName(name string) *User {
|
||||
if IsGhostUserName(name) {
|
||||
return NewGhostUser()
|
||||
}
|
||||
if IsGiteaActionsUserName(name) {
|
||||
return NewActionsUser()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
32
models/user/user_system_test.go
Normal file
32
models/user/user_system_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSystemUser(t *testing.T) {
|
||||
u, err := GetPossibleUserByID(db.DefaultContext, -1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Ghost", u.Name)
|
||||
assert.Equal(t, "ghost", u.LowerName)
|
||||
assert.True(t, u.IsGhost())
|
||||
assert.True(t, IsGhostUserName("gHost"))
|
||||
|
||||
u, err = GetPossibleUserByID(db.DefaultContext, -2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "gitea-actions", u.Name)
|
||||
assert.Equal(t, "gitea-actions", u.LowerName)
|
||||
assert.True(t, u.IsGiteaActions())
|
||||
assert.True(t, IsGiteaActionsUserName("Gitea-actionS"))
|
||||
|
||||
_, err = GetPossibleUserByID(db.DefaultContext, -3)
|
||||
require.Error(t, err)
|
||||
}
|
@ -333,14 +333,14 @@ func TestGetUserIDsByNames(t *testing.T) {
|
||||
func TestGetMaileableUsersByIDs(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
results, err := user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, false)
|
||||
results, err := user_model.GetMailableUsersByIDs(db.DefaultContext, []int64{1, 4}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
if len(results) > 1 {
|
||||
assert.Equal(t, 1, results[0].ID)
|
||||
}
|
||||
|
||||
results, err = user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, true)
|
||||
results, err = user_model.GetMailableUsersByIDs(db.DefaultContext, []int64{1, 4}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, results, 2)
|
||||
if len(results) > 2 {
|
||||
|
@ -46,9 +46,9 @@ func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID
|
||||
if len(optCommitID) == 2 {
|
||||
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
|
||||
} else if len(optCommitID) == 1 {
|
||||
commitLink = sf.repoLink + "/commit/" + optCommitID[0]
|
||||
commitLink = sf.repoLink + "/tree/" + optCommitID[0]
|
||||
} else {
|
||||
commitLink = sf.repoLink + "/commit/" + sf.refID
|
||||
commitLink = sf.repoLink + "/tree/" + sf.refID
|
||||
}
|
||||
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ func TestCommitSubmoduleLink(t *testing.T) {
|
||||
|
||||
wl := sf.SubmoduleWebLink(context.Background())
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/commit/aaaa", wl.CommitWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
|
||||
|
||||
wl = sf.SubmoduleWebLink(context.Background(), "1111")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/commit/1111", wl.CommitWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/tree/1111", wl.CommitWebLink)
|
||||
|
||||
wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
|
@ -357,5 +357,5 @@ func Test_GetCommitBranchStart(t *testing.T) {
|
||||
startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, startCommitID)
|
||||
assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID)
|
||||
assert.EqualValues(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
|
||||
}
|
||||
|
@ -519,6 +519,7 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCommitBranchStart returns the commit where the branch diverged
|
||||
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
|
||||
cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
|
||||
cmd.AddDynamicArguments(endCommitID)
|
||||
@ -533,7 +534,8 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
|
||||
|
||||
parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
|
||||
|
||||
var startCommitID string
|
||||
// check the commits one by one until we find a commit contained by another branch
|
||||
// and we think this commit is the divergence point
|
||||
for _, commitID := range parts {
|
||||
branches, err := repo.getBranches(env, string(commitID), 2)
|
||||
if err != nil {
|
||||
@ -541,11 +543,9 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
|
||||
}
|
||||
for _, b := range branches {
|
||||
if b != branch {
|
||||
return startCommitID, nil
|
||||
return string(commitID), nil
|
||||
}
|
||||
}
|
||||
|
||||
startCommitID = string(commitID)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
|
@ -93,7 +93,7 @@ func Clean(storage ObjectStorage) error {
|
||||
}
|
||||
|
||||
// SaveFrom saves data to the ObjectStorage with path p from the callback
|
||||
func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) error) error {
|
||||
func SaveFrom(objStorage ObjectStorage, path string, callback func(w io.Writer) error) error {
|
||||
pr, pw := io.Pipe()
|
||||
defer pr.Close()
|
||||
go func() {
|
||||
@ -103,7 +103,7 @@ func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) err
|
||||
}
|
||||
}()
|
||||
|
||||
_, err := objStorage.Save(p, pr, -1)
|
||||
_, err := objStorage.Save(path, pr, -1)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -116,14 +116,7 @@ var (
|
||||
_ Payloader = &PackagePayload{}
|
||||
)
|
||||
|
||||
// _________ __
|
||||
// \_ ___ \_______ ____ _____ _/ |_ ____
|
||||
// / \ \/\_ __ \_/ __ \\__ \\ __\/ __ \
|
||||
// \ \____| | \/\ ___/ / __ \| | \ ___/
|
||||
// \______ /|__| \___ >____ /__| \___ >
|
||||
// \/ \/ \/ \/
|
||||
|
||||
// CreatePayload FIXME
|
||||
// CreatePayload represents a payload information of create event.
|
||||
type CreatePayload struct {
|
||||
Sha string `json:"sha"`
|
||||
Ref string `json:"ref"`
|
||||
@ -157,13 +150,6 @@ func ParseCreateHook(raw []byte) (*CreatePayload, error) {
|
||||
return hook, nil
|
||||
}
|
||||
|
||||
// ________ .__ __
|
||||
// \______ \ ____ | | _____/ |_ ____
|
||||
// | | \_/ __ \| | _/ __ \ __\/ __ \
|
||||
// | ` \ ___/| |_\ ___/| | \ ___/
|
||||
// /_______ /\___ >____/\___ >__| \___ >
|
||||
// \/ \/ \/ \/
|
||||
|
||||
// PusherType define the type to push
|
||||
type PusherType string
|
||||
|
||||
@ -186,13 +172,6 @@ func (p *DeletePayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
// ___________ __
|
||||
// \_ _____/__________| | __
|
||||
// | __)/ _ \_ __ \ |/ /
|
||||
// | \( <_> ) | \/ <
|
||||
// \___ / \____/|__| |__|_ \
|
||||
// \/ \/
|
||||
|
||||
// ForkPayload represents fork payload
|
||||
type ForkPayload struct {
|
||||
Forkee *Repository `json:"forkee"`
|
||||
@ -232,13 +211,6 @@ func (p *IssueCommentPayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \ ____ | | ____ _____ ______ ____
|
||||
// | _// __ \| | _/ __ \\__ \ / ___// __ \
|
||||
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
|
||||
// |____|_ /\___ >____/\___ >____ /____ >\___ >
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
// HookReleaseAction defines hook release action type
|
||||
type HookReleaseAction string
|
||||
|
||||
@ -302,13 +274,6 @@ func (p *PushPayload) Branch() string {
|
||||
return strings.ReplaceAll(p.Ref, "refs/heads/", "")
|
||||
}
|
||||
|
||||
// .___
|
||||
// | | ______ ________ __ ____
|
||||
// | |/ ___// ___/ | \_/ __ \
|
||||
// | |\___ \ \___ \| | /\ ___/
|
||||
// |___/____ >____ >____/ \___ >
|
||||
// \/ \/ \/
|
||||
|
||||
// HookIssueAction FIXME
|
||||
type HookIssueAction string
|
||||
|
||||
@ -371,13 +336,6 @@ type ChangesPayload struct {
|
||||
Ref *ChangesFromPayload `json:"ref,omitempty"`
|
||||
}
|
||||
|
||||
// __________ .__ .__ __________ __
|
||||
// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_
|
||||
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
|
||||
// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | |
|
||||
// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__|
|
||||
// \/ \/ |__| \/ \/
|
||||
|
||||
// PullRequestPayload represents a payload information of pull request event.
|
||||
type PullRequestPayload struct {
|
||||
Action HookIssueAction `json:"action"`
|
||||
@ -402,13 +360,6 @@ type ReviewPayload struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// __ __.__ __ .__
|
||||
// / \ / \__| | _|__|
|
||||
// \ \/\/ / | |/ / |
|
||||
// \ /| | <| |
|
||||
// \__/\ / |__|__|_ \__|
|
||||
// \/ \/
|
||||
|
||||
// HookWikiAction an action that happens to a wiki page
|
||||
type HookWikiAction string
|
||||
|
||||
@ -435,13 +386,6 @@ func (p *WikiPayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
//__________ .__ __
|
||||
//\______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
|
||||
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
|
||||
// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
|
||||
// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
|
||||
// \/ \/|__| \/ \/
|
||||
|
||||
// HookRepoAction an action that happens to a repo
|
||||
type HookRepoAction string
|
||||
|
||||
@ -480,7 +424,7 @@ type PackagePayload struct {
|
||||
Action HookPackageAction `json:"action"`
|
||||
Repository *Repository `json:"repository"`
|
||||
Package *Package `json:"package"`
|
||||
Organization *User `json:"organization"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Sender *User `json:"sender"`
|
||||
}
|
||||
|
||||
|
@ -1345,6 +1345,8 @@ editor.new_branch_name_desc = New branch name…
|
||||
editor.cancel = Cancel
|
||||
editor.filename_cannot_be_empty = The filename cannot be empty.
|
||||
editor.filename_is_invalid = The filename is invalid: "%s".
|
||||
editor.commit_email = Commit email
|
||||
editor.invalid_commit_email = The email for the commit is invalid.
|
||||
editor.branch_does_not_exist = Branch "%s" does not exist in this repository.
|
||||
editor.branch_already_exists = Branch "%s" already exists in this repository.
|
||||
editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository.
|
||||
|
@ -1499,7 +1499,7 @@ issues.remove_labels=a supprimé les labels %s %s.
|
||||
issues.add_remove_labels=a ajouté le label %s et supprimé %s %s.
|
||||
issues.add_milestone_at=`a ajouté ça au jalon <b>%s</b> %s.`
|
||||
issues.add_project_at=`a ajouté ça au projet <b>%s</b> %s.`
|
||||
issues.move_to_column_of_project=`a déplacé ça vers %s dans %s sur %s`
|
||||
issues.move_to_column_of_project=`a déplacé ça vers %s dans %s %s.`
|
||||
issues.change_milestone_at=`a remplacé le jalon <b><strike>%s</strike></b> par <b>%s</b> %s.`
|
||||
issues.change_project_at=`a remplacé le projet <b><strike>%s</strike></b> par <b>%s</b> %s.`
|
||||
issues.remove_milestone_at=`a supprimé ça du jalon <b>%s</b> %s.`
|
||||
|
@ -1646,7 +1646,7 @@ issues.label.filter_sort.by_size=Méid is lú
|
||||
issues.label.filter_sort.reverse_by_size=Méid is mó
|
||||
issues.num_participants=%d Rannpháirtithe
|
||||
issues.attachment.open_tab=`Cliceáil chun "%s" a fheiceáil i gcluaisín nua`
|
||||
issues.attachment.download=Cliceáil chun "%s" a íoslódáil
|
||||
issues.attachment.download=`Cliceáil chun "%s" a íoslódáil`
|
||||
issues.subscribe=Liostáil
|
||||
issues.unsubscribe=Díliostáil
|
||||
issues.unpin=Díphoráil
|
||||
|
@ -1135,9 +1135,9 @@ file_view_raw=Ver original
|
||||
file_permalink=Link permanente
|
||||
file_too_large=O arquivo é muito grande para ser mostrado.
|
||||
invisible_runes_header=`Este arquivo contém caracteres Unicode invisíveis`
|
||||
invisible_runes_description=Este arquivo contém caracteres Unicode invisíveis que são indistinguíveis para humanos, mas que podem ser processados de forma diferente por um computador. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los
|
||||
invisible_runes_description=`Este arquivo contém caracteres Unicode invisíveis que são indistinguíveis para humanos, mas que podem ser processados de forma diferente por um computador. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los`
|
||||
ambiguous_runes_header=`Este arquivo contém caracteres Unicode ambíguos`
|
||||
ambiguous_runes_description=Este arquivo contém caracteres Unicode que podem ser confundidos com outros caracteres. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los
|
||||
ambiguous_runes_description=`Este arquivo contém caracteres Unicode que podem ser confundidos com outros caracteres. Se você acha que isso é intencional, pode ignorar esse aviso com segurança. Use o botão Escapar para revelá-los`
|
||||
invisible_runes_line=`Esta linha tem caracteres unicode invisíveis`
|
||||
ambiguous_runes_line=`Esta linha tem caracteres unicode ambíguos`
|
||||
|
||||
|
63
package-lock.json
generated
63
package-lock.json
generated
@ -11,7 +11,7 @@
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.5",
|
||||
"@github/text-expander-element": "2.8.0",
|
||||
"@github/text-expander-element": "2.9.0",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.14.0",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||
@ -103,9 +103,11 @@
|
||||
"markdownlint-cli": "0.43.0",
|
||||
"nolyfill": "1.0.43",
|
||||
"postcss-html": "1.8.0",
|
||||
"stylelint": "16.13.2",
|
||||
"stylelint": "16.14.1",
|
||||
"stylelint-config-recommended": "15.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.7",
|
||||
"stylelint-define-config": "16.14.0",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.3.2",
|
||||
"type-fest": "4.33.0",
|
||||
@ -2848,9 +2850,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@github/text-expander-element": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.8.0.tgz",
|
||||
"integrity": "sha512-kkS2rZ/CG8HGKblpLDQ8vcK/K7l/Jsvzi/N4ovwPAsFSOImcIbJh2MgCv9tzqE3wAm/qXlscvh3Ms4Hh1vtZvw==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.9.0.tgz",
|
||||
"integrity": "sha512-NjoFiQ/3955XyefrkmtUpZvrgDl0MGyncv2QJBrUZ1+oOFOu+UmCR/ybkcuTgNg0O6AGcl8rUEXStUfrRPUCVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@github/combobox-nav": "^2.0.2",
|
||||
@ -13143,9 +13145,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/stylelint": {
|
||||
"version": "16.13.2",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.13.2.tgz",
|
||||
"integrity": "sha512-wDlgh0mRO9RtSa3TdidqHd0nOG8MmUyVKl+dxA6C1j8aZRzpNeEgdhFmU5y4sZx4Fc6r46p0fI7p1vR5O2DZqA==",
|
||||
"version": "16.14.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.14.1.tgz",
|
||||
"integrity": "sha512-oqCL7AC3786oTax35T/nuLL8p2C3k/8rHKAooezrPGRvUX0wX+qqs5kMWh5YYT4PHQgVDobHT4tw55WgpYG6Sw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -13177,7 +13179,7 @@
|
||||
"globby": "^11.1.0",
|
||||
"globjoin": "^0.1.4",
|
||||
"html-tags": "^3.3.1",
|
||||
"ignore": "^7.0.1",
|
||||
"ignore": "^7.0.3",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"known-css-properties": "^0.35.0",
|
||||
@ -13186,7 +13188,7 @@
|
||||
"micromatch": "^4.0.8",
|
||||
"normalize-path": "^3.0.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-resolve-nested-selector": "^0.1.6",
|
||||
"postcss-safe-parser": "^7.0.1",
|
||||
"postcss-selector-parser": "^7.0.0",
|
||||
@ -13205,6 +13207,29 @@
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-recommended": {
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-15.0.0.tgz",
|
||||
"integrity": "sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/stylelint"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/stylelint"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": "^16.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-declaration-block-no-ignored-properties": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-declaration-block-no-ignored-properties/-/stylelint-declaration-block-no-ignored-properties-2.8.0.tgz",
|
||||
@ -13231,6 +13256,24 @@
|
||||
"stylelint": ">=7 <=16"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-define-config": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.14.0.tgz",
|
||||
"integrity": "sha512-5R7/Vv6awCkNaPcedo1GuUp+7YTFvDnexogO4l/C0i349pBDYbefN6XzsDGsGOhU++maQSh2fp3mWNO0F16IjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-value-no-unknown-custom-properties": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz",
|
||||
|
@ -10,7 +10,7 @@
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.5",
|
||||
"@github/text-expander-element": "2.8.0",
|
||||
"@github/text-expander-element": "2.9.0",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.14.0",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||
@ -102,9 +102,11 @@
|
||||
"markdownlint-cli": "0.43.0",
|
||||
"nolyfill": "1.0.43",
|
||||
"postcss-html": "1.8.0",
|
||||
"stylelint": "16.13.2",
|
||||
"stylelint": "16.14.1",
|
||||
"stylelint-config-recommended": "15.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.7",
|
||||
"stylelint-define-config": "16.14.0",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.3.2",
|
||||
"type-fest": "4.33.0",
|
||||
|
21
poetry.lock
generated
21
poetry.lock
generated
@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
@ -6,6 +6,7 @@ version = "8.1.8"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
|
||||
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
|
||||
@ -20,6 +21,7 @@ version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
@ -31,6 +33,7 @@ version = "1.15.1"
|
||||
description = "CSS unobfuscator and beautifier."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "cssbeautifier-1.15.1.tar.gz", hash = "sha256:9f7064362aedd559c55eeecf6b6bed65e05f33488dcbe39044f0403c26e1c006"},
|
||||
]
|
||||
@ -46,6 +49,7 @@ version = "1.36.4"
|
||||
description = "HTML Template Linter and Formatter"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "djlint-1.36.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2dfb60883ceb92465201bfd392291a7597c6752baede6fbb6f1980cac8d6c5c"},
|
||||
{file = "djlint-1.36.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc6a1320c0030244b530ac200642f883d3daa451a115920ef3d56d08b644292"},
|
||||
@ -90,6 +94,7 @@ version = "0.17.0"
|
||||
description = "EditorConfig File Locator and Interpreter for Python"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "EditorConfig-0.17.0-py3-none-any.whl", hash = "sha256:fe491719c5f65959ec00b167d07740e7ffec9a3f362038c72b289330b9991dfc"},
|
||||
{file = "editorconfig-0.17.0.tar.gz", hash = "sha256:8739052279699840065d3a9f5c125d7d5a98daeefe53b0e5274261d77cb49aa2"},
|
||||
@ -101,6 +106,7 @@ version = "1.15.1"
|
||||
description = "JavaScript unobfuscator and beautifier."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "jsbeautifier-1.15.1.tar.gz", hash = "sha256:ebd733b560704c602d744eafc839db60a1ee9326e30a2a80c4adb8718adc1b24"},
|
||||
]
|
||||
@ -115,6 +121,7 @@ version = "0.10.0"
|
||||
description = "A Python implementation of the JSON5 data format."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"},
|
||||
{file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"},
|
||||
@ -129,6 +136,7 @@ version = "0.12.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
@ -140,6 +148,7 @@ version = "6.0.2"
|
||||
description = "YAML parser and emitter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||
@ -202,6 +211,7 @@ version = "2024.11.6"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
|
||||
@ -305,6 +315,7 @@ version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
@ -316,6 +327,8 @@ version = "2.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
@ -357,6 +370,7 @@ version = "4.67.1"
|
||||
description = "Fast, Extensible Progress Meter"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
|
||||
{file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
|
||||
@ -378,6 +392,8 @@ version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
@ -389,6 +405,7 @@ version = "1.35.1"
|
||||
description = "A linter for YAML files."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "yamllint-1.35.1-py3-none-any.whl", hash = "sha256:2e16e504bb129ff515b37823b472750b36b6de07963bd74b307341ef5ad8bdc3"},
|
||||
{file = "yamllint-1.35.1.tar.gz", hash = "sha256:7a003809f88324fd2c877734f2d575ee7881dd9043360657cc8049c809eba6cd"},
|
||||
@ -402,6 +419,6 @@ pyyaml = "*"
|
||||
dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "f2e8260efe6e25f77ef387daff9551e41d25027e4794b42bc7a851ed0dfafd85"
|
||||
|
@ -1,4 +1,3 @@
|
||||
[virtualenvs]
|
||||
in-project = true
|
||||
options.no-pip = true
|
||||
options.no-setuptools = true
|
||||
|
@ -489,12 +489,12 @@ func ChangeFiles(ctx *context.APIContext) {
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
GitUserName: apiOpts.Committer.Name,
|
||||
GitUserEmail: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
GitUserName: apiOpts.Author.Name,
|
||||
GitUserEmail: apiOpts.Author.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: apiOpts.Dates.Author,
|
||||
@ -586,12 +586,12 @@ func CreateFile(ctx *context.APIContext) {
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
GitUserName: apiOpts.Committer.Name,
|
||||
GitUserEmail: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
GitUserName: apiOpts.Author.Name,
|
||||
GitUserEmail: apiOpts.Author.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: apiOpts.Dates.Author,
|
||||
@ -689,12 +689,12 @@ func UpdateFile(ctx *context.APIContext) {
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
GitUserName: apiOpts.Committer.Name,
|
||||
GitUserEmail: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
GitUserName: apiOpts.Author.Name,
|
||||
GitUserEmail: apiOpts.Author.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: apiOpts.Dates.Author,
|
||||
@ -848,12 +848,12 @@ func DeleteFile(ctx *context.APIContext) {
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
GitUserName: apiOpts.Committer.Name,
|
||||
GitUserEmail: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
GitUserName: apiOpts.Author.Name,
|
||||
GitUserEmail: apiOpts.Author.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: apiOpts.Dates.Author,
|
||||
|
@ -132,13 +132,15 @@ func CreateFork(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
|
||||
return
|
||||
} else if !isMember {
|
||||
ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
|
||||
return
|
||||
if !ctx.Doer.IsAdmin {
|
||||
isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
|
||||
return
|
||||
} else if !isMember {
|
||||
ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
forker = org.AsUser()
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ func ApplyDiffPatch(ctx *context.APIContext) {
|
||||
OldBranch: apiOpts.BranchName,
|
||||
NewBranch: apiOpts.NewBranchName,
|
||||
Committer: &files.IdentityOptions{
|
||||
Name: apiOpts.Committer.Name,
|
||||
Email: apiOpts.Committer.Email,
|
||||
GitUserName: apiOpts.Committer.Name,
|
||||
GitUserEmail: apiOpts.Committer.Email,
|
||||
},
|
||||
Author: &files.IdentityOptions{
|
||||
Name: apiOpts.Author.Name,
|
||||
Email: apiOpts.Author.Email,
|
||||
GitUserName: apiOpts.Author.Name,
|
||||
GitUserEmail: apiOpts.Author.Email,
|
||||
},
|
||||
Dates: &files.CommitDateOptions{
|
||||
Author: apiOpts.Dates.Author,
|
||||
|
@ -204,6 +204,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
||||
webhook_module.HookEventWiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
|
||||
webhook_module.HookEventRepository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
|
||||
webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
||||
webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
|
||||
webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
|
||||
},
|
||||
BranchFilter: form.BranchFilter,
|
||||
|
@ -43,6 +43,7 @@ func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType stri
|
||||
},
|
||||
Description: commit.Message(),
|
||||
Content: commit.Message(),
|
||||
Created: commit.Committer.When,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
|
||||
},
|
||||
Description: commit.Message(),
|
||||
Content: commit.Message(),
|
||||
Created: commit.Committer.When,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
@ -102,10 +103,32 @@ func getParentTreeFields(treePath string) (treeNames, treePaths []string) {
|
||||
return treeNames, treePaths
|
||||
}
|
||||
|
||||
func editFile(ctx *context.Context, isNewFile bool) {
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
func getCandidateEmailAddresses(ctx *context.Context) []string {
|
||||
emails, err := user_model.GetActivatedEmailAddresses(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
log.Error("getCandidateEmailAddresses: GetActivatedEmailAddresses: %v", err)
|
||||
}
|
||||
|
||||
if ctx.Doer.KeepEmailPrivate {
|
||||
emails = append([]string{ctx.Doer.GetPlaceholderEmail()}, emails...)
|
||||
}
|
||||
return emails
|
||||
}
|
||||
|
||||
func editFileCommon(ctx *context.Context, isNewFile bool) {
|
||||
ctx.Data["PageIsEdit"] = true
|
||||
ctx.Data["IsNewFile"] = isNewFile
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
|
||||
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
|
||||
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != ""
|
||||
ctx.Data["ReturnURI"] = ctx.FormString("return_uri")
|
||||
ctx.Data["CommitCandidateEmails"] = getCandidateEmailAddresses(ctx)
|
||||
ctx.Data["CommitDefaultEmail"] = ctx.Doer.GetEmail()
|
||||
}
|
||||
|
||||
func editFile(ctx *context.Context, isNewFile bool) {
|
||||
editFileCommon(ctx, isNewFile)
|
||||
canCommit := renderCommitRights(ctx)
|
||||
|
||||
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
||||
@ -174,28 +197,19 @@ func editFile(ctx *context.Context, isNewFile bool) {
|
||||
ctx.Data["FileContent"] = content
|
||||
}
|
||||
} else {
|
||||
// Append filename from query, or empty string to allow user name the new file.
|
||||
// Append filename from query, or empty string to allow username the new file.
|
||||
treeNames = append(treeNames, fileName)
|
||||
}
|
||||
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["TreePaths"] = treePaths
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
|
||||
ctx.Data["commit_summary"] = ""
|
||||
ctx.Data["commit_message"] = ""
|
||||
if canCommit {
|
||||
ctx.Data["commit_choice"] = frmCommitChoiceDirect
|
||||
} else {
|
||||
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
||||
}
|
||||
ctx.Data["commit_choice"] = util.Iif(canCommit, frmCommitChoiceDirect, frmCommitChoiceNewBranch)
|
||||
ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
|
||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
||||
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
|
||||
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath)
|
||||
|
||||
ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != ""
|
||||
ctx.Data["ReturnURI"] = ctx.FormString("return_uri")
|
||||
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, treePath)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplEditFile)
|
||||
}
|
||||
@ -224,6 +238,9 @@ func NewFile(ctx *context.Context) {
|
||||
}
|
||||
|
||||
func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile bool) {
|
||||
editFileCommon(ctx, isNewFile)
|
||||
ctx.Data["PageHasPosted"] = true
|
||||
|
||||
canCommit := renderCommitRights(ctx)
|
||||
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
||||
branchName := ctx.Repo.BranchName
|
||||
@ -231,21 +248,15 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||
branchName = form.NewBranchName
|
||||
}
|
||||
|
||||
ctx.Data["PageIsEdit"] = true
|
||||
ctx.Data["PageHasPosted"] = true
|
||||
ctx.Data["IsNewFile"] = isNewFile
|
||||
ctx.Data["TreePath"] = form.TreePath
|
||||
ctx.Data["TreeNames"] = treeNames
|
||||
ctx.Data["TreePaths"] = treePaths
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
|
||||
ctx.Data["FileContent"] = form.Content
|
||||
ctx.Data["commit_summary"] = form.CommitSummary
|
||||
ctx.Data["commit_message"] = form.CommitMessage
|
||||
ctx.Data["commit_choice"] = form.CommitChoice
|
||||
ctx.Data["new_branch_name"] = form.NewBranchName
|
||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
||||
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
|
||||
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
ctx.Data["EditorconfigJson"] = GetEditorConfig(ctx, form.TreePath)
|
||||
|
||||
if ctx.HasError() {
|
||||
@ -253,7 +264,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||
return
|
||||
}
|
||||
|
||||
// Cannot commit to a an existing branch if user doesn't have rights
|
||||
// Cannot commit to an existing branch if user doesn't have rights
|
||||
if branchName == ctx.Repo.BranchName && !canCommit {
|
||||
ctx.Data["Err_NewBranchName"] = true
|
||||
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
|
||||
@ -276,6 +287,17 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||
message += "\n\n" + form.CommitMessage
|
||||
}
|
||||
|
||||
gitCommitter := &files_service.IdentityOptions{}
|
||||
if form.CommitEmail != "" {
|
||||
if util.SliceContainsString(getCandidateEmailAddresses(ctx), form.CommitEmail, true) {
|
||||
gitCommitter.GitUserEmail = form.CommitEmail
|
||||
} else {
|
||||
ctx.Data["Err_CommitEmail"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_email"), tplEditFile, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
operation := "update"
|
||||
if isNewFile {
|
||||
operation = "create"
|
||||
@ -294,7 +316,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||
ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")),
|
||||
},
|
||||
},
|
||||
Signoff: form.Signoff,
|
||||
Signoff: form.Signoff,
|
||||
Author: gitCommitter,
|
||||
Committer: gitCommitter,
|
||||
}); err != nil {
|
||||
// This is where we handle all the errors thrown by files_service.ChangeRepoFiles
|
||||
if git.IsErrNotExist(err) {
|
||||
|
@ -412,3 +412,9 @@ func Home(ctx *context.Context) {
|
||||
|
||||
ctx.HTML(http.StatusOK, tplRepoHome)
|
||||
}
|
||||
|
||||
// HomeRedirect redirects from /tree/* to /src/* in order to maintain a similar URL structure.
|
||||
func HomeRedirect(ctx *context.Context) {
|
||||
remainder := ctx.PathParam("*")
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + util.PathEscapeSegments(remainder))
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/avatars"
|
||||
@ -21,27 +20,18 @@ func cacheableRedirect(ctx *context.Context, location string) {
|
||||
ctx.Redirect(location)
|
||||
}
|
||||
|
||||
// AvatarByUserName redirect browser to user avatar of requested size
|
||||
func AvatarByUserName(ctx *context.Context) {
|
||||
userName := ctx.PathParam("username")
|
||||
size := int(ctx.PathParamInt64("size"))
|
||||
|
||||
var user *user_model.User
|
||||
if strings.ToLower(userName) != user_model.GhostUserLowerName {
|
||||
// AvatarByUsernameSize redirect browser to user avatar of requested size
|
||||
func AvatarByUsernameSize(ctx *context.Context) {
|
||||
username := ctx.PathParam("username")
|
||||
user := user_model.GetSystemUserByName(username)
|
||||
if user == nil {
|
||||
var err error
|
||||
if user, err = user_model.GetUserByName(ctx, userName); err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.NotFound("GetUserByName", err)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("Invalid user: "+userName, err)
|
||||
if user, err = user_model.GetUserByName(ctx, username); err != nil {
|
||||
ctx.NotFoundOrServerError("GetUserByName", user_model.IsErrUserNotExist, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
user = user_model.NewGhostUser()
|
||||
}
|
||||
|
||||
cacheableRedirect(ctx, user.AvatarLinkWithSize(ctx, size))
|
||||
cacheableRedirect(ctx, user.AvatarLinkWithSize(ctx, int(ctx.PathParamInt64("size"))))
|
||||
}
|
||||
|
||||
// AvatarByEmailHash redirects the browser to the email avatar link
|
||||
|
@ -732,7 +732,7 @@ func UsernameSubRoute(ctx *context.Context) {
|
||||
switch {
|
||||
case strings.HasSuffix(username, ".png"):
|
||||
if reloadParam(".png") {
|
||||
AvatarByUserName(ctx)
|
||||
AvatarByUsernameSize(ctx)
|
||||
}
|
||||
case strings.HasSuffix(username, ".keys"):
|
||||
if reloadParam(".keys") {
|
||||
|
@ -681,7 +681,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("/activate", auth.Activate)
|
||||
m.Post("/activate", auth.ActivatePost)
|
||||
m.Any("/activate_email", auth.ActivateEmail)
|
||||
m.Get("/avatar/{username}/{size}", user.AvatarByUserName)
|
||||
m.Get("/avatar/{username}/{size}", user.AvatarByUsernameSize)
|
||||
m.Get("/recover_account", auth.ResetPasswd)
|
||||
m.Post("/recover_account", auth.ResetPasswdPost)
|
||||
m.Get("/forgot_password", auth.ForgotPasswd)
|
||||
@ -1584,6 +1584,13 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("/*", context.RepoRefByType(""), repo.Home) // "/*" route is deprecated, and kept for backward compatibility
|
||||
}, repo.SetEditorconfigIfExists)
|
||||
|
||||
// Add a /tree/* path to redirect to the /src/* path, which
|
||||
// will redirect to the canonical URL for that ref. This is
|
||||
// included so that Gitea's repo URL structure matches what
|
||||
// other forges provide, allowing clients to construct URLs
|
||||
// that work across forges.
|
||||
m.Get("/tree/*", repo.HomeRedirect)
|
||||
|
||||
m.Get("/forks", context.RepoRef(), repo.Forks)
|
||||
m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff)
|
||||
m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit)
|
||||
|
@ -117,7 +117,7 @@ func (input *notifyInput) Notify(ctx context.Context) {
|
||||
|
||||
func notify(ctx context.Context, input *notifyInput) error {
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||
if input.Doer.IsActions() {
|
||||
if input.Doer.IsGiteaActions() {
|
||||
// avoiding triggering cyclically, for example:
|
||||
// a comment of an issue will trigger the runner to add a new comment as reply,
|
||||
// and the new comment will trigger the runner again.
|
||||
|
@ -721,6 +721,7 @@ type EditRepoFileForm struct {
|
||||
NewBranchName string `binding:"GitRefName;MaxSize(100)"`
|
||||
LastCommit string
|
||||
Signoff bool
|
||||
CommitEmail string
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
|
@ -230,7 +230,7 @@ func TestSubmoduleInfo(t *testing.T) {
|
||||
assert.EqualValues(t, "name", sdi.SubmoduleRepoLinkHTML(ctx))
|
||||
|
||||
sdi.SubmoduleFile = git.NewCommitSubmoduleFile("https://github.com/owner/repo", "1234")
|
||||
assert.EqualValues(t, `<a href="https://github.com/owner/repo/commit/1111">1111</a>`, sdi.CommitRefIDLinkHTML(ctx, "1111"))
|
||||
assert.EqualValues(t, `<a href="https://github.com/owner/repo/tree/1111">1111</a>`, sdi.CommitRefIDLinkHTML(ctx, "1111"))
|
||||
assert.EqualValues(t, `<a href="https://github.com/owner/repo/compare/aaaa...bbbb">aaaa...bbbb</a>`, sdi.CompareRefIDLinkHTML(ctx))
|
||||
assert.EqualValues(t, `<a href="https://github.com/owner/repo">name</a>`, sdi.SubmoduleRepoLinkHTML(ctx))
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo
|
||||
}
|
||||
visited.AddMultiple(ids...)
|
||||
|
||||
unfilteredUsers, err := user_model.GetMaileableUsersByIDs(ctx, unfiltered, false)
|
||||
unfilteredUsers, err := user_model.GetMailableUsersByIDs(ctx, unfiltered, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -35,9 +35,9 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) {
|
||||
return
|
||||
}
|
||||
|
||||
recipients, err := user_model.GetMaileableUsersByIDs(ctx, watcherIDList, false)
|
||||
recipients, err := user_model.GetMailableUsersByIDs(ctx, watcherIDList, false)
|
||||
if err != nil {
|
||||
log.Error("user_model.GetMaileableUsersByIDs: %v", err)
|
||||
log.Error("user_model.GetMailableUsersByIDs: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
@ -296,8 +295,13 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
commitHash, err := t.CommitTreeWithDate(lastCommitID, doer, doer, treeHash, commitMessage, false, now, now)
|
||||
commitOpts := &files_service.CommitTreeUserOptions{
|
||||
ParentCommitID: lastCommitID,
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: commitMessage,
|
||||
DoerUser: doer,
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -32,15 +32,13 @@ func (err ErrCommitIDDoesNotMatch) Error() string {
|
||||
return fmt.Sprintf("file CommitID does not match [given: %s, expected: %s]", err.GivenCommitID, err.CurrentCommitID)
|
||||
}
|
||||
|
||||
// CherryPick cherrypicks or reverts a commit to the given repository
|
||||
// CherryPick cherry-picks or reverts a commit to the given repository
|
||||
func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) {
|
||||
if err := opts.Validate(ctx, repo, doer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("NewTemporaryUploadRepository failed: %v", err)
|
||||
@ -112,12 +110,21 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
|
||||
}
|
||||
|
||||
// Now commit the tree
|
||||
var commitHash string
|
||||
if opts.Dates != nil {
|
||||
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
|
||||
} else {
|
||||
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: "HEAD",
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
AuthorIdentity: opts.Author,
|
||||
AuthorTime: nil,
|
||||
CommitterIdentity: opts.Committer,
|
||||
CommitterTime: nil,
|
||||
}
|
||||
if opts.Dates != nil {
|
||||
commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -111,51 +110,6 @@ func GetFileCommitResponse(repo *repo_model.Repository, commit *git.Commit) (*ap
|
||||
return fileCommit, nil
|
||||
}
|
||||
|
||||
// GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
|
||||
func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_model.User) (authorUser, committerUser *user_model.User) {
|
||||
// Committer and author are optional. If they are not the doer (not same email address)
|
||||
// then we use bogus User objects for them to store their FullName and Email.
|
||||
// If only one of the two are provided, we set both of them to it.
|
||||
// If neither are provided, both are the doer.
|
||||
if committer != nil && committer.Email != "" {
|
||||
if doer != nil && strings.EqualFold(doer.Email, committer.Email) {
|
||||
committerUser = doer // the committer is the doer, so will use their user object
|
||||
if committer.Name != "" {
|
||||
committerUser.FullName = committer.Name
|
||||
}
|
||||
} else {
|
||||
committerUser = &user_model.User{
|
||||
FullName: committer.Name,
|
||||
Email: committer.Email,
|
||||
}
|
||||
}
|
||||
}
|
||||
if author != nil && author.Email != "" {
|
||||
if doer != nil && strings.EqualFold(doer.Email, author.Email) {
|
||||
authorUser = doer // the author is the doer, so will use their user object
|
||||
if authorUser.Name != "" {
|
||||
authorUser.FullName = author.Name
|
||||
}
|
||||
} else {
|
||||
authorUser = &user_model.User{
|
||||
FullName: author.Name,
|
||||
Email: author.Email,
|
||||
}
|
||||
}
|
||||
}
|
||||
if authorUser == nil {
|
||||
if committerUser != nil {
|
||||
authorUser = committerUser // No valid author was given so use the committer
|
||||
} else if doer != nil {
|
||||
authorUser = doer // No valid author was given and no valid committer so use the doer
|
||||
}
|
||||
}
|
||||
if committerUser == nil {
|
||||
committerUser = authorUser // No valid committer so use the author as the committer (was set to a valid user above)
|
||||
}
|
||||
return authorUser, committerUser
|
||||
}
|
||||
|
||||
// ErrFilenameInvalid represents a "FilenameInvalid" kind of error.
|
||||
type ErrFilenameInvalid struct {
|
||||
Path string
|
||||
|
@ -126,8 +126,6 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
||||
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("NewTemporaryUploadRepository failed: %v", err)
|
||||
@ -187,12 +185,21 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
|
||||
}
|
||||
|
||||
// Now commit the tree
|
||||
var commitHash string
|
||||
if opts.Dates != nil {
|
||||
commitHash, err = t.CommitTreeWithDate("HEAD", author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
|
||||
} else {
|
||||
commitHash, err = t.CommitTree("HEAD", author, committer, treeHash, message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: "HEAD",
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
AuthorIdentity: opts.Author,
|
||||
AuthorTime: nil,
|
||||
CommitterIdentity: opts.Committer,
|
||||
CommitterTime: nil,
|
||||
}
|
||||
if opts.Dates != nil {
|
||||
commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
)
|
||||
@ -225,15 +226,53 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
// CommitTree creates a commit from a given tree for the user with provided message
|
||||
func (t *TemporaryUploadRepository) CommitTree(parent string, author, committer *user_model.User, treeHash, message string, signoff bool) (string, error) {
|
||||
return t.CommitTreeWithDate(parent, author, committer, treeHash, message, signoff, time.Now(), time.Now())
|
||||
type CommitTreeUserOptions struct {
|
||||
ParentCommitID string
|
||||
TreeHash string
|
||||
CommitMessage string
|
||||
SignOff bool
|
||||
|
||||
DoerUser *user_model.User
|
||||
|
||||
AuthorIdentity *IdentityOptions // if nil, use doer
|
||||
AuthorTime *time.Time // if nil, use now
|
||||
CommitterIdentity *IdentityOptions
|
||||
CommitterTime *time.Time
|
||||
}
|
||||
|
||||
// CommitTreeWithDate creates a commit from a given tree for the user with provided message
|
||||
func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, committer *user_model.User, treeHash, message string, signoff bool, authorDate, committerDate time.Time) (string, error) {
|
||||
authorSig := author.NewGitSig()
|
||||
committerSig := committer.NewGitSig()
|
||||
func makeGitUserSignature(doer *user_model.User, identity, other *IdentityOptions) *git.Signature {
|
||||
gitSig := &git.Signature{}
|
||||
if identity != nil {
|
||||
gitSig.Name, gitSig.Email = identity.GitUserName, identity.GitUserEmail
|
||||
}
|
||||
if other != nil {
|
||||
gitSig.Name = util.IfZero(gitSig.Name, other.GitUserName)
|
||||
gitSig.Email = util.IfZero(gitSig.Email, other.GitUserEmail)
|
||||
}
|
||||
if gitSig.Name == "" {
|
||||
gitSig.Name = doer.GitName()
|
||||
}
|
||||
if gitSig.Email == "" {
|
||||
gitSig.Email = doer.GetEmail()
|
||||
}
|
||||
return gitSig
|
||||
}
|
||||
|
||||
// CommitTree creates a commit from a given tree for the user with provided message
|
||||
func (t *TemporaryUploadRepository) CommitTree(opts *CommitTreeUserOptions) (string, error) {
|
||||
authorSig := makeGitUserSignature(opts.DoerUser, opts.AuthorIdentity, opts.CommitterIdentity)
|
||||
committerSig := makeGitUserSignature(opts.DoerUser, opts.CommitterIdentity, opts.AuthorIdentity)
|
||||
|
||||
authorDate := opts.AuthorTime
|
||||
committerDate := opts.CommitterTime
|
||||
if authorDate == nil && committerDate == nil {
|
||||
authorDate = util.ToPointer(time.Now())
|
||||
committerDate = authorDate
|
||||
} else if authorDate == nil {
|
||||
authorDate = committerDate
|
||||
} else if committerDate == nil {
|
||||
committerDate = authorDate
|
||||
}
|
||||
|
||||
// Because this may call hooks we should pass in the environment
|
||||
env := append(os.Environ(),
|
||||
@ -244,21 +283,21 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co
|
||||
)
|
||||
|
||||
messageBytes := new(bytes.Buffer)
|
||||
_, _ = messageBytes.WriteString(message)
|
||||
_, _ = messageBytes.WriteString(opts.CommitMessage)
|
||||
_, _ = messageBytes.WriteString("\n")
|
||||
|
||||
cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(treeHash)
|
||||
if parent != "" {
|
||||
cmdCommitTree.AddOptionValues("-p", parent)
|
||||
cmdCommitTree := git.NewCommand(t.ctx, "commit-tree").AddDynamicArguments(opts.TreeHash)
|
||||
if opts.ParentCommitID != "" {
|
||||
cmdCommitTree.AddOptionValues("-p", opts.ParentCommitID)
|
||||
}
|
||||
|
||||
var sign bool
|
||||
var keyID string
|
||||
var signer *git.Signature
|
||||
if parent != "" {
|
||||
sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent)
|
||||
if opts.ParentCommitID != "" {
|
||||
sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), opts.DoerUser, t.basePath, opts.ParentCommitID)
|
||||
} else {
|
||||
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author)
|
||||
sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), opts.DoerUser)
|
||||
}
|
||||
if sign {
|
||||
cmdCommitTree.AddOptionFormat("-S%s", keyID)
|
||||
@ -279,7 +318,7 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co
|
||||
cmdCommitTree.AddArguments("--no-gpg-sign")
|
||||
}
|
||||
|
||||
if signoff {
|
||||
if opts.SignOff {
|
||||
// Signed-off-by
|
||||
_, _ = messageBytes.WriteString("\n")
|
||||
_, _ = messageBytes.WriteString("Signed-off-by: ")
|
||||
|
@ -27,8 +27,8 @@ import (
|
||||
|
||||
// IdentityOptions for a person's identity like an author or committer
|
||||
type IdentityOptions struct {
|
||||
Name string
|
||||
Email string
|
||||
GitUserName string // to match "git config user.name"
|
||||
GitUserEmail string // to match "git config user.email"
|
||||
}
|
||||
|
||||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||
@ -160,8 +160,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("NewTemporaryUploadRepository failed: %v", err)
|
||||
@ -262,12 +260,21 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
}
|
||||
|
||||
// Now commit the tree
|
||||
var commitHash string
|
||||
if opts.Dates != nil {
|
||||
commitHash, err = t.CommitTreeWithDate(opts.LastCommitID, author, committer, treeHash, message, opts.Signoff, opts.Dates.Author, opts.Dates.Committer)
|
||||
} else {
|
||||
commitHash, err = t.CommitTree(opts.LastCommitID, author, committer, treeHash, message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: opts.LastCommitID,
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
AuthorIdentity: opts.Author,
|
||||
AuthorTime: nil,
|
||||
CommitterIdentity: opts.Committer,
|
||||
CommitterTime: nil,
|
||||
}
|
||||
if opts.Dates != nil {
|
||||
commitOpts.AuthorTime, commitOpts.CommitterTime = &opts.Dates.Author, &opts.Dates.Committer
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -128,12 +128,15 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
|
||||
return err
|
||||
}
|
||||
|
||||
// make author and committer the doer
|
||||
author := doer
|
||||
committer := doer
|
||||
|
||||
// Now commit the tree
|
||||
commitHash, err := t.CommitTree(opts.LastCommitID, author, committer, treeHash, opts.Message, opts.Signoff)
|
||||
commitOpts := &CommitTreeUserOptions{
|
||||
ParentCommitID: opts.LastCommitID,
|
||||
TreeHash: treeHash,
|
||||
CommitMessage: opts.Message,
|
||||
SignOff: opts.Signoff,
|
||||
DoerUser: doer,
|
||||
}
|
||||
commitHash, err := t.CommitTree(commitOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -256,9 +256,11 @@ type findForksOptions struct {
|
||||
}
|
||||
|
||||
func (opts findForksOptions) ToConds() builder.Cond {
|
||||
return builder.Eq{"fork_id": opts.RepoID}.And(
|
||||
repo_model.AccessibleRepositoryCondition(opts.Doer, unit.TypeInvalid),
|
||||
)
|
||||
cond := builder.Eq{"fork_id": opts.RepoID}
|
||||
if opts.Doer != nil && opts.Doer.IsAdmin {
|
||||
return cond
|
||||
}
|
||||
return cond.And(repo_model.AccessibleRepositoryCondition(opts.Doer, unit.TypeInvalid))
|
||||
}
|
||||
|
||||
// FindForks returns all the forks of the repository
|
||||
|
@ -196,3 +196,7 @@ func newDingtalkRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_
|
||||
var pc payloadConvertor[DingtalkPayload] = dingtalkConvertor{}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.DINGTALK, newDingtalkRequest)
|
||||
}
|
||||
|
@ -283,6 +283,10 @@ func newDiscordRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_m
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.DISCORD, newDiscordRequest)
|
||||
}
|
||||
|
||||
func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) {
|
||||
switch event {
|
||||
case webhook_module.HookEventPullRequestReviewApproved:
|
||||
|
@ -176,3 +176,7 @@ func newFeishuRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mo
|
||||
var pc payloadConvertor[FeishuPayload] = feishuConvertor{}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.FEISHU, newFeishuRequest)
|
||||
}
|
||||
|
@ -319,8 +319,8 @@ func packageTestPayload() *api.PackagePayload {
|
||||
AvatarURL: "http://localhost:3000/user1/avatar",
|
||||
},
|
||||
Repository: nil,
|
||||
Organization: &api.User{
|
||||
UserName: "org1",
|
||||
Organization: &api.Organization{
|
||||
Name: "org1",
|
||||
AvatarURL: "http://localhost:3000/org1/avatar",
|
||||
},
|
||||
Package: &api.Package{
|
||||
|
@ -24,6 +24,10 @@ import (
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.MATRIX, newMatrixRequest)
|
||||
}
|
||||
|
||||
func newMatrixRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
|
||||
meta := &MatrixMeta{}
|
||||
if err := json.Unmarshal([]byte(w.Meta), meta); err != nil {
|
||||
|
@ -363,3 +363,7 @@ func newMSTeamsRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_m
|
||||
var pc payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.MSTEAMS, newMSTeamsRequest)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
@ -920,10 +921,16 @@ func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_mo
|
||||
return
|
||||
}
|
||||
|
||||
var org *api.Organization
|
||||
if pd.Owner.IsOrganization() {
|
||||
org = convert.ToOrganization(ctx, organization.OrgFromUser(pd.Owner))
|
||||
}
|
||||
|
||||
if err := PrepareWebhooks(ctx, source, webhook_module.HookEventPackage, &api.PackagePayload{
|
||||
Action: action,
|
||||
Package: apiPackage,
|
||||
Sender: convert.ToUser(ctx, sender, nil),
|
||||
Action: action,
|
||||
Package: apiPackage,
|
||||
Organization: org,
|
||||
Sender: convert.ToUser(ctx, sender, nil),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
|
@ -124,3 +124,7 @@ func newPackagistRequest(_ context.Context, w *webhook_model.Webhook, t *webhook
|
||||
}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.PACKAGIST, newPackagistRequest)
|
||||
}
|
||||
|
@ -301,6 +301,10 @@ func newSlackRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_mod
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.SLACK, newSlackRequest)
|
||||
}
|
||||
|
||||
var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`)
|
||||
|
||||
// IsValidSlackChannel validates a channel name conforms to what slack expects:
|
||||
|
@ -193,3 +193,7 @@ func newTelegramRequest(_ context.Context, w *webhook_model.Webhook, t *webhook_
|
||||
var pc payloadConvertor[TelegramPayload] = telegramConvertor{}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.TELEGRAM, newTelegramRequest)
|
||||
}
|
||||
|
@ -27,16 +27,12 @@ import (
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
var webhookRequesters = map[webhook_module.HookType]func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error){
|
||||
webhook_module.SLACK: newSlackRequest,
|
||||
webhook_module.DISCORD: newDiscordRequest,
|
||||
webhook_module.DINGTALK: newDingtalkRequest,
|
||||
webhook_module.TELEGRAM: newTelegramRequest,
|
||||
webhook_module.MSTEAMS: newMSTeamsRequest,
|
||||
webhook_module.FEISHU: newFeishuRequest,
|
||||
webhook_module.MATRIX: newMatrixRequest,
|
||||
webhook_module.WECHATWORK: newWechatworkRequest,
|
||||
webhook_module.PACKAGIST: newPackagistRequest,
|
||||
type Requester func(context.Context, *webhook_model.Webhook, *webhook_model.HookTask) (req *http.Request, body []byte, err error)
|
||||
|
||||
var webhookRequesters = map[webhook_module.HookType]Requester{}
|
||||
|
||||
func RegisterWebhookRequester(hookType webhook_module.HookType, requester Requester) {
|
||||
webhookRequesters[hookType] = requester
|
||||
}
|
||||
|
||||
// IsValidHookTaskType returns true if a webhook registered
|
||||
|
@ -185,3 +185,7 @@ func newWechatworkRequest(_ context.Context, w *webhook_model.Webhook, t *webhoo
|
||||
var pc payloadConvertor[WechatworkPayload] = wechatworkConvertor{}
|
||||
return newJSONRequest(pc, w, t, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterWebhookRequester(webhook_module.WECHATWORK, newWechatworkRequest)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
// @ts-check
|
||||
import {defineConfig} from 'stylelint-define-config';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
const cssVarFiles = [
|
||||
@ -6,8 +8,8 @@ const cssVarFiles = [
|
||||
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
|
||||
];
|
||||
|
||||
/** @type {import('stylelint').Config} */
|
||||
export default {
|
||||
export default defineConfig({
|
||||
extends: 'stylelint-config-recommended',
|
||||
plugins: [
|
||||
'stylelint-declaration-strict-value',
|
||||
'stylelint-declaration-block-no-ignored-properties',
|
||||
@ -67,7 +69,7 @@ export default {
|
||||
'@stylistic/function-comma-space-after': null,
|
||||
'@stylistic/function-comma-space-before': null,
|
||||
'@stylistic/function-max-empty-lines': 0,
|
||||
'@stylistic/function-parentheses-newline-inside': 'never-multi-line',
|
||||
'@stylistic/function-parentheses-newline-inside': null,
|
||||
'@stylistic/function-parentheses-space-inside': null,
|
||||
'@stylistic/function-whitespace-after': null,
|
||||
'@stylistic/indentation': 2,
|
||||
@ -114,134 +116,34 @@ export default {
|
||||
'@stylistic/value-list-comma-space-after': null,
|
||||
'@stylistic/value-list-comma-space-before': null,
|
||||
'@stylistic/value-list-max-empty-lines': 0,
|
||||
'alpha-value-notation': null,
|
||||
'annotation-no-unknown': true,
|
||||
'at-rule-allowed-list': null,
|
||||
'at-rule-disallowed-list': null,
|
||||
'at-rule-empty-line-before': null,
|
||||
'at-rule-no-unknown': [true, {ignoreAtRules: ['tailwind']}],
|
||||
'at-rule-no-vendor-prefix': true,
|
||||
'at-rule-property-required-list': null,
|
||||
'block-no-empty': true,
|
||||
'color-function-notation': null,
|
||||
'color-hex-alpha': null,
|
||||
'color-hex-length': null,
|
||||
'color-named': null,
|
||||
'color-no-hex': null,
|
||||
'color-no-invalid-hex': true,
|
||||
'comment-empty-line-before': null,
|
||||
'comment-no-empty': true,
|
||||
'comment-pattern': null,
|
||||
'comment-whitespace-inside': null,
|
||||
'comment-word-disallowed-list': null,
|
||||
'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}],
|
||||
'custom-media-pattern': null,
|
||||
'custom-property-empty-line-before': null,
|
||||
'custom-property-no-missing-var-function': true,
|
||||
'custom-property-pattern': null,
|
||||
'declaration-block-no-duplicate-custom-properties': true,
|
||||
'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}],
|
||||
'declaration-block-no-redundant-longhand-properties': [true, {ignoreShorthands: ['flex-flow', 'overflow']}],
|
||||
'declaration-block-no-shorthand-property-overrides': null,
|
||||
'declaration-block-single-line-max-declarations': null,
|
||||
'declaration-empty-line-before': null,
|
||||
'declaration-no-important': null,
|
||||
'declaration-property-max-values': null,
|
||||
'declaration-property-unit-allowed-list': null,
|
||||
'declaration-block-no-redundant-longhand-properties': [true, {ignoreShorthands: ['flex-flow', 'overflow', 'grid-template']}],
|
||||
// @ts-expect-error - https://github.com/stylelint-types/stylelint-define-config/issues/1
|
||||
'declaration-property-unit-disallowed-list': {'line-height': ['em']},
|
||||
'declaration-property-value-allowed-list': null,
|
||||
// @ts-expect-error - https://github.com/stylelint-types/stylelint-define-config/issues/1
|
||||
'declaration-property-value-disallowed-list': {'word-break': ['break-word']},
|
||||
'declaration-property-value-no-unknown': true,
|
||||
'font-family-name-quotes': 'always-where-recommended',
|
||||
'font-family-no-duplicate-names': true,
|
||||
'font-family-no-missing-generic-family-keyword': true,
|
||||
'font-weight-notation': null,
|
||||
'function-allowed-list': null,
|
||||
'function-calc-no-unspaced-operator': true,
|
||||
'function-disallowed-list': null,
|
||||
'function-linear-gradient-no-nonstandard-direction': true,
|
||||
'function-name-case': 'lower',
|
||||
'function-no-unknown': true,
|
||||
'function-url-no-scheme-relative': null,
|
||||
'function-url-quotes': 'always',
|
||||
'function-url-scheme-allowed-list': null,
|
||||
'function-url-scheme-disallowed-list': null,
|
||||
'hue-degree-notation': null,
|
||||
'import-notation': 'string',
|
||||
'keyframe-block-no-duplicate-selectors': true,
|
||||
'keyframe-declaration-no-important': true,
|
||||
'keyframe-selector-notation': null,
|
||||
'keyframes-name-pattern': null,
|
||||
'length-zero-no-unit': [true, {ignore: ['custom-properties']}, {ignoreFunctions: ['var']}],
|
||||
'max-nesting-depth': null,
|
||||
'media-feature-name-allowed-list': null,
|
||||
'media-feature-name-disallowed-list': null,
|
||||
'media-feature-name-no-unknown': true,
|
||||
'length-zero-no-unit': [true, {ignore: ['custom-properties'], ignoreFunctions: ['var']}],
|
||||
'media-feature-name-no-vendor-prefix': true,
|
||||
'media-feature-name-unit-allowed-list': null,
|
||||
'media-feature-name-value-allowed-list': null,
|
||||
'media-feature-name-value-no-unknown': true,
|
||||
'media-feature-range-notation': null,
|
||||
'media-query-no-invalid': true,
|
||||
'named-grid-areas-no-invalid': true,
|
||||
'no-descending-specificity': null,
|
||||
'no-duplicate-at-import-rules': true,
|
||||
'no-duplicate-selectors': true,
|
||||
'no-empty-source': true,
|
||||
'no-invalid-double-slash-comments': true,
|
||||
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
|
||||
'no-irregular-whitespace': true,
|
||||
'no-unknown-animations': null, // disabled until stylelint supports multi-file linting
|
||||
'no-unknown-custom-media': null, // disabled until stylelint supports multi-file linting
|
||||
'no-unknown-custom-properties': null, // disabled until stylelint supports multi-file linting
|
||||
'number-max-precision': null,
|
||||
'plugin/declaration-block-no-ignored-properties': true,
|
||||
'property-allowed-list': null,
|
||||
'property-disallowed-list': null,
|
||||
'property-no-unknown': true,
|
||||
'property-no-vendor-prefix': null,
|
||||
'rule-empty-line-before': null,
|
||||
'rule-selector-property-disallowed-list': null,
|
||||
'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: false, disableFix: true, expandShorthand: true}],
|
||||
'selector-anb-no-unmatchable': true,
|
||||
'selector-attribute-name-disallowed-list': null,
|
||||
'selector-attribute-operator-allowed-list': null,
|
||||
'selector-attribute-operator-disallowed-list': null,
|
||||
'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: true, disableFix: true, expandShorthand: true}],
|
||||
'selector-attribute-quotes': 'always',
|
||||
'selector-class-pattern': null,
|
||||
'selector-combinator-allowed-list': null,
|
||||
'selector-combinator-disallowed-list': null,
|
||||
'selector-disallowed-list': null,
|
||||
'selector-id-pattern': null,
|
||||
'selector-max-attribute': null,
|
||||
'selector-max-class': null,
|
||||
'selector-max-combinators': null,
|
||||
'selector-max-compound-selectors': null,
|
||||
'selector-max-id': null,
|
||||
'selector-max-pseudo-class': null,
|
||||
'selector-max-specificity': null,
|
||||
'selector-max-type': null,
|
||||
'selector-max-universal': null,
|
||||
'selector-nested-pattern': null,
|
||||
'selector-no-qualifying-type': null,
|
||||
'selector-no-vendor-prefix': true,
|
||||
'selector-not-notation': null,
|
||||
'selector-pseudo-class-allowed-list': null,
|
||||
'selector-pseudo-class-disallowed-list': null,
|
||||
'selector-pseudo-class-no-unknown': true,
|
||||
'selector-pseudo-element-allowed-list': null,
|
||||
'selector-pseudo-element-colon-notation': 'double',
|
||||
'selector-pseudo-element-disallowed-list': null,
|
||||
'selector-pseudo-element-no-unknown': true,
|
||||
'selector-type-case': 'lower',
|
||||
'selector-type-no-unknown': [true, {ignore: ['custom-elements']}],
|
||||
'shorthand-property-no-redundant-values': true,
|
||||
'string-no-newline': true,
|
||||
'time-min-milliseconds': null,
|
||||
'unit-allowed-list': null,
|
||||
'unit-disallowed-list': null,
|
||||
'unit-no-unknown': true,
|
||||
'value-keyword-case': null,
|
||||
'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -66,6 +66,16 @@
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if and .CommitCandidateEmails (gt (len .CommitCandidateEmails) 1)}}
|
||||
<div class="field {{if .Err_CommitEmail}}error{{end}}">
|
||||
<label>{{ctx.Locale.Tr "repo.editor.commit_email"}}</label>
|
||||
<select class="ui selection dropdown" name="commit_email">
|
||||
{{- range $email := .CommitCandidateEmails -}}
|
||||
<option {{if eq $email $.CommitDefaultEmail}}selected{{end}} value="{{$email}}">{{$email}}</option>
|
||||
{{- end -}}
|
||||
</select>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<button id="commit-button" type="submit" class="ui primary button">
|
||||
{{if eq .commit_choice "commit-to-new-branch"}}{{ctx.Locale.Tr "repo.editor.propose_file_change"}}{{else}}{{ctx.Locale.Tr "repo.editor.commit_changes"}}{{end}}
|
||||
|
@ -81,12 +81,12 @@ func TestPullRequestTargetEvent(t *testing.T) {
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -109,12 +109,12 @@ func TestPullRequestTargetEvent(t *testing.T) {
|
||||
OldBranch: "main",
|
||||
NewBranch: "fork-branch-1",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
GitUserName: user4.Name,
|
||||
GitUserEmail: user4.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
GitUserName: user4.Name,
|
||||
GitUserEmail: user4.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -164,12 +164,12 @@ func TestPullRequestTargetEvent(t *testing.T) {
|
||||
OldBranch: "main",
|
||||
NewBranch: "fork-branch-2",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
GitUserName: user4.Name,
|
||||
GitUserEmail: user4.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user4.Name,
|
||||
Email: user4.Email,
|
||||
GitUserName: user4.Name,
|
||||
GitUserEmail: user4.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -237,12 +237,12 @@ func TestSkipCI(t *testing.T) {
|
||||
OldBranch: "master",
|
||||
NewBranch: "master",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -268,12 +268,12 @@ func TestSkipCI(t *testing.T) {
|
||||
OldBranch: "master",
|
||||
NewBranch: "master",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -299,12 +299,12 @@ func TestSkipCI(t *testing.T) {
|
||||
OldBranch: "master",
|
||||
NewBranch: "test-skip-ci",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -356,12 +356,12 @@ func TestCreateDeleteRefEvent(t *testing.T) {
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -470,12 +470,12 @@ func TestPullRequestCommitStatusEvent(t *testing.T) {
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -576,12 +576,12 @@ func TestPullRequestCommitStatusEvent(t *testing.T) {
|
||||
OldBranch: testBranch,
|
||||
NewBranch: testBranch,
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
GitUserName: user2.Name,
|
||||
GitUserEmail: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@ -81,8 +82,8 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
var forks []*api.Repository
|
||||
DecodeJSON(t, resp, &forks)
|
||||
|
||||
assert.Len(t, forks, 1)
|
||||
assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count"))
|
||||
assert.Len(t, forks, 2)
|
||||
assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count"))
|
||||
|
||||
assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1))
|
||||
|
||||
@ -96,3 +97,31 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPrivateReposForks(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user1Sess := loginUser(t, "user1")
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) // private repository
|
||||
privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23})
|
||||
user1Token := getTokenForLoggedInUser(t, user1Sess, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
forkedRepoName := "forked-repo"
|
||||
// create fork from a private repository
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+repo2.FullName()+"/forks", &api.CreateForkOption{
|
||||
Organization: &privateOrg.Name,
|
||||
Name: &forkedRepoName,
|
||||
}).AddTokenAuth(user1Token)
|
||||
MakeRequest(t, req, http.StatusAccepted)
|
||||
|
||||
// test get a private fork without clear permissions
|
||||
req = NewRequest(t, "GET", "/api/v1/repos/"+repo2.FullName()+"/forks").AddTokenAuth(user1Token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
forks := []*api.Repository{}
|
||||
DecodeJSON(t, resp, &forks)
|
||||
assert.Len(t, forks, 1)
|
||||
assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count"))
|
||||
assert.EqualValues(t, "forked-repo", forks[0].Name)
|
||||
assert.EqualValues(t, privateOrg.Name, forks[0].Owner.UserName)
|
||||
}
|
||||
|
@ -471,6 +471,15 @@ func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) {
|
||||
assert.Equal(t, "Repository is not a mirror", errRespJSON["message"])
|
||||
}
|
||||
|
||||
func testAPIOrgCreateRepo(t *testing.T, session *TestSession, orgName, repoName string, status int) {
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", orgName), &api.CreateRepoOption{
|
||||
Name: repoName,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, status)
|
||||
}
|
||||
|
||||
func TestAPIOrgRepoCreate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
ctxUserID int64
|
||||
@ -488,11 +497,7 @@ func TestAPIOrgRepoCreate(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{
|
||||
Name: testCase.repoName,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, testCase.expectedStatus)
|
||||
testAPIOrgCreateRepo(t, session, testCase.orgName, testCase.repoName, testCase.expectedStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,6 +172,19 @@ func TestAPIListWikiPages(t *testing.T) {
|
||||
assert.Equal(t, dummymeta, meta)
|
||||
}
|
||||
|
||||
func testAPICreateWikiPage(t *testing.T, session *TestSession, userName, repoName, title string, status int) {
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", userName, repoName)
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{
|
||||
Title: title,
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")),
|
||||
Message: "",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, status)
|
||||
}
|
||||
|
||||
func TestAPINewWikiPage(t *testing.T) {
|
||||
for _, title := range []string{
|
||||
"New page",
|
||||
@ -180,16 +193,7 @@ func TestAPINewWikiPage(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
username := "user2"
|
||||
session := loginUser(t, username)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", username, "repo1")
|
||||
|
||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{
|
||||
Title: title,
|
||||
ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")),
|
||||
Message: "",
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
testAPICreateWikiPage(t, session, username, "repo1", title, http.StatusCreated)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,16 @@ import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateFile(t *testing.T) {
|
||||
@ -173,3 +180,102 @@ func TestEditFileToNewBranch(t *testing.T) {
|
||||
testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEditFileCommitEmail(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
assert.True(t, user.KeepEmailPrivate)
|
||||
|
||||
session := loginUser(t, user.Name)
|
||||
link := "/user2/repo1/_edit/master/README.md"
|
||||
|
||||
getLastCommitID := func(t *testing.T) string {
|
||||
req := NewRequest(t, "GET", link)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
lastCommit := htmlDoc.GetInputValueByName("last_commit")
|
||||
require.NotEmpty(t, lastCommit)
|
||||
return lastCommit
|
||||
}
|
||||
|
||||
newReq := func(t *testing.T, session *TestSession, email, content string) *RequestWrapper {
|
||||
req := NewRequestWithValues(t, "POST", link, map[string]string{
|
||||
"_csrf": GetUserCSRFToken(t, session),
|
||||
"last_commit": getLastCommitID(t),
|
||||
"tree_path": "README.md",
|
||||
"content": content,
|
||||
"commit_choice": "direct",
|
||||
"commit_email": email,
|
||||
})
|
||||
return req
|
||||
}
|
||||
|
||||
t.Run("EmailInactive", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID})
|
||||
assert.False(t, email.IsActivated)
|
||||
|
||||
req := newReq(t, session, email.Email, "test content")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Contains(t,
|
||||
htmlDoc.doc.Find(".ui.negative.message").Text(),
|
||||
translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_email"),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("EmailInvalid", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true})
|
||||
assert.NotEqualValues(t, email.UID, user.ID)
|
||||
|
||||
req := newReq(t, session, email.Email, "test content")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Contains(t,
|
||||
htmlDoc.doc.Find(".ui.negative.message").Text(),
|
||||
translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_email"),
|
||||
)
|
||||
})
|
||||
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
|
||||
defer gitRepo.Close()
|
||||
|
||||
t.Run("DefaultEmailKeepPrivate", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
req := newReq(t, session, "", "privacy email")
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
commit, err := gitRepo.GetCommitByPath("README.md")
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, err := commit.GetFileContent("README.md", 64)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "privacy email", fileContent)
|
||||
assert.EqualValues(t, "User Two", commit.Author.Name)
|
||||
assert.EqualValues(t, "user2@noreply.example.org", commit.Author.Email)
|
||||
assert.EqualValues(t, "User Two", commit.Committer.Name)
|
||||
assert.EqualValues(t, "user2@noreply.example.org", commit.Committer.Email)
|
||||
})
|
||||
|
||||
t.Run("ChooseEmail", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 3, UID: user.ID, IsActivated: true})
|
||||
req := newReq(t, session, email.Email, "chosen email")
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
commit, err := gitRepo.GetCommitByPath("README.md")
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileContent, err := commit.GetFileContent("README.md", 64)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "chosen email", fileContent)
|
||||
assert.EqualValues(t, "User Two", commit.Author.Name)
|
||||
assert.EqualValues(t, email.Email, commit.Author.Email)
|
||||
assert.EqualValues(t, "User Two", commit.Committer.Name)
|
||||
assert.EqualValues(t, email.Email, commit.Committer.Email)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
35
tests/integration/feed_repo_test.go
Normal file
35
tests/integration/feed_repo_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFeedRepo(t *testing.T) {
|
||||
t.Run("RSS", func(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo1.rss")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
data := resp.Body.String()
|
||||
assert.Contains(t, data, `<rss version="2.0"`)
|
||||
|
||||
var rss RSS
|
||||
err := xml.Unmarshal(resp.Body.Bytes(), &rss)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rss.Channel.Link, "/user2/repo1")
|
||||
assert.NotEmpty(t, rss.Channel.PubDate)
|
||||
assert.Len(t, rss.Channel.Items, 1)
|
||||
assert.EqualValues(t, "issue5", rss.Channel.Items[0].Description)
|
||||
assert.NotEmpty(t, rss.Channel.Items[0].PubDate)
|
||||
})
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@ -12,7 +13,23 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFeed(t *testing.T) {
|
||||
// RSS is a struct to unmarshal RSS feeds test only
|
||||
type RSS struct {
|
||||
Channel struct {
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
PubDate string `xml:"pubDate"`
|
||||
Items []struct {
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
PubDate string `xml:"pubDate"`
|
||||
} `xml:"item"`
|
||||
} `xml:"channel"`
|
||||
}
|
||||
|
||||
func TestFeedUser(t *testing.T) {
|
||||
t.Run("User", func(t *testing.T) {
|
||||
t.Run("Atom", func(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
@ -32,6 +49,12 @@ func TestFeed(t *testing.T) {
|
||||
|
||||
data := resp.Body.String()
|
||||
assert.Contains(t, data, `<rss version="2.0"`)
|
||||
|
||||
var rss RSS
|
||||
err := xml.Unmarshal(resp.Body.Bytes(), &rss)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rss.Channel.Link, "/user2")
|
||||
assert.NotEmpty(t, rss.Channel.PubDate)
|
||||
})
|
||||
})
|
||||
}
|
@ -80,6 +80,7 @@ func testGitGeneral(t *testing.T, u *url.URL) {
|
||||
mediaTest(t, &httpContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
|
||||
|
||||
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head"))
|
||||
t.Run("CreateProtectedBranch", doCreateProtectedBranch(&httpContext, dstPath))
|
||||
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
|
||||
t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath))
|
||||
t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge"))
|
||||
@ -121,6 +122,7 @@ func testGitGeneral(t *testing.T, u *url.URL) {
|
||||
mediaTest(t, &sshContext, pushedFilesStandard[0], pushedFilesStandard[1], pushedFilesLFS[0], pushedFilesLFS[1])
|
||||
|
||||
t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2"))
|
||||
t.Run("CreateProtectedBranch", doCreateProtectedBranch(&sshContext, dstPath))
|
||||
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
|
||||
t.Run("MergeFork", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
@ -325,6 +327,34 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin
|
||||
return filepath.Base(tmpFile.Name()), err
|
||||
}
|
||||
|
||||
func doCreateProtectedBranch(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
t.Run("ProtectBranchWithFilePatterns", doProtectBranch(ctx, "release-*", baseCtx.Username, "", "", "config*"))
|
||||
|
||||
// push a new branch without any new commits
|
||||
t.Run("CreateProtectedBranch-NoChanges", doGitCreateBranch(dstPath, "release-v1.0"))
|
||||
t.Run("PushProtectedBranch-NoChanges", doGitPushTestRepository(dstPath, "origin", "release-v1.0"))
|
||||
t.Run("CheckoutMaster-NoChanges", doGitCheckoutBranch(dstPath, "master"))
|
||||
|
||||
// push a new branch with a new unprotected file
|
||||
t.Run("CreateProtectedBranch-UnprotectedFile", doGitCreateBranch(dstPath, "release-v2.0"))
|
||||
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "abc.txt")
|
||||
assert.NoError(t, err)
|
||||
t.Run("PushProtectedBranch-UnprotectedFile", doGitPushTestRepository(dstPath, "origin", "release-v2.0"))
|
||||
t.Run("CheckoutMaster-UnprotectedFile", doGitCheckoutBranch(dstPath, "master"))
|
||||
|
||||
// push a new branch with a new protected file
|
||||
t.Run("CreateProtectedBranch-ProtectedFile", doGitCreateBranch(dstPath, "release-v3.0"))
|
||||
_, err = generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "config")
|
||||
assert.NoError(t, err)
|
||||
t.Run("PushProtectedBranch-ProtectedFile", doGitPushTestRepositoryFail(dstPath, "origin", "release-v3.0"))
|
||||
t.Run("CheckoutMaster-ProtectedFile", doGitCheckoutBranch(dstPath, "master"))
|
||||
}
|
||||
}
|
||||
|
||||
func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
@ -334,27 +364,23 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes
|
||||
ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
// Protect branch without any whitelisting
|
||||
t.Run("ProtectBranchNoWhitelist", func(t *testing.T) {
|
||||
doProtectBranch(ctx, "protected", "", "", "")
|
||||
})
|
||||
t.Run("ProtectBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", ""))
|
||||
|
||||
// Try to push without permissions, which should fail
|
||||
t.Run("TryPushWithoutPermissions", func(t *testing.T) {
|
||||
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
|
||||
assert.NoError(t, err)
|
||||
doGitPushTestRepositoryFail(dstPath, "origin", "protected")
|
||||
doGitPushTestRepositoryFail(dstPath, "origin", "protected")(t)
|
||||
})
|
||||
|
||||
// Set up permissions for normal push but not force push
|
||||
t.Run("SetupNormalPushPermissions", func(t *testing.T) {
|
||||
doProtectBranch(ctx, "protected", baseCtx.Username, "", "")
|
||||
})
|
||||
t.Run("SetupNormalPushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", ""))
|
||||
|
||||
// Normal push should work
|
||||
t.Run("NormalPushWithPermissions", func(t *testing.T) {
|
||||
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-")
|
||||
assert.NoError(t, err)
|
||||
doGitPushTestRepository(dstPath, "origin", "protected")
|
||||
doGitPushTestRepository(dstPath, "origin", "protected")(t)
|
||||
})
|
||||
|
||||
// Try to force push without force push permissions, which should fail
|
||||
@ -364,30 +390,22 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes
|
||||
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "branch-data-file-new")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")
|
||||
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")(t)
|
||||
})
|
||||
|
||||
// Set up permissions for force push but not normal push
|
||||
t.Run("SetupForcePushPermissions", func(t *testing.T) {
|
||||
doProtectBranch(ctx, "protected", "", baseCtx.Username, "")
|
||||
})
|
||||
t.Run("SetupForcePushPermissions", doProtectBranch(ctx, "protected", "", baseCtx.Username, "", ""))
|
||||
|
||||
// Try to force push without normal push permissions, which should fail
|
||||
t.Run("ForcePushWithoutNormalPermissions", func(t *testing.T) {
|
||||
doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected")
|
||||
})
|
||||
t.Run("ForcePushWithoutNormalPermissions", doGitPushTestRepositoryFail(dstPath, "-f", "origin", "protected"))
|
||||
|
||||
// Set up permissions for normal and force push (both are required to force push)
|
||||
t.Run("SetupNormalAndForcePushPermissions", func(t *testing.T) {
|
||||
doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "")
|
||||
})
|
||||
t.Run("SetupNormalAndForcePushPermissions", doProtectBranch(ctx, "protected", baseCtx.Username, baseCtx.Username, "", ""))
|
||||
|
||||
// Force push should now work
|
||||
t.Run("ForcePushWithPermissions", func(t *testing.T) {
|
||||
doGitPushTestRepository(dstPath, "-f", "origin", "protected")
|
||||
})
|
||||
t.Run("ForcePushWithPermissions", doGitPushTestRepository(dstPath, "-f", "origin", "protected"))
|
||||
|
||||
t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", ""))
|
||||
t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "", "", ""))
|
||||
t.Run("PushToUnprotectedBranch", doGitPushTestRepository(dstPath, "origin", "protected:unprotected"))
|
||||
var pr api.PullRequest
|
||||
var err error
|
||||
@ -409,14 +427,14 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes
|
||||
t.Run("MergePR", doAPIMergePullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index))
|
||||
t.Run("PullProtected", doGitPull(dstPath, "origin", "protected"))
|
||||
|
||||
t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "", "unprotected-file-*"))
|
||||
t.Run("ProtectProtectedBranchUnprotectedFilePaths", doProtectBranch(ctx, "protected", "", "", "unprotected-file-*", ""))
|
||||
t.Run("GenerateCommit", func(t *testing.T) {
|
||||
_, err := generateCommitWithNewData(testFileSizeSmall, dstPath, "user2@example.com", "User Two", "unprotected-file-")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
t.Run("PushUnprotectedFilesToProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected"))
|
||||
|
||||
t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "", ""))
|
||||
t.Run("ProtectProtectedBranchWhitelist", doProtectBranch(ctx, "protected", baseCtx.Username, "", "", ""))
|
||||
|
||||
t.Run("CheckoutMaster", doGitCheckoutBranch(dstPath, "master"))
|
||||
t.Run("CreateBranchForced", doGitCreateBranch(dstPath, "toforce"))
|
||||
@ -431,7 +449,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns string) func(t *testing.T) {
|
||||
func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhitelistForcePush, unprotectedFilePatterns, protectedFilePatterns string) func(t *testing.T) {
|
||||
// We are going to just use the owner to set the protection.
|
||||
return func(t *testing.T) {
|
||||
csrf := GetUserCSRFToken(t, ctx.Session)
|
||||
@ -440,6 +458,7 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhit
|
||||
"_csrf": csrf,
|
||||
"rule_name": branch,
|
||||
"unprotected_file_patterns": unprotectedFilePatterns,
|
||||
"protected_file_patterns": protectedFilePatterns,
|
||||
}
|
||||
|
||||
if userToWhitelistPush != "" {
|
||||
|
@ -174,7 +174,7 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
|
||||
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
|
||||
val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text()
|
||||
val := strings.TrimSpace(htmlDoc.doc.Find(".comment-list .comment .render-content").Eq(commentCount).Text())
|
||||
assert.Equal(t, content, val)
|
||||
|
||||
idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id")
|
||||
|
@ -115,12 +115,12 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_mod
|
||||
OldBranch: "master",
|
||||
NewBranch: "master",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: actor.Name,
|
||||
Email: actor.Email,
|
||||
GitUserName: actor.Name,
|
||||
GitUserEmail: actor.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: actor.Name,
|
||||
Email: actor.Email,
|
||||
GitUserName: actor.Name,
|
||||
GitUserEmail: actor.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
@ -142,12 +142,12 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_mod
|
||||
OldBranch: "master",
|
||||
NewBranch: "newBranch",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: actor.Name,
|
||||
Email: actor.Email,
|
||||
GitUserName: actor.Name,
|
||||
GitUserEmail: actor.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: actor.Name,
|
||||
Email: actor.Email,
|
||||
GitUserName: actor.Name,
|
||||
GitUserEmail: actor.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
|
@ -118,7 +118,8 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo1/forks")
|
||||
resp := user1Sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.EqualValues(t, 1, htmlDoc.Find(forkItemSelector).Length())
|
||||
// since user1 is an admin, he can get both of the forked repositories
|
||||
assert.EqualValues(t, 2, htmlDoc.Find(forkItemSelector).Length())
|
||||
|
||||
assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1))
|
||||
resp = user1Sess.MakeRequest(t, req, http.StatusOK)
|
||||
|
@ -4,10 +4,22 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
@ -39,3 +51,514 @@ func TestNewWebHookLink(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testAPICreateWebhookForRepo(t *testing.T, session *TestSession, userName, repoName, url, event string) {
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+userName+"/"+repoName+"/hooks", api.CreateHookOption{
|
||||
Type: "gitea",
|
||||
Config: api.CreateHookOptionConfig{
|
||||
"content_type": "json",
|
||||
"url": url,
|
||||
},
|
||||
Events: []string{event},
|
||||
Active: true,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
func testAPICreateWebhookForOrg(t *testing.T, session *TestSession, userName, url, event string) {
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/"+userName+"/hooks", api.CreateHookOption{
|
||||
Type: "gitea",
|
||||
Config: api.CreateHookOptionConfig{
|
||||
"content_type": "json",
|
||||
"url": url,
|
||||
},
|
||||
Events: []string{event},
|
||||
Active: true,
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
}
|
||||
|
||||
type mockWebhookProvider struct {
|
||||
server *httptest.Server
|
||||
}
|
||||
|
||||
func newMockWebhookProvider(callback func(r *http.Request), status int) *mockWebhookProvider {
|
||||
m := &mockWebhookProvider{}
|
||||
m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
callback(r)
|
||||
w.WriteHeader(status)
|
||||
}))
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mockWebhookProvider) URL() string {
|
||||
if m.server == nil {
|
||||
return ""
|
||||
}
|
||||
return m.server.URL
|
||||
}
|
||||
|
||||
// Close closes the mock webhook http server
|
||||
func (m *mockWebhookProvider) Close() {
|
||||
if m.server != nil {
|
||||
m.server.Close()
|
||||
m.server = nil
|
||||
}
|
||||
}
|
||||
|
||||
func Test_WebhookCreate(t *testing.T) {
|
||||
var payloads []api.CreatePayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.CreatePayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = string(webhook_module.HookEventCreate)
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "create")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, string(webhook_module.HookEventCreate), triggeredEvent)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
|
||||
assert.EqualValues(t, "master2", payloads[0].Ref)
|
||||
assert.EqualValues(t, "branch", payloads[0].RefType)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookDelete(t *testing.T) {
|
||||
var payloads []api.DeletePayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.DeletePayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "delete"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "delete")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
|
||||
testAPIDeleteBranch(t, "master2", http.StatusNoContent)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "delete", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
|
||||
assert.EqualValues(t, "master2", payloads[0].Ref)
|
||||
assert.EqualValues(t, "branch", payloads[0].RefType)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookFork(t *testing.T) {
|
||||
var payloads []api.ForkPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.ForkPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "fork"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user1")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "fork")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master")
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "fork", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "repo1-fork", payloads[0].Repo.Name)
|
||||
assert.EqualValues(t, "user1/repo1-fork", payloads[0].Repo.FullName)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Forkee.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Forkee.FullName)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookIssueComment(t *testing.T) {
|
||||
var payloads []api.IssueCommentPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.IssueCommentPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "issue_comment"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_comment")
|
||||
|
||||
// 2. trigger the webhook
|
||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title2", "Description2")
|
||||
testIssueAddComment(t, session, issueURL, "issue title2 comment1", "")
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "issue_comment", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "created", payloads[0].Action)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
|
||||
assert.EqualValues(t, "Title2", payloads[0].Issue.Title)
|
||||
assert.EqualValues(t, "Description2", payloads[0].Issue.Body)
|
||||
assert.EqualValues(t, "issue title2 comment1", payloads[0].Comment.Body)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookRelease(t *testing.T) {
|
||||
var payloads []api.ReleasePayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.ReleasePayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "release"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "release")
|
||||
|
||||
// 2. trigger the webhook
|
||||
createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "release", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Repository.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName)
|
||||
assert.EqualValues(t, "v0.0.99", payloads[0].Release.TagName)
|
||||
assert.False(t, payloads[0].Release.IsDraft)
|
||||
assert.False(t, payloads[0].Release.IsPrerelease)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookPush(t *testing.T) {
|
||||
var payloads []api.PushPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.PushPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "push"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push")
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "push", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
|
||||
assert.Len(t, payloads[0].Commits, 1)
|
||||
assert.EqualValues(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookIssue(t *testing.T) {
|
||||
var payloads []api.IssuePayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.IssuePayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "issues"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testNewIssue(t, session, "user2", "repo1", "Title1", "Description1")
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "issues", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "opened", payloads[0].Action)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
|
||||
assert.EqualValues(t, "Title1", payloads[0].Issue.Title)
|
||||
assert.EqualValues(t, "Description1", payloads[0].Issue.Body)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookPullRequest(t *testing.T) {
|
||||
var payloads []api.PullRequestPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.PullRequestPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "pull_request"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request")
|
||||
|
||||
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
|
||||
// 2. trigger the webhook
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||
testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "pull_request", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "repo1", payloads[0].PullRequest.Base.Repository.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName)
|
||||
assert.EqualValues(t, "repo1", payloads[0].PullRequest.Head.Repository.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName)
|
||||
assert.EqualValues(t, 0, payloads[0].PullRequest.Additions)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookPullRequestComment(t *testing.T) {
|
||||
var payloads []api.IssueCommentPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.IssueCommentPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "pull_request_comment"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request_comment")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated)
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||
prID := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request")
|
||||
|
||||
testIssueAddComment(t, session, "/user2/repo1/pulls/"+prID, "pull title2 comment1", "")
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "pull_request_comment", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "created", payloads[0].Action)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName)
|
||||
assert.EqualValues(t, "first pull request", payloads[0].Issue.Title)
|
||||
assert.EqualValues(t, "", payloads[0].Issue.Body)
|
||||
assert.EqualValues(t, "pull title2 comment1", payloads[0].Comment.Body)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookWiki(t *testing.T) {
|
||||
var payloads []api.WikiPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.WikiPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "wiki"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "wiki")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "wiki", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "created", payloads[0].Action)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Repository.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName)
|
||||
assert.EqualValues(t, "Test-Wiki-Page", payloads[0].Page)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookRepository(t *testing.T) {
|
||||
var payloads []api.RepositoryPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.RepositoryPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "repository"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user1")
|
||||
|
||||
testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "repository")
|
||||
|
||||
// 2. trigger the webhook
|
||||
testAPIOrgCreateRepo(t, session, "org3", "repo_new", http.StatusCreated)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "repository", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "created", payloads[0].Action)
|
||||
assert.EqualValues(t, "org3", payloads[0].Organization.UserName)
|
||||
assert.EqualValues(t, "repo_new", payloads[0].Repository.Name)
|
||||
assert.EqualValues(t, "org3/repo_new", payloads[0].Repository.FullName)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookPackage(t *testing.T) {
|
||||
var payloads []api.PackagePayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.PackagePayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "package"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user1")
|
||||
|
||||
testAPICreateWebhookForOrg(t, session, "org3", provider.URL(), "package")
|
||||
|
||||
// 2. trigger the webhook
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
|
||||
url := fmt.Sprintf("/api/packages/%s/generic/%s/%s", "org3", "gitea", "v1.24.0")
|
||||
req := NewRequestWithBody(t, "PUT", url+"/gitea", strings.NewReader("This is a dummy file")).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "package", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, "created", payloads[0].Action)
|
||||
assert.EqualValues(t, "gitea", payloads[0].Package.Name)
|
||||
assert.EqualValues(t, "generic", payloads[0].Package.Type)
|
||||
assert.EqualValues(t, "org3", payloads[0].Organization.UserName)
|
||||
assert.EqualValues(t, "v1.24.0", payloads[0].Package.Version)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_WebhookStatus(t *testing.T) {
|
||||
var payloads []api.CommitStatusPayload
|
||||
var triggeredEvent string
|
||||
provider := newMockWebhookProvider(func(r *http.Request) {
|
||||
assert.Contains(t, r.Header["X-Github-Event-Type"], "status", "X-GitHub-Event-Type should contain status")
|
||||
assert.Contains(t, r.Header["X-Gitea-Event-Type"], "status", "X-Gitea-Event-Type should contain status")
|
||||
assert.Contains(t, r.Header["X-Gogs-Event-Type"], "status", "X-Gogs-Event-Type should contain status")
|
||||
content, _ := io.ReadAll(r.Body)
|
||||
var payload api.CommitStatusPayload
|
||||
err := json.Unmarshal(content, &payload)
|
||||
assert.NoError(t, err)
|
||||
payloads = append(payloads, payload)
|
||||
triggeredEvent = "status"
|
||||
}, http.StatusOK)
|
||||
defer provider.Close()
|
||||
|
||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
// 1. create a new webhook with special webhook for repo1
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "status")
|
||||
|
||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||
|
||||
gitRepo1, err := gitrepo.OpenRepository(context.Background(), repo1)
|
||||
assert.NoError(t, err)
|
||||
commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 2. trigger the webhook
|
||||
testCtx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeAll)
|
||||
|
||||
// update a status for a commit via API
|
||||
doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{
|
||||
State: api.CommitStatusSuccess,
|
||||
TargetURL: "http://test.ci/",
|
||||
Description: "",
|
||||
Context: "testci",
|
||||
})(t)
|
||||
|
||||
// 3. validate the webhook is triggered
|
||||
assert.EqualValues(t, "status", triggeredEvent)
|
||||
assert.Len(t, payloads, 1)
|
||||
assert.EqualValues(t, commitID, payloads[0].Commit.ID)
|
||||
assert.EqualValues(t, "repo1", payloads[0].Repo.Name)
|
||||
assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName)
|
||||
assert.EqualValues(t, "testci", payloads[0].Context)
|
||||
assert.EqualValues(t, commitID, payloads[0].SHA)
|
||||
})
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang
|
||||
NewBranch: repo.DefaultBranch,
|
||||
Message: "Deletes README.md",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: "Bob Smith",
|
||||
Email: "bob@smith.com",
|
||||
GitUserName: "Bob Smith",
|
||||
GitUserEmail: "bob@smith.com",
|
||||
},
|
||||
Committer: nil,
|
||||
}
|
||||
|
@ -26,6 +26,9 @@ TYPE = immediate
|
||||
[queue.push_update]
|
||||
TYPE = immediate
|
||||
|
||||
[queue.webhook_sender]
|
||||
TYPE = immediate
|
||||
|
||||
[repository]
|
||||
ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/gitea-repositories
|
||||
|
||||
@ -111,3 +114,6 @@ ENABLED = true
|
||||
|
||||
[actions]
|
||||
ENABLED = true
|
||||
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = 127.0.0.1
|
||||
|
@ -28,6 +28,9 @@ TYPE = immediate
|
||||
[queue.push_update]
|
||||
TYPE = immediate
|
||||
|
||||
[queue.webhook_sender]
|
||||
TYPE = immediate
|
||||
|
||||
[repository]
|
||||
ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mysql/gitea-repositories
|
||||
|
||||
@ -118,3 +121,6 @@ REPLY_TO_ADDRESS = incoming+%{token}@localhost
|
||||
|
||||
[actions]
|
||||
ENABLED = true
|
||||
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = 127.0.0.1
|
||||
|
@ -27,6 +27,9 @@ TYPE = immediate
|
||||
[queue.push_update]
|
||||
TYPE = immediate
|
||||
|
||||
[queue.webhook_sender]
|
||||
TYPE = immediate
|
||||
|
||||
[repository]
|
||||
ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/gitea-repositories
|
||||
|
||||
@ -127,3 +130,6 @@ ENABLED = true
|
||||
|
||||
[actions]
|
||||
ENABLED = true
|
||||
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = 127.0.0.1
|
||||
|
@ -22,6 +22,9 @@ TYPE = immediate
|
||||
[queue.push_update]
|
||||
TYPE = immediate
|
||||
|
||||
[queue.webhook_sender]
|
||||
TYPE = immediate
|
||||
|
||||
[repository]
|
||||
ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea-repositories
|
||||
|
||||
@ -116,3 +119,6 @@ RENDER_CONTENT_MODE=sanitized
|
||||
|
||||
[actions]
|
||||
ENABLED = true
|
||||
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = 127.0.0.1
|
||||
|
@ -22,6 +22,8 @@
|
||||
"verbatimModuleSyntax": true,
|
||||
"stripInternal": true,
|
||||
"strict": false,
|
||||
"strictBindCallApply": true,
|
||||
"strictBuiltinIteratorReturn": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
|
@ -65,6 +65,7 @@
|
||||
}
|
||||
|
||||
#repo-files-table .repo-file-last-commit {
|
||||
min-width: 0; /* otherwise the flex axis is not limited and the text might overflow in Pale Moon */
|
||||
background: var(--color-box-header);
|
||||
}
|
||||
|
||||
|
@ -6,21 +6,9 @@ import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts'
|
||||
import {getIssueColor, getIssueIcon} from '../issue.ts';
|
||||
import {debounce} from 'perfect-debounce';
|
||||
import type TextExpanderElement from '@github/text-expander-element';
|
||||
import type {TextExpanderChangeEvent, TextExpanderResult} from '@github/text-expander-element/dist/text-expander-element.d.ts';
|
||||
|
||||
type TextExpanderProvideResult = {
|
||||
matched: boolean,
|
||||
fragment?: HTMLElement,
|
||||
}
|
||||
|
||||
type TextExpanderChangeEvent = Event & {
|
||||
detail?: {
|
||||
key: string,
|
||||
text: string,
|
||||
provide: (result: TextExpanderProvideResult | Promise<TextExpanderProvideResult>) => void,
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchIssueSuggestions(key: string, text: string): Promise<TextExpanderProvideResult> {
|
||||
async function fetchIssueSuggestions(key: string, text: string): Promise<TextExpanderResult> {
|
||||
const issuePathInfo = parseIssueHref(window.location.href);
|
||||
if (!issuePathInfo.ownerName) {
|
||||
const repoOwnerPathInfo = parseRepoOwnerPathInfo(window.location.pathname);
|
||||
@ -59,7 +47,7 @@ export function initTextExpander(expander: TextExpanderElement) {
|
||||
return keyStart > lineStart;
|
||||
};
|
||||
|
||||
const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderProvideResult> => {
|
||||
const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderResult> => {
|
||||
// https://github.com/github/text-expander-element/issues/71
|
||||
// Upstream bug: when using "multiword+promise", TextExpander will get wrong "key" position.
|
||||
// To reproduce, comment out the "shouldShowIssueSuggestions" check, use the "await sleep" below,
|
||||
|
Loading…
Reference in New Issue
Block a user