Преглед на файлове

Merge remote-tracking branch 'upstream/master'

Gitote преди 7 години
родител
ревизия
843be08311
променени са 78 файла, в които са добавени 576 реда и са изтрити 112 реда
  1. 5 0
      README.md
  2. 9 7
      cmd/admin.go
  3. 10 0
      cmd/cert.go
  4. 3 0
      cmd/hook.go
  5. 8 0
      cmd/web.go
  6. 1 1
      conf/locale/locale_en-GB.ini
  7. 1 1
      conf/locale/locale_en-US.ini
  8. 1 1
      gitote.go
  9. 2 0
      models/access.go
  10. 5 0
      models/action.go
  11. 2 0
      models/admin.go
  12. 10 0
      models/comment.go
  13. 13 0
      models/issue.go
  14. 2 0
      models/issue_mail.go
  15. 6 0
      models/login_source.go
  16. 3 0
      models/milestone.go
  17. 24 0
      models/mirror.go
  18. 4 0
      models/models.go
  19. 24 0
      models/pull.go
  20. 2 0
      models/release.go
  21. 31 1
      models/repo.go
  22. 2 0
      models/repo_collaboration.go
  23. 10 0
      models/repo_editor.go
  24. 2 0
      models/ssh_key.go
  25. 12 4
      models/user.go
  26. 12 0
      models/webhook.go
  27. 13 5
      pkg/auth/auth.go
  28. 8 0
      pkg/auth/ldap/ldap.go
  29. 6 6
      pkg/bindata/bindata.go
  30. 2 0
      pkg/context/context.go
  31. 5 0
      pkg/cron/cron.go
  32. 2 0
      pkg/form/admin.go
  33. 3 1
      pkg/form/user.go
  34. 4 0
      pkg/httplib/httplib.go
  35. 6 0
      pkg/mailer/mail.go
  36. 3 0
      pkg/mailer/mailer.go
  37. 2 0
      pkg/process/manager.go
  38. 31 0
      pkg/setting/setting.go
  39. 12 0
      pkg/ssh/ssh.go
  40. 2 0
      pkg/template/template.go
  41. 1 1
      public/css/gitote.min.css
  42. 5 0
      public/less/_user.less
  43. 3 5
      public/robots.txt
  44. 7 0
      routes/admin/admin.go
  45. 9 7
      routes/admin/users.go
  46. 3 0
      routes/api/v1/repo/repo.go
  47. 26 5
      routes/home.go
  48. 9 0
      routes/install.go
  49. 2 0
      routes/org/members.go
  50. 3 0
      routes/org/teams.go
  51. 3 0
      routes/repo/branch.go
  52. 2 0
      routes/repo/editor.go
  53. 4 0
      routes/repo/http.go
  54. 2 0
      routes/repo/issue.go
  55. 3 0
      routes/repo/repo.go
  56. 3 0
      routes/repo/setting.go
  57. 2 0
      routes/repo/view.go
  58. 27 6
      routes/user/auth.go
  59. 0 2
      routes/user/certificate.go
  60. 7 2
      routes/user/setting.go
  61. 25 0
      routes/user/sitemap.go
  62. 65 0
      templates/admin/api.tmpl
  63. 3 1
      templates/admin/dashboard.tmpl
  64. 4 0
      templates/admin/navbar.tmpl
  65. 10 0
      templates/admin/user/edit.tmpl
  66. 1 0
      templates/admin/user/list.tmpl
  67. 2 2
      templates/base/head.tmpl
  68. 9 0
      templates/base/meta.tmpl
  69. 4 0
      templates/misc/ad.tmpl
  70. 0 36
      templates/misc/sponsor.tmpl
  71. 0 2
      templates/org/member/members.tmpl
  72. 1 1
      templates/pages/verified.tmpl
  73. 1 1
      templates/user/auth/two_factor.tmpl
  74. 1 1
      templates/user/auth/two_factor_recovery_code.tmpl
  75. 0 4
      templates/user/dashboard/dashboard.tmpl
  76. 19 6
      templates/user/profile.tmpl
  77. 3 3
      templates/user/settings/profile.tmpl
  78. 4 0
      templates/user/settings/social.tmpl

+ 5 - 0
README.md

@@ -135,6 +135,11 @@ Additional technologies and services are listed on [our stackshare](https://stac
     <img src="https://cdn.jsdelivr.net/npm/gitote@1.1.7/img/sponsors/digitalocean.png">
   </a>
 </p>
+<p>
+  <a href="https://sentry.io?utm_source=Gitote">
+    <img src="https://cdn.jsdelivr.net/npm/gitote@1.2.6/img/sponsors/sentry.png">
+  </a>
+</p>
 
 ## License
 

+ 9 - 7
cmd/admin.go

@@ -7,6 +7,7 @@ import (
 	"reflect"
 	"runtime"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/urfave/cli"
 )
 
@@ -130,14 +131,15 @@ func runCreateUser(c *cli.Context) error {
 	models.SetEngine()
 
 	if err := models.CreateUser(&models.User{
-		Name:         c.String("name"),
-		Email:        c.String("email"),
-		Passwd:       c.String("password"),
-		ThemeColor:   "#161616",
-		IsActive:     true,
-		ShowSponsors: true,
-		IsAdmin:      c.Bool("admin"),
+		Name:       c.String("name"),
+		Email:      c.String("email"),
+		Passwd:     c.String("password"),
+		ThemeColor: "#161616",
+		IsActive:   true,
+		ShowAds:    true,
+		IsAdmin:    c.Bool("admin"),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		return fmt.Errorf("CreateUser: %v", err)
 	}
 

+ 10 - 0
cmd/cert.go

@@ -17,6 +17,7 @@ import (
 	"strings"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/urfave/cli"
 )
 
@@ -54,6 +55,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
 	case *ecdsa.PrivateKey:
 		b, err := x509.MarshalECPrivateKey(k)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatalf("Unable to marshal ECDSA private key: %v\n", err)
 		}
 		return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
@@ -64,6 +66,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
 
 func runCert(ctx *cli.Context) error {
 	if len(ctx.String("host")) == 0 {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal("Missing required --host parameter")
 	}
 
@@ -81,9 +84,11 @@ func runCert(ctx *cli.Context) error {
 	case "P521":
 		priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
 	default:
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatalf("Unrecognized elliptic curve: %q", ctx.String("ecdsa-curve"))
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatalf("Failed to generate private key: %s", err)
 	}
 
@@ -93,6 +98,7 @@ func runCert(ctx *cli.Context) error {
 	} else {
 		notBefore, err = time.Parse("Jan 2 15:04:05 2006", ctx.String("start-date"))
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatalf("Failed to parse creation date: %s", err)
 		}
 	}
@@ -102,6 +108,7 @@ func runCert(ctx *cli.Context) error {
 	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
 	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatalf("Failed to generate serial number: %s", err)
 	}
 
@@ -135,11 +142,13 @@ func runCert(ctx *cli.Context) error {
 
 	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatalf("Failed to create certificate: %s", err)
 	}
 
 	certOut, err := os.Create("cert.pem")
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatalf("Failed to open cert.pem for writing: %s", err)
 	}
 	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
@@ -148,6 +157,7 @@ func runCert(ctx *cli.Context) error {
 
 	keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatalf("Failed to open key.pem for writing: %v\n", err)
 	}
 	pem.Encode(keyOut, pemBlockForKey(priv))

+ 3 - 0
cmd/hook.go

@@ -19,6 +19,7 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/urfave/cli"
 	"gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
@@ -227,6 +228,7 @@ func runHookPostReceive(c *cli.Context) error {
 			RepoName:     os.Getenv(http.ENV_REPO_NAME),
 		}
 		if err := models.PushUpdate(options); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "PushUpdate: %v", err)
 		}
 
@@ -246,6 +248,7 @@ func runHookPostReceive(c *cli.Context) error {
 				log.Error(2, "Fail to trigger task: not 2xx response code")
 			}
 		} else {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Fail to trigger task: %v", err)
 		}
 	}

+ 8 - 0
cmd/web.go

@@ -26,6 +26,7 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-macaron/binding"
 	"github.com/go-macaron/cache"
 	"github.com/go-macaron/captcha"
@@ -98,6 +99,7 @@ func newMacaron() *macaron.Macaron {
 
 	localeNames, err := bindata.AssetDir("conf/locale")
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Fail to list locale files: %v", err)
 	}
 	localFiles := make(map[string][]byte)
@@ -250,6 +252,7 @@ func runWeb(c *cli.Context) error {
 	m.Group("/admin", func() {
 		m.Get("", admin.Dashboard)
 		m.Get("/analytics", admin.Analytics)
+		m.Get("/api", admin.AdminAPI)
 		m.Get("/config", admin.Config)
 		m.Post("/config/test_mail", admin.SendTestMail)
 		m.Get("/monitor", admin.Monitor)
@@ -308,6 +311,8 @@ func runWeb(c *cli.Context) error {
 
 	// ***** END: Pages *****
 
+	m.Get("/user.sitemap.xml", ignSignIn, user.Sitemap)
+
 	// ***** START: Misc *****
 	m.Get("/certificate/:username", ignSignIn, user.InternCertificate)
 	// ***** END: Misc *****
@@ -743,14 +748,17 @@ func runWeb(c *cli.Context) error {
 		// FIXME: add proper implementation of signal capture on all protocols
 		// execute this on SIGTERM or SIGINT: listener.Close()
 		if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(4, "Failed to set permission of unix socket: %v", err)
 		}
 		err = http.Serve(listener, m)
 	default:
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
 	}
 
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Failed to start server: %v", err)
 	}
 

+ 1 - 1
conf/locale/locale_en-GB.ini

@@ -304,7 +304,7 @@ location = Location
 status = Status
 private_email = This email is Private
 beta = Enroll to Beta Member
-sponsor = Display Sponsors on dashboard
+ads = Display Ads on dashboard
 update_profile = Update Profile
 update_profile_success = Your profile has been updated successfully.
 update_social = Update Social

+ 1 - 1
conf/locale/locale_en-US.ini

@@ -304,7 +304,7 @@ location = Location
 status = Status
 private_email = This email is Private
 beta = Enroll to Beta Member
-sponsor = Display Sponsors on dashboard
+ads = Display Ads on dashboard
 update_profile = Update Profile
 update_profile_success = Your profile has been updated successfully.
 update_social = Update Social

+ 1 - 1
gitote.go

@@ -23,7 +23,7 @@ import (
 )
 
 // AppVer represents the version of Gitote
-const AppVer = "1.0.0-prod-rc.9"
+const AppVer = "1.0.1-prod-rc.1"
 
 // APIVer represents the API version of Gitote
 const APIVer = "v1"

+ 2 - 0
models/access.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"gitote/gitote/models/errors"
 
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 )
 
@@ -107,6 +108,7 @@ func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
 		repo, err := GetRepositoryByID(access.RepoID)
 		if err != nil {
 			if errors.IsRepoNotExist(err) {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "GetRepositoryByID: %v", err)
 				continue
 			}

+ 5 - 0
models/action.go

@@ -12,6 +12,7 @@ import (
 	"unicode"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"github.com/json-iterator/go"
 	"gitlab.com/gitote/git-module"
@@ -159,6 +160,7 @@ func (a *Action) GetIssueTitle() string {
 	index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
 	issue, err := GetIssueByIndex(a.RepoID, index)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "GetIssueByIndex: %v", err)
 		return "500 when get issue"
 	}
@@ -169,6 +171,7 @@ func (a *Action) GetIssueContent() string {
 	index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
 	issue, err := GetIssueByIndex(a.RepoID, index)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "GetIssueByIndex: %v", err)
 		return "500 when get issue"
 	}
@@ -304,6 +307,7 @@ func (push *PushCommits) AvatarLink(email string) string {
 		if err != nil {
 			push.avatars[email] = tool.AvatarLink(email)
 			if !errors.IsUserNotExist(err) {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(4, "GetUserByEmail: %v", err)
 			}
 		} else {
@@ -487,6 +491,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		}
 
 		if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "UpdateIssuesCommit: %v", err)
 		}
 	}

+ 2 - 0
models/admin.go

@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	log "gopkg.in/clog.v1"
 )
@@ -85,6 +86,7 @@ func RemoveAllWithNotice(title, path string) {
 		desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
 		log.Warn(desc)
 		if err = CreateRepositoryNotice(desc); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "CreateRepositoryNotice: %v", err)
 		}
 	}

+ 10 - 0
models/comment.go

@@ -8,6 +8,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	api "gitlab.com/gitote/go-gitote-client"
 	log "gopkg.in/clog.v1"
@@ -174,6 +175,7 @@ func (cmt *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue)
 		issue.Content = fmt.Sprintf("Reopened #%d", issue.Index)
 	}
 	if err = mailIssueCommentToParticipants(issue, cmt.Poster, mentions); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "mailIssueCommentToParticipants: %v", err)
 	}
 
@@ -276,9 +278,11 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
 	// Notify watchers for whatever action comes in, ignore if no action type.
 	if act.OpType > 0 {
 		if err = notifyWatchers(e, act); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "notifyWatchers: %v", err)
 		}
 		if err = comment.mailParticipants(e, act.OpType, opts.Issue); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "MailParticipants: %v", err)
 		}
 	}
@@ -350,6 +354,7 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
 		Repository: repo.APIFormat(nil),
 		Sender:     doer.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
 	}
 
