Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-subscribe user to repository when they commit/tag to it #7657

Merged
merged 36 commits into from
Nov 10, 2019
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c0226f2
Add support for AUTO_WATCH_ON_CHANGES and AUTO_WATCH_ON_CLONE
Jul 29, 2019
6c1a039
Update models/repo_watch.go
guillep2k Jul 29, 2019
5d52dda
Round up changes suggested by lafriks
Jul 29, 2019
6e4a28d
Added changes suggested from automated tests
Jul 29, 2019
ce99d45
Updated deleteUser to take RepoWatchModeDont into account, corrected …
Jul 30, 2019
7781419
Merge branch 'master' into watchonchanges
Jul 30, 2019
70790fa
Merge branch 'master' into watchonchanges
Jul 31, 2019
6d039b8
Merge branch 'master' into watchonchanges
guillep2k Aug 8, 2019
3dad90f
Merge branch 'master' into watchonchanges
guillep2k Aug 8, 2019
34643f1
Merge branch 'master' into watchonchanges
guillep2k Aug 15, 2019
4a9fad0
Merge branch 'master' into watchonchanges
guillep2k Aug 15, 2019
2abd413
Reinsert import "github.com/Unknwon/com" on http.go
guillep2k Aug 15, 2019
dd2fa5a
Merge branch 'master' of github.com:go-gitea/gitea into watchonchanges
guillep2k Aug 15, 2019
3034d5e
Add migration for new column `watch`.`mode`
guillep2k Aug 15, 2019
623cfe5
Merge branch 'master' into watchonchanges
guillep2k Aug 22, 2019
d1cc6fb
Merge branch master into watchonchanges
guillep2k Aug 27, 2019
6715664
Remove serv code
guillep2k Aug 27, 2019
00782cf
Remove WATCH_ON_CLONE; use hooks, add integrations
guillep2k Aug 27, 2019
f0377e1
Merge branch 'master' of github.com:go-gitea/gitea into watchonchanges
guillep2k Aug 27, 2019
ff09de9
Renamed watch_test.go to repo_watch_test.go
guillep2k Aug 27, 2019
fde42fa
Correct fmt
guillep2k Aug 27, 2019
0969aa8
Add missing EOL
guillep2k Aug 27, 2019
4a0d89e
Correct name of test function
guillep2k Aug 27, 2019
28a4af3
Merge branch 'master' of github.com:go-gitea/gitea into watchonchanges
guillep2k Aug 29, 2019
3ef2ff1
Merge from master, resolve conflicts
guillep2k Oct 31, 2019
de0852f
Reword cheat and ini descriptions
guillep2k Oct 31, 2019
f0e6aeb
Add update to migration to ensure column value
guillep2k Nov 2, 2019
ad68861
Merge branch 'master' into watchonchanges
guillep2k Nov 2, 2019
df24eb0
Merge branch 'master' into watchonchanges
lunny Nov 2, 2019
4e76559
Merge branch 'master' into watchonchanges
lafriks Nov 5, 2019
edc9f52
Merge master and resolve conflicts
guillep2k Nov 8, 2019
59e6f88
Merge branch 'watchonchanges' of github.com:guillep2k/gitea into watc…
guillep2k Nov 8, 2019
4341a4b
Clarify comment
guillep2k Nov 10, 2019
c59a238
Simplify if condition
guillep2k Nov 10, 2019
8301471
Merge branch 'master' into watchonchanges
lunny Nov 10, 2019
7eec08b
Merge branch 'master' into watchonchanges
zeripath Nov 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions custom/conf/app.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,9 @@ SHOW_REGISTRATION_BUTTON = true
; When adding a repo to a team or creating a new repo all team members will watch the
; repo automatically if enabled
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]
; Hook task queue length, increase if webhook shooting starts hanging
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ relation to port exhaustion.
on this instance.
- `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_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_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.

Expand Down
24 changes: 24 additions & 0 deletions integrations/repo_watch_test.go
Original file line number Diff line number Diff line change
@@ -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})
})
}
7 changes: 5 additions & 2 deletions models/consistency.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,17 @@ func (user *User) checkForConsistency(t *testing.T) {
func (repo *Repository) checkForConsistency(t *testing.T) {
assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
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, &Repository{ForkID: repo.ID}, repo.NumForks)
if repo.IsFork {
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,
"Unexpected number of issues for repo %+v", repo)

Expand Down
2 changes: 1 addition & 1 deletion models/fixtures/repository.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
num_closed_pulls: 0
num_milestones: 3
num_closed_milestones: 1
num_watches: 3
num_watches: 4
status: 0

-
Expand Down
15 changes: 15 additions & 0 deletions models/fixtures/watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@
id: 1
user_id: 1
repo_id: 1
mode: 1 # normal

-
id: 2
user_id: 4
repo_id: 1
mode: 1 # normal

-
id: 3
user_id: 9
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
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ var migrations = []Migration{
NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
// v104 -> v105
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
// v105 -> v106
NewMigration("add column `mode` to table watch", addModeColumnToWatch),
}

// Migrate database to current version
Expand Down
21 changes: 21 additions & 0 deletions models/migrations/v105.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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 {
Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
}

func addModeColumnToWatch(x *xorm.Engine) error {
return x.Sync2(new(Watch))
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
}
4 changes: 2 additions & 2 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2396,8 +2396,8 @@ func CheckRepoStats() {
checkers := []*repoChecker{
// 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.NumStars
Expand Down
146 changes: 124 additions & 22 deletions models/repo_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,118 @@

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 (
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
// 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.
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"`
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
}

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.
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
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
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
}
_, 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
}
_, 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
}
Expand All @@ -52,6 +128,7 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
watches := make([]*Watch, 0, 10)
return watches, e.Where("`watch`.repo_id=?", repoID).
And("`watch`.mode<>?", RepoWatchModeDont).
And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false).
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
Expand All @@ -67,7 +144,8 @@ func GetWatchers(repoID int64) ([]*Watch, error) {
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
users := make([]*User, 0, ItemsPerPage)
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 {
sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
}
Expand Down Expand Up @@ -137,3 +215,27 @@ func notifyWatchers(e Engine, act *Action) error {
func NotifyWatchers(act *Action) error {
return notifyWatchers(x, act)
}

func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error {
if isWrite {
if !setting.Service.AutoWatchOnChanges {
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
} else {
// No other kind of auto-watch for the moment
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)
}
Loading