diff --git a/docs/content/doc/advanced/mail-templates-us.md b/docs/content/doc/advanced/mail-templates-us.md
new file mode 100644
index 0000000000..ffe2d4a27b
--- /dev/null
+++ b/docs/content/doc/advanced/mail-templates-us.md
@@ -0,0 +1,272 @@
+---
+date: "2019-10-23T17:00:00-03:00"
+title: "Mail templates"
+slug: "mail-templates"
+weight: 45
+toc: true
+draft: false
+menu:
+  sidebar:
+    parent: "advanced"
+    name: "Mail templates"
+    weight: 45
+    identifier: "mail-templates"
+---
+
+# Mail templates
+
+To craft the e-mail subject and contents for certain operations, Gitea can be customized by using templates. The templates
+for these functions are located under the [`custom` directory](https://docs.gitea.io/en-us/customizing-gitea/).
+Gitea has an internal template that serves as default in case there's no custom alternative.
+
+Custom templates are loaded when Gitea starts. Changes made to them are not recognized until Gitea is restarted again.
+
+## Mail notifications supporting templates
+
+Currently, the following notification events make use of templates:
+
+| Action name   | Usage                                                                                                        |
+|---------------|--------------------------------------------------------------------------------------------------------------|
+| `new`         | A new issue or pull request was created.                                                                     |
+| `comment`     | A new comment was created in an existing issue or pull request.                                              |
+| `close`       | An issue or pull request was closed.                                                                         |
+| `reopen`      | An issue or pull request was reopened.                                                                       |
+| `review`      | The head comment of a review in a pull request.                                                              |
+| `code`        | A single comment on the code of a pull request.                                                              |
+| `assigned`    | Used was assigned to an issue or pull request.                                                               |
+| `default`     | Any action not included in the above categories, or when the corresponding category template is not present. |
+
+The path for the template of a particular message type is:
+
+```
+custom/templates/mail/{action type}/{action name}.tmpl
+```
+
+Where `{action type}` is one of `issue` or `pull` (for pull requests), and `{action name}` is one of the names listed above.
+
+For example, the specific template for a mail regarding a comment in a pull request is:
+```
+custom/templates/mail/pull/comment.tmpl
+```
+
+However, creating templates for each and every action type/name combination is not required.
+A fallback system is used to choose the appropriate template for an event. The _first existing_
+template on this list is used:
+
+* The specific template for the desired **action type** and **action name**.
+* The template for action type `issue` and the desired **action name**.
+* The template for the desired **action type**, action name `default`.
+* The template for action type `issue`, action name `default`.
+
+The only mandatory template is action type `issue`, action name `default`, which is already embedded in Gitea
+unless it's overridden by the user in the `custom` directory.
+
+## Template syntax
+
+Mail templates are UTF-8 encoded text files that need to follow one of the following formats:
+
+```
+Text and macros for the subject line
+------------
+Text and macros for the mail body
+```
+
+or
+
+```
+Text and macros for the mail body
+```
+
+Specifying a _subject_ section is optional (and therefore also the dash line separator). When used, the separator between
+_subject_ and _mail body_ templates requires at least three dashes; no other characters are allowed in the separator line.
+
+
+_Subject_ and _mail body_ are parsed by [Golang's template engine](https://golang.org/pkg/text/template/) and
+are provided with a _metadata context_ assembled for each notification. The context contains the following elements:
+
+| Name               | Type           | Available     | Usage                                                                                                                                                                                                                                             |
+|--------------------|----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `.FallbackSubject` | string         | Always        | A default subject line. See Below.                                                                                                                                                                                                                |
+| `.Subject`         | string         | Only in body  | The _subject_, once resolved.                                                                                                                                                                                                                     |
+| `.Body`            | string         | Always        | The message of the issue, pull request or comment, parsed from Markdown into HTML and sanitized. Do not confuse with the _mail body_                                                                                                              |
+| `.Link`            | string         | Always        | The address of the originating issue, pull request or comment.                                                                                                                                                                                    |
+| `.Issue`           | models.Issue   | Always        | The issue (or pull request) originating the notification. To get data specific to a pull request (e.g. `HasMerged`), `.Issue.PullRequest` can be used, but care should be taken as this field will be `nil` if the issue is *not* a pull request. |
+| `.Comment`         | models.Comment | If applicable | If the notification is from a comment added to an issue or pull request, this will contain the information about the comment.                                                                                                                     |
+| `.IsPull`          | bool           | Always        | `true` if the mail notification is associated with a pull request (i.e. `.Issue.PullRequest` is not `nil`).                                                                                                                                       |
+| `.Repo`            | string         | Always        | Name of the repository, including owner name (e.g. `mike/stuff`)                                                                                                                                                                                  |
+| `.User`            | models.User    | Always        | Owner of the repository from which the event originated. To get the user name (e.g. `mike`),`.User.Name` can be used.                                                                                                                             |
+| `.Doer`            | models.User    | Always        | User that executed the action triggering the notification event. To get the user name (e.g. `rhonda`), `.Doer.Name` can be used.                                                                                                                  |
+| `.IsMention`       | bool           | Always        | `true` if this notification was only generated because the user was mentioned in the comment, while not being subscribed to the source. It will be `false` if the recipient was subscribed to the issue or repository.                            |
+| `.SubjectPrefix`   | string         | Always        | `Re: ` if the notification is about other than issue or pull request creation; otherwise an empty string.                                                                                                                                         |
+| `.ActionType`      | string         | Always        | `"issue"` or `"pull"`. Will correspond to the actual _action type_ independently of which template was selected.                                                                                                                                  |
+| `.ActionName`      | string         | Always        | It will be one of the action types described above (`new`, `comment`, etc.), and will correspond to the actual _action name_ independently of which template was selected.                                                                        |
+
+All names are case sensitive.
+
+### The _subject_ part of the template
+
+The template engine used for the mail _subject_ is golang's [`text/template`](https://golang.org/pkg/text/template/).
+Please refer to the linked documentation for details about its syntax.
+
+The _subject_ is built using the following steps:
+
+* A template is selected according to the type of notification and to what templates are present.
+* The template is parsed and resolved (e.g. `{{.Issue.Index}}` is converted to the number of the issue
+  or pull request).
+* All space-like characters (e.g. `TAB`, `LF`, etc.) are converted to normal spaces.
+* All leading, trailing and redundant spaces are removed.
+* The string is truncated to its first 256 runes (characters).
+
+If the end result is an empty string, **or** no subject template was available (i.e. the selected template
+did not include a subject part), Gitea's **internal default** will be used.
+
+The internal default (fallback) subject is the equivalent of:
+
+```
+{{.SubjectPrefix}}[{{.Repo}}] {{.Issue.Title}} (#.Issue.Index)
+```
+
+For example: `Re: [mike/stuff] New color palette (#38)`
+
+Gitea's default subject can also be found in the template _metadata_ as `.FallbackSubject` from any of
+the two templates, even if a valid subject template is present.
+
+### The _mail body_ part of the template
+
+The template engine used for the _mail body_ is golang's [`html/template`](https://golang.org/pkg/html/template/).
+Please refer to the linked documentation for details about its syntax.
+
+The _mail body_ is parsed after the mail subject, so there is an additional _metadata_ field which is
+the actual rendered subject, after all considerations.
+
+The expected result is HTML (including structural elements like`<html>`, `<body>`, etc.). Styling
+through `<style>` blocks, `class` and `style` attributes is possible. However, `html/template`
+does some [automatic escaping](https://golang.org/pkg/html/template/#hdr-Contexts) that should be considered.
+
+Attachments (such as images or external style sheets) are not supported. However, other templates can
+be referenced too, for example to provide the contents of a `<style>` element in a centralized fashion.
+The external template must be placed under `custom/mail` and referenced relative to that directory.
+For example, `custom/mail/styles/base.tmpl` can be included using `{{template styles/base}}`.
+
+The mail is sent with `Content-Type: multipart/alternative`, so the body is sent in both HTML
+and text formats. The latter is obtained by stripping the HTML markup.
+
+## Troubleshooting
+
+How a mail is rendered is directly dependent on the capabilities of the mail application. Many mail
+clients don't even support HTML, so they show the text version included in the generated mail.
+
+If the template fails to render, it will be noticed only at the moment the mail is sent.
+A default subject is used if the subject template fails, and whatever was rendered successfully
+from the the _mail body_ is used, disregarding the rest.
+
+Please check [Gitea's logs](https://docs.gitea.io/en-us/logging-configuration/) for error messages in case of trouble.
+
+## Example
+
+`custom/templates/mail/issue/default.tmpl`:
+
+```
+[{{.Repo}}] @{{.Doer.Name}}
+{{if eq .ActionName "new"}}
+    created
+{{else if eq .ActionName "comment"}}
+    commented on
+{{else if eq .ActionName "close"}}
+    closed
+{{else if eq .ActionName "reopen"}}
+    reopened
+{{else}}
+    updated
+{{end}}
+{{if eq .ActionType "issue"}}
+    issue
+{{else}}
+    pull request
+{{end}}
+#{{.Issue.Index}}: {{.Issue.Title}}
+------------
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <title>{{.Subject}}</title>
+</head>
+
+<body>
+    {{if .IsMention}}
+    <p>
+        You are receiving this because @{{.Doer.Name}} mentioned you.
+    </p>
+    {{end}}
+    <p>
+        <p>
+        <a href="{{AppURL}}/{{.Doer.LowerName}}">@{{.Doer.Name}}</a>
+        {{if not (eq .Doer.FullName "")}}
+            ({{.Doer.FullName}})
+        {{end}}
+        {{if eq .ActionName "new"}}
+            created
+        {{else if eq .ActionName "close"}}
+            closed
+        {{else if eq .ActionName "reopen"}}
+            reopened
+        {{else}}
+            updated
+        {{end}}
+        <a href="{{.Link}}">{{.Repo}}#{{.Issue.Index}}</a>.
+        </p>
+        {{if not (eq .Body "")}}
+            <h3>Message content:</h3>
+            <hr>
+            {{.Body | Str2html}}
+        {{end}}
+    </p>
+    <hr>
+    <p>
+        <a href="{{.Link}}">View it on Gitea</a>.
+    </p>
+</body>
+</html>
+```
+
+This template produces something along these lines:
+
+#### Subject
+
+> [mike/stuff] @rhonda commented on pull request #38: New color palette
+
+#### Mail body
+
+> [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#).
+>
+> #### Message content:
+>
+> \__________________________________________________________________
+>
+> Mike, I think we should tone down the blues a little.  
+> \__________________________________________________________________
+> 
+> [View it on Gitea](#).
+
+## Advanced
+
+The template system contains several functions that can be used to further process and format
+the messages. Here's a list of some of them:
+
+| Name                 | Parameters  | Available | Usage                                                               |
+|----------------------|-------------|-----------|---------------------------------------------------------------------|
+| `AppUrl`             | -           | Any       | Gitea's URL                                                         |
+| `AppName`            | -           | Any       | Set from `app.ini`, usually "Gitea"                                 |
+| `AppDomain`          | -           | Any       | Gitea's host name                                                   |
+| `EllipsisString`     | string, int | Any       | Truncates a string to the specified length; adds ellipsis as needed |
+| `Str2html`           | string      | Body only | Sanitizes text by removing any HTML tags from it.                   |
+
+These are _functions_, not metadata, so they have to be used:
+
+```
+Like this:         {{Str2html "Escape<my>text"}}
+Or this:           {{"Escape<my>text" | Str2html}}
+Or this:           {{AppUrl}}
+But not like this: {{.AppUrl}}
+```
diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go
index 38e202f239..e25ffdf7b1 100644
--- a/integrations/api_team_test.go
+++ b/integrations/api_team_test.go
@@ -55,37 +55,44 @@ func TestAPITeam(t *testing.T) {
 
 	// Create team.
 	teamToCreate := &api.CreateTeamOption{
-		Name:        "team1",
-		Description: "team one",
-		Permission:  "write",
-		Units:       []string{"repo.code", "repo.issues"},
+		Name:                    "team1",
+		Description:             "team one",
+		IncludesAllRepositories: true,
+		Permission:              "write",
+		Units:                   []string{"repo.code", "repo.issues"},
 	}
 	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
 	resp = session.MakeRequest(t, req, http.StatusCreated)
 	DecodeJSON(t, resp, &apiTeam)
-	checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
-	checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
+	checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
+		teamToCreate.Permission, teamToCreate.Units)
+	checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
+		teamToCreate.Permission, teamToCreate.Units)
 	teamID := apiTeam.ID
 
 	// Edit team.
 	teamToEdit := &api.EditTeamOption{
-		Name:        "teamone",
-		Description: "team 1",
-		Permission:  "admin",
-		Units:       []string{"repo.code", "repo.pulls", "repo.releases"},
+		Name:                    "teamone",
+		Description:             "team 1",
+		IncludesAllRepositories: false,
+		Permission:              "admin",
+		Units:                   []string{"repo.code", "repo.pulls", "repo.releases"},
 	}
 	req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
 	resp = session.MakeRequest(t, req, http.StatusOK)
 	DecodeJSON(t, resp, &apiTeam)
-	checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
-	checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
+	checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
+		teamToEdit.Permission, teamToEdit.Units)
+	checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
+		teamToEdit.Permission, teamToEdit.Units)
 
 	// Read team.
 	teamRead := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
 	req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
 	resp = session.MakeRequest(t, req, http.StatusOK)
 	DecodeJSON(t, resp, &apiTeam)
-	checkTeamResponse(t, &apiTeam, teamRead.Name, teamRead.Description, teamRead.Authorize.String(), teamRead.GetUnitNames())
+	checkTeamResponse(t, &apiTeam, teamRead.Name, teamRead.Description, teamRead.IncludesAllRepositories,
+		teamRead.Authorize.String(), teamRead.GetUnitNames())
 
 	// Delete team.
 	req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
@@ -93,19 +100,20 @@ func TestAPITeam(t *testing.T) {
 	models.AssertNotExistsBean(t, &models.Team{ID: teamID})
 }
 
-func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, permission string, units []string) {
+func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string) {
 	assert.Equal(t, name, apiTeam.Name, "name")
 	assert.Equal(t, description, apiTeam.Description, "description")
+	assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories")
 	assert.Equal(t, permission, apiTeam.Permission, "permission")
 	sort.StringSlice(units).Sort()
 	sort.StringSlice(apiTeam.Units).Sort()
 	assert.EqualValues(t, units, apiTeam.Units, "units")
 }
 
-func checkTeamBean(t *testing.T, id int64, name, description string, permission string, units []string) {
+func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string) {
 	team := models.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team)
 	assert.NoError(t, team.GetUnits(), "GetUnits")
-	checkTeamResponse(t, convert.ToTeam(team), name, description, permission, units)
+	checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units)
 }
 
 type TeamSearchResults struct {
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 90bb8c53ac..63f5f6b778 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -535,6 +535,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
 		return nil, err
 	}
 
+	if err = updateCommentInfos(e, opts, comment); err != nil {
+		return nil, err
+	}
+
 	if err = sendCreateCommentAction(e, opts, comment); err != nil {
 		return nil, err
 	}
@@ -546,6 +550,56 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
 	return comment, nil
 }
 
+func updateCommentInfos(e *xorm.Session, opts *CreateCommentOptions, comment *Comment) (err error) {
+	// Check comment type.
+	switch opts.Type {
+	case CommentTypeCode:
+		if comment.ReviewID != 0 {
+			if comment.Review == nil {
+				if err := comment.loadReview(e); err != nil {
+					return err
+				}
+			}
+			if comment.Review.Type <= ReviewTypePending {
+				return nil
+			}
+		}
+		fallthrough
+	case CommentTypeComment:
+		if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
+			return err
+		}
+
+		// Check attachments
+		attachments := make([]*Attachment, 0, len(opts.Attachments))
+		for _, uuid := range opts.Attachments {
+			attach, err := getAttachmentByUUID(e, uuid)
+			if err != nil {
+				if IsErrAttachmentNotExist(err) {
+					continue
+				}
+				return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
+			}
+			attachments = append(attachments, attach)
+		}
+
+		for i := range attachments {
+			attachments[i].IssueID = opts.Issue.ID
+			attachments[i].CommentID = comment.ID
+			// No assign value could be 0, so ignore AllCols().
+			if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
+				return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
+			}
+		}
+	case CommentTypeReopen, CommentTypeClose:
+		if err = opts.Issue.updateClosedNum(e); err != nil {
+			return err
+		}
+	}
+	// update the issue's updated_unix column
+	return updateIssueCols(e, opts.Issue, "updated_unix")
+}
+
 func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, comment *Comment) (err error) {
 	// Compose comment action, could be plain comment, close or reopen issue/pull request.
 	// This object will be used to notify watchers in the end of function.
@@ -575,56 +629,16 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
 		fallthrough
 	case CommentTypeComment:
 		act.OpType = ActionCommentIssue
-
-		if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
-			return err
-		}
-
-		// Check attachments
-		attachments := make([]*Attachment, 0, len(opts.Attachments))
-		for _, uuid := range opts.Attachments {
-			attach, err := getAttachmentByUUID(e, uuid)
-			if err != nil {
-				if IsErrAttachmentNotExist(err) {
-					continue
-				}
-				return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
-			}
-			attachments = append(attachments, attach)
-		}
-
-		for i := range attachments {
-			attachments[i].IssueID = opts.Issue.ID
-			attachments[i].CommentID = comment.ID
-			// No assign value could be 0, so ignore AllCols().
-			if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
-				return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
-			}
-		}
-
 	case CommentTypeReopen:
 		act.OpType = ActionReopenIssue
 		if opts.Issue.IsPull {
 			act.OpType = ActionReopenPullRequest
 		}
-
-		if err = opts.Issue.updateClosedNum(e); err != nil {
-			return err
-		}
-
 	case CommentTypeClose:
 		act.OpType = ActionCloseIssue
 		if opts.Issue.IsPull {
 			act.OpType = ActionClosePullRequest
 		}
-
-		if err = opts.Issue.updateClosedNum(e); err != nil {
-			return err
-		}
-	}
-	// update the issue's updated_unix column
-	if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil {
-		return err
 	}
 	// Notify watchers for whatever action comes in, ignore if no action type.
 	if act.OpType > 0 {
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index e214f16a2a..5ed70dc4f5 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -264,6 +264,8 @@ var migrations = []Migration{
 	NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
 	// v104 -> v105
 	NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
+	// v105 -> v106
+	NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
 }
 
 // Migrate database to current version
diff --git a/models/migrations/v105.go b/models/migrations/v105.go
new file mode 100644
index 0000000000..6c9a5817af
--- /dev/null
+++ b/models/migrations/v105.go
@@ -0,0 +1,25 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"xorm.io/xorm"
+)
+
+func addTeamIncludesAllRepositories(x *xorm.Engine) error {
+
+	type Team struct {
+		ID                      int64 `xorm:"pk autoincr"`
+		IncludesAllRepositories bool  `xorm:"NOT NULL DEFAULT false"`
+	}
+
+	if err := x.Sync2(new(Team)); err != nil {
+		return err
+	}
+
+	_, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
+		true, "Owners")
+	return err
+}
diff --git a/models/org.go b/models/org.go
index 2cc302dac6..78b035b101 100644
--- a/models/org.go
+++ b/models/org.go
@@ -48,6 +48,9 @@ func (org *User) GetOwnerTeam() (*Team, error) {
 }
 
 func (org *User) getTeams(e Engine) error {
+	if org.Teams != nil {
+		return nil
+	}
 	return e.
 		Where("org_id=?", org.ID).
 		OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END").
@@ -149,11 +152,12 @@ func CreateOrganization(org, owner *User) (err error) {
 
 	// Create default owner team.
 	t := &Team{
-		OrgID:      org.ID,
-		LowerName:  strings.ToLower(ownerTeamName),
-		Name:       ownerTeamName,
-		Authorize:  AccessModeOwner,
-		NumMembers: 1,
+		OrgID:                   org.ID,
+		LowerName:               strings.ToLower(ownerTeamName),
+		Name:                    ownerTeamName,
+		Authorize:               AccessModeOwner,
+		NumMembers:              1,
+		IncludesAllRepositories: true,
 	}
 	if _, err = sess.Insert(t); err != nil {
 		return fmt.Errorf("insert owner team: %v", err)
diff --git a/models/org_team.go b/models/org_team.go
index a7a179f104..d740e1c240 100644
--- a/models/org_team.go
+++ b/models/org_team.go
@@ -22,17 +22,18 @@ const ownerTeamName = "Owners"
 
 // Team represents a organization team.
 type Team struct {
-	ID          int64 `xorm:"pk autoincr"`
-	OrgID       int64 `xorm:"INDEX"`
-	LowerName   string
-	Name        string
-	Description string
-	Authorize   AccessMode
-	Repos       []*Repository `xorm:"-"`
-	Members     []*User       `xorm:"-"`
-	NumRepos    int
-	NumMembers  int
-	Units       []*TeamUnit `xorm:"-"`
+	ID                      int64 `xorm:"pk autoincr"`
+	OrgID                   int64 `xorm:"INDEX"`
+	LowerName               string
+	Name                    string
+	Description             string
+	Authorize               AccessMode
+	Repos                   []*Repository `xorm:"-"`
+	Members                 []*User       `xorm:"-"`
+	NumRepos                int
+	NumMembers              int
+	Units                   []*TeamUnit `xorm:"-"`
+	IncludesAllRepositories bool        `xorm:"NOT NULL DEFAULT false"`
 }
 
 // SearchTeamOptions holds the search options
@@ -149,6 +150,9 @@ func (t *Team) IsMember(userID int64) bool {
 }
 
 func (t *Team) getRepositories(e Engine) error {
+	if t.Repos != nil {
+		return nil
+	}
 	return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
 		Where("team_repo.team_id=?", t.ID).
 		OrderBy("repository.name").
@@ -220,6 +224,25 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
 	return nil
 }
 
+// addAllRepositories adds all repositories to the team.
+// If the team already has some repositories they will be left unchanged.
+func (t *Team) addAllRepositories(e Engine) error {
+	var orgRepos []Repository
+	if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil {
+		return fmt.Errorf("get org repos: %v", err)
+	}
+
+	for _, repo := range orgRepos {
+		if !t.hasRepository(e, repo.ID) {
+			if err := t.addRepository(e, &repo); err != nil {
+				return fmt.Errorf("addRepository: %v", err)
+			}
+		}
+	}
+
+	return nil
+}
+
 // AddRepository adds new repository to team of organization.
 func (t *Team) AddRepository(repo *Repository) (err error) {
 	if repo.OwnerID != t.OrgID {
@@ -241,6 +264,8 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
 	return sess.Commit()
 }
 
+// removeRepository removes a repository from a team and recalculates access
+// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
 func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
 	if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
 		return err
@@ -284,11 +309,16 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
 }
 
 // RemoveRepository removes repository from team of organization.
+// If the team shall include all repositories the request is ignored.
 func (t *Team) RemoveRepository(repoID int64) error {
 	if !t.HasRepository(repoID) {
 		return nil
 	}
 
+	if t.IncludesAllRepositories {
+		return nil
+	}
+
 	repo, err := GetRepositoryByID(repoID)
 	if err != nil {
 		return err
@@ -394,6 +424,14 @@ func NewTeam(t *Team) (err error) {
 		}
 	}
 
+	// Add all repositories to the team if it has access to all of them.
+	if t.IncludesAllRepositories {
+		err = t.addAllRepositories(sess)
+		if err != nil {
+			return fmt.Errorf("addAllRepositories: %v", err)
+		}
+	}
+
 	// Update organization number of teams.
 	if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
 		errRollback := sess.Rollback()
@@ -446,7 +484,7 @@ func GetTeamByID(teamID int64) (*Team, error) {
 }
 
 // UpdateTeam updates information of team.
-func UpdateTeam(t *Team, authChanged bool) (err error) {
+func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) {
 	if len(t.Name) == 0 {
 		return errors.New("empty team name")
 	}
@@ -511,6 +549,14 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
 		}
 	}
 
+	// Add all repositories to the team if it has access to all of them.
+	if includeAllChanged && t.IncludesAllRepositories {
+		err = t.addAllRepositories(sess)
+		if err != nil {
+			return fmt.Errorf("addAllRepositories: %v", err)
+		}
+	}
+
 	return sess.Commit()
 }
 
diff --git a/models/org_team_test.go b/models/org_team_test.go
index 06ab4637d8..b7e2ef113d 100644
--- a/models/org_team_test.go
+++ b/models/org_team_test.go
@@ -5,9 +5,12 @@
 package models
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
+	"code.gitea.io/gitea/modules/structs"
+
 	"github.com/stretchr/testify/assert"
 )
 
@@ -206,7 +209,7 @@ func TestUpdateTeam(t *testing.T) {
 	team.Name = "newName"
 	team.Description = strings.Repeat("A long description!", 100)
 	team.Authorize = AccessModeAdmin
-	assert.NoError(t, UpdateTeam(team, true))
+	assert.NoError(t, UpdateTeam(team, true, false))
 
 	team = AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team)
 	assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
@@ -225,7 +228,7 @@ func TestUpdateTeam2(t *testing.T) {
 	team.LowerName = "owners"
 	team.Name = "Owners"
 	team.Description = strings.Repeat("A long description!", 100)
-	err := UpdateTeam(team, true)
+	err := UpdateTeam(team, true, false)
 	assert.True(t, IsErrTeamAlreadyExist(err))
 
 	CheckConsistencyFor(t, &Team{ID: team.ID})
@@ -374,3 +377,133 @@ func TestUsersInTeamsCount(t *testing.T) {
 	test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2)    // userid 2,4
 	test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
 }
+
+func TestIncludesAllRepositoriesTeams(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	testTeamRepositories := func(teamID int64, repoIds []int64) {
+		team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
+		assert.NoError(t, team.GetRepositories(), "%s: GetRepositories", team.Name)
+		assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
+		assert.Equal(t, len(repoIds), len(team.Repos), "%s: repo count", team.Name)
+		for i, rid := range repoIds {
+			if rid > 0 {
+				assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i)
+			}
+		}
+	}
+
+	// Get an admin user.
+	user, err := GetUserByID(1)
+	assert.NoError(t, err, "GetUserByID")
+
+	// Create org.
+	org := &User{
+		Name:       "All repo",
+		IsActive:   true,
+		Type:       UserTypeOrganization,
+		Visibility: structs.VisibleTypePublic,
+	}
+	assert.NoError(t, CreateOrganization(org, user), "CreateOrganization")
+
+	// Check Owner team.
+	ownerTeam, err := org.GetOwnerTeam()
+	assert.NoError(t, err, "GetOwnerTeam")
+	assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
+
+	// Create repos.
+	repoIds := make([]int64, 0)
+	for i := 0; i < 3; i++ {
+		r, err := CreateRepository(user, org, CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
+		assert.NoError(t, err, "CreateRepository %d", i)
+		if r != nil {
+			repoIds = append(repoIds, r.ID)
+		}
+	}
+	// Get fresh copy of Owner team after creating repos.
+	ownerTeam, err = org.GetOwnerTeam()
+	assert.NoError(t, err, "GetOwnerTeam")
+
+	// Create teams and check repositories.
+	teams := []*Team{
+		ownerTeam,
+		{
+			OrgID:                   org.ID,
+			Name:                    "team one",
+			Authorize:               AccessModeRead,
+			IncludesAllRepositories: true,
+		},
+		{
+			OrgID:                   org.ID,
+			Name:                    "team 2",
+			Authorize:               AccessModeRead,
+			IncludesAllRepositories: false,
+		},
+		{
+			OrgID:                   org.ID,
+			Name:                    "team three",
+			Authorize:               AccessModeWrite,
+			IncludesAllRepositories: true,
+		},
+		{
+			OrgID:                   org.ID,
+			Name:                    "team 4",
+			Authorize:               AccessModeWrite,
+			IncludesAllRepositories: false,
+		},
+	}
+	teamRepos := [][]int64{
+		repoIds,
+		repoIds,
+		{},
+		repoIds,
+		{},
+	}
+	for i, team := range teams {
+		if i > 0 { // first team is Owner.
+			assert.NoError(t, NewTeam(team), "%s: NewTeam", team.Name)
+		}
+		testTeamRepositories(team.ID, teamRepos[i])
+	}
+
+	// Update teams and check repositories.
+	teams[3].IncludesAllRepositories = false
+	teams[4].IncludesAllRepositories = true
+	teamRepos[4] = repoIds
+	for i, team := range teams {
+		assert.NoError(t, UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name)
+		testTeamRepositories(team.ID, teamRepos[i])
+	}
+
+	// Create repo and check teams repositories.
+	org.Teams = nil // Reset teams to allow their reloading.
+	r, err := CreateRepository(user, org, CreateRepoOptions{Name: "repo-last"})
+	assert.NoError(t, err, "CreateRepository last")
+	if r != nil {
+		repoIds = append(repoIds, r.ID)
+	}
+	teamRepos[0] = repoIds
+	teamRepos[1] = repoIds
+	teamRepos[4] = repoIds
+	for i, team := range teams {
+		testTeamRepositories(team.ID, teamRepos[i])
+	}
+
+	// Remove repo and check teams repositories.
+	assert.NoError(t, DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository")
+	teamRepos[0] = repoIds[1:]
+	teamRepos[1] = repoIds[1:]
+	teamRepos[3] = repoIds[1:3]
+	teamRepos[4] = repoIds[1:]
+	for i, team := range teams {
+		testTeamRepositories(team.ID, teamRepos[i])
+	}
+
+	// Wipe created items.
+	for i, rid := range repoIds {
+		if i > 0 { // first repo already deleted.
+			assert.NoError(t, DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i)
+		}
+	}
+	assert.NoError(t, DeleteOrganization(org), "DeleteOrganization")
+}
diff --git a/models/repo.go b/models/repo.go
index 7945cb309d..89e579d1ec 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -1447,14 +1447,17 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
 	}
 	u.NumRepos++
 
-	// Give access to all members in owner team.
+	// Give access to all members in teams with access to all repositories.
 	if u.IsOrganization() {
-		t, err := u.getOwnerTeam(e)
-		if err != nil {
-			return fmt.Errorf("getOwnerTeam: %v", err)
+		if err := u.GetTeams(); err != nil {
+			return fmt.Errorf("GetTeams: %v", err)
 		}
-		if err = t.addRepository(e, repo); err != nil {
-			return fmt.Errorf("addRepository: %v", err)
+		for _, t := range u.Teams {
+			if t.IncludesAllRepositories {
+				if err := t.addRepository(e, repo); err != nil {
+					return fmt.Errorf("addRepository: %v", err)
+				}
+			}
 		}
 	} else if err = repo.recalculateAccesses(e); err != nil {
 		// Organization automatically called this in addRepository method.
@@ -1641,11 +1644,15 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
 	}
 
 	if newOwner.IsOrganization() {
-		t, err := newOwner.getOwnerTeam(sess)
-		if err != nil {
-			return fmt.Errorf("getOwnerTeam: %v", err)
-		} else if err = t.addRepository(sess, repo); err != nil {
-			return fmt.Errorf("add to owner team: %v", err)
+		if err := newOwner.GetTeams(); err != nil {
+			return fmt.Errorf("GetTeams: %v", err)
+		}
+		for _, t := range newOwner.Teams {
+			if t.IncludesAllRepositories {
+				if err := t.addRepository(sess, repo); err != nil {
+					return fmt.Errorf("addRepository: %v", err)
+				}
+			}
 		}
 	} else if err = repo.recalculateAccesses(sess); err != nil {
 		// Organization called this in addRepository method.
diff --git a/models/review.go b/models/review.go
index 58660b2e3d..89a26d6fdb 100644
--- a/models/review.go
+++ b/models/review.go
@@ -129,13 +129,17 @@ func (r *Review) publish(e *xorm.Engine) error {
 				go func(en *xorm.Engine, review *Review, comm *Comment) {
 					sess := en.NewSession()
 					defer sess.Close()
-					if err := sendCreateCommentAction(sess, &CreateCommentOptions{
+					opts := &CreateCommentOptions{
 						Doer:    comm.Poster,
 						Issue:   review.Issue,
 						Repo:    review.Issue.Repo,
 						Type:    comm.Type,
 						Content: comm.Content,
-					}, comm); err != nil {
+					}
+					if err := updateCommentInfos(sess, opts, comm); err != nil {
+						log.Warn("updateCommentInfos: %v", err)
+					}
+					if err := sendCreateCommentAction(sess, opts, comm); err != nil {
 						log.Warn("sendCreateCommentAction: %v", err)
 					}
 				}(e, r, comment)
diff --git a/modules/auth/org.go b/modules/auth/org.go
index 94e659cb5b..509358882a 100644
--- a/modules/auth/org.go
+++ b/modules/auth/org.go
@@ -62,6 +62,7 @@ type CreateTeamForm struct {
 	Description string `binding:"MaxSize(255)"`
 	Permission  string
 	Units       []models.UnitType
+	RepoAccess  string
 }
 
 // Validate validates the fields
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 45b943e79e..ce55dd55f6 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -248,6 +248,16 @@ func CommitChanges(repoPath string, opts CommitChangesOptions) error {
 	return err
 }
 
+// AllCommitsCount returns count of all commits in repository
+func AllCommitsCount(repoPath string) (int64, error) {
+	stdout, err := NewCommand("rev-list", "--all", "--count").RunInDir(repoPath)
+	if err != nil {
+		return 0, err
+	}
+
+	return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
+}
+
 func commitsCount(repoPath, revision, relpath string) (int64, error) {
 	cmd := NewCommand("rev-list", "--count")
 	cmd.AddArguments(revision)
diff --git a/modules/git/repo.go b/modules/git/repo.go
index e1d75ca4aa..4c6690b913 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -46,6 +46,11 @@ type GPGSettings struct {
 
 const prettyLogFormat = `--pretty=format:%H`
 
+// GetAllCommitsCount returns count of all commits in repository
+func (repo *Repository) GetAllCommitsCount() (int64, error) {
+	return AllCommitsCount(repo.Path)
+}
+
 func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {
 	l := list.New()
 	if len(logs) == 0 {
diff --git a/modules/notification/base/notifier.go b/modules/notification/base/notifier.go
index 286ebe5d69..72bf52c938 100644
--- a/modules/notification/base/notifier.go
+++ b/modules/notification/base/notifier.go
@@ -43,4 +43,6 @@ type Notifier interface {
 	NotifyDeleteRelease(doer *models.User, rel *models.Release)
 
 	NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits)
+	NotifyCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string)
+	NotifyDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string)
 }
diff --git a/modules/notification/base/null.go b/modules/notification/base/null.go
index 5b6359cbd5..a9d9d6a164 100644
--- a/modules/notification/base/null.go
+++ b/modules/notification/base/null.go
@@ -114,3 +114,11 @@ func (*NullNotifier) NotifyMigrateRepository(doer *models.User, u *models.User,
 // NotifyPushCommits notifies commits pushed to notifiers
 func (*NullNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *models.PushCommits) {
 }
+
+// NotifyCreateRef notifies branch or tag creation to notifiers
+func (*NullNotifier) NotifyCreateRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
+}
+
+// NotifyDeleteRef notifies branch or tag deleteion to notifiers
+func (*NullNotifier) NotifyDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) {
+}
diff --git a/modules/notification/notification.go b/modules/notification/notification.go
index a5e450ee66..5ac09a72e5 100644
--- a/modules/notification/notification.go
+++ b/modules/notification/notification.go
@@ -199,3 +199,17 @@ func NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, ol
 		notifier.NotifyPushCommits(pusher, repo, refName, oldCommitID, newCommitID, commits)
 	}
 }
+
+// NotifyCreateRef notifies branch or tag creation to notifiers
+func NotifyCreateRef(pusher *models.User, repo *models.Repository, refType, refFullName string) {
+	for _, notifier := range notifiers {
+		notifier.NotifyCreateRef(pusher, repo, refType, refFullName)
+	}
+}
+
+// NotifyDeleteRef notifies branch or tag deletion to notifiers
+func NotifyDeleteRef(pusher *models.User, repo *models.Repository, refType, refFullName string) {
+	for _, notifier := range notifiers {
+		notifier.NotifyDeleteRef(pusher, repo, refType, refFullName)
+	}
+}
diff --git a/modules/notification/webhook/webhook.go b/modules/notification/webhook/webhook.go
index 39c63edb05..8059ec1c00 100644
--- a/modules/notification/webhook/webhook.go
+++ b/modules/notification/webhook/webhook.go
@@ -6,11 +6,11 @@ package webhook
 
 import (
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/notification/base"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/webhook"
 	webhook_module "code.gitea.io/gitea/modules/webhook"
 )
 
@@ -288,7 +288,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
 	}
 
 	mode, _ := models.AccessLevel(pull.Issue.Poster, pull.Issue.Repo)
-	if err := webhook.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
+	if err := webhook_module.PrepareWebhooks(pull.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
 		Action:      api.HookIssueOpened,
 		Index:       pull.Issue.Index,
 		PullRequest: pull.APIFormat(),
@@ -547,7 +547,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
 		log.Error("models.AccessLevel: %v", err)
 		return
 	}
-	if err := webhook.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
+	if err := webhook_module.PrepareWebhooks(review.Issue.Repo, reviewHookType, &api.PullRequestPayload{
 		Action:      api.HookIssueSynchronized,
 		Index:       review.Issue.Index,
 		PullRequest: pr.APIFormat(),
@@ -562,6 +562,34 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
 	}
 }
 
+func (m *webhookNotifier) NotifyCreateRef(pusher *models.User, repo *models.Repository, refType, refFullName string) {
+	apiPusher := pusher.APIFormat()
+	apiRepo := repo.APIFormat(models.AccessModeNone)
+	refName := git.RefEndName(refFullName)
+
+	gitRepo, err := git.OpenRepository(repo.RepoPath())
+	if err != nil {
+		log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err)
+		return
+	}
+
+	shaSum, err := gitRepo.GetBranchCommitID(refName)
+	if err != nil {
+		log.Error("GetBranchCommitID[%s]: %v", refFullName, err)
+		return
+	}
+
+	if err = webhook_module.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{
+		Ref:     refName,
+		Sha:     shaSum,
+		RefType: refType,
+		Repo:    apiRepo,
+		Sender:  apiPusher,
+	}); err != nil {
+		log.Error("PrepareWebhooks: %v", err)
+	}
+}
+
 func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
 	if err := pr.LoadIssue(); err != nil {
 		log.Error("pr.LoadIssue: %v", err)
@@ -572,7 +600,7 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m
 		return
 	}
 
-	if err := webhook.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
+	if err := webhook_module.PrepareWebhooks(pr.Issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{
 		Action:      api.HookIssueSynchronized,
 		Index:       pr.Issue.Index,
 		PullRequest: pr.Issue.PullRequest.APIFormat(),
@@ -582,3 +610,48 @@ func (m *webhookNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *m
 		log.Error("PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
 	}
 }
+
+func (m *webhookNotifier) NotifyDeleteRef(pusher *models.User, repo *models.Repository, refType, refFullName string) {
+	apiPusher := pusher.APIFormat()
+	apiRepo := repo.APIFormat(models.AccessModeNone)
+	refName := git.RefEndName(refFullName)
+
+	if err := webhook_module.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{
+		Ref:        refName,
+		RefType:    "branch",
+		PusherType: api.PusherTypeUser,
+		Repo:       apiRepo,
+		Sender:     apiPusher,
+	}); err != nil {
+		log.Error("PrepareWebhooks.(delete branch): %v", err)
+	}
+}
+
+func sendReleaseHook(doer *models.User, rel *models.Release, action api.HookReleaseAction) {
+	if err := rel.LoadAttributes(); err != nil {
+		log.Error("LoadAttributes: %v", err)
+		return
+	}
+
+	mode, _ := models.AccessLevel(rel.Publisher, rel.Repo)
+	if err := webhook_module.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{
+		Action:     action,
+		Release:    rel.APIFormat(),
+		Repository: rel.Repo.APIFormat(mode),
+		Sender:     rel.Publisher.APIFormat(),
+	}); err != nil {
+		log.Error("PrepareWebhooks: %v", err)
+	}
+}
+
+func (m *webhookNotifier) NotifyNewRelease(rel *models.Release) {
+	sendReleaseHook(rel.Publisher, rel, api.HookReleasePublished)
+}
+
+func (m *webhookNotifier) NotifyUpdateRelease(doer *models.User, rel *models.Release) {
+	sendReleaseHook(doer, rel, api.HookReleaseUpdated)
+}
+
+func (m *webhookNotifier) NotifyDeleteRelease(doer *models.User, rel *models.Release) {
+	sendReleaseHook(doer, rel, api.HookReleaseDeleted)
+}
diff --git a/modules/repofiles/action.go b/modules/repofiles/action.go
index e5f6bf8718..996363863d 100644
--- a/modules/repofiles/action.go
+++ b/modules/repofiles/action.go
@@ -14,8 +14,6 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/notification"
 	"code.gitea.io/gitea/modules/setting"
-	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/webhook"
 )
 
 // CommitRepoActionOptions represent options of a new commit action.
@@ -113,81 +111,23 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		return fmt.Errorf("NotifyWatchers: %v", err)
 	}
 
-	apiPusher := pusher.APIFormat()
-	apiRepo := repo.APIFormat(models.AccessModeNone)
-
-	var shaSum string
-	var isHookEventPush = false
+	var isHookEventPush = true
 	switch opType {
 	case models.ActionCommitRepo: // Push
-		isHookEventPush = true
-
 		if isNewBranch {
-			gitRepo, err := git.OpenRepository(repo.RepoPath())
-			if err != nil {
-				log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err)
-			}
-
-			shaSum, err = gitRepo.GetBranchCommitID(refName)
-			if err != nil {
-				log.Error("GetBranchCommitID[%s]: %v", opts.RefFullName, err)
-			}
-			if err = webhook.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{
-				Ref:     refName,
-				Sha:     shaSum,
-				RefType: "branch",
-				Repo:    apiRepo,
-				Sender:  apiPusher,
-			}); err != nil {
-				return fmt.Errorf("PrepareWebhooks: %v", err)
-			}
+			notification.NotifyCreateRef(pusher, repo, "branch", opts.RefFullName)
 		}
 
 	case models.ActionDeleteBranch: // Delete Branch
-		isHookEventPush = true
-
-		if err = webhook.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{
-			Ref:        refName,
-			RefType:    "branch",
-			PusherType: api.PusherTypeUser,
-			Repo:       apiRepo,
-			Sender:     apiPusher,
-		}); err != nil {
-			return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err)
-		}
+		notification.NotifyDeleteRef(pusher, repo, "branch", opts.RefFullName)
 
 	case models.ActionPushTag: // Create
-		isHookEventPush = true
+		notification.NotifyCreateRef(pusher, repo, "tag", opts.RefFullName)
 
-		gitRepo, err := git.OpenRepository(repo.RepoPath())
-		if err != nil {
-			log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err)
-		}
-		shaSum, err = gitRepo.GetTagCommitID(refName)
-		if err != nil {
-			log.Error("GetTagCommitID[%s]: %v", opts.RefFullName, err)
-		}
-		if err = webhook.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{
-			Ref:     refName,
-			Sha:     shaSum,
-			RefType: "tag",
-			Repo:    apiRepo,
-			Sender:  apiPusher,
-		}); err != nil {
-			return fmt.Errorf("PrepareWebhooks: %v", err)
-		}
 	case models.ActionDeleteTag: // Delete Tag
-		isHookEventPush = true
-
-		if err = webhook.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{
-			Ref:        refName,
-			RefType:    "tag",
-			PusherType: api.PusherTypeUser,
-			Repo:       apiRepo,
-			Sender:     apiPusher,
-		}); err != nil {
-			return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err)
-		}
+		notification.NotifyDeleteRef(pusher, repo, "tag", opts.RefFullName)
+	default:
+		isHookEventPush = false
 	}
 
 	if isHookEventPush {
diff --git a/modules/structs/org_team.go b/modules/structs/org_team.go
index bea4a10ad4..5053468b4a 100644
--- a/modules/structs/org_team.go
+++ b/modules/structs/org_team.go
@@ -7,10 +7,11 @@ package structs
 
 // Team represents a team in an organization
 type Team struct {
-	ID           int64         `json:"id"`
-	Name         string        `json:"name"`
-	Description  string        `json:"description"`
-	Organization *Organization `json:"organization"`
+	ID                      int64         `json:"id"`
+	Name                    string        `json:"name"`
+	Description             string        `json:"description"`
+	Organization            *Organization `json:"organization"`
+	IncludesAllRepositories bool          `json:"includes_all_repositories"`
 	// enum: none,read,write,admin,owner
 	Permission string `json:"permission"`
 	// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
@@ -20,8 +21,9 @@ type Team struct {
 // CreateTeamOption options for creating a team
 type CreateTeamOption struct {
 	// required: true
-	Name        string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
-	Description string `json:"description" binding:"MaxSize(255)"`
+	Name                    string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
+	Description             string `json:"description" binding:"MaxSize(255)"`
+	IncludesAllRepositories bool   `json:"includes_all_repositories"`
 	// enum: read,write,admin
 	Permission string `json:"permission"`
 	// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
@@ -31,8 +33,9 @@ type CreateTeamOption struct {
 // EditTeamOption options for editing a team
 type EditTeamOption struct {
 	// required: true
-	Name        string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
-	Description string `json:"description" binding:"MaxSize(255)"`
+	Name                    string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
+	Description             string `json:"description" binding:"MaxSize(255)"`
+	IncludesAllRepositories bool   `json:"includes_all_repositories"`
 	// enum: read,write,admin
 	Permission string `json:"permission"`
 	// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
diff --git a/modules/templates/dynamic.go b/modules/templates/dynamic.go
index 6217f1c3b0..6153e8d027 100644
--- a/modules/templates/dynamic.go
+++ b/modules/templates/dynamic.go
@@ -11,6 +11,7 @@ import (
 	"io/ioutil"
 	"path"
 	"strings"
+	texttmpl "text/template"
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -20,7 +21,8 @@ import (
 )
 
 var (
-	templates = template.New("")
+	subjectTemplates = texttmpl.New("")
+	bodyTemplates    = template.New("")
 )
 
 // HTMLRenderer implements the macaron handler for serving HTML templates.
@@ -59,9 +61,12 @@ func JSRenderer() macaron.Handler {
 }
 
 // Mailer provides the templates required for sending notification mails.
-func Mailer() *template.Template {
+func Mailer() (*texttmpl.Template, *template.Template) {
+	for _, funcs := range NewTextFuncMap() {
+		subjectTemplates.Funcs(funcs)
+	}
 	for _, funcs := range NewFuncMap() {
-		templates.Funcs(funcs)
+		bodyTemplates.Funcs(funcs)
 	}
 
 	staticDir := path.Join(setting.StaticRootPath, "templates", "mail")
@@ -84,15 +89,7 @@ func Mailer() *template.Template {
 					continue
 				}
 
-				_, err = templates.New(
-					strings.TrimSuffix(
-						filePath,
-						".tmpl",
-					),
-				).Parse(string(content))
-				if err != nil {
-					log.Warn("Failed to parse template %v", err)
-				}
+				buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, strings.TrimSuffix(filePath, ".tmpl"), content)
 			}
 		}
 	}
@@ -117,18 +114,10 @@ func Mailer() *template.Template {
 					continue
 				}
 
-				_, err = templates.New(
-					strings.TrimSuffix(
-						filePath,
-						".tmpl",
-					),
-				).Parse(string(content))
-				if err != nil {
-					log.Warn("Failed to parse template %v", err)
-				}
+				buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, strings.TrimSuffix(filePath, ".tmpl"), content)
 			}
 		}
 	}
 
-	return templates
+	return subjectTemplates, bodyTemplates
 }
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 2d7a1aee9b..1347835b80 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -16,8 +16,10 @@ import (
 	"mime"
 	"net/url"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"strings"
+	texttmpl "text/template"
 	"time"
 	"unicode"
 
@@ -34,6 +36,9 @@ import (
 	"github.com/editorconfig/editorconfig-core-go/v2"
 )
 
+// Used from static.go && dynamic.go
+var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`)
+
 // NewFuncMap returns functions for injecting to templates
 func NewFuncMap() []template.FuncMap {
 	return []template.FuncMap{map[string]interface{}{
@@ -261,6 +266,112 @@ func NewFuncMap() []template.FuncMap {
 	}}
 }
 
+// NewTextFuncMap returns functions for injecting to text templates
+// It's a subset of those used for HTML and other templates
+func NewTextFuncMap() []texttmpl.FuncMap {
+	return []texttmpl.FuncMap{map[string]interface{}{
+		"GoVer": func() string {
+			return strings.Title(runtime.Version())
+		},
+		"AppName": func() string {
+			return setting.AppName
+		},
+		"AppSubUrl": func() string {
+			return setting.AppSubURL
+		},
+		"AppUrl": func() string {
+			return setting.AppURL
+		},
+		"AppVer": func() string {
+			return setting.AppVer
+		},
+		"AppBuiltWith": func() string {
+			return setting.AppBuiltWith
+		},
+		"AppDomain": func() string {
+			return setting.Domain
+		},
+		"TimeSince":     timeutil.TimeSince,
+		"TimeSinceUnix": timeutil.TimeSinceUnix,
+		"RawTimeSince":  timeutil.RawTimeSince,
+		"DateFmtLong": func(t time.Time) string {
+			return t.Format(time.RFC1123Z)
+		},
+		"DateFmtShort": func(t time.Time) string {
+			return t.Format("Jan 02, 2006")
+		},
+		"List": List,
+		"SubStr": func(str string, start, length int) string {
+			if len(str) == 0 {
+				return ""
+			}
+			end := start + length
+			if length == -1 {
+				end = len(str)
+			}
+			if len(str) < end {
+				return str
+			}
+			return str[start:end]
+		},
+		"EllipsisString": base.EllipsisString,
+		"URLJoin":        util.URLJoin,
+		"Dict": func(values ...interface{}) (map[string]interface{}, error) {
+			if len(values)%2 != 0 {
+				return nil, errors.New("invalid dict call")
+			}
+			dict := make(map[string]interface{}, len(values)/2)
+			for i := 0; i < len(values); i += 2 {
+				key, ok := values[i].(string)
+				if !ok {
+					return nil, errors.New("dict keys must be strings")
+				}
+				dict[key] = values[i+1]
+			}
+			return dict, nil
+		},
+		"Printf":   fmt.Sprintf,
+		"Escape":   Escape,
+		"Sec2Time": models.SecToTime,
+		"ParseDeadline": func(deadline string) []string {
+			return strings.Split(deadline, "|")
+		},
+		"dict": func(values ...interface{}) (map[string]interface{}, error) {
+			if len(values) == 0 {
+				return nil, errors.New("invalid dict call")
+			}
+
+			dict := make(map[string]interface{})
+
+			for i := 0; i < len(values); i++ {
+				switch key := values[i].(type) {
+				case string:
+					i++
+					if i == len(values) {
+						return nil, errors.New("specify the key for non array values")
+					}
+					dict[key] = values[i]
+				case map[string]interface{}:
+					m := values[i].(map[string]interface{})
+					for i, v := range m {
+						dict[i] = v
+					}
+				default:
+					return nil, errors.New("dict values must be maps")
+				}
+			}
+			return dict, nil
+		},
+		"percentage": func(n int, values ...int) float32 {
+			var sum = 0
+			for i := 0; i < len(values); i++ {
+				sum += values[i]
+			}
+			return float32(n) * 100 / float32(sum)
+		},
+	}}
+}
+
 // Safe render raw as HTML
 func Safe(raw string) template.HTML {
 	return template.HTML(raw)
@@ -551,3 +662,22 @@ func MigrationIcon(hostname string) string {
 		return "fa-git-alt"
 	}
 }
+
+func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template, name string, content []byte) {
+	// Split template into subject and body
+	var subjectContent []byte
+	bodyContent := content
+	loc := mailSubjectSplit.FindIndex(content)
+	if loc != nil {
+		subjectContent = content[0:loc[0]]
+		bodyContent = content[loc[1]:]
+	}
+	if _, err := stpl.New(name).
+		Parse(string(subjectContent)); err != nil {
+		log.Warn("Failed to parse template [%s/subject]: %v", name, err)
+	}
+	if _, err := btpl.New(name).
+		Parse(string(bodyContent)); err != nil {
+		log.Warn("Failed to parse template [%s/body]: %v", name, err)
+	}
+}
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
new file mode 100644
index 0000000000..e2997cb853
--- /dev/null
+++ b/modules/templates/helper_test.go
@@ -0,0 +1,55 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package templates
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSubjectBodySeparator(t *testing.T) {
+	test := func(input, subject, body string) {
+		loc := mailSubjectSplit.FindIndex([]byte(input))
+		if loc == nil {
+			assert.Empty(t, subject, "no subject found, but one expected")
+			assert.Equal(t, body, input)
+		} else {
+			assert.Equal(t, subject, string(input[0:loc[0]]))
+			assert.Equal(t, body, string(input[loc[1]:]))
+		}
+	}
+
+	test("Simple\n---------------\nCase",
+		"Simple\n",
+		"\nCase")
+	test("Only\nBody",
+		"",
+		"Only\nBody")
+	test("Minimal\n---\nseparator",
+		"Minimal\n",
+		"\nseparator")
+	test("False --- separator",
+		"",
+		"False --- separator")
+	test("False\n--- separator",
+		"",
+		"False\n--- separator")
+	test("False ---\nseparator",
+		"",
+		"False ---\nseparator")
+	test("With extra spaces\n-----   \t   \nBody",
+		"With extra spaces\n",
+		"\nBody")
+	test("With leading spaces\n   -------\nOnly body",
+		"",
+		"With leading spaces\n   -------\nOnly body")
+	test("Multiple\n---\n-------\n---\nSeparators",
+		"Multiple\n",
+		"\n-------\n---\nSeparators")
+	test("Insuficient\n--\nSeparators",
+		"",
+		"Insuficient\n--\nSeparators")
+}
diff --git a/modules/templates/static.go b/modules/templates/static.go
index f7e53ce887..435ccb1f95 100644
--- a/modules/templates/static.go
+++ b/modules/templates/static.go
@@ -14,6 +14,7 @@ import (
 	"io/ioutil"
 	"path"
 	"strings"
+	texttmpl "text/template"
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -23,7 +24,8 @@ import (
 )
 
 var (
-	templates = template.New("")
+	subjectTemplates = texttmpl.New("")
+	bodyTemplates    = template.New("")
 )
 
 type templateFileSystem struct {
@@ -140,9 +142,12 @@ func JSRenderer() macaron.Handler {
 }
 
 // Mailer provides the templates required for sending notification mails.
-func Mailer() *template.Template {
+func Mailer() (*texttmpl.Template, *template.Template) {
+	for _, funcs := range NewTextFuncMap() {
+		subjectTemplates.Funcs(funcs)
+	}
 	for _, funcs := range NewFuncMap() {
-		templates.Funcs(funcs)
+		bodyTemplates.Funcs(funcs)
 	}
 
 	for _, assetPath := range AssetNames() {
@@ -161,7 +166,8 @@ func Mailer() *template.Template {
 			continue
 		}
 
-		templates.New(
+		buildSubjectBodyTemplate(subjectTemplates,
+			bodyTemplates,
 			strings.TrimPrefix(
 				strings.TrimSuffix(
 					assetPath,
@@ -169,7 +175,7 @@ func Mailer() *template.Template {
 				),
 				"mail/",
 			),
-		).Parse(string(content))
+			content)
 	}
 
 	customDir := path.Join(setting.CustomPath, "templates", "mail")
@@ -192,17 +198,18 @@ func Mailer() *template.Template {
 					continue
 				}
 
-				templates.New(
+				buildSubjectBodyTemplate(subjectTemplates,
+					bodyTemplates,
 					strings.TrimSuffix(
 						filePath,
 						".tmpl",
 					),
-				).Parse(string(content))
+					content)
 			}
 		}
 	}
 
-	return templates
+	return subjectTemplates, bodyTemplates
 }
 
 func Asset(name string) ([]byte, error) {
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index a37d9d7dd7..dc32a9f154 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -1089,6 +1089,9 @@ activity.period.daily=1 Tag
 activity.period.halfweekly=3 Tage
 activity.period.weekly=1 Woche
 activity.period.monthly=1 Monat
+activity.period.quarterly=3 Monate
+activity.period.semiyearly=6 Monate
+activity.period.yearly=1 Jahr
 activity.overview=Übersicht
 activity.active_prs_count_1=<strong>%d</strong> aktiver Pull-Request
 activity.active_prs_count_n=<strong>%d</strong> aktive Pull-Requests
@@ -1511,6 +1514,7 @@ team_name=Teamname
 team_desc=Beschreibung
 team_name_helper=Teamnamen sollten kurz und einprägsam sein.
 team_desc_helper=Beschreibe den Zweck oder die Rolle des Teams.
+team_access_desc=Zugriff auf das Repository
 team_permission_desc=Berechtigungen
 team_unit_desc=Zugriff auf Repositorybereiche erlauben
 
@@ -1584,6 +1588,13 @@ teams.add_nonexistent_repo=Das Repository, das du hinzufügen möchten, existier
 teams.add_duplicate_users=Dieser Benutzer ist bereits ein Teammitglied.
 teams.repos.none=Dieses Team hat Zugang zu keinem Repository.
 teams.members.none=Keine Mitglieder in diesem Team.
+teams.specific_repositories=Bestimmte Repositories
+teams.specific_repositories_helper=Mitglieder haben nur Zugriff auf Repositories, die explizit dem Team hinzugefügt wurden. Wenn Du diese Option wählst, werden Repositories, die bereits mit <i>Alle Repositories</i> hinzugefügt wurden, <strong>nicht</strong> automatisch entfernt.
+teams.all_repositories=Alle Repositories
+teams.all_repositories_helper=Team hat Zugriff auf alle Repositorys. Wenn dies ausgewählt wird, werden <strong>alle vorhandenen</strong> Repositories zum Team hinzugefügt.
+teams.all_repositories_read_permission_desc=Dieses Team gewährt <strong>Lese</strong>-Zugriff auf <strong>Repositories</strong>: Mitglieder können Repositories ansehen und klonen.
+teams.all_repositories_write_permission_desc=Dieses Team gewährt <strong>Schreib</strong>-Zugriff auf <strong>alle Repositories</strong>: Mitglieder können Repositories lesen und auf sie pushen.
+teams.all_repositories_admin_permission_desc=Dieses Team gewährt <strong>Administrator</strong>-Zugriff auf <strong> alle Repositories </strong>: Mitglieder können Repositories lesen, auf sie pushen und Mitwirkende zu Repositories hinzufügen.
 
 [admin]
 dashboard=Dashboard
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index db28fcede0..932d0bceac 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1515,6 +1515,7 @@ team_name = Team Name
 team_desc = Description
 team_name_helper = Team names should be short and memorable.
 team_desc_helper = Describe the purpose or role of the team.
+team_access_desc = Repository access
 team_permission_desc = Permission
 team_unit_desc = Allow Access to Repository Sections
 
@@ -1588,6 +1589,13 @@ teams.add_nonexistent_repo = "The repository you're trying to add does not exist
 teams.add_duplicate_users = User is already a team member.
 teams.repos.none = No repositories could be accessed by this team.
 teams.members.none = No members on this team.
+teams.specific_repositories = Specific repositories
+teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this <strong>will not</strong> automatically remove repositories already added with <i>All repositories</i>.
+teams.all_repositories = All repositories
+teams.all_repositories_helper = Team has access to all repositories. Selecting this will <strong>add all existing</strong> repositories to the team.
+teams.all_repositories_read_permission_desc = This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories.
+teams.all_repositories_write_permission_desc = This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories.
+teams.all_repositories_admin_permission_desc = This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories.
 
 [admin]
 dashboard = Dashboard
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index c9a8711533..c1509b1089 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -586,6 +586,8 @@ fork_visibility_helper=La visibilité d'un dépôt bifurqué ne peut pas être m
 repo_desc=Description
 repo_lang=Langue
 repo_gitignore_helper=Choisissez un modèle de fichier .gitignore.
+issue_labels=Étiquettes des tickets
+issue_labels_helper=Sélectionnez une étiquette de ticket.
 license=Licence
 license_helper=Sélectionner un fichier de licence.
 readme=LISEZMOI
@@ -846,6 +848,8 @@ issues.create_comment=Créer un commentaire
 issues.closed_at=`a fermé <a id="%[1]s"href="#%[1]s"> %[2]s</a>`
 issues.reopened_at=`réouvert à <a id="%[1]s" href="#%[1]s"> %[2]s</a>`
 issues.commit_ref_at=`a référencé ce ticket depuis une révision <a id="%[1]s" href="#%[1]s"> %[2]s</a>`
+issues.ref_issue_at=`a fait référence à ce ticket : %[1]s`
+issues.ref_issue_ext_at=`a fait référence à ce ticket depuis : %[1]s %[2]s`
 issues.poster=Publier
 issues.collaborator=Collaborateur
 issues.owner=Propriétaire
@@ -1330,6 +1334,8 @@ settings.protect_merge_whitelist_committers_desc=N'autoriser que les utilisateur
 settings.protect_merge_whitelist_users=Utilisateurs en liste blanche de fusion :
 settings.protect_merge_whitelist_teams=Équipes en liste blanche de fusion :
 settings.protect_check_status_contexts=Activer le Contrôle Qualité
+settings.protect_check_status_contexts_desc=Exiger le passage du contrôle qualité avant de fusionner Choisir quels contrôles qualité doivent être validés avant que les branches puissent être fusionnées dans une branche qui correspond à cette règle. Si activé, les commits doivent d'abord être poussés vers une autre branche avant d'être fusionnés ou bien poussés directement vers une branche qui correspond à cette règle après que les contrôles qualité soient passés. Si aucun contexte n'a été choisi, le dernier commit doit passer le contrôle qualité peu-importe le contexte.
+settings.protect_check_status_contexts_list=Contrôles qualité trouvés au cours de la semaine dernière pour ce dépôt
 settings.protect_required_approvals=Agréments nécessaires :
 settings.protect_required_approvals_desc=N'autoriser la fusion qu'avec suffisamment de revues positives d'utilisateurs ou équipes sur liste blanche.
 settings.protect_approvals_whitelist_users=Réviseurs sur liste blanche :
@@ -1366,6 +1372,10 @@ diff.parent=Parent
 diff.commit=révision
 diff.git-notes=Notes
 diff.data_not_available=Contenu de la comparaison indisponible
+diff.options_button=Option de Diff
+diff.show_diff_stats=Voir les Statistiques
+diff.download_patch=Télécharger le Fichier Patch
+diff.download_diff=Télécharger le Fichier des Différences
 diff.show_split_view=Vue séparée
 diff.show_unified_view=Vue unifiée
 diff.whitespace_button=Espace
@@ -1376,6 +1386,11 @@ diff.whitespace_ignore_at_eol=Ignorer les changements quand ce sont des espaces
 diff.stats_desc=<strong> %d fichiers modifiés</strong> avec <strong>%d ajouts</strong> et <strong>%d suppressions</strong>
 diff.bin=BIN
 diff.view_file=Voir le fichier
+diff.file_before=Avant
+diff.file_after=Après
+diff.file_image_width=Largeur
+diff.file_image_height=Hauteur
+diff.file_byte_size=Taille
 diff.file_suppressed=Fichier diff supprimé car celui-ci est trop grand
 diff.too_many_files=Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff
 diff.comment.placeholder=Laisser un commentaire
@@ -1443,6 +1458,8 @@ branch.restore_failed=La restauration de la branche '%s' a échoué.
 branch.protected_deletion_failed=La branche '%s' est protégé. Il ne peut pas être supprimé.
 branch.restore=Restaurer la branche '%s'
 branch.download=Télécharger la branche '%s'
+branch.included_desc=Cette branche fait partie de la branche par défaut
+branch.included=Incluses
 
 topic.manage_topics=Gérer les sujets
 topic.done=Terminé
@@ -1478,6 +1495,8 @@ settings.options=Organisation
 settings.full_name=Non Complet
 settings.website=Site Web
 settings.location=Localisation
+settings.permission=Autorisations
+settings.repoadminchangeteam=L'administrateur de dépôt peut ajouter et supprimer l'accès aux équipes
 settings.visibility=Visibilité
 settings.visibility.public=Public
 settings.visibility.limited=Limité (Visible uniquement aux utilisateurs connectés)
@@ -1724,6 +1743,7 @@ auths.tip.google_plus=Obtenez des identifiants OAuth2 sur la console API de Goog
 auths.tip.openid_connect=Utilisez l'URL de découvert OpenID (<server>/.well-known/openid-configuration) pour spécifier les points d'accès
 auths.tip.twitter=Rendez-vous sur https://dev.twitter.com/apps, créez une application et assurez-vous que l'option "Autoriser l'application à être utilisée avec Twitter Connect" est activée
 auths.tip.discord=Enregistrer une nouvelle application sur https://discordapp.com/developers/applications/me
+auths.tip.gitea=Enregistrez une nouvelle application OAuth2. Un guide peut être trouvé sur https://docs.gitea.io/en-us/oauth2-provider/
 auths.edit=Mettre à jour la source d'authentification
 auths.activated=Cette source d'authentification est activée
 auths.new_success=L'authentification "%s" a été ajoutée.
@@ -1956,6 +1976,7 @@ mark_as_unread=Marquer comme non lue
 mark_all_as_read=Tout marquer comme lu
 
 [gpg]
+default_key=Signé avec la clé par défaut
 error.extract_sign=Impossible d'extraire la signature
 error.generate_hash=Impossible de générer la chaine de hachage de la révision
 error.no_committer_account=Aucun compte lié à l'adresse e-mail de l'auteur
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index 50eed5558d..71c9522921 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -74,6 +74,7 @@ preview=Podgląd
 loading=Ładowanie…
 
 [startpage]
+app_desc=Bezbolesna usługa Git na własnym serwerze
 
 [install]
 install=Instalacja
@@ -282,9 +283,9 @@ AuthName=Nazwa autoryzacji
 AdminEmail=E-mail administratora
 
 NewBranchName=Nazwa nowej gałęzi
-CommitSummary=Podsumowanie commitu
-CommitMessage=Wiadomość commitu
-CommitChoice=Wybór commitu
+CommitSummary=Podsumowanie commita
+CommitMessage=Wiadomość commita
+CommitChoice=Wybór commita
 TreeName=Ścieżka pliku
 Content=Treść
 
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index df37b23357..33da0ed0c4 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -1514,6 +1514,7 @@ team_name=Nome da equipe
 team_desc=Descrição
 team_name_helper=Nomes de equipe devem ser curtos e memoráveis.
 team_desc_helper=Descreva a finalidade ou o papel da equipe.
+team_access_desc=Acesso ao repositório
 team_permission_desc=Permissão
 team_unit_desc=Permitir o acesso a seções de repositório
 
@@ -1587,6 +1588,13 @@ teams.add_nonexistent_repo=O repositório que você está tentando adicionar nã
 teams.add_duplicate_users=Usuário já é um membro da equipe.
 teams.repos.none=Nenhum repositório pode ser acessado por essa equipe.
 teams.members.none=Nenhum membro nesta equipe.
+teams.specific_repositories=Repositórios específicos
+teams.specific_repositories_helper=Os membros terão acesso apenas aos repositórios explicitamente adicionados à equipe. Selecionar este <strong>não</strong> removerá automaticamente os repositórios já adicionados com <i>Todos os repositórios</i>.
+teams.all_repositories=Todos os repositórios
+teams.all_repositories_helper=A equipe tem acesso a todos os repositórios. Selecionar isto irá <strong>adicionar todos os repositórios existentes</strong> à equipe.
+teams.all_repositories_read_permission_desc=Esta equipe concede acesso <strong>Leitura</strong> a <strong>todos os repositórios</strong>: membros podem ver e clonar repositórios.
+teams.all_repositories_write_permission_desc=Esta equipe concede acesso <strong>Escrita</strong> a <strong>todos os repositórios</strong>: os membros podem ler de e fazer push para os repositórios.
+teams.all_repositories_admin_permission_desc=Esta equipe concede acesso <strong>Administrativo</strong> a <strong>todos os repositórios</strong>: os membros podem ler, fazer push e adicionar colaboradores aos repositórios.
 
 [admin]
 dashboard=Painel
diff --git a/public/css/index.css b/public/css/index.css
index f7eb02b296..3ec47eb85c 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -818,6 +818,7 @@ i.icon.centerlock{top:1.5em}
 .issue.list>.item .desc .checklist{padding-left:5px}
 .issue.list>.item .desc .checklist .progress-bar{margin-left:2px;width:80px;height:6px;display:inline-block;background-color:#eee;overflow:hidden;border-radius:3px;vertical-align:2px!important}
 .issue.list>.item .desc .checklist .progress-bar .progress{background-color:#ccc;display:block;height:100%}
+.issue.list>.item .desc .due-date{padding-left:5px}
 .issue.list>.item .desc a.milestone{margin-left:5px;color:#999!important}
 .issue.list>.item .desc a.milestone:hover{color:#000!important}
 .issue.list>.item .desc a.ref{margin-left:8px;color:#999!important}
diff --git a/public/css/theme-arc-green.css b/public/css/theme-arc-green.css
index 28a127a3dd..74a7c3ddc3 100644
--- a/public/css/theme-arc-green.css
+++ b/public/css/theme-arc-green.css
@@ -249,6 +249,11 @@ a.ui.label:hover,a.ui.labels .label:hover{background-color:#505667!important;col
 .xdsoft_datetimepicker .xdsoft_datepicker .xdsoft_calendar td,.xdsoft_datetimepicker .xdsoft_datepicker .xdsoft_calendar th{border-color:#4c505c;background-color:#2a2e39}
 .xdsoft_datetimepicker .xdsoft_datepicker .xdsoft_calendar td.xdsoft_disabled,.xdsoft_datetimepicker .xdsoft_datepicker .xdsoft_calendar td.xdsoft_other_month{opacity:.8;background:#a0cc75;color:#000}
 .heatmap-color-0{background-color:#2d303b}
+.heatmap-color-1{background-color:#444f47}
+.heatmap-color-2{background-color:#5b6e52}
+.heatmap-color-3{background-color:#728e5e}
+.heatmap-color-4{background-color:#89ad69}
+.heatmap-color-5{background-color:#a0cc75}
 .CodeMirror{color:#9daccc;background-color:#2b2b2b;border-top:0}
 .CodeMirror div.CodeMirror-cursor{border-left:1px solid #9e9e9e}
 .CodeMirror .CodeMirror-gutters{background-color:#2b2b2b}
diff --git a/public/less/_repository.less b/public/less/_repository.less
index d585eb03a6..84d59bbe91 100644
--- a/public/less/_repository.less
+++ b/public/less/_repository.less
@@ -2073,6 +2073,10 @@
                 }
             }
 
+            .due-date {
+                padding-left: 5px;
+            }
+
             a.milestone {
                 margin-left: 5px;
                 color: #999999 !important;
diff --git a/public/less/themes/arc-green.less b/public/less/themes/arc-green.less
index 27c32728a2..06e7c78d41 100644
--- a/public/less/themes/arc-green.less
+++ b/public/less/themes/arc-green.less
@@ -1294,8 +1294,34 @@ a.ui.labels .label:hover {
     }
 }
 
+.heatmap(@heat) {
+    @heatmap-cold: #2d303b;
+    @heatmap-hot: #a0cc75;
+    background-color: mix(@heatmap-hot, @heatmap-cold, @heat);
+}
+
 .heatmap-color-0 {
-    background-color: #2d303b;
+    .heatmap(0%);
+}
+
+.heatmap-color-1 {
+    .heatmap(20%);
+}
+
+.heatmap-color-2 {
+    .heatmap(40%);
+}
+
+.heatmap-color-3 {
+    .heatmap(60%);
+}
+
+.heatmap-color-4 {
+    .heatmap(80%);
+}
+
+.heatmap-color-5 {
+    .heatmap(100%);
 }
 
 /* code mirror dark theme */
diff --git a/routers/api/v1/convert/convert.go b/routers/api/v1/convert/convert.go
index 6da53d6275..f52ed63476 100644
--- a/routers/api/v1/convert/convert.go
+++ b/routers/api/v1/convert/convert.go
@@ -227,11 +227,12 @@ func ToOrganization(org *models.User) *api.Organization {
 // ToTeam convert models.Team to api.Team
 func ToTeam(team *models.Team) *api.Team {
 	return &api.Team{
-		ID:          team.ID,
-		Name:        team.Name,
-		Description: team.Description,
-		Permission:  team.Authorize.String(),
-		Units:       team.GetUnitNames(),
+		ID:                      team.ID,
+		Name:                    team.Name,
+		Description:             team.Description,
+		IncludesAllRepositories: team.IncludesAllRepositories,
+		Permission:              team.Authorize.String(),
+		Units:                   team.GetUnitNames(),
 	}
 }
 
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index d01f051626..a22b60a2c6 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -128,10 +128,11 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
 	//   "201":
 	//     "$ref": "#/responses/Team"
 	team := &models.Team{
-		OrgID:       ctx.Org.Organization.ID,
-		Name:        form.Name,
-		Description: form.Description,
-		Authorize:   models.ParseAccessMode(form.Permission),
+		OrgID:                   ctx.Org.Organization.ID,
+		Name:                    form.Name,
+		Description:             form.Description,
+		IncludesAllRepositories: form.IncludesAllRepositories,
+		Authorize:               models.ParseAccessMode(form.Permission),
 	}
 
 	unitTypes := models.FindUnitTypes(form.Units...)
@@ -182,11 +183,27 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
 	//   "200":
 	//     "$ref": "#/responses/Team"
 	team := ctx.Org.Team
-	team.Name = form.Name
 	team.Description = form.Description
-	team.Authorize = models.ParseAccessMode(form.Permission)
 	unitTypes := models.FindUnitTypes(form.Units...)
 
+	isAuthChanged := false
+	isIncludeAllChanged := false
+	if !team.IsOwnerTeam() {
+		// Validate permission level.
+		auth := models.ParseAccessMode(form.Permission)
+
+		team.Name = form.Name
+		if team.Authorize != auth {
+			isAuthChanged = true
+			team.Authorize = auth
+		}
+
+		if team.IncludesAllRepositories != form.IncludesAllRepositories {
+			isIncludeAllChanged = true
+			team.IncludesAllRepositories = form.IncludesAllRepositories
+		}
+	}
+
 	if team.Authorize < models.AccessModeOwner {
 		var units = make([]*models.TeamUnit, 0, len(form.Units))
 		for _, tp := range unitTypes {
@@ -198,7 +215,7 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
 		team.Units = units
 	}
 
-	if err := models.UpdateTeam(team, true); err != nil {
+	if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {
 		ctx.Error(500, "EditTeam", err)
 		return
 	}
diff --git a/routers/org/teams.go b/routers/org/teams.go
index 7ead6ea5ff..24612459a4 100644
--- a/routers/org/teams.go
+++ b/routers/org/teams.go
@@ -1,4 +1,5 @@
 // Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
@@ -180,12 +181,14 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
 	ctx.Data["PageIsOrgTeams"] = true
 	ctx.Data["PageIsOrgTeamsNew"] = true
 	ctx.Data["Units"] = models.Units
+	var includesAllRepositories = (form.RepoAccess == "all")
 
 	t := &models.Team{
-		OrgID:       ctx.Org.Organization.ID,
-		Name:        form.TeamName,
-		Description: form.Description,
-		Authorize:   models.ParseAccessMode(form.Permission),
+		OrgID:                   ctx.Org.Organization.ID,
+		Name:                    form.TeamName,
+		Description:             form.Description,
+		Authorize:               models.ParseAccessMode(form.Permission),
+		IncludesAllRepositories: includesAllRepositories,
 	}
 
 	if t.Authorize < models.AccessModeOwner {
@@ -268,6 +271,8 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
 	ctx.Data["Units"] = models.Units
 
 	isAuthChanged := false
+	isIncludeAllChanged := false
+	var includesAllRepositories = (form.RepoAccess == "all")
 	if !t.IsOwnerTeam() {
 		// Validate permission level.
 		auth := models.ParseAccessMode(form.Permission)
@@ -277,6 +282,11 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
 			isAuthChanged = true
 			t.Authorize = auth
 		}
+
+		if t.IncludesAllRepositories != includesAllRepositories {
+			isIncludeAllChanged = true
+			t.IncludesAllRepositories = includesAllRepositories
+		}
 	}
 	t.Description = form.Description
 	if t.Authorize < models.AccessModeOwner {
@@ -305,7 +315,7 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
 		return
 	}
 
-	if err := models.UpdateTeam(t, isAuthChanged); err != nil {
+	if err := models.UpdateTeam(t, isAuthChanged, isIncludeAllChanged); err != nil {
 		ctx.Data["Err_TeamName"] = true
 		switch {
 		case models.IsErrTeamAlreadyExist(err):
diff --git a/routers/repo/commit.go b/routers/repo/commit.go
index 550e4c3a9c..f067729ca9 100644
--- a/routers/repo/commit.go
+++ b/routers/repo/commit.go
@@ -91,6 +91,12 @@ func Graph(ctx *context.Context) {
 		return
 	}
 
+	allCommitsCount, err := ctx.Repo.GitRepo.GetAllCommitsCount()
+	if err != nil {
+		ctx.ServerError("GetAllCommitsCount", err)
+		return
+	}
+
 	page := ctx.QueryInt("page")
 
 	graph, err := models.GetCommitGraph(ctx.Repo.GitRepo, page)
@@ -105,7 +111,7 @@ func Graph(ctx *context.Context) {
 	ctx.Data["CommitCount"] = commitsCount
 	ctx.Data["Branch"] = ctx.Repo.BranchName
 	ctx.Data["RequireGitGraph"] = true
-	ctx.Data["Page"] = context.NewPagination(int(commitsCount), setting.UI.GraphMaxCommitNum, page, 5)
+	ctx.Data["Page"] = context.NewPagination(int(allCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
 	ctx.HTML(200, tplGraph)
 }
 
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index bc2aff7314..fc892f6076 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -9,7 +9,11 @@ import (
 	"bytes"
 	"fmt"
 	"html/template"
+	"mime"
 	"path"
+	"regexp"
+	"strings"
+	texttmpl "text/template"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/base"
@@ -28,18 +32,22 @@ const (
 	mailAuthResetPassword  base.TplName = "auth/reset_passwd"
 	mailAuthRegisterNotify base.TplName = "auth/register_notify"
 
-	mailIssueComment  base.TplName = "issue/comment"
-	mailIssueMention  base.TplName = "issue/mention"
-	mailIssueAssigned base.TplName = "issue/assigned"
-
 	mailNotifyCollaborator base.TplName = "notify/collaborator"
+
+	// There's no actual limit for subject in RFC 5322
+	mailMaxSubjectRunes = 256
 )
 
-var templates *template.Template
+var (
+	bodyTemplates       *template.Template
+	subjectTemplates    *texttmpl.Template
+	subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
+)
 
 // InitMailRender initializes the mail renderer
-func InitMailRender(tmpls *template.Template) {
-	templates = tmpls
+func InitMailRender(subjectTpl *texttmpl.Template, bodyTpl *template.Template) {
+	subjectTemplates = subjectTpl
+	bodyTemplates = bodyTpl
 }
 
 // SendTestMail sends a test mail
@@ -58,7 +66,7 @@ func SendUserMail(language string, u *models.User, tpl base.TplName, code, subje
 
 	var content bytes.Buffer
 
-	if err := templates.ExecuteTemplate(&content, string(tpl), data); err != nil {
+	if err := bodyTemplates.ExecuteTemplate(&content, string(tpl), data); err != nil {
 		log.Error("Template: %v", err)
 		return
 	}
@@ -96,7 +104,7 @@ func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAdd
 
 	var content bytes.Buffer
 
-	if err := templates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
+	if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
 		log.Error("Template: %v", err)
 		return
 	}
@@ -121,7 +129,7 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) {
 
 	var content bytes.Buffer
 
-	if err := templates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
+	if err := bodyTemplates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
 		log.Error("Template: %v", err)
 		return
 	}
@@ -145,7 +153,7 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
 
 	var content bytes.Buffer
 
-	if err := templates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
+	if err := bodyTemplates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
 		log.Error("Template: %v", err)
 		return
 	}
@@ -156,40 +164,70 @@ func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
 	SendAsync(msg)
 }
 
-func composeTplData(subject, body, link string) map[string]interface{} {
-	data := make(map[string]interface{}, 10)
-	data["Subject"] = subject
-	data["Body"] = body
-	data["Link"] = link
-	return data
-}
+func composeIssueCommentMessage(issue *models.Issue, doer *models.User, actionType models.ActionType, fromMention bool,
+	content string, comment *models.Comment, tos []string, info string) *Message {
 
-func composeIssueCommentMessage(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tplName base.TplName, tos []string, info string) *Message {
-	var subject string
+	if err := issue.LoadPullRequest(); err != nil {
+		log.Error("LoadPullRequest: %v", err)
+		return nil
+	}
+
+	var (
+		subject string
+		link    string
+		prefix  string
+		// Fall back subject for bad templates, make sure subject is never empty
+		fallback string
+	)
+
+	commentType := models.CommentTypeComment
 	if comment != nil {
-		subject = "Re: " + mailSubject(issue)
+		prefix = "Re: "
+		commentType = comment.Type
+		link = issue.HTMLURL() + "#" + comment.HashTag()
 	} else {
-		subject = mailSubject(issue)
-	}
-	err := issue.LoadRepo()
-	if err != nil {
-		log.Error("LoadRepo: %v", err)
+		link = issue.HTMLURL()
 	}
+
+	fallback = prefix + fallbackMailSubject(issue)
+
+	// This is the body of the new issue or comment, not the mail body
 	body := string(markup.RenderByType(markdown.MarkupName, []byte(content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
 
-	var data = make(map[string]interface{}, 10)
-	if comment != nil {
-		data = composeTplData(subject, body, issue.HTMLURL()+"#"+comment.HashTag())
-	} else {
-		data = composeTplData(subject, body, issue.HTMLURL())
+	actType, actName, tplName := actionToTemplate(issue, actionType, commentType)
+
+	mailMeta := map[string]interface{}{
+		"FallbackSubject": fallback,
+		"Body":            body,
+		"Link":            link,
+		"Issue":           issue,
+		"Comment":         comment,
+		"IsPull":          issue.IsPull,
+		"User":            issue.Repo.MustOwner(),
+		"Repo":            issue.Repo.FullName(),
+		"Doer":            doer,
+		"IsMention":       fromMention,
+		"SubjectPrefix":   prefix,
+		"ActionType":      actType,
+		"ActionName":      actName,
 	}
-	data["Doer"] = doer
-	data["Issue"] = issue
+
+	var mailSubject bytes.Buffer
+	if err := subjectTemplates.ExecuteTemplate(&mailSubject, string(tplName), mailMeta); err == nil {
+		subject = sanitizeSubject(mailSubject.String())
+	} else {
+		log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/subject", err)
+	}
+
+	if subject == "" {
+		subject = fallback
+	}
+	mailMeta["Subject"] = subject
 
 	var mailBody bytes.Buffer
 
-	if err := templates.ExecuteTemplate(&mailBody, string(tplName), data); err != nil {
-		log.Error("Template: %v", err)
+	if err := bodyTemplates.ExecuteTemplate(&mailBody, string(tplName), mailMeta); err != nil {
+		log.Error("ExecuteTemplate [%s]: %v", string(tplName)+"/body", err)
 	}
 
 	msg := NewMessageFrom(tos, doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
@@ -206,24 +244,81 @@ func composeIssueCommentMessage(issue *models.Issue, doer *models.User, content
 	return msg
 }
 
+func sanitizeSubject(subject string) string {
+	runes := []rune(strings.TrimSpace(subjectRemoveSpaces.ReplaceAllLiteralString(subject, " ")))
+	if len(runes) > mailMaxSubjectRunes {
+		runes = runes[:mailMaxSubjectRunes]
+	}
+	// Encode non-ASCII characters
+	return mime.QEncoding.Encode("utf-8", string(runes))
+}
+
 // SendIssueCommentMail composes and sends issue comment emails to target receivers.
-func SendIssueCommentMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
+func SendIssueCommentMail(issue *models.Issue, doer *models.User, actionType models.ActionType, content string, comment *models.Comment, tos []string) {
 	if len(tos) == 0 {
 		return
 	}
 
-	SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueComment, tos, "issue comment"))
+	SendAsync(composeIssueCommentMessage(issue, doer, actionType, false, content, comment, tos, "issue comment"))
 }
 
 // SendIssueMentionMail composes and sends issue mention emails to target receivers.
-func SendIssueMentionMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
+func SendIssueMentionMail(issue *models.Issue, doer *models.User, actionType models.ActionType, content string, comment *models.Comment, tos []string) {
 	if len(tos) == 0 {
 		return
 	}
-	SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueMention, tos, "issue mention"))
+	SendAsync(composeIssueCommentMessage(issue, doer, actionType, true, content, comment, tos, "issue mention"))
+}
+
+// actionToTemplate returns the type and name of the action facing the user
+// (slightly different from models.ActionType) and the name of the template to use (based on availability)
+func actionToTemplate(issue *models.Issue, actionType models.ActionType, commentType models.CommentType) (typeName, name, template string) {
+	if issue.IsPull {
+		typeName = "pull"
+	} else {
+		typeName = "issue"
+	}
+	switch actionType {
+	case models.ActionCreateIssue, models.ActionCreatePullRequest:
+		name = "new"
+	case models.ActionCommentIssue:
+		name = "comment"
+	case models.ActionCloseIssue, models.ActionClosePullRequest:
+		name = "close"
+	case models.ActionReopenIssue, models.ActionReopenPullRequest:
+		name = "reopen"
+	case models.ActionMergePullRequest:
+		name = "merge"
+	default:
+		switch commentType {
+		case models.CommentTypeReview:
+			name = "review"
+		case models.CommentTypeCode:
+			name = "code"
+		case models.CommentTypeAssignees:
+			name = "assigned"
+		default:
+			name = "default"
+		}
+	}
+
+	template = typeName + "/" + name
+	ok := bodyTemplates.Lookup(template) != nil
+	if !ok && typeName != "issue" {
+		template = "issue/" + name
+		ok = bodyTemplates.Lookup(template) != nil
+	}
+	if !ok {
+		template = typeName + "/default"
+		ok = bodyTemplates.Lookup(template) != nil
+	}
+	if !ok {
+		template = "issue/default"
+	}
+	return
 }
 
 // SendIssueAssignedMail composes and sends issue assigned email
 func SendIssueAssignedMail(issue *models.Issue, doer *models.User, content string, comment *models.Comment, tos []string) {
-	SendAsync(composeIssueCommentMessage(issue, doer, content, comment, mailIssueAssigned, tos, "issue assigned"))
+	SendAsync(composeIssueCommentMessage(issue, doer, models.ActionType(0), false, content, comment, tos, "issue assigned"))
 }
diff --git a/services/mailer/mail_comment.go b/services/mailer/mail_comment.go
index d306c14f42..6469eb1fa1 100644
--- a/services/mailer/mail_comment.go
+++ b/services/mailer/mail_comment.go
@@ -31,24 +31,8 @@ func mailParticipantsComment(ctx models.DBContext, c *models.Comment, opType mod
 	for i, u := range userMentions {
 		mentions[i] = u.LowerName
 	}
-	if len(c.Content) > 0 {
-		if err = mailIssueCommentToParticipants(issue, c.Poster, c.Content, c, mentions); err != nil {
-			log.Error("mailIssueCommentToParticipants: %v", err)
-		}
+	if err = mailIssueCommentToParticipants(issue, c.Poster, opType, c.Content, c, mentions); err != nil {
+		log.Error("mailIssueCommentToParticipants: %v", err)
 	}
-
-	switch opType {
-	case models.ActionCloseIssue:
-		ct := fmt.Sprintf("Closed #%d.", issue.Index)
-		if err = mailIssueCommentToParticipants(issue, c.Poster, ct, c, mentions); err != nil {
-			log.Error("mailIssueCommentToParticipants: %v", err)
-		}
-	case models.ActionReopenIssue:
-		ct := fmt.Sprintf("Reopened #%d.", issue.Index)
-		if err = mailIssueCommentToParticipants(issue, c.Poster, ct, c, mentions); err != nil {
-			log.Error("mailIssueCommentToParticipants: %v", err)
-		}
-	}
-
 	return nil
 }
diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go
index a5f3251807..32b21b1324 100644
--- a/services/mailer/mail_issue.go
+++ b/services/mailer/mail_issue.go
@@ -14,7 +14,7 @@ import (
 	"github.com/unknwon/com"
 )
 
-func mailSubject(issue *models.Issue) string {
+func fallbackMailSubject(issue *models.Issue) string {
 	return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.FullName(), issue.Title, issue.Index)
 }
 
@@ -22,7 +22,7 @@ func mailSubject(issue *models.Issue) string {
 // This function sends two list of emails:
 // 1. Repository watchers and users who are participated in comments.
 // 2. Users who are not in 1. but get mentioned in current issue/comment.
-func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, content string, comment *models.Comment, mentions []string) error {
+func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, actionType models.ActionType, content string, comment *models.Comment, mentions []string) error {
 
 	watchers, err := models.GetWatchers(issue.RepoID)
 	if err != nil {
@@ -89,7 +89,7 @@ func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, cont
 	}
 
 	for _, to := range tos {
-		SendIssueCommentMail(issue, doer, content, comment, []string{to})
+		SendIssueCommentMail(issue, doer, actionType, content, comment, []string{to})
 	}
 
 	// Mail mentioned people and exclude watchers.
@@ -106,7 +106,7 @@ func mailIssueCommentToParticipants(issue *models.Issue, doer *models.User, cont
 	emails := models.GetUserEmailsByNames(tos)
 
 	for _, to := range emails {
-		SendIssueMentionMail(issue, doer, content, comment, []string{to})
+		SendIssueMentionMail(issue, doer, actionType, content, comment, []string{to})
 	}
 
 	return nil
@@ -131,32 +131,8 @@ func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.Us
 	for i, u := range userMentions {
 		mentions[i] = u.LowerName
 	}
-
-	if len(issue.Content) > 0 {
-		if err = mailIssueCommentToParticipants(issue, doer, issue.Content, nil, mentions); err != nil {
-			log.Error("mailIssueCommentToParticipants: %v", err)
-		}
+	if err = mailIssueCommentToParticipants(issue, doer, opType, issue.Content, nil, mentions); err != nil {
+		log.Error("mailIssueCommentToParticipants: %v", err)
 	}
-
-	switch opType {
-	case models.ActionCreateIssue, models.ActionCreatePullRequest:
-		if len(issue.Content) == 0 {
-			ct := fmt.Sprintf("Created #%d.", issue.Index)
-			if err = mailIssueCommentToParticipants(issue, doer, ct, nil, mentions); err != nil {
-				log.Error("mailIssueCommentToParticipants: %v", err)
-			}
-		}
-	case models.ActionCloseIssue, models.ActionClosePullRequest:
-		ct := fmt.Sprintf("Closed #%d.", issue.Index)
-		if err = mailIssueCommentToParticipants(issue, doer, ct, nil, mentions); err != nil {
-			log.Error("mailIssueCommentToParticipants: %v", err)
-		}
-	case models.ActionReopenIssue, models.ActionReopenPullRequest:
-		ct := fmt.Sprintf("Reopened #%d.", issue.Index)
-		if err = mailIssueCommentToParticipants(issue, doer, ct, nil, mentions); err != nil {
-			log.Error("mailIssueCommentToParticipants: %v", err)
-		}
-	}
-
 	return nil
 }
diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go
index c7a84d6b33..a10507e0e4 100644
--- a/services/mailer/mail_test.go
+++ b/services/mailer/mail_test.go
@@ -5,8 +5,10 @@
 package mailer
 
 import (
+	"bytes"
 	"html/template"
 	"testing"
+	texttmpl "text/template"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/setting"
@@ -14,7 +16,11 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-const tmpl = `
+const subjectTpl = `
+{{.SubjectPrefix}}[{{.Repo}}] @{{.Doer.Name}} #{{.Issue.Index}} - {{.Issue.Title}}
+`
+
+const bodyTpl = `
 <!DOCTYPE html>
 <html>
 <head>
@@ -47,17 +53,19 @@ func TestComposeIssueCommentMessage(t *testing.T) {
 	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1, Repo: repo, Poster: doer}).(*models.Issue)
 	comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2, Issue: issue}).(*models.Comment)
 
-	email := template.Must(template.New("issue/comment").Parse(tmpl))
-	InitMailRender(email)
+	stpl := texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl))
+	btpl := template.Must(template.New("issue/comment").Parse(bodyTpl))
+	InitMailRender(stpl, btpl)
 
 	tos := []string{"test@gitea.com", "test2@gitea.com"}
-	msg := composeIssueCommentMessage(issue, doer, "test body", comment, mailIssueComment, tos, "issue comment")
+	msg := composeIssueCommentMessage(issue, doer, models.ActionCommentIssue, false, "test body", comment, tos, "issue comment")
 
 	subject := msg.GetHeader("Subject")
 	inreplyTo := msg.GetHeader("In-Reply-To")
 	references := msg.GetHeader("References")
 
-	assert.Equal(t, subject[0], "Re: "+mailSubject(issue), "Comment reply subject should contain Re:")
+	assert.Equal(t, "Re: ", subject[0][:4], "Comment reply subject should contain Re:")
+	assert.Equal(t, "Re: [user2/repo1] @user2 #1 - issue1", subject[0])
 	assert.Equal(t, inreplyTo[0], "<user2/repo1/issues/1@localhost>", "In-Reply-To header doesn't match")
 	assert.Equal(t, references[0], "<user2/repo1/issues/1@localhost>", "References header doesn't match")
 }
@@ -75,17 +83,122 @@ func TestComposeIssueMessage(t *testing.T) {
 	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1, Owner: doer}).(*models.Repository)
 	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1, Repo: repo, Poster: doer}).(*models.Issue)
 
-	email := template.Must(template.New("issue/comment").Parse(tmpl))
-	InitMailRender(email)
+	stpl := texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl))
+	btpl := template.Must(template.New("issue/new").Parse(bodyTpl))
+	InitMailRender(stpl, btpl)
 
 	tos := []string{"test@gitea.com", "test2@gitea.com"}
-	msg := composeIssueCommentMessage(issue, doer, "test body", nil, mailIssueComment, tos, "issue create")
+	msg := composeIssueCommentMessage(issue, doer, models.ActionCreateIssue, false, "test body", nil, tos, "issue create")
 
 	subject := msg.GetHeader("Subject")
 	messageID := msg.GetHeader("Message-ID")
 
-	assert.Equal(t, subject[0], mailSubject(issue), "Subject not equal to issue.mailSubject()")
+	assert.Equal(t, "[user2/repo1] @user2 #1 - issue1", subject[0])
 	assert.Nil(t, msg.GetHeader("In-Reply-To"))
 	assert.Nil(t, msg.GetHeader("References"))
 	assert.Equal(t, messageID[0], "<user2/repo1/issues/1@localhost>", "Message-ID header doesn't match")
 }
+
+func TestTemplateSelection(t *testing.T) {
+	assert.NoError(t, models.PrepareTestDatabase())
+	var mailService = setting.Mailer{
+		From: "test@gitea.com",
+	}
+
+	setting.MailService = &mailService
+	setting.Domain = "localhost"
+
+	doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1, Owner: doer}).(*models.Repository)
+	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1, Repo: repo, Poster: doer}).(*models.Issue)
+	tos := []string{"test@gitea.com"}
+
+	stpl := texttmpl.Must(texttmpl.New("issue/default").Parse("issue/default/subject"))
+	texttmpl.Must(stpl.New("issue/new").Parse("issue/new/subject"))
+	texttmpl.Must(stpl.New("pull/comment").Parse("pull/comment/subject"))
+	texttmpl.Must(stpl.New("issue/close").Parse("")) // Must default to fallback subject
+
+	btpl := template.Must(template.New("issue/default").Parse("issue/default/body"))
+	template.Must(btpl.New("issue/new").Parse("issue/new/body"))
+	template.Must(btpl.New("pull/comment").Parse("pull/comment/body"))
+	template.Must(btpl.New("issue/close").Parse("issue/close/body"))
+
+	InitMailRender(stpl, btpl)
+
+	expect := func(t *testing.T, msg *Message, expSubject, expBody string) {
+		subject := msg.GetHeader("Subject")
+		msgbuf := new(bytes.Buffer)
+		_, _ = msg.WriteTo(msgbuf)
+		wholemsg := msgbuf.String()
+		assert.Equal(t, []string{expSubject}, subject)
+		assert.Contains(t, wholemsg, expBody)
+	}
+
+	msg := composeIssueCommentMessage(issue, doer, models.ActionCreateIssue, false, "test body", nil, tos, "TestTemplateSelection")
+	expect(t, msg, "issue/new/subject", "issue/new/body")
+
+	comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2, Issue: issue}).(*models.Comment)
+	msg = composeIssueCommentMessage(issue, doer, models.ActionCommentIssue, false, "test body", comment, tos, "TestTemplateSelection")
+	expect(t, msg, "issue/default/subject", "issue/default/body")
+
+	pull := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2, Repo: repo, Poster: doer}).(*models.Issue)
+	comment = models.AssertExistsAndLoadBean(t, &models.Comment{ID: 4, Issue: pull}).(*models.Comment)
+	msg = composeIssueCommentMessage(pull, doer, models.ActionCommentIssue, false, "test body", comment, tos, "TestTemplateSelection")
+	expect(t, msg, "pull/comment/subject", "pull/comment/body")
+
+	msg = composeIssueCommentMessage(issue, doer, models.ActionCloseIssue, false, "test body", nil, tos, "TestTemplateSelection")
+	expect(t, msg, "[user2/repo1] issue1 (#1)", "issue/close/body")
+}
+
+func TestTemplateServices(t *testing.T) {
+	assert.NoError(t, models.PrepareTestDatabase())
+	var mailService = setting.Mailer{
+		From: "test@gitea.com",
+	}
+
+	setting.MailService = &mailService
+	setting.Domain = "localhost"
+
+	doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1, Owner: doer}).(*models.Repository)
+	issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 1, Repo: repo, Poster: doer}).(*models.Issue)
+	comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 2, Issue: issue}).(*models.Comment)
+	assert.NoError(t, issue.LoadRepo())
+
+	expect := func(t *testing.T, issue *models.Issue, comment *models.Comment, doer *models.User,
+		actionType models.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string) {
+
+		stpl := texttmpl.Must(texttmpl.New("issue/default").Parse(tplSubject))
+		btpl := template.Must(template.New("issue/default").Parse(tplBody))
+		InitMailRender(stpl, btpl)
+
+		tos := []string{"test@gitea.com"}
+		msg := composeIssueCommentMessage(issue, doer, actionType, fromMention, "test body", comment, tos, "TestTemplateServices")
+
+		subject := msg.GetHeader("Subject")
+		msgbuf := new(bytes.Buffer)
+		_, _ = msg.WriteTo(msgbuf)
+		wholemsg := msgbuf.String()
+
+		assert.Equal(t, []string{expSubject}, subject)
+		assert.Contains(t, wholemsg, "\r\n"+expBody+"\r\n")
+	}
+
+	expect(t, issue, comment, doer, models.ActionCommentIssue, false,
+		"{{.SubjectPrefix}}[{{.Repo}}]: @{{.Doer.Name}} commented on #{{.Issue.Index}} - {{.Issue.Title}}",
+		"//{{.ActionType}},{{.ActionName}},{{if .IsMention}}norender{{end}}//",
+		"Re: [user2/repo1]: @user2 commented on #1 - issue1",
+		"//issue,comment,//")
+
+	expect(t, issue, comment, doer, models.ActionCommentIssue, true,
+		"{{if .IsMention}}must render{{end}}",
+		"//subject is: {{.Subject}}//",
+		"must render",
+		"//subject is: must render//")
+
+	expect(t, issue, comment, doer, models.ActionCommentIssue, true,
+		"{{.FallbackSubject}}",
+		"//{{.SubjectPrefix}}//",
+		"Re: [user2/repo1] issue1 (#1)",
+		"//Re: //")
+}
diff --git a/services/release/release.go b/services/release/release.go
index a3f027c949..681e8c0d9a 100644
--- a/services/release/release.go
+++ b/services/release/release.go
@@ -12,10 +12,9 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/notification"
 	"code.gitea.io/gitea/modules/process"
-	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
-	"code.gitea.io/gitea/modules/webhook"
 )
 
 func createTag(gitRepo *git.Repository, rel *models.Release) error {
@@ -81,19 +80,7 @@ func CreateRelease(gitRepo *git.Repository, rel *models.Release, attachmentUUIDs
 	}
 
 	if !rel.IsDraft {
-		if err := rel.LoadAttributes(); err != nil {
-			log.Error("LoadAttributes: %v", err)
-		} else {
-			mode, _ := models.AccessLevel(rel.Publisher, rel.Repo)
-			if err := webhook.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{
-				Action:     api.HookReleasePublished,
-				Release:    rel.APIFormat(),
-				Repository: rel.Repo.APIFormat(mode),
-				Sender:     rel.Publisher.APIFormat(),
-			}); err != nil {
-				log.Error("PrepareWebhooks: %v", err)
-			}
-		}
+		notification.NotifyNewRelease(rel)
 	}
 
 	return nil
@@ -114,20 +101,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea
 		log.Error("AddReleaseAttachments: %v", err)
 	}
 
-	if err = rel.LoadAttributes(); err != nil {
-		return err
-	}
-
-	// even if attachments added failed, hooks will be still triggered
-	mode, _ := models.AccessLevel(doer, rel.Repo)
-	if err1 := webhook.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{
-		Action:     api.HookReleaseUpdated,
-		Release:    rel.APIFormat(),
-		Repository: rel.Repo.APIFormat(mode),
-		Sender:     doer.APIFormat(),
-	}); err1 != nil {
-		log.Error("PrepareWebhooks: %v", err)
-	}
+	notification.NotifyUpdateRelease(doer, rel)
 
 	return err
 }
@@ -183,15 +157,7 @@ func DeleteReleaseByID(id int64, doer *models.User, delTag bool) error {
 		}
 	}
 
-	mode, _ := models.AccessLevel(doer, rel.Repo)
-	if err := webhook.PrepareWebhooks(rel.Repo, models.HookEventRelease, &api.ReleasePayload{
-		Action:     api.HookReleaseDeleted,
-		Release:    rel.APIFormat(),
-		Repository: rel.Repo.APIFormat(mode),
-		Sender:     doer.APIFormat(),
-	}); err != nil {
-		log.Error("PrepareWebhooks: %v", err)
-	}
+	notification.NotifyDeleteRelease(doer, rel)
 
 	return nil
 }
diff --git a/templates/mail/issue/assigned.tmpl b/templates/mail/issue/assigned.tmpl
index ab06ade1f4..997e2447fc 100644
--- a/templates/mail/issue/assigned.tmpl
+++ b/templates/mail/issue/assigned.tmpl
@@ -6,11 +6,11 @@
 </head>
 
 <body>
-	<p>@{{.Doer.Name}} assigned you to the {{if eq .Issue.IsPull true}}pull request{{else}}issue{{end}} <a href="{{.Link}}">#{{.Issue.Index}}</a> in repository {{.Issue.Repo.FullName}}.</p>
+	<p>@{{.Doer.Name}} assigned you to the {{if .IsPull}}pull request{{else}}issue{{end}} <a href="{{.Link}}">#{{.Issue.Index}}</a> in repository {{.Repo}}.</p>
     <p>
         ---
         <br>
-        <a href="{{.Link}}">View it on Gitea</a>.
+        <a href="{{.Link}}">View it on {{AppName}}</a>.
     </p>
 
 </body>
diff --git a/templates/mail/issue/comment.tmpl b/templates/mail/issue/comment.tmpl
deleted file mode 100644
index cc86addaf0..0000000000
--- a/templates/mail/issue/comment.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-	<title>{{.Subject}}</title>
-</head>
-
-<body>
-	<p>{{.Body | Str2html}}</p>
-	<p>
-		---
-		<br>
-		<a href="{{.Link}}">View it on Gitea</a>.
-	</p>
-</body>
-</html>
diff --git a/templates/mail/issue/default.tmpl b/templates/mail/issue/default.tmpl
new file mode 100644
index 0000000000..ee15d6d8e1
--- /dev/null
+++ b/templates/mail/issue/default.tmpl
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<title>{{.Subject}}</title>
+</head>
+
+<body>
+	{{if .IsMention}}<p>@{{.Doer.Name}} mentioned you:</p>{{end}}
+	<p>
+		{{- if eq .Body ""}}
+			{{if eq .ActionName "new"}}
+				Created #{{.Issue.Index}}.
+			{{else if eq .ActionName "close"}}
+				Closed #{{.Issue.Index}}.
+			{{else if eq .ActionName "reopen"}}
+				Reopened #{{.Issue.Index}}.
+			{{else}}
+				Empty comment on #{{.Issue.Index}}.
+			{{end}}
+		{{else}}
+			{{.Body | Str2html}}
+		{{end -}}
+	</p>
+	<p>
+		---
+		<br>
+		<a href="{{.Link}}">View it on {{AppName}}</a>.
+	</p>
+</body>
+</html>
diff --git a/templates/mail/issue/mention.tmpl b/templates/mail/issue/mention.tmpl
deleted file mode 100644
index 032eea053d..0000000000
--- a/templates/mail/issue/mention.tmpl
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-	<title>{{.Subject}}</title>
-</head>
-
-<body>
-	<p>@{{.Doer.Name}} mentioned you:</p>
-	<p>{{.Body | Str2html}}</p>
-	<p>
-		---
-		<br>
-		<a href="{{.Link}}">View it on Gitea</a>.
-	</p>
-</body>
-</html>
diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl
index fb79c9b7fb..e50a1777d2 100644
--- a/templates/org/team/new.tmpl
+++ b/templates/org/team/new.tmpl
@@ -24,6 +24,24 @@
 						<span class="help">{{.i18n.Tr "org.team_desc_helper"}}</span>
 					</div>
 					{{if not (eq .Team.LowerName "owners")}}
+						<div class="grouped field">
+							<label>{{.i18n.Tr "org.team_access_desc"}}</label>
+							<br>
+							<div class="field">
+								<div class="ui radio checkbox">
+									<input type="radio" name="repo_access" value="specific" {{if not .Team.IncludesAllRepositories}}checked{{end}}>
+									<label>{{.i18n.Tr "org.teams.specific_repositories"}}</label>
+									<span class="help">{{.i18n.Tr "org.teams.specific_repositories_helper"}}</span>
+								</div>
+							</div>
+							<div class="field">
+								<div class="ui radio checkbox">
+									<input type="radio" name="repo_access" value="all" {{if .Team.IncludesAllRepositories}}checked{{end}}>
+									<label>{{.i18n.Tr "org.teams.all_repositories"}}</label>
+									<span class="help">{{.i18n.Tr "org.teams.all_repositories_helper"}}</span>
+								</div>
+							</div>
+						</div>
 						<div class="grouped field">
 							<label>{{.i18n.Tr "org.team_permission_desc"}}</label>
 							<br>
diff --git a/templates/org/team/repositories.tmpl b/templates/org/team/repositories.tmpl
index eeb86564fd..1b2a411c2b 100644
--- a/templates/org/team/repositories.tmpl
+++ b/templates/org/team/repositories.tmpl
@@ -7,7 +7,7 @@
 			{{template "org/team/sidebar" .}}
 			<div class="ui ten wide column">
 				{{template "org/team/navbar" .}}
-				{{$canAddRemove := and $.IsOrganizationOwner (not (eq $.Team.LowerName "owners"))}}
+				{{$canAddRemove := and $.IsOrganizationOwner (not $.Team.IncludesAllRepositories)}}
 				{{if $canAddRemove}}
 					<div class="ui attached segment">
 						<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/add" method="post">
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl
index 846613e32e..dd189df5f3 100644
--- a/templates/org/team/sidebar.tmpl
+++ b/templates/org/team/sidebar.tmpl
@@ -22,11 +22,23 @@
 			{{if eq .Team.LowerName "owners"}}
 				{{.i18n.Tr "org.teams.owners_permission_desc" | Str2html}}
 			{{else if (eq .Team.Authorize 1)}}
-				{{.i18n.Tr "org.teams.read_permission_desc" | Str2html}}
+				{{if .Team.IncludesAllRepositories}}
+					{{.i18n.Tr "org.teams.all_repositories_read_permission_desc" | Str2html}}
+				{{else}}
+					{{.i18n.Tr "org.teams.read_permission_desc" | Str2html}}
+				{{end}}
 			{{else if (eq .Team.Authorize 2)}}
-				{{.i18n.Tr "org.teams.write_permission_desc" | Str2html}}
+				{{if .Team.IncludesAllRepositories}}
+					{{.i18n.Tr "org.teams.all_repositories_write_permission_desc" | Str2html}}
+				{{else}}
+					{{.i18n.Tr "org.teams.write_permission_desc" | Str2html}}
+				{{end}}
 			{{else if (eq .Team.Authorize 3)}}
-				{{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}}
+				{{if .Team.IncludesAllRepositories}}
+					{{.i18n.Tr "org.teams.all_repositories_admin_permission_desc" | Str2html}}
+				{{else}}
+					{{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}}
+				{{end}}
 			{{end}}
 		</div>
 	</div>
diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl
index d68e6dac26..b4c2d88bfd 100644
--- a/templates/repo/issue/list.tmpl
+++ b/templates/repo/issue/list.tmpl
@@ -258,8 +258,9 @@
 							</span>
 						{{end}}
 						{{if ne .DeadlineUnix 0}}
-							<span class="octicon octicon-calendar"></span>
-							<span{{if .IsOverdue}} class="overdue"{{end}}>{{.DeadlineUnix.FormatShort}}</span>
+							<span class="due-date poping up" data-content="{{$.i18n.Tr "repo.issues.due_date"}}" data-variation="tiny inverted" data-position="right center">
+								<span class="octicon octicon-calendar"></span><span{{if .IsOverdue}} class="overdue"{{end}}>{{.DeadlineUnix.FormatShort}}</span>
+							</span>
 						{{end}}
 						{{range .Assignees}}
 							<a class="ui right assignee poping up" href="{{.HomeLink}}" data-content="{{.Name}}" data-variation="inverted" data-position="left center">
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 43c92ea55e..1fcf48d8bc 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -8242,6 +8242,10 @@
           "type": "string",
           "x-go-name": "Description"
         },
+        "includes_all_repositories": {
+          "type": "boolean",
+          "x-go-name": "IncludesAllRepositories"
+        },
         "name": {
           "type": "string",
           "x-go-name": "Name"
@@ -8801,6 +8805,10 @@
           "type": "string",
           "x-go-name": "Description"
         },
+        "includes_all_repositories": {
+          "type": "boolean",
+          "x-go-name": "IncludesAllRepositories"
+        },
         "name": {
           "type": "string",
           "x-go-name": "Name"
@@ -10457,6 +10465,10 @@
           "format": "int64",
           "x-go-name": "ID"
         },
+        "includes_all_repositories": {
+          "type": "boolean",
+          "x-go-name": "IncludesAllRepositories"
+        },
         "name": {
           "type": "string",
           "x-go-name": "Name"
diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl
index ac5f10a599..5418c6de47 100644
--- a/templates/user/dashboard/issues.tmpl
+++ b/templates/user/dashboard/issues.tmpl
@@ -126,6 +126,11 @@
 										<span class="octicon octicon-checklist"></span> {{$tasksDone}} / {{$tasks}} <span class="progress-bar"><span class="progress" style="width:calc(100% * {{$tasksDone}} / {{$tasks}});"></span></span>
 									</span>
 								{{end}}
+								{{if ne .DeadlineUnix 0}}
+									<span class="due-date poping up" data-content="{{$.i18n.Tr "repo.issues.due_date"}}" data-variation="tiny inverted" data-position="right center">
+										<span class="octicon octicon-calendar"></span><span{{if .IsOverdue}} class="overdue"{{end}}>{{.DeadlineUnix.FormatShort}}</span>
+									</span>
+								{{end}}
 							</p>
 						</li>
 					{{end}}