mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-23 16:44:41 +02:00
enhance: allow custom watches to bypass on-mention email restriction (#39652)
Users with "Only Email on Mention" can now still receive email for the events they picked in a custom repository watch, covering issues, pull requests and releases. The setting is renamed to "Only Email on Mention and Custom Watches" to make this explicit, matching GitHub's "Participating, @mentions and custom". "All activity" watches don't bypass the setting, because they are also created automatically by `AUTO_WATCH_NEW_REPOS` for repository creators and team members. To keep picking every event distinct from "All activity", the custom watch dialog now stores a new `WatchModeCustom`. Existing custom watches keep working without a migration. ## Screenshots ### Before <img width="1650" height="675" alt="grafik" src="https://github.com/user-attachments/assets/ecc56e20-cd05-41e9-a840-607b352c50a8" /> ### After <img width="1657" height="671" alt="grafik" src="https://github.com/user-attachments/assets/a406db94-3ac4-4587-b840-0cd472b135c5" /> Addresses https://github.com/openSUSE/openSUSE-git/issues/183 Ref: https://github.com/go-gitea/gitea/issues/35492 --------- Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
11 files changed
+123
-22
No files matched your search
@@ -477,7 +477,7 @@ func applySubscribedCondition(sess db.Session, subscriberID int64) {
|
||||
Select("repo_id").
|
||||
From("watch").
|
||||
Where(builder.And(builder.Eq{"user_id": subscriberID},
|
||||
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),
|
||||
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto, repo_model.WatchModeCustom))),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
+28
-7
@@ -20,9 +20,10 @@ type WatchMode int8
|
||||
const (
|
||||
WatchModeNone WatchMode = iota // 0 watch nothing unless mentioned
|
||||
|
||||
WatchModeNormal // 1 proactively watching (all or custom)
|
||||
WatchModeNormal // 1 proactively watching (all, or custom before WatchModeCustom)
|
||||
WatchModeDont // 2 ignore the repo
|
||||
WatchModeAuto // 3 automatically watching (from AutoWatchOnChanges)
|
||||
WatchModeCustom // 4 watching the events picked in the custom watch options
|
||||
)
|
||||
|
||||
// WatchType is the `watch` column gating one kind of notification
|
||||
@@ -79,7 +80,7 @@ func (w *Watch) IsWatching() bool {
|
||||
|
||||
// IsWatchingAll reports whether every event is enabled, which is the "all activity" mode
|
||||
func (w *Watch) IsWatchingAll() bool {
|
||||
return w.IncludePullRequests && w.IncludeIssues && w.IncludeReleases
|
||||
return w.Mode != WatchModeCustom && w.IncludePullRequests && w.IncludeIssues && w.IncludeReleases
|
||||
}
|
||||
|
||||
func (w *Watch) IsWatchingAny() bool {
|
||||
@@ -166,6 +167,9 @@ func WatchRepoAuto(ctx context.Context, doer *user_model.User, repo *Repository,
|
||||
if user_model.IsUserBlockedBy(ctx, doer, repo.OwnerID) {
|
||||
return user_model.ErrBlockedUser
|
||||
}
|
||||
if watch.Mode == WatchModeCustom { // keep the events the user picked
|
||||
return nil
|
||||
}
|
||||
|
||||
return watchRepoByMode(ctx, watch, WatchModeNormal)
|
||||
}
|
||||
@@ -189,7 +193,7 @@ func WatchRepoWithOptions(ctx context.Context, doer *user_model.User, repo *Repo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Mode == WatchModeNormal {
|
||||
if opts.Mode == WatchModeNormal || opts.Mode == WatchModeCustom {
|
||||
_, err = db.GetEngine(ctx).Where("user_id=? AND repo_id=?", doer.ID, repo.ID).
|
||||
Cols("include_pull_requests", "include_issues", "include_releases").
|
||||
Update(&Watch{IncludePullRequests: opts.WatchPullRequests, IncludeIssues: opts.WatchIssues, IncludeReleases: opts.WatchReleases})
|
||||
@@ -247,6 +251,16 @@ func GetRepoIgnorersIDs(ctx context.Context, repoID int64) ([]int64, error) {
|
||||
// but avoids joining with `user` for performance reasons
|
||||
// User permissions must be verified elsewhere if required
|
||||
func GetRepoWatchersIDs(ctx context.Context, repoID int64, watchType WatchType) ([]int64, error) {
|
||||
return getRepoWatchersIDs(ctx, repoID, watchType, false)
|
||||
}
|
||||
|
||||
// GetRepoCustomWatchersIDs is GetRepoWatchersIDs limited to custom watches, which unlike
|
||||
// watching all activity are never set up automatically (e.g. by AutoWatchNewRepos)
|
||||
func GetRepoCustomWatchersIDs(ctx context.Context, repoID int64, watchType WatchType) ([]int64, error) {
|
||||
return getRepoWatchersIDs(ctx, repoID, watchType, true)
|
||||
}
|
||||
|
||||
func getRepoWatchersIDs(ctx context.Context, repoID int64, watchType WatchType, customOnly bool) ([]int64, error) {
|
||||
ids := make([]int64, 0, 64)
|
||||
var watchColName string
|
||||
switch watchType {
|
||||
@@ -259,12 +273,19 @@ func GetRepoWatchersIDs(ctx context.Context, repoID int64, watchType WatchType)
|
||||
default:
|
||||
panic("invalid WatchType")
|
||||
}
|
||||
return ids, db.GetEngine(ctx).Table("watch").
|
||||
sess := db.GetEngine(ctx).Table("watch").
|
||||
Where("watch.repo_id=?", repoID).
|
||||
And("watch.mode<>?", WatchModeDont).
|
||||
And(builder.Eq{watchColName: true}).
|
||||
Select("user_id").
|
||||
Find(&ids)
|
||||
And(builder.Eq{watchColName: true})
|
||||
if customOnly { // matches SelectedMode() == "custom"
|
||||
sess = sess.And(builder.Or(
|
||||
builder.Eq{"watch.mode": WatchModeCustom},
|
||||
builder.Eq{"watch.include_pull_requests": false},
|
||||
builder.Eq{"watch.include_issues": false},
|
||||
builder.Eq{"watch.include_releases": false},
|
||||
))
|
||||
}
|
||||
return ids, sess.Select("user_id").Find(&ids)
|
||||
}
|
||||
|
||||
// GetRepoWatchers returns range of users watching given repository.
|
||||
|
||||
@@ -145,7 +145,7 @@ func TestWatchOptions(t *testing.T) {
|
||||
// repo 1 is watched by users 1, 4, 9 and 11, all with every event enabled
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
assert.NoError(t, repo_model.WatchRepoWithOptions(t.Context(), user, repo, repo_model.WatchOptions{Mode: repo_model.WatchModeNormal, WatchPullRequests: true}))
|
||||
assert.NoError(t, repo_model.WatchRepoWithOptions(t.Context(), user, repo, repo_model.WatchOptions{Mode: repo_model.WatchModeNormal, WatchPullRequests: true})) // custom watches were stored like this before WatchModeCustom
|
||||
|
||||
for watchType, expected := range map[repo_model.WatchType][]int64{
|
||||
repo_model.WatchPullRequests: {1, 4, 9, 11},
|
||||
@@ -157,6 +157,20 @@ func TestWatchOptions(t *testing.T) {
|
||||
assert.ElementsMatch(t, expected, ids, watchType)
|
||||
}
|
||||
|
||||
// a custom watch picking every event differs from all activity, which can be set up automatically
|
||||
customUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
assert.NoError(t, repo_model.WatchRepoWithOptions(t.Context(), customUser, repo, repo_model.WatchOptions{Mode: repo_model.WatchModeCustom, WatchPullRequests: true, WatchIssues: true, WatchReleases: true}))
|
||||
assert.NoError(t, repo_model.WatchRepoAuto(t.Context(), customUser, repo, true)) // must keep the custom watch
|
||||
for watchType, expected := range map[repo_model.WatchType][]int64{
|
||||
repo_model.WatchPullRequests: {1, 4},
|
||||
repo_model.WatchIssues: {4},
|
||||
repo_model.WatchReleases: {4},
|
||||
} {
|
||||
ids, err := repo_model.GetRepoCustomWatchersIDs(t.Context(), repo.ID, watchType)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expected, ids, watchType)
|
||||
}
|
||||
|
||||
// the options of one user must not show up for another
|
||||
watches, err := repo_model.GetUserWatches(t.Context(), 4, []int64{repo.ID})
|
||||
assert.NoError(t, err)
|
||||
@@ -176,5 +190,6 @@ func TestWatchSelectedMode(t *testing.T) {
|
||||
assert.Equal(t, "participate", (&repo_model.Watch{Mode: repo_model.WatchModeNormal}).SelectedMode())
|
||||
assert.Equal(t, "ignore", (&repo_model.Watch{Mode: repo_model.WatchModeDont}).SelectedMode())
|
||||
assert.Equal(t, "custom", (&repo_model.Watch{Mode: repo_model.WatchModeNormal, IncludeIssues: true}).SelectedMode())
|
||||
assert.Equal(t, "custom", (&repo_model.Watch{Mode: repo_model.WatchModeCustom, IncludePullRequests: true, IncludeIssues: true, IncludeReleases: true}).SelectedMode())
|
||||
assert.Equal(t, "all", (&repo_model.Watch{Mode: repo_model.WatchModeAuto, IncludePullRequests: true, IncludeIssues: true, IncludeReleases: true}).SelectedMode())
|
||||
}
|
||||
Reference in new issue
Block a user