From a0d0de7233cd8a85d6572ae13d74078482a1ee27 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Sun, 19 Mar 2017 16:54:12 -0300
Subject: [PATCH 01/12] Create issue_watch table

---
 models/issue_watch.go | 20 ++++++++++++++++++++
 models/models.go      |  1 +
 2 files changed, 21 insertions(+)
 create mode 100644 models/issue_watch.go

diff --git a/models/issue_watch.go b/models/issue_watch.go
new file mode 100644
index 000000000..96e080136
--- /dev/null
+++ b/models/issue_watch.go
@@ -0,0 +1,20 @@
+package models
+
+import (
+	"time"
+)
+
+// IssueWatch is connection request for receiving issue notification.
+type IssueWatch struct {
+	ID          int64     `xorm:"pk autoincr"`
+	UserID      int64     `xorm:"UNIQUE(watch) NOT NULL"`
+	IssueID     int64     `xorm:"UNIQUE(watch) NOT NULL"`
+	IsWatching  bool      `xorm:"NOT NULL"`
+	Created     time.Time `xorm:"-"`
+	CreatedUnix int64     `xorm:"NOT NULL"`
+}
+
+// BeforeInsert is invoked from XORM before inserting an object of this type.
+func (iw *IssueWatch) BeforeInsert() {
+	iw.CreatedUnix = time.Now().Unix()
+}
diff --git a/models/models.go b/models/models.go
index 2ae6e355f..a1332ac23 100644
--- a/models/models.go
+++ b/models/models.go
@@ -117,6 +117,7 @@ func init() {
 		new(ExternalLoginUser),
 		new(ProtectedBranch),
 		new(UserOpenID),
+		new(IssueWatch),
 	)
 
 	gonicNames := []string{"SSL", "UID"}

From b6744607484008826d18f129326664105b9d7bfc Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Wed, 29 Mar 2017 20:31:47 -0300
Subject: [PATCH 02/12] Add watch button on issue

---
 cmd/web.go                                    |  1 +
 models/issue_watch.go                         | 40 +++++++++++++++++++
 options/locale/locale_en-US.ini               |  3 ++
 routers/repo/issue.go                         | 14 +++++++
 routers/repo/issue_watch.go                   | 34 ++++++++++++++++
 .../repo/issue/view_content/sidebar.tmpl      | 19 +++++++++
 6 files changed, 111 insertions(+)
 create mode 100644 routers/repo/issue_watch.go

