* 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 conditiontags/v1.21.12.1
| @@ -501,6 +501,9 @@ SHOW_REGISTRATION_BUTTON = true | |||||
| ; When adding a repo to a team or creating a new repo all team members will watch the | ; When adding a repo to a team or creating a new repo all team members will watch the | ||||
| ; repo automatically if enabled | ; repo automatically if enabled | ||||
| AUTO_WATCH_NEW_REPOS = true | AUTO_WATCH_NEW_REPOS = true | ||||
| ; Default value for AutoWatchOnChanges | |||||
| ; Make the user watch a repository When they commit for the first time | |||||
| AUTO_WATCH_ON_CHANGES = false | |||||
| [webhook] | [webhook] | ||||
| ; Hook task queue length, increase if webhook shooting starts hanging | ; Hook task queue length, increase if webhook shooting starts hanging | ||||
| @@ -303,6 +303,7 @@ relation to port exhaustion. | |||||
| on this instance. | on this instance. | ||||
| - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button | - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button | ||||
| - `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created | - `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created | ||||
| - `AUTO_WATCH_ON_CHANGES`: **false**: Enable this to make users watch a repository after their first commit to it | |||||
| - `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private". | - `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private". | ||||
| - `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation. | - `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation. | ||||
| @@ -0,0 +1,24 @@ | |||||
| // Copyright 2019 The Gitea Authors. All rights reserved. | |||||
| // Use of this source code is governed by a MIT-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package integrations | |||||
| import ( | |||||
| "net/url" | |||||
| "testing" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| ) | |||||
| func TestRepoWatch(t *testing.T) { | |||||
| onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { | |||||
| // Test round-trip auto-watch | |||||
| setting.Service.AutoWatchOnChanges = true | |||||
| session := loginUser(t, "user2") | |||||
| models.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3}) | |||||
| testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n") | |||||
| models.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto}) | |||||
| }) | |||||
| } | |||||
| @@ -84,14 +84,17 @@ func (user *User) checkForConsistency(t *testing.T) { | |||||
| func (repo *Repository) checkForConsistency(t *testing.T) { | func (repo *Repository) checkForConsistency(t *testing.T) { | ||||
| assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo) | assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo) | ||||
| assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars) | assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars) | ||||
| assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches) | |||||
| assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones) | assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones) | ||||
| assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks) | assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks) | ||||
| if repo.IsFork { | if repo.IsFork { | ||||
| AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID}) | AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID}) | ||||
| } | } | ||||
| actual := getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID}) | |||||
| actual := getCount(t, x.Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID}) | |||||
| assert.EqualValues(t, repo.NumWatches, actual, | |||||
| "Unexpected number of watches for repo %+v", repo) | |||||
| actual = getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID}) | |||||
| assert.EqualValues(t, repo.NumIssues, actual, | assert.EqualValues(t, repo.NumIssues, actual, | ||||
| "Unexpected number of issues for repo %+v", repo) | "Unexpected number of issues for repo %+v", repo) | ||||
| @@ -10,7 +10,7 @@ | |||||
| num_closed_pulls: 0 | num_closed_pulls: 0 | ||||
| num_milestones: 3 | num_milestones: 3 | ||||
| num_closed_milestones: 1 | num_closed_milestones: 1 | ||||
| num_watches: 3 | |||||
| num_watches: 4 | |||||
| status: 0 | status: 0 | ||||
| - | - | ||||
| @@ -2,13 +2,28 @@ | |||||
| id: 1 | id: 1 | ||||
| user_id: 1 | user_id: 1 | ||||
| repo_id: 1 | repo_id: 1 | ||||
| mode: 1 # normal | |||||
| - | - | ||||
| id: 2 | id: 2 | ||||
| user_id: 4 | user_id: 4 | ||||
| repo_id: 1 | repo_id: 1 | ||||
| mode: 1 # normal | |||||
| - | - | ||||
| id: 3 | id: 3 | ||||
| user_id: 9 | user_id: 9 | ||||
| repo_id: 1 | repo_id: 1 | ||||
| mode: 1 # normal | |||||
| - | |||||
| id: 4 | |||||
| user_id: 8 | |||||
| repo_id: 1 | |||||
| mode: 2 # don't watch | |||||
| - | |||||
| id: 5 | |||||
| user_id: 11 | |||||
| repo_id: 1 | |||||
| mode: 3 # auto | |||||
| @@ -266,6 +266,8 @@ var migrations = []Migration{ | |||||
| NewMigration("remove unnecessary columns from label", removeLabelUneededCols), | NewMigration("remove unnecessary columns from label", removeLabelUneededCols), | ||||
| // v105 -> v106 | // v105 -> v106 | ||||
| NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories), | NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories), | ||||
| // v106 -> v107 | |||||
| NewMigration("add column `mode` to table watch", addModeColumnToWatch), | |||||
| } | } | ||||
| // Migrate database to current version | // Migrate database to current version | ||||
| @@ -0,0 +1,26 @@ | |||||
| // Copyright 2019 The Gitea Authors. All rights reserved. | |||||
| // Use of this source code is governed by a MIT-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package migrations | |||||
| import ( | |||||
| "xorm.io/xorm" | |||||
| ) | |||||
| // RepoWatchMode specifies what kind of watch the user has on a repository | |||||
| type RepoWatchMode int8 | |||||
| // Watch is connection request for receiving repository notification. | |||||
| type Watch struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` | |||||
| } | |||||
| func addModeColumnToWatch(x *xorm.Engine) (err error) { | |||||
| if err = x.Sync2(new(Watch)); err != nil { | |||||
| return | |||||
| } | |||||
| _, err = x.Exec("UPDATE `watch` SET `mode` = 1") | |||||
| return err | |||||
| } | |||||
| @@ -2410,8 +2410,8 @@ func CheckRepoStats() { | |||||
| checkers := []*repoChecker{ | checkers := []*repoChecker{ | ||||
| // Repository.NumWatches | // Repository.NumWatches | ||||
| { | { | ||||
| "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", | |||||
| "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", | |||||
| "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)", | |||||
| "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", | |||||
| "repository count 'num_watches'", | "repository count 'num_watches'", | ||||
| }, | }, | ||||
| // Repository.NumStars | // Repository.NumStars | ||||
| @@ -4,42 +4,118 @@ | |||||
| package models | package models | ||||
| import "fmt" | |||||
| import ( | |||||
| "fmt" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| ) | |||||
| // RepoWatchMode specifies what kind of watch the user has on a repository | |||||
| type RepoWatchMode int8 | |||||
| const ( | |||||
| // RepoWatchModeNone don't watch | |||||
| RepoWatchModeNone RepoWatchMode = iota // 0 | |||||
| // RepoWatchModeNormal watch repository (from other sources) | |||||
| RepoWatchModeNormal // 1 | |||||
| // RepoWatchModeDont explicit don't auto-watch | |||||
| RepoWatchModeDont // 2 | |||||
| // RepoWatchModeAuto watch repository (from AutoWatchOnChanges) | |||||
| RepoWatchModeAuto // 3 | |||||
| ) | |||||
| // Watch is connection request for receiving repository notification. | // Watch is connection request for receiving repository notification. | ||||
| type Watch struct { | type Watch struct { | ||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserID int64 `xorm:"UNIQUE(watch)"` | |||||
| RepoID int64 `xorm:"UNIQUE(watch)"` | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserID int64 `xorm:"UNIQUE(watch)"` | |||||
| RepoID int64 `xorm:"UNIQUE(watch)"` | |||||
| Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` | |||||
| } | } | ||||
| func isWatching(e Engine, userID, repoID int64) bool { | |||||
| has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID}) | |||||
| return has | |||||
| // getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found | |||||
| func getWatch(e Engine, userID, repoID int64) (Watch, error) { | |||||
| watch := Watch{UserID: userID, RepoID: repoID} | |||||
| has, err := e.Get(&watch) | |||||
| if err != nil { | |||||
| return watch, err | |||||
| } | |||||
| if !has { | |||||
| watch.Mode = RepoWatchModeNone | |||||
| } | |||||
| return watch, nil | |||||
| } | |||||
| // Decodes watchability of RepoWatchMode | |||||
| func isWatchMode(mode RepoWatchMode) bool { | |||||
| return mode != RepoWatchModeNone && mode != RepoWatchModeDont | |||||
| } | } | ||||
| // IsWatching checks if user has watched given repository. | // IsWatching checks if user has watched given repository. | ||||
| func IsWatching(userID, repoID int64) bool { | func IsWatching(userID, repoID int64) bool { | ||||
| return isWatching(x, userID, repoID) | |||||
| watch, err := getWatch(x, userID, repoID) | |||||
| return err == nil && isWatchMode(watch.Mode) | |||||
| } | } | ||||
| func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) { | |||||
| if watch { | |||||
| if isWatching(e, userID, repoID) { | |||||
| return nil | |||||
| } | |||||
| if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil { | |||||
| func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) { | |||||
| if watch.Mode == mode { | |||||
| return nil | |||||
| } | |||||
| if mode == RepoWatchModeAuto && (watch.Mode == RepoWatchModeDont || isWatchMode(watch.Mode)) { | |||||
| // Don't auto watch if already watching or deliberately not watching | |||||
| return nil | |||||
| } | |||||
| hadrec := watch.Mode != RepoWatchModeNone | |||||
| needsrec := mode != RepoWatchModeNone | |||||
| repodiff := 0 | |||||
| if isWatchMode(mode) && !isWatchMode(watch.Mode) { | |||||
| repodiff = 1 | |||||
| } else if !isWatchMode(mode) && isWatchMode(watch.Mode) { | |||||
| repodiff = -1 | |||||
| } | |||||
| watch.Mode = mode | |||||
| if !hadrec && needsrec { | |||||
| watch.Mode = mode | |||||
| if _, err = e.Insert(watch); err != nil { | |||||
| return err | return err | ||||
| } | } | ||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID) | |||||
| } else { | |||||
| if !isWatching(e, userID, repoID) { | |||||
| return nil | |||||
| } | |||||
| if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil { | |||||
| } else if needsrec { | |||||
| watch.Mode = mode | |||||
| if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil { | |||||
| return err | return err | ||||
| } | } | ||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID) | |||||
| } else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil { | |||||
| return err | |||||
| } | |||||
| if repodiff != 0 { | |||||
| _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID) | |||||
| } | |||||
| return err | |||||
| } | |||||
| // WatchRepoMode watch repository in specific mode. | |||||
| func WatchRepoMode(userID, repoID int64, mode RepoWatchMode) (err error) { | |||||
| var watch Watch | |||||
| if watch, err = getWatch(x, userID, repoID); err != nil { | |||||
| return err | |||||
| } | |||||
| return watchRepoMode(x, watch, mode) | |||||
| } | |||||
| func watchRepo(e Engine, userID, repoID int64, doWatch bool) (err error) { | |||||
| var watch Watch | |||||
| if watch, err = getWatch(e, userID, repoID); err != nil { | |||||
| return err | |||||
| } | |||||
| if !doWatch && watch.Mode == RepoWatchModeAuto { | |||||
| err = watchRepoMode(e, watch, RepoWatchModeDont) | |||||
| } else if !doWatch { | |||||
| err = watchRepoMode(e, watch, RepoWatchModeNone) | |||||
| } else { | |||||
| err = watchRepoMode(e, watch, RepoWatchModeNormal) | |||||
| } | } | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -52,6 +128,7 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) { | |||||
| func getWatchers(e Engine, repoID int64) ([]*Watch, error) { | func getWatchers(e Engine, repoID int64) ([]*Watch, error) { | ||||
| watches := make([]*Watch, 0, 10) | watches := make([]*Watch, 0, 10) | ||||
| return watches, e.Where("`watch`.repo_id=?", repoID). | return watches, e.Where("`watch`.repo_id=?", repoID). | ||||
| And("`watch`.mode<>?", RepoWatchModeDont). | |||||
| And("`user`.is_active=?", true). | And("`user`.is_active=?", true). | ||||
| And("`user`.prohibit_login=?", false). | And("`user`.prohibit_login=?", false). | ||||
| Join("INNER", "`user`", "`user`.id = `watch`.user_id"). | Join("INNER", "`user`", "`user`.id = `watch`.user_id"). | ||||
| @@ -67,7 +144,8 @@ func GetWatchers(repoID int64) ([]*Watch, error) { | |||||
| func (repo *Repository) GetWatchers(page int) ([]*User, error) { | func (repo *Repository) GetWatchers(page int) ([]*User, error) { | ||||
| users := make([]*User, 0, ItemsPerPage) | users := make([]*User, 0, ItemsPerPage) | ||||
| sess := x.Where("watch.repo_id=?", repo.ID). | sess := x.Where("watch.repo_id=?", repo.ID). | ||||
| Join("LEFT", "watch", "`user`.id=`watch`.user_id") | |||||
| Join("LEFT", "watch", "`user`.id=`watch`.user_id"). | |||||
| And("`watch`.mode<>?", RepoWatchModeDont) | |||||
| if page > 0 { | if page > 0 { | ||||
| sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage) | sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage) | ||||
| } | } | ||||
| @@ -137,3 +215,22 @@ func notifyWatchers(e Engine, act *Action) error { | |||||
| func NotifyWatchers(act *Action) error { | func NotifyWatchers(act *Action) error { | ||||
| return notifyWatchers(x, act) | return notifyWatchers(x, act) | ||||
| } | } | ||||
| func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error { | |||||
| if !isWrite || !setting.Service.AutoWatchOnChanges { | |||||
| return nil | |||||
| } | |||||
| watch, err := getWatch(e, userID, repoID) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if watch.Mode != RepoWatchModeNone { | |||||
| return nil | |||||
| } | |||||
| return watchRepoMode(e, watch, RepoWatchModeAuto) | |||||
| } | |||||
| // WatchIfAuto subscribes to repo if AutoWatchOnChanges is set | |||||
| func WatchIfAuto(userID int64, repoID int64, isWrite bool) error { | |||||
| return watchIfAuto(x, userID, repoID, isWrite) | |||||
| } | |||||
| @@ -7,6 +7,8 @@ package models | |||||
| import ( | import ( | ||||
| "testing" | "testing" | ||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
| ) | ) | ||||
| @@ -15,8 +17,10 @@ func TestIsWatching(t *testing.T) { | |||||
| assert.True(t, IsWatching(1, 1)) | assert.True(t, IsWatching(1, 1)) | ||||
| assert.True(t, IsWatching(4, 1)) | assert.True(t, IsWatching(4, 1)) | ||||
| assert.True(t, IsWatching(11, 1)) | |||||
| assert.False(t, IsWatching(1, 5)) | assert.False(t, IsWatching(1, 5)) | ||||
| assert.False(t, IsWatching(8, 1)) | |||||
| assert.False(t, IsWatching(NonexistentID, NonexistentID)) | assert.False(t, IsWatching(NonexistentID, NonexistentID)) | ||||
| } | } | ||||
| @@ -78,7 +82,7 @@ func TestNotifyWatchers(t *testing.T) { | |||||
| } | } | ||||
| assert.NoError(t, NotifyWatchers(action)) | assert.NoError(t, NotifyWatchers(action)) | ||||
| // One watchers are inactive, thus action is only created for user 8, 1, 4 | |||||
| // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 | |||||
| AssertExistsAndLoadBean(t, &Action{ | AssertExistsAndLoadBean(t, &Action{ | ||||
| ActUserID: action.ActUserID, | ActUserID: action.ActUserID, | ||||
| UserID: 8, | UserID: 8, | ||||
| @@ -97,4 +101,88 @@ func TestNotifyWatchers(t *testing.T) { | |||||
| RepoID: action.RepoID, | RepoID: action.RepoID, | ||||
| OpType: action.OpType, | OpType: action.OpType, | ||||
| }) | }) | ||||
| AssertExistsAndLoadBean(t, &Action{ | |||||
| ActUserID: action.ActUserID, | |||||
| UserID: 11, | |||||
| RepoID: action.RepoID, | |||||
| OpType: action.OpType, | |||||
| }) | |||||
| } | |||||
| func TestWatchIfAuto(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | |||||
| watchers, err := repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, repo.NumWatches) | |||||
| setting.Service.AutoWatchOnChanges = false | |||||
| prevCount := repo.NumWatches | |||||
| // Must not add watch | |||||
| assert.NoError(t, WatchIfAuto(8, 1, true)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount) | |||||
| // Should not add watch | |||||
| assert.NoError(t, WatchIfAuto(10, 1, true)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount) | |||||
| setting.Service.AutoWatchOnChanges = true | |||||
| // Must not add watch | |||||
| assert.NoError(t, WatchIfAuto(8, 1, true)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount) | |||||
| // Should not add watch | |||||
| assert.NoError(t, WatchIfAuto(12, 1, false)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount) | |||||
| // Should add watch | |||||
| assert.NoError(t, WatchIfAuto(12, 1, true)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount+1) | |||||
| // Should remove watch, inhibit from adding auto | |||||
| assert.NoError(t, WatchRepo(12, 1, false)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount) | |||||
| // Must not add watch | |||||
| assert.NoError(t, WatchIfAuto(12, 1, true)) | |||||
| watchers, err = repo.GetWatchers(1) | |||||
| assert.NoError(t, err) | |||||
| assert.Len(t, watchers, prevCount) | |||||
| } | |||||
| func TestWatchRepoMode(t *testing.T) { | |||||
| assert.NoError(t, PrepareTestDatabase()) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) | |||||
| assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeAuto)) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeAuto}, 1) | |||||
| assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNormal)) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeNormal}, 1) | |||||
| assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeDont)) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeDont}, 1) | |||||
| assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNone)) | |||||
| AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) | |||||
| } | } | ||||
| @@ -1082,7 +1082,7 @@ func deleteUser(e *xorm.Session, u *User) error { | |||||
| // ***** START: Watch ***** | // ***** START: Watch ***** | ||||
| watchedRepoIDs := make([]int64, 0, 10) | watchedRepoIDs := make([]int64, 0, 10) | ||||
| if err = e.Table("watch").Cols("watch.repo_id"). | if err = e.Table("watch").Cols("watch.repo_id"). | ||||
| Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil { | |||||
| Where("watch.user_id = ?", u.ID).And("watch.mode <>?", RepoWatchModeDont).Find(&watchedRepoIDs); err != nil { | |||||
| return fmt.Errorf("get all watches: %v", err) | return fmt.Errorf("get all watches: %v", err) | ||||
| } | } | ||||
| if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil { | if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil { | ||||
| @@ -1543,6 +1543,7 @@ func GetStarredRepos(userID int64, private bool) ([]*Repository, error) { | |||||
| // GetWatchedRepos returns the repos watched by a particular user | // GetWatchedRepos returns the repos watched by a particular user | ||||
| func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { | func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) { | ||||
| sess := x.Where("watch.user_id=?", userID). | sess := x.Where("watch.user_id=?", userID). | ||||
| And("`watch`.mode<>?", RepoWatchModeDont). | |||||
| Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") | Join("LEFT", "watch", "`repository`.id=`watch`.repo_id") | ||||
| if !private { | if !private { | ||||
| sess = sess.And("is_private=?", false) | sess = sess.And("is_private=?", false) | ||||
| @@ -504,5 +504,10 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp | |||||
| if opts.RefFullName == git.BranchPrefix+repo.DefaultBranch { | if opts.RefFullName == git.BranchPrefix+repo.DefaultBranch { | ||||
| models.UpdateRepoIndexer(repo) | models.UpdateRepoIndexer(repo) | ||||
| } | } | ||||
| if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil { | |||||
| log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) | |||||
| } | |||||
| return nil | return nil | ||||
| } | } | ||||
| @@ -44,6 +44,7 @@ var Service struct { | |||||
| NoReplyAddress string | NoReplyAddress string | ||||
| EnableUserHeatmap bool | EnableUserHeatmap bool | ||||
| AutoWatchNewRepos bool | AutoWatchNewRepos bool | ||||
| AutoWatchOnChanges bool | |||||
| DefaultOrgMemberVisible bool | DefaultOrgMemberVisible bool | ||||
| // OpenID settings | // OpenID settings | ||||
| @@ -85,6 +86,7 @@ func newService() { | |||||
| Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") | Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") | ||||
| Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true) | Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true) | ||||
| Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true) | Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true) | ||||
| Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false) | |||||
| Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes)) | Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes)) | ||||
| Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility] | Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility] | ||||
| Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool() | Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool() | ||||