diff --git a/cmd/web.go b/cmd/web.go
index 55b6bf087..91d675245 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -373,6 +373,7 @@ func runWeb(ctx *cli.Context) {
m.Group("/:username/:reponame", func() {
m.Get("/settings", repo.Settings)
m.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
+ m.Post("/commit/comment/:action/:commitId", repo.CreateCommitComment)
m.Group("/settings", func() {
m.Route("/collaboration", "GET,POST", repo.SettingsCollaboration)
m.Get("/hooks", repo.Webhooks)
@@ -439,6 +440,7 @@ func runWeb(ctx *cli.Context) {
m.Get("/src/*", repo.Home)
m.Get("/raw/*", repo.SingleDownload)
m.Get("/commits/*", repo.RefCommits)
+ m.Get("/commit/comment/*", repo.GetCommentForm)
m.Get("/commit/*", repo.Diff)
}, middleware.RepoRef())
diff --git a/conf/locale/locale_de-DE.ini b/conf/locale/locale_de-DE.ini
index dbf31a329..edc321134 100755
--- a/conf/locale/locale_de-DE.ini
+++ b/conf/locale/locale_de-DE.ini
@@ -323,6 +323,9 @@ commits.message=Mitteilung
commits.date=Datum
commits.older=Älter
commits.newer=Neuer
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings=Einstellungen
settings.options=Optionen
diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini
index 660fb253b..474a63c96 100644
--- a/conf/locale/locale_en-US.ini
+++ b/conf/locale/locale_en-US.ini
@@ -325,6 +325,9 @@ commits.message = Message
commits.date = Date
commits.older = Older
commits.newer = Newer
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings = Settings
settings.options = Options
diff --git a/conf/locale/locale_fr-CA.ini b/conf/locale/locale_fr-CA.ini
index 833d8e1dc..cb402b6ca 100755
--- a/conf/locale/locale_fr-CA.ini
+++ b/conf/locale/locale_fr-CA.ini
@@ -323,6 +323,9 @@ commits.message=Message
commits.date=Date
commits.older=Précédemment
commits.newer=Récemment
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings=Paramètres
settings.options=Options
diff --git a/conf/locale/locale_ja-JP.ini b/conf/locale/locale_ja-JP.ini
index 1d1d61493..9b16e58d9 100755
--- a/conf/locale/locale_ja-JP.ini
+++ b/conf/locale/locale_ja-JP.ini
@@ -323,6 +323,9 @@ commits.message=メッセージ
commits.date=日付
commits.older=古い
commits.newer=新しい
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings=設定
settings.options=オプション
diff --git a/conf/locale/locale_lv-LV.ini b/conf/locale/locale_lv-LV.ini
index 19373118f..25b931a59 100755
--- a/conf/locale/locale_lv-LV.ini
+++ b/conf/locale/locale_lv-LV.ini
@@ -323,6 +323,9 @@ commits.message=Ziņojums
commits.date=Datums
commits.older=Vecāki
commits.newer=Jaunāki
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings=Iestatījumi
settings.options=Opcijas
diff --git a/conf/locale/locale_nl-NL.ini b/conf/locale/locale_nl-NL.ini
index f0bc7c70a..b8d23c5f1 100755
--- a/conf/locale/locale_nl-NL.ini
+++ b/conf/locale/locale_nl-NL.ini
@@ -323,6 +323,9 @@ commits.message=Bericht
commits.date=Datum
commits.older=Ouder
commits.newer=Nieuwer
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings=Instellingen
settings.options=Opties
diff --git a/conf/locale/locale_ru-RU.ini b/conf/locale/locale_ru-RU.ini
index b0da5c550..d1e9bf9f0 100755
--- a/conf/locale/locale_ru-RU.ini
+++ b/conf/locale/locale_ru-RU.ini
@@ -323,6 +323,9 @@ commits.message=Сообщение
commits.date=Дата
commits.older=Раньше
commits.newer=Новее
+commits.comment.add = Отправить комментарий
+commits.comment.comment_line = Комментировать строку
+commits.comment.required_field = Не заполнено обязательное поле
settings=Настройки
settings.options=Опции
diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini
index 49c013a9f..3edfe737d 100755
--- a/conf/locale/locale_zh-CN.ini
+++ b/conf/locale/locale_zh-CN.ini
@@ -323,6 +323,9 @@ commits.message=备注
commits.date=提交日期
commits.older=更旧的提交
commits.newer=更新的提交
+commits.comment.add = Comment
+commits.comment.comment_line = Comment line
+commits.comment.required_field = Missing required field
settings=仓库设置
settings.options=基本设置
diff --git a/models/action.go b/models/action.go
index 318a5f6ad..49e64dc18 100644
--- a/models/action.go
+++ b/models/action.go
@@ -33,6 +33,7 @@ const (
TRANSFER_REPO // 8
PUSH_TAG // 9
COMMENT_ISSUE // 10
+ COMMENT_COMMIT // 11
)
var (
@@ -143,7 +144,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
message := fmt.Sprintf(`%s`, url, c.Message)
- if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message, nil); err != nil {
+ if _, err = CreateComment(userId, issue.RepoId, issue.Id, "", "", COMMIT, message, nil); err != nil {
return err
}
}
@@ -194,7 +195,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// If commit happened in the referenced repository, it means the issue can be closed.
- if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, CLOSE, "", nil); err != nil {
+ if _, err = CreateComment(userId, repoId, issue.Id, "", "", CLOSE, "", nil); err != nil {
return err
}
}
diff --git a/models/issue.go b/models/issue.go
index c756e4975..b8a6f2724 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -884,14 +884,14 @@ type Comment struct {
PosterId int64
Poster *User `xorm:"-"`
IssueId int64
- CommitId int64
- Line int64
+ CommitId string
+ Line string
Content string `xorm:"TEXT"`
Created time.Time `xorm:"CREATED"`
}
// CreateComment creates comment of issue or commit.
-func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
+func CreateComment(userId, repoId, issueId int64, commitId, line string, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
@@ -971,6 +971,13 @@ func (c *Comment) Attachments() []*Attachment {
return a
}
+// GetCommitComments returns list of comment by given commit id.
+func GetCommitComments(commitId string) ([]Comment, error) {
+ comments := make([]Comment, 0, 10)
+ err := x.Asc("created").Find(&comments, &Comment{CommitId: commitId})
+ return comments, err
+}
+
func (c *Comment) AfterDelete() {
_, err := DeleteAttachmentsByComment(c.Id, true)
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 3586e5d0b..7bc291d2f 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -2,6 +2,7 @@ package migrations
import (
"errors"
+ "time"
"github.com/go-xorm/xorm"
)
@@ -16,7 +17,9 @@ type Version struct {
// This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, replace it with "expiredMigration"
-var migrations = []migration{}
+var migrations = []migration{
+ prepareToCommitComments,
+}
// Migrate database to current version
func Migrate(x *xorm.Engine) error {
@@ -51,3 +54,24 @@ func Migrate(x *xorm.Engine) error {
func expiredMigration(x *xorm.Engine) error {
return errors.New("You are migrating from a too old gogs version")
}
+
+func prepareToCommitComments(x *xorm.Engine) error {
+ type Comment struct {
+ Id int64
+ Type int64
+ PosterId int64
+ IssueId int64
+ CommitId string
+ Line string
+ Content string `xorm:"TEXT"`
+ Created time.Time `xorm:"CREATED"`
+ }
+ x.Sync(new(Comment))
+
+ sql := `UPDATE comment SET commit_id = '', line = '' WHERE commit_id = '0' AND line = '0'`
+ _, err := x.Exec(sql)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/modules/base/template.go b/modules/base/template.go
index f3fa13857..a08578528 100644
--- a/modules/base/template.go
+++ b/modules/base/template.go
@@ -155,6 +155,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
},
"DiffTypeToStr": DiffTypeToStr,
"DiffLineTypeToStr": DiffLineTypeToStr,
+ "DiffLinePosToStr": DiffLinePosToStr,
"ShortSha": ShortSha,
"Md5": EncodeMd5,
"ActionContent2Commits": ActionContent2Commits,
@@ -234,6 +235,10 @@ func DiffLineTypeToStr(diffType int) string {
return "same"
}
+func DiffLinePosToStr(file int, section int, line int) string {
+ return fmt.Sprintf("%vL%v%v", file + 1, section, line)
+}
+
func Oauth2Icon(t int) string {
switch t {
case 1:
diff --git a/modules/mailer/mail.go b/modules/mailer/mail.go
index ae8ce6531..004feedb8 100644
--- a/modules/mailer/mail.go
+++ b/modules/mailer/mail.go
@@ -154,8 +154,33 @@ func SendResetPasswdMail(r macaron.Render, u *models.User) {
SendAsync(&msg)
}
-// SendIssueNotifyMail sends mail notification of all watchers of repository.
+// SendIssueNotifyMail sends mail notification to all watchers of repository.
func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
+
+ subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index)
+ content := fmt.Sprintf("%s
-
View it on Gogs.",
+ base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
+ setting.AppUrl, owner.Name, repo.Name, issue.Index)
+
+ msgInfo := fmt.Sprintf("Subject: %s, send issue notify emails", subject)
+
+ return SendNotifyMail(u, repo, subject, content, msgInfo)
+}
+
+// SendCommentNotifyMail sends mail notification to all watchers of repository.
+func SendCommentNotifyMail(u, owner *models.User, repo *models.Repository, comment *models.Comment) ([]string, error) {
+
+ subject := fmt.Sprintf("[%s] New comment to commit %s", repo.Name, comment.CommitId)
+ content := fmt.Sprintf("%s
-
View it on Gogs.",
+ base.RenderSpecialLink([]byte(comment.Content), owner.Name+"/"+repo.Name),
+ setting.AppUrl, owner.Name, repo.Name, comment.CommitId)
+
+ msgInfo := fmt.Sprintf("Subject: %s, send issue notify emails", subject)
+
+ return SendNotifyMail(u, repo, subject, content, msgInfo)
+}
+
+func SendNotifyMail(u *models.User, repo *models.Repository, subject, content, msgInfo string) ([]string, error) {
ws, err := models.GetWatchers(repo.Id)
if err != nil {
return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error())
@@ -178,12 +203,8 @@ func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *
return tos, nil
}
- subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index)
- content := fmt.Sprintf("%s
-
View it on Gogs.",
- base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
- setting.AppUrl, owner.Name, repo.Name, issue.Index)
msg := NewMailMessageFrom(tos, u.Email, subject, content)
- msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
+ msg.Info = msgInfo
SendAsync(&msg)
return tos, nil
}
@@ -213,6 +234,30 @@ func SendIssueMentionMail(r macaron.Render, u, owner *models.User,
return nil
}
+func SendCommentMentionMail(r macaron.Render, u, owner *models.User,
+ repo *models.Repository, comment *models.Comment, tos []string) error {
+
+ if len(tos) == 0 {
+ return nil
+ }
+
+ subject := fmt.Sprintf("[%s] Commit %s", repo.Name, comment.CommitId)
+
+ data := GetMailTmplData(nil)
+ data["IssueLink"] = fmt.Sprintf("%s/%s/commit/%s", owner.Name, repo.Name, comment.CommitId)
+ data["Subject"] = subject
+
+ body, err := r.HTMLString(string(NOTIFY_MENTION), data)
+ if err != nil {
+ return fmt.Errorf("mail.SendCommentMentionMail(fail to render): %v", err)
+ }
+
+ msg := NewMailMessageFrom(tos, u.Email, subject, body)
+ msg.Info = fmt.Sprintf("Subject: %s, send comment mention emails", subject)
+ SendAsync(&msg)
+ return nil
+}
+
// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(r macaron.Render, u, owner *models.User,
repo *models.Repository) error {
diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css
index 6a6c10ddb..957c6d1ec 100644
--- a/public/ng/css/gogs.css
+++ b/public/ng/css/gogs.css
@@ -278,6 +278,9 @@ img.avatar-100 {
padding-left: 0;
list-style: none;
}
+.text-danger {
+ color: #a94442;
+}
.markdown {
background-color: white;
font-size: 16px;
@@ -1449,42 +1452,9 @@ The register and sign-in page style
cursor: pointer;
display: block;
}
-.code-view .lines-code b:hover {
- width: 22px;
- height: 22px;
- line-height: 22px;
- font-size: 22px;
- margin: -1px -12px -2px -11px;
-}
-.code-view .lines-code b {
- position: relative;
- z-index: 5;
- float: left;
- width: 20px;
- height: 20px;
- margin: 0px -10px -1px -10px;
- line-height: 20px;
- color: #fff;
- text-align: center;
- text-indent: 0;
- cursor: pointer;
- background-color: #4183c4;
- background-color: #4183c4;
- background-image: -webkit-linear-gradient(#5490ca, #4183c4);
- background-image: linear-gradient(#5490ca, #4183c4);
- background-repeat: repeat-x;
- border-radius: 3px;
- box-shadow: 0 1px 4px rgba(0,0,0,0.15);
- opacity: 0;
- -webkit-transform: scale(0.8, 0.8);
- -ms-transform: scale(0.8, 0.8);
- transform: scale(0.8, 0.8);
- -webkit-transition: -webkit-transform 0.1s ease-in-out;
- transition: transform 0.1s ease-in-out;
- font-size: 20px;
-}
-.code-view .lines-code b.ishovered {
- opacity:1;
+
+.code-view .comment {
+ background-color: #fafafa;
}
.code-view .lines-code > pre {
border: none;
@@ -1499,6 +1469,85 @@ The register and sign-in page style
.code-view .lines-code > pre > ol.linenums > li.active {
background: #ffffdd;
}
+.code-view .lines-code > b {
+ position: relative;
+ float: left;
+ width: 16px;
+ height: 16px;
+ margin: 2px -8px 0px -8px;
+ line-height: 16px;
+ color: #fff;
+ text-align: center;
+ cursor: pointer;
+ background-color: #428bca;
+ border-radius: 2px;
+ opacity: 0;
+ font-size: 16px;
+}
+.code-view .lines-code > b.ishovered {
+ opacity: 1;
+}
+.code-view .lines-code > b:hover {
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+ font-size: 20px;
+ margin: 1px -10px 0px -9px;
+}
+.code-view .lines-code .commit-comment {
+ margin: 5px;
+ max-width: 800px;
+}
+.code-view .lines-code .commit-comment .label-default {
+ background-color: #999;
+ font-size: 80%;
+ padding: 5px;
+ line-height: 10px;
+ margin-right: 5px;
+}
+.code-view .lines-code .commit-comment .commit-content {
+ border-bottom-width: 1px;
+}
+.code-view .lines-code .commit-comment .user.pull-left {
+ margin: 5px;
+}
+.code-view .lines-code .commit-comment .user .avatar {
+ width: 26px;
+ height: 26px;
+ margin-right: 3px;
+}
+.code-view .lines-code .commit-comment .panel-heading {
+ padding-top: 4px;
+ padding-bottom: 0px;
+ font-weight: normal;
+ background-color: #FAFAFA;
+ border-bottom: 1px solid #DDD;
+}
+.code-view .lines-code .commit-comment .panel-default .panel-heading {
+ font-weight: bold;
+}
+.code-view .lines-code .commit-comment .markdown {
+ font-size: 14px;
+ padding: 12px 15px;
+}
+.code-view .panel-default {
+ border-color: #d8d8d8;
+}
+.code-view .alert {
+ padding: 8px;
+ margin-bottom: 18px;
+ border-radius: 2px;
+ border: 1px solid transparent;
+ margin: 5px;
+}
+.code-view .alert-warning {
+ background-color: #f9edbe;
+ border-color: #f0c36d;
+ color: #333;
+}
+.code-view #commit-conversation {
+ margin: 10px;
+}
.repo-setting-zone {
padding: 30px;
}
@@ -1664,6 +1713,10 @@ The register and sign-in page style
background-color: #ffe2dd !important;
border-color: #e9aeae !important;
}
+.diff-file-box .code-diff tbody tr.del-code td.selected-line,
+.diff-file-box .code-diff tbody tr.del-code td.selected-line pre {
+ background-color: #ffffdd !important;
+}
.diff-file-box .code-diff tbody tr.add-code td,
.diff-file-box .code-diff tbody tr.add-code pre {
background-color: #d1ffd6 !important;
@@ -1678,6 +1731,10 @@ The register and sign-in page style
background-color: #FFF8D2 !important;
border-color: #F0DB88 !important;
}
+.diff-file-box .code-diff tbody tr.add-comment:hover td,
+.diff-file-box .code-diff tbody tr.add-comment:hover pre {
+ background-color: #FFFFFF !important;
+}
.compare-head-box {
margin-top: 10px;
}
diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js
index 92f560ecf..0f87adb49 100644
--- a/public/ng/js/gogs.js
+++ b/public/ng/js/gogs.js
@@ -238,6 +238,11 @@ var Gogs = {};
$.changeHash('#' + $select.attr('rel'));
}
+ function prepareToForm() {
+ $('.add-comment').hide('fast', function(){ $(this).remove(); });
+ $('button.answer').show();
+ }
+
$(document).on('click', '.code-diff .lines-num span', function (e) {
var $select = $(this);
var $list = $select.parent().siblings('.lines-code').parents().find('td.lines-num > span');
@@ -249,6 +254,57 @@ var Gogs = {};
$.deSelect();
});
+ $('.code-diff .lines-code > b, .code-diff .lines-code > button.answer').click(function () {
+ prepareToForm();
+ var commit = document.location.href.match(/([a-zA-Z0-9:\/\/]+)\/commit\/([a-z0-9]+)/);
+ var lineNum;
+ if ($(this).prop("tagName") == "BUTTON") {
+ lineNum = $(this).attr('rel');
+ } else {
+ lineNum = $(this).parent().prev().find('span').attr('rel');
+ }
+ $('button[rel='+lineNum+']').fadeOut();
+ lineNum = lineNum.substr(5);
+ var commentTr = $(".comment-"+lineNum);
+ if (commit) {
+ var elem = (commentTr.length > 0) ? commentTr : $(this).parents('tr');
+ var url = commit[1] + '/commit/comment/' + commit[2];
+ elem.after(
+ $('
- {{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}} + {{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}} | - {{if $line.RightIdx}}{{$line.RightIdx}}{{end}} + {{if $line.RightIdx}}{{$line.RightIdx}}{{end}} |
- +
+ {{if $.SignedUser}}{{if $k}}{{end}}{{end}}
{{$line.Content}} |
+ {{range (index $.Comments $lineNum)}}
+
+
+
+
+
+
+ {{end}}
+
+
+
+ {{.Poster.Name}} + {{TimeSince .Created $.Lang}} + + {{if $.IsRepositoryOwner}} + {{$.i18n.Tr "repo.owner"}} + {{end}} + + + + + +
+ {{if len .Content}}
+ {{Str2html .Content}}
+ {{else}}
+ No comment entered
+ {{end}}
+
+ |
+
+- {{$.i18n.Tr "repo.release.write"}}
+ - {{$.i18n.Tr "repo.release.preview"}}
+
+