mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-07 12:15:19 +02:00
Merge branch 'main' of https://github.com/go-gitea/gitea into workflow-webhook-api
This commit is contained in:
@@ -127,6 +127,34 @@ var (
|
|||||||
&cli.UintFlag{
|
&cli.UintFlag{
|
||||||
Name: "page-size",
|
Name: "page-size",
|
||||||
Usage: "Search page size.",
|
Usage: "Search page size.",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "enable-groups",
|
||||||
|
Usage: "Enable LDAP groups",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-search-base-dn",
|
||||||
|
Usage: "The LDAP base DN at which group accounts will be searched for",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-member-attribute",
|
||||||
|
Usage: "Group attribute containing list of users",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-user-attribute",
|
||||||
|
Usage: "User attribute listed in group",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-filter",
|
||||||
|
Usage: "Verify group membership in LDAP",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "group-team-map",
|
||||||
|
Usage: "Map LDAP groups to Organization teams",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "group-team-map-removal",
|
||||||
|
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
|
||||||
})
|
})
|
||||||
|
|
||||||
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
|
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
|
||||||
@@ -273,6 +301,27 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
|
|||||||
if c.IsSet("skip-local-2fa") {
|
if c.IsSet("skip-local-2fa") {
|
||||||
config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
||||||
}
|
}
|
||||||
|
if c.IsSet("enable-groups") {
|
||||||
|
config.GroupsEnabled = c.Bool("enable-groups")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-search-base-dn") {
|
||||||
|
config.GroupDN = c.String("group-search-base-dn")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-member-attribute") {
|
||||||
|
config.GroupMemberUID = c.String("group-member-attribute")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-user-attribute") {
|
||||||
|
config.UserUID = c.String("group-user-attribute")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-filter") {
|
||||||
|
config.GroupFilter = c.String("group-filter")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-team-map") {
|
||||||
|
config.GroupTeamMap = c.String("group-team-map")
|
||||||
|
}
|
||||||
|
if c.IsSet("group-team-map-removal") {
|
||||||
|
config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
"--attributes-in-bind",
|
"--attributes-in-bind",
|
||||||
"--synchronize-users",
|
"--synchronize-users",
|
||||||
"--page-size", "99",
|
"--page-size", "99",
|
||||||
|
"--enable-groups",
|
||||||
|
"--group-search-base-dn", "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
"--group-member-attribute", "memberUid",
|
||||||
|
"--group-user-attribute", "uid",
|
||||||
|
"--group-filter", "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
"--group-team-map", `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
"--group-team-map-removal",
|
||||||
},
|
},
|
||||||
source: &auth.Source{
|
source: &auth.Source{
|
||||||
Type: auth.LDAP,
|
Type: auth.LDAP,
|
||||||
@@ -78,6 +85,13 @@ func TestAddLdapBindDn(t *testing.T) {
|
|||||||
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
GroupsEnabled: true,
|
||||||
|
GroupDN: "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
GroupMemberUID: "memberUid",
|
||||||
|
UserUID: "uid",
|
||||||
|
GroupFilter: "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
GroupTeamMap: `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -510,6 +524,13 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
"--bind-password", "secret-bind-full",
|
"--bind-password", "secret-bind-full",
|
||||||
"--synchronize-users",
|
"--synchronize-users",
|
||||||
"--page-size", "99",
|
"--page-size", "99",
|
||||||
|
"--enable-groups",
|
||||||
|
"--group-search-base-dn", "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
"--group-member-attribute", "memberUid",
|
||||||
|
"--group-user-attribute", "uid",
|
||||||
|
"--group-filter", "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
"--group-team-map", `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
"--group-team-map-removal",
|
||||||
},
|
},
|
||||||
id: 23,
|
id: 23,
|
||||||
existingAuthSource: &auth.Source{
|
existingAuthSource: &auth.Source{
|
||||||
@@ -545,6 +566,13 @@ func TestUpdateLdapBindDn(t *testing.T) {
|
|||||||
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
GroupsEnabled: true,
|
||||||
|
GroupDN: "ou=group,dc=full-domain-bind,dc=org",
|
||||||
|
GroupMemberUID: "memberUid",
|
||||||
|
UserUID: "uid",
|
||||||
|
GroupFilter: "(|(cn=gitea_users)(cn=admins))",
|
||||||
|
GroupTeamMap: `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
|
||||||
|
GroupTeamMapRemoval: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -172,7 +172,10 @@ func (a *Action) TableIndices() []*schemas.Index {
|
|||||||
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||||
cuIndex.AddColumn("user_id", "is_deleted")
|
cuIndex.AddColumn("user_id", "is_deleted")
|
||||||
|
|
||||||
indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex}
|
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||||
|
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||||
|
|
||||||
|
indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||||
|
|
||||||
return indices
|
return indices
|
||||||
}
|
}
|
||||||
@@ -442,6 +445,7 @@ type GetFeedsOptions struct {
|
|||||||
OnlyPerformedBy bool // only actions performed by requested user
|
OnlyPerformedBy bool // only actions performed by requested user
|
||||||
IncludeDeleted bool // include deleted actions
|
IncludeDeleted bool // include deleted actions
|
||||||
Date string // the day we want activity for: YYYY-MM-DD
|
Date string // the day we want activity for: YYYY-MM-DD
|
||||||
|
DontCount bool // do counting in GetFeeds
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActivityReadable return whether doer can read activities of user
|
// ActivityReadable return whether doer can read activities of user
|
||||||
|
|||||||
@@ -243,7 +243,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||||||
sess := db.GetEngine(ctx).Where(cond)
|
sess := db.GetEngine(ctx).Where(cond)
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
|
||||||
count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
|
if opts.DontCount {
|
||||||
|
err = sess.Desc("`action`.created_unix").Find(&actions)
|
||||||
|
} else {
|
||||||
|
count, err = sess.Desc("`action`.created_unix").FindAndCount(&actions)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
||||||
}
|
}
|
||||||
@@ -257,11 +261,13 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||||||
return nil, 0, fmt.Errorf("Find(actionsIDs): %w", err)
|
return nil, 0, fmt.Errorf("Find(actionsIDs): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err = db.GetEngine(ctx).Where(cond).
|
if !opts.DontCount {
|
||||||
Table("action").
|
count, err = db.GetEngine(ctx).Where(cond).
|
||||||
Cols("`action`.id").Count()
|
Table("action").
|
||||||
if err != nil {
|
Cols("`action`.id").Count()
|
||||||
return nil, 0, fmt.Errorf("Count: %w", err)
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("Count: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.GetEngine(ctx).In("`action`.id", actionIDs).Desc("`action`.created_unix").Find(&actions); err != nil {
|
if err := db.GetEngine(ctx).In("`action`.id", actionIDs).Desc("`action`.created_unix").Find(&actions); err != nil {
|
||||||
@@ -275,3 +281,9 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||||||
|
|
||||||
return actions, count, nil
|
return actions, count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CountUserFeeds(ctx context.Context, userID int64) (int64, error) {
|
||||||
|
return db.GetEngine(ctx).Where("user_id = ?", userID).
|
||||||
|
And("is_deleted = ?", false).
|
||||||
|
Count(&Action{})
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,3 @@ const (
|
|||||||
// NoConditionID means a condition to filter the records which don't match any id.
|
// NoConditionID means a condition to filter the records which don't match any id.
|
||||||
// eg: "milestone_id=-1" means "find the items without any milestone.
|
// eg: "milestone_id=-1" means "find the items without any milestone.
|
||||||
const NoConditionID int64 = -1
|
const NoConditionID int64 = -1
|
||||||
|
|
||||||
// NonExistingID means a condition to match no result (eg: a non-existing user)
|
|
||||||
// It doesn't use -1 or -2 because they are used as builtin users.
|
|
||||||
const NonExistingID int64 = -1000000
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ type IssuesOptions struct { //nolint
|
|||||||
RepoIDs []int64 // overwrites RepoCond if the length is not 0
|
RepoIDs []int64 // overwrites RepoCond if the length is not 0
|
||||||
AllPublic bool // include also all public repositories
|
AllPublic bool // include also all public repositories
|
||||||
RepoCond builder.Cond
|
RepoCond builder.Cond
|
||||||
AssigneeID optional.Option[int64]
|
AssigneeID string // "(none)" or "(any)" or a user ID
|
||||||
PosterID optional.Option[int64]
|
PosterID string // "(none)" or "(any)" or a user ID
|
||||||
MentionedID int64
|
MentionedID int64
|
||||||
ReviewRequestedID int64
|
ReviewRequestedID int64
|
||||||
ReviewedID int64
|
ReviewedID int64
|
||||||
@@ -356,26 +356,25 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, owner *user_mod
|
|||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssigneeCondition(sess *xorm.Session, assigneeID optional.Option[int64]) {
|
func applyAssigneeCondition(sess *xorm.Session, assigneeID string) {
|
||||||
// old logic: 0 is also treated as "not filtering assignee", because the "assignee" was read as FormInt64
|
// old logic: 0 is also treated as "not filtering assignee", because the "assignee" was read as FormInt64
|
||||||
if !assigneeID.Has() || assigneeID.Value() == 0 {
|
if assigneeID == "(none)" {
|
||||||
return
|
|
||||||
}
|
|
||||||
if assigneeID.Value() == db.NoConditionID {
|
|
||||||
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_assignees)")
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_assignees)")
|
||||||
} else {
|
} else if assigneeID == "(any)" {
|
||||||
|
sess.Where("issue.id IN (SELECT issue_id FROM issue_assignees)")
|
||||||
|
} else if assigneeIDInt64, _ := strconv.ParseInt(assigneeID, 10, 64); assigneeIDInt64 > 0 {
|
||||||
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||||
And("issue_assignees.assignee_id = ?", assigneeID.Value())
|
And("issue_assignees.assignee_id = ?", assigneeIDInt64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPosterCondition(sess *xorm.Session, posterID optional.Option[int64]) {
|
func applyPosterCondition(sess *xorm.Session, posterID string) {
|
||||||
if !posterID.Has() {
|
// Actually every issue has a poster.
|
||||||
return
|
// The "(none)" is for internal usage only: when doer tries to search non-existing user as poster, use "(none)" to return empty result.
|
||||||
}
|
if posterID == "(none)" {
|
||||||
// poster doesn't need to support db.NoConditionID(-1), so just use the value as-is
|
sess.And("issue.poster_id=0")
|
||||||
if posterID.Has() {
|
} else if posterIDInt64, _ := strconv.ParseInt(posterID, 10, 64); posterIDInt64 > 0 {
|
||||||
sess.And("issue.poster_id=?", posterID.Value())
|
sess.And("issue.poster_id=?", posterIDInt64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -155,7 +154,7 @@ func TestIssues(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
issues_model.IssuesOptions{
|
issues_model.IssuesOptions{
|
||||||
AssigneeID: optional.Some(int64(1)),
|
AssigneeID: "1",
|
||||||
SortType: "oldest",
|
SortType: "oldest",
|
||||||
},
|
},
|
||||||
[]int64{1, 6},
|
[]int64{1, 6},
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
|
newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
|
||||||
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
|
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
|
||||||
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
|
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
|
||||||
|
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_24 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
type improveActionTableIndicesAction struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"INDEX"` // Receiver user id.
|
||||||
|
OpType int
|
||||||
|
ActUserID int64 // Action user id.
|
||||||
|
RepoID int64
|
||||||
|
CommentID int64 `xorm:"INDEX"`
|
||||||
|
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
RefName string
|
||||||
|
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
Content string `xorm:"TEXT"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName sets the name of this table
|
||||||
|
func (*improveActionTableIndicesAction) TableName() string {
|
||||||
|
return "action"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableIndices implements xorm's TableIndices interface
|
||||||
|
func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index {
|
||||||
|
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
|
||||||
|
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
|
||||||
|
|
||||||
|
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
|
||||||
|
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
|
||||||
|
|
||||||
|
cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
|
||||||
|
cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
|
||||||
|
|
||||||
|
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
|
||||||
|
cuIndex.AddColumn("user_id", "is_deleted")
|
||||||
|
|
||||||
|
actUserUserIndex := schemas.NewIndex("au_c_u", schemas.IndexType)
|
||||||
|
actUserUserIndex.AddColumn("act_user_id", "created_unix", "user_id")
|
||||||
|
|
||||||
|
indices := []*schemas.Index{actUserIndex, repoIndex, cudIndex, cuIndex, actUserUserIndex}
|
||||||
|
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddNewIndexForUserDashboard(x *xorm.Engine) error {
|
||||||
|
return x.Sync(new(improveActionTableIndicesAction))
|
||||||
|
}
|
||||||
+16
-4
@@ -215,12 +215,24 @@ func init() {
|
|||||||
db.RegisterModel(new(Repository))
|
db.RegisterModel(new(Repository))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *Repository) GetName() string {
|
func RelativePath(ownerName, repoName string) string {
|
||||||
return repo.Name
|
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *Repository) GetOwnerName() string {
|
// RelativePath should be an unix style path like username/reponame.git
|
||||||
return repo.OwnerName
|
func (repo *Repository) RelativePath() string {
|
||||||
|
return RelativePath(repo.OwnerName, repo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorageRepo string
|
||||||
|
|
||||||
|
// RelativePath should be an unix style path like username/reponame.git
|
||||||
|
func (sr StorageRepo) RelativePath() string {
|
||||||
|
return string(sr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) WikiStorageRepo() StorageRepo {
|
||||||
|
return StorageRepo(strings.ToLower(repo.OwnerName) + "/" + strings.ToLower(repo.Name) + ".wiki.git")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizedOriginalURL returns a sanitized OriginalURL
|
// SanitizedOriginalURL returns a sanitized OriginalURL
|
||||||
|
|||||||
@@ -44,24 +44,12 @@ func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
|
|||||||
return git.GetDefaultBranch(ctx, repoPath(repo))
|
return git.GetDefaultBranch(ctx, repoPath(repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWikiDefaultBranch(ctx context.Context, repo Repository) (string, error) {
|
|
||||||
return git.GetDefaultBranch(ctx, wikiPath(repo))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReferenceExist returns true if given reference exists in the repository.
|
// IsReferenceExist returns true if given reference exists in the repository.
|
||||||
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
|
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
|
||||||
return git.IsReferenceExist(ctx, repoPath(repo), name)
|
return git.IsReferenceExist(ctx, repoPath(repo), name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsWikiReferenceExist(ctx context.Context, repo Repository, name string) bool {
|
|
||||||
return git.IsReferenceExist(ctx, wikiPath(repo), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsBranchExist returns true if given branch exists in the repository.
|
// IsBranchExist returns true if given branch exists in the repository.
|
||||||
func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
|
func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
|
||||||
return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
|
return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsWikiBranchExist(ctx context.Context, repo Repository, name string) bool {
|
|
||||||
return IsWikiReferenceExist(ctx, repo, git.BranchPrefix+name)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/reqctx"
|
"code.gitea.io/gitea/modules/reqctx"
|
||||||
@@ -16,21 +15,15 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Repository represents a git repository which stored in a disk
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
GetName() string
|
RelativePath() string // We don't assume how the directory structure of the repository is, so we only need the relative path
|
||||||
GetOwnerName() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func absPath(owner, name string) string {
|
|
||||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(owner), strings.ToLower(name)+".git")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RelativePath should be an unix style path like username/reponame.git
|
||||||
|
// This method should change it according to the current OS.
|
||||||
func repoPath(repo Repository) string {
|
func repoPath(repo Repository) string {
|
||||||
return absPath(repo.GetOwnerName(), repo.GetName())
|
return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath()))
|
||||||
}
|
|
||||||
|
|
||||||
func wikiPath(repo Repository) string {
|
|
||||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(repo.GetOwnerName()), strings.ToLower(repo.GetName())+".wiki.git")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenRepository opens the repository at the given relative path with the provided context.
|
// OpenRepository opens the repository at the given relative path with the provided context.
|
||||||
@@ -38,10 +31,6 @@ func OpenRepository(ctx context.Context, repo Repository) (*git.Repository, erro
|
|||||||
return git.OpenRepository(ctx, repoPath(repo))
|
return git.OpenRepository(ctx, repoPath(repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenWikiRepository(ctx context.Context, repo Repository) (*git.Repository, error) {
|
|
||||||
return git.OpenRepository(ctx, wikiPath(repo))
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextKey is a value for use with context.WithValue.
|
// contextKey is a value for use with context.WithValue.
|
||||||
type contextKey struct {
|
type contextKey struct {
|
||||||
repoPath string
|
repoPath string
|
||||||
@@ -86,9 +75,8 @@ func DeleteRepository(ctx context.Context, repo Repository) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenameRepository renames a repository's name on disk
|
// RenameRepository renames a repository's name on disk
|
||||||
func RenameRepository(ctx context.Context, repo Repository, newName string) error {
|
func RenameRepository(ctx context.Context, repo, newRepo Repository) error {
|
||||||
newRepoPath := absPath(repo.GetOwnerName(), newName)
|
if err := util.Rename(repoPath(repo), repoPath(newRepo)); err != nil {
|
||||||
if err := util.Rename(repoPath(repo), newRepoPath); err != nil {
|
|
||||||
return fmt.Errorf("rename repository directory: %w", err)
|
return fmt.Errorf("rename repository directory: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -106,16 +106,11 @@ done
|
|||||||
return hookNames, hookTpls, giteaHookTpls
|
return hookNames, hookTpls, giteaHookTpls
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDelegateHooksForRepo creates all the hooks scripts for the repo
|
// CreateDelegateHooks creates all the hooks scripts for the repo
|
||||||
func CreateDelegateHooksForRepo(_ context.Context, repo Repository) (err error) {
|
func CreateDelegateHooks(_ context.Context, repo Repository) (err error) {
|
||||||
return createDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
|
return createDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDelegateHooksForWiki creates all the hooks scripts for the wiki repo
|
|
||||||
func CreateDelegateHooksForWiki(_ context.Context, repo Repository) (err error) {
|
|
||||||
return createDelegateHooks(filepath.Join(wikiPath(repo), "hooks"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDelegateHooks(hookDir string) (err error) {
|
func createDelegateHooks(hookDir string) (err error) {
|
||||||
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
|
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
|
||||||
|
|
||||||
@@ -178,16 +173,11 @@ func ensureExecutable(filename string) error {
|
|||||||
return os.Chmod(filename, mode)
|
return os.Chmod(filename, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckDelegateHooksForRepo checks the hooks scripts for the repo
|
// CheckDelegateHooks checks the hooks scripts for the repo
|
||||||
func CheckDelegateHooksForRepo(_ context.Context, repo Repository) ([]string, error) {
|
func CheckDelegateHooks(_ context.Context, repo Repository) ([]string, error) {
|
||||||
return checkDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
|
return checkDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckDelegateHooksForWiki checks the hooks scripts for the repo
|
|
||||||
func CheckDelegateHooksForWiki(_ context.Context, repo Repository) ([]string, error) {
|
|
||||||
return checkDelegateHooks(filepath.Join(wikiPath(repo), "hooks"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDelegateHooks(hookDir string) ([]string, error) {
|
func checkDelegateHooks(hookDir string) ([]string, error) {
|
||||||
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
|
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ package bleve
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/indexer"
|
"code.gitea.io/gitea/modules/indexer"
|
||||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||||
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
|
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
|
||||||
"code.gitea.io/gitea/modules/indexer/issues/internal"
|
"code.gitea.io/gitea/modules/indexer/issues/internal"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/blevesearch/bleve/v2"
|
"github.com/blevesearch/bleve/v2"
|
||||||
@@ -246,12 +248,20 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectColumnID.Value(), "project_board_id"))
|
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectColumnID.Value(), "project_board_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.PosterID.Has() {
|
if options.PosterID != "" {
|
||||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.PosterID.Value(), "poster_id"))
|
// "(none)" becomes 0, it means no poster
|
||||||
|
posterIDInt64, _ := strconv.ParseInt(options.PosterID, 10, 64)
|
||||||
|
queries = append(queries, inner_bleve.NumericEqualityQuery(posterIDInt64, "poster_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.AssigneeID.Has() {
|
if options.AssigneeID != "" {
|
||||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.AssigneeID.Value(), "assignee_id"))
|
if options.AssigneeID == "(any)" {
|
||||||
|
queries = append(queries, inner_bleve.NumericRangeInclusiveQuery(optional.Some[int64](1), optional.None[int64](), "assignee_id"))
|
||||||
|
} else {
|
||||||
|
// "(none)" becomes 0, it means no assignee
|
||||||
|
assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64)
|
||||||
|
queries = append(queries, inner_bleve.NumericEqualityQuery(assigneeIDInt64, "assignee_id"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.MentionID.Has() {
|
if options.MentionID.Has() {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
|
|||||||
RepoIDs: options.RepoIDs,
|
RepoIDs: options.RepoIDs,
|
||||||
AllPublic: options.AllPublic,
|
AllPublic: options.AllPublic,
|
||||||
RepoCond: nil,
|
RepoCond: nil,
|
||||||
AssigneeID: optional.Some(convertID(options.AssigneeID)),
|
AssigneeID: options.AssigneeID,
|
||||||
PosterID: options.PosterID,
|
PosterID: options.PosterID,
|
||||||
MentionedID: convertID(options.MentionID),
|
MentionedID: convertID(options.MentionID),
|
||||||
ReviewRequestedID: convertID(options.ReviewRequestedID),
|
ReviewRequestedID: convertID(options.ReviewRequestedID),
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
|||||||
searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0)
|
searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.AssigneeID.Value() == db.NoConditionID {
|
searchOpt.AssigneeID = opts.AssigneeID
|
||||||
searchOpt.AssigneeID = optional.Some[int64](0) // FIXME: this is inconsistent from other places, 0 means "no assignee"
|
|
||||||
} else if opts.AssigneeID.Value() != 0 {
|
|
||||||
searchOpt.AssigneeID = opts.AssigneeID
|
|
||||||
}
|
|
||||||
|
|
||||||
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
||||||
convertID := func(id int64) optional.Option[int64] {
|
convertID := func(id int64) optional.Option[int64] {
|
||||||
|
|||||||
@@ -212,12 +212,22 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||||||
query.Must(elastic.NewTermQuery("project_board_id", options.ProjectColumnID.Value()))
|
query.Must(elastic.NewTermQuery("project_board_id", options.ProjectColumnID.Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.PosterID.Has() {
|
if options.PosterID != "" {
|
||||||
query.Must(elastic.NewTermQuery("poster_id", options.PosterID.Value()))
|
// "(none)" becomes 0, it means no poster
|
||||||
|
posterIDInt64, _ := strconv.ParseInt(options.PosterID, 10, 64)
|
||||||
|
query.Must(elastic.NewTermQuery("poster_id", posterIDInt64))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.AssigneeID.Has() {
|
if options.AssigneeID != "" {
|
||||||
query.Must(elastic.NewTermQuery("assignee_id", options.AssigneeID.Value()))
|
if options.AssigneeID == "(any)" {
|
||||||
|
q := elastic.NewRangeQuery("assignee_id")
|
||||||
|
q.Gte(1)
|
||||||
|
query.Must(q)
|
||||||
|
} else {
|
||||||
|
// "(none)" becomes 0, it means no assignee
|
||||||
|
assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64)
|
||||||
|
query.Must(elastic.NewTermQuery("assignee_id", assigneeIDInt64))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.MentionID.Has() {
|
if options.MentionID.Has() {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func TestDBSearchIssues(t *testing.T) {
|
|||||||
t.Run("search issues with order", searchIssueWithOrder)
|
t.Run("search issues with order", searchIssueWithOrder)
|
||||||
t.Run("search issues in project", searchIssueInProject)
|
t.Run("search issues in project", searchIssueInProject)
|
||||||
t.Run("search issues with paginator", searchIssueWithPaginator)
|
t.Run("search issues with paginator", searchIssueWithPaginator)
|
||||||
|
t.Run("search issues with any assignee", searchIssueWithAnyAssignee)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchIssueWithKeyword(t *testing.T) {
|
func searchIssueWithKeyword(t *testing.T) {
|
||||||
@@ -176,19 +177,19 @@ func searchIssueByID(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
opts: SearchOptions{
|
opts: SearchOptions{
|
||||||
PosterID: optional.Some(int64(1)),
|
PosterID: "1",
|
||||||
},
|
},
|
||||||
expectedIDs: []int64{11, 6, 3, 2, 1},
|
expectedIDs: []int64{11, 6, 3, 2, 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
opts: SearchOptions{
|
opts: SearchOptions{
|
||||||
AssigneeID: optional.Some(int64(1)),
|
AssigneeID: "1",
|
||||||
},
|
},
|
||||||
expectedIDs: []int64{6, 1},
|
expectedIDs: []int64{6, 1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// NOTE: This tests no assignees filtering and also ToSearchOptions() to ensure it will set AssigneeID to 0 when it is passed as -1.
|
// NOTE: This tests no assignees filtering and also ToSearchOptions() to ensure it handles the filter correctly
|
||||||
opts: *ToSearchOptions("", &issues.IssuesOptions{AssigneeID: optional.Some(db.NoConditionID)}),
|
opts: *ToSearchOptions("", &issues.IssuesOptions{AssigneeID: "(none)"}),
|
||||||
expectedIDs: []int64{22, 21, 16, 15, 14, 13, 12, 11, 20, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2},
|
expectedIDs: []int64{22, 21, 16, 15, 14, 13, 12, 11, 20, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -462,3 +463,25 @@ func searchIssueWithPaginator(t *testing.T) {
|
|||||||
assert.Equal(t, test.expectedTotal, total)
|
assert.Equal(t, test.expectedTotal, total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func searchIssueWithAnyAssignee(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
opts SearchOptions
|
||||||
|
expectedIDs []int64
|
||||||
|
expectedTotal int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
SearchOptions{
|
||||||
|
AssigneeID: "(any)",
|
||||||
|
},
|
||||||
|
[]int64{17, 6, 1},
|
||||||
|
3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
issueIDs, total, err := SearchIssues(t.Context(), &test.opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||||
|
assert.Equal(t, test.expectedTotal, total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,9 +97,8 @@ type SearchOptions struct {
|
|||||||
ProjectID optional.Option[int64] // project the issues belong to
|
ProjectID optional.Option[int64] // project the issues belong to
|
||||||
ProjectColumnID optional.Option[int64] // project column the issues belong to
|
ProjectColumnID optional.Option[int64] // project column the issues belong to
|
||||||
|
|
||||||
PosterID optional.Option[int64] // poster of the issues
|
PosterID string // poster of the issues, "(none)" or "(any)" or a user ID
|
||||||
|
AssigneeID string // assignee of the issues, "(none)" or "(any)" or a user ID
|
||||||
AssigneeID optional.Option[int64] // assignee of the issues, zero means no assignee
|
|
||||||
|
|
||||||
MentionID optional.Option[int64] // mentioned user of the issues
|
MentionID optional.Option[int64] // mentioned user of the issues
|
||||||
|
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ var cases = []*testIndexerCase{
|
|||||||
Paginator: &db.ListOptions{
|
Paginator: &db.ListOptions{
|
||||||
PageSize: 5,
|
PageSize: 5,
|
||||||
},
|
},
|
||||||
PosterID: optional.Some(int64(1)),
|
PosterID: "1",
|
||||||
},
|
},
|
||||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||||
assert.Len(t, result.Hits, 5)
|
assert.Len(t, result.Hits, 5)
|
||||||
@@ -397,7 +397,7 @@ var cases = []*testIndexerCase{
|
|||||||
Paginator: &db.ListOptions{
|
Paginator: &db.ListOptions{
|
||||||
PageSize: 5,
|
PageSize: 5,
|
||||||
},
|
},
|
||||||
AssigneeID: optional.Some(int64(1)),
|
AssigneeID: "1",
|
||||||
},
|
},
|
||||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||||
assert.Len(t, result.Hits, 5)
|
assert.Len(t, result.Hits, 5)
|
||||||
@@ -415,7 +415,7 @@ var cases = []*testIndexerCase{
|
|||||||
Paginator: &db.ListOptions{
|
Paginator: &db.ListOptions{
|
||||||
PageSize: 5,
|
PageSize: 5,
|
||||||
},
|
},
|
||||||
AssigneeID: optional.Some(int64(0)),
|
AssigneeID: "(none)",
|
||||||
},
|
},
|
||||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||||
assert.Len(t, result.Hits, 5)
|
assert.Len(t, result.Hits, 5)
|
||||||
@@ -647,6 +647,21 @@ var cases = []*testIndexerCase{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "SearchAnyAssignee",
|
||||||
|
SearchOptions: &internal.SearchOptions{
|
||||||
|
AssigneeID: "(any)",
|
||||||
|
},
|
||||||
|
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||||
|
assert.Len(t, result.Hits, 180)
|
||||||
|
for _, v := range result.Hits {
|
||||||
|
assert.GreaterOrEqual(t, data[v.ID].AssigneeID, int64(1))
|
||||||
|
}
|
||||||
|
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
|
||||||
|
return v.AssigneeID >= 1
|
||||||
|
}), result.Total)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type testIndexerCase struct {
|
type testIndexerCase struct {
|
||||||
|
|||||||
@@ -187,12 +187,20 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||||||
query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectColumnID.Value()))
|
query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectColumnID.Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.PosterID.Has() {
|
if options.PosterID != "" {
|
||||||
query.And(inner_meilisearch.NewFilterEq("poster_id", options.PosterID.Value()))
|
// "(none)" becomes 0, it means no poster
|
||||||
|
posterIDInt64, _ := strconv.ParseInt(options.PosterID, 10, 64)
|
||||||
|
query.And(inner_meilisearch.NewFilterEq("poster_id", posterIDInt64))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.AssigneeID.Has() {
|
if options.AssigneeID != "" {
|
||||||
query.And(inner_meilisearch.NewFilterEq("assignee_id", options.AssigneeID.Value()))
|
if options.AssigneeID == "(any)" {
|
||||||
|
query.And(inner_meilisearch.NewFilterGte("assignee_id", 1))
|
||||||
|
} else {
|
||||||
|
// "(none)" becomes 0, it means no assignee
|
||||||
|
assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64)
|
||||||
|
query.And(inner_meilisearch.NewFilterEq("assignee_id", assigneeIDInt64))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.MentionID.Has() {
|
if options.MentionID.Has() {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func CheckInitRepository(ctx context.Context, repo *repo_model.Repository) (err
|
|||||||
// Init git bare new repository.
|
// Init git bare new repository.
|
||||||
if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil {
|
if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil {
|
||||||
return fmt.Errorf("git.InitRepository: %w", err)
|
return fmt.Errorf("git.InitRepository: %w", err)
|
||||||
} else if err = gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
|
} else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
|
||||||
return fmt.Errorf("createDelegateHooks: %w", err)
|
return fmt.Errorf("createDelegateHooks: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3679,6 +3679,7 @@ secrets=Tajné klíče
|
|||||||
description=Tejné klíče budou předány určitým akcím a nelze je přečíst jinak.
|
description=Tejné klíče budou předány určitým akcím a nelze je přečíst jinak.
|
||||||
none=Zatím zde nejsou žádné tajné klíče.
|
none=Zatím zde nejsou žádné tajné klíče.
|
||||||
creation=Přidat tajný klíč
|
creation=Přidat tajný klíč
|
||||||
|
creation.description=Popis
|
||||||
creation.name_placeholder=nerozlišovat velká a malá písmena, pouze alfanumerické znaky nebo podtržítka, nemohou začínat na GITEA_ nebo GITHUB_
|
creation.name_placeholder=nerozlišovat velká a malá písmena, pouze alfanumerické znaky nebo podtržítka, nemohou začínat na GITEA_ nebo GITHUB_
|
||||||
creation.value_placeholder=Vložte jakýkoliv obsah. Mezery na začátku a konci budou vynechány.
|
creation.value_placeholder=Vložte jakýkoliv obsah. Mezery na začátku a konci budou vynechány.
|
||||||
creation.success=Tajný klíč „%s“ byl přidán.
|
creation.success=Tajný klíč „%s“ byl přidán.
|
||||||
|
|||||||
@@ -3670,6 +3670,7 @@ secrets=Secrets
|
|||||||
description=Secrets werden an bestimmte Aktionen weitergegeben und können nicht anderweitig ausgelesen werden.
|
description=Secrets werden an bestimmte Aktionen weitergegeben und können nicht anderweitig ausgelesen werden.
|
||||||
none=Noch keine Secrets vorhanden.
|
none=Noch keine Secrets vorhanden.
|
||||||
creation=Secret hinzufügen
|
creation=Secret hinzufügen
|
||||||
|
creation.description=Beschreibung
|
||||||
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
|
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
|
||||||
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
|
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
|
||||||
creation.success=Das Secret "%s" wurde hinzugefügt.
|
creation.success=Das Secret "%s" wurde hinzugefügt.
|
||||||
|
|||||||
@@ -3338,6 +3338,7 @@ secrets=Μυστικά
|
|||||||
description=Τα μυστικά θα περάσουν σε ορισμένες δράσεις και δεν μπορούν να αναγνωστούν αλλού.
|
description=Τα μυστικά θα περάσουν σε ορισμένες δράσεις και δεν μπορούν να αναγνωστούν αλλού.
|
||||||
none=Δεν υπάρχουν ακόμα μυστικά.
|
none=Δεν υπάρχουν ακόμα μυστικά.
|
||||||
creation=Προσθήκη Μυστικού
|
creation=Προσθήκη Μυστικού
|
||||||
|
creation.description=Περιγραφή
|
||||||
creation.name_placeholder=αλφαριθμητικοί χαρακτήρες ή κάτω παύλες μόνο, δεν μπορούν να ξεκινούν με GITEA_ ή GITHUB_
|
creation.name_placeholder=αλφαριθμητικοί χαρακτήρες ή κάτω παύλες μόνο, δεν μπορούν να ξεκινούν με GITEA_ ή GITHUB_
|
||||||
creation.value_placeholder=Εισάγετε οποιοδήποτε περιεχόμενο. Τα κενά στην αρχή παραλείπονται.
|
creation.value_placeholder=Εισάγετε οποιοδήποτε περιεχόμενο. Τα κενά στην αρχή παραλείπονται.
|
||||||
creation.success=Το μυστικό "%s" προστέθηκε.
|
creation.success=Το μυστικό "%s" προστέθηκε.
|
||||||
|
|||||||
@@ -1547,8 +1547,8 @@ issues.filter_project = Project
|
|||||||
issues.filter_project_all = All projects
|
issues.filter_project_all = All projects
|
||||||
issues.filter_project_none = No project
|
issues.filter_project_none = No project
|
||||||
issues.filter_assignee = Assignee
|
issues.filter_assignee = Assignee
|
||||||
issues.filter_assginee_no_select = All assignees
|
issues.filter_assginee_no_assignee = Assigned to nobody
|
||||||
issues.filter_assginee_no_assignee = No assignee
|
issues.filter_assignee_any_assignee = Assigned to anybody
|
||||||
issues.filter_poster = Author
|
issues.filter_poster = Author
|
||||||
issues.filter_user_placeholder = Search users
|
issues.filter_user_placeholder = Search users
|
||||||
issues.filter_user_no_select = All users
|
issues.filter_user_no_select = All users
|
||||||
|
|||||||
@@ -3318,6 +3318,7 @@ secrets=Secretos
|
|||||||
description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo.
|
description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo.
|
||||||
none=Todavía no hay secretos.
|
none=Todavía no hay secretos.
|
||||||
creation=Añadir secreto
|
creation=Añadir secreto
|
||||||
|
creation.description=Descripción
|
||||||
creation.name_placeholder=sin distinción de mayúsculas, solo carácteres alfanuméricos o guiones bajos, no puede empezar por GITEA_ o GITHUB_
|
creation.name_placeholder=sin distinción de mayúsculas, solo carácteres alfanuméricos o guiones bajos, no puede empezar por GITEA_ o GITHUB_
|
||||||
creation.value_placeholder=Introduce cualquier contenido. Se omitirá el espacio en blanco en el inicio y el final.
|
creation.value_placeholder=Introduce cualquier contenido. Se omitirá el espacio en blanco en el inicio y el final.
|
||||||
creation.success=El secreto "%s" ha sido añadido.
|
creation.success=El secreto "%s" ha sido añadido.
|
||||||
|
|||||||
@@ -2513,6 +2513,7 @@ conan.details.repository=مخزن
|
|||||||
owner.settings.cleanuprules.enabled=فعال شده
|
owner.settings.cleanuprules.enabled=فعال شده
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=شرح
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -1694,6 +1694,7 @@ conan.details.repository=Repo
|
|||||||
owner.settings.cleanuprules.enabled=Käytössä
|
owner.settings.cleanuprules.enabled=Käytössä
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Kuvaus
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -3705,6 +3705,7 @@ secrets=Secrets
|
|||||||
description=Les secrets seront transmis à certaines actions et ne pourront pas être lus autrement.
|
description=Les secrets seront transmis à certaines actions et ne pourront pas être lus autrement.
|
||||||
none=Il n'y a pas encore de secrets.
|
none=Il n'y a pas encore de secrets.
|
||||||
creation=Ajouter un secret
|
creation=Ajouter un secret
|
||||||
|
creation.description=Description
|
||||||
creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement, insensibles à la casse, ne peut commencer par GITEA_ ou GITHUB_.
|
creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement, insensibles à la casse, ne peut commencer par GITEA_ ou GITHUB_.
|
||||||
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
|
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
|
||||||
creation.success=Le secret "%s" a été ajouté.
|
creation.success=Le secret "%s" a été ajouté.
|
||||||
|
|||||||
@@ -3705,6 +3705,7 @@ secrets=Rúin
|
|||||||
description=Cuirfear rúin ar aghaidh chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt.
|
description=Cuirfear rúin ar aghaidh chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt.
|
||||||
none=Níl aon rúin ann fós.
|
none=Níl aon rúin ann fós.
|
||||||
creation=Cuir Rúnda leis
|
creation=Cuir Rúnda leis
|
||||||
|
creation.description=Cur síos
|
||||||
creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féidir a thosú le GITEA_ nó GITHUB_
|
creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féidir a thosú le GITEA_ nó GITHUB_
|
||||||
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
|
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
|
||||||
creation.success=Tá an rún "%s" curtha leis.
|
creation.success=Tá an rún "%s" curtha leis.
|
||||||
|
|||||||
@@ -1598,6 +1598,7 @@ conan.details.repository=Tároló
|
|||||||
owner.settings.cleanuprules.enabled=Engedélyezett
|
owner.settings.cleanuprules.enabled=Engedélyezett
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Leírás
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -1398,6 +1398,7 @@ conan.details.repository=Repositori
|
|||||||
owner.settings.cleanuprules.enabled=Aktif
|
owner.settings.cleanuprules.enabled=Aktif
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Deskripsi
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -1326,6 +1326,7 @@ npm.details.tag=Merki
|
|||||||
pypi.requires=Þarfnast Python
|
pypi.requires=Þarfnast Python
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Lýsing
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -2790,6 +2790,7 @@ settings.delete.error=Impossibile eliminare il pacchetto.
|
|||||||
owner.settings.cleanuprules.enabled=Attivo
|
owner.settings.cleanuprules.enabled=Attivo
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Descrizione
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -3699,6 +3699,7 @@ secrets=シークレット
|
|||||||
description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。
|
description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。
|
||||||
none=シークレットはまだありません。
|
none=シークレットはまだありません。
|
||||||
creation=シークレットを追加
|
creation=シークレットを追加
|
||||||
|
creation.description=説明
|
||||||
creation.name_placeholder=大文字小文字の区別なし、英数字とアンダースコアのみ、GITEA_ や GITHUB_ で始まるものは不可
|
creation.name_placeholder=大文字小文字の区別なし、英数字とアンダースコアのみ、GITEA_ や GITHUB_ で始まるものは不可
|
||||||
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
||||||
creation.success=シークレット "%s" を追加しました。
|
creation.success=シークレット "%s" を追加しました。
|
||||||
|
|||||||
@@ -1547,6 +1547,7 @@ conan.details.repository=저장소
|
|||||||
owner.settings.cleanuprules.enabled=활성화됨
|
owner.settings.cleanuprules.enabled=활성화됨
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=설명
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -3341,6 +3341,7 @@ secrets=Noslēpumi
|
|||||||
description=Noslēpumi tiks padoti atsevišķām darbībām un citādi nevar tikt nolasīti.
|
description=Noslēpumi tiks padoti atsevišķām darbībām un citādi nevar tikt nolasīti.
|
||||||
none=Pagaidām nav neviena noslēpuma.
|
none=Pagaidām nav neviena noslēpuma.
|
||||||
creation=Pievienot noslēpumu
|
creation=Pievienot noslēpumu
|
||||||
|
creation.description=Apraksts
|
||||||
creation.name_placeholder=reģistr-nejūtīgs, tikai burti, cipari un apakšsvītras, nevar sākties ar GITEA_ vai GITHUB_
|
creation.name_placeholder=reģistr-nejūtīgs, tikai burti, cipari un apakšsvītras, nevar sākties ar GITEA_ vai GITHUB_
|
||||||
creation.value_placeholder=Ievadiet jebkādu saturu. Atstarpes sākumā un beigā tiks noņemtas.
|
creation.value_placeholder=Ievadiet jebkādu saturu. Atstarpes sākumā un beigā tiks noņemtas.
|
||||||
creation.success=Noslēpums "%s" tika pievienots.
|
creation.success=Noslēpums "%s" tika pievienots.
|
||||||
|
|||||||
@@ -2523,6 +2523,7 @@ settings.link.button=Repository link bijwerken
|
|||||||
owner.settings.cleanuprules.enabled=Ingeschakeld
|
owner.settings.cleanuprules.enabled=Ingeschakeld
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Omschrijving
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -2412,6 +2412,7 @@ conan.details.repository=Repozytorium
|
|||||||
owner.settings.cleanuprules.enabled=Włączone
|
owner.settings.cleanuprules.enabled=Włączone
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Opis
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -2194,7 +2194,7 @@ settings.protect_check_status_contexts_list=Verificações de status encontradas
|
|||||||
settings.protect_required_approvals=Aprovações necessárias:
|
settings.protect_required_approvals=Aprovações necessárias:
|
||||||
settings.dismiss_stale_approvals=Descartar aprovações obsoletas
|
settings.dismiss_stale_approvals=Descartar aprovações obsoletas
|
||||||
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
|
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
|
||||||
settings.require_signed_commits=Exibir commits assinados
|
settings.require_signed_commits=Exigir commits assinados
|
||||||
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
|
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
|
||||||
settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
|
settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
|
||||||
settings.protect_patterns=Padrões
|
settings.protect_patterns=Padrões
|
||||||
@@ -3278,6 +3278,7 @@ secrets=Segredos
|
|||||||
description=Os segredos serão passados a certas ações e não poderão ser lidos de outra forma.
|
description=Os segredos serão passados a certas ações e não poderão ser lidos de outra forma.
|
||||||
none=Não há segredos ainda.
|
none=Não há segredos ainda.
|
||||||
creation=Adicionar Segredo
|
creation=Adicionar Segredo
|
||||||
|
creation.description=Descrição
|
||||||
creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
|
creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
|
||||||
creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
|
creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
|
||||||
creation.success=O segredo "%s" foi adicionado.
|
creation.success=O segredo "%s" foi adicionado.
|
||||||
|
|||||||
@@ -3711,8 +3711,10 @@ secrets=Segredos
|
|||||||
description=Os segredos serão transmitidos a certas operações e não poderão ser lidos de outra forma.
|
description=Os segredos serão transmitidos a certas operações e não poderão ser lidos de outra forma.
|
||||||
none=Ainda não há segredos.
|
none=Ainda não há segredos.
|
||||||
creation=Adicionar segredo
|
creation=Adicionar segredo
|
||||||
|
creation.description=Descrição
|
||||||
creation.name_placeholder=Só sublinhados ou alfanuméricos sem distinguir maiúsculas, sem começar com GITEA_ nem GITHUB_
|
creation.name_placeholder=Só sublinhados ou alfanuméricos sem distinguir maiúsculas, sem começar com GITEA_ nem GITHUB_
|
||||||
creation.value_placeholder=Insira um conteúdo qualquer. Espaços em branco no início ou no fim serão omitidos.
|
creation.value_placeholder=Insira um conteúdo qualquer. Espaços em branco no início ou no fim serão omitidos.
|
||||||
|
creation.description_placeholder=Escreva uma descrição curta (opcional).
|
||||||
creation.success=O segredo "%s" foi adicionado.
|
creation.success=O segredo "%s" foi adicionado.
|
||||||
creation.failed=Falhou ao adicionar o segredo.
|
creation.failed=Falhou ao adicionar o segredo.
|
||||||
deletion=Remover segredo
|
deletion=Remover segredo
|
||||||
|
|||||||
@@ -3275,6 +3275,7 @@ secrets=Секреты
|
|||||||
description=Секреты будут передаваться определенным действиям и не могут быть прочитаны иначе.
|
description=Секреты будут передаваться определенным действиям и не могут быть прочитаны иначе.
|
||||||
none=Секретов пока нет.
|
none=Секретов пока нет.
|
||||||
creation=Добавить секрет
|
creation=Добавить секрет
|
||||||
|
creation.description=Описание
|
||||||
creation.name_placeholder=регистр не важен, только алфавитно-цифровые символы и подчёркивания, не может начинаться с GITEA_ или GITHUB_
|
creation.name_placeholder=регистр не важен, только алфавитно-цифровые символы и подчёркивания, не может начинаться с GITEA_ или GITHUB_
|
||||||
creation.value_placeholder=Введите любое содержимое. Пробельные символы в начале и конце будут опущены.
|
creation.value_placeholder=Введите любое содержимое. Пробельные символы в начале и конце будут опущены.
|
||||||
creation.success=Секрет «%s» добавлен.
|
creation.success=Секрет «%s» добавлен.
|
||||||
|
|||||||
@@ -2454,6 +2454,7 @@ conan.details.repository=කෝෂ්ඨය
|
|||||||
owner.settings.cleanuprules.enabled=සබල කර ඇත
|
owner.settings.cleanuprules.enabled=සබල කර ඇත
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=සවිස්තරය
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -1989,6 +1989,7 @@ conan.details.repository=Utvecklingskatalog
|
|||||||
owner.settings.cleanuprules.enabled=Aktiv
|
owner.settings.cleanuprules.enabled=Aktiv
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Beskrivning
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -3534,6 +3534,7 @@ secrets=Gizlilikler
|
|||||||
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
|
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
|
||||||
none=Henüz gizlilik yok.
|
none=Henüz gizlilik yok.
|
||||||
creation=Gizlilik Ekle
|
creation=Gizlilik Ekle
|
||||||
|
creation.description=Açıklama
|
||||||
creation.name_placeholder=küçük-büyük harfe duyarlı değil, alfanümerik karakterler veya sadece alt tire, GITEA_ veya GITHUB_ ile başlayamaz
|
creation.name_placeholder=küçük-büyük harfe duyarlı değil, alfanümerik karakterler veya sadece alt tire, GITEA_ veya GITHUB_ ile başlayamaz
|
||||||
creation.value_placeholder=Herhangi bir içerik girin. Baştaki ve sondaki boşluklar ihmal edilecektir.
|
creation.value_placeholder=Herhangi bir içerik girin. Baştaki ve sondaki boşluklar ihmal edilecektir.
|
||||||
creation.success=Gizlilik "%s" eklendi.
|
creation.success=Gizlilik "%s" eklendi.
|
||||||
|
|||||||
@@ -2524,6 +2524,7 @@ conan.details.repository=Репозиторій
|
|||||||
owner.settings.cleanuprules.enabled=Увімкнено
|
owner.settings.cleanuprules.enabled=Увімкнено
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=Опис
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -3658,6 +3658,7 @@ secrets=密钥
|
|||||||
description=Secrets 将被传给特定的 Actions,其它情况将不能读取
|
description=Secrets 将被传给特定的 Actions,其它情况将不能读取
|
||||||
none=还没有密钥。
|
none=还没有密钥。
|
||||||
creation=添加密钥
|
creation=添加密钥
|
||||||
|
creation.description=组织描述
|
||||||
creation.name_placeholder=不区分大小写,字母数字或下划线不能以GITEA_ 或 GITHUB_ 开头。
|
creation.name_placeholder=不区分大小写,字母数字或下划线不能以GITEA_ 或 GITHUB_ 开头。
|
||||||
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
||||||
creation.success=您的密钥 '%s' 添加成功。
|
creation.success=您的密钥 '%s' 添加成功。
|
||||||
|
|||||||
@@ -963,6 +963,7 @@ conan.details.repository=儲存庫
|
|||||||
owner.settings.cleanuprules.enabled=已啟用
|
owner.settings.cleanuprules.enabled=已啟用
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
creation.description=組織描述
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|||||||
@@ -3646,6 +3646,7 @@ secrets=Secret
|
|||||||
description=Secret 會被傳給特定的 Action,其他情況無法讀取。
|
description=Secret 會被傳給特定的 Action,其他情況無法讀取。
|
||||||
none=還沒有 Secret。
|
none=還沒有 Secret。
|
||||||
creation=加入 Secret
|
creation=加入 Secret
|
||||||
|
creation.description=描述
|
||||||
creation.name_placeholder=不區分大小寫,只能包含英文字母、數字、底線 ('_'),不能以 GITEA_ 或 GITHUB_ 開頭。
|
creation.name_placeholder=不區分大小寫,只能包含英文字母、數字、底線 ('_'),不能以 GITEA_ 或 GITHUB_ 開頭。
|
||||||
creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。
|
creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。
|
||||||
creation.success=已新增 Secret「%s」。
|
creation.success=已新增 Secret「%s」。
|
||||||
|
|||||||
@@ -290,10 +290,10 @@ func SearchIssues(ctx *context.APIContext) {
|
|||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
ctxUserID := ctx.Doer.ID
|
ctxUserID := ctx.Doer.ID
|
||||||
if ctx.FormBool("created") {
|
if ctx.FormBool("created") {
|
||||||
searchOpt.PosterID = optional.Some(ctxUserID)
|
searchOpt.PosterID = strconv.FormatInt(ctxUserID, 10)
|
||||||
}
|
}
|
||||||
if ctx.FormBool("assigned") {
|
if ctx.FormBool("assigned") {
|
||||||
searchOpt.AssigneeID = optional.Some(ctxUserID)
|
searchOpt.AssigneeID = strconv.FormatInt(ctxUserID, 10)
|
||||||
}
|
}
|
||||||
if ctx.FormBool("mentioned") {
|
if ctx.FormBool("mentioned") {
|
||||||
searchOpt.MentionID = optional.Some(ctxUserID)
|
searchOpt.MentionID = optional.Some(ctxUserID)
|
||||||
@@ -538,10 +538,10 @@ func ListIssues(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createdByID > 0 {
|
if createdByID > 0 {
|
||||||
searchOpt.PosterID = optional.Some(createdByID)
|
searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
|
||||||
}
|
}
|
||||||
if assignedByID > 0 {
|
if assignedByID > 0 {
|
||||||
searchOpt.AssigneeID = optional.Some(assignedByID)
|
searchOpt.AssigneeID = strconv.FormatInt(assignedByID, 10)
|
||||||
}
|
}
|
||||||
if mentionedByID > 0 {
|
if mentionedByID > 0 {
|
||||||
searchOpt.MentionID = optional.Some(mentionedByID)
|
searchOpt.MentionID = optional.Some(mentionedByID)
|
||||||
|
|||||||
@@ -476,7 +476,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
|
|||||||
// findWikiRepoCommit opens the wiki repo and returns the latest commit, writing to context on error.
|
// findWikiRepoCommit opens the wiki repo and returns the latest commit, writing to context on error.
|
||||||
// The caller is responsible for closing the returned repo again
|
// The caller is responsible for closing the returned repo again
|
||||||
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
|
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
|
||||||
wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
|
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
|
|||||||
@@ -347,11 +347,11 @@ func ViewProject(ctx *context.Context) {
|
|||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
|
assigneeID := ctx.FormString("assignee")
|
||||||
|
|
||||||
opts := issues_model.IssuesOptions{
|
opts := issues_model.IssuesOptions{
|
||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
AssigneeID: optional.Some(assigneeID),
|
AssigneeID: assigneeID,
|
||||||
Owner: project.Owner,
|
Owner: project.Owner,
|
||||||
Doer: ctx.Doer,
|
Doer: ctx.Doer,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ func Diff(ctx *context.Context) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if ctx.Data["PageIsWiki"] != nil {
|
if ctx.Data["PageIsWiki"] != nil {
|
||||||
gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Repo.GitRepo.GetCommit", err)
|
ctx.ServerError("Repo.GitRepo.GetCommit", err)
|
||||||
return
|
return
|
||||||
@@ -417,7 +417,7 @@ func Diff(ctx *context.Context) {
|
|||||||
func RawDiff(ctx *context.Context) {
|
func RawDiff(ctx *context.Context) {
|
||||||
var gitRepo *git.Repository
|
var gitRepo *git.Repository
|
||||||
if ctx.Data["PageIsWiki"] != nil {
|
if ctx.Data["PageIsWiki"] != nil {
|
||||||
wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("OpenRepository", err)
|
ctx.ServerError("OpenRepository", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -885,7 +885,7 @@ func ExcerptBlob(ctx *context.Context) {
|
|||||||
gitRepo := ctx.Repo.GitRepo
|
gitRepo := ctx.Repo.GitRepo
|
||||||
if ctx.Data["PageIsWiki"] == true {
|
if ctx.Data["PageIsWiki"] == true {
|
||||||
var err error
|
var err error
|
||||||
gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("OpenRepository", err)
|
ctx.ServerError("OpenRepository", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -208,10 +208,10 @@ func SearchIssues(ctx *context.Context) {
|
|||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
ctxUserID := ctx.Doer.ID
|
ctxUserID := ctx.Doer.ID
|
||||||
if ctx.FormBool("created") {
|
if ctx.FormBool("created") {
|
||||||
searchOpt.PosterID = optional.Some(ctxUserID)
|
searchOpt.PosterID = strconv.FormatInt(ctxUserID, 10)
|
||||||
}
|
}
|
||||||
if ctx.FormBool("assigned") {
|
if ctx.FormBool("assigned") {
|
||||||
searchOpt.AssigneeID = optional.Some(ctxUserID)
|
searchOpt.AssigneeID = strconv.FormatInt(ctxUserID, 10)
|
||||||
}
|
}
|
||||||
if ctx.FormBool("mentioned") {
|
if ctx.FormBool("mentioned") {
|
||||||
searchOpt.MentionID = optional.Some(ctxUserID)
|
searchOpt.MentionID = optional.Some(ctxUserID)
|
||||||
@@ -373,10 +373,10 @@ func SearchRepoIssuesJSON(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if createdByID > 0 {
|
if createdByID > 0 {
|
||||||
searchOpt.PosterID = optional.Some(createdByID)
|
searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
|
||||||
}
|
}
|
||||||
if assignedByID > 0 {
|
if assignedByID > 0 {
|
||||||
searchOpt.AssigneeID = optional.Some(assignedByID)
|
searchOpt.AssigneeID = strconv.FormatInt(assignedByID, 10)
|
||||||
}
|
}
|
||||||
if mentionedByID > 0 {
|
if mentionedByID > 0 {
|
||||||
searchOpt.MentionID = optional.Some(mentionedByID)
|
searchOpt.MentionID = optional.Some(mentionedByID)
|
||||||
@@ -490,7 +490,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||||||
viewType = "all"
|
viewType = "all"
|
||||||
}
|
}
|
||||||
|
|
||||||
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
|
assigneeID := ctx.FormString("assignee")
|
||||||
posterUsername := ctx.FormString("poster")
|
posterUsername := ctx.FormString("poster")
|
||||||
posterUserID := shared_user.GetFilterUserIDByName(ctx, posterUsername)
|
posterUserID := shared_user.GetFilterUserIDByName(ctx, posterUsername)
|
||||||
var mentionedID, reviewRequestedID, reviewedID int64
|
var mentionedID, reviewRequestedID, reviewedID int64
|
||||||
@@ -498,11 +498,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
switch viewType {
|
switch viewType {
|
||||||
case "created_by":
|
case "created_by":
|
||||||
posterUserID = optional.Some(ctx.Doer.ID)
|
posterUserID = strconv.FormatInt(ctx.Doer.ID, 10)
|
||||||
case "mentioned":
|
case "mentioned":
|
||||||
mentionedID = ctx.Doer.ID
|
mentionedID = ctx.Doer.ID
|
||||||
case "assigned":
|
case "assigned":
|
||||||
assigneeID = ctx.Doer.ID
|
assigneeID = fmt.Sprint(ctx.Doer.ID)
|
||||||
case "review_requested":
|
case "review_requested":
|
||||||
reviewRequestedID = ctx.Doer.ID
|
reviewRequestedID = ctx.Doer.ID
|
||||||
case "reviewed_by":
|
case "reviewed_by":
|
||||||
@@ -532,7 +532,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
MilestoneIDs: mileIDs,
|
MilestoneIDs: mileIDs,
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
AssigneeID: optional.Some(assigneeID),
|
AssigneeID: assigneeID,
|
||||||
MentionedID: mentionedID,
|
MentionedID: mentionedID,
|
||||||
PosterID: posterUserID,
|
PosterID: posterUserID,
|
||||||
ReviewRequestedID: reviewRequestedID,
|
ReviewRequestedID: reviewRequestedID,
|
||||||
@@ -613,7 +613,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||||||
PageSize: setting.UI.IssuePagingNum,
|
PageSize: setting.UI.IssuePagingNum,
|
||||||
},
|
},
|
||||||
RepoIDs: []int64{repo.ID},
|
RepoIDs: []int64{repo.ID},
|
||||||
AssigneeID: optional.Some(assigneeID),
|
AssigneeID: assigneeID,
|
||||||
PosterID: posterUserID,
|
PosterID: posterUserID,
|
||||||
MentionedID: mentionedID,
|
MentionedID: mentionedID,
|
||||||
ReviewRequestedID: reviewRequestedID,
|
ReviewRequestedID: reviewRequestedID,
|
||||||
|
|||||||
@@ -315,12 +315,12 @@ func ViewProject(ctx *context.Context) {
|
|||||||
|
|
||||||
labelIDs := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner)
|
labelIDs := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner)
|
||||||
|
|
||||||
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
|
assigneeID := ctx.FormString("assignee")
|
||||||
|
|
||||||
issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &issues_model.IssuesOptions{
|
issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &issues_model.IssuesOptions{
|
||||||
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
AssigneeID: optional.Some(assigneeID),
|
AssigneeID: assigneeID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadIssuesOfColumns", err)
|
ctx.ServerError("LoadIssuesOfColumns", err)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
|
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
|
||||||
wikiGitRepo, errGitRepo := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
wikiGitRepo, errGitRepo := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||||
if errGitRepo != nil {
|
if errGitRepo != nil {
|
||||||
ctx.ServerError("OpenRepository", errGitRepo)
|
ctx.ServerError("OpenRepository", errGitRepo)
|
||||||
return nil, nil, errGitRepo
|
return nil, nil, errGitRepo
|
||||||
@@ -105,7 +105,7 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err
|
|||||||
commit, errCommit := wikiGitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
|
commit, errCommit := wikiGitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
|
||||||
if git.IsErrNotExist(errCommit) {
|
if git.IsErrNotExist(errCommit) {
|
||||||
// if the default branch recorded in database is out of sync, then re-sync it
|
// if the default branch recorded in database is out of sync, then re-sync it
|
||||||
gitRepoDefaultBranch, errBranch := gitrepo.GetWikiDefaultBranch(ctx, ctx.Repo.Repository)
|
gitRepoDefaultBranch, errBranch := gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||||
if errBranch != nil {
|
if errBranch != nil {
|
||||||
return wikiGitRepo, nil, errBranch
|
return wikiGitRepo, nil, errBranch
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) *git.TreeEntry {
|
func wikiEntry(t *testing.T, repo *repo_model.Repository, wikiName wiki_service.WebPath) *git.TreeEntry {
|
||||||
wikiRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
|
wikiRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo.WikiStorageRepo())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
defer wikiRepo.Close()
|
defer wikiRepo.Close()
|
||||||
commit, err := wikiRepo.GetBranchCommit("master")
|
commit, err := wikiRepo.GetBranchCommit("master")
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/user"
|
"code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
||||||
@@ -34,19 +32,20 @@ func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
|||||||
// So it's better to make it work like GitHub: users could input username directly.
|
// So it's better to make it work like GitHub: users could input username directly.
|
||||||
// Since it only converts the username to ID directly and is only used internally (to search issues), so no permission check is needed.
|
// Since it only converts the username to ID directly and is only used internally (to search issues), so no permission check is needed.
|
||||||
// Return values:
|
// Return values:
|
||||||
// * nil: no filter
|
// * "": no filter
|
||||||
// * some(id): match the id, the id could be -1 to match the issues without assignee
|
// * "{the-id}": match the id
|
||||||
// * some(NonExistingID): match no issue (due to the user doesn't exist)
|
// * "(none)": match no issue (due to the user doesn't exist)
|
||||||
func GetFilterUserIDByName(ctx context.Context, name string) optional.Option[int64] {
|
func GetFilterUserIDByName(ctx context.Context, name string) string {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return optional.None[int64]()
|
return ""
|
||||||
}
|
}
|
||||||
u, err := user.GetUserByName(ctx, name)
|
u, err := user.GetUserByName(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if id, err := strconv.ParseInt(name, 10, 64); err == nil {
|
if id, err := strconv.ParseInt(name, 10, 64); err == nil {
|
||||||
return optional.Some(id)
|
return strconv.FormatInt(id, 10)
|
||||||
}
|
}
|
||||||
return optional.Some(db.NonExistingID)
|
// The "(none)" is for internal usage only: when doer tries to search non-existing user, use "(none)" to return empty result.
|
||||||
|
return "(none)"
|
||||||
}
|
}
|
||||||
return optional.Some(u.ID)
|
return strconv.FormatInt(u.ID, 10)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ func Dashboard(ctx *context.Context) {
|
|||||||
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
|
ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
feeds, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{
|
feeds, count, err := feed_service.GetFeedsForDashboard(ctx, activities_model.GetFeedsOptions{
|
||||||
RequestedUser: ctxUser,
|
RequestedUser: ctxUser,
|
||||||
RequestedTeam: ctx.Org.Team,
|
RequestedTeam: ctx.Org.Team,
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
@@ -501,9 +501,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
|||||||
case issues_model.FilterModeAll:
|
case issues_model.FilterModeAll:
|
||||||
case issues_model.FilterModeYourRepositories:
|
case issues_model.FilterModeYourRepositories:
|
||||||
case issues_model.FilterModeAssign:
|
case issues_model.FilterModeAssign:
|
||||||
opts.AssigneeID = optional.Some(ctx.Doer.ID)
|
opts.AssigneeID = strconv.FormatInt(ctx.Doer.ID, 10)
|
||||||
case issues_model.FilterModeCreate:
|
case issues_model.FilterModeCreate:
|
||||||
opts.PosterID = optional.Some(ctx.Doer.ID)
|
opts.PosterID = strconv.FormatInt(ctx.Doer.ID, 10)
|
||||||
case issues_model.FilterModeMention:
|
case issues_model.FilterModeMention:
|
||||||
opts.MentionedID = ctx.Doer.ID
|
opts.MentionedID = ctx.Doer.ID
|
||||||
case issues_model.FilterModeReviewRequested:
|
case issues_model.FilterModeReviewRequested:
|
||||||
@@ -792,9 +792,9 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
|
|||||||
case issues_model.FilterModeYourRepositories:
|
case issues_model.FilterModeYourRepositories:
|
||||||
openClosedOpts.AllPublic = false
|
openClosedOpts.AllPublic = false
|
||||||
case issues_model.FilterModeAssign:
|
case issues_model.FilterModeAssign:
|
||||||
openClosedOpts.AssigneeID = optional.Some(doerID)
|
openClosedOpts.AssigneeID = strconv.FormatInt(doerID, 10)
|
||||||
case issues_model.FilterModeCreate:
|
case issues_model.FilterModeCreate:
|
||||||
openClosedOpts.PosterID = optional.Some(doerID)
|
openClosedOpts.PosterID = strconv.FormatInt(doerID, 10)
|
||||||
case issues_model.FilterModeMention:
|
case issues_model.FilterModeMention:
|
||||||
openClosedOpts.MentionID = optional.Some(doerID)
|
openClosedOpts.MentionID = optional.Some(doerID)
|
||||||
case issues_model.FilterModeReviewRequested:
|
case issues_model.FilterModeReviewRequested:
|
||||||
@@ -816,8 +816,8 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
|
|||||||
|
|
||||||
// Below stats are for the left sidebar
|
// Below stats are for the left sidebar
|
||||||
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
|
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
|
||||||
o.AssigneeID = nil
|
o.AssigneeID = ""
|
||||||
o.PosterID = nil
|
o.PosterID = ""
|
||||||
o.MentionID = nil
|
o.MentionID = nil
|
||||||
o.ReviewRequestedID = nil
|
o.ReviewRequestedID = nil
|
||||||
o.ReviewedID = nil
|
o.ReviewedID = nil
|
||||||
@@ -827,11 +827,11 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = optional.Some(doerID) }))
|
ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = strconv.FormatInt(doerID, 10) }))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = optional.Some(doerID) }))
|
ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = strconv.FormatInt(doerID, 10) }))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ Loop:
|
|||||||
return false, "", nil, &ErrWontSign{twofa}
|
return false, "", nil, &ErrWontSign{twofa}
|
||||||
}
|
}
|
||||||
case parentSigned:
|
case parentSigned:
|
||||||
gitRepo, err := gitrepo.OpenWikiRepository(ctx, repo)
|
gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", nil, err
|
return false, "", nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,14 +49,14 @@ func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error
|
|||||||
|
|
||||||
func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
|
func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||||
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
|
||||||
results, err := gitrepo.CheckDelegateHooksForRepo(ctx, repo)
|
results, err := gitrepo.CheckDelegateHooks(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
|
logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
|
||||||
return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err)
|
return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err)
|
||||||
}
|
}
|
||||||
if len(results) > 0 && autofix {
|
if len(results) > 0 && autofix {
|
||||||
logger.Warn("Regenerated hooks for %s", repo.FullName())
|
logger.Warn("Regenerated hooks for %s", repo.FullName())
|
||||||
if err := gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
|
if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
|
||||||
logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err)
|
logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err)
|
||||||
return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err)
|
return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,28 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func userFeedCacheKey(userID int64) string {
|
||||||
|
return fmt.Sprintf("user_feed_%d", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFeedsForDashboard(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
|
||||||
|
opts.DontCount = opts.RequestedTeam == nil && opts.Date == ""
|
||||||
|
results, cnt, err := activities_model.GetFeeds(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if opts.DontCount {
|
||||||
|
cnt, err = cache.GetInt64(userFeedCacheKey(opts.Actor.ID), func() (int64, error) {
|
||||||
|
return activities_model.CountUserFeeds(ctx, opts.Actor.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return results, cnt, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetFeeds returns actions according to the provided options
|
// GetFeeds returns actions according to the provided options
|
||||||
func GetFeeds(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
|
func GetFeeds(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
|
||||||
return activities_model.GetFeeds(ctx, opts)
|
return activities_model.GetFeeds(ctx, opts)
|
||||||
@@ -68,6 +87,13 @@ func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers
|
|||||||
if err := db.Insert(ctx, act); err != nil {
|
if err := db.Insert(ctx, act); err != nil {
|
||||||
return fmt.Errorf("insert new action: %w", err)
|
return fmt.Errorf("insert new action: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
total, err := activities_model.CountUserFeeds(ctx, act.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("count user feeds: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = cache.GetCache().Put(userFeedCacheKey(act.UserID), fmt.Sprintf("%d", total), setting.CacheService.TTLSeconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
|
|||||||
|
|
||||||
var gitRepo *git.Repository
|
var gitRepo *git.Repository
|
||||||
if isWiki {
|
if isWiki {
|
||||||
gitRepo, err = gitrepo.OpenWikiRepository(ctx, repo)
|
gitRepo, err = gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
|
||||||
} else {
|
} else {
|
||||||
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
|
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr
|
|||||||
return fmt.Errorf("adoptRepository: path does not already exist: %s", repo.FullName())
|
return fmt.Errorf("adoptRepository: path does not already exist: %s", repo.FullName())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
|
if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
|
||||||
return fmt.Errorf("createDelegateHooks: %w", err)
|
return fmt.Errorf("createDelegateHooks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
|
|||||||
return fmt.Errorf("git update-server-info: %w", err)
|
return fmt.Errorf("git update-server-info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
|
if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
|
||||||
return fmt.Errorf("createDelegateHooks: %w", err)
|
return fmt.Errorf("createDelegateHooks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ func SyncRepositoryHooks(ctx context.Context) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
|
if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
|
||||||
return fmt.Errorf("SyncRepositoryHook: %w", err)
|
return fmt.Errorf("SyncRepositoryHook: %w", err)
|
||||||
}
|
}
|
||||||
if repo.HasWiki() {
|
if repo.HasWiki() {
|
||||||
if err := gitrepo.CreateDelegateHooksForWiki(ctx, repo); err != nil {
|
if err := gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
|
||||||
return fmt.Errorf("SyncRepositoryHook: %w", err)
|
return fmt.Errorf("SyncRepositoryHook: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,11 +265,11 @@ func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error {
|
|||||||
|
|
||||||
// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
|
// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
|
||||||
func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
|
func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
|
||||||
if err := gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
|
if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
|
||||||
return repo, fmt.Errorf("createDelegateHooks: %w", err)
|
return repo, fmt.Errorf("createDelegateHooks: %w", err)
|
||||||
}
|
}
|
||||||
if repo.HasWiki() {
|
if repo.HasWiki() {
|
||||||
if err := gitrepo.CreateDelegateHooksForWiki(ctx, repo); err != nil {
|
if err := gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
|
||||||
return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
|
return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-86
@@ -167,8 +167,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
branch := opts.RefFullName.BranchName()
|
|
||||||
if !opts.IsDelRef() {
|
if !opts.IsDelRef() {
|
||||||
|
branch := opts.RefFullName.BranchName()
|
||||||
|
|
||||||
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
|
||||||
|
|
||||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||||
@@ -176,60 +177,15 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
return fmt.Errorf("gitRepo.GetCommit(%s) in %s/%s[%d]: %w", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
|
return fmt.Errorf("gitRepo.GetCommit(%s) in %s/%s[%d]: %w", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
refName := opts.RefName()
|
|
||||||
|
|
||||||
// Push new branch.
|
// Push new branch.
|
||||||
var l []*git.Commit
|
var l []*git.Commit
|
||||||
if opts.IsNewRef() {
|
if opts.IsNewRef() {
|
||||||
if repo.IsEmpty { // Change default branch and empty status only if pushed ref is non-empty branch.
|
l, err = pushNewBranch(ctx, repo, pusher, opts, newCommit)
|
||||||
repo.DefaultBranch = refName
|
|
||||||
repo.IsEmpty = false
|
|
||||||
if repo.DefaultBranch != setting.Repository.DefaultBranch {
|
|
||||||
if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update the is empty and default_branch columns
|
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil {
|
|
||||||
return fmt.Errorf("UpdateRepositoryCols: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err = newCommit.CommitsBeforeLimit(10)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("newCommit.CommitsBeforeLimit: %w", err)
|
|
||||||
}
|
|
||||||
notify_service.CreateRef(ctx, pusher, repo, opts.RefFullName, opts.NewCommitID)
|
|
||||||
} else {
|
} else {
|
||||||
l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
|
l, err = pushUpdateBranch(ctx, repo, pusher, opts, newCommit)
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("newCommit.CommitsBeforeUntil: %w", err)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
isForcePush, err := newCommit.IsForcePush(opts.OldCommitID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// only update branch can trigger pull request task because the pull request hasn't been created yet when creaing a branch
|
|
||||||
go pull_service.AddTestPullRequestTask(pull_service.TestPullRequestOptions{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Doer: pusher,
|
|
||||||
Branch: branch,
|
|
||||||
IsSync: true,
|
|
||||||
IsForcePush: isForcePush,
|
|
||||||
OldCommitID: opts.OldCommitID,
|
|
||||||
NewCommitID: opts.NewCommitID,
|
|
||||||
})
|
|
||||||
|
|
||||||
if isForcePush {
|
|
||||||
log.Trace("Push %s is a force push", opts.NewCommitID)
|
|
||||||
|
|
||||||
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
|
|
||||||
} else {
|
|
||||||
// TODO: increment update the commit count cache but not remove
|
|
||||||
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete cache for divergence
|
// delete cache for divergence
|
||||||
@@ -246,36 +202,11 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
commits := repo_module.GitToPushCommits(l)
|
commits := repo_module.GitToPushCommits(l)
|
||||||
commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
|
commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
|
||||||
|
|
||||||
if err := issue_service.UpdateIssuesCommit(ctx, pusher, repo, commits.Commits, refName); err != nil {
|
if err := issue_service.UpdateIssuesCommit(ctx, pusher, repo, commits.Commits, opts.RefName()); err != nil {
|
||||||
log.Error("updateIssuesCommit: %v", err)
|
log.Error("updateIssuesCommit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldCommitID := opts.OldCommitID
|
commits.CompareURL = getCompareURL(repo, gitRepo, objectFormat, commits.Commits, opts)
|
||||||
if oldCommitID == objectFormat.EmptyObjectID().String() && len(commits.Commits) > 0 {
|
|
||||||
oldCommit, err := gitRepo.GetCommit(commits.Commits[len(commits.Commits)-1].Sha1)
|
|
||||||
if err != nil && !git.IsErrNotExist(err) {
|
|
||||||
log.Error("unable to GetCommit %s from %-v: %v", oldCommitID, repo, err)
|
|
||||||
}
|
|
||||||
if oldCommit != nil {
|
|
||||||
for i := 0; i < oldCommit.ParentCount(); i++ {
|
|
||||||
commitID, _ := oldCommit.ParentID(i)
|
|
||||||
if !commitID.IsZero() {
|
|
||||||
oldCommitID = commitID.String()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldCommitID == objectFormat.EmptyObjectID().String() && repo.DefaultBranch != branch {
|
|
||||||
oldCommitID = repo.DefaultBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldCommitID != objectFormat.EmptyObjectID().String() {
|
|
||||||
commits.CompareURL = repo.ComposeCompareURL(oldCommitID, opts.NewCommitID)
|
|
||||||
} else {
|
|
||||||
commits.CompareURL = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(commits.Commits) > setting.UI.FeedMaxCommitNum {
|
if len(commits.Commits) > setting.UI.FeedMaxCommitNum {
|
||||||
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
|
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
|
||||||
@@ -288,12 +219,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
|
log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notify_service.DeleteRef(ctx, pusher, repo, opts.RefFullName)
|
pushDeleteBranch(ctx, repo, pusher, opts)
|
||||||
|
|
||||||
if err := pull_service.AdjustPullsCausedByBranchDeleted(ctx, pusher, repo, branch); err != nil {
|
|
||||||
// close all related pulls
|
|
||||||
log.Error("close related pull request failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even if user delete a branch on a repository which he didn't watch, he will be watch that.
|
// Even if user delete a branch on a repository which he didn't watch, he will be watch that.
|
||||||
@@ -304,8 +230,11 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
log.Trace("Non-tag and non-branch commits pushed.")
|
log.Trace("Non-tag and non-branch commits pushed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, addTags, delTags); err != nil {
|
|
||||||
return fmt.Errorf("PushUpdateAddDeleteTags: %w", err)
|
if len(addTags)+len(delTags) > 0 {
|
||||||
|
if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, addTags, delTags); err != nil {
|
||||||
|
return fmt.Errorf("PushUpdateAddDeleteTags: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change repository last updated time.
|
// Change repository last updated time.
|
||||||
@@ -316,6 +245,102 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCompareURL(repo *repo_model.Repository, gitRepo *git.Repository, objectFormat git.ObjectFormat, commits []*repo_module.PushCommit, opts *repo_module.PushUpdateOptions) string {
|
||||||
|
oldCommitID := opts.OldCommitID
|
||||||
|
if oldCommitID == objectFormat.EmptyObjectID().String() && len(commits) > 0 {
|
||||||
|
oldCommit, err := gitRepo.GetCommit(commits[len(commits)-1].Sha1)
|
||||||
|
if err != nil && !git.IsErrNotExist(err) {
|
||||||
|
log.Error("unable to GetCommit %s from %-v: %v", oldCommitID, repo, err)
|
||||||
|
}
|
||||||
|
if oldCommit != nil {
|
||||||
|
for i := 0; i < oldCommit.ParentCount(); i++ {
|
||||||
|
commitID, _ := oldCommit.ParentID(i)
|
||||||
|
if !commitID.IsZero() {
|
||||||
|
oldCommitID = commitID.String()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldCommitID == objectFormat.EmptyObjectID().String() && repo.DefaultBranch != opts.RefFullName.BranchName() {
|
||||||
|
oldCommitID = repo.DefaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldCommitID != objectFormat.EmptyObjectID().String() {
|
||||||
|
return repo.ComposeCompareURL(oldCommitID, opts.NewCommitID)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushNewBranch(ctx context.Context, repo *repo_model.Repository, pusher *user_model.User, opts *repo_module.PushUpdateOptions, newCommit *git.Commit) ([]*git.Commit, error) {
|
||||||
|
if repo.IsEmpty { // Change default branch and empty status only if pushed ref is non-empty branch.
|
||||||
|
repo.DefaultBranch = opts.RefName()
|
||||||
|
repo.IsEmpty = false
|
||||||
|
if repo.DefaultBranch != setting.Repository.DefaultBranch {
|
||||||
|
if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the is empty and default_branch columns
|
||||||
|
if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil {
|
||||||
|
return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := newCommit.CommitsBeforeLimit(10)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %w", err)
|
||||||
|
}
|
||||||
|
notify_service.CreateRef(ctx, pusher, repo, opts.RefFullName, opts.NewCommitID)
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushUpdateBranch(_ context.Context, repo *repo_model.Repository, pusher *user_model.User, opts *repo_module.PushUpdateOptions, newCommit *git.Commit) ([]*git.Commit, error) {
|
||||||
|
l, err := newCommit.CommitsBeforeUntil(opts.OldCommitID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
branch := opts.RefFullName.BranchName()
|
||||||
|
|
||||||
|
isForcePush, err := newCommit.IsForcePush(opts.OldCommitID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only update branch can trigger pull request task because the pull request hasn't been created yet when creating a branch
|
||||||
|
go pull_service.AddTestPullRequestTask(pull_service.TestPullRequestOptions{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Doer: pusher,
|
||||||
|
Branch: branch,
|
||||||
|
IsSync: true,
|
||||||
|
IsForcePush: isForcePush,
|
||||||
|
OldCommitID: opts.OldCommitID,
|
||||||
|
NewCommitID: opts.NewCommitID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if isForcePush {
|
||||||
|
log.Trace("Push %s is a force push", opts.NewCommitID)
|
||||||
|
|
||||||
|
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
|
||||||
|
} else {
|
||||||
|
// TODO: increment update the commit count cache but not remove
|
||||||
|
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushDeleteBranch(ctx context.Context, repo *repo_model.Repository, pusher *user_model.User, opts *repo_module.PushUpdateOptions) {
|
||||||
|
notify_service.DeleteRef(ctx, pusher, repo, opts.RefFullName)
|
||||||
|
|
||||||
|
if err := pull_service.AdjustPullsCausedByBranchDeleted(ctx, pusher, repo, opts.RefFullName.BranchName()); err != nil {
|
||||||
|
// close all related pulls
|
||||||
|
log.Error("close related pull request failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PushUpdateAddDeleteTags updates a number of added and delete tags
|
// PushUpdateAddDeleteTags updates a number of added and delete tags
|
||||||
func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error {
|
func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
|||||||
@@ -331,12 +331,13 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
|
|||||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||||
} else if has {
|
} else if has {
|
||||||
return repo_model.ErrRepoAlreadyExist{
|
return repo_model.ErrRepoAlreadyExist{
|
||||||
Uname: repo.Owner.Name,
|
Uname: repo.OwnerName,
|
||||||
Name: newRepoName,
|
Name: newRepoName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gitrepo.RenameRepository(ctx, repo, newRepoName); err != nil {
|
if err = gitrepo.RenameRepository(ctx, repo,
|
||||||
|
repo_model.StorageRepo(repo_model.RelativePath(repo.OwnerName, newRepoName))); err != nil {
|
||||||
return fmt.Errorf("rename repository directory: %w", err)
|
return fmt.Errorf("rename repository directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error {
|
|||||||
|
|
||||||
if err := git.InitRepository(ctx, repo.WikiPath(), true, repo.ObjectFormatName); err != nil {
|
if err := git.InitRepository(ctx, repo.WikiPath(), true, repo.ObjectFormatName); err != nil {
|
||||||
return fmt.Errorf("InitRepository: %w", err)
|
return fmt.Errorf("InitRepository: %w", err)
|
||||||
} else if err = gitrepo.CreateDelegateHooksForWiki(ctx, repo); err != nil {
|
} else if err = gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
|
||||||
return fmt.Errorf("createDelegateHooks: %w", err)
|
return fmt.Errorf("createDelegateHooks: %w", err)
|
||||||
} else if _, _, err = git.NewCommand("symbolic-ref", "HEAD").AddDynamicArguments(git.BranchPrefix+repo.DefaultWikiBranch).RunStdString(ctx, &git.RunOpts{Dir: repo.WikiPath()}); err != nil {
|
} else if _, _, err = git.NewCommand("symbolic-ref", "HEAD").AddDynamicArguments(git.BranchPrefix+repo.DefaultWikiBranch).RunStdString(ctx, &git.RunOpts{Dir: repo.WikiPath()}); err != nil {
|
||||||
return fmt.Errorf("unable to set default wiki branch to %q: %w", repo.DefaultWikiBranch, err)
|
return fmt.Errorf("unable to set default wiki branch to %q: %w", repo.DefaultWikiBranch, err)
|
||||||
@@ -100,7 +100,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
|
|||||||
return fmt.Errorf("InitWiki: %w", err)
|
return fmt.Errorf("InitWiki: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasDefaultBranch := gitrepo.IsWikiBranchExist(ctx, repo, repo.DefaultWikiBranch)
|
hasDefaultBranch := gitrepo.IsBranchExist(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch)
|
||||||
|
|
||||||
basePath, err := repo_module.CreateTemporaryPath("update-wiki")
|
basePath, err := repo_module.CreateTemporaryPath("update-wiki")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -381,7 +381,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
oldDefBranch, err := gitrepo.GetWikiDefaultBranch(ctx, repo)
|
oldDefBranch, err := gitrepo.GetDefaultBranch(ctx, repo.WikiStorageRepo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to get default branch: %w", err)
|
return fmt.Errorf("unable to get default branch: %w", err)
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := gitrepo.OpenWikiRepository(ctx, repo)
|
gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
return nil // no git repo on storage, no need to do anything else
|
return nil // no git repo on storage, no need to do anything else
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ func TestRepository_AddWikiPage(t *testing.T) {
|
|||||||
webPath := UserTitleToWebPath("", userTitle)
|
webPath := UserTitleToWebPath("", userTitle)
|
||||||
assert.NoError(t, AddWikiPage(git.DefaultContext, doer, repo, webPath, wikiContent, commitMsg))
|
assert.NoError(t, AddWikiPage(git.DefaultContext, doer, repo, webPath, wikiContent, commitMsg))
|
||||||
// Now need to show that the page has been added:
|
// Now need to show that the page has been added:
|
||||||
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo.WikiStorageRepo())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
@@ -213,7 +213,7 @@ func TestRepository_EditWikiPage(t *testing.T) {
|
|||||||
assert.NoError(t, EditWikiPage(git.DefaultContext, doer, repo, "Home", webPath, newWikiContent, commitMsg))
|
assert.NoError(t, EditWikiPage(git.DefaultContext, doer, repo, "Home", webPath, newWikiContent, commitMsg))
|
||||||
|
|
||||||
// Now need to show that the page has been added:
|
// Now need to show that the page has been added:
|
||||||
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo.WikiStorageRepo())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch)
|
masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -237,7 +237,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
|
|||||||
assert.NoError(t, DeleteWikiPage(git.DefaultContext, doer, repo, "Home"))
|
assert.NoError(t, DeleteWikiPage(git.DefaultContext, doer, repo, "Home"))
|
||||||
|
|
||||||
// Now need to show that the page has been added:
|
// Now need to show that the page has been added:
|
||||||
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo.WikiStorageRepo())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
@@ -251,7 +251,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
|
|||||||
func TestPrepareWikiFileName(t *testing.T) {
|
func TestPrepareWikiFileName(t *testing.T) {
|
||||||
unittest.PrepareTestEnv(t)
|
unittest.PrepareTestEnv(t)
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
gitRepo, err := gitrepo.OpenWikiRepository(git.DefaultContext, repo)
|
gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo.WikiStorageRepo())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer gitRepo.Close()
|
defer gitRepo.Close()
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"UserSearchList" $.Assignees
|
"UserSearchList" $.Assignees
|
||||||
"SelectedUserId" $.AssigneeID
|
"SelectedUserId" $.AssigneeID
|
||||||
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
||||||
"TextZeroValue" (ctx.Locale.Tr "repo.issues.filter_assginee_no_select")
|
"TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
||||||
"TextNegativeOne" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
"TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
* UserSearchList
|
* UserSearchList
|
||||||
* SelectedUserId: 0 or empty means default, -1 means "no user is set"
|
* SelectedUserId: 0 or empty means default, -1 means "no user is set"
|
||||||
* TextFilterTitle
|
* TextFilterTitle
|
||||||
* TextZeroValue: the text for "all issues"
|
* TextFilterMatchNone: the text for "issues with no assignee"
|
||||||
* TextNegativeOne: the text for "issues with no assignee"
|
* TextFilterMatchAny: the text for "issues with any assignee"
|
||||||
*/}}
|
*/}}
|
||||||
{{$queryLink := .QueryLink}}
|
{{$queryLink := .QueryLink}}
|
||||||
<div class="item ui dropdown jump {{if not .UserSearchList}}disabled{{end}}">
|
<div class="item ui dropdown jump {{if not .UserSearchList}}disabled{{end}}">
|
||||||
@@ -15,16 +15,24 @@
|
|||||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_user_placeholder"}}">
|
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_user_placeholder"}}">
|
||||||
</div>
|
</div>
|
||||||
{{if $.TextZeroValue}}
|
{{if $.TextFilterMatchNone}}
|
||||||
<a class="item {{if not .SelectedUserId}}selected{{end}}" href="{{QueryBuild $queryLink $.QueryParamKey NIL}}">{{$.TextZeroValue}}</a>
|
{{$isSelected := eq .SelectedUserId "(none)"}}
|
||||||
|
<a class="item" href="{{QueryBuild $queryLink $.QueryParamKey (Iif $isSelected NIL "(none)")}}">
|
||||||
|
{{svg "octicon-check" 14 (Iif $isSelected "" "tw-invisible")}} {{$.TextFilterMatchNone}}
|
||||||
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if $.TextNegativeOne}}
|
{{if $.TextFilterMatchAny}}
|
||||||
<a class="item {{if eq .SelectedUserId -1}}selected{{end}}" href="{{QueryBuild $queryLink $.QueryParamKey -1}}">{{$.TextNegativeOne}}</a>
|
{{$isSelected := eq .SelectedUserId "(any)"}}
|
||||||
|
<a class="item" href="{{QueryBuild $queryLink $.QueryParamKey (Iif $isSelected NIL "(any)")}}">
|
||||||
|
{{svg "octicon-check" 14 (Iif $isSelected "" "tw-invisible")}} {{$.TextFilterMatchAny}}
|
||||||
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{range .UserSearchList}}
|
{{range $user := .UserSearchList}}
|
||||||
<a class="item {{if eq $.SelectedUserId .ID}}selected{{end}}" href="{{QueryBuild $queryLink $.QueryParamKey .ID}}">
|
{{$isSelected := eq $.SelectedUserId (print $user.ID)}}
|
||||||
{{ctx.AvatarUtils.Avatar . 20}}{{template "repo/search_name" .}}
|
<a class="item" href="{{QueryBuild $queryLink $.QueryParamKey (Iif $isSelected NIL $user.ID)}}">
|
||||||
|
{{svg "octicon-check" 14 (Iif $isSelected "" "tw-invisible")}}
|
||||||
|
{{ctx.AvatarUtils.Avatar $user 20}}{{template "repo/search_name" .}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,8 +94,8 @@
|
|||||||
"UserSearchList" $.Assignees
|
"UserSearchList" $.Assignees
|
||||||
"SelectedUserId" $.AssigneeID
|
"SelectedUserId" $.AssigneeID
|
||||||
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
||||||
"TextZeroValue" (ctx.Locale.Tr "repo.issues.filter_assginee_no_select")
|
"TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
||||||
"TextNegativeOne" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
"TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
|
||||||
}}
|
}}
|
||||||
|
|
||||||
{{if .IsSigned}}
|
{{if .IsSigned}}
|
||||||
|
|||||||
@@ -14,19 +14,21 @@
|
|||||||
{{$entry := $item.Entry}}
|
{{$entry := $item.Entry}}
|
||||||
{{$commit := $item.Commit}}
|
{{$commit := $item.Commit}}
|
||||||
{{$submoduleFile := $item.SubmoduleFile}}
|
{{$submoduleFile := $item.SubmoduleFile}}
|
||||||
<div class="repo-file-cell name {{if not $commit}}notready{{end}}">
|
<div class="repo-file-cell name muted-links {{if not $commit}}notready{{end}}">
|
||||||
{{ctx.RenderUtils.RenderFileIcon $entry}}
|
{{ctx.RenderUtils.RenderFileIcon $entry}}
|
||||||
{{if $entry.IsSubModule}}
|
{{if $entry.IsSubModule}}
|
||||||
{{$submoduleLink := $submoduleFile.SubmoduleWebLink ctx}}
|
{{$submoduleLink := $submoduleFile.SubmoduleWebLink ctx}}
|
||||||
{{if $submoduleLink}}
|
{{if $submoduleLink}}
|
||||||
<a class="muted" href="{{$submoduleLink.RepoWebLink}}">{{$entry.Name}}</a> <span class="at">@</span> <a href="{{$submoduleLink.CommitWebLink}}">{{ShortSha $submoduleFile.RefID}}</a>
|
<a class="entry-name" href="{{$submoduleLink.RepoWebLink}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
|
||||||
|
@ <a class="text primary" href="{{$submoduleLink.CommitWebLink}}">{{ShortSha $submoduleFile.RefID}}</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{$entry.Name}} <span class="at">@</span> {{ShortSha $submoduleFile.RefID}}
|
<span class="entry-name" title="{{$entry.Name}}">{{$entry.Name}}</span>
|
||||||
|
@ {{ShortSha $submoduleFile.RefID}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{if $entry.IsDir}}
|
{{if $entry.IsDir}}
|
||||||
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
|
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
|
||||||
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
|
<a class="entry-name" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
|
||||||
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
|
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
|
||||||
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
|
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
|
||||||
{{if eq $subJumpablePathFieldLast 0}}
|
{{if eq $subJumpablePathFieldLast 0}}
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</a>
|
</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
|
<a class="entry-name" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,10 +14,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#repo-files-table .repo-file-cell.name .svg {
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#repo-files-table .svg.octicon-file-directory-fill,
|
#repo-files-table .svg.octicon-file-directory-fill,
|
||||||
#repo-files-table .svg.octicon-file-submodule {
|
#repo-files-table .svg.octicon-file-submodule {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
@@ -70,11 +66,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#repo-files-table .repo-file-cell.name {
|
#repo-files-table .repo-file-cell.name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#repo-files-table .repo-file-cell.name > a,
|
||||||
|
#repo-files-table .repo-file-cell.name > span {
|
||||||
|
flex-shrink: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#repo-files-table .repo-file-cell.name .entry-name {
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
#repo-files-table .repo-file-cell.name {
|
#repo-files-table .repo-file-cell.name {
|
||||||
max-width: 35vw;
|
max-width: 35vw;
|
||||||
|
|||||||
@@ -1130,7 +1130,11 @@ $.fn.dropdown = function(parameters) {
|
|||||||
icon: {
|
icon: {
|
||||||
click: function(event) {
|
click: function(event) {
|
||||||
iconClicked=true;
|
iconClicked=true;
|
||||||
if(module.has.search()) {
|
// GITEA-PATCH: official dropdown doesn't support the search input in menu
|
||||||
|
// so we need to make the menu could be shown when the search input is in menu and user clicks the icon
|
||||||
|
const searchInputInMenu = Boolean($menu.find('.search > input').length);
|
||||||
|
if(module.has.search() && !searchInputInMenu) {
|
||||||
|
// the search input is in the dropdown element (but not in the popup menu), try to focus it
|
||||||
if(!module.is.active()) {
|
if(!module.is.active()) {
|
||||||
if(settings.showOnFocus){
|
if(settings.showOnFocus){
|
||||||
module.focusSearch();
|
module.focusSearch();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ test('toAbsoluteLocaleDate', () => {
|
|||||||
// test different timezone
|
// test different timezone
|
||||||
const oldTZ = process.env.TZ;
|
const oldTZ = process.env.TZ;
|
||||||
process.env.TZ = 'America/New_York';
|
process.env.TZ = 'America/New_York';
|
||||||
expect(new Date('2024-03-15').toLocaleString()).toEqual('3/14/2024, 8:00:00 PM');
|
expect(new Date('2024-03-15').toLocaleString('en-US')).toEqual('3/14/2024, 8:00:00 PM');
|
||||||
expect(toAbsoluteLocaleDate('2024-03-15')).toEqual('3/15/2024, 12:00:00 AM');
|
expect(toAbsoluteLocaleDate('2024-03-15', 'en-US')).toEqual('3/15/2024, 12:00:00 AM');
|
||||||
process.env.TZ = oldTZ;
|
process.env.TZ = oldTZ;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user