Merge 820a995d13
into 7be5935c55
This commit is contained in:
commit
f519972513
280
models/action.go
280
models/action.go
|
@ -49,14 +49,43 @@ const (
|
|||
ActionDeleteBranch // 17
|
||||
)
|
||||
|
||||
// KeywordMaskType represents the bitmask of types of keywords found in a message.
|
||||
type KeywordMaskType int
|
||||
|
||||
// Possible bitmask types for keywords that can be found.
|
||||
const (
|
||||
KeywordReference KeywordMaskType = 1 << iota // 1 = 1 << 0
|
||||
KeywordReopen // 2 = 1 << 1
|
||||
KeywordClose // 4 = 1 << 2
|
||||
)
|
||||
|
||||
// IssueKeywordsToFind represents a pairing of a pattern to use to find keywords in message and the keywords bitmask value.
|
||||
type IssueKeywordsToFind struct {
|
||||
Pattern *regexp.Regexp
|
||||
KeywordMask KeywordMaskType
|
||||
}
|
||||
|
||||
var (
|
||||
// Same as Github. See
|
||||
// https://help.github.com/articles/closing-issues-via-commit-messages
|
||||
issueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
|
||||
issueReopenKeywords = []string{"reopen", "reopens", "reopened"}
|
||||
|
||||
issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp
|
||||
issueReferenceKeywordsPat *regexp.Regexp
|
||||
// populate with details to find keywords for reference, reopen, close
|
||||
issueKeywordsToFind = []*IssueKeywordsToFind{
|
||||
{
|
||||
Pattern: regexp.MustCompile(issueRefRegexpStr),
|
||||
KeywordMask: KeywordReference,
|
||||
},
|
||||
{
|
||||
Pattern: regexp.MustCompile(assembleKeywordsPattern(issueReopenKeywords)),
|
||||
KeywordMask: KeywordReopen,
|
||||
},
|
||||
{
|
||||
Pattern: regexp.MustCompile(assembleKeywordsPattern(issueCloseKeywords)),
|
||||
KeywordMask: KeywordClose,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const issueRefRegexpStr = `(?:\S+/\S=)?#\d+`
|
||||
|
@ -65,12 +94,6 @@ func assembleKeywordsPattern(words []string) string {
|
|||
return fmt.Sprintf(`(?i)(?:%s) %s`, strings.Join(words, "|"), issueRefRegexpStr)
|
||||
}
|
||||
|
||||
func init() {
|
||||
issueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueCloseKeywords))
|
||||
issueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueReopenKeywords))
|
||||
issueReferenceKeywordsPat = regexp.MustCompile(issueRefRegexpStr)
|
||||
}
|
||||
|
||||
// Action represents user operation type and other information to
|
||||
// repository. It implemented interface base.Actioner so that can be
|
||||
// used in template render.
|
||||
|
@ -435,70 +458,125 @@ func getIssueFromRef(repo *Repository, ref string) (*Issue, error) {
|
|||
return issue, nil
|
||||
}
|
||||
|
||||
// findIssueReferencesInString iterates over the keywords to find in a message and accumulates the findings into refs
|
||||
func findIssueReferencesInString(message string, repo *Repository) (map[int64]KeywordMaskType, error) {
|
||||
refs := make(map[int64]KeywordMaskType)
|
||||
for _, kwToFind := range issueKeywordsToFind {
|
||||
for _, ref := range kwToFind.Pattern.FindAllString(message, -1) {
|
||||
issue, err := getIssueFromRef(repo, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if issue != nil {
|
||||
refs[issue.ID] |= kwToFind.KeywordMask
|
||||
}
|
||||
}
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
// changeIssueStatus encapsulates the logic for changing the status of an issue based on what keywords are marked in the keyword mask
|
||||
func changeIssueStatus(mask KeywordMaskType, doer *User, repo *Repository, issue *Issue) error {
|
||||
// take no action if both KeywordClose and KeywordOpen are set
|
||||
switch mask & (KeywordReopen | KeywordClose) {
|
||||
case KeywordClose:
|
||||
if issue.RepoID == repo.ID && !issue.IsClosed {
|
||||
if err := issue.ChangeStatus(doer, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case KeywordReopen:
|
||||
if issue.RepoID == repo.ID && issue.IsClosed {
|
||||
if err := issue.ChangeStatus(doer, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIssuesComment checks if issues are manipulated by a comment
|
||||
func UpdateIssuesComment(doer *User, repo *Repository, commentIssue *Issue, comment *Comment, canOpenClose bool) error {
|
||||
var refString string
|
||||
if comment != nil {
|
||||
refString = comment.Content
|
||||
} else {
|
||||
refString = commentIssue.Title + ": " + commentIssue.Content
|
||||
}
|
||||
|
||||
uniqueID := fmt.Sprintf("%d", commentIssue.ID)
|
||||
if comment != nil {
|
||||
uniqueID += fmt.Sprintf("@%d", comment.ID)
|
||||
}
|
||||
|
||||
refs, err := findIssueReferencesInString(refString, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for id, mask := range refs {
|
||||
issue, err := GetIssueByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if issue == nil || issue.ID == commentIssue.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
if (mask & KeywordReference) == KeywordReference {
|
||||
if comment != nil {
|
||||
err = CreateCommentRefComment(doer, repo, issue, fmt.Sprintf(`%d`, comment.ID), base.EncodeSha1(uniqueID))
|
||||
} else if commentIssue.IsPull {
|
||||
err = CreatePullRefComment(doer, repo, issue, fmt.Sprintf(`%d`, commentIssue.ID), base.EncodeSha1(uniqueID))
|
||||
} else {
|
||||
err = CreateIssueRefComment(doer, repo, issue, fmt.Sprintf(`%d`, commentIssue.ID), base.EncodeSha1(uniqueID))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if canOpenClose {
|
||||
if err = changeIssueStatus(mask, doer, repo, issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIssuesCommit checks if issues are manipulated by commit message.
|
||||
func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) error {
|
||||
func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, commitsAreMerged bool) error {
|
||||
// Commits are appended in the reverse order.
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
c := commits[i]
|
||||
|
||||
refMarked := make(map[int64]bool)
|
||||
for _, ref := range issueReferenceKeywordsPat.FindAllString(c.Message, -1) {
|
||||
issue, err := getIssueFromRef(repo, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if issue == nil || refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, c.Message)
|
||||
if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
refs, err := findIssueReferencesInString(c.Message, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
refMarked = make(map[int64]bool)
|
||||
// FIXME: can merge this one and next one to a common function.
|
||||
for _, ref := range issueCloseKeywordsPat.FindAllString(c.Message, -1) {
|
||||
issue, err := getIssueFromRef(repo, ref)
|
||||
for id, mask := range refs {
|
||||
issue, err := GetIssueByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if issue == nil || refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
if issue.RepoID != repo.ID || issue.IsClosed {
|
||||
if issue == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(doer, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// It is conflict to have close and reopen at same time, so refsMarked doesn't need to reinit here.
|
||||
for _, ref := range issueReopenKeywordsPat.FindAllString(c.Message, -1) {
|
||||
issue, err := getIssueFromRef(repo, ref)
|
||||
if err != nil {
|
||||
return err
|
||||
if (mask & KeywordReference) == KeywordReference {
|
||||
message := fmt.Sprintf("%d %s", repo.ID, c.Sha1)
|
||||
if err = CreateCommitRefComment(doer, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if issue == nil || refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
if issue.RepoID != repo.ID || !issue.IsClosed {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(doer, repo, false); err != nil {
|
||||
return err
|
||||
if commitsAreMerged {
|
||||
if err = changeIssueStatus(mask, doer, repo, issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -560,8 +638,8 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
|||
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
|
||||
}
|
||||
|
||||
if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil {
|
||||
log.Error(4, "updateIssuesCommit: %v", err)
|
||||
if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, true); err != nil {
|
||||
log.Error(4, "UpdateIssuesCommit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -715,21 +793,91 @@ func TransferRepoAction(doer, oldOwner *User, repo *Repository) error {
|
|||
return transferRepoAction(x, doer, oldOwner, repo)
|
||||
}
|
||||
|
||||
func mergePullRequestAction(e Engine, doer *User, repo *Repository, issue *Issue) error {
|
||||
return notifyWatchers(e, &Action{
|
||||
// MergePullRequestAction adds new action for merging pull request (including manually merged pull requests).
|
||||
func MergePullRequestAction(doer *User, repo *Repository, pull *Issue, commits *PushCommits) error {
|
||||
if commits != nil {
|
||||
if err := UpdateIssuesCommit(doer, repo, commits.Commits, true); err != nil {
|
||||
log.Error(4, "UpdateIssuesCommit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := UpdateIssuesComment(doer, repo, pull, nil, true); err != nil {
|
||||
log.Error(4, "UpdateIssuesComment: %v", err)
|
||||
}
|
||||
|
||||
if err := notifyWatchers(x, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUser: doer,
|
||||
OpType: ActionMergePullRequest,
|
||||
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
})
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notifyWatchers: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MergePullRequestAction adds new action for merging pull request.
|
||||
func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error {
|
||||
return mergePullRequestAction(x, actUser, repo, pull)
|
||||
// NewPullRequestAction adds new action for creating a new pull request.
|
||||
func NewPullRequestAction(doer *User, repo *Repository, pull *Issue, commits *PushCommits) error {
|
||||
if err := UpdateIssuesCommit(doer, repo, commits.Commits, false); err != nil {
|
||||
log.Error(4, "UpdateIssuesCommit: %v", err)
|
||||
}
|
||||
|
||||
if err := UpdateIssuesComment(doer, repo, pull, nil, false); err != nil {
|
||||
log.Error(4, "UpdateIssuesComment: %v", err)
|
||||
}
|
||||
|
||||
if err := NotifyWatchers(&Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUser: doer,
|
||||
OpType: ActionCreatePullRequest,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}); err != nil {
|
||||
log.Error(4, "NotifyWatchers: %v", err)
|
||||
} else if err := pull.MailParticipants(); err != nil {
|
||||
log.Error(4, "MailParticipants: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitPullRequestAction adds new action for pushed commits tracked by a pull request.
|
||||
func CommitPullRequestAction(doer *User, repo *Repository, commits *PushCommits) error {
|
||||
if err := UpdateIssuesCommit(doer, repo, commits.Commits, false); err != nil {
|
||||
log.Error(4, "UpdateIssuesCommit: %v", err)
|
||||
}
|
||||
|
||||
// no action added
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateCommentAction adds new action when creating or updating a comment.
|
||||
func CreateOrUpdateCommentAction(doer *User, repo *Repository, issue *Issue, comment *Comment) error {
|
||||
if err := UpdateIssuesComment(doer, repo, issue, comment, false); err != nil {
|
||||
log.Error(4, "UpdateIssuesComment: %v", err)
|
||||
}
|
||||
|
||||
// no action added
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateIssueAction adds new action when creating a new issue or pull request.
|
||||
func CreateOrUpdateIssueAction(doer *User, repo *Repository, issue *Issue) error {
|
||||
if err := UpdateIssuesComment(doer, repo, issue, nil, false); err != nil {
|
||||
log.Error(4, "UpdateIssuesComment: %v", err)
|
||||
}
|
||||
|
||||
// no action added
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFeedsOptions options for retrieving feeds
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -184,53 +185,530 @@ func Test_getIssueFromRef(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUpdateIssuesCommit(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
pushCommits := []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef1",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user4@example.com",
|
||||
AuthorName: "User Four",
|
||||
Message: "start working on #FST-1, #1",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef2",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "a plain message",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef2",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "close #2",
|
||||
},
|
||||
}
|
||||
func TestUpdateIssuesCommentIssues(t *testing.T) {
|
||||
for _, canOpenClose := range []bool{false, true} {
|
||||
// if cannot open or close then issue should not change status
|
||||
isOpen := "is_closed!=1"
|
||||
isClosed := "is_closed=1"
|
||||
if !canOpenClose {
|
||||
isClosed = isOpen
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
repo.Owner = user
|
||||
|
||||
commentIssue := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 1}).(*Issue)
|
||||
refIssue1 := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}).(*Issue)
|
||||
refIssue2 := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}).(*Issue)
|
||||
|
||||
commentBean := []*Comment{
|
||||
{
|
||||
Type: CommentTypeIssueRef,
|
||||
CommitSHA: base.EncodeSha1(fmt.Sprintf("%d", commentIssue.ID)),
|
||||
PosterID: user.ID,
|
||||
IssueID: commentIssue.ID,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeIssueRef,
|
||||
CommitSHA: base.EncodeSha1(fmt.Sprintf("%d", commentIssue.ID)),
|
||||
PosterID: user.ID,
|
||||
IssueID: refIssue1.ID,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeIssueRef,
|
||||
CommitSHA: base.EncodeSha1(fmt.Sprintf("%d", commentIssue.ID)),
|
||||
PosterID: user.ID,
|
||||
IssueID: refIssue2.ID,
|
||||
},
|
||||
}
|
||||
|
||||
// test issue/pull request closing multiple issues
|
||||
commentIssue.Title = "close #2"
|
||||
commentIssue.Content = "close #3"
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertNotExistsBean(t, commentBean[1])
|
||||
AssertNotExistsBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesComment(user, repo, commentIssue, nil, canOpenClose))
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isClosed)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test issue/pull request re-opening multiple issues
|
||||
commentIssue.Title = "reopen #2"
|
||||
commentIssue.Content = "reopen #3"
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isClosed)
|
||||
assert.NoError(t, UpdateIssuesComment(user, repo, commentIssue, nil, canOpenClose))
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test issue/pull request mixing re-opening and closing issue and self-referencing issue
|
||||
commentIssue.Title = "reopen #2"
|
||||
commentIssue.Content = "close #2 and reference #1"
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesComment(user, repo, commentIssue, nil, canOpenClose))
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateIssuesCommentComments(t *testing.T) {
|
||||
isOpen := "is_closed!=1"
|
||||
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
repo.Owner = user
|
||||
|
||||
commentBean := &Comment{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: "abcdef1",
|
||||
PosterID: user.ID,
|
||||
IssueID: 1,
|
||||
}
|
||||
issueBean := &Issue{RepoID: repo.ID, Index: 2}
|
||||
commentIssue := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 1}).(*Issue)
|
||||
refIssue1 := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}).(*Issue)
|
||||
refIssue2 := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}).(*Issue)
|
||||
|
||||
AssertNotExistsBean(t, commentBean)
|
||||
AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits))
|
||||
AssertExistsAndLoadBean(t, commentBean)
|
||||
AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
|
||||
comment := Comment{
|
||||
ID: 123456789,
|
||||
Type: CommentTypeComment,
|
||||
PosterID: user.ID,
|
||||
Poster: user,
|
||||
IssueID: commentIssue.ID,
|
||||
Content: "this is a comment that mentions #2 and #1 too",
|
||||
}
|
||||
|
||||
commentBean := []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommentRef,
|
||||
CommitSHA: base.EncodeSha1(fmt.Sprintf("%d@%d", commentIssue.ID, comment.ID)),
|
||||
PosterID: user.ID,
|
||||
IssueID: commentIssue.ID,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommentRef,
|
||||
CommitSHA: base.EncodeSha1(fmt.Sprintf("%d@%d", commentIssue.ID, comment.ID)),
|
||||
PosterID: user.ID,
|
||||
IssueID: refIssue1.ID,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommentRef,
|
||||
CommitSHA: base.EncodeSha1(fmt.Sprintf("%d@%d", commentIssue.ID, comment.ID)),
|
||||
PosterID: user.ID,
|
||||
IssueID: refIssue2.ID,
|
||||
},
|
||||
}
|
||||
|
||||
// test comment referencing issue including self-referencing
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertNotExistsBean(t, commentBean[1])
|
||||
AssertNotExistsBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesComment(user, repo, commentIssue, &comment, false))
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertNotExistsBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test comment updating issue reference
|
||||
comment.Content = "this is a comment that mentions #2 and #3 too"
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertNotExistsBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesComment(user, repo, commentIssue, &comment, false))
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 3}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
|
||||
func TestUpdateIssuesCommit(t *testing.T) {
|
||||
for _, commitsAreMerged := range []bool{false, true} {
|
||||
// if commits were not merged then issue should not change status
|
||||
isOpen := "is_closed!=1"
|
||||
isClosed := "is_closed=1"
|
||||
if !commitsAreMerged {
|
||||
isClosed = isOpen
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
repo.Owner = user
|
||||
|
||||
// test re-open of already open issue
|
||||
pushCommits := []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef1",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "reoopen #2",
|
||||
},
|
||||
}
|
||||
commentBean := []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test simultaneous open and close on an already open issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef2",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "reopen #2 and the close #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test close of an open issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef3",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "closes #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test close of an already closed issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef4",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "close #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test simultaneous open and close on a closed issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef5",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "close #2 and reopen #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test referencing an closed issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef6",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "for details on how to open, see #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test re-open a closed issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef7",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "reopens #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test referencing an open issue
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef8",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "for details on how to close, see #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test close-then-open commit order
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef10",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "reopened #2",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef9",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "fixes #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[1].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertNotExistsBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test open-then-close commit order
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef12",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "resolved #2",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef11",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "reopened #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[1].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertNotExistsBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
|
||||
// test more complex commit pattern
|
||||
pushCommits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef15",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user4@example.com",
|
||||
AuthorName: "User Four",
|
||||
Message: "start working on #FST-1, #1",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef14",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "reopen #2",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef13",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "close #2",
|
||||
},
|
||||
}
|
||||
commentBean = []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[0].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 1,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[1].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommitRef,
|
||||
CommitSHA: pushCommits[2].Sha1,
|
||||
PosterID: user.ID,
|
||||
IssueID: 2,
|
||||
},
|
||||
}
|
||||
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertNotExistsBean(t, commentBean[1])
|
||||
AssertNotExistsBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isClosed)
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, commitsAreMerged))
|
||||
AssertExistsAndLoadBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
AssertExistsAndLoadBean(t, commentBean[2])
|
||||
AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}, isOpen)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
}
|
||||
|
||||
func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) {
|
||||
|
@ -379,6 +857,7 @@ func TestMergePullRequestAction(t *testing.T) {
|
|||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, OwnerID: user.ID}).(*Repository)
|
||||
repo.Owner = user
|
||||
issue := AssertExistsAndLoadBean(t, &Issue{ID: 3, RepoID: repo.ID}).(*Issue)
|
||||
commits := &PushCommits{0, make([]*PushCommit, 0), "", nil}
|
||||
|
||||
actionBean := &Action{
|
||||
OpType: ActionMergePullRequest,
|
||||
|
@ -389,7 +868,7 @@ func TestMergePullRequestAction(t *testing.T) {
|
|||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
AssertNotExistsBean(t, actionBean)
|
||||
assert.NoError(t, MergePullRequestAction(user, repo, issue))
|
||||
assert.NoError(t, MergePullRequestAction(user, repo, issue, commits))
|
||||
AssertExistsAndLoadBean(t, actionBean)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
|
|
|
@ -802,6 +802,10 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
|
|||
go HookQueue.Add(issue.RepoID)
|
||||
}
|
||||
|
||||
if err = CreateOrUpdateIssueAction(doer, issue.Repo, issue); err != nil {
|
||||
return fmt.Errorf("CreateOrUpdateIssueAction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -867,6 +871,10 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
|
|||
go HookQueue.Add(issue.RepoID)
|
||||
}
|
||||
|
||||
if err = CreateOrUpdateIssueAction(doer, issue.Repo, issue); err != nil {
|
||||
return fmt.Errorf("CreateOrUpdateIssueAction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1037,6 +1045,10 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
|
|||
|
||||
UpdateIssueIndexer(issue.ID)
|
||||
|
||||
if err = CreateOrUpdateIssueAction(issue.Poster, issue.Repo, issue); err != nil {
|
||||
return fmt.Errorf("CreateOrUpdateIssueAction: %v", err)
|
||||
}
|
||||
|
||||
if err = NotifyWatchers(&Action{
|
||||
ActUserID: issue.Poster.ID,
|
||||
ActUser: issue.Poster,
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
api "code.gitea.io/sdk/gitea"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
@ -107,7 +108,13 @@ type Comment struct {
|
|||
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
|
||||
|
||||
// Reference issue in commit message
|
||||
// Reference issue in commit message, comments, issues, or pull requests
|
||||
RefExists bool `xorm:"-"`
|
||||
RefIssue *Issue `xorm:"-"`
|
||||
RefComment *Comment `xorm:"-"`
|
||||
RefMessage string `xorm:"-"`
|
||||
RefURL string `xorm:"-"`
|
||||
// the commit SHA for commit refs otherwise a SHA of a unique reference identifier
|
||||
CommitSHA string `xorm:"VARCHAR(40)"`
|
||||
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
|
@ -225,6 +232,105 @@ func (c *Comment) EventTag() string {
|
|||
return "event-" + com.ToStr(c.ID)
|
||||
}
|
||||
|
||||
// LoadReference if comment.Type is CommentType{Issue,Commit,Comment,Pull}Ref, then load RefIssue, RefComment
|
||||
func (c *Comment) LoadReference() error {
|
||||
if c.Type == CommentTypeIssueRef || c.Type == CommentTypePullRef {
|
||||
var issueID int64
|
||||
n, err := fmt.Sscanf(c.Content, "%d", &issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
refIssue, err := GetIssueByID(issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pullOrIssue := "issues"
|
||||
if refIssue.IsPull {
|
||||
pullOrIssue = "pulls"
|
||||
}
|
||||
|
||||
c.RefIssue = refIssue
|
||||
c.RefURL = fmt.Sprintf("%s/%s/%d", refIssue.Repo.Link(), pullOrIssue, refIssue.Index)
|
||||
c.RefExists = true
|
||||
}
|
||||
} else if c.Type == CommentTypeCommitRef {
|
||||
if strings.HasPrefix(c.Content, `<a href="`) && strings.HasSuffix(c.Content, `</a>`) {
|
||||
// this is an old style commit ref
|
||||
content := strings.TrimSuffix(strings.TrimPrefix(c.Content, `<a href="`), `</a>`)
|
||||
contentParts := strings.SplitN(content, `">`, 2)
|
||||
|
||||
if len(contentParts) == 2 {
|
||||
c.RefURL = contentParts[0]
|
||||
c.RefMessage = contentParts[1]
|
||||
c.RefExists = true
|
||||
}
|
||||
} else {
|
||||
// this is a new style commit ref
|
||||
contentParts := strings.SplitN(c.Content, " ", 2)
|
||||
if len(contentParts) == 2 {
|
||||
var repoID int64
|
||||
n, err := fmt.Sscanf(contentParts[0], "%d", &repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
refRepo, err := GetRepositoryByID(repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(refRepo.RepoPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
refCommit, err := gitRepo.GetCommit(contentParts[1][:40])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.RefURL = fmt.Sprintf("%s/commit/%s", refRepo.Link(), refCommit.ID.String())
|
||||
c.RefMessage = refCommit.CommitMessage
|
||||
c.RefExists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if c.Type == CommentTypeCommentRef {
|
||||
var commentID int64
|
||||
n, err := fmt.Sscanf(c.Content, "%d", &commentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
refComment, err := GetCommentByID(commentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
refIssue, err := GetIssueByID(refComment.IssueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pullOrIssue := "issues"
|
||||
if refIssue.IsPull {
|
||||
pullOrIssue = "pulls"
|
||||
}
|
||||
|
||||
c.RefIssue = refIssue
|
||||
c.RefComment = refComment
|
||||
c.RefURL = fmt.Sprintf("%s/%s/%d#%s", refIssue.Repo.Link(), pullOrIssue, refIssue.Index, refComment.HashTag())
|
||||
c.RefExists = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
|
||||
func (c *Comment) LoadLabel() error {
|
||||
var label Label
|
||||
|
@ -589,6 +695,10 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
|
|||
|
||||
if opts.Type == CommentTypeComment {
|
||||
UpdateIssueIndexer(opts.Issue.ID)
|
||||
|
||||
if err = CreateOrUpdateCommentAction(comment.Poster, opts.Repo, opts.Issue, comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return comment, nil
|
||||
}
|
||||
|
@ -622,17 +732,17 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
|
|||
return comment, nil
|
||||
}
|
||||
|
||||
// CreateRefComment creates a commit reference comment to issue.
|
||||
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
|
||||
if len(commitSHA) == 0 {
|
||||
return fmt.Errorf("cannot create reference with empty commit SHA")
|
||||
// createRefComment creates a commit, comment, issue, or pull request reference comment to issue.
|
||||
func createRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string, commentType CommentType) error {
|
||||
if len(refSHA) == 0 {
|
||||
return fmt.Errorf("cannot create reference with empty SHA")
|
||||
}
|
||||
|
||||
// Check if same reference from same commit has already existed.
|
||||
// Check if same reference from same issue and comment has already existed.
|
||||
has, err := x.Get(&Comment{
|
||||
Type: CommentTypeCommitRef,
|
||||
Type: commentType,
|
||||
IssueID: issue.ID,
|
||||
CommitSHA: commitSHA,
|
||||
CommitSHA: refSHA,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("check reference comment: %v", err)
|
||||
|
@ -641,16 +751,36 @@ func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commi
|
|||
}
|
||||
|
||||
_, err = CreateComment(&CreateCommentOptions{
|
||||
Type: CommentTypeCommitRef,
|
||||
Type: commentType,
|
||||
Doer: doer,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
CommitSHA: commitSHA,
|
||||
CommitSHA: refSHA,
|
||||
Content: content,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateCommitRefComment creates a commit reference comment to issue.
|
||||
func CreateCommitRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
|
||||
return createRefComment(doer, repo, issue, content, commitSHA, CommentTypeCommitRef)
|
||||
}
|
||||
|
||||
// CreateCommentRefComment creates a comment reference comment to issue.
|
||||
func CreateCommentRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string) error {
|
||||
return createRefComment(doer, repo, issue, content, refSHA, CommentTypeCommentRef)
|
||||
}
|
||||
|
||||
// CreateIssueRefComment creates a comment reference comment to issue.
|
||||
func CreateIssueRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string) error {
|
||||
return createRefComment(doer, repo, issue, content, refSHA, CommentTypeIssueRef)
|
||||
}
|
||||
|
||||
// CreatePullRefComment creates a comment reference comment to issue.
|
||||
func CreatePullRefComment(doer *User, repo *Repository, issue *Issue, content, refSHA string) error {
|
||||
return createRefComment(doer, repo, issue, content, refSHA, CommentTypePullRef)
|
||||
}
|
||||
|
||||
// GetCommentByID returns the comment by given ID.
|
||||
func GetCommentByID(id int64) (*Comment, error) {
|
||||
c := new(Comment)
|
||||
|
@ -737,6 +867,15 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error {
|
|||
return err
|
||||
} else if c.Type == CommentTypeComment {
|
||||
UpdateIssueIndexer(c.IssueID)
|
||||
|
||||
issue, err := GetIssueByID(c.IssueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = CreateOrUpdateCommentAction(c.Poster, issue.Repo, issue, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.LoadIssue(); err != nil {
|
||||
|
|
|
@ -14,9 +14,27 @@ import (
|
|||
func TestCreateComment(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
issue := AssertExistsAndLoadBean(t, &Issue{}).(*Issue)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: issue.RepoID}).(*Repository)
|
||||
doer := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
|
||||
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
repo.Owner = doer
|
||||
|
||||
issue := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 1}).(*Issue)
|
||||
refIssue := AssertExistsAndLoadBean(t, &Issue{RepoID: repo.ID, Index: 2}).(*Issue)
|
||||
|
||||
commentBean := []*Comment{
|
||||
{
|
||||
Type: CommentTypeCommentRef,
|
||||
PosterID: doer.ID,
|
||||
IssueID: issue.ID,
|
||||
},
|
||||
{
|
||||
Type: CommentTypeCommentRef,
|
||||
PosterID: doer.ID,
|
||||
IssueID: refIssue.ID,
|
||||
},
|
||||
}
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertNotExistsBean(t, commentBean[1])
|
||||
|
||||
now := time.Now().Unix()
|
||||
comment, err := CreateComment(&CreateCommentOptions{
|
||||
|
@ -24,18 +42,26 @@ func TestCreateComment(t *testing.T) {
|
|||
Doer: doer,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
Content: "Hello",
|
||||
Content: "Hello, this comment references issue #2",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
then := time.Now().Unix()
|
||||
|
||||
assert.EqualValues(t, CommentTypeComment, comment.Type)
|
||||
assert.EqualValues(t, "Hello", comment.Content)
|
||||
assert.EqualValues(t, "Hello, this comment references issue #2", comment.Content)
|
||||
assert.EqualValues(t, issue.ID, comment.IssueID)
|
||||
assert.EqualValues(t, doer.ID, comment.PosterID)
|
||||
AssertInt64InRange(t, now, then, int64(comment.CreatedUnix))
|
||||
AssertExistsAndLoadBean(t, comment) // assert actually added to DB
|
||||
AssertNotExistsBean(t, commentBean[0])
|
||||
AssertExistsAndLoadBean(t, commentBean[1])
|
||||
|
||||
updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue)
|
||||
AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
|
||||
|
||||
err = commentBean[1].LoadReference()
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, commentBean[1].RefIssue) {
|
||||
assert.EqualValues(t, issue.ID, commentBean[1].RefIssue.ID)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -445,10 +445,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
|
|||
log.Error(4, "setMerged [%d]: %v", pr.ID, err)
|
||||
}
|
||||
|
||||
if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
|
||||
log.Error(4, "MergePullRequestAction [%d]: %v", pr.ID, err)
|
||||
}
|
||||
|
||||
// Reset cached commit count
|
||||
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
|
||||
|
||||
|
@ -488,13 +484,18 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
|
|||
if mergeStyle == MergeStyleMerge {
|
||||
l.PushFront(mergeCommit)
|
||||
}
|
||||
commits := ListToPushCommits(l)
|
||||
|
||||
if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue, commits); err != nil {
|
||||
log.Error(4, "MergePullRequestAction [%d]: %v", pr.ID, err)
|
||||
}
|
||||
|
||||
p := &api.PushPayload{
|
||||
Ref: git.BranchPrefix + pr.BaseBranch,
|
||||
Before: pr.MergeBase,
|
||||
After: mergeCommit.ID.String(),
|
||||
CompareURL: setting.AppURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
|
||||
Commits: ListToPushCommits(l).ToAPIPayloadCommits(pr.BaseRepo.HTMLURL()),
|
||||
Commits: commits.ToAPIPayloadCommits(pr.BaseRepo.HTMLURL()),
|
||||
Repo: pr.BaseRepo.APIFormat(mode),
|
||||
Pusher: pr.HeadRepo.MustOwner().APIFormat(),
|
||||
Sender: doer.APIFormat(),
|
||||
|
@ -580,6 +581,11 @@ func (pr *PullRequest) manuallyMerged() bool {
|
|||
return false
|
||||
}
|
||||
log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commit.ID.String())
|
||||
|
||||
if err = MergePullRequestAction(pr.Merger, pr.Issue.Repo, pr.Issue, nil); err != nil {
|
||||
log.Error(4, "MergePullRequestAction [%d]: %v", pr.ID, err)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -771,23 +777,45 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
|
|||
|
||||
UpdateIssueIndexer(pull.ID)
|
||||
|
||||
if err = NotifyWatchers(&Action{
|
||||
ActUserID: pull.Poster.ID,
|
||||
ActUser: pull.Poster,
|
||||
OpType: ActionCreatePullRequest,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}); err != nil {
|
||||
log.Error(4, "NotifyWatchers: %v", err)
|
||||
} else if err = pull.MailParticipants(); err != nil {
|
||||
log.Error(4, "MailParticipants: %v", err)
|
||||
}
|
||||
|
||||
pr.Issue = pull
|
||||
pull.PullRequest = pr
|
||||
mode, _ := AccessLevel(pull.Poster.ID, repo)
|
||||
|
||||
var (
|
||||
baseBranch *Branch
|
||||
headBranch *Branch
|
||||
baseCommit *git.Commit
|
||||
headCommit *git.Commit
|
||||
baseGitRepo *git.Repository
|
||||
)
|
||||
if baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch); err != nil {
|
||||
return nil
|
||||
}
|
||||
if baseCommit, err = baseBranch.GetCommit(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch); err != nil {
|
||||
return nil
|
||||
}
|
||||
if headCommit, err = headBranch.GetCommit(); err != nil {
|
||||
return nil
|
||||
}
|
||||
if baseGitRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath()); err != nil {
|
||||
log.Error(4, "OpenRepository", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
l, err := baseGitRepo.CommitsBetweenIDs(headCommit.ID.String(), baseCommit.ID.String())
|
||||
if err != nil {
|
||||
log.Error(4, "CommitsBetweenIDs: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
commits := ListToPushCommits(l)
|
||||
if err = NewPullRequestAction(pull.Poster, repo, pull, commits); err != nil {
|
||||
log.Error(4, "NewPullRequestAction [%d]: %v", pr.ID, err)
|
||||
}
|
||||
|
||||
if err = PrepareWebhooks(repo, HookEventPullRequest, &api.PullRequestPayload{
|
||||
Action: api.HookIssueOpened,
|
||||
Index: pull.Index,
|
||||
|
|
|
@ -67,7 +67,7 @@ type PushUpdateOptions struct {
|
|||
// PushUpdate must be called for any push actions in order to
|
||||
// generates necessary push action history feeds.
|
||||
func PushUpdate(branch string, opt PushUpdateOptions) error {
|
||||
repo, err := pushUpdate(opt)
|
||||
repo, err := pushUpdate(branch, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
|
|||
return nil
|
||||
}
|
||||
|
||||
func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
|
||||
func pushUpdate(branch string, opts PushUpdateOptions) (repo *Repository, err error) {
|
||||
isNewRef := opts.OldCommitID == git.EmptySHA
|
||||
isDelRef := opts.NewCommitID == git.EmptySHA
|
||||
if isNewRef && isDelRef {
|
||||
|
@ -277,5 +277,69 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
|
|||
}); err != nil {
|
||||
return nil, fmt.Errorf("CommitRepoAction: %v", err)
|
||||
}
|
||||
|
||||
// create actions that update pull requests tracking the branch that was pushed to
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(repo.ID, branch)
|
||||
if err != nil {
|
||||
log.Error(4, "Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repo.ID, branch, err)
|
||||
} else {
|
||||
pusher, err := GetUserByID(opts.PusherID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserByID: %v", err)
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
if err = pr.GetHeadRepo(); err != nil {
|
||||
log.Error(4, "GetHeadRepo: %v", err)
|
||||
continue
|
||||
} else if err = pr.GetBaseRepo(); err != nil {
|
||||
log.Error(4, "GetBaseRepo: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
baseBranch *Branch
|
||||
headBranch *Branch
|
||||
baseCommit *git.Commit
|
||||
headCommit *git.Commit
|
||||
headGitRepo *git.Repository
|
||||
)
|
||||
if baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch); err != nil {
|
||||
log.Error(4, "BaseRepo.GetBranch: %v", err)
|
||||
continue
|
||||
}
|
||||
if baseCommit, err = baseBranch.GetCommit(); err != nil {
|
||||
log.Error(4, "baseBranch.GetCommit: %v", err)
|
||||
continue
|
||||
}
|
||||
if headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch); err != nil {
|
||||
log.Error(4, "HeadRepo.GetBranch: %v", err)
|
||||
continue
|
||||
}
|
||||
if headCommit, err = headBranch.GetCommit(); err != nil {
|
||||
log.Error(4, "headRepo.GetCommit: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTICE: this is using pr.HeadRepo rather than pr.BaseRepo to get commits since they are going to be pushed in a go routine
|
||||
if headGitRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath()); err != nil {
|
||||
log.Error(4, "OpenRepository", err)
|
||||
continue
|
||||
}
|
||||
|
||||
l, err := headGitRepo.CommitsBetweenIDs(headCommit.ID.String(), baseCommit.ID.String())
|
||||
if err != nil {
|
||||
log.Error(4, "CommitsBetweenIDs: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
commits := ListToPushCommits(l)
|
||||
if err = CommitPullRequestAction(pusher, pr.BaseRepo, commits); err != nil {
|
||||
log.Error(4, "CommitPullRequestAction [%d]: %v", pr.ID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
|
|
@ -721,7 +721,10 @@ issues.reopen_comment_issue = Comment and Reopen
|
|||
issues.create_comment = Comment
|
||||
issues.closed_at = `closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at = `reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.issue_ref_at = `referenced this issue from an issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at = `referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.comment_ref_at = `referenced this issue from a comment <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.pull_ref_at = `referenced this issue from a pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster = Poster
|
||||
issues.collaborator = Collaborator
|
||||
issues.owner = Owner
|
||||
|
|
|
@ -696,6 +696,10 @@ func ViewIssue(ctx *context.Context) {
|
|||
if !isAdded && !issue.IsPoster(comment.Poster.ID) {
|
||||
participants = append(participants, comment.Poster)
|
||||
}
|
||||
} else if comment.Type == models.CommentTypeIssueRef || comment.Type == models.CommentTypeCommitRef || comment.Type == models.CommentTypeCommentRef || comment.Type == models.CommentTypePullRef {
|
||||
if err = comment.LoadReference(); err != nil {
|
||||
continue
|
||||
}
|
||||
} else if comment.Type == models.CommentTypeLabel {
|
||||
if err = comment.LoadLabel(); err != nil {
|
||||
ctx.ServerError("LoadLabel", err)
|
||||
|
|
|
@ -82,7 +82,20 @@
|
|||
</a>
|
||||
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.closed_at" .EventTag $createdStr | Safe}}</span>
|
||||
</div>
|
||||
{{else if eq .Type 4}}
|
||||
{{else if and (eq .Type 3) .RefExists}}
|
||||
<div class="event">
|
||||
<span class="octicon octicon-bookmark"></span>
|
||||
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
|
||||
<img src="{{.Poster.RelAvatarLink}}">
|
||||
</a>
|
||||
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.issue_ref_at" .EventTag $createdStr | Safe}}</span>
|
||||
|
||||
<div class="detail">
|
||||
<span class="octicon octicon-issue-opened"></span>
|
||||
<span class="text grey"><a href="{{.RefURL}}"><span class="title has-emoji">{{.RefIssue.Title | Escape}}</span> (#{{.RefIssue.Index}})</a></span>
|
||||
</div>
|
||||
</div>
|
||||
{{else if and (eq .Type 4) .RefExists}}
|
||||
<div class="event">
|
||||
<span class="octicon octicon-bookmark"></span>
|
||||
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
|
||||
|
@ -92,7 +105,33 @@
|
|||
|
||||
<div class="detail">
|
||||
<span class="octicon octicon-git-commit"></span>
|
||||
<span class="text grey">{{.Content | Str2html}}</span>
|
||||
<span class="text grey"><a href="{{.RefURL}}"><span class="title has-emoji">{{.RefMessage | Escape}}</span> ({{ShortSha .CommitSHA}})</a></span>
|
||||
</div>
|
||||
</div>
|
||||
{{else if and (eq .Type 5) .RefExists}}
|
||||
<div class="event">
|
||||
<span class="octicon octicon-bookmark"></span>
|
||||
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
|
||||
<img src="{{.Poster.RelAvatarLink}}">
|
||||
</a>
|
||||
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.comment_ref_at" .EventTag $createdStr | Safe}}</span>
|
||||
|
||||
<div class="detail">
|
||||
<span class="octicon octicon-comment-discussion"></span>
|
||||
<span class="text grey"><a href="{{.RefURL}}"><span class="title has-emoji">{{.RefIssue.Title | Escape}}</span> (#{{.RefIssue.Index}})</a></span>
|
||||
</div>
|
||||
</div>
|
||||
{{else if and (eq .Type 6) .RefExists}}
|
||||
<div class="event">
|
||||
<span class="octicon octicon-bookmark"></span>
|
||||
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
|
||||
<img src="{{.Poster.RelAvatarLink}}">
|
||||
</a>
|
||||
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.pull_ref_at" .EventTag $createdStr | Safe}}</span>
|
||||
|
||||
<div class="detail">
|
||||
<span class="octicon octicon-git-pull-request"></span>
|
||||
<span class="text grey"><a href="{{.RefURL}}"><span class="title has-emoji">{{.RefIssue.Title | Escape}}</span> (#{{.RefIssue.Index}})</a></span>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .Type 7}}
|
||||
|
|
Loading…
Reference in New Issue
Block a user