diff --git a/.github/workflows/cron-lock.yml b/.github/workflows/cron-lock.yml deleted file mode 100644 index 665313135b679..0000000000000 --- a/.github/workflows/cron-lock.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: cron-lock - -on: - schedule: - - cron: "0 0 * * *" # every day at 00:00 UTC - workflow_dispatch: - -permissions: - issues: write - pull-requests: write - -concurrency: - group: lock - -jobs: - action: - runs-on: ubuntu-latest - if: github.repository == 'go-gitea/gitea' - steps: - - uses: dessant/lock-threads@v5 - with: - issue-inactive-days: 10 - pr-inactive-days: 7 diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 04923acdcb9ab..2309021f94937 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -590,7 +590,7 @@ And the following unique queues: ## OpenID (`openid`) -- `ENABLE_OPENID_SIGNIN`: **false**: Allow authentication in via OpenID. +- `ENABLE_OPENID_SIGNIN`: **true**: Allow authentication in via OpenID. - `ENABLE_OPENID_SIGNUP`: **! DISABLE\_REGISTRATION**: Allow registering via OpenID. - `WHITELISTED_URIS`: **_empty_**: If non-empty, list of POSIX regex patterns matching OpenID URI's to permit. diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index 1f98db78aafd8..3115e4cc06453 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -562,7 +562,7 @@ Gitea 创建以下非唯一队列: ## OpenID (`openid`) -- `ENABLE_OPENID_SIGNIN`: **false**:允许通过OpenID进行身份验证。 +- `ENABLE_OPENID_SIGNIN`: **true**:允许通过OpenID进行身份验证。 - `ENABLE_OPENID_SIGNUP`: **! DISABLE\_REGISTRATION**:允许通过OpenID进行注册。 - `WHITELISTED_URIS`: **_empty_**:如果非空,是一组匹配OpenID URI的POSIX正则表达式模式,用于允许访问。 - `BLACKLISTED_URIS`: **_empty_**:如果非空,是一组匹配OpenID URI的POSIX正则表达式模式,用于阻止访问。 diff --git a/models/activities/notification.go b/models/activities/notification.go index 230bcdd6e8053..dc1b8c6fae91a 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -12,12 +12,8 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" - access_model "code.gitea.io/gitea/models/perm/access" 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/container" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -79,53 +75,6 @@ func init() { db.RegisterModel(new(Notification)) } -// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored. -type FindNotificationOptions struct { - db.ListOptions - UserID int64 - RepoID int64 - IssueID int64 - Status []NotificationStatus - Source []NotificationSource - UpdatedAfterUnix int64 - UpdatedBeforeUnix int64 -} - -// ToCond will convert each condition into a xorm-Cond -func (opts FindNotificationOptions) ToConds() builder.Cond { - cond := builder.NewCond() - if opts.UserID != 0 { - cond = cond.And(builder.Eq{"notification.user_id": opts.UserID}) - } - if opts.RepoID != 0 { - cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID}) - } - if opts.IssueID != 0 { - cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID}) - } - if len(opts.Status) > 0 { - if len(opts.Status) == 1 { - cond = cond.And(builder.Eq{"notification.status": opts.Status[0]}) - } else { - cond = cond.And(builder.In("notification.status", opts.Status)) - } - } - if len(opts.Source) > 0 { - cond = cond.And(builder.In("notification.source", opts.Source)) - } - if opts.UpdatedAfterUnix != 0 { - cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix}) - } - if opts.UpdatedBeforeUnix != 0 { - cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix}) - } - return cond -} - -func (opts FindNotificationOptions) ToOrders() string { - return "notification.updated_unix DESC" -} - // CreateRepoTransferNotification creates notification for the user a repository was transferred to func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error { return db.WithTx(ctx, func(ctx context.Context) error { @@ -159,109 +108,6 @@ func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_mo }) } -// CreateOrUpdateIssueNotifications creates an issue notification -// for each watcher, or updates it if already exists -// receiverID > 0 just send to receiver, else send to all watcher -func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil { - return err - } - - return committer.Commit() -} - -func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { - // init - var toNotify container.Set[int64] - notifications, err := db.Find[Notification](ctx, FindNotificationOptions{ - IssueID: issueID, - }) - if err != nil { - return err - } - - issue, err := issues_model.GetIssueByID(ctx, issueID) - if err != nil { - return err - } - - if receiverID > 0 { - toNotify = make(container.Set[int64], 1) - toNotify.Add(receiverID) - } else { - toNotify = make(container.Set[int64], 32) - issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true) - if err != nil { - return err - } - toNotify.AddMultiple(issueWatches...) - if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { - repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) - if err != nil { - return err - } - toNotify.AddMultiple(repoWatches...) - } - issueParticipants, err := issue.GetParticipantIDsByIssue(ctx) - if err != nil { - return err - } - toNotify.AddMultiple(issueParticipants...) - - // dont notify user who cause notification - delete(toNotify, notificationAuthorID) - // explicit unwatch on issue - issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false) - if err != nil { - return err - } - for _, id := range issueUnWatches { - toNotify.Remove(id) - } - } - - err = issue.LoadRepo(ctx) - if err != nil { - return err - } - - // notify - for userID := range toNotify { - issue.Repo.Units = nil - user, err := user_model.GetUserByID(ctx, userID) - if err != nil { - if user_model.IsErrUserNotExist(err) { - continue - } - - return err - } - if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { - continue - } - if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { - continue - } - - if notificationExists(notifications, issue.ID, userID) { - if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil { - return err - } - continue - } - if err = createIssueNotification(ctx, userID, issue, commentID, notificationAuthorID); err != nil { - return err - } - } - return nil -} - func createIssueNotification(ctx context.Context, userID int64, issue *issues_model.Issue, commentID, updatedByID int64) error { notification := &Notification{ UserID: userID, @@ -449,309 +295,6 @@ func GetUIDsAndNotificationCounts(ctx context.Context, since, until timeutil.Tim return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res) } -// NotificationList contains a list of notifications -type NotificationList []*Notification - -// LoadAttributes load Repo Issue User and Comment if not loaded -func (nl NotificationList) LoadAttributes(ctx context.Context) error { - if _, _, err := nl.LoadRepos(ctx); err != nil { - return err - } - if _, err := nl.LoadIssues(ctx); err != nil { - return err - } - if _, err := nl.LoadUsers(ctx); err != nil { - return err - } - if _, err := nl.LoadComments(ctx); err != nil { - return err - } - return nil -} - -func (nl NotificationList) getPendingRepoIDs() []int64 { - ids := make(container.Set[int64], len(nl)) - for _, notification := range nl { - if notification.Repository != nil { - continue - } - ids.Add(notification.RepoID) - } - return ids.Values() -} - -// LoadRepos loads repositories from database -func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.RepositoryList, []int, error) { - if len(nl) == 0 { - return repo_model.RepositoryList{}, []int{}, nil - } - - repoIDs := nl.getPendingRepoIDs() - repos := make(map[int64]*repo_model.Repository, len(repoIDs)) - left := len(repoIDs) - for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } - rows, err := db.GetEngine(ctx). - In("id", repoIDs[:limit]). - Rows(new(repo_model.Repository)) - if err != nil { - return nil, nil, err - } - - for rows.Next() { - var repo repo_model.Repository - err = rows.Scan(&repo) - if err != nil { - rows.Close() - return nil, nil, err - } - - repos[repo.ID] = &repo - } - _ = rows.Close() - - left -= limit - repoIDs = repoIDs[limit:] - } - - failed := []int{} - - reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) - for i, notification := range nl { - if notification.Repository == nil { - notification.Repository = repos[notification.RepoID] - } - if notification.Repository == nil { - log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID) - failed = append(failed, i) - continue - } - var found bool - for _, r := range reposList { - if r.ID == notification.RepoID { - found = true - break - } - } - if !found { - reposList = append(reposList, notification.Repository) - } - } - return reposList, failed, nil -} - -func (nl NotificationList) getPendingIssueIDs() []int64 { - ids := make(container.Set[int64], len(nl)) - for _, notification := range nl { - if notification.Issue != nil { - continue - } - ids.Add(notification.IssueID) - } - return ids.Values() -} - -// LoadIssues loads issues from database -func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) { - if len(nl) == 0 { - return []int{}, nil - } - - issueIDs := nl.getPendingIssueIDs() - issues := make(map[int64]*issues_model.Issue, len(issueIDs)) - left := len(issueIDs) - for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } - rows, err := db.GetEngine(ctx). - In("id", issueIDs[:limit]). - Rows(new(issues_model.Issue)) - if err != nil { - return nil, err - } - - for rows.Next() { - var issue issues_model.Issue - err = rows.Scan(&issue) - if err != nil { - rows.Close() - return nil, err - } - - issues[issue.ID] = &issue - } - _ = rows.Close() - - left -= limit - issueIDs = issueIDs[limit:] - } - - failures := []int{} - - for i, notification := range nl { - if notification.Issue == nil { - notification.Issue = issues[notification.IssueID] - if notification.Issue == nil { - if notification.IssueID != 0 { - log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID) - failures = append(failures, i) - } - continue - } - notification.Issue.Repo = notification.Repository - } - } - return failures, nil -} - -// Without returns the notification list without the failures -func (nl NotificationList) Without(failures []int) NotificationList { - if len(failures) == 0 { - return nl - } - remaining := make([]*Notification, 0, len(nl)) - last := -1 - var i int - for _, i = range failures { - remaining = append(remaining, nl[last+1:i]...) - last = i - } - if len(nl) > i { - remaining = append(remaining, nl[i+1:]...) - } - return remaining -} - -func (nl NotificationList) getPendingCommentIDs() []int64 { - ids := make(container.Set[int64], len(nl)) - for _, notification := range nl { - if notification.CommentID == 0 || notification.Comment != nil { - continue - } - ids.Add(notification.CommentID) - } - return ids.Values() -} - -func (nl NotificationList) getUserIDs() []int64 { - ids := make(container.Set[int64], len(nl)) - for _, notification := range nl { - if notification.UserID == 0 || notification.User != nil { - continue - } - ids.Add(notification.UserID) - } - return ids.Values() -} - -// LoadUsers loads users from database -func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) { - if len(nl) == 0 { - return []int{}, nil - } - - userIDs := nl.getUserIDs() - users := make(map[int64]*user_model.User, len(userIDs)) - left := len(userIDs) - for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } - rows, err := db.GetEngine(ctx). - In("id", userIDs[:limit]). - Rows(new(user_model.User)) - if err != nil { - return nil, err - } - - for rows.Next() { - var user user_model.User - err = rows.Scan(&user) - if err != nil { - rows.Close() - return nil, err - } - - users[user.ID] = &user - } - _ = rows.Close() - - left -= limit - userIDs = userIDs[limit:] - } - - failures := []int{} - for i, notification := range nl { - if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil { - notification.User = users[notification.UserID] - if notification.User == nil { - log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID) - failures = append(failures, i) - continue - } - } - } - return failures, nil -} - -// LoadComments loads comments from database -func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) { - if len(nl) == 0 { - return []int{}, nil - } - - commentIDs := nl.getPendingCommentIDs() - comments := make(map[int64]*issues_model.Comment, len(commentIDs)) - left := len(commentIDs) - for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } - rows, err := db.GetEngine(ctx). - In("id", commentIDs[:limit]). - Rows(new(issues_model.Comment)) - if err != nil { - return nil, err - } - - for rows.Next() { - var comment issues_model.Comment - err = rows.Scan(&comment) - if err != nil { - rows.Close() - return nil, err - } - - comments[comment.ID] = &comment - } - _ = rows.Close() - - left -= limit - commentIDs = commentIDs[limit:] - } - - failures := []int{} - for i, notification := range nl { - if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil { - notification.Comment = comments[notification.CommentID] - if notification.Comment == nil { - log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID) - failures = append(failures, i) - continue - } - notification.Comment.Issue = notification.Issue - } - } - return failures, nil -} - // SetIssueReadBy sets issue to be read by given user. func SetIssueReadBy(ctx context.Context, issueID, userID int64) error { if err := issues_model.UpdateIssueUserByRead(ctx, userID, issueID); err != nil { diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go new file mode 100644 index 0000000000000..957f9456e7bdd --- /dev/null +++ b/models/activities/notification_list.go @@ -0,0 +1,472 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package activities + +import ( + "context" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + access_model "code.gitea.io/gitea/models/perm/access" + 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/container" + "code.gitea.io/gitea/modules/log" + + "xorm.io/builder" +) + +// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored. +type FindNotificationOptions struct { + db.ListOptions + UserID int64 + RepoID int64 + IssueID int64 + Status []NotificationStatus + Source []NotificationSource + UpdatedAfterUnix int64 + UpdatedBeforeUnix int64 +} + +// ToCond will convert each condition into a xorm-Cond +func (opts FindNotificationOptions) ToConds() builder.Cond { + cond := builder.NewCond() + if opts.UserID != 0 { + cond = cond.And(builder.Eq{"notification.user_id": opts.UserID}) + } + if opts.RepoID != 0 { + cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID}) + } + if opts.IssueID != 0 { + cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID}) + } + if len(opts.Status) > 0 { + if len(opts.Status) == 1 { + cond = cond.And(builder.Eq{"notification.status": opts.Status[0]}) + } else { + cond = cond.And(builder.In("notification.status", opts.Status)) + } + } + if len(opts.Source) > 0 { + cond = cond.And(builder.In("notification.source", opts.Source)) + } + if opts.UpdatedAfterUnix != 0 { + cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix}) + } + if opts.UpdatedBeforeUnix != 0 { + cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix}) + } + return cond +} + +func (opts FindNotificationOptions) ToOrders() string { + return "notification.updated_unix DESC" +} + +// CreateOrUpdateIssueNotifications creates an issue notification +// for each watcher, or updates it if already exists +// receiverID > 0 just send to receiver, else send to all watcher +func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil { + return err + } + + return committer.Commit() +} + +func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { + // init + var toNotify container.Set[int64] + notifications, err := db.Find[Notification](ctx, FindNotificationOptions{ + IssueID: issueID, + }) + if err != nil { + return err + } + + issue, err := issues_model.GetIssueByID(ctx, issueID) + if err != nil { + return err + } + + if receiverID > 0 { + toNotify = make(container.Set[int64], 1) + toNotify.Add(receiverID) + } else { + toNotify = make(container.Set[int64], 32) + issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true) + if err != nil { + return err + } + toNotify.AddMultiple(issueWatches...) + if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { + repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) + if err != nil { + return err + } + toNotify.AddMultiple(repoWatches...) + } + issueParticipants, err := issue.GetParticipantIDsByIssue(ctx) + if err != nil { + return err + } + toNotify.AddMultiple(issueParticipants...) + + // dont notify user who cause notification + delete(toNotify, notificationAuthorID) + // explicit unwatch on issue + issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false) + if err != nil { + return err + } + for _, id := range issueUnWatches { + toNotify.Remove(id) + } + } + + err = issue.LoadRepo(ctx) + if err != nil { + return err + } + + // notify + for userID := range toNotify { + issue.Repo.Units = nil + user, err := user_model.GetUserByID(ctx, userID) + if err != nil { + if user_model.IsErrUserNotExist(err) { + continue + } + + return err + } + if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { + continue + } + if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { + continue + } + + if notificationExists(notifications, issue.ID, userID) { + if err = updateIssueNotification(ctx, userID, issue.ID, commentID, notificationAuthorID); err != nil { + return err + } + continue + } + if err = createIssueNotification(ctx, userID, issue, commentID, notificationAuthorID); err != nil { + return err + } + } + return nil +} + +// NotificationList contains a list of notifications +type NotificationList []*Notification + +// LoadAttributes load Repo Issue User and Comment if not loaded +func (nl NotificationList) LoadAttributes(ctx context.Context) error { + if _, _, err := nl.LoadRepos(ctx); err != nil { + return err + } + if _, err := nl.LoadIssues(ctx); err != nil { + return err + } + if _, err := nl.LoadUsers(ctx); err != nil { + return err + } + if _, err := nl.LoadComments(ctx); err != nil { + return err + } + return nil +} + +func (nl NotificationList) getPendingRepoIDs() []int64 { + ids := make(container.Set[int64], len(nl)) + for _, notification := range nl { + if notification.Repository != nil { + continue + } + ids.Add(notification.RepoID) + } + return ids.Values() +} + +// LoadRepos loads repositories from database +func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.RepositoryList, []int, error) { + if len(nl) == 0 { + return repo_model.RepositoryList{}, []int{}, nil + } + + repoIDs := nl.getPendingRepoIDs() + repos := make(map[int64]*repo_model.Repository, len(repoIDs)) + left := len(repoIDs) + for left > 0 { + limit := db.DefaultMaxInSize + if left < limit { + limit = left + } + rows, err := db.GetEngine(ctx). + In("id", repoIDs[:limit]). + Rows(new(repo_model.Repository)) + if err != nil { + return nil, nil, err + } + + for rows.Next() { + var repo repo_model.Repository + err = rows.Scan(&repo) + if err != nil { + rows.Close() + return nil, nil, err + } + + repos[repo.ID] = &repo + } + _ = rows.Close() + + left -= limit + repoIDs = repoIDs[limit:] + } + + failed := []int{} + + reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) + for i, notification := range nl { + if notification.Repository == nil { + notification.Repository = repos[notification.RepoID] + } + if notification.Repository == nil { + log.Error("Notification[%d]: RepoID: %d not found", notification.ID, notification.RepoID) + failed = append(failed, i) + continue + } + var found bool + for _, r := range reposList { + if r.ID == notification.RepoID { + found = true + break + } + } + if !found { + reposList = append(reposList, notification.Repository) + } + } + return reposList, failed, nil +} + +func (nl NotificationList) getPendingIssueIDs() []int64 { + ids := make(container.Set[int64], len(nl)) + for _, notification := range nl { + if notification.Issue != nil { + continue + } + ids.Add(notification.IssueID) + } + return ids.Values() +} + +// LoadIssues loads issues from database +func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) { + if len(nl) == 0 { + return []int{}, nil + } + + issueIDs := nl.getPendingIssueIDs() + issues := make(map[int64]*issues_model.Issue, len(issueIDs)) + left := len(issueIDs) + for left > 0 { + limit := db.DefaultMaxInSize + if left < limit { + limit = left + } + rows, err := db.GetEngine(ctx). + In("id", issueIDs[:limit]). + Rows(new(issues_model.Issue)) + if err != nil { + return nil, err + } + + for rows.Next() { + var issue issues_model.Issue + err = rows.Scan(&issue) + if err != nil { + rows.Close() + return nil, err + } + + issues[issue.ID] = &issue + } + _ = rows.Close() + + left -= limit + issueIDs = issueIDs[limit:] + } + + failures := []int{} + + for i, notification := range nl { + if notification.Issue == nil { + notification.Issue = issues[notification.IssueID] + if notification.Issue == nil { + if notification.IssueID != 0 { + log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID) + failures = append(failures, i) + } + continue + } + notification.Issue.Repo = notification.Repository + } + } + return failures, nil +} + +// Without returns the notification list without the failures +func (nl NotificationList) Without(failures []int) NotificationList { + if len(failures) == 0 { + return nl + } + remaining := make([]*Notification, 0, len(nl)) + last := -1 + var i int + for _, i = range failures { + remaining = append(remaining, nl[last+1:i]...) + last = i + } + if len(nl) > i { + remaining = append(remaining, nl[i+1:]...) + } + return remaining +} + +func (nl NotificationList) getPendingCommentIDs() []int64 { + ids := make(container.Set[int64], len(nl)) + for _, notification := range nl { + if notification.CommentID == 0 || notification.Comment != nil { + continue + } + ids.Add(notification.CommentID) + } + return ids.Values() +} + +func (nl NotificationList) getUserIDs() []int64 { + ids := make(container.Set[int64], len(nl)) + for _, notification := range nl { + if notification.UserID == 0 || notification.User != nil { + continue + } + ids.Add(notification.UserID) + } + return ids.Values() +} + +// LoadUsers loads users from database +func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) { + if len(nl) == 0 { + return []int{}, nil + } + + userIDs := nl.getUserIDs() + users := make(map[int64]*user_model.User, len(userIDs)) + left := len(userIDs) + for left > 0 { + limit := db.DefaultMaxInSize + if left < limit { + limit = left + } + rows, err := db.GetEngine(ctx). + In("id", userIDs[:limit]). + Rows(new(user_model.User)) + if err != nil { + return nil, err + } + + for rows.Next() { + var user user_model.User + err = rows.Scan(&user) + if err != nil { + rows.Close() + return nil, err + } + + users[user.ID] = &user + } + _ = rows.Close() + + left -= limit + userIDs = userIDs[limit:] + } + + failures := []int{} + for i, notification := range nl { + if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil { + notification.User = users[notification.UserID] + if notification.User == nil { + log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID) + failures = append(failures, i) + continue + } + } + } + return failures, nil +} + +// LoadComments loads comments from database +func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) { + if len(nl) == 0 { + return []int{}, nil + } + + commentIDs := nl.getPendingCommentIDs() + comments := make(map[int64]*issues_model.Comment, len(commentIDs)) + left := len(commentIDs) + for left > 0 { + limit := db.DefaultMaxInSize + if left < limit { + limit = left + } + rows, err := db.GetEngine(ctx). + In("id", commentIDs[:limit]). + Rows(new(issues_model.Comment)) + if err != nil { + return nil, err + } + + for rows.Next() { + var comment issues_model.Comment + err = rows.Scan(&comment) + if err != nil { + rows.Close() + return nil, err + } + + comments[comment.ID] = &comment + } + _ = rows.Close() + + left -= limit + commentIDs = commentIDs[limit:] + } + + failures := []int{} + for i, notification := range nl { + if notification.CommentID > 0 && notification.Comment == nil && comments[notification.CommentID] != nil { + notification.Comment = comments[notification.CommentID] + if notification.Comment == nil { + log.Error("Notification[%d]: CommentID[%d] failed to load", notification.ID, notification.CommentID) + failures = append(failures, i) + continue + } + notification.Comment.Issue = notification.Issue + } + } + return failures, nil +} diff --git a/modules/actions/task_state.go b/modules/actions/task_state.go index fe925bbb5d987..31a74be3fd360 100644 --- a/modules/actions/task_state.go +++ b/modules/actions/task_state.go @@ -41,6 +41,12 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { } logIndex += preStep.LogLength + // lastHasRunStep is the last step that has run. + // For example, + // 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1. + // 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3. + // 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2. + // So its Stopped is the Started of postStep when there are no more steps to run. var lastHasRunStep *actions_model.ActionTaskStep for _, step := range task.Steps { if step.Status.HasRun() { @@ -56,11 +62,15 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { Name: postStepName, Status: actions_model.StatusWaiting, } - if task.Status.IsDone() { + // If the lastHasRunStep is the last step, or it has failed, postStep has started. + if lastHasRunStep.Status.IsFailure() || lastHasRunStep == task.Steps[len(task.Steps)-1] { postStep.LogIndex = logIndex postStep.LogLength = task.LogLength - postStep.LogIndex - postStep.Status = task.Status postStep.Started = lastHasRunStep.Stopped + postStep.Status = actions_model.StatusRunning + } + if task.Status.IsDone() { + postStep.Status = task.Status postStep.Stopped = task.Stopped } ret := make([]*actions_model.ActionTaskStep, 0, len(task.Steps)+2) diff --git a/modules/actions/task_state_test.go b/modules/actions/task_state_test.go index 3a599fbcbd2b0..28213d781befe 100644 --- a/modules/actions/task_state_test.go +++ b/modules/actions/task_state_test.go @@ -103,6 +103,40 @@ func TestFullSteps(t *testing.T) { {Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100}, }, }, + { + name: "all steps finished but task is running", + task: &actions_model.ActionTask{ + Steps: []*actions_model.ActionTaskStep{ + {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, + }, + Status: actions_model.StatusRunning, + Started: 10000, + Stopped: 0, + LogLength: 100, + }, + want: []*actions_model.ActionTaskStep{ + {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010}, + {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, + {Name: postStepName, Status: actions_model.StatusRunning, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 0}, + }, + }, + { + name: "skipped task", + task: &actions_model.ActionTask{ + Steps: []*actions_model.ActionTaskStep{ + {Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + }, + Status: actions_model.StatusSkipped, + Started: 0, + Stopped: 0, + LogLength: 0, + }, + want: []*actions_model.ActionTaskStep{ + {Name: preStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + {Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + {Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/git/repo.go b/modules/git/repo.go index cef45c6af0cd9..4511e900e08a2 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -283,7 +283,7 @@ type DivergeObject struct { // GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) { cmd := NewCommand(ctx, "rev-list", "--count", "--left-right"). - AddDynamicArguments(baseBranch + "..." + targetBranch) + AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--") stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return do, err diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 3dad39f7b1df7..a09956f73865d 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -75,6 +75,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { updates = append(updates, option) if repo.IsEmpty && (refFullName.BranchName() == "master" || refFullName.BranchName() == "main") { // put the master/main branch first + // FIXME: It doesn't always work, since the master/main branch may not be the first batch of updates. + // If the user pushes many branches at once, the Git hook will call the internal API in batches, rather than all at once. + // See https://github.com/go-gitea/gitea/blob/cb52b17f92e2d2293f7c003649743464492bca48/cmd/hook.go#L27 + // If the user executes `git push origin --all` and pushes more than 30 branches, the master/main may not be the default branch. copy(updates[1:], updates) updates[0] = option } @@ -129,9 +133,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { commitIDs = append(commitIDs, update.NewCommitID) } - if err := repo_service.SyncBranchesToDB(ctx, repo.ID, opts.UserID, branchNames, commitIDs, func(commitID string) (*git.Commit, error) { - return gitRepo.GetCommit(commitID) - }); err != nil { + if err := repo_service.SyncBranchesToDB(ctx, repo.ID, opts.UserID, branchNames, commitIDs, gitRepo.GetCommit); err != nil { ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ Err: fmt.Sprintf("Failed to sync branch to DB in repository: %s/%s Error: %v", ownerName, repoName, err), }) diff --git a/services/repository/branch.go b/services/repository/branch.go index 8d8cfa2d19d82..0353c75fe9b2c 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -158,10 +158,7 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g p := protectedBranches.GetFirstMatched(branchName) isProtected := p != nil - divergence := &git.DivergeObject{ - Ahead: -1, - Behind: -1, - } + var divergence *git.DivergeObject // it's not default branch if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted { @@ -180,6 +177,11 @@ func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *g } } + if divergence == nil { + // tolerate the error that we cannot get divergence + divergence = &git.DivergeObject{Ahead: -1, Behind: -1} + } + pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName) if err != nil { return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err) @@ -318,11 +320,11 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, for i, branchName := range branchNames { commitID := commitIDs[i] branch, exist := branchMap[branchName] - if exist && branch.CommitID == commitID { + if exist && branch.CommitID == commitID && !branch.IsDeleted { continue } - commit, err := getCommit(branchName) + commit, err := getCommit(commitID) if err != nil { return fmt.Errorf("get commit of %s failed: %v", branchName, err) } diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 1e552fba889b8..660df55999ebf 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -15,10 +15,10 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 36f7e53da99dd..f7d77eab1d41c 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -49,7 +49,7 @@ - diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index efb0a8847e12e..4609d1b8b4159 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -18,12 +18,12 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/admin/stacktrace.tmpl b/templates/admin/stacktrace.tmpl index 42944615c38b8..950aa0ea86a12 100644 --- a/templates/admin/stacktrace.tmpl +++ b/templates/admin/stacktrace.tmpl @@ -4,8 +4,8 @@
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 17c8bcb6a3c5f..7de3df5bee5c1 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -32,7 +32,7 @@
{{if .PrimaryLanguage}} - + {{.PrimaryLanguage.Language}} {{end}} diff --git a/templates/explore/search.tmpl b/templates/explore/search.tmpl index 54d995989a38a..c1d114125efbc 100644 --- a/templates/explore/search.tmpl +++ b/templates/explore/search.tmpl @@ -13,10 +13,10 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 414c9dca2ef06..d87e7e0663b32 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -1,11 +1,11 @@ {{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 62d30305b3603..55c0494566430 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -8,9 +8,9 @@
- + {{ctx.Locale.Tr "actions.runs.actors_no_select"}} {{range .Actors}} - + {{ctx.AvatarUtils.Avatar . 20}} {{.GetDisplayName}} {{end}} @@ -54,11 +54,11 @@ {{svg "octicon-search"}}
- + {{ctx.Locale.Tr "actions.runs.status_no_select"}} {{range .StatusInfoList}} - + {{.DisplayedStatus}} {{end}} diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 39df6faea50dc..d2ee5db1b82cd 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -68,7 +68,7 @@ binaryFileMessage: "{{ctx.Locale.Tr "repo.diff.bin"}}", showMoreMessage: "{{ctx.Locale.Tr "repo.diff.show_more"}}", statisticsMessage: "{{ctx.Locale.Tr "repo.diff.stats_desc_file"}}", - linkLoadMore: "{{$.Link}}?skip-to={{.Diff.End}}&file-only=true", + linkLoadMore: "?skip-to={{.Diff.End}}&file-only=true", }; // for first time loading, the diffFileInfo is a plain object @@ -184,7 +184,7 @@ {{ctx.Locale.Tr "repo.diff.file_suppressed_line_too_long"}} {{else}} {{ctx.Locale.Tr "repo.diff.file_suppressed"}} - {{ctx.Locale.Tr "repo.diff.load"}} + {{ctx.Locale.Tr "repo.diff.load"}} {{end}} {{else}} {{ctx.Locale.Tr "repo.diff.bin_not_shown"}} @@ -220,7 +220,7 @@

{{ctx.Locale.Tr "repo.diff.too_many_files"}} - {{ctx.Locale.Tr "repo.diff.show_more"}} + {{ctx.Locale.Tr "repo.diff.show_more"}}

{{end}} diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl index bb13f63b9889b..696b7db46b62a 100644 --- a/templates/repo/issue/filter_list.tmpl +++ b/templates/repo/issue/filter_list.tmpl @@ -23,8 +23,8 @@
{{ctx.Locale.Tr "repo.issues.filter_label_exclude"}}
- {{ctx.Locale.Tr "repo.issues.filter_label_no_select"}} - {{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}} + {{ctx.Locale.Tr "repo.issues.filter_label_no_select"}} + {{ctx.Locale.Tr "repo.issues.filter_label_select_no_label"}} {{$previousExclusiveScope := "_no_scope"}} {{range .Labels}} {{$exclusiveScope := .ExclusiveScope}} @@ -32,7 +32,7 @@
{{end}} {{$previousExclusiveScope = $exclusiveScope}} - + {{if .IsExcluded}} {{svg "octicon-circle-slash"}} {{else if .IsSelected}} @@ -62,13 +62,13 @@
-
{{ctx.Locale.Tr "repo.issues.filter_milestone_all"}} - {{ctx.Locale.Tr "repo.issues.filter_milestone_none"}} + {{ctx.Locale.Tr "repo.issues.filter_milestone_all"}} + {{ctx.Locale.Tr "repo.issues.filter_milestone_none"}} {{if .OpenMilestones}}
{{ctx.Locale.Tr "repo.issues.filter_milestone_open"}}
{{range .OpenMilestones}} - + {{svg "octicon-milestone" 16 "mr-2"}} {{.Name}} @@ -78,7 +78,7 @@
{{ctx.Locale.Tr "repo.issues.filter_milestone_closed"}}
{{range .ClosedMilestones}} - + {{svg "octicon-milestone" 16 "mr-2"}} {{.Name}} @@ -99,15 +99,15 @@ {{svg "octicon-search" 16}} - {{ctx.Locale.Tr "repo.issues.filter_project_all"}} - {{ctx.Locale.Tr "repo.issues.filter_project_none"}} + {{ctx.Locale.Tr "repo.issues.filter_project_all"}} + {{ctx.Locale.Tr "repo.issues.filter_project_none"}} {{if .OpenProjects}}
{{ctx.Locale.Tr "repo.issues.new.open_projects"}}
{{range .OpenProjects}} - + {{svg .IconName 18 "gt-mr-3 tw-shrink-0"}}{{.Title}} {{end}} @@ -118,7 +118,7 @@ {{ctx.Locale.Tr "repo.issues.new.closed_projects"}} {{range .ClosedProjects}} - + {{svg .IconName 18 "gt-mr-3"}}{{.Title}} {{end}} @@ -130,7 +130,7 @@ - {{ctx.Locale.Tr "repo.issues.filter_assginee_no_select"}} - {{ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee"}} + {{ctx.Locale.Tr "repo.issues.filter_assginee_no_select"}} + {{ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee"}}
{{range .Assignees}} - + {{ctx.AvatarUtils.Avatar . 20}}{{template "repo/search_name" .}} {{end}} @@ -175,14 +175,14 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} {{end}} @@ -194,13 +194,13 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl index ca8d40f1e0c1f..ca28e3af2d0fa 100644 --- a/templates/repo/issue/labels/label_list.tmpl +++ b/templates/repo/issue/labels/label_list.tmpl @@ -9,10 +9,10 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/repo/issue/milestone/filter_list.tmpl b/templates/repo/issue/milestone/filter_list.tmpl index 0eea42d6ee61f..45f9866a165b3 100644 --- a/templates/repo/issue/milestone/filter_list.tmpl +++ b/templates/repo/issue/milestone/filter_list.tmpl @@ -5,11 +5,11 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index c9170d97469c5..6654224320168 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -374,10 +374,9 @@ {{$reviewType := -1}} {{if .Review}}{{$reviewType = .Review.Type}}{{end}} {{if not .OriginalAuthor}} - {{/* Some timeline avatars need a offset to correctly align with their speech - bubble. The condition depends on review type and for positive reviews whether - there is a comment element or not */}} - + {{/* Some timeline avatars need a offset to correctly align with their speech bubble. + The condition depends on whether the comment has contents/attachments or reviews */}} + {{ctx.AvatarUtils.Avatar .Poster 40}} {{end}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index cdd415a47a99b..8c1e7982eb536 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -37,7 +37,7 @@
{{if .HasSourceRenderedToggle}}
- {{svg "octicon-code" 15}} + {{svg "octicon-code" 15}} {{svg "octicon-file" 15}}
{{end}} diff --git a/templates/repo/wiki/new.tmpl b/templates/repo/wiki/new.tmpl index 640f8ca9cd607..411c7fc869044 100644 --- a/templates/repo/wiki/new.tmpl +++ b/templates/repo/wiki/new.tmpl @@ -9,7 +9,7 @@ {{ctx.Locale.Tr "repo.wiki.new_page_button"}} {{end}}
- + {{.CsrfTokenHtml}}
diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index fb502413fa19e..adb2f61c54a11 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -21,7 +21,7 @@ {{end}} {{range .Labels}} - {{RenderLabel $.Context ctx.Locale .}} + {{RenderLabel $.Context ctx.Locale .}} {{end}}
diff --git a/templates/shared/search/code/results.tmpl b/templates/shared/search/code/results.tmpl index de5ee0c311efe..68b183e9bf430 100644 --- a/templates/shared/search/code/results.tmpl +++ b/templates/shared/search/code/results.tmpl @@ -1,7 +1,7 @@
{{range $term := .SearchResultLanguages}} + href="?q={{$.Keyword}}{{if ne $.Language $term.Language}}&l={{$term.Language}}{{end}}&fuzzy={{$.IsFuzzy}}"> {{$term.Language}}
{{$term.Count}}
diff --git a/templates/user/auth/reset_passwd.tmpl b/templates/user/auth/reset_passwd.tmpl index 4d569e206c0e6..f8303feef38d9 100644 --- a/templates/user/auth/reset_passwd.tmpl +++ b/templates/user/auth/reset_passwd.tmpl @@ -51,7 +51,7 @@
{{if and .has_two_factor (not .scratch_code)}} - {{ctx.Locale.Tr "auth.use_scratch_code"}} + {{ctx.Locale.Tr "auth.use_scratch_code"}} {{end}}
{{else}} diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 0fbf9a7361b4f..88afcf58ec273 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -5,29 +5,29 @@
-
+
diff --git a/templates/user/notification/notification_subscriptions.tmpl b/templates/user/notification/notification_subscriptions.tmpl index a37f0c352e046..d39e628263b7b 100644 --- a/templates/user/notification/notification_subscriptions.tmpl +++ b/templates/user/notification/notification_subscriptions.tmpl @@ -14,14 +14,14 @@ diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl index e57658b197338..2f90d0bdad8c4 100644 --- a/templates/user/settings/keys_gpg.tmpl +++ b/templates/user/settings/keys_gpg.tmpl @@ -73,7 +73,7 @@ {{ctx.Locale.Tr "settings.delete_key"}} {{if and (not .Verified) (ne $.VerifyingID .KeyID)}} - {{ctx.Locale.Tr "settings.gpg_key_verify"}} + {{ctx.Locale.Tr "settings.gpg_key_verify"}} {{end}}
diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl index 94ee2a1a55cda..5577cd0ffd1cc 100644 --- a/templates/user/settings/keys_ssh.tmpl +++ b/templates/user/settings/keys_ssh.tmpl @@ -61,7 +61,7 @@ {{ctx.Locale.Tr "settings.delete_key"}} {{if and (not .Verified) (ne $.VerifyingFingerprint .Fingerprint)}} - {{ctx.Locale.Tr "settings.ssh_key_verify"}} + {{ctx.Locale.Tr "settings.ssh_key_verify"}} {{end}}
diff --git a/tests/integration/explore_user_test.go b/tests/integration/explore_user_test.go index 046caf378eff5..441d89cea5416 100644 --- a/tests/integration/explore_user_test.go +++ b/tests/integration/explore_user_test.go @@ -16,17 +16,17 @@ func TestExploreUser(t *testing.T) { defer tests.PrepareTestEnv(t)() cases := []struct{ sortOrder, expected string }{ - {"", "/explore/users?sort=newest&q="}, - {"newest", "/explore/users?sort=newest&q="}, - {"oldest", "/explore/users?sort=oldest&q="}, - {"alphabetically", "/explore/users?sort=alphabetically&q="}, - {"reversealphabetically", "/explore/users?sort=reversealphabetically&q="}, + {"", "?sort=newest&q="}, + {"newest", "?sort=newest&q="}, + {"oldest", "?sort=oldest&q="}, + {"alphabetically", "?sort=alphabetically&q="}, + {"reversealphabetically", "?sort=reversealphabetically&q="}, } for _, c := range cases { req := NewRequest(t, "GET", "/explore/users?sort="+c.sortOrder) resp := MakeRequest(t, req, http.StatusOK) h := NewHTMLParser(t, resp.Body) - href, _ := h.Find(`.ui.dropdown .menu a.active.item[href^="/explore/users"]`).Attr("href") + href, _ := h.Find(`.ui.dropdown .menu a.active.item[href^="?sort="]`).Attr("href") assert.Equal(t, c.expected, href) } diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index cb2910b175e8d..0a357248077ea 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -69,6 +69,23 @@ func testGitPush(t *testing.T, u *url.URL) { return pushed, deleted }) }) + + t.Run("Push to deleted branch", func(t *testing.T) { + runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { + doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete + pushed = append(pushed, "master") + + doGitCreateBranch(gitPath, "branch-1")(t) + doGitPushTestRepository(gitPath, "origin", "branch-1")(t) + pushed = append(pushed, "branch-1") + + // delete and restore + doGitPushTestRepository(gitPath, "origin", "--delete", "branch-1")(t) + doGitPushTestRepository(gitPath, "origin", "branch-1")(t) + + return pushed, deleted + }) + }) } func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gitPath string) (pushed, deleted []string)) { diff --git a/web_src/css/base.css b/web_src/css/base.css index a6525fa041fef..c347ad3a9d02d 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -227,16 +227,6 @@ a.label, border-bottom-color: var(--color-secondary); } -.page-content { - margin-top: 15px; -} - -.page-content .header-wrapper, -.page-content overflow-menu { - margin-top: -15px !important; - padding-top: 15px !important; -} - /* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */ .ui.input > input { line-height: var(--line-height-default); @@ -678,6 +668,11 @@ img.ui.avatar, padding-bottom: 80px; } +.page-content.new:is(.repo,.migrate,.org), +.page-content.profile:is(.user,.organization) { + padding-top: 15px; +} + /* overwrite semantic width of containers inside the main page content div (div with class "page-content") */ .page-content .ui.ui.ui.container:not(.fluid) { width: 1280px; diff --git a/web_src/css/dashboard.css b/web_src/css/dashboard.css index 51ddd45e31214..140cd2e4bf644 100644 --- a/web_src/css/dashboard.css +++ b/web_src/css/dashboard.css @@ -78,8 +78,7 @@ } .dashboard .dashboard-navbar { - padding-left: 0.5rem; - padding-right: 0.5rem; + padding: 4px 12px; } .dashboard .dashboard-navbar .org-visibility .label { diff --git a/web_src/css/repo/header.css b/web_src/css/repo/header.css index 4461e3338e5fb..f66df4cd8b2b4 100644 --- a/web_src/css/repo/header.css +++ b/web_src/css/repo/header.css @@ -71,6 +71,7 @@ } .repository .header-wrapper { + padding-top: 12px; background-color: var(--color-header-wrapper); } diff --git a/web_src/css/user.css b/web_src/css/user.css index af8a2f5adc66a..4267ca0b7d7ad 100644 --- a/web_src/css/user.css +++ b/web_src/css/user.css @@ -112,6 +112,10 @@ border: 1px solid var(--color-secondary); } +#notification_div { + padding-top: 15px; +} + #notification_table { background: var(--color-box-body); border: 1px solid var(--color-secondary); diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js index 992d4ef0206db..541052c174455 100644 --- a/web_src/js/features/comp/SearchUserBox.js +++ b/web_src/js/features/comp/SearchUserBox.js @@ -5,9 +5,12 @@ const {appSubUrl} = window.config; const looksLikeEmailAddressCheck = /^\S+@\S+$/; export function initCompSearchUserBox() { - const $searchUserBox = $('#search-user-box'); - const allowEmailInput = $searchUserBox.attr('data-allow-email') === 'true'; - const allowEmailDescription = $searchUserBox.attr('data-allow-email-description'); + const searchUserBox = document.getElementById('search-user-box'); + if (!searchUserBox) return; + + const $searchUserBox = $(searchUserBox); + const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true'; + const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined; $searchUserBox.search({ minCharacters: 2, apiSettings: { diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index 293e1f809a799..2bac13b0bf689 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -70,7 +70,7 @@ export function initImageDiff() { $('.image-diff:not([data-image-diff-loaded])').each(async function() { const $container = $(this); - $container.attr('data-image-diff-loaded', 'true'); + this.setAttribute('data-image-diff-loaded', 'true'); // the container may be hidden by "viewed" checkbox, so use the parent's width for reference const diffContainerWidth = Math.max($container.closest('.diff-file-box').width() - 300, 100); diff --git a/web_src/js/features/repo-common.js b/web_src/js/features/repo-common.js index e2d162c86d05b..0557e53631ddb 100644 --- a/web_src/js/features/repo-common.js +++ b/web_src/js/features/repo-common.js @@ -36,7 +36,7 @@ async function getArchive($target, url, first) { export function initRepoArchiveLinks() { $('.archive-link').on('click', function (event) { event.preventDefault(); - const url = $(this).attr('href'); + const url = this.getAttribute('href'); if (!url) return; getArchive($(event.target), url, true); }); @@ -82,10 +82,10 @@ export function initRepoCommonFilterSearchDropdown(selector) { fullTextSearch: 'exact', selectOnKeydown: false, onChange(_text, _value, $choice) { - if ($choice.attr('data-url')) { - window.location.href = $choice.attr('data-url'); + if ($choice[0].getAttribute('data-url')) { + window.location.href = $choice[0].getAttribute('data-url'); } }, - message: {noResults: $dropdown.attr('data-no-results')}, + message: {noResults: $dropdown[0].getAttribute('data-no-results')}, }); } diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js index ba00573c07eec..1ab0a578656cd 100644 --- a/web_src/js/features/repo-editor.js +++ b/web_src/js/features/repo-editor.js @@ -72,7 +72,7 @@ export function initRepoEditor() { hideElem($('.quick-pull-branch-name')); document.querySelector('.quick-pull-branch-name input').required = false; } - $('#commit-button').text($(this).attr('button_text')); + $('#commit-button').text(this.getAttribute('button_text')); }); const joinTreePath = ($fileNameEl) => { diff --git a/web_src/js/features/repo-issue-list.js b/web_src/js/features/repo-issue-list.js index 880ecf94892cf..48658fd723ca5 100644 --- a/web_src/js/features/repo-issue-list.js +++ b/web_src/js/features/repo-issue-list.js @@ -93,9 +93,9 @@ function initRepoIssueListAuthorDropdown() { const $searchDropdown = $('.user-remote-search'); if (!$searchDropdown.length) return; - let searchUrl = $searchDropdown.attr('data-search-url'); - const actionJumpUrl = $searchDropdown.attr('data-action-jump-url'); - const selectedUserId = $searchDropdown.attr('data-selected-user-id'); + let searchUrl = $searchDropdown[0].getAttribute('data-search-url'); + const actionJumpUrl = $searchDropdown[0].getAttribute('data-action-jump-url'); + const selectedUserId = $searchDropdown[0].getAttribute('data-selected-user-id'); if (!searchUrl.includes('?')) searchUrl += '?'; $searchDropdown.dropdown('setting', {