diff --git a/cmd/web.go b/cmd/web.go
index 1f2561ca6..b2cc3959a 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -491,6 +491,7 @@ func runWeb(ctx *cli.Context) error {
 			m.Group("/:index", func() {
 				m.Post("/title", repo.UpdateIssueTitle)
 				m.Post("/content", repo.UpdateIssueContent)
+				m.Post("/watch", repo.IssueWatch)
 				m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
 			})
 
diff --git a/models/issue_watch.go b/models/issue_watch.go
index 96e080136..d082211c7 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -16,5 +16,45 @@ type IssueWatch struct {
 
 // BeforeInsert is invoked from XORM before inserting an object of this type.
 func (iw *IssueWatch) BeforeInsert() {
+	iw.Created = time.Now()
 	iw.CreatedUnix = time.Now().Unix()
 }
+
+// CreateOrUpdateIssueWatch set watching for a user and issue
+func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
+	iw, exists, err := getIssueWatch(x, userID, issueID)
+	if err != nil {
+		return err
+	}
+
+	if !exists {
+		iw = &IssueWatch{
+			UserID:     userID,
+			IssueID:    issueID,
+			IsWatching: isWatching,
+		}
+
+		if _, err := x.Insert(iw); err != nil {
+			return err
+		}
+	} else {
+		if _, err := x.Table(&IssueWatch{}).Id(iw.ID).Update(map[string]interface{}{"is_watching": isWatching}); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// GetIssueWatch returns an issue watch by user and issue
+func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
+	iw, exists, err = getIssueWatch(x, userID, issueID)
+	return
+}
+func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
+	iw = new(IssueWatch)
+	exists, err = e.
+		Where("user_id = ?", userID).
+		And("issue_id = ?", issueID).
+		Get(iw)
+	return
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 80260d4b7..822d9cdc9 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -652,6 +652,9 @@ issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
 issues.num_participants = %d Participants
 issues.attachment.open_tab = `Click to see "%s" in a new tab`
 issues.attachment.download = `Click to download "%s"`
+issues.watch = Watch
+issues.watch_issue = Watch issue
+issues.unwatch_issue = Unwatch issue
 
 pulls.new = New Pull Request
 pulls.compare_changes = Compare Changes
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 0a723d755..61f79a239 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -465,6 +465,20 @@ func ViewIssue(ctx *context.Context) {
 	}
 	ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
 
+	iw, exists, err := models.GetIssueWatch(ctx.User.ID, issue.ID)
+	if err != nil {
+		ctx.Handle(500, "GetIssueWatch", err)
+		return
+	}
+	if !exists {
+		iw = &models.IssueWatch{
+			UserID:     ctx.User.ID,
+			IssueID:    issue.ID,
+			IsWatching: models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID),
+		}
+	}
+	ctx.Data["IssueWatch"] = iw
+
 	// Make sure type and URL matches.
 	if ctx.Params(":type") == "issues" && issue.IsPull {
 		ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
diff --git a/routers/repo/issue_watch.go b/routers/repo/issue_watch.go
new file mode 100644
index 000000000..64c99c5f7
--- /dev/null
+++ b/routers/repo/issue_watch.go
@@ -0,0 +1,34 @@
+package repo
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/context"
+)
+
+// IssueWatch sets issue watching
+func IssueWatch(c *context.Context) {
+	watch, err := strconv.ParseBool(c.Req.PostForm.Get("watch"))
+	if err != nil {
+		c.Handle(http.StatusInternalServerError, "watch is not bool", err)
+		return
+	}
+
+	issueIndex := c.ParamsInt64("index")
+	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex)
+	if err != nil {
+		c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err)
+		return
+	}
+
+	if err := models.CreateOrUpdateIssueWatch(c.User.ID, issue.ID, watch); err != nil {
+		c.Handle(http.StatusInternalServerError, "CreateOrUpdateIssueWatch", err)
+		return
+	}
+
+	url := fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issueIndex)
+	c.Redirect(url, http.StatusSeeOther)
+}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index ea46e5f94..9a4a6cb1a 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -98,5 +98,24 @@
 				{{end}}
 			</div>
 		</div>
+
+		<div class="ui divider"></div>
+
+		<div class="ui watching">
+			<span class="text"><strong>{{.i18n.Tr "repo.issues.watch"}}</strong></span>
+			<div>
+				<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/watch">
+					<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}" />
+					{{$.CsrfTokenHtml}}
+					<button class="fluid ui button">
+						{{if $.IssueWatch.IsWatching}}
+							{{.i18n.Tr "repo.issues.unwatch_issue"}}
+						{{else}}
+							{{.i18n.Tr "repo.issues.watch_issue"}}
+						{{end}}
+					</button>
+				</form>
+			</div>
+		</div>
 	</div>
 </div>

From aa6e949b3de11e675311e54b59e027cd82a230f4 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Wed, 29 Mar 2017 20:54:57 -0300
Subject: [PATCH 03/12] Consider issue_watchers while sending notifications

---
 models/issue_watch.go  | 10 ++++++++++
 models/notification.go | 40 ++++++++++++++++++++++++++++++++--------
 2 files changed, 42 insertions(+), 8 deletions(-)

diff --git a/models/issue_watch.go b/models/issue_watch.go
index d082211c7..03a677a3a 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -58,3 +58,13 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool
 		Get(iw)
 	return
 }