@@ -459,6 +464,7 @@ func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
 	}
 
 	if err = c.Issue.LoadAttributes(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", c.IssueID, err)
 	} else if err = PrepareWebhooks(c.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{
 		Action:  api.HOOK_ISSUE_COMMENT_EDITED,
@@ -472,6 +478,7 @@ func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
 		Repository: c.Issue.Repo.APIFormat(nil),
 		Sender:     doer.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
 	}
 
@@ -510,10 +517,12 @@ func DeleteCommentByID(doer *User, id int64) error {
 
 	_, err = DeleteAttachmentsByComment(comment.ID, true)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Failed to delete attachments by comment[%d]: %v", comment.ID, err)
 	}
 
 	if err = comment.Issue.LoadAttributes(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Issue.LoadAttributes [issue_id: %d]: %v", comment.IssueID, err)
 	} else if err = PrepareWebhooks(comment.Issue.Repo, HOOK_EVENT_ISSUE_COMMENT, &api.IssueCommentPayload{
 		Action:     api.HOOK_ISSUE_COMMENT_DELETED,
@@ -522,6 +531,7 @@ func DeleteCommentByID(doer *User, id int64) error {
 		Repository: comment.Issue.Repo.APIFormat(nil),
 		Sender:     doer.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
 	}
 	return nil

+ 13 - 0
models/issue.go

@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	api "gitlab.com/gitote/go-gitote-client"
 	log "gopkg.in/clog.v1"
@@ -225,6 +226,7 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
 	if issue.IsPull {
 		err = issue.PullRequest.LoadIssue()
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LoadIssue: %v", err)
 			return
 		}
@@ -245,6 +247,7 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
 		})
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
 	}
 }
@@ -335,6 +338,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
 	if issue.IsPull {
 		err = issue.PullRequest.LoadIssue()
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LoadIssue: %v", err)
 			return
 		}
@@ -355,6 +359,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
 		})
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
 	}
 
@@ -492,6 +497,7 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
 		err = PrepareWebhooks(repo, HOOK_EVENT_ISSUES, apiIssues)
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
 	}
 
@@ -534,6 +540,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
 		})
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
 	}
 
@@ -576,6 +583,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
 		})
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
 	}
 
@@ -590,6 +598,7 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
 
 	issue.Assignee, err = GetUserByID(issue.AssigneeID)
 	if err != nil && !errors.IsUserNotExist(err) {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "GetUserByID [assignee_id: %v]: %v", issue.AssigneeID, err)
 		return nil
 	}
@@ -625,6 +634,7 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
 		err = PrepareWebhooks(issue.Repo, HOOK_EVENT_ISSUES, apiIssues)
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, isRemoveAssignee, err)
 	}
 
@@ -766,9 +776,11 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
 		RepoName:     repo.Name,
 		IsPrivate:    repo.IsPrivate,
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "NotifyWatchers: %v", err)
 	}
 	if err = issue.MailParticipants(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "MailParticipants: %v", err)
 	}
 
@@ -779,6 +791,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
 		Repository: repo.APIFormat(nil),
 		Sender:     issue.Poster.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks: %v", err)
 	}
 

+ 2 - 0
models/issue_mail.go

@@ -7,6 +7,7 @@ import (
 	"gitote/gitote/pkg/setting"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 )
 
@@ -162,6 +163,7 @@ func (issue *Issue) MailParticipants() (err error) {
 	}
 
 	if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "mailIssueCommentToParticipants: %v", err)
 	}
 

+ 6 - 0
models/login_source.go

@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-macaron/binding"
 	"github.com/go-xorm/core"
 	"github.com/go-xorm/xorm"
@@ -437,6 +438,7 @@ func LoadAuthSources() {
 
 	paths, err := com.GetFileListBySuffix(authdPath, ".conf")
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to list authentication sources: %v", err)
 	}
 
@@ -445,6 +447,7 @@ func LoadAuthSources() {
 	for _, fpath := range paths {
 		authSource, err := ini.Load(fpath)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Failed to load authentication source: %v", err)
 		}
 		authSource.NameMapper = ini.TitleUnderscore
@@ -464,6 +467,7 @@ func LoadAuthSources() {
 
 		fi, err := os.Stat(fpath)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Failed to load authentication source: %v", err)
 		}
 		loginSource.Updated = fi.ModTime()
@@ -484,10 +488,12 @@ func LoadAuthSources() {
 			loginSource.Type = LOGIN_PAM
 			loginSource.Cfg = &PAMConfig{}
 		default:
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Failed to load authentication source: unknown type '%s'", authType)
 		}
 
 		if err = authSource.Section("config").MapTo(loginSource.Cfg); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Failed to parse authentication source 'config': %v", err)
 		}
 

+ 3 - 0
models/milestone.go

@@ -5,6 +5,7 @@ import (
 	"gitote/gitote/pkg/setting"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	api "gitlab.com/gitote/go-gitote-client"
 	log "gopkg.in/clog.v1"
@@ -330,6 +331,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
 	if issue.IsPull {
 		err = issue.PullRequest.LoadIssue()
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LoadIssue: %v", err)
 			return
 		}
@@ -350,6 +352,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
 		})
 	}
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
 	}
 

+ 24 - 0
models/mirror.go

@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
@@ -52,6 +53,7 @@ func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
 	case "repo_id":
 		m.Repo, err = GetRepositoryByID(m.RepoID)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "GetRepositoryByID [%d]: %v", m.ID, err)
 		}
 	case "updated_unix":
@@ -113,6 +115,7 @@ func (m *Mirror) readAddress() {
 
 	cfg, err := ini.Load(m.Repo.GitConfigPath())
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Load: %v", err)
 		return
 	}
@@ -256,6 +259,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 	}) {
 		desc := fmt.Sprintf("Source URL of mirror repository '%s' is not accessible: %s", m.Repo.FullName(), m.MosaicsAddress())
 		if err := CreateRepositoryNotice(desc); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "CreateRepositoryNotice: %v", err)
 		}
 		return nil, false
@@ -272,6 +276,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 		desc := fmt.Sprintf("Fail to update mirror repository '%s': %s", repoPath, stderr)
 		log.Error(2, desc)
 		if err = CreateRepositoryNotice(desc); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "CreateRepositoryNotice: %v", err)
 		}
 		return nil, false
@@ -279,6 +284,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 	output := stderr
 
 	if err := m.Repo.UpdateSize(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "UpdateSize [repo_id: %d]: %v", m.Repo.ID, err)
 	}
 
@@ -288,8 +294,10 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 			timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath),
 			"git", "remote", "update", "--prune"); err != nil {
 			desc := fmt.Sprintf("Fail to update mirror wiki repository '%s': %s", wikiPath, stderr)
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, desc)
 			if err = CreateRepositoryNotice(desc); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "CreateRepositoryNotice: %v", err)
 			}
 		}
@@ -348,6 +356,7 @@ func MirrorUpdate() {
 		MirrorQueue.Add(m.RepoID)
 		return nil
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "MirrorUpdate: %v", err)
 	}
 }
@@ -362,6 +371,7 @@ func SyncMirrors() {
 
 		m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64())
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "GetMirrorByRepoID [%d]: %v", m.RepoID, err)
 			continue
 		}
@@ -373,6 +383,7 @@ func SyncMirrors() {
 
 		m.ScheduleNextSync()
 		if err = UpdateMirror(m); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "UpdateMirror [%d]: %v", m.RepoID, err)
 			continue
 		}
@@ -387,6 +398,7 @@ func SyncMirrors() {
 		} else {
 			gitRepo, err = git.OpenRepository(m.Repo.RepoPath())
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "OpenRepository [%d]: %v", m.RepoID, err)
 				continue
 			}
@@ -401,6 +413,7 @@ func SyncMirrors() {
 			// Delete reference
 			if result.newCommitID == GIT_SHORT_EMPTY_SHA {
 				if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err)
 				}
 				continue
@@ -410,6 +423,7 @@ func SyncMirrors() {
 			isNewRef := false
 			if result.oldCommitID == GIT_SHORT_EMPTY_SHA {
 				if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err)
 					continue
 				}
@@ -422,32 +436,38 @@ func SyncMirrors() {
 			if !isNewRef {
 				oldCommitID, err = git.GetFullCommitID(gitRepo.Path, result.oldCommitID)
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err)
 					continue
 				}
 				newCommitID, err = git.GetFullCommitID(gitRepo.Path, result.newCommitID)
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err)
 					continue
 				}
 				commits, err = gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID)
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err)
 					continue
 				}
 			} else {
 				refNewCommitID, err := gitRepo.GetBranchCommitID(result.refName)
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err)
 					continue
 				}
 				if newCommit, err := gitRepo.GetCommit(refNewCommitID); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "GetCommit [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommitID, err)
 					continue
 				} else {
 					// TODO: Get the commits for the new ref until the closest ancestor branch like Github does
 					commits, err = newCommit.CommitsBeforeLimit(10)
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(2, "CommitsBeforeLimit [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommitID, err)
 					}
 					oldCommitID = git.EMPTY_SHA
@@ -460,12 +480,14 @@ func SyncMirrors() {
 				NewCommitID: newCommitID,
 				Commits:     ListToPushCommits(commits),
 			}); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err)
 				continue
 			}
 		}
 
 		if _, err = x.Exec("UPDATE mirror SET updated_unix = ? WHERE repo_id = ?", time.Now().Unix(), m.RepoID); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Update 'mirror.updated_unix' [%d]: %v", m.RepoID, err)
 			continue
 		}
@@ -474,6 +496,7 @@ func SyncMirrors() {
 		// update if latest commit date is newer.
 		commitDate, err := git.GetLatestCommitDate(m.Repo.RepoPath(), "")
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "GetLatestCommitDate [%d]: %v", m.RepoID, err)
 			continue
 		} else if commitDate.Before(m.Repo.Updated) {
@@ -481,6 +504,7 @@ func SyncMirrors() {
 		}
 
 		if _, err = x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", commitDate.Unix(), m.RepoID); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Update 'repository.updated_unix' [%d]: %v", m.RepoID, err)
 			continue
 		}

+ 4 - 0
models/models.go

@@ -13,6 +13,7 @@ import (
 
 	"github.com/Unknwon/com"
 	_ "github.com/denisenkom/go-mssqldb"
+	raven "github.com/getsentry/raven-go"
 	_ "github.com/go-sql-driver/mysql"
 	"github.com/go-xorm/core"
 	"github.com/go-xorm/xorm"
@@ -352,12 +353,14 @@ func ImportDatabase(dirPath string, verbose bool) (err error) {
 
 			meta := make(map[string]interface{})
 			if err = jsoniter.Unmarshal(scanner.Bytes(), &meta); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "Failed to unmarshal to map: %v", err)
 			}
 
 			// Reset created_unix back to the date save in archive because Insert method updates its value
 			if isInsertProcessor && !skipInsertProcessors[rawTableName] {
 				if _, err = x.Exec("UPDATE "+rawTableName+" SET created_unix=? WHERE id=?", meta["CreatedUnix"], meta["ID"]); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "Failed to reset 'created_unix': %v", err)
 				}
 			}
@@ -365,6 +368,7 @@ func ImportDatabase(dirPath string, verbose bool) (err error) {
 			switch rawTableName {
 			case "milestone":
 				if _, err = x.Exec("UPDATE "+rawTableName+" SET deadline_unix=?, closed_date_unix=? WHERE id=?", meta["DeadlineUnix"], meta["ClosedDateUnix"], meta["ID"]); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "Failed to reset 'milestone.deadline_unix', 'milestone.closed_date_unix': %v", err)
 				}
 			}

+ 24 - 0
models/pull.go

@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"gitlab.com/gitote/git-module"
 	api "gitlab.com/gitote/go-gitote-client"
@@ -324,11 +325,13 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 	}
 
 	if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "MergePullRequestAction [%d]: %v", pr.ID, err)
 	}
 
 	// Reload pull request information.
 	if err = pr.LoadAttributes(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "LoadAttributes: %v", err)
 		return nil
 	}
@@ -339,12 +342,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 		Repository:  pr.Issue.Repo.APIFormat(nil),
 		Sender:      doer.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks: %v", err)
 		return nil
 	}
 
 	l, err := headGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "CommitsBetweenIDs: %v", err)
 		return nil
 	}
@@ -354,6 +359,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 	// to avoid strange diff commits produced.
 	mergeCommit, err := baseGitRepo.GetBranchCommit(pr.BaseBranch)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "GetBranchCommit: %v", err)
 		return nil
 	}
@@ -363,6 +369,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 
 	commits, err := ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "ToApiPayloadCommits: %v", err)
 		return nil
 	}
@@ -378,6 +385,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 		Sender:     doer.APIFormat(),
 	}
 	if err = PrepareWebhooks(pr.BaseRepo, HOOK_EVENT_PUSH, p); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks: %v", err)
 		return nil
 	}
@@ -483,9 +491,11 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
 		RepoName:     repo.Name,
 		IsPrivate:    repo.IsPrivate,
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "NotifyWatchers: %v", err)
 	}
 	if err = pull.MailParticipants(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "MailParticipants: %v", err)
 	}
 
@@ -498,6 +508,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
 		Repository:  repo.APIFormat(nil),
 		Sender:      pull.Poster.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks: %v", err)
 	}
 
@@ -660,6 +671,7 @@ func (pr *PullRequest) AddToTaskQueue() {
 	go PullRequestQueue.AddFunc(pr.ID, func() {
 		pr.Status = PULL_REQUEST_STATUS_CHECKING
 		if err := pr.UpdateCols("status"); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
 		}
 	})
@@ -710,9 +722,11 @@ func addHeadRepoTasks(prs []*PullRequest) {
 	for _, pr := range prs {
 		log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
 		if err := pr.UpdatePatch(); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "UpdatePatch: %v", err)
 			continue
 		} else if err := pr.PushToBaseRepo(); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "PushToBaseRepo: %v", err)
 			continue
 		}
