aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/admin_auth_ldap.go49
-rw-r--r--cmd/admin_auth_ldap_test.go28
-rw-r--r--models/activities/action.go6
-rw-r--r--models/activities/action_list.go24
-rw-r--r--models/db/search.go4
-rw-r--r--models/issues/issue_search.go31
-rw-r--r--models/issues/issue_test.go3
-rw-r--r--models/migrations/migrations.go1
-rw-r--r--models/migrations/v1_24/v317.go56
-rw-r--r--models/repo/repo.go20
-rw-r--r--modules/gitrepo/branch.go12
-rw-r--r--modules/gitrepo/gitrepo.go26
-rw-r--r--modules/gitrepo/hooks.go18
-rw-r--r--modules/indexer/issues/bleve/bleve.go18
-rw-r--r--modules/indexer/issues/db/options.go2
-rw-r--r--modules/indexer/issues/dboptions.go6
-rw-r--r--modules/indexer/issues/elasticsearch/elasticsearch.go18
-rw-r--r--modules/indexer/issues/indexer_test.go31
-rw-r--r--modules/indexer/issues/internal/model.go5
-rw-r--r--modules/indexer/issues/internal/tests/tests.go21
-rw-r--r--modules/indexer/issues/meilisearch/meilisearch.go20
-rw-r--r--modules/repository/init.go2
-rw-r--r--options/locale/locale_en-US.ini4
-rw-r--r--routers/api/v1/repo/issue.go8
-rw-r--r--routers/api/v1/repo/wiki.go2
-rw-r--r--routers/web/auth/oauth2_provider.go2
-rw-r--r--routers/web/org/projects.go4
-rw-r--r--routers/web/repo/commit.go4
-rw-r--r--routers/web/repo/compare.go2
-rw-r--r--routers/web/repo/issue_list.go18
-rw-r--r--routers/web/repo/projects.go4
-rw-r--r--routers/web/repo/wiki.go4
-rw-r--r--routers/web/repo/wiki_test.go2
-rw-r--r--routers/web/shared/user/helper.go19
-rw-r--r--routers/web/user/home.go18
-rw-r--r--services/asymkey/sign.go2
-rw-r--r--services/doctor/misc.go4
-rw-r--r--services/feed/feed.go26
-rw-r--r--services/mirror/mirror_push.go2
-rw-r--r--services/repository/adopt.go2
-rw-r--r--services/repository/fork.go2
-rw-r--r--services/repository/hooks.go4
-rw-r--r--services/repository/migrate.go4
-rw-r--r--services/repository/push.go197
-rw-r--r--services/repository/transfer.go5
-rw-r--r--services/wiki/wiki.go8
-rw-r--r--services/wiki/wiki_test.go8
-rw-r--r--templates/projects/view.tmpl4
-rw-r--r--templates/repo/issue/filter_item_user_assign.tmpl26
-rw-r--r--templates/repo/issue/filter_list.tmpl4
-rw-r--r--templates/user/auth/grant.tmpl60
-rw-r--r--templates/user/auth/grant_error.tmpl19
-rw-r--r--web_src/fomantic/build/components/dropdown.js6
53 files changed, 558 insertions, 317 deletions
diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go
index aff2a12855..274ec181d1 100644
--- a/cmd/admin_auth_ldap.go
+++ b/cmd/admin_auth_ldap.go
@@ -127,6 +127,34 @@ var (
&cli.UintFlag{
Name: "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,
@@ -273,6 +301,27 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("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
}
diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go
index 7791f3a9cc..bab42226ae 100644
--- a/cmd/admin_auth_ldap_test.go
+++ b/cmd/admin_auth_ldap_test.go
@@ -51,6 +51,13 @@ func TestAddLdapBindDn(t *testing.T) {
"--attributes-in-bind",
"--synchronize-users",
"--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{
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)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
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",
"--synchronize-users",
"--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,
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)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
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,
},
},
},
diff --git a/models/activities/action.go b/models/activities/action.go
index c16c49c0ac..c89ba3e14e 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -172,7 +172,10 @@ func (a *Action) TableIndices() []*schemas.Index {
cuIndex := schemas.NewIndex("c_u", schemas.IndexType)
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
}
@@ -442,6 +445,7 @@ type GetFeedsOptions struct {
OnlyPerformedBy bool // only actions performed by requested user
IncludeDeleted bool // include deleted actions
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
diff --git a/models/activities/action_list.go b/models/activities/action_list.go
index f7ea48f03e..6789ebcb99 100644
--- a/models/activities/action_list.go
+++ b/models/activities/action_list.go
@@ -243,7 +243,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
sess := db.GetEngine(ctx).Where(cond)
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 {
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)
}
- count, err = db.GetEngine(ctx).Where(cond).
- Table("action").
- Cols("`action`.id").Count()
- if err != nil {
- return nil, 0, fmt.Errorf("Count: %w", err)
+ if !opts.DontCount {
+ count, err = db.GetEngine(ctx).Where(cond).
+ Table("action").
+ Cols("`action`.id").Count()
+ 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 {
@@ -275,3 +281,9 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
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{})
+}
diff --git a/models/db/search.go b/models/db/search.go
index e0a1b6bde9..44d54f21fc 100644
--- a/models/db/search.go
+++ b/models/db/search.go
@@ -29,7 +29,3 @@ const (
// 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.
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
diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go
index 694b918755..737b69f154 100644
--- a/models/issues/issue_search.go
+++ b/models/issues/issue_search.go
@@ -27,8 +27,8 @@ type IssuesOptions struct { //nolint
RepoIDs []int64 // overwrites RepoCond if the length is not 0
AllPublic bool // include also all public repositories
RepoCond builder.Cond
- AssigneeID optional.Option[int64]
- PosterID optional.Option[int64]
+ AssigneeID string // "(none)" or "(any)" or a user ID
+ PosterID string // "(none)" or "(any)" or a user ID
MentionedID int64
ReviewRequestedID int64
ReviewedID int64
@@ -356,26 +356,25 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, owner *user_mod
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
- if !assigneeID.Has() || assigneeID.Value() == 0 {
- return
- }
- if assigneeID.Value() == db.NoConditionID {
+ if assigneeID == "(none)" {
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").
- And("issue_assignees.assignee_id = ?", assigneeID.Value())
+ And("issue_assignees.assignee_id = ?", assigneeIDInt64)
}
}
-func applyPosterCondition(sess *xorm.Session, posterID optional.Option[int64]) {
- if !posterID.Has() {
- return
- }
- // poster doesn't need to support db.NoConditionID(-1), so just use the value as-is
- if posterID.Has() {
- sess.And("issue.poster_id=?", posterID.Value())
+func applyPosterCondition(sess *xorm.Session, posterID string) {
+ // Actually every issue has a poster.
+ // 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)" {
+ sess.And("issue.poster_id=0")
+ } else if posterIDInt64, _ := strconv.ParseInt(posterID, 10, 64); posterIDInt64 > 0 {
+ sess.And("issue.poster_id=?", posterIDInt64)
}
}
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 3f76a81bb6..c32aa26b2b 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -15,7 +15,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
@@ -155,7 +154,7 @@ func TestIssues(t *testing.T) {
}{
{
issues_model.IssuesOptions{
- AssigneeID: optional.Some(int64(1)),
+ AssigneeID: "1",
SortType: "oldest",
},
[]int64{1, 6},
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 297c50a267..572738013f 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -377,6 +377,7 @@ func prepareMigrationTasks() []*migration {
newMigration(314, "Update OwnerID as zero for repository level action tables", v1_24.UpdateOwnerIDOfRepoLevelActionsTables),
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
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
}
diff --git a/models/migrations/v1_24/v317.go b/models/migrations/v1_24/v317.go
new file mode 100644
index 0000000000..3da5a4a078
--- /dev/null
+++ b/models/migrations/v1_24/v317.go
@@ -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))
+}
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 13473699f3..a8732f60bf 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -215,12 +215,24 @@ func init() {
db.RegisterModel(new(Repository))
}
-func (repo *Repository) GetName() string {
- return repo.Name
+func RelativePath(ownerName, repoName string) string {
+ return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
}
-func (repo *Repository) GetOwnerName() string {
- return repo.OwnerName
+// RelativePath should be an unix style path like username/reponame.git
+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
diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go
index 9c4bdc5bdf..25ea5abfca 100644
--- a/modules/gitrepo/branch.go
+++ b/modules/gitrepo/branch.go
@@ -44,24 +44,12 @@ func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
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.
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
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.
func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
}
-
-func IsWikiBranchExist(ctx context.Context, repo Repository, name string) bool {
- return IsWikiReferenceExist(ctx, repo, git.BranchPrefix+name)
-}
diff --git a/modules/gitrepo/gitrepo.go b/modules/gitrepo/gitrepo.go
index 5e2ec9ed1e..5da65e2452 100644
--- a/modules/gitrepo/gitrepo.go
+++ b/modules/gitrepo/gitrepo.go
@@ -8,7 +8,6 @@ import (
"fmt"
"io"
"path/filepath"
- "strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/reqctx"
@@ -16,21 +15,15 @@ import (
"code.gitea.io/gitea/modules/util"
)
+// Repository represents a git repository which stored in a disk
type Repository interface {
- GetName() string
- GetOwnerName() string
-}
-
-func absPath(owner, name string) string {
- return filepath.Join(setting.RepoRootPath, strings.ToLower(owner), strings.ToLower(name)+".git")
+ RelativePath() string // We don't assume how the directory structure of the repository is, so we only need the relative path
}
+// 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 {
- return absPath(repo.GetOwnerName(), repo.GetName())
-}
-
-func wikiPath(repo Repository) string {
- return filepath.Join(setting.RepoRootPath, strings.ToLower(repo.GetOwnerName()), strings.ToLower(repo.GetName())+".wiki.git")
+ return filepath.Join(setting.RepoRootPath, filepath.FromSlash(repo.RelativePath()))
}
// 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))
}
-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.
type contextKey struct {
repoPath string
@@ -86,9 +75,8 @@ func DeleteRepository(ctx context.Context, repo Repository) error {
}
// RenameRepository renames a repository's name on disk
-func RenameRepository(ctx context.Context, repo Repository, newName string) error {
- newRepoPath := absPath(repo.GetOwnerName(), newName)
- if err := util.Rename(repoPath(repo), newRepoPath); err != nil {
+func RenameRepository(ctx context.Context, repo, newRepo Repository) error {
+ if err := util.Rename(repoPath(repo), repoPath(newRepo)); err != nil {
return fmt.Errorf("rename repository directory: %w", err)
}
return nil
diff --git a/modules/gitrepo/hooks.go b/modules/gitrepo/hooks.go
index cf44f792c6..d9d4a88ff1 100644
--- a/modules/gitrepo/hooks.go
+++ b/modules/gitrepo/hooks.go
@@ -106,16 +106,11 @@ done
return hookNames, hookTpls, giteaHookTpls
}
-// CreateDelegateHooksForRepo creates all the hooks scripts for the repo
-func CreateDelegateHooksForRepo(_ context.Context, repo Repository) (err error) {
+// CreateDelegateHooks creates all the hooks scripts for the repo
+func CreateDelegateHooks(_ context.Context, repo Repository) (err error) {
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) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
@@ -178,16 +173,11 @@ func ensureExecutable(filename string) error {
return os.Chmod(filename, mode)
}
-// CheckDelegateHooksForRepo checks the hooks scripts for the repo
-func CheckDelegateHooksForRepo(_ context.Context, repo Repository) ([]string, error) {
+// CheckDelegateHooks checks the hooks scripts for the repo
+func CheckDelegateHooks(_ context.Context, repo Repository) ([]string, error) {
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) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go
index 9534b0b750..39d96cab98 100644
--- a/modules/indexer/issues/bleve/bleve.go
+++ b/modules/indexer/issues/bleve/bleve.go
@@ -5,11 +5,13 @@ package bleve
import (
"context"
+ "strconv"
"code.gitea.io/gitea/modules/indexer"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
"code.gitea.io/gitea/modules/indexer/issues/internal"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/util"
"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"))
}
- if options.PosterID.Has() {
- queries = append(queries, inner_bleve.NumericEqualityQuery(options.PosterID.Value(), "poster_id"))
+ if options.PosterID != "" {
+ // "(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() {
- queries = append(queries, inner_bleve.NumericEqualityQuery(options.AssigneeID.Value(), "assignee_id"))
+ if options.AssigneeID != "" {
+ 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() {
diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go
index 87ce398a20..3b19d742ba 100644
--- a/modules/indexer/issues/db/options.go
+++ b/modules/indexer/issues/db/options.go
@@ -54,7 +54,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
RepoIDs: options.RepoIDs,
AllPublic: options.AllPublic,
RepoCond: nil,
- AssigneeID: optional.Some(convertID(options.AssigneeID)),
+ AssigneeID: options.AssigneeID,
PosterID: options.PosterID,
MentionedID: convertID(options.MentionID),
ReviewRequestedID: convertID(options.ReviewRequestedID),
diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go
index 4f6ad96d22..4e2dff572a 100644
--- a/modules/indexer/issues/dboptions.go
+++ b/modules/indexer/issues/dboptions.go
@@ -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)
}
- if opts.AssigneeID.Value() == db.NoConditionID {
- 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
- }
+ searchOpt.AssigneeID = opts.AssigneeID
// See the comment of issues_model.SearchOptions for the reason why we need to convert
convertID := func(id int64) optional.Option[int64] {
diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go
index 464c0028f2..e3b1b17059 100644
--- a/modules/indexer/issues/elasticsearch/elasticsearch.go
+++ b/modules/indexer/issues/elasticsearch/elasticsearch.go
@@ -212,12 +212,22 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query.Must(elastic.NewTermQuery("project_board_id", options.ProjectColumnID.Value()))
}
- if options.PosterID.Has() {
- query.Must(elastic.NewTermQuery("poster_id", options.PosterID.Value()))
+ if options.PosterID != "" {
+ // "(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() {
- query.Must(elastic.NewTermQuery("assignee_id", options.AssigneeID.Value()))
+ if options.AssigneeID != "" {
+ 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() {
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index 14dd6ba101..3e38ac49b7 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -44,6 +44,7 @@ func TestDBSearchIssues(t *testing.T) {
t.Run("search issues with order", searchIssueWithOrder)
t.Run("search issues in project", searchIssueInProject)
t.Run("search issues with paginator", searchIssueWithPaginator)
+ t.Run("search issues with any assignee", searchIssueWithAnyAssignee)
}
func searchIssueWithKeyword(t *testing.T) {
@@ -176,19 +177,19 @@ func searchIssueByID(t *testing.T) {
}{
{
opts: SearchOptions{
- PosterID: optional.Some(int64(1)),
+ PosterID: "1",
},
expectedIDs: []int64{11, 6, 3, 2, 1},
},
{
opts: SearchOptions{
- AssigneeID: optional.Some(int64(1)),
+ AssigneeID: "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.
- opts: *ToSearchOptions("", &issues.IssuesOptions{AssigneeID: optional.Some(db.NoConditionID)}),
+ // NOTE: This tests no assignees filtering and also ToSearchOptions() to ensure it handles the filter correctly
+ 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},
},
{
@@ -462,3 +463,25 @@ func searchIssueWithPaginator(t *testing.T) {
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)
+ }
+}
diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go
index 976e2d696b..0d4f0f727d 100644
--- a/modules/indexer/issues/internal/model.go
+++ b/modules/indexer/issues/internal/model.go
@@ -97,9 +97,8 @@ type SearchOptions struct {
ProjectID optional.Option[int64] // project the issues belong to
ProjectColumnID optional.Option[int64] // project column the issues belong to
- PosterID optional.Option[int64] // poster of the issues
-
- AssigneeID optional.Option[int64] // assignee of the issues, zero means no assignee
+ 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
MentionID optional.Option[int64] // mentioned user of the issues
diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go
index 0483853dfd..6e92c7885c 100644
--- a/modules/indexer/issues/internal/tests/tests.go
+++ b/modules/indexer/issues/internal/tests/tests.go
@@ -379,7 +379,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- PosterID: optional.Some(int64(1)),
+ PosterID: "1",
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Len(t, result.Hits, 5)
@@ -397,7 +397,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- AssigneeID: optional.Some(int64(1)),
+ AssigneeID: "1",
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Len(t, result.Hits, 5)
@@ -415,7 +415,7 @@ var cases = []*testIndexerCase{
Paginator: &db.ListOptions{
PageSize: 5,
},
- AssigneeID: optional.Some(int64(0)),
+ AssigneeID: "(none)",
},
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
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 {
diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go
index a1746f5954..759a98473f 100644
--- a/modules/indexer/issues/meilisearch/meilisearch.go
+++ b/modules/indexer/issues/meilisearch/meilisearch.go
@@ -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()))
}
- if options.PosterID.Has() {
- query.And(inner_meilisearch.NewFilterEq("poster_id", options.PosterID.Value()))
- }
-
- if options.AssigneeID.Has() {
- query.And(inner_meilisearch.NewFilterEq("assignee_id", options.AssigneeID.Value()))
+ if options.PosterID != "" {
+ // "(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 != "" {
+ 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() {
diff --git a/modules/repository/init.go b/modules/repository/init.go
index 3fc1261baa..ace21254ba 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -138,7 +138,7 @@ func CheckInitRepository(ctx context.Context, repo *repo_model.Repository) (err
// Init git bare new repository.
if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil {
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 nil
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 876e135b22..573c00eee6 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1547,8 +1547,8 @@ issues.filter_project = Project
issues.filter_project_all = All projects
issues.filter_project_none = No project
issues.filter_assignee = Assignee
-issues.filter_assginee_no_select = All assignees
-issues.filter_assginee_no_assignee = No assignee
+issues.filter_assginee_no_assignee = Assigned to nobody
+issues.filter_assignee_any_assignee = Assigned to anybody
issues.filter_poster = Author
issues.filter_user_placeholder = Search users
issues.filter_user_no_select = All users
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index c9575ff98a..e678db5262 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -290,10 +290,10 @@ func SearchIssues(ctx *context.APIContext) {
if ctx.IsSigned {
ctxUserID := ctx.Doer.ID
if ctx.FormBool("created") {
- searchOpt.PosterID = optional.Some(ctxUserID)
+ searchOpt.PosterID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("assigned") {
- searchOpt.AssigneeID = optional.Some(ctxUserID)
+ searchOpt.AssigneeID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("mentioned") {
searchOpt.MentionID = optional.Some(ctxUserID)
@@ -538,10 +538,10 @@ func ListIssues(ctx *context.APIContext) {
}
if createdByID > 0 {
- searchOpt.PosterID = optional.Some(createdByID)
+ searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
}
if assignedByID > 0 {
- searchOpt.AssigneeID = optional.Some(assignedByID)
+ searchOpt.AssigneeID = strconv.FormatInt(assignedByID, 10)
}
if mentionedByID > 0 {
searchOpt.MentionID = optional.Some(mentionedByID)
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index 8d73383f76..67dd6c913d 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -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.
// The caller is responsible for closing the returned repo again
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 git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
ctx.APIErrorNotFound(err)
diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go
index 00b5b2db52..ff571fbf2c 100644
--- a/routers/web/auth/oauth2_provider.go
+++ b/routers/web/auth/oauth2_provider.go
@@ -249,7 +249,7 @@ func AuthorizeOAuth(ctx *context.Context) {
}, form.RedirectURI)
return
}
- if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
+ if err := ctx.Session.Set("CodeChallenge", form.CodeChallenge); err != nil {
handleAuthorizeError(ctx, AuthorizeError{
ErrorCode: ErrorCodeServerError,
ErrorDescription: "cannot set code challenge",
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 985fd2ca45..49f4792772 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -347,11 +347,11 @@ func ViewProject(ctx *context.Context) {
if ctx.Written() {
return
}
- assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
+ assigneeID := ctx.FormString("assignee")
opts := issues_model.IssuesOptions{
LabelIDs: labelIDs,
- AssigneeID: optional.Some(assigneeID),
+ AssigneeID: assigneeID,
Owner: project.Owner,
Doer: ctx.Doer,
}
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index bbdcf9875e..3fd1eacb58 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -284,7 +284,7 @@ func Diff(ctx *context.Context) {
)
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 {
ctx.ServerError("Repo.GitRepo.GetCommit", err)
return
@@ -417,7 +417,7 @@ func Diff(ctx *context.Context) {
func RawDiff(ctx *context.Context) {
var gitRepo *git.Repository
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 {
ctx.ServerError("OpenRepository", err)
return
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 6cea95e387..3e9cdb5df8 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -885,7 +885,7 @@ func ExcerptBlob(ctx *context.Context) {
gitRepo := ctx.Repo.GitRepo
if ctx.Data["PageIsWiki"] == true {
var err error
- gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
+ gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
if err != nil {
ctx.ServerError("OpenRepository", err)
return
diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go
index a65ae77795..69b38c81ec 100644
--- a/routers/web/repo/issue_list.go
+++ b/routers/web/repo/issue_list.go
@@ -208,10 +208,10 @@ func SearchIssues(ctx *context.Context) {
if ctx.IsSigned {
ctxUserID := ctx.Doer.ID
if ctx.FormBool("created") {
- searchOpt.PosterID = optional.Some(ctxUserID)
+ searchOpt.PosterID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("assigned") {
- searchOpt.AssigneeID = optional.Some(ctxUserID)
+ searchOpt.AssigneeID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("mentioned") {
searchOpt.MentionID = optional.Some(ctxUserID)
@@ -373,10 +373,10 @@ func SearchRepoIssuesJSON(ctx *context.Context) {
}
if createdByID > 0 {
- searchOpt.PosterID = optional.Some(createdByID)
+ searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
}
if assignedByID > 0 {
- searchOpt.AssigneeID = optional.Some(assignedByID)
+ searchOpt.AssigneeID = strconv.FormatInt(assignedByID, 10)
}
if mentionedByID > 0 {
searchOpt.MentionID = optional.Some(mentionedByID)
@@ -490,7 +490,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
viewType = "all"
}
- assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
+ assigneeID := ctx.FormString("assignee")
posterUsername := ctx.FormString("poster")
posterUserID := shared_user.GetFilterUserIDByName(ctx, posterUsername)
var mentionedID, reviewRequestedID, reviewedID int64
@@ -498,11 +498,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
if ctx.IsSigned {
switch viewType {
case "created_by":
- posterUserID = optional.Some(ctx.Doer.ID)
+ posterUserID = strconv.FormatInt(ctx.Doer.ID, 10)
case "mentioned":
mentionedID = ctx.Doer.ID
case "assigned":
- assigneeID = ctx.Doer.ID
+ assigneeID = fmt.Sprint(ctx.Doer.ID)
case "review_requested":
reviewRequestedID = ctx.Doer.ID
case "reviewed_by":
@@ -532,7 +532,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
LabelIDs: labelIDs,
MilestoneIDs: mileIDs,
ProjectID: projectID,
- AssigneeID: optional.Some(assigneeID),
+ AssigneeID: assigneeID,
MentionedID: mentionedID,
PosterID: posterUserID,
ReviewRequestedID: reviewRequestedID,
@@ -613,7 +613,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
PageSize: setting.UI.IssuePagingNum,
},
RepoIDs: []int64{repo.ID},
- AssigneeID: optional.Some(assigneeID),
+ AssigneeID: assigneeID,
PosterID: posterUserID,
MentionedID: mentionedID,
ReviewRequestedID: reviewRequestedID,
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 5b81a5e4d1..6810025c6f 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -315,12 +315,12 @@ func ViewProject(ctx *context.Context) {
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{
RepoIDs: []int64{ctx.Repo.Repository.ID},
LabelIDs: labelIDs,
- AssigneeID: optional.Some(assigneeID),
+ AssigneeID: assigneeID,
})
if err != nil {
ctx.ServerError("LoadIssuesOfColumns", err)
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index 0f8e1223c6..20c8c2b406 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -96,7 +96,7 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, 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 {
ctx.ServerError("OpenRepository", 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)
if git.IsErrNotExist(errCommit) {
// 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 {
return wikiGitRepo, nil, errBranch
}
diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go
index 99114c93e0..e44cf46ba8 100644
--- a/routers/web/repo/wiki_test.go
+++ b/routers/web/repo/wiki_test.go
@@ -29,7 +29,7 @@ const (
)
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)
defer wikiRepo.Close()
commit, err := wikiRepo.GetBranchCommit("master")
diff --git a/routers/web/shared/user/helper.go b/routers/web/shared/user/helper.go
index b82181a1df..3fc39fd3ab 100644
--- a/routers/web/shared/user/helper.go
+++ b/routers/web/shared/user/helper.go
@@ -8,9 +8,7 @@ import (
"slices"
"strconv"
- "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/optional"
)
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.
// 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:
-// * nil: no filter
-// * some(id): match the id, the id could be -1 to match the issues without assignee
-// * some(NonExistingID): match no issue (due to the user doesn't exist)
-func GetFilterUserIDByName(ctx context.Context, name string) optional.Option[int64] {
+// * "": no filter
+// * "{the-id}": match the id
+// * "(none)": match no issue (due to the user doesn't exist)
+func GetFilterUserIDByName(ctx context.Context, name string) string {
if name == "" {
- return optional.None[int64]()
+ return ""
}
u, err := user.GetUserByName(ctx, name)
if 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)
}
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 8e030a62a2..864a2831d1 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -119,7 +119,7 @@ func Dashboard(ctx *context.Context) {
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,
RequestedTeam: ctx.Org.Team,
Actor: ctx.Doer,
@@ -501,9 +501,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
case issues_model.FilterModeAll:
case issues_model.FilterModeYourRepositories:
case issues_model.FilterModeAssign:
- opts.AssigneeID = optional.Some(ctx.Doer.ID)
+ opts.AssigneeID = strconv.FormatInt(ctx.Doer.ID, 10)
case issues_model.FilterModeCreate:
- opts.PosterID = optional.Some(ctx.Doer.ID)
+ opts.PosterID = strconv.FormatInt(ctx.Doer.ID, 10)
case issues_model.FilterModeMention:
opts.MentionedID = ctx.Doer.ID
case issues_model.FilterModeReviewRequested:
@@ -792,9 +792,9 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
case issues_model.FilterModeYourRepositories:
openClosedOpts.AllPublic = false
case issues_model.FilterModeAssign:
- openClosedOpts.AssigneeID = optional.Some(doerID)
+ openClosedOpts.AssigneeID = strconv.FormatInt(doerID, 10)
case issues_model.FilterModeCreate:
- openClosedOpts.PosterID = optional.Some(doerID)
+ openClosedOpts.PosterID = strconv.FormatInt(doerID, 10)
case issues_model.FilterModeMention:
openClosedOpts.MentionID = optional.Some(doerID)
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
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
- o.AssigneeID = nil
- o.PosterID = nil
+ o.AssigneeID = ""
+ o.PosterID = ""
o.MentionID = nil
o.ReviewRequestedID = nil
o.ReviewedID = nil
@@ -827,11 +827,11 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
if err != nil {
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 {
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 {
return nil, err
}
diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go
index da265dec27..2216bca54a 100644
--- a/services/asymkey/sign.go
+++ b/services/asymkey/sign.go
@@ -204,7 +204,7 @@ Loop:
return false, "", nil, &ErrWontSign{twofa}
}
case parentSigned:
- gitRepo, err := gitrepo.OpenWikiRepository(ctx, repo)
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
if err != nil {
return false, "", nil, err
}
diff --git a/services/doctor/misc.go b/services/doctor/misc.go
index 260a28ec4c..d934640af5 100644
--- a/services/doctor/misc.go
+++ b/services/doctor/misc.go
@@ -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 {
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 {
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)
}
if len(results) > 0 && autofix {
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)
return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err)
}
diff --git a/services/feed/feed.go b/services/feed/feed.go
index a1c327fb51..41a918f00e 100644
--- a/services/feed/feed.go
+++ b/services/feed/feed.go
@@ -13,9 +13,28 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/cache"
"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
func GetFeeds(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int64, error) {
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 {
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
diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go
index 6e72876893..9b57427d98 100644
--- a/services/mirror/mirror_push.go
+++ b/services/mirror/mirror_push.go
@@ -143,7 +143,7 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
var gitRepo *git.Repository
if isWiki {
- gitRepo, err = gitrepo.OpenWikiRepository(ctx, repo)
+ gitRepo, err = gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
} else {
gitRepo, err = gitrepo.OpenRepository(ctx, repo)
}
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index ea4f9a1920..b7321156d9 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -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())
}
- if err := gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
+ if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)
}
diff --git a/services/repository/fork.go b/services/repository/fork.go
index 7f7364acfc..5b1ba7a418 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -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)
}
- if err = gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
+ if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)
}
diff --git a/services/repository/hooks.go b/services/repository/hooks.go
index 2b3eb79153..c13b272550 100644
--- a/services/repository/hooks.go
+++ b/services/repository/hooks.go
@@ -31,11 +31,11 @@ func SyncRepositoryHooks(ctx context.Context) error {
default:
}
- if err := gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
+ if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
return fmt.Errorf("SyncRepositoryHook: %w", err)
}
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)
}
}
diff --git a/services/repository/migrate.go b/services/repository/migrate.go
index 1969b16a2d..5b6feccb8d 100644
--- a/services/repository/migrate.go
+++ b/services/repository/migrate.go
@@ -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.
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)
}
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)
}
}
diff --git a/services/repository/push.go b/services/repository/push.go
index c40333f0a8..6d3b9dd252 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -167,8 +167,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
}
}
- branch := opts.RefFullName.BranchName()
if !opts.IsDelRef() {
+ branch := opts.RefFullName.BranchName()
+
log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
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)
}
- refName := opts.RefName()
-
// Push new branch.
var l []*git.Commit
if opts.IsNewRef() {
- if repo.IsEmpty { // Change default branch and empty status only if pushed ref is non-empty branch.
- 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)
+ l, err = pushNewBranch(ctx, repo, pusher, opts, newCommit)
} else {
- l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
- if err != nil {
- return fmt.Errorf("newCommit.CommitsBeforeUntil: %w", 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))
- }
+ l, err = pushUpdateBranch(ctx, repo, pusher, opts, newCommit)
+ }
+ if err != nil {
+ return err
}
// delete cache for divergence
@@ -246,36 +202,11 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
commits := repo_module.GitToPushCommits(l)
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)
}
- oldCommitID := opts.OldCommitID
- 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 = ""
- }
+ commits.CompareURL = getCompareURL(repo, gitRepo, objectFormat, commits.Commits, opts)
if len(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)
}
} else {
- notify_service.DeleteRef(ctx, pusher, repo, opts.RefFullName)
-
- if err := pull_service.AdjustPullsCausedByBranchDeleted(ctx, pusher, repo, branch); err != nil {
- // close all related pulls
- log.Error("close related pull request failed: %v", err)
- }
+ pushDeleteBranch(ctx, repo, pusher, opts)
}
// 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.")
}
}
- 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.
@@ -316,6 +245,102 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
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
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 {
diff --git a/services/repository/transfer.go b/services/repository/transfer.go
index 3940b2a142..a589bc469d 100644
--- a/services/repository/transfer.go
+++ b/services/repository/transfer.go
@@ -331,12 +331,13 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
return fmt.Errorf("IsRepositoryExist: %w", err)
} else if has {
return repo_model.ErrRepoAlreadyExist{
- Uname: repo.Owner.Name,
+ Uname: repo.OwnerName,
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)
}
diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go
index a3fe07927d..b21f46639d 100644
--- a/services/wiki/wiki.go
+++ b/services/wiki/wiki.go
@@ -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 {
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)
} 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)
@@ -100,7 +100,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
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")
if err != nil {
@@ -381,7 +381,7 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
return nil
}
- oldDefBranch, err := gitrepo.GetWikiDefaultBranch(ctx, repo)
+ oldDefBranch, err := gitrepo.GetDefaultBranch(ctx, repo.WikiStorageRepo())
if err != nil {
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
}
- gitRepo, err := gitrepo.OpenWikiRepository(ctx, repo)
+ gitRepo, err := gitrepo.OpenRepository(ctx, repo.WikiStorageRepo())
if errors.Is(err, util.ErrNotExist) {
return nil // no git repo on storage, no need to do anything else
} else if err != nil {
diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go
index e8b89f5e97..288d258279 100644
--- a/services/wiki/wiki_test.go
+++ b/services/wiki/wiki_test.go
@@ -166,7 +166,7 @@ func TestRepository_AddWikiPage(t *testing.T) {
webPath := UserTitleToWebPath("", userTitle)
assert.NoError(t, AddWikiPage(git.DefaultContext, doer, repo, webPath, wikiContent, commitMsg))
// 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)
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))
// 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)
masterTree, err := gitRepo.GetTree(repo.DefaultWikiBranch)
assert.NoError(t, err)
@@ -237,7 +237,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
assert.NoError(t, DeleteWikiPage(git.DefaultContext, doer, repo, "Home"))
// 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)
defer gitRepo.Close()
@@ -251,7 +251,7 @@ func TestRepository_DeleteWikiPage(t *testing.T) {
func TestPrepareWikiFileName(t *testing.T) {
unittest.PrepareTestEnv(t)
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)
defer gitRepo.Close()
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index 3cbd651059..f4478d544a 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -15,8 +15,8 @@
"UserSearchList" $.Assignees
"SelectedUserId" $.AssigneeID
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
- "TextZeroValue" (ctx.Locale.Tr "repo.issues.filter_assginee_no_select")
- "TextNegativeOne" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
+ "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
+ "TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
}}
</div>
</div>
diff --git a/templates/repo/issue/filter_item_user_assign.tmpl b/templates/repo/issue/filter_item_user_assign.tmpl
index 4f1db71d57..42886edaa0 100644
--- a/templates/repo/issue/filter_item_user_assign.tmpl
+++ b/templates/repo/issue/filter_item_user_assign.tmpl
@@ -4,8 +4,8 @@
* UserSearchList
* SelectedUserId: 0 or empty means default, -1 means "no user is set"
* TextFilterTitle
-* TextZeroValue: the text for "all issues"
-* TextNegativeOne: the text for "issues with no assignee"
+* TextFilterMatchNone: the text for "issues with no assignee"
+* TextFilterMatchAny: the text for "issues with any assignee"
*/}}
{{$queryLink := .QueryLink}}
<div class="item ui dropdown jump {{if not .UserSearchList}}disabled{{end}}">
@@ -15,16 +15,24 @@
<i class="icon">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_user_placeholder"}}">
</div>
- {{if $.TextZeroValue}}
- <a class="item {{if not .SelectedUserId}}selected{{end}}" href="{{QueryBuild $queryLink $.QueryParamKey NIL}}">{{$.TextZeroValue}}</a>
+ {{if $.TextFilterMatchNone}}
+ {{$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}}
- {{if $.TextNegativeOne}}
- <a class="item {{if eq .SelectedUserId -1}}selected{{end}}" href="{{QueryBuild $queryLink $.QueryParamKey -1}}">{{$.TextNegativeOne}}</a>
+ {{if $.TextFilterMatchAny}}
+ {{$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}}
<div class="divider"></div>
- {{range .UserSearchList}}
- <a class="item {{if eq $.SelectedUserId .ID}}selected{{end}}" href="{{QueryBuild $queryLink $.QueryParamKey .ID}}">
- {{ctx.AvatarUtils.Avatar . 20}}{{template "repo/search_name" .}}
+ {{range $user := .UserSearchList}}
+ {{$isSelected := eq $.SelectedUserId (print $user.ID)}}
+ <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>
{{end}}
</div>
diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl
index 7612d93b21..58ca4a7c00 100644
--- a/templates/repo/issue/filter_list.tmpl
+++ b/templates/repo/issue/filter_list.tmpl
@@ -94,8 +94,8 @@
"UserSearchList" $.Assignees
"SelectedUserId" $.AssigneeID
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
- "TextZeroValue" (ctx.Locale.Tr "repo.issues.filter_assginee_no_select")
- "TextNegativeOne" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
+ "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
+ "TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
}}
{{if .IsSigned}}
diff --git a/templates/user/auth/grant.tmpl b/templates/user/auth/grant.tmpl
index 7a6f156e36..e56241b0f8 100644
--- a/templates/user/auth/grant.tmpl
+++ b/templates/user/auth/grant.tmpl
@@ -1,35 +1,33 @@
{{template "base/head" .}}
-<div role="main" aria-label="{{.Title}}" class="page-content ui one column stackable tw-text-center page grid oauth2-authorize-application-box">
- <div class="column seven wide">
- <div class="ui middle centered raised segments">
- <h3 class="ui top attached header">
- {{ctx.Locale.Tr "auth.authorize_title" .Application.Name}}
- </h3>
- <div class="ui attached segment">
- {{template "base/alert" .}}
- <p>
- {{if not .AdditionalScopes}}
- <b>{{ctx.Locale.Tr "auth.authorize_application_description"}}</b><br>
- {{end}}
- {{ctx.Locale.Tr "auth.authorize_application_created_by" .ApplicationCreatorLinkHTML}}<br>
- {{ctx.Locale.Tr "auth.authorize_application_with_scopes" (HTMLFormat "<b>%s</b>" .Scope)}}
- </p>
- </div>
- <div class="ui attached segment">
- <p>{{ctx.Locale.Tr "auth.authorize_redirect_notice" .ApplicationRedirectDomainHTML}}</p>
- </div>
- <div class="ui attached segment">
- <form method="post" action="{{AppSubUrl}}/login/oauth/grant">
- {{.CsrfTokenHtml}}
- <input type="hidden" name="client_id" value="{{.Application.ClientID}}">
- <input type="hidden" name="state" value="{{.State}}">
- <input type="hidden" name="scope" value="{{.Scope}}">
- <input type="hidden" name="nonce" value="{{.Nonce}}">
- <input type="hidden" name="redirect_uri" value="{{.RedirectURI}}">
- <button type="submit" id="authorize-app" name="granted" value="true" class="ui red inline button">{{ctx.Locale.Tr "auth.authorize_application"}}</button>
- <button type="submit" name="granted" value="false" class="ui basic primary inline button">{{ctx.Locale.Tr "cancel"}}</button>
- </form>
- </div>
+<div role="main" aria-label="{{.Title}}" class="page-content oauth2-authorize-application-box">
+ <div class="ui container tw-max-w-[500px]">
+ <h3 class="ui top attached header">
+ {{ctx.Locale.Tr "auth.authorize_title" .Application.Name}}
+ </h3>
+ <div class="ui attached segment">
+ {{template "base/alert" .}}
+ <p>
+ {{if not .AdditionalScopes}}
+ <b>{{ctx.Locale.Tr "auth.authorize_application_description"}}</b><br>
+ {{end}}
+ {{ctx.Locale.Tr "auth.authorize_application_created_by" .ApplicationCreatorLinkHTML}}<br>
+ {{ctx.Locale.Tr "auth.authorize_application_with_scopes" (HTMLFormat "<b>%s</b>" .Scope)}}
+ </p>
+ </div>
+ <div class="ui attached segment">
+ <p>{{ctx.Locale.Tr "auth.authorize_redirect_notice" .ApplicationRedirectDomainHTML}}</p>
+ </div>
+ <div class="ui attached segment tw-text-center">
+ <form method="post" action="{{AppSubUrl}}/login/oauth/grant">
+ {{.CsrfTokenHtml}}
+ <input type="hidden" name="client_id" value="{{.Application.ClientID}}">
+ <input type="hidden" name="state" value="{{.State}}">
+ <input type="hidden" name="scope" value="{{.Scope}}">
+ <input type="hidden" name="nonce" value="{{.Nonce}}">
+ <input type="hidden" name="redirect_uri" value="{{.RedirectURI}}">
+ <button type="submit" id="authorize-app" name="granted" value="true" class="ui red inline button">{{ctx.Locale.Tr "auth.authorize_application"}}</button>
+ <button type="submit" name="granted" value="false" class="ui basic primary inline button">{{ctx.Locale.Tr "cancel"}}</button>
+ </form>
</div>
</div>
</div>
diff --git a/templates/user/auth/grant_error.tmpl b/templates/user/auth/grant_error.tmpl
index e37c4f6544..7a4521d331 100644
--- a/templates/user/auth/grant_error.tmpl
+++ b/templates/user/auth/grant_error.tmpl
@@ -1,15 +1,12 @@
{{template "base/head" .}}
-<div role="main" aria-label="{{.Title}}" class="page-content ui one column stackable tw-text-center page grid oauth2-authorize-application-box {{if .IsRepo}}repository{{end}}">
- {{if .IsRepo}}{{template "repo/header" .}}{{end}}
- <div class="column seven wide">
- <div class="ui middle centered raised segments">
- <h1 class="ui top attached header">
- {{ctx.Locale.Tr "auth.authorization_failed"}}
- </h1>
- <h3 class="ui attached segment">{{.Error.ErrorDescription}}</h3>
- <div class="ui attached segment">
- <p>{{ctx.Locale.Tr "auth.authorization_failed_desc"}}</p>
- </div>
+<div role="main" aria-label="{{.Title}}" class="page-content oauth2-authorize-application-box">
+ <div class="ui container tw-max-w-[500px]">
+ <h1 class="ui top attached header">
+ {{ctx.Locale.Tr "auth.authorization_failed"}}
+ </h1>
+ <h3 class="ui attached segment">{{.Error.ErrorDescription}}</h3>
+ <div class="ui attached segment">
+ <p>{{ctx.Locale.Tr "auth.authorization_failed_desc"}}</p>
</div>
</div>
</div>
diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js
index d3a1f7dc24..009b51d8b1 100644
--- a/web_src/fomantic/build/components/dropdown.js
+++ b/web_src/fomantic/build/components/dropdown.js
@@ -1130,7 +1130,11 @@ $.fn.dropdown = function(parameters) {
icon: {
click: function(event) {
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(settings.showOnFocus){
module.focusSearch();