+
+func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {
+	return getIssueWatchers(x, issueID)
+}
+func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) {
+	err = e.
+		Where("issue_id = ?", issueID).
+		Find(&watches)
+	return
+}
diff --git a/models/notification.go b/models/notification.go
index bba662c06..a59c6f140 100644
--- a/models/notification.go
+++ b/models/notification.go
@@ -96,6 +96,11 @@ func CreateOrUpdateIssueNotifications(issue *Issue, notificationAuthorID int64)
 }
 
 func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error {
+	issueWatches, err := getIssueWatchers(e, issue.ID)
+	if err != nil {
+		return err
+	}
+
 	watches, err := getWatchers(e, issue.RepoID)
 	if err != nil {
 		return err
@@ -106,23 +111,42 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
 		return err
 	}
 
-	for _, watch := range watches {
+	alreadyNotified := make(map[int64]struct{}, len(issueWatches)+len(watches))
+
+	notifyUser := func(userID int64) error {
 		// do not send notification for the own issuer/commenter
-		if watch.UserID == notificationAuthorID {
-			continue
+		if userID == notificationAuthorID {
+			return nil
 		}
 
-		if notificationExists(notifications, issue.ID, watch.UserID) {
-			err = updateIssueNotification(e, watch.UserID, issue.ID, notificationAuthorID)
-		} else {
-			err = createIssueNotification(e, watch.UserID, issue, notificationAuthorID)
+		if _, ok := alreadyNotified[userID]; ok {
+			return nil
 		}
+		alreadyNotified[userID] = struct{}{}
 
-		if err != nil {
+		if notificationExists(notifications, issue.ID, userID) {
+			return updateIssueNotification(e, userID, issue.ID, notificationAuthorID)
+		}
+		return createIssueNotification(e, userID, issue, notificationAuthorID)
+	}
+
+	for _, issueWatch := range issueWatches {
+		// ignore if user unwatched the issue
+		if !issueWatch.IsWatching {
+			alreadyNotified[issueWatch.UserID] = struct{}{}
+			continue
+		}
+
+		if err := notifyUser(issueWatch.UserID); err != nil {
 			return err
 		}
 	}
 
+	for _, watch := range watches {
+		if err := notifyUser(watch.UserID); err != nil {
+			return err
+		}
+	}
 	return nil
 }
 

From cb362513f027ca8e2c53204f5f2ea447ad06bf05 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Wed, 29 Mar 2017 20:59:28 -0300
Subject: [PATCH 04/12] Add updated_unix column on issue_watch

---
 models/issue_watch.go | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/models/issue_watch.go b/models/issue_watch.go
index 03a677a3a..1e2650963 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -12,12 +12,21 @@ type IssueWatch struct {
 	IsWatching  bool      `xorm:"NOT NULL"`
 	Created     time.Time `xorm:"-"`
 	CreatedUnix int64     `xorm:"NOT NULL"`
+	Updated     time.Time `xorm:"-"`
+	UpdatedUnix int64     `xorm:"NOT NULL"`
 }
 
 // BeforeInsert is invoked from XORM before inserting an object of this type.
 func (iw *IssueWatch) BeforeInsert() {
 	iw.Created = time.Now()
 	iw.CreatedUnix = time.Now().Unix()
+	iw.Updated = time.Now()
+	iw.UpdatedUnix = time.Now().Unix()
+}
+
+func (iw *IssueWatch) BeforeUpdate() {
+	iw.Updated = time.Now()
+	iw.UpdatedUnix = time.Now().Unix()
 }
 
 // CreateOrUpdateIssueWatch set watching for a user and issue
@@ -38,7 +47,9 @@ func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
 			return err
 		}
 	} else {
-		if _, err := x.Table(&IssueWatch{}).Id(iw.ID).Update(map[string]interface{}{"is_watching": isWatching}); err != nil {
+		iw.IsWatching = isWatching
+
+		if _, err := x.Id(iw.ID).Cols("is_watching", "updated_unix").Update(iw); err != nil {
 			return err
 		}
 	}

From e4a33ed4d0d078c6d8a5b9025865c805f7eee179 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Wed, 29 Mar 2017 21:08:46 -0300
Subject: [PATCH 05/12] Add octicons to watch/unwatch buttons

---
 templates/repo/issue/view_content/sidebar.tmpl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 9a4a6cb1a..a9df130df 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -109,8 +109,10 @@
 					{{$.CsrfTokenHtml}}
 					<button class="fluid ui button">
 						{{if $.IssueWatch.IsWatching}}
+							<i class="octicon octicon-mute"></i>
 							{{.i18n.Tr "repo.issues.unwatch_issue"}}
 						{{else}}
+							<i class="octicon octicon-megaphone"></i>
 							{{.i18n.Tr "repo.issues.watch_issue"}}
 						{{end}}
 					</button>

From caed86fc6ef87d11035889a11b8949c839e19d52 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Wed, 29 Mar 2017 21:18:28 -0300
Subject: [PATCH 06/12] Fix lint

---
 models/issue_watch.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/models/issue_watch.go b/models/issue_watch.go
index 1e2650963..d08e1253e 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -24,6 +24,7 @@ func (iw *IssueWatch) BeforeInsert() {
 	iw.UpdatedUnix = time.Now().Unix()
 }
 
+// BeforeUpdate is invoked from XORM before updating an object of this type.
 func (iw *IssueWatch) BeforeUpdate() {
 	iw.Updated = time.Now()
 	iw.UpdatedUnix = time.Now().Unix()
@@ -70,6 +71,7 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool
 	return
 }
 
+// GetIssueWatchers returns watchers/unwatchers of a given issue
 func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {
 	return getIssueWatchers(x, issueID)
 }

From 4b284f814c21c34b61f94f7daa39d6254246ab5f Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Thu, 30 Mar 2017 19:10:30 -0300
Subject: [PATCH 07/12] UI and translation improvements

---
 options/locale/locale_en-US.ini                | 5 ++---
 templates/repo/issue/view_content/sidebar.tmpl | 8 ++++----
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 822d9cdc9..35a524494 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -652,9 +652,8 @@ issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
 issues.num_participants = %d Participants
 issues.attachment.open_tab = `Click to see "%s" in a new tab`
 issues.attachment.download = `Click to download "%s"`
-issues.watch = Watch
-issues.watch_issue = Watch issue
-issues.unwatch_issue = Unwatch issue
+issues.subscribe = Subscribe
+issues.unsubscribe = Unsubscribe
 
 pulls.new = New Pull Request
 pulls.compare_changes = Compare Changes
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index a9df130df..28bd755e4 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -102,7 +102,7 @@
 		<div class="ui divider"></div>
 
 		<div class="ui watching">
-			<span class="text"><strong>{{.i18n.Tr "repo.issues.watch"}}</strong></span>
+			<span class="text"><strong>{{.i18n.Tr "notification.notifications"}}</strong></span>
 			<div>
 				<form method="POST" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/watch">
 					<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}" />
@@ -110,10 +110,10 @@
 					<button class="fluid ui button">
 						{{if $.IssueWatch.IsWatching}}
 							<i class="octicon octicon-mute"></i>
-							{{.i18n.Tr "repo.issues.unwatch_issue"}}
+							{{.i18n.Tr "repo.issues.unsubscribe"}}
 						{{else}}
-							<i class="octicon octicon-megaphone"></i>
-							{{.i18n.Tr "repo.issues.watch_issue"}}
+							<i class="octicon octicon-unmute"></i>
+							{{.i18n.Tr "repo.issues.subscribe"}}
 						{{end}}
 					</button>
 				</form>

From 18952c40f80d41d2edc582305265dbfe3b62120d Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Thu, 30 Mar 2017 19:11:58 -0300
Subject: [PATCH 08/12] Add copyright headers

---
 models/issue_watch.go       | 4 ++++
 routers/repo/issue_watch.go | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/models/issue_watch.go b/models/issue_watch.go
index d08e1253e..c48f05b11 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -1,3 +1,7 @@
+// Copyright 2017 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 models
 
 import (
diff --git a/routers/repo/issue_watch.go b/routers/repo/issue_watch.go
index 64c99c5f7..382798025 100644
--- a/routers/repo/issue_watch.go
+++ b/routers/repo/issue_watch.go
@@ -1,3 +1,7 @@
+// Copyright 2017 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 repo
 
 import (

From a90ffffb1a84d79f3e08bd1c7b90ab2f565833a0 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Thu, 30 Mar 2017 19:14:16 -0300
Subject: [PATCH 09/12] Use variables for times

---
 models/issue_watch.go | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/models/issue_watch.go b/models/issue_watch.go
index c48f05b11..8262ae2ea 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -22,16 +22,24 @@ type IssueWatch struct {
 
 // BeforeInsert is invoked from XORM before inserting an object of this type.
 func (iw *IssueWatch) BeforeInsert() {
-	iw.Created = time.Now()
-	iw.CreatedUnix = time.Now().Unix()
-	iw.Updated = time.Now()
-	iw.UpdatedUnix = time.Now().Unix()
+	var (
+		t = time.Now()
+		u = t.Unix()
+	)
+	iw.Created = t
+	iw.CreatedUnix = u
+	iw.Updated = t
+	iw.UpdatedUnix = u
 }
 
 // BeforeUpdate is invoked from XORM before updating an object of this type.
 func (iw *IssueWatch) BeforeUpdate() {
-	iw.Updated = time.Now()
-	iw.UpdatedUnix = time.Now().Unix()
+	var (
+		t = time.Now()
+		u = t.Unix()
+	)
+	iw.Updated = t
+	iw.UpdatedUnix = u
 }
 
 // CreateOrUpdateIssueWatch set watching for a user and issue

From e6781d5488849d9415abc2402e914259214a13c4 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Thu, 30 Mar 2017 20:20:08 -0300
Subject: [PATCH 10/12] Add unit tests for issue_watch

---
 models/fixtures/issue_watch.yml | 15 ++++++++++++
 models/issue_watch_test.go      | 42 +++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+)
 create mode 100644 models/fixtures/issue_watch.yml
 create mode 100644 models/issue_watch_test.go

diff --git a/models/fixtures/issue_watch.yml b/models/fixtures/issue_watch.yml
new file mode 100644
index 000000000..596662d20
--- /dev/null
+++ b/models/fixtures/issue_watch.yml
@@ -0,0 +1,15 @@
+-
+  id: 1
+  user_id: 1
+  issue_id: 1
+  is_watching: true
+  created_unix: 946684800
+  updated_unix: 946684800
+
+-
+  id: 2
+  user_id: 2
+  issue_id: 2
+  is_watching: false
+  created_unix: 946684800
+  updated_unix: 946684800
diff --git a/models/issue_watch_test.go b/models/issue_watch_test.go
new file mode 100644
index 000000000..5b820ded7
--- /dev/null
+++ b/models/issue_watch_test.go
@@ -0,0 +1,42 @@
+// Copyright 2017 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 models
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCreateOrUpdateIssueWatch(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	assert.NoError(t, CreateOrUpdateIssueWatch(3, 1, true))
+	iw := AssertExistsAndLoadBean(t, &IssueWatch{UserID: 3, IssueID: 1}).(*IssueWatch)
+	assert.Equal(t, true, iw.IsWatching)
+
+	assert.NoError(t, CreateOrUpdateIssueWatch(1, 1, false))
+	iw = AssertExistsAndLoadBean(t, &IssueWatch{UserID: 1, IssueID: 1}).(*IssueWatch)
+	assert.Equal(t, false, iw.IsWatching)
+}
+
+func TestGetIssueWatch(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	_, exists, err := GetIssueWatch(1, 1)
+	assert.Equal(t, true, exists)
+	assert.NoError(t, err)
+	_, exists, err = GetIssueWatch(2, 2)
+	assert.Equal(t, true, exists)
+	assert.NoError(t, err)
+}
+
+func TestGetIssueWatchers(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	iws, err := GetIssueWatchers(1)
+	assert.NoError(t, err)
+	assert.Equal(t, 1, len(iws))
+}

From e5c56fe30ddfe2ea5b065a286cb467a914d044e8 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Sat, 1 Apr 2017 09:58:20 -0300
Subject: [PATCH 11/12] Code style fixes

---
 models/issue_watch.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/models/issue_watch.go b/models/issue_watch.go
index 8262ae2ea..37511787e 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -71,9 +71,9 @@ func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
 
 // GetIssueWatch returns an issue watch by user and issue
 func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
-	iw, exists, err = getIssueWatch(x, userID, issueID)
-	return
+	return getIssueWatch(x, userID, issueID)
 }
+
 func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
 	iw = new(IssueWatch)
 	exists, err = e.
@@ -87,6 +87,7 @@ func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool
 func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {
 	return getIssueWatchers(x, issueID)
 }
+
 func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) {
 	err = e.
 		Where("issue_id = ?", issueID).

From f6e5ce65b28fb6c97d9011c1fbf2950acf7c0647 Mon Sep 17 00:00:00 2001
From: Andrey Nering <andrey.nering@gmail.com>
Date: Sat, 1 Apr 2017 10:05:58 -0300
Subject: [PATCH 12/12] Improve tests a little

---
 models/issue_watch_test.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/models/issue_watch_test.go b/models/issue_watch_test.go
index 5b820ded7..d8b456c3a 100644
--- a/models/issue_watch_test.go
+++ b/models/issue_watch_test.go
@@ -28,9 +28,14 @@ func TestGetIssueWatch(t *testing.T) {
 	_, exists, err := GetIssueWatch(1, 1)
 	assert.Equal(t, true, exists)
 	assert.NoError(t, err)
+
 	_, exists, err = GetIssueWatch(2, 2)
 	assert.Equal(t, true, exists)
 	assert.NoError(t, err)
+
+	_, exists, err = GetIssueWatch(3, 1)
+	assert.Equal(t, false, exists)
+	assert.NoError(t, err)
 }
 
 func TestGetIssueWatchers(t *testing.T) {
@@ -39,4 +44,8 @@ func TestGetIssueWatchers(t *testing.T) {
 	iws, err := GetIssueWatchers(1)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, len(iws))
+
+	iws, err = GetIssueWatchers(5)
+	assert.NoError(t, err)
+	assert.Equal(t, 0, len(iws))
 }