@@ -727,12 +741,14 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
 	log.Trace("AddTestPullRequestTask [head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
 	prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
 		return
 	}
 
 	if isSync {
 		if err = PullRequestList(prs).LoadAttributes(); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "PullRequestList.LoadAttributes: %v", err)
 		}
 
@@ -740,6 +756,7 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
 			for _, pr := range prs {
 				pr.Issue.PullRequest = pr
 				if err = pr.Issue.LoadAttributes(); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "LoadAttributes: %v", err)
 					continue
 				}
@@ -750,6 +767,7 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
 					Repository:  pr.Issue.Repo.APIFormat(nil),
 					Sender:      doer.APIFormat(),
 				}); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
 					continue
 				}
@@ -762,6 +780,7 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
 	log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
 	prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Find pull requests [base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
 		return
 	}
@@ -789,6 +808,7 @@ func (pr *PullRequest) checkAndUpdateStatus() {
 	// Make sure there is no waiting test to process before levaing the checking status.
 	if !PullRequestQueue.Exist(pr.ID) {
 		if err := pr.UpdateCols("status"); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "Update[%d]: %v", pr.ID, err)
 		}
 	}
@@ -805,11 +825,13 @@ func TestPullRequests() {
 			pr := bean.(*PullRequest)
 
 			if err := pr.LoadAttributes(); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(3, "LoadAttributes: %v", err)
 				return nil
 			}
 
 			if err := pr.testPatch(); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(3, "testPatch: %v", err)
 				return nil
 			}
@@ -829,9 +851,11 @@ func TestPullRequests() {
 
 		pr, err := GetPullRequestByID(com.StrTo(prID).MustInt64())
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "GetPullRequestByID[%s]: %v", prID, err)
 			continue
 		} else if err = pr.testPatch(); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "testPatch[%d]: %v", pr.ID, err)
 			continue
 		}

+ 2 - 0
models/release.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"gitlab.com/gitote/git-module"
 	api "gitlab.com/gitote/go-gitote-client"
@@ -150,6 +151,7 @@ func (r *Release) preparePublishWebhooks() {
 		Repository: r.Repo.APIFormat(nil),
 		Sender:     r.Publisher.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks: %v", err)
 	}
 }

+ 31 - 1
models/repo.go

@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"github.com/mcuadros/go-version"
 	"github.com/nfnt/resize"
@@ -42,7 +43,7 @@ var (
 	Gitignores, Licenses, Readmes, LabelTemplates []string
 
 	// Maximum items per page in forks, watchers and stars of a repo
-	ItemsPerPage = 40
+	ItemsPerPage = 39
 )
 
 func LoadRepoConfig() {
@@ -52,12 +53,14 @@ func LoadRepoConfig() {
 	for i, t := range types {
 		files, err := bindata.AssetDir("conf/" + t)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(4, "Fail to get %s files: %v", t, err)
 		}
 		customPath := path.Join(setting.CustomPath, "conf", t)
 		if com.IsDir(customPath) {
 			customFiles, err := com.StatDir(customPath)
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Fatal(4, "Fail to get custom %s files: %v", t, err)
 			}
 
@@ -99,6 +102,7 @@ func NewRepoContext() {
 
 	// Check Git installation.
 	if _, err := exec.LookPath("git"); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Fail to test 'git' command: %v (forgotten install?)", err)
 	}
 
@@ -106,11 +110,13 @@ func NewRepoContext() {
 	var err error
 	setting.Git.Version, err = git.BinVersion()
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Fail to get Git version: %v", err)
 	}
 
 	log.Info("Git Version: %s", setting.Git.Version)
 	if version.Compare("1.7.1", setting.Git.Version, ">") {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Gitote requires Git version greater or equal to 1.7.1")
 	}
 	git.HookDir = "custom_hooks"
@@ -123,10 +129,12 @@ func NewRepoContext() {
 			// ExitError indicates this config is not set
 			if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" {
 				if _, stderr, gerr := process.Exec("NewRepoContext(set "+configKey+")", "git", "config", "--global", configKey, defaultValue); gerr != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Fatal(4, "Fail to set git %s(%s): %s", configKey, gerr, stderr)
 				}
 				log.Info("Git config %s set to %s", configKey, defaultValue)
 			} else {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Fatal(4, "Fail to get git %s(%s): %s", configKey, err, stderr)
 			}
 		}
@@ -135,6 +143,7 @@ func NewRepoContext() {
 	// Set git some configurations.
 	if _, stderr, err := process.Exec("NewRepoContext(git config --global core.quotepath false)",
 		"git", "config", "--global", "core.quotepath", "false"); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Fail to execute 'git config --global core.quotepath false': %s", stderr)
 	}
 
@@ -807,6 +816,7 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 		}
 
 		if err = repo.UpdateSize(); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "UpdateSize [repo_id: %d]: %v", repo.ID, err)
 		}
 	}
@@ -1157,6 +1167,7 @@ func countRepositories(userID int64, private bool) int64 {
 
 	count, err := sess.Count(new(Repository))
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "countRepositories: %v", err)
 	}
 	return count
@@ -1426,10 +1437,12 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
 		daemonExportFile := path.Join(repo.RepoPath(), "git-daemon-export-ok")
 		if repo.IsPrivate && com.IsExist(daemonExportFile) {
 			if err = os.Remove(daemonExportFile); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(4, "Failed to remove %s: %v", daemonExportFile, err)
 			}
 		} else if !repo.IsPrivate && !com.IsExist(daemonExportFile) {
 			if f, err := os.Create(daemonExportFile); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(4, "Failed to create %s: %v", daemonExportFile, err)
 			} else {
 				f.Close()
@@ -1582,6 +1595,7 @@ func DeleteRepository(uid, repoID int64) error {
 
 	if repo.NumForks > 0 {
 		if _, err = x.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "reset 'fork_id' and 'is_fork': %v", err)
 		}
 	}
@@ -1771,6 +1785,7 @@ func DeleteOldRepositoryArchives() {
 
 				dir, err := os.Open(dirPath)
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(3, "Fail to open directory '%s': %v", dirPath, err)
 					continue
 				}
@@ -1778,6 +1793,7 @@ func DeleteOldRepositoryArchives() {
 				fis, err := dir.Readdir(0)
 				dir.Close()
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(3, "Fail to read directory '%s': %v", dirPath, err)
 					continue
 				}
@@ -1792,6 +1808,7 @@ func DeleteOldRepositoryArchives() {
 						desc := fmt.Sprintf("Fail to health delete archive '%s': %v", archivePath, err)
 						log.Warn(desc)
 						if err = CreateRepositoryNotice(desc); err != nil {
+							raven.CaptureErrorAndWait(err, nil)
 							log.Error(3, "CreateRepositoryNotice: %v", err)
 						}
 					}
@@ -1800,6 +1817,7 @@ func DeleteOldRepositoryArchives() {
 
 			return nil
 		}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "DeleteOldRepositoryArchives: %v", err)
 	}
 }
@@ -1925,11 +1943,13 @@ func GitFsck() {
 				desc := fmt.Sprintf("Fail to health check repository '%s': %v", repoPath, err)
 				log.Warn(desc)
 				if err = CreateRepositoryNotice(desc); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(3, "CreateRepositoryNotice: %v", err)
 				}
 			}
 			return nil
 		}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "GitFsck: %v", err)
 	}
 }
@@ -1961,6 +1981,7 @@ type repoChecker struct {
 func repoStatsCheck(checker *repoChecker) {
 	results, err := x.Query(checker.querySQL)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Select %s: %v", checker.desc, err)
 		return
 	}
@@ -1969,6 +1990,7 @@ func repoStatsCheck(checker *repoChecker) {
 		log.Trace("Updating %s: %d", checker.desc, id)
 		_, err = x.Exec(checker.correctSQL, id, id)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Update %s[%d]: %v", checker.desc, id, err)
 		}
 	}
@@ -2023,6 +2045,7 @@ func CheckRepoStats() {
 	desc := "repository count 'num_closed_issues'"
 	results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Select %s: %v", desc, err)
 	} else {
 		for _, result := range results {
@@ -2030,6 +2053,7 @@ func CheckRepoStats() {
 			log.Trace("Updating %s: %d", desc, id)
 			_, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id)
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "Update %s[%d]: %v", desc, id, err)
 			}
 		}
@@ -2040,6 +2064,7 @@ func CheckRepoStats() {
 	// ***** START: Repository.NumForks *****
 	results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Select repository count 'num_forks': %v", err)
 	} else {
 		for _, result := range results {
@@ -2048,18 +2073,21 @@ func CheckRepoStats() {
 
 			repo, err := GetRepositoryByID(id)
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "GetRepositoryByID[%d]: %v", id, err)
 				continue
 			}
 
 			rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID)
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "Select count of forks[%d]: %v", repo.ID, err)
 				continue
 			}
 			repo.NumForks = int(parseCountResult(rawResult))
 
 			if err = UpdateRepository(repo, false); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "UpdateRepository[%d]: %v", id, err)
 				continue
 			}
@@ -2393,6 +2421,7 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string)
 	}
 
 	if err = repo.UpdateSize(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "UpdateSize [repo_id: %d]: %v", repo.ID, err)
 	}
 	if err = PrepareWebhooks(baseRepo, HOOK_EVENT_FORK, &api.ForkPayload{
@@ -2400,6 +2429,7 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string)
 		Repo:   baseRepo.APIFormat(nil),
 		Sender: doer.APIFormat(),
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", baseRepo.ID, err)
 	}
 	return repo, nil

+ 2 - 0
models/repo_collaboration.go

@@ -3,6 +3,7 @@ package models
 import (
 	"fmt"
 
+	raven "github.com/getsentry/raven-go"
 	api "gitlab.com/gitote/go-gitote-client"
 	log "gopkg.in/clog.v1"
 )
@@ -36,6 +37,7 @@ func IsCollaborator(repoID, userID int64) bool {
 	}
 	has, err := x.Get(collaboration)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "get collaboration [repo_id: %d, user_id: %d]: %v", repoID, userID, err)
 		return false
 	}

+ 10 - 0
models/repo_editor.go

@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	gouuid "github.com/satori/go.uuid"
 	git "gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
@@ -139,11 +140,13 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 
 	gitRepo, err := git.OpenRepository(repo.RepoPath())
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "OpenRepository: %v", err)
 		return nil
 	}
 	commit, err := gitRepo.GetBranchCommit(opts.NewBranch)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err)
 		return nil
 	}
@@ -166,6 +169,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 		NewCommitID: commit.ID.String(),
 		Commits:     pushCommits,
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "CommitRepoAction: %v", err)
 		return nil
 	}
@@ -262,11 +266,13 @@ func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (
 
 	gitRepo, err := git.OpenRepository(repo.RepoPath())
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "OpenRepository: %v", err)
 		return nil
 	}
 	commit, err := gitRepo.GetBranchCommit(opts.NewBranch)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err)
 		return nil
 	}
@@ -285,6 +291,7 @@ func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (
 		NewCommitID: commit.ID.String(),
 		Commits:     pushCommits,
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "CommitRepoAction: %v", err)
 		return nil
 	}
@@ -479,11 +486,13 @@ func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions)
 
 	gitRepo, err := git.OpenRepository(repo.RepoPath())
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "OpenRepository: %v", err)
 		return nil
 	}
 	commit, err := gitRepo.GetBranchCommit(opts.NewBranch)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "GetBranchCommit [branch: %s]: %v", opts.NewBranch, err)
 		return nil
 	}
@@ -502,6 +511,7 @@ func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions)
 		NewCommitID: commit.ID.String(),
 		Commits:     pushCommits,
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "CommitRepoAction: %v", err)
 		return nil
 	}

+ 2 - 0
models/ssh_key.go

@@ -17,6 +17,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"golang.org/x/crypto/ssh"
 	log "gopkg.in/clog.v1"
@@ -336,6 +337,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
 
 		// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
 		if fi.Mode().Perm() > 0600 {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
 			if err = f.Chmod(0600); err != nil {
 				return err

+ 12 - 4
models/user.go

@@ -21,6 +21,7 @@ import (
 	"unicode/utf8"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"github.com/nfnt/resize"
 	"gitlab.com/gitote/git-module"
@@ -77,7 +78,7 @@ type User struct {
 	IsActive         bool // Activate primary email
 	PrivateEmail     bool
 	IsBeta           bool
-	ShowSponsors     bool
+	ShowAds          bool
 	IsStaff          bool
 	IsIntern         bool
 	IsAdmin          bool
@@ -86,9 +87,10 @@ type User struct {
 	Suspended        bool
 
 	// Badges
-	IsVerified  bool
-	IsMaker     bool
-	IsBugHunter bool
+	IsVerified      bool
+	IsMaker         bool
+	IsBugHunter     bool
+	GitoteDeveloper bool
 
 	// Social
 	Twitter       string
@@ -99,6 +101,8 @@ type User struct {
 	Reddit        string
 	Telegram      string
 	Codepen       string
+	Gitlab        string
+
 
 	// Avatar
 	Avatar          string `xorm:"VARCHAR(2048) NOT NULL"`
@@ -302,6 +306,7 @@ func (u *User) RelAvatarLink() string {
 	case setting.DisableGravatar, setting.OfflineMode:
 		if !com.IsExist(u.CustomAvatarPath()) {
 			if err := u.GenerateRandomAvatar(); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(3, "GenerateRandomAvatar: %v", err)
 			}
 		}
@@ -408,6 +413,7 @@ func (u *User) DeleteAvatar() error {
 func (u *User) IsAdminOfRepo(repo *Repository) bool {
 	has, err := HasAccess(u.ID, repo, ACCESS_MODE_ADMIN)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "HasAccess: %v", err)
 	}
 	return has
@@ -417,6 +423,7 @@ func (u *User) IsAdminOfRepo(repo *Repository) bool {
 func (u *User) IsWriterOfRepo(repo *Repository) bool {
 	has, err := HasAccess(u.ID, repo, ACCESS_MODE_WRITE)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "HasAccess: %v", err)
 	}
 	return has
@@ -645,6 +652,7 @@ func parseUserFromCode(code string) (user *User) {
 		if user, err = GetUserByName(string(b)); user != nil {
 			return user
 		} else if !errors.IsUserNotExist(err) {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "GetUserByName: %v", err)
 		}
 	}

+ 12 - 0
models/webhook.go

@@ -14,6 +14,7 @@ import (
 	"strings"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"github.com/json-iterator/go"
 	gouuid "github.com/satori/go.uuid"
@@ -121,6 +122,7 @@ func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
 	case "events":
 		w.HookEvent = &HookEvent{}
 		if err = jsoniter.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "Unmarshal [%d]: %v", w.ID, err)
 		}
 	case "created_unix":
@@ -133,6 +135,7 @@ func (w *Webhook) AfterSet(colName string, _ xorm.Cell) {
 func (w *Webhook) GetSlackHook() *SlackMeta {
 	s := &SlackMeta{}
 	if err := jsoniter.Unmarshal([]byte(w.Meta), s); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "GetSlackHook [%d]: %v", w.ID, err)
 	}
 	return s
@@ -440,6 +443,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
 
 		t.RequestInfo = &HookRequest{}
 		if err = jsoniter.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "Unmarshal[%d]: %v", t.ID, err)
 		}
 
@@ -450,6 +454,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
 
 		t.ResponseInfo = &HookResponse{}
 		if err = jsoniter.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "Unmarshal [%d]: %v", t.ID, err)
 		}
 	}
@@ -458,6 +463,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
 func (t *HookTask) MarshalJSON(v interface{}) string {
 	p, err := jsoniter.Marshal(v)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "Marshal [%d]: %v", t.ID, err)
 	}
 	return string(p)
@@ -566,6 +572,7 @@ func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Pay
 		if len(w.Secret) > 0 {
 			data, err := payloader.JSONPayload()
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "prepareWebhooks.JSONPayload: %v", err)
 			}
 			sig := hmac.New(sha256.New, []byte(w.Secret))
@@ -669,6 +676,7 @@ func (t *HookTask) deliver() {
 		// Update webhook last delivery status.
 		w, err := GetWebhookByID(t.HookID)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "GetWebhookByID: %v", err)
 			return
 		}
@@ -678,6 +686,7 @@ func (t *HookTask) deliver() {
 			w.LastStatus = HOOK_STATUS_FAILED
 		}
 		if err = UpdateWebhook(w); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "UpdateWebhook: %v", err)
 			return
 		}
@@ -720,6 +729,7 @@ func DeliverHooks() {
 	// Update hook task status.
 	for _, t := range tasks {
 		if err := UpdateHookTask(t); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
 		}
 	}
@@ -731,12 +741,14 @@ func DeliverHooks() {
 
 		tasks = make([]*HookTask, 0, 5)
 		if err := x.Where("repo_id = ?", repoID).And("is_delivered = ?", false).Find(&tasks); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "Get repository [%s] hook tasks: %v", repoID, err)
 			continue
 		}
 		for _, t := range tasks {
 			t.deliver()
 			if err := UpdateHookTask(t); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
 				continue
 			}

+ 13 - 5
pkg/auth/auth.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-macaron/session"
 	gouuid "github.com/satori/go.uuid"
 	log "gopkg.in/clog.v1"
@@ -46,12 +47,14 @@ func SignedInID(c *macaron.Context, sess session.Store) int64 {
 			t, err := models.GetAccessTokenBySHA(tokenSHA)
 			if err != nil {
 				if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "GetAccessTokenBySHA: %v", err)
 				}
 				return 0
 			}
 			t.Updated = time.Now()
 			if err = models.UpdateAccessToken(t); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "UpdateAccessToken: %v", err)
 			}
 			return t.UID
@@ -65,6 +68,7 @@ func SignedInID(c *macaron.Context, sess session.Store) int64 {
 	if id, ok := uid.(int64); ok {
 		if _, err := models.GetUserByID(id); err != nil {
 			if !errors.IsUserNotExist(err) {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "GetUserByID: %v", err)
 			}
 			return 0
@@ -90,6 +94,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
 				u, err := models.GetUserByName(webAuthUser)
 				if err != nil {
 					if !errors.IsUserNotExist(err) {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(4, "GetUserByName: %v", err)
 						return nil, false
 					}
@@ -97,14 +102,15 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
 					// Check if enabled auto-registration.
 					if setting.Service.EnableReverseProxyAutoRegister {
 						u := &models.User{
-							Name:         webAuthUser,
-							Email:        gouuid.NewV4().String() + "@localhost",
-							Passwd:       webAuthUser,
-							ShowSponsors: true,
-							IsActive:     true,
+							Name:     webAuthUser,
+							Email:    gouuid.NewV4().String() + "@localhost",
+							Passwd:   webAuthUser,
+							ShowAds:  true,
+							IsActive: true,
 						}
 						if err = models.CreateUser(u); err != nil {
 							// FIXME: should I create a system notice?
+							raven.CaptureErrorAndWait(err, nil)
 							log.Error(4, "CreateUser: %v", err)
 							return nil, false
 						} else {
@@ -126,6 +132,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
 				u, err := models.UserLogin(uname, passwd, -1)
 				if err != nil {
 					if !errors.IsUserNotExist(err) {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(4, "UserLogin: %v", err)
 					}
 					return nil, false
@@ -139,6 +146,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
 
 	u, err := models.GetUserByID(uid)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "GetUserById: %v", err)
 		return nil, false
 	}

+ 8 - 0
pkg/auth/ldap/ldap.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"strings"
 
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 	"gopkg.in/ldap.v2"
 )
@@ -126,6 +127,7 @@ func (ls *Source) findUserDN(l *ldap.Conn, name string) (string, bool) {
 
 	userDN := sr.Entries[0].DN
 	if userDN == "" {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "LDAP: Search was successful, but found no DN!")
 		return "", false
 	}
@@ -179,6 +181,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 	}
 	l, err := dial(ls)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "LDAP connect failed for '%s': %v", ls.Host, err)
 		return "", "", "", "", false, false
 	}
@@ -225,6 +228,7 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 
 	sr, err := l.Search(search)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "LDAP: User search failed: %v", err)
 		return "", "", "", "", false, false
 	} else if len(sr.Entries) < 1 {
@@ -262,9 +266,11 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 
 		srg, err := l.Search(groupSearch)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LDAP: Group search failed: %v", err)
 			return "", "", "", "", false, false
 		} else if len(srg.Entries) < 1 {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LDAP: Group search failed: 0 entries")
 			return "", "", "", "", false, false
 		}
@@ -304,8 +310,10 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
 
 		sr, err = l.Search(search)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LDAP: Admin search failed: %v", err)
 		} else if len(sr.Entries) < 1 {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "LDAP: Admin search failed: 0 entries")
 		} else {
 			isAdmin = true

Файловите разлики са ограничени, защото са твърде много
+ 6 - 6
pkg/bindata/bindata.go


+ 2 - 0
pkg/context/context.go

@@ -17,6 +17,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-macaron/cache"
 	"github.com/go-macaron/csrf"
 	"github.com/go-macaron/i18n"
@@ -169,6 +170,7 @@ func (c *Context) Handle(status int, title string, err error) {
 		c.Data["Title"] = "Page Not Found"
 	case http.StatusInternalServerError:
 		c.Data["Title"] = "Internal Server Error"
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "%s: %v", title, err)
 		if !setting.ProdMode || (c.IsLogged && c.User.IsAdmin) {
 			c.Data["ErrorMsg"] = err

+ 5 - 0
pkg/cron/cron.go

@@ -5,6 +5,7 @@ import (
 	"gitote/gitote/pkg/setting"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/gitote/cron"
 	log "gopkg.in/clog.v1"
 )
@@ -19,6 +20,7 @@ func NewContext() {
 	if setting.Cron.UpdateMirror.Enabled {
 		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, models.MirrorUpdate)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Cron.(update mirrors): %v", err)
 		}
 		if setting.Cron.UpdateMirror.RunAtStart {
@@ -30,6 +32,7 @@ func NewContext() {
 	if setting.Cron.RepoHealthCheck.Enabled {
 		entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, models.GitFsck)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Cron.(repository health check): %v", err)
 		}
 		if setting.Cron.RepoHealthCheck.RunAtStart {
@@ -41,6 +44,7 @@ func NewContext() {
 	if setting.Cron.CheckRepoStats.Enabled {
 		entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, models.CheckRepoStats)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Cron.(check repository statistics): %v", err)
 		}
 		if setting.Cron.CheckRepoStats.RunAtStart {
@@ -52,6 +56,7 @@ func NewContext() {
 	if setting.Cron.RepoArchiveCleanup.Enabled {
 		entry, err = c.AddFunc("Repository archive cleanup", setting.Cron.RepoArchiveCleanup.Schedule, models.DeleteOldRepositoryArchives)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Cron.(repository archive cleanup): %v", err)
 		}
 		if setting.Cron.RepoArchiveCleanup.RunAtStart {

+ 2 - 0
pkg/form/admin.go

@@ -39,6 +39,7 @@ type AdminEditUser struct {
 	Reddit           string `binding:"MaxSize(50)"`
 	Telegram         string `binding:"MaxSize(50)"`
 	Codepen          string `binding:"MaxSize(50)"`
+	Gitlab           string `binding:"MaxSize(50)"`
 	Recognized       string
 	Certified        string
 	MaxRepoCreation  int
@@ -50,6 +51,7 @@ type AdminEditUser struct {
 	IsVerified       bool
 	IsMaker          bool
 	IsBugHunter      bool
+	GitoteDeveloper  bool
 	IsBeta           bool
 	IsStaff          bool
 	IsIntern         bool

+ 3 - 1
pkg/form/user.go

@@ -83,7 +83,7 @@ type UpdateProfile struct {
 	Status       string `binding:"MaxSize(25)"`
 	ThemeColor   string
 	IsBeta       bool
-	ShowSponsors bool
+	ShowAds      bool
 	PrivateEmail bool
 }
 
@@ -96,6 +96,8 @@ type UpdateSocial struct {
 	Reddit        string `binding:"MaxSize(50)"`
 	Telegram      string `binding:"MaxSize(50)"`
 	Codepen       string `binding:"MaxSize(50)"`
+	Gitlab        string `binding:"MaxSize(50)"`
+
 }
 
 func (f *UpdateProfile) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

+ 4 - 0
pkg/httplib/httplib.go

@@ -18,6 +18,7 @@ import (
 	"sync"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/json-iterator/go"
 )
 
@@ -258,16 +259,19 @@ func (r *Request) getResponse() (*http.Response, error) {
 				for formname, filename := range r.files {
 					fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Fatal(err)
 					}
 					fh, err := os.Open(filename)
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Fatal(err)
 					}
 					//iocopy
 					_, err = io.Copy(fileWriter, fh)
 					fh.Close()
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Fatal(err)
 					}
 				}

+ 6 - 0
pkg/mailer/mail.go

@@ -6,6 +6,7 @@ import (
 	"gitote/gitote/pkg/setting"
 	"html/template"
 
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 	"gopkg.in/gomail.v2"
 	"gopkg.in/macaron.v1"
@@ -82,6 +83,7 @@ func SendUserMail(c *macaron.Context, u User, tpl, code, subject, info string) {
 	}
 	body, err := mailRender.HTMLString(string(tpl), data)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "HTMLString: %v", err)
 		return
 	}
@@ -110,6 +112,7 @@ func SendActivateEmailMail(c *macaron.Context, u User, email string) {
 	}
 	body, err := mailRender.HTMLString(string(MAIL_AUTH_ACTIVATE_EMAIL), data)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "HTMLString: %v", err)
 		return
 	}
@@ -127,6 +130,7 @@ func SendRegisterNotifyMail(c *macaron.Context, u User) {
 	}
 	body, err := mailRender.HTMLString(string(MAIL_AUTH_REGISTER_NOTIFY), data)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "HTMLString: %v", err)
 		return
 	}
@@ -148,6 +152,7 @@ func SendCollaboratorMail(u, doer User, repo Repository) {
 	}
 	body, err := mailRender.HTMLString(string(MAIL_NOTIFY_COLLABORATOR), data)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "HTMLString: %v", err)
 		return
 	}
@@ -173,6 +178,7 @@ func composeIssueMessage(issue Issue, repo Repository, doer User, tplName string
 	data["Doer"] = doer
 	content, err := mailRender.HTMLString(tplName, data)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "HTMLString (%s): %v", tplName, err)
 	}
 	from := gomail.NewMessage().FormatAddress(setting.MailService.FromEmail, doer.DisplayName())

+ 3 - 0
pkg/mailer/mailer.go

@@ -11,6 +11,7 @@ import (
 	"strings"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/jaytaylor/html2text"
 	log "gopkg.in/clog.v1"
 	"gopkg.in/gomail.v2"
@@ -37,6 +38,7 @@ func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
 	if setting.MailService.UsePlainText {
 		plainBody, err := html2text.FromString(htmlBody)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "html2text.FromString: %v", err)
 		} else {
 			contentType = "text/plain"
@@ -196,6 +198,7 @@ func processMailQueue() {
 		case msg := <-mailQueue:
 			log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
 			if err := gomail.Send(sender, msg.Message); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
 			} else {
 				log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)

+ 2 - 0
pkg/process/manager.go

@@ -8,6 +8,7 @@ import (
 	"sync"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 )
 
@@ -97,6 +98,7 @@ func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (
 	select {
 	case <-time.After(timeout):
 		if errKill := Kill(pid); errKill != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Fail to kill timeout process [pid: %d, desc: %s]: %v", pid, desc, errKill)
 		}
 		<-done

+ 31 - 0
pkg/setting/setting.go

@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	_ "github.com/go-macaron/cache/memcache"
 	_ "github.com/go-macaron/cache/redis"
 	"github.com/go-macaron/session"
@@ -339,6 +340,7 @@ func init() {
 
 	var err error
 	if AppPath, err = execPath(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to get app path: %v\n", err)
 	}
 
@@ -386,6 +388,7 @@ func getOpenSSHVersion() string {
 	// Note: somehow version is printed to stderr
 	_, stderr, err := process.Exec("getOpenSSHVersion", "ssh", "-V")
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to get OpenSSH version: %v - %s", err, stderr)
 	}
 
@@ -400,6 +403,7 @@ func getOpenSSHVersion() string {
 func NewContext() {
 	workDir, err := WorkDir()
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to get work directory: %v", err)
 	}
 
@@ -407,6 +411,7 @@ func NewContext() {
 		IgnoreInlineComment: true,
 	}, bindata.MustAsset("conf/app.ini"))
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to parse 'conf/app.ini': %v", err)
 	}
 
@@ -421,6 +426,7 @@ func NewContext() {
 
 	if com.IsFile(CustomConf) {
 		if err = Cfg.Append(CustomConf); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Fail to load custom conf '%s': %v", CustomConf, err)
 		}
 	} else {
@@ -430,6 +436,7 @@ func NewContext() {
 
 	homeDir, err := com.HomeDir()
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to get home directory: %v", err)
 	}
 	homeDir = strings.Replace(homeDir, "\\", "/", -1)
@@ -446,6 +453,7 @@ func NewContext() {
 	// Check if has app suburl.
 	url, err := url.Parse(AppURL)
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Invalid ROOT_URL '%s': %s", AppURL, err)
 	}
 	// Suburl should start with '/' and end without '/', such as '/{subpath}'.
@@ -466,6 +474,7 @@ func NewContext() {
 		UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
 		UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32)
 		if err != nil || UnixSocketPermissionParsed > 0777 {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Fail to parse unixSocketPermission: %s", UnixSocketPermissionRaw)
 		}
 		UnixSocketPermission = uint32(UnixSocketPermissionParsed)
@@ -492,6 +501,7 @@ func NewContext() {
 	SSH.ServerCiphers = sec.Key("SSH_SERVER_CIPHERS").Strings(",")
 	SSH.KeyTestPath = os.TempDir()
 	if err = Cfg.Section("server").MapTo(&SSH); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to map SSH settings: %v", err)
 	}
 	if SSH.Disabled {
@@ -501,8 +511,10 @@ func NewContext() {
 
 	if !SSH.Disabled && !SSH.StartBuiltinServer {
 		if err := os.MkdirAll(SSH.RootPath, 0700); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Fail to create '%s': %v", SSH.RootPath, err)
 		} else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Fail to create '%s': %v", SSH.KeyTestPath, err)
 		}
 	}
@@ -575,6 +587,7 @@ func NewContext() {
 	if InstallLock {
 		currentUser, match := IsRunUserMatchCurrentUser(RunUser)
 		if !match {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Expect user '%s' but current user is: %s", RunUser, currentUser)
 		}
 	}
@@ -592,10 +605,13 @@ func NewContext() {
 	}
 	ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
 	if err = Cfg.Section("repository").MapTo(&Repository); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to map Repository settings: %v", err)
 	} else if err = Cfg.Section("repository.editor").MapTo(&Repository.Editor); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to map Repository.Editor settings: %v", err)
 	} else if err = Cfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Fail to map Repository.Upload settings: %v", err)
 	}
 
@@ -649,28 +665,40 @@ func NewContext() {
 	}
 
 	if err = Cfg.Section("http").MapTo(&HTTP); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map HTTP settings: %v", err)
 	} else if err = Cfg.Section("webhook").MapTo(&Webhook); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Webhook settings: %v", err)
 	} else if err = Cfg.Section("release.attachment").MapTo(&Release.Attachment); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Release.Attachment settings: %v", err)
 	} else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Markdown settings: %v", err)
 	} else if err = Cfg.Section("smartypants").MapTo(&Smartypants); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Smartypants settings: %v", err)
 	} else if err = Cfg.Section("admin").MapTo(&Admin); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Admin settings: %v", err)
 	} else if err = Cfg.Section("cron").MapTo(&Cron); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Cron settings: %v", err)
 	} else if err = Cfg.Section("git").MapTo(&Git); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Git settings: %v", err)
 	} else if err = Cfg.Section("mirror").MapTo(&Mirror); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Mirror settings: %v", err)
 	} else if err = Cfg.Section("api").MapTo(&API); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map API settings: %v", err)
 	} else if err = Cfg.Section("ui").MapTo(&UI); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map UI settings: %v", err)
 	} else if err = Cfg.Section("prometheus").MapTo(&Prometheus); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(2, "Failed to map Prometheus settings: %v", err)
 	}
 
@@ -734,6 +762,7 @@ func newLogService() {
 		mode = strings.ToLower(strings.TrimSpace(mode))
 		sec, err := Cfg.GetSection("log." + mode)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Unknown logger mode: %s", mode)
 		}
 
@@ -759,6 +788,7 @@ func newLogService() {
 		case log.FILE:
 			logPath := path.Join(LogRootPath, "gitote.log")
 			if err = os.MkdirAll(path.Dir(logPath), os.ModePerm); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Fatal(2, "Fail to create log directory '%s': %v", path.Dir(logPath), err)
 			}
 
@@ -877,6 +907,7 @@ func newMailService() {
 	if len(MailService.From) > 0 {
 		parsed, err := mail.ParseAddress(MailService.From)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Invalid mailer.FROM (%s): %v", MailService.From, err)
 		}
 		MailService.FromEmail = parsed.Address

+ 12 - 0
pkg/ssh/ssh.go

@@ -13,6 +13,7 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"golang.org/x/crypto/ssh"
 	log "gopkg.in/clog.v1"
 )
@@ -34,6 +35,7 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 
 		ch, reqs, err := newChan.Accept()
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "Error accepting channel: %v", err)
 			continue
 		}
@@ -52,6 +54,7 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 					args[0] = strings.TrimLeft(args[0], "\x04")
 					_, _, err := com.ExecCmdBytes("env", args[0]+"="+args[1])
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(3, "env: %v", err)
 						return
 					}
@@ -66,22 +69,26 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 
 					stdout, err := cmd.StdoutPipe()
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(3, "SSH: StdoutPipe: %v", err)
 						return
 					}
 					stderr, err := cmd.StderrPipe()
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(3, "SSH: StderrPipe: %v", err)
 						return
 					}
 					input, err := cmd.StdinPipe()
 					if err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(3, "SSH: StdinPipe: %v", err)
 						return
 					}
 
 					// FIXME: check timeout
 					if err = cmd.Start(); err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(3, "SSH: Start: %v", err)
 						return
 					}
@@ -92,6 +99,7 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 					io.Copy(ch.Stderr(), stderr)
 
 					if err = cmd.Wait(); err != nil {
+						raven.CaptureErrorAndWait(err, nil)
 						log.Error(3, "SSH: Wait: %v", err)
 						return
 					}
@@ -108,12 +116,14 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 func listen(config *ssh.ServerConfig, host string, port int) {
 	listener, err := net.Listen("tcp", host+":"+com.ToStr(port))
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Fatal(4, "Fail to start SSH server: %v", err)
 	}
 	for {
 		// Once a ServerConfig has been configured, connections can be accepted.
 		conn, err := listener.Accept()
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "SSH: Error accepting incoming connection: %v", err)
 			continue
 		}
@@ -129,6 +139,7 @@ func listen(config *ssh.ServerConfig, host string, port int) {
 				if err == io.EOF {
 					log.Warn("SSH: Handshaking was terminated: %v", err)
 				} else {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(3, "SSH: Error on handshaking: %v", err)
 				}
 				return
@@ -151,6 +162,7 @@ func Listen(host string, port int, ciphers []string) {
 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
 			pkey, err := models.SearchPublicKeyByContent(strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))))
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(3, "SearchPublicKeyByContent: %v", err)
 				return nil, err
 			}

+ 2 - 0
pkg/template/template.go

@@ -14,6 +14,7 @@ import (
 	"strings"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/json-iterator/go"
 	"github.com/microcosm-cc/bluemonday"
 	"golang.org/x/net/html/charset"
@@ -272,6 +273,7 @@ func ActionIcon(opType int) string {
 func ActionContent2Commits(act Actioner) *models.PushCommits {
 	push := models.NewPushCommits()
 	if err := jsoniter.Unmarshal([]byte(act.GetContent()), push); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
 	}
 	return push

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
public/css/gitote.min.css


+ 5 - 0
public/less/_user.less

@@ -183,6 +183,11 @@
 	color: #00bcd4;
 }
 
+.gitotedeveloper {
+	font-size: 16px;
+	color: #21ba45;
+}
+
 .bughunter {
 	font-size: 16px;
 	color: #ff0000;

+ 3 - 5
public/robots.txt

@@ -29,8 +29,10 @@
 #
 #
 
-User-agent: *
+User-agent: Googlebot
 Allow: /humans.txt
+Allow: .js
+Allow: .css
 Allow: /
 Allow: /login
 Allow: /join
@@ -58,8 +60,4 @@ Disallow: /*/*/issues?
 Disallow: /*/*/compare
 Disallow: /*?
 
-User-Agent: Googlebot
-Allow: .js
-Allow: .css
-
 Sitemap: https://gitote.in/sitemap.xml

+ 7 - 0
routes/admin/admin.go

@@ -21,6 +21,7 @@ import (
 const (
 	DASHBOARD = "admin/dashboard"
 	ANALYTICS = "admin/analytics"
+	API       = "admin/api"
 	CONFIG    = "admin/config"
 	MONITOR   = "admin/monitor"
 	STATUS    = "admin/status"
@@ -264,3 +265,9 @@ func Analytics(c *context.Context) {
 	c.Data["PageIsAdminAnalytics"] = true
 	c.HTML(200, ANALYTICS)
 }
+
+func AdminAPI(c *context.Context) {
+	c.Data["Title"] = "API"
+	c.Data["PageIsAdminAPI"] = true
+	c.HTML(200, API)
+}

+ 9 - 7
routes/admin/users.go

@@ -72,13 +72,13 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
 	}
 
 	u := &models.User{
-		Name:         f.UserName,
-		Email:        f.Email,
-		Passwd:       f.Password,
-		ThemeColor:   "#161616",
-		IsActive:     true,
-		ShowSponsors: true,
-		LoginType:    models.LOGIN_PLAIN,
+		Name:       f.UserName,
+		Email:      f.Email,
+		Passwd:     f.Password,
+		ThemeColor: "#161616",
+		IsActive:   true,
+		ShowAds:    true,
+		LoginType:  models.LOGIN_PLAIN,
 	}
 
 	if len(f.LoginType) > 0 {
@@ -216,6 +216,7 @@ func EditUserPost(c *context.Context, f form.AdminEditUser) {
 	u.IsVerified = f.IsVerified
 	u.IsMaker = f.IsMaker
 	u.IsBugHunter = f.IsBugHunter
+	u.GitoteDeveloper = f.GitoteDeveloper
 	u.IsBeta = f.IsBeta
 	u.IsStaff = f.IsStaff
 	u.IsIntern = f.IsIntern
@@ -229,6 +230,7 @@ func EditUserPost(c *context.Context, f form.AdminEditUser) {
 	u.Reddit = f.Reddit
 	u.Telegram = f.Telegram
 	u.Codepen = f.Codepen
+	u.Gitlab = f.Gitlab
 
 	if err := models.UpdateUser(u); err != nil {
 		if models.IsErrEmailAlreadyUsed(err) {

+ 3 - 0
routes/api/v1/repo/repo.go

@@ -9,6 +9,7 @@ import (
 	"gitote/gitote/routes/api/v1/convert"
 	"path"
 
+	raven "github.com/getsentry/raven-go"
 	api "gitlab.com/gitote/go-gitote-client"
 	log "gopkg.in/clog.v1"
 )
@@ -165,6 +166,7 @@ func CreateUserRepo(c *context.APIContext, owner *models.User, opt api.CreateRep
 		} else {
 			if repo != nil {
 				if err = models.DeleteRepository(c.User.ID, repo.ID); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "DeleteRepository: %v", err)
 				}
 			}
@@ -266,6 +268,7 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 	if err != nil {
 		if repo != nil {
 			if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "DeleteRepository: %v", errDelete)
 			}
 		}

+ 26 - 5
routes/home.go

@@ -10,14 +10,26 @@ import (
 )
 
 const (
-	HOME                  = "home"
-	EXPLORE_HOME          = "explore/home"
-	EXPLORE_REPOS         = "explore/repos"
-	EXPLORE_USERS         = "explore/users"
-	EXPLORE_TRENDING      = "explore/trending"
+	// HOME page template
+	HOME = "home"
+
+	// EXPLORE_HOME page template
+	EXPLORE_HOME = "explore/home"
+
+	// EXPLORE_REPOS page template
+	EXPLORE_REPOS = "explore/repos"
+
+	// EXPLORE_USERS page template
+	EXPLORE_USERS = "explore/users"
+
+	// EXPLORE_TRENDING page template
+	EXPLORE_TRENDING = "explore/trending"
+
+	// EXPLORE_ORGANIZATIONS page template
 	EXPLORE_ORGANIZATIONS = "explore/organizations"
 )
 
+// Home shows the home page
 func Home(c *context.Context) {
 	if c.IsLogged {
 		if !c.User.IsActive && setting.Service.RegisterEmailConfirm {
@@ -40,6 +52,7 @@ func Home(c *context.Context) {
 	c.Success(HOME)
 }
 
+// ExploreHome shows explore page
 func ExploreHome(c *context.Context) {
 	c.Data["Title"] = c.Tr("explore")
 	c.Data["PageIsExplore"] = true
@@ -75,6 +88,7 @@ func ExploreHome(c *context.Context) {
 	c.Success(EXPLORE_HOME)
 }
 
+// ExploreRepos shows explore repositories page
 func ExploreRepos(c *context.Context) {
 	c.Data["Title"] = c.Tr("explore")
 	c.Data["PageIsExplore"] = true
@@ -86,6 +100,7 @@ func ExploreRepos(c *context.Context) {
 	}
 
 	keyword := c.Query("q")
+	// Search a repository
 	repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
 		Keyword:  keyword,
 		UserID:   c.UserID(),
@@ -110,6 +125,7 @@ func ExploreRepos(c *context.Context) {
 	c.Success(EXPLORE_REPOS)
 }
 
+// ExploreTrending shows trending repositories page
 func ExploreTrending(c *context.Context) {
 	c.Data["Title"] = c.Tr("explore.trending")
 	c.Data["PageIsTrending"] = true
@@ -134,6 +150,7 @@ func ExploreTrending(c *context.Context) {
 	c.Success(EXPLORE_TRENDING)
 }
 
+// UserSearchOptions is the structure of the options choosed by the user
 type UserSearchOptions struct {
 	Type     models.UserType
 	Counter  func() int64
@@ -143,6 +160,7 @@ type UserSearchOptions struct {
 	TplName  string
 }
 
+// RenderUserSearch renders user search
 func RenderUserSearch(c *context.Context, opts *UserSearchOptions) {
 	page := c.QueryInt("page")
 	if page <= 1 {
@@ -184,6 +202,7 @@ func RenderUserSearch(c *context.Context, opts *UserSearchOptions) {
 	c.Success(opts.TplName)
 }
 
+// ExploreUsers shows explore users page
 func ExploreUsers(c *context.Context) {
 	c.Data["Title"] = c.Tr("explore")
 	c.Data["PageIsExplore"] = true
@@ -199,6 +218,7 @@ func ExploreUsers(c *context.Context) {
 	})
 }
 
+//ExploreOrganizations shows explore organizations page
 func ExploreOrganizations(c *context.Context) {
 	c.Data["Title"] = c.Tr("explore")
 	c.Data["PageIsExplore"] = true
@@ -214,6 +234,7 @@ func ExploreOrganizations(c *context.Context) {
 	})
 }
 
+// NotFound renders 404 page if page not found
 func NotFound(c *context.Context) {
 	c.Data["Title"] = "Page Not Found"
 	c.NotFound()

+ 9 - 0
routes/install.go

@@ -19,6 +19,7 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-xorm/xorm"
 	"gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
@@ -27,9 +28,11 @@ import (
 )
 
 const (
+	// INSTALL page template
 	INSTALL = "install"
 )
 
+// checkRunMode check if it is runned or not in production
 func checkRunMode() {
 	if setting.ProdMode {
 		macaron.Env = macaron.PROD
@@ -40,6 +43,7 @@ func checkRunMode() {
 	log.Info("Run Mode: %s", strings.Title(macaron.Env))
 }
 
+// NewServices initializes new services
 func NewServices() {
 	setting.NewServices()
 	mailer.NewContext()
@@ -57,6 +61,7 @@ func GlobalInit() {
 		highlight.NewContext()
 		markup.NewSanitizer()
 		if err := models.NewEngine(); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Fatal(2, "Fail to initialize ORM engine: %v", err)
 		}
 		models.HasEngine = true
@@ -96,6 +101,7 @@ func GlobalInit() {
 	}
 }
 
+// InstallInit initialize the install
 func InstallInit(c *context.Context) {
 	if setting.InstallLock {
 		c.NotFound()
@@ -112,6 +118,7 @@ func InstallInit(c *context.Context) {
 	c.Data["DbOptions"] = dbOpts
 }
 
+// Install shows install page
 func Install(c *context.Context) {
 	f := form.Install{}
 
@@ -172,6 +179,7 @@ func Install(c *context.Context) {
 	c.Success(INSTALL)
 }
 
+// InstallPost install gitote
 func InstallPost(c *context.Context, f form.Install) {
 	c.Data["CurDbOption"] = f.DbType
 
@@ -292,6 +300,7 @@ func InstallPost(c *context.Context, f form.Install) {
 	if com.IsFile(setting.CustomConf) {
 		// Keeps custom settings if there is already something.
 		if err := cfg.Append(setting.CustomConf); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Fail to load custom conf '%s': %v", setting.CustomConf, err)
 		}
 	}

+ 2 - 0
routes/org/members.go

@@ -7,6 +7,7 @@ import (
 	"gitote/gitote/pkg/setting"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 )
 
@@ -82,6 +83,7 @@ func MembersAction(c *context.Context) {
 	}
 
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(4, "Action(%s): %v", c.Params(":action"), err)
 		c.JSON(200, map[string]interface{}{
 			"ok":  false,

+ 3 - 0
routes/org/teams.go

@@ -8,6 +8,7 @@ import (
 	"path"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 )
 
@@ -99,6 +100,7 @@ func TeamsAction(c *context.Context) {
 		if models.IsErrLastOrgOwner(err) {
 			c.Flash.Error(c.Tr("form.last_org_owner"))
 		} else {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(3, "Action(%s): %v", c.Params(":action"), err)
 			c.JSON(200, map[string]interface{}{
 				"ok":  false,
@@ -145,6 +147,7 @@ func TeamsRepoAction(c *context.Context) {
 	}
 
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(3, "Action(%s): '%s' %v", c.Params(":action"), c.Org.Team.Name, err)
 		c.Handle(500, "TeamsRepoAction", err)
 		return

+ 3 - 0
routes/repo/branch.go

@@ -6,6 +6,7 @@ import (
 	"gitote/gitote/pkg/tool"
 	"time"
 
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/gitote/git-module"
 	api "gitlab.com/gitote/go-gitote-client"
 	log "gopkg.in/clog.v1"
@@ -119,6 +120,7 @@ func DeleteBranchPost(c *context.Context) {
 	if len(commitID) > 0 {
 		branchCommitID, err := c.Repo.GitRepo.GetBranchCommitID(branchName)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Failed to get commit ID of branch %q: %v", branchName, err)
 			return
 		}
@@ -132,6 +134,7 @@ func DeleteBranchPost(c *context.Context) {
 	if err := c.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
 		Force: true,
 	}); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Failed to delete branch %q: %v", branchName, err)
 		return
 	}

+ 2 - 0
routes/repo/editor.go

@@ -13,6 +13,7 @@ import (
 	"path"
 	"strings"
 
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
 )
@@ -84,6 +85,7 @@ func editFile(c *context.Context, isNewFile bool) {
 		buf = append(buf, d...)
 		if err, content := template.ToUTF8WithErr(buf); err != nil {
 			if err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "ToUTF8WithErr: %v", err)
 			}
 			c.Data["FileContent"] = string(buf)

+ 4 - 0
routes/repo/http.go

@@ -19,6 +19,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	log "gopkg.in/clog.v1"
 	"gopkg.in/macaron.v1"
 )
@@ -265,6 +266,7 @@ func serviceRPC(h serviceHandler, service string) {
 	if h.r.Header.Get("Content-Encoding") == "gzip" {
 		reqBody, err = gzip.NewReader(reqBody)
 		if err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "HTTP.Get: fail to create gzip reader: %v", err)
 			h.w.WriteHeader(http.StatusInternalServerError)
 			return
@@ -288,6 +290,7 @@ func serviceRPC(h serviceHandler, service string) {
 	cmd.Stderr = &stderr
 	cmd.Stdin = reqBody
 	if err = cmd.Run(); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "HTTP.serviceRPC: fail to serve RPC '%s': %v - %s", service, err, stderr.String())
 		h.w.WriteHeader(http.StatusInternalServerError)
 		return
@@ -316,6 +319,7 @@ func gitCommand(dir string, args ...string) []byte {
 	cmd.Dir = dir
 	out, err := cmd.Output()
 	if err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, fmt.Sprintf("Git: %v - %s", err, out))
 	}
 	return out

+ 2 - 0
routes/repo/issue.go

@@ -18,6 +18,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/yoginth/paginater"
 	log "gopkg.in/clog.v1"
 )
@@ -889,6 +890,7 @@ func NewComment(c *context.Context, f form.CreateComment) {
 				c.Flash.Info(c.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
 			} else {
 				if err = issue.ChangeStatus(c.User, c.Repo.Repository, f.Status == "close"); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "ChangeStatus: %v", err)
 				} else {
 					log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)

+ 3 - 0
routes/repo/repo.go

@@ -13,6 +13,7 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
 )
@@ -133,6 +134,7 @@ func CreatePost(c *context.Context, f form.CreateRepo) {
 
 	if repo != nil {
 		if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "DeleteRepository: %v", errDelete)
 		}
 	}
@@ -205,6 +207,7 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
 
 	if repo != nil {
 		if errDelete := models.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(4, "DeleteRepository: %v", errDelete)
 		}
 	}

+ 3 - 0
routes/repo/setting.go

@@ -14,6 +14,7 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/gitote/git-module"
 	log "gopkg.in/clog.v1"
 )
@@ -96,6 +97,7 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
 
 		if isNameChanged {
 			if err := models.RenameRepoAction(c.User, oldRepoName, repo); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "RenameRepoAction: %v", err)
 			}
 		}
@@ -406,6 +408,7 @@ func ChangeCollaborationAccessMode(c *context.Context) {
 	if err := c.Repo.Repository.ChangeCollaborationAccessMode(
 		c.QueryInt64("uid"),
 		models.AccessMode(c.QueryInt("mode"))); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "ChangeCollaborationAccessMode: %v", err)
 		return
 	}

+ 2 - 0
routes/repo/view.go

@@ -15,6 +15,7 @@ import (
 	"path"
 	"strings"
 
+	raven "github.com/getsentry/raven-go"
 	"gitlab.com/gitote/git-module"
 	"gitlab.com/yoginth/paginater"
 	log "gopkg.in/clog.v1"
@@ -170,6 +171,7 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 			var fileContent string
 			if err, content := template.ToUTF8WithErr(buf); err != nil {
 				if err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(4, "ToUTF8WithErr: %s", err)
 				}
 				fileContent = string(buf)

+ 27 - 6
routes/user/auth.go

@@ -11,6 +11,7 @@ import (
 	"gitote/gitote/pkg/tool"
 	"net/url"
 
+	raven "github.com/getsentry/raven-go"
 	"github.com/go-macaron/captcha"
 	log "gopkg.in/clog.v1"
 )
@@ -68,6 +69,7 @@ func AutoLogin(c *context.Context) (bool, error) {
 	return true, nil
 }
 
+//Login is the handler for "/login"
 func Login(c *context.Context) {
 	c.Title("sign_in")
 
@@ -116,6 +118,7 @@ func Login(c *context.Context) {
 	c.Success(LOGIN)
 }
 
+//afterLogin set sessions cookies and redirect to "/"
 func afterLogin(c *context.Context, u *models.User, remember bool) {
 	if remember {
 		days := 86400 * setting.LoginRememberDays
@@ -144,6 +147,7 @@ func afterLogin(c *context.Context, u *models.User, remember bool) {
 	c.SubURLRedirect("/")
 }
 
+//LoginPost is the POST handler for "/login"
 func LoginPost(c *context.Context, f form.SignIn) {
 	c.Title("sign_in")
 
@@ -196,6 +200,7 @@ func LoginPost(c *context.Context, f form.SignIn) {
 	c.SubURLRedirect("/login/two_factor")
 }
 
+//LoginTwoFactor is the handler for "/login" with two factors authentication
 func LoginTwoFactor(c *context.Context) {
 	_, ok := c.Session.Get("twoFactorUserID").(int64)
 	if !ok {
@@ -206,6 +211,7 @@ func LoginTwoFactor(c *context.Context) {
 	c.Success(TWO_FACTOR)
 }
 
+//LoginTwoFactorPost is the POST handler for "/login" with two factors authentication
 func LoginTwoFactorPost(c *context.Context) {
 	userID, ok := c.Session.Get("twoFactorUserID").(int64)
 	if !ok {
@@ -243,12 +249,14 @@ func LoginTwoFactorPost(c *context.Context) {
 		return
 	}
 	if err = c.Cache.Put(u.TwoFactorCacheKey(passcode), 1, 60); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Failed to put cache 'two factor passcode': %v", err)
 	}
 
 	afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool))
 }
 
+//LoginTwoFactorRecoveryCode is the handler to recover Two Factor Code
 func LoginTwoFactorRecoveryCode(c *context.Context) {
 	_, ok := c.Session.Get("twoFactorUserID").(int64)
 	if !ok {
@@ -259,6 +267,7 @@ func LoginTwoFactorRecoveryCode(c *context.Context) {
 	c.Success(TWO_FACTOR_RECOVERY_CODE)
 }
 
+//LoginTwoFactorRecoveryCodePost is the POST handler to recover Two Factor Code
 func LoginTwoFactorRecoveryCodePost(c *context.Context) {
 	userID, ok := c.Session.Get("twoFactorUserID").(int64)
 	if !ok {
@@ -284,6 +293,7 @@ func LoginTwoFactorRecoveryCodePost(c *context.Context) {
 	afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool))
 }
 
+//SignOut is the handler for "/user/logout"
 func SignOut(c *context.Context) {
 	c.Session.Delete("uid")
 	c.Session.Delete("uname")
@@ -293,6 +303,7 @@ func SignOut(c *context.Context) {
 	c.SubURLRedirect("/")
 }
 
+//SignUp is the handler for "/signup"
 func SignUp(c *context.Context) {
 	c.Title("sign_up")
 
@@ -307,6 +318,7 @@ func SignUp(c *context.Context) {
 	c.Success(SIGNUP)
 }
 
+//SignUpPost is the POST handler for "/join"
 func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 	c.Title("sign_up")
 
@@ -335,12 +347,12 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 	}
 
 	u := &models.User{
-		Name:         f.UserName,
-		Email:        f.Email,
-		Passwd:       f.Password,
-		ThemeColor:   "#161616",
-		ShowSponsors: true,
-		IsActive:     !setting.Service.RegisterEmailConfirm,
+		Name:       f.UserName,
+		Email:      f.Email,
+		Passwd:     f.Password,
+		ThemeColor: "#161616",
+		ShowAds:    true,
+		IsActive:   !setting.Service.RegisterEmailConfirm,
 	}
 	if err := models.CreateUser(u); err != nil {
 		switch {
@@ -385,6 +397,7 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 		c.Success(ACTIVATE)
 
 		if err := c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Failed to put cache key 'mail resend': %v", err)
 		}
 		return
@@ -393,6 +406,7 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
 	c.SubURLRedirect("/login")
 }
 
+//Activate activates the code
 func Activate(c *context.Context) {
 	code := c.Query("code")
 	if len(code) == 0 {
@@ -410,6 +424,7 @@ func Activate(c *context.Context) {
 				mailer.SendActivateAccountMail(c.Context, models.NewMailerUser(c.User))
 
 				if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil {
+					raven.CaptureErrorAndWait(err, nil)
 					log.Error(2, "Failed to put cache key 'mail resend': %v", err)
 				}
 			}
@@ -445,6 +460,7 @@ func Activate(c *context.Context) {
 	c.Success(ACTIVATE)
 }
 
+//ActivateEmail is the handler to activate the user email
 func ActivateEmail(c *context.Context) {
 	code := c.Query("code")
 	email_string := c.Query("email")
@@ -463,6 +479,7 @@ func ActivateEmail(c *context.Context) {
 	return
 }
 
+//ForgotPasswd is the handler for "/user/forget_password"
 func ForgotPasswd(c *context.Context) {
 	c.Title("auth.forgot_password")
 
@@ -476,6 +493,7 @@ func ForgotPasswd(c *context.Context) {
 	c.Success(FORGOT_PASSWORD)
 }
 
+//ForgotPasswdPost is the POST handler for "/user/forget_password"
 func ForgotPasswdPost(c *context.Context) {
 	c.Title("auth.forgot_password")
 
@@ -515,6 +533,7 @@ func ForgotPasswdPost(c *context.Context) {
 
 	mailer.SendResetPasswordMail(c.Context, models.NewMailerUser(u))
 	if err = c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil {
+		raven.CaptureErrorAndWait(err, nil)
 		log.Error(2, "Failed to put cache key 'mail resend': %v", err)
 	}
 
@@ -523,6 +542,7 @@ func ForgotPasswdPost(c *context.Context) {
 	c.Success(FORGOT_PASSWORD)
 }
 
+//ResetPasswd is the handler to reset your password
 func ResetPasswd(c *context.Context) {
 	c.Title("auth.reset_password")
 
@@ -536,6 +556,7 @@ func ResetPasswd(c *context.Context) {
 	c.Success(RESET_PASSWORD)
 }
 
+//ResetPasswdPost is the POST handler to reset your password
 func ResetPasswdPost(c *context.Context) {
 	c.Title("auth.reset_password")
 

+ 0 - 2
routes/user/certificate.go

@@ -11,8 +11,6 @@ const (
 
 func InternCertificate(c *context.Context) {
 	ctxUser := GetUserByName(c, strings.TrimSuffix(c.Params(":username"), ""))
-
-	c.Data["PageIsUserProfile"] = true
 	c.Data["Owner"] = ctxUser
 
 	c.HTML(200, INTERNCERTIFICATE)

+ 7 - 2
routes/user/setting.go

@@ -17,6 +17,7 @@ import (
 	"strings"
 
 	"github.com/Unknwon/com"
+	raven "github.com/getsentry/raven-go"
 	"github.com/pquerna/otp"
 	"github.com/pquerna/otp/totp"
 	log "gopkg.in/clog.v1"
@@ -55,7 +56,7 @@ func Settings(c *context.Context) {
 	c.Data["status"] = c.User.Status
 	c.Data["themecolor"] = c.User.ThemeColor
 	c.Data["is_beta"] = c.User.IsBeta
-	c.Data["show_sponsors"] = c.User.ShowSponsors
+	c.Data["show_ads"] = c.User.ShowAds
 	c.Data["private_email"] = c.User.PrivateEmail
 	c.Success(SETTINGS_PROFILE)
 }
@@ -112,7 +113,7 @@ func SettingsPost(c *context.Context, f form.UpdateProfile) {
 	c.User.Status = f.Status
 	c.User.ThemeColor = f.ThemeColor
 	c.User.IsBeta = f.IsBeta
-	c.User.ShowSponsors = f.ShowSponsors
+	c.User.ShowAds = f.ShowAds
 	c.User.PrivateEmail = f.PrivateEmail
 
 	if err := models.UpdateUser(c.User); err != nil {
@@ -136,6 +137,7 @@ func SettingsSocial(c *context.Context) {
 	c.Data["reddit"] = c.User.Reddit
 	c.Data["telegram"] = c.User.Telegram
 	c.Data["codepen"] = c.User.Codepen
+	c.Data["gitlab"] = c.User.Gitlab
 	c.Success(SETTINGS_SOCIAL)
 }
 
@@ -157,6 +159,7 @@ func SettingsSocialPost(c *context.Context, f form.UpdateSocial) {
 	c.User.Reddit = f.Reddit
 	c.User.Telegram = f.Telegram
 	c.User.Codepen = f.Codepen
+	c.User.Gitlab = f.Gitlab
 	if err := models.UpdateUser(c.User); err != nil {
 		c.ServerError("UpdateUser", err)
 		return
@@ -196,6 +199,7 @@ func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *models.User
 		// generate a random one when needed.
 		if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
 			if err := ctxUser.GenerateRandomAvatar(); err != nil {
+				raven.CaptureErrorAndWait(err, nil)
 				log.Error(2, "generate random avatar [%d]: %v", ctxUser.ID, err)
 			}
 		}
@@ -330,6 +334,7 @@ func SettingsEmailPost(c *context.Context, f form.AddEmail) {
 		mailer.SendActivateEmailMail(c.Context, models.NewMailerUser(c.User), email.Email)
 
 		if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil {
+			raven.CaptureErrorAndWait(err, nil)
 			log.Error(2, "Set cache 'MailResendLimit' failed: %v", err)
 		}
 		c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60))

+ 25 - 0
routes/user/sitemap.go

@@ -0,0 +1,25 @@
+package user
+
+import (
+	"encoding/xml"
+	"net/http"
+)
+
+type Urlset struct {
+	Loc        string `xml:"url>loc"`
+	Changefreq string `xml:"url>changefreq"`
+	Priority   string `xml:"url>priority"`
+}
+
+func Sitemap(w http.ResponseWriter, r *http.Request) {
+	profile := Urlset{"https://gitote.in/", "Daily", "1.0"}
+
+	x, err := xml.MarshalIndent(profile, "", "  ")
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/xml")
+	w.Write(x)
+}

+ 65 - 0
templates/admin/api.tmpl

@@ -0,0 +1,65 @@
+{{template "base/head" .}}
+<div class="admin monitor">
+	<div class="ui container">
+		<div class="ui grid">
+			{{template "admin/navbar" .}}
+			<div class="twelve wide column content">
+				{{template "base/alert" .}}
+				<h4 class="ui top attached header">
+					API 📲 <span class="ui basic green label small">Beta</span>
+				</h4>
+                <div class="ui attached segment">
+                    <h3>
+                        Version - <a href="{{AppURL}}api/{{APIVer}}/users/gitote">{{APIVer}}</a>
+                    </h3>
+                </div>
+
+                <h4 class="ui top attached header">
+					User
+				</h4>
+                <div class="ui attached segment">
+                    <h3>
+                        Soon
+                    </h3>
+                </div>
+
+                <h4 class="ui top attached header">
+					Organization
+				</h4>
+                <div class="ui attached segment">
+                    <h3>
+                        Soon
+                    </h3>
+                </div>
+
+                <h4 class="ui top attached header">
+					Repository
+				</h4>
+                <div class="ui attached segment">
+                    <h3>
+                        Soon
+                    </h3>
+                </div>
+
+                <h4 class="ui top attached header">
+					Issues
+				</h4>
+                <div class="ui attached segment">
+                    <h3>
+                        Soon
+                    </h3>
+                </div>
+
+                <h4 class="ui top attached header">
+					Miscellaneous
+				</h4>
+                <div class="ui attached segment">
+                    <h3>
+                        Soon
+                    </h3>
+                </div>
+			</div>
+		</div>
+	</div>
+</div>
+{{template "base/footer" .}}

+ 3 - 1
templates/admin/dashboard.tmpl

@@ -18,7 +18,9 @@
 						<br/>
 						GoLang - <a href="https://golang.org">{{GoVer}}</a>
 						<br/>
-						PostgreSQL - <a href="https://www.postgresql.org">{{"9.8.0"}}</a>
+						Macaron - <a href="https://golang.org">ToDo</a>
+						<br/>
+						PostgreSQL - <a href="https://www.postgresql.org">ToDo</a>
 					</h3>
 					<img style="margin-top:-9em;margin-right:1em" class="ui image tiny right" src="https://cdn.jsdelivr.net/npm/gitote@1.2.1/logo.png">
 				</div>

+ 4 - 0
templates/admin/navbar.tmpl

@@ -27,6 +27,10 @@
 		<a class="{{if .PageIsAdminConfig}}active{{end}} item" href="{{AppSubURL}}/admin/config">
 			🛠️ Configuration
 		</a>
+		<a class="{{if .PageIsAdminAPI}}active{{end}} item" href="{{AppSubURL}}/admin/api">
+			📲 API
+			<span class="ui green basic label small">Beta</span>
+		</a>
 		<a class="{{if .PageIsAdminNotices}}active{{end}} item" href="{{AppSubURL}}/admin/notices">
 			📜 System Notices
 		</a>

+ 10 - 0
templates/admin/user/edit.tmpl

@@ -78,6 +78,10 @@
 							<label for="github">GitHub</label>
 							<input id="github" name="github" value="{{.User.Github}}">
 						</div>
+						<div class="field {{if .Err_Gitlab}}error{{end}}">
+							<label for="gitlab">GitLab</label>
+							<input id="gitlab" name="gitlab" value="{{.User.Gitlab}}">
+						</div>
 						<div class="field {{if .Err_Devto}}error{{end}}">
 							<label for="devto">Dev.to</label>
 							<input id="devto" name="devto" value="{{.User.Devto}}">
@@ -157,6 +161,12 @@
 								<input name="is_bug_hunter" type="checkbox" {{if .User.IsBugHunter}}checked{{end}}>
 							</div>
 						</div>
+						<div class="inline field">
+							<div class="ui checkbox">
+								<label><strong>This user is Gitote Developer</strong></label>
+								<input name="gitote_developer" type="checkbox" {{if .User.GitoteDeveloper}}checked{{end}}>
+							</div>
+						</div>
 						<div class="inline field">
 							<div class="ui checkbox">
 								<label><strong>This account is Beta Member</strong> <i class="developer octicon octicon-circuit-board"></i></label>

+ 1 - 0
templates/admin/user/list.tmpl

@@ -41,6 +41,7 @@
 										{{if .IsBeta}}<i class="admin-badge developer octicon octicon-circuit-board"></i>{{end}}
 										{{if .IsStaff}}<i class="admin-badge isstaff octicon octicon-tools"></i>{{end}}
 										{{if .IsIntern}}<i class="admin-badge isintern octicon octicon-mortar-board"></i>{{end}}
+										{{if .GitoteDeveloper}}<i class="admin-badge gitotedeveloper octicon octicon-jersey"></i>{{end}}
 									</td>
 									<td><span class="text truncate email">{{.Email}}</span></td>
 									<td>{{.NumRepos}}</td>

+ 2 - 2
templates/base/head.tmpl

@@ -53,7 +53,7 @@
 	<script src="https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.0.4/jscolor.min.js"></script>
 	{{end}}
 
-	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitote@1.2.2/css/bundle.min.css">
+	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitote@1.2.4/css/bundle.min.css">
 	<link rel="stylesheet" href="{{AppSubURL}}/css/gitote.min.css?v={{MD5 AppVer}}">
 	{{if .IsBeta}}
 		<link rel="stylesheet" href="{{AppSubURL}}/css/gitote.{{if .IsBeta}}night{{end}}.min.css?v={{MD5 AppVer}}">
@@ -122,7 +122,7 @@
 
 								<a class="navbar bold main-menu item{{if .PageIsExplore}} nav-active{{end}}" href="{{AppSubURL}}/trending"><i class="menu-icon octicon">🌐</i>{{.i18n.Tr "explore"}}</a>
 
-								<form class="search-mobile item" action="{{AppSubURL}}/trending" method="get">
+								<form class="search-mobile item" action="{{AppSubURL}}/explore/repos" method="get">
 									<div class="ui icon input">
 										<input class="searchbox" type="text" name="q" placeholder="{{.i18n.Tr "explore.search"}}..." autocomplete="off" required>
 										<i class="search icon"></i>

+ 9 - 0
templates/base/meta.tmpl

@@ -4,7 +4,16 @@
 <meta name="_suburl" content="{{AppSubURL}}" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
 <meta name="hostname" content="{{AppURL}}">
+
+{{if .PageIsUserProfile}}
+<link rel="canonical" href="{{.Owner.HTMLURL}}" />
+{{else if .PageIsIssueConversation}}
+<link rel="canonical" href="{{$.Link}}/{{.Index}}" />
+{{else if .Repository}}
+<link rel="canonical" href="{{.Repository.HTMLURL}}" />
+{{else}}
 <link rel="canonical" href="{{AppURL}}" />
+{{end}}
 
 {{if .PageIsAdmin}}
 {{else if .PageIsUserProfile}}

+ 4 - 0
templates/misc/ad.tmpl

@@ -0,0 +1,4 @@
+{{if .Owner.ShowAds}}
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitote@1.2.5/css/ads.css">
+    <script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CK7D52J7&placement=gitotein" id="_carbonads_js"></script>
+{{end}}

+ 0 - 36
templates/misc/sponsor.tmpl

@@ -1,36 +0,0 @@
-{{if .Owner.ShowSponsors}}
-    <h4 class="ui top attached header">
-        <i class="menu-icon octicon">❤️</i> Community Sponsors
-    </h4>
-    <div class="ui attached table segment dashboard sponsor">
-        <center>
-            <img class="ui center image small" src="https://cdn.jsdelivr.net/npm/gitote@1.1.3/img/sponsors/digitalocean.svg">
-            <h5>
-                The all-in-one cloud platform developers & their teams love.
-            </h5>
-            <a href="https://www.digitalocean.com">
-                <h5>Learn more</h5>
-            </a>
-        </center>
-
-        <center>
-            <img class="ui center image small" src="https://cdn.jsdelivr.net/npm/gitote@1.1.1/img/sponsors/browserstack.png">
-            <h5>
-                Most Reliable Mobile App & Browser Testing Platform.
-            </h5>
-            <a href="https://www.browserstack.com">
-                <h5>Learn more</h5>
-            </a>
-        </center>
-
-        <center class="dashboard sponsor divider">
-            <span>
-                We are grateful for wonderful sponsors who help sustain the Gitote Platform.
-            </span>
-            <br/>
-            <a href="{{AppURL}}sponsorship">
-                <div class="sponsor button">Sponsorship Info</div>
-            </a>
-        </center>
-    </div>
-{{end}}

+ 0 - 2
templates/org/member/members.tmpl

@@ -21,8 +21,6 @@
 						<div class="meta">
 							{{.FullName}}
 							{{if .IsVerified}}<a class="poping up" target="_blank" href="/verified" data-content="Verified" data-variation="inverted tiny" data-position="right center"><i class="verified octicon octicon-verified"></i></a>{{end}}
-							{{if .IsMaker}}<a class="poping up" target="_blank" href="/maker" data-content="Maker" data-variation="inverted tiny" data-position="right center"><i class="maker octicon octicon-paintcan"></i></a>{{end}}
-							{{if .IsBugHunter}}<a class="poping up" target="_blank" href="/security" data-content="Bug Hunter" data-variation="inverted tiny" data-position="right center"><i class="bughunter octicon octicon-gist-secret"></i></a>{{end}}
 						</div>	
 					</div>
 					<div class="ui five wide column center">

+ 1 - 1
templates/pages/verified.tmpl

@@ -6,7 +6,7 @@
     <div class="pages body">
         <h2>About verified accounts</h2>
         <p class="pages-small">
-            The blue verified badge <i class="verified octicon octicon-verified"></i> on Gitote lets people 
+            The green verified badge <i class="verified octicon octicon-verified"></i> on Gitote lets people 
             know that an account of public interest is authentic.
         </p>
         <p class="pages-small">

+ 1 - 1
templates/user/auth/two_factor.tmpl

@@ -15,7 +15,7 @@
 					<div class="required field">
 						<label for="passcode">{{.i18n.Tr "auth.login_two_factor_passcode"}}</label>
 						<div class="ui fluid input">
-							<input id="passcode" name="passcode" autofocus required>
+							<input id="passcode" name="passcode" autocomplete="off" autofocus required>
 						</div>
 					</div>
 

+ 1 - 1
templates/user/auth/two_factor_recovery_code.tmpl

@@ -15,7 +15,7 @@
 					<div class="required field">
 						<label for="recovery_code">{{.i18n.Tr "auth.login_two_factor_recovery_code"}}</label>
 						<div class="ui fluid input">
-							<input id="recovery_code" name="recovery_code" autofocus required>
+							<input id="recovery_code" name="recovery_code" autocomplete="off" autofocus required>
 						</div>
 					</div>
 

+ 0 - 4
templates/user/dashboard/dashboard.tmpl

@@ -56,9 +56,6 @@
 								<a style="color:#212121" href="{{AppSubURL}}/{{.LoggedUser.Name}}">
 									{{.Owner.FullName}}
 									{{if .Owner.IsVerified}}<a class="poping up" target="_blank" href="/verified" data-content="Verified" data-variation="inverted tiny" data-position="right center"><i class="verified octicon octicon-verified"></i></a>{{end}}
-									{{if .Owner.IsMaker}}<a class="poping up" target="_blank" href="/maker" data-content="Maker" data-variation="inverted tiny" data-position="right center"><i class="maker octicon octicon-paintcan"></i></a>{{end}}
-									{{if .Owner.IsBugHunter}}<a class="poping up" target="_blank" href="/security" data-content="Bug Hunter" data-variation="inverted tiny" data-position="right center"><i class="bughunter octicon octicon-gist-secret"></i></a>{{end}}
-									{{if .Owner.IsBeta}}<a class="poping up" target="_blank" href="/developer" data-content="Beta Member" data-variation="inverted tiny" data-position="right center"><i class="developer octicon octicon-circuit-board"></i></a>{{end}}
 									{{if .Owner.IsStaff}}<a class="poping up" target="_blank" href="/jobs" data-content="Staff" data-variation="inverted tiny" data-position="right center"><i class="isstaff octicon octicon-tools"></i></a>{{end}}
 								</a>
 							</div>
@@ -251,7 +248,6 @@
 						</ul>
 					</div>
 				</div>
-				{{template "misc/sponsor" .}}
 				{{template "misc/ad" .}}
 
 				<div class="side-footer"> 

+ 19 - 6
templates/user/profile.tmpl

@@ -11,9 +11,6 @@
 		{{if .Owner.FullName}}
 			<span class="mobile-name">{{.Owner.FullName}}</span>
 			{{if .Owner.IsVerified}}<a class="poping up" target="_blank" href="/verified" data-content="Verified" data-variation="inverted tiny" data-position="right center"><i class="verified octicon octicon-verified"></i></a>{{end}}
-			{{if .Owner.IsMaker}}<a class="poping up" target="_blank" href="/maker" data-content="Maker" data-variation="inverted tiny" data-position="right center"><i class="maker octicon octicon-paintcan"></i></a>{{end}}
-			{{if .Owner.IsBugHunter}}<a class="poping up" target="_blank" href="/security" data-content="Bug Hunter" data-variation="inverted tiny" data-position="right center"><i class="bughunter octicon octicon-gist-secret"></i></a>{{end}}
-			{{if .Owner.IsBeta}}<a class="poping up" target="_blank" href="/developer" data-content="Beta Member" data-variation="inverted tiny" data-position="right center"><i class="developer octicon octicon-circuit-board"></i></a>{{end}}
 			{{if .Owner.IsStaff}}<a class="poping up" target="_blank" href="/jobs" data-content="Staff" data-variation="inverted tiny" data-position="right center"><i class="isstaff octicon octicon-tools"></i></a>{{end}}
 		{{end}}
 		</div>
@@ -72,6 +69,14 @@
 				</a>
 			</div>
 		</div>
+		{{if or .Owner.GitoteDeveloper .Owner.IsBeta .Owner.IsMaker .Owner.IsBugHunter}}
+			<div style="margin-top:1rem" class="ui mini images">
+				{{if .Owner.GitoteDeveloper}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/gitotedeveloper.png">{{end}}
+				{{if .Owner.IsBeta}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/beta.png">{{end}}
+				{{if .Owner.IsMaker}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/maker.png">{{end}}
+				{{if .Owner.IsBugHunter}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/bughunter.png">{{end}}
+			</div>
+		{{end}}
 		{{if and .IsLogged (ne .LoggedUserName .Owner.Name)}}
 			<div class="follow">
 				{{if .LoggedUser.IsFollowing .Owner.ID}}
@@ -102,9 +107,6 @@
 							<span class="header text center">
 								{{.Owner.FullName}}
 								{{if .Owner.IsVerified}}<a class="poping up" target="_blank" href="/verified" data-content="Verified" data-variation="inverted tiny" data-position="right center"><i class="verified octicon octicon-verified"></i></a>{{end}}
-								{{if .Owner.IsMaker}}<a class="poping up" target="_blank" href="/maker" data-content="Maker" data-variation="inverted tiny" data-position="right center"><i class="maker octicon octicon-paintcan"></i></a>{{end}}
-								{{if .Owner.IsBugHunter}}<a class="poping up" target="_blank" href="/security" data-content="Bug Hunter" data-variation="inverted tiny" data-position="right center"><i class="bughunter octicon octicon-gist-secret"></i></a>{{end}}
-								{{if .Owner.IsBeta}}<a class="poping up" target="_blank" href="/developer" data-content="Beta Member" data-variation="inverted tiny" data-position="right center"><i class="developer octicon octicon-circuit-board"></i></a>{{end}}
 								{{if .Owner.IsStaff}}<a class="poping up" target="_blank" href="/jobs" data-content="Staff" data-variation="inverted tiny" data-position="right center"><i class="isstaff octicon octicon-tools"></i></a>{{end}}
 							</span>
 						{{end}}
@@ -134,6 +136,7 @@
 									{{if .Owner.Reddit}}<a href="https://reddit.com/user/{{.Owner.Reddit}}" target="_blank"><i class="reddit icon"></i></a>{{end}}
 									{{if .Owner.Telegram}}<a href="https://t.me/{{.Owner.Telegram}}" target="_blank"><i class="telegram icon"></i></a>{{end}}
 									{{if .Owner.Codepen}}<a href="https://codepen.io/{{.Owner.Codepen}}" target="_blank"><i class="codepen icon"></i></a>{{end}}
+									{{if .Owner.Gitlab}}<a href="https://gitlab.com/{{.Owner.Gitlab}}" target="_blank"><i class="gitlab icon"></i></a>{{end}}
 								</li>
 							{{end}}
 							{{if .Owner.Description}}
@@ -190,6 +193,16 @@
 									{{.Owner.NumFollowing}} {{.i18n.Tr "user.following"}}
 								</a>
 							</li>
+							{{if or .Owner.GitoteDeveloper .Owner.IsBeta .Owner.IsMaker .Owner.IsBugHunter}}
+								<li>
+									<div class="ui tiny images">
+										{{if .Owner.GitoteDeveloper}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/gitotedeveloper.png">{{end}}
+										{{if .Owner.IsBeta}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/beta.png">{{end}}
+										{{if .Owner.IsMaker}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/maker.png">{{end}}
+										{{if .Owner.IsBugHunter}}<img class="ui image" src="https://cdn.jsdelivr.net/npm/gitote@1.2.3/img/badges/bughunter.png">{{end}}
+									</div>
+								</li>
+							{{end}}
 							{{if .Orgs}}
 							<li>
 								{{range .Orgs}}

+ 3 - 3
templates/user/settings/profile.tmpl

@@ -58,10 +58,10 @@
 							<input type="text" id="theme_color" name="theme_color" class="jscolor {hash:true} jscolor-active" value="{{.themecolor}}" placeholder="#161616" autocomplete="off">
 						</div>
 						<div class="inline field">
-							<label for="location">Sponsors: </label>
+							<label for="location">Ads: </label>
 							<div class="ui checkbox">
-								<label><strong>{{.i18n.Tr "settings.sponsor"}}</strong></label>
-								<input name="show_sponsors" type="checkbox" {{if .show_sponsors}}checked{{end}}>
+								<label><strong>{{.i18n.Tr "settings.ads"}}</strong></label>
+								<input name="show_ads" type="checkbox" {{if .show_ads}}checked{{end}}>
 							</div>
 						</div>
 						<div class="inline field">

+ 4 - 0
templates/user/settings/social.tmpl

@@ -24,6 +24,10 @@
 							<label for="github">GitHub</label>
 							<input id="github" name="github" value="{{.github}}" placeholder="username">
 						</div>
+						<div class="field">
+							<label for="gitlab">GitLab</label>
+							<input id="gitlab" name="gitlab" value="{{.gitlab}}"  placeholder="username">
+						</div>
 						<div class="field">
 							<label for="devto">Dev.to</label>
 							<input id="devto" name="devto" value="{{.devto}}" placeholder="username">