|
|
|
@ -24,8 +24,10 @@ import ( |
|
|
|
|
"code.gitea.io/gitea/modules/setting" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// ActionType represents the type of an action.
|
|
|
|
|
type ActionType int |
|
|
|
|
|
|
|
|
|
// Possible action types.
|
|
|
|
|
const ( |
|
|
|
|
ActionCreateRepo ActionType = iota + 1 // 1
|
|
|
|
|
ActionRenameRepo // 2
|
|
|
|
@ -45,12 +47,13 @@ const ( |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
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"} |
|
|
|
|
// 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 |
|
|
|
|
issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp |
|
|
|
|
issueReferenceKeywordsPat *regexp.Regexp |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func assembleKeywordsPattern(words []string) string { |
|
|
|
@ -58,13 +61,14 @@ func assembleKeywordsPattern(words []string) string { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func init() { |
|
|
|
|
IssueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(IssueCloseKeywords)) |
|
|
|
|
IssueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(IssueReopenKeywords)) |
|
|
|
|
IssueReferenceKeywordsPat = regexp.MustCompile(`(?i)(?:)(^| )\S+`) |
|
|
|
|
issueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueCloseKeywords)) |
|
|
|
|
issueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueReopenKeywords)) |
|
|
|
|
issueReferenceKeywordsPat = regexp.MustCompile(`(?i)(?:)(^| )\S+`) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Action represents user operation type and other information to repository.,
|
|
|
|
|
// it implemented interface base.Actioner so that can be used in template render.
|
|
|
|
|
// Action represents user operation type and other information to
|
|
|
|
|
// repository. It implemented interface base.Actioner so that can be
|
|
|
|
|
// used in template render.
|
|
|
|
|
type Action struct { |
|
|
|
|
ID int64 `xorm:"pk autoincr"` |
|
|
|
|
UserID int64 // Receiver user id.
|
|
|
|
@ -82,10 +86,13 @@ type Action struct { |
|
|
|
|
CreatedUnix int64 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BeforeInsert will be invoked by XORM before inserting a record
|
|
|
|
|
// representing this object.
|
|
|
|
|
func (a *Action) BeforeInsert() { |
|
|
|
|
a.CreatedUnix = time.Now().Unix() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// AfterSet updates the webhook object upon setting a column.
|
|
|
|
|
func (a *Action) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
|
switch colName { |
|
|
|
|
case "created_unix": |
|
|
|
@ -93,42 +100,57 @@ func (a *Action) AfterSet(colName string, _ xorm.Cell) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetOpType gets the ActionType of this action.
|
|
|
|
|
// TODO: change return type to ActionType ?
|
|
|
|
|
func (a *Action) GetOpType() int { |
|
|
|
|
return int(a.OpType) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetActUserName gets the action's user name.
|
|
|
|
|
func (a *Action) GetActUserName() string { |
|
|
|
|
return a.ActUserName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ShortActUserName gets the action's user name trimmed to max 20
|
|
|
|
|
// chars.
|
|
|
|
|
func (a *Action) ShortActUserName() string { |
|
|
|
|
return base.EllipsisString(a.ActUserName, 20) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetRepoUserName returns the name of the action repository owner.
|
|
|
|
|
func (a *Action) GetRepoUserName() string { |
|
|
|
|
return a.RepoUserName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ShortRepoUserName returns the name of the action repository owner
|
|
|
|
|
// trimmed to max 20 chars.
|
|
|
|
|
func (a *Action) ShortRepoUserName() string { |
|
|
|
|
return base.EllipsisString(a.RepoUserName, 20) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetRepoName returns the name of the action repository.
|
|
|
|
|
func (a *Action) GetRepoName() string { |
|
|
|
|
return a.RepoName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ShortRepoName returns the name of the action repository
|
|
|
|
|
// trimmed to max 33 chars.
|
|
|
|
|
func (a *Action) ShortRepoName() string { |
|
|
|
|
return base.EllipsisString(a.RepoName, 33) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetRepoPath returns the virtual path to the action repository.
|
|
|
|
|
func (a *Action) GetRepoPath() string { |
|
|
|
|
return path.Join(a.RepoUserName, a.RepoName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ShortRepoPath returns the virtual path to the action repository
|
|
|
|
|
// trimed to max 20 + 1 + 33 chars.
|
|
|
|
|
func (a *Action) ShortRepoPath() string { |
|
|
|
|
return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetRepoLink returns relative link to action repository.
|
|
|
|
|
func (a *Action) GetRepoLink() string { |
|
|
|
|
if len(setting.AppSubUrl) > 0 { |
|
|
|
|
return path.Join(setting.AppSubUrl, a.GetRepoPath()) |
|
|
|
@ -136,22 +158,29 @@ func (a *Action) GetRepoLink() string { |
|
|
|
|
return "/" + a.GetRepoPath() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetBranch returns the action's repository branch.
|
|
|
|
|
func (a *Action) GetBranch() string { |
|
|
|
|
return a.RefName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetContent returns the action's content.
|
|
|
|
|
func (a *Action) GetContent() string { |
|
|
|
|
return a.Content |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetCreate returns the action creation time.
|
|
|
|
|
func (a *Action) GetCreate() time.Time { |
|
|
|
|
return a.Created |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetIssueInfos returns a list of issues associated with
|
|
|
|
|
// the action.
|
|
|
|
|
func (a *Action) GetIssueInfos() []string { |
|
|
|
|
return strings.SplitN(a.Content, "|", 2) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetIssueTitle returns the title of first issue associated
|
|
|
|
|
// with the action.
|
|
|
|
|
func (a *Action) GetIssueTitle() string { |
|
|
|
|
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64() |
|
|
|
|
issue, err := GetIssueByIndex(a.RepoID, index) |
|
|
|
@ -162,6 +191,8 @@ func (a *Action) GetIssueTitle() string { |
|
|
|
|
return issue.Title |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetIssueContent returns the content of first issue associated with
|
|
|
|
|
// this action.
|
|
|
|
|
func (a *Action) GetIssueContent() string { |
|
|
|
|
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64() |
|
|
|
|
issue, err := GetIssueByIndex(a.RepoID, index) |
|
|
|
@ -221,6 +252,7 @@ func issueIndexTrimRight(c rune) bool { |
|
|
|
|
return !unicode.IsDigit(c) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// PushCommit represents a commit in a push operation.
|
|
|
|
|
type PushCommit struct { |
|
|
|
|
Sha1 string |
|
|
|
|
Message string |
|
|
|
@ -231,6 +263,7 @@ type PushCommit struct { |
|
|
|
|
Timestamp time.Time |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// PushCommits represents list of commits in a push operation.
|
|
|
|
|
type PushCommits struct { |
|
|
|
|
Len int |
|
|
|
|
Commits []*PushCommit |
|
|
|
@ -239,13 +272,16 @@ type PushCommits struct { |
|
|
|
|
avatars map[string]string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewPushCommits creates a new PushCommits object.
|
|
|
|
|
func NewPushCommits() *PushCommits { |
|
|
|
|
return &PushCommits{ |
|
|
|
|
avatars: make(map[string]string), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (pc *PushCommits) ToApiPayloadCommits(repoLink string) []*api.PayloadCommit { |
|
|
|
|
// ToAPIPayloadCommits converts a PushCommits object to
|
|
|
|
|
// api.PayloadCommit format.
|
|
|
|
|
func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit { |
|
|
|
|
commits := make([]*api.PayloadCommit, len(pc.Commits)) |
|
|
|
|
for i, commit := range pc.Commits { |
|
|
|
|
authorUsername := "" |
|
|
|
@ -281,21 +317,21 @@ func (pc *PushCommits) ToApiPayloadCommits(repoLink string) []*api.PayloadCommit |
|
|
|
|
|
|
|
|
|
// AvatarLink tries to match user in database with e-mail
|
|
|
|
|
// in order to show custom avatar, and falls back to general avatar link.
|
|
|
|
|
func (push *PushCommits) AvatarLink(email string) string { |
|
|
|
|
_, ok := push.avatars[email] |
|
|
|
|
func (pc *PushCommits) AvatarLink(email string) string { |
|
|
|
|
_, ok := pc.avatars[email] |
|
|
|
|
if !ok { |
|
|
|
|
u, err := GetUserByEmail(email) |
|
|
|
|
if err != nil { |
|
|
|
|
push.avatars[email] = base.AvatarLink(email) |
|
|
|
|
pc.avatars[email] = base.AvatarLink(email) |
|
|
|
|
if !IsErrUserNotExist(err) { |
|
|
|
|
log.Error(4, "GetUserByEmail: %v", err) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
push.avatars[email] = u.RelAvatarLink() |
|
|
|
|
pc.avatars[email] = u.RelAvatarLink() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return push.avatars[email] |
|
|
|
|
return pc.avatars[email] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// UpdateIssuesCommit checks if issues are manipulated by commit message.
|
|
|
|
@ -305,7 +341,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err |
|
|
|
|
c := commits[i] |
|
|
|
|
|
|
|
|
|
refMarked := make(map[int64]bool) |
|
|
|
|
for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) { |
|
|
|
|
for _, ref := range issueReferenceKeywordsPat.FindAllString(c.Message, -1) { |
|
|
|
|
ref = ref[strings.IndexByte(ref, byte(' '))+1:] |
|
|
|
|
ref = strings.TrimRightFunc(ref, issueIndexTrimRight) |
|
|
|
|
|
|
|
|
@ -343,7 +379,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) 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) { |
|
|
|
|
for _, ref := range issueCloseKeywordsPat.FindAllString(c.Message, -1) { |
|
|
|
|
ref = ref[strings.IndexByte(ref, byte(' '))+1:] |
|
|
|
|
ref = strings.TrimRightFunc(ref, issueIndexTrimRight) |
|
|
|
|
|
|
|
|
@ -383,7 +419,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// It is conflict to have close and reopen at same time, so refsMarkd doesn't need to reinit here.
|
|
|
|
|
for _, ref := range IssueReopenKeywordsPat.FindAllString(c.Message, -1) { |
|
|
|
|
for _, ref := range issueReopenKeywordsPat.FindAllString(c.Message, -1) { |
|
|
|
|
ref = ref[strings.IndexByte(ref, byte(' '))+1:] |
|
|
|
|
ref = strings.TrimRightFunc(ref, issueIndexTrimRight) |
|
|
|
|
|
|
|
|
@ -425,6 +461,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CommitRepoActionOptions represent options of a new commit action.
|
|
|
|
|
type CommitRepoActionOptions struct { |
|
|
|
|
PusherName string |
|
|
|
|
RepoOwnerID int64 |
|
|
|
@ -435,7 +472,8 @@ type CommitRepoActionOptions struct { |
|
|
|
|
Commits *PushCommits |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CommitRepoAction adds new commit actio to the repository, and prepare corresponding webhooks.
|
|
|
|
|
// CommitRepoAction adds new commit action to the repository, and prepare
|
|
|
|
|
// corresponding webhooks.
|
|
|
|
|
func CommitRepoAction(opts CommitRepoActionOptions) error { |
|
|
|
|
pusher, err := GetUserByName(opts.PusherName) |
|
|
|
|
if err != nil { |
|
|
|
@ -509,7 +547,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { |
|
|
|
|
Before: opts.OldCommitID, |
|
|
|
|
After: opts.NewCommitID, |
|
|
|
|
CompareURL: setting.AppUrl + opts.Commits.CompareURL, |
|
|
|
|
Commits: opts.Commits.ToApiPayloadCommits(repo.HTMLURL()), |
|
|
|
|
Commits: opts.Commits.ToAPIPayloadCommits(repo.HTMLURL()), |
|
|
|
|
Repo: apiRepo, |
|
|
|
|
Pusher: apiPusher, |
|
|
|
|
Sender: apiPusher, |
|
|
|
|