Skip to content

Commit 01a4a7c

Browse files
guillep2kzeripath
authored andcommitted
Auto-subscribe user to repository when they commit/tag to it (#7657)
* Add support for AUTO_WATCH_ON_CHANGES and AUTO_WATCH_ON_CLONE * Update models/repo_watch.go Co-Authored-By: Lauris BH <lauris@nix.lv> * Round up changes suggested by lafriks * Added changes suggested from automated tests * Updated deleteUser to take RepoWatchModeDont into account, corrected inverted DefaultWatchOnClone and DefaultWatchOnChanges behaviour, updated and added tests. * Reinsert import "github.com/Unknwon/com" on http.go * Add migration for new column `watch`.`mode` * Remove serv code * Remove WATCH_ON_CLONE; use hooks, add integrations * Renamed watch_test.go to repo_watch_test.go * Correct fmt * Add missing EOL * Correct name of test function * Reword cheat and ini descriptions * Add update to migration to ensure column value * Clarify comment Co-Authored-By: zeripath <art27@cantab.net> * Simplify if condition
1 parent 8eeb287 commit 01a4a7c

File tree

14 files changed

+296
-29
lines changed

14 files changed

+296
-29
lines changed

custom/conf/app.ini.sample

+3
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,9 @@ SHOW_REGISTRATION_BUTTON = true
501501
; When adding a repo to a team or creating a new repo all team members will watch the
502502
; repo automatically if enabled
503503
AUTO_WATCH_NEW_REPOS = true
504+
; Default value for AutoWatchOnChanges
505+
; Make the user watch a repository When they commit for the first time
506+
AUTO_WATCH_ON_CHANGES = false
504507

505508
[webhook]
506509
; Hook task queue length, increase if webhook shooting starts hanging

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ relation to port exhaustion.
303303
on this instance.
304304
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
305305
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
306+
- `AUTO_WATCH_ON_CHANGES`: **false**: Enable this to make users watch a repository after their first commit to it
306307
- `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
307308
- `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.
308309

integrations/repo_watch_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"net/url"
9+
"testing"
10+
11+
"code.gitea.io/gitea/models"
12+
"code.gitea.io/gitea/modules/setting"
13+
)
14+
15+
func TestRepoWatch(t *testing.T) {
16+
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
17+
// Test round-trip auto-watch
18+
setting.Service.AutoWatchOnChanges = true
19+
session := loginUser(t, "user2")
20+
models.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3})
21+
testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n")
22+
models.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto})
23+
})
24+
}

models/consistency.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,17 @@ func (user *User) checkForConsistency(t *testing.T) {
8484
func (repo *Repository) checkForConsistency(t *testing.T) {
8585
assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
8686
assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
87-
assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches)
8887
assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
8988
assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
9089
if repo.IsFork {
9190
AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID})
9291
}
9392

94-
actual := getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
93+
actual := getCount(t, x.Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID})
94+
assert.EqualValues(t, repo.NumWatches, actual,
95+
"Unexpected number of watches for repo %+v", repo)
96+
97+
actual = getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
9598
assert.EqualValues(t, repo.NumIssues, actual,
9699
"Unexpected number of issues for repo %+v", repo)
97100

models/fixtures/repository.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
num_closed_pulls: 0
1111
num_milestones: 3
1212
num_closed_milestones: 1
13-
num_watches: 3
13+
num_watches: 4
1414
status: 0
1515

1616
-

models/fixtures/watch.yml

+15
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@
22
id: 1
33
user_id: 1
44
repo_id: 1
5+
mode: 1 # normal
56

67
-
78
id: 2
89
user_id: 4
910
repo_id: 1
11+
mode: 1 # normal
1012

1113
-
1214
id: 3
1315
user_id: 9
1416
repo_id: 1
17+
mode: 1 # normal
18+
19+
-
20+
id: 4
21+
user_id: 8
22+
repo_id: 1
23+
mode: 2 # don't watch
24+
25+
-
26+
id: 5
27+
user_id: 11
28+
repo_id: 1
29+
mode: 3 # auto

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ var migrations = []Migration{
266266
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
267267
// v105 -> v106
268268
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
269+
// v106 -> v107
270+
NewMigration("add column `mode` to table watch", addModeColumnToWatch),
269271
}
270272

271273
// Migrate database to current version

models/migrations/v106.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"xorm.io/xorm"
9+
)
10+
11+
// RepoWatchMode specifies what kind of watch the user has on a repository
12+
type RepoWatchMode int8
13+
14+
// Watch is connection request for receiving repository notification.
15+
type Watch struct {
16+
ID int64 `xorm:"pk autoincr"`
17+
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
18+
}
19+
20+
func addModeColumnToWatch(x *xorm.Engine) (err error) {
21+
if err = x.Sync2(new(Watch)); err != nil {
22+
return
23+
}
24+
_, err = x.Exec("UPDATE `watch` SET `mode` = 1")
25+
return err
26+
}

models/repo.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -2410,8 +2410,8 @@ func CheckRepoStats() {
24102410
checkers := []*repoChecker{
24112411
// Repository.NumWatches
24122412
{
2413-
"SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)",
2414-
"UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?",
2413+
"SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)",
2414+
"UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?",
24152415
"repository count 'num_watches'",
24162416
},
24172417
// Repository.NumStars

models/repo_watch.go

+119-22
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,118 @@
44

55
package models
66

7-
import "fmt"
7+
import (
8+
"fmt"
9+
10+
"code.gitea.io/gitea/modules/setting"
11+
)
12+
13+
// RepoWatchMode specifies what kind of watch the user has on a repository
14+
type RepoWatchMode int8
15+
16+
const (
17+
// RepoWatchModeNone don't watch
18+
RepoWatchModeNone RepoWatchMode = iota // 0
19+
// RepoWatchModeNormal watch repository (from other sources)
20+
RepoWatchModeNormal // 1
21+
// RepoWatchModeDont explicit don't auto-watch
22+
RepoWatchModeDont // 2
23+
// RepoWatchModeAuto watch repository (from AutoWatchOnChanges)
24+
RepoWatchModeAuto // 3
25+
)
826

927
// Watch is connection request for receiving repository notification.
1028
type Watch struct {
11-
ID int64 `xorm:"pk autoincr"`
12-
UserID int64 `xorm:"UNIQUE(watch)"`
13-
RepoID int64 `xorm:"UNIQUE(watch)"`
29+
ID int64 `xorm:"pk autoincr"`
30+
UserID int64 `xorm:"UNIQUE(watch)"`
31+
RepoID int64 `xorm:"UNIQUE(watch)"`
32+
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
1433
}
1534

16-
func isWatching(e Engine, userID, repoID int64) bool {
17-
has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID})
18-
return has
35+
// getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
36+
func getWatch(e Engine, userID, repoID int64) (Watch, error) {
37+
watch := Watch{UserID: userID, RepoID: repoID}
38+
has, err := e.Get(&watch)
39+
if err != nil {
40+
return watch, err
41+
}
42+
if !has {
43+
watch.Mode = RepoWatchModeNone
44+
}
45+
return watch, nil
46+
}
47+
48+
// Decodes watchability of RepoWatchMode
49+
func isWatchMode(mode RepoWatchMode) bool {
50+
return mode != RepoWatchModeNone && mode != RepoWatchModeDont
1951
}
2052

2153
// IsWatching checks if user has watched given repository.
2254
func IsWatching(userID, repoID int64) bool {
23-
return isWatching(x, userID, repoID)
55+
watch, err := getWatch(x, userID, repoID)
56+
return err == nil && isWatchMode(watch.Mode)
2457
}
2558

26-
func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) {
27-
if watch {
28-
if isWatching(e, userID, repoID) {
29-
return nil
30-
}
31-
if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil {
59+
func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) {
60+
if watch.Mode == mode {
61+
return nil
62+
}
63+
if mode == RepoWatchModeAuto && (watch.Mode == RepoWatchModeDont || isWatchMode(watch.Mode)) {
64+
// Don't auto watch if already watching or deliberately not watching
65+
return nil
66+
}
67+
68+
hadrec := watch.Mode != RepoWatchModeNone
69+
needsrec := mode != RepoWatchModeNone
70+
repodiff := 0
71+
72+
if isWatchMode(mode) && !isWatchMode(watch.Mode) {
73+
repodiff = 1
74+
} else if !isWatchMode(mode) && isWatchMode(watch.Mode) {
75+
repodiff = -1
76+
}
77+
78+
watch.Mode = mode
79+
80+
if !hadrec && needsrec {
81+
watch.Mode = mode
82+
if _, err = e.Insert(watch); err != nil {
3283
return err
3384
}
34-
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID)
35-
} else {
36-
if !isWatching(e, userID, repoID) {
37-
return nil
38-
}
39-
if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil {
85+
} else if needsrec {
86+
watch.Mode = mode
87+
if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil {
4088
return err
4189
}
42-
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID)
90+
} else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil {
91+
return err
92+
}
93+
if repodiff != 0 {
94+
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
95+
}
96+
return err
97+
}
98+
99+
// WatchRepoMode watch repository in specific mode.
100+
func WatchRepoMode(userID, repoID int64, mode RepoWatchMode) (err error) {
101+
var watch Watch
102+
if watch, err = getWatch(x, userID, repoID); err != nil {
103+
return err
104+
}
105+
return watchRepoMode(x, watch, mode)
106+
}
107+
108+
func watchRepo(e Engine, userID, repoID int64, doWatch bool) (err error) {
109+
var watch Watch
110+
if watch, err = getWatch(e, userID, repoID); err != nil {
111+
return err
112+
}
113+
if !doWatch && watch.Mode == RepoWatchModeAuto {
114+
err = watchRepoMode(e, watch, RepoWatchModeDont)
115+
} else if !doWatch {
116+
err = watchRepoMode(e, watch, RepoWatchModeNone)
117+
} else {
118+
err = watchRepoMode(e, watch, RepoWatchModeNormal)
43119
}
44120
return err
45121
}
@@ -52,6 +128,7 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
52128
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
53129
watches := make([]*Watch, 0, 10)
54130
return watches, e.Where("`watch`.repo_id=?", repoID).
131+
And("`watch`.mode<>?", RepoWatchModeDont).
55132
And("`user`.is_active=?", true).
56133
And("`user`.prohibit_login=?", false).
57134
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
@@ -67,7 +144,8 @@ func GetWatchers(repoID int64) ([]*Watch, error) {
67144
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
68145
users := make([]*User, 0, ItemsPerPage)
69146
sess := x.Where("watch.repo_id=?", repo.ID).
70-
Join("LEFT", "watch", "`user`.id=`watch`.user_id")
147+
Join("LEFT", "watch", "`user`.id=`watch`.user_id").
148+
And("`watch`.mode<>?", RepoWatchModeDont)
71149
if page > 0 {
72150
sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
73151
}
@@ -137,3 +215,22 @@ func notifyWatchers(e Engine, act *Action) error {
137215
func NotifyWatchers(act *Action) error {
138216
return notifyWatchers(x, act)
139217
}
218+
219+
func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error {
220+
if !isWrite || !setting.Service.AutoWatchOnChanges {
221+
return nil
222+
}
223+
watch, err := getWatch(e, userID, repoID)
224+
if err != nil {
225+
return err
226+
}
227+
if watch.Mode != RepoWatchModeNone {
228+
return nil
229+
}
230+
return watchRepoMode(e, watch, RepoWatchModeAuto)
231+
}
232+
233+
// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set
234+
func WatchIfAuto(userID int64, repoID int64, isWrite bool) error {
235+
return watchIfAuto(x, userID, repoID, isWrite)
236+
}

0 commit comments

Comments
 (0)