2019-07-30 09:59:10 +08:00
// 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 repofiles
import (
"encoding/json"
"fmt"
2019-12-07 23:52:36 +08:00
"html"
2020-06-19 09:19:56 +02:00
"strings"
2019-07-30 09:59:10 +08:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
2019-11-03 14:59:26 +08:00
"code.gitea.io/gitea/modules/notification"
2019-12-07 23:52:36 +08:00
"code.gitea.io/gitea/modules/references"
2020-01-10 17:34:21 +08:00
"code.gitea.io/gitea/modules/repository"
2019-07-30 09:59:10 +08:00
"code.gitea.io/gitea/modules/setting"
)
2019-12-07 23:52:36 +08:00
// getIssueFromRef returns the issue referenced by a ref. Returns a nil *Issue
// if the provided ref references a non-existent issue.
func getIssueFromRef ( repo * models . Repository , index int64 ) ( * models . Issue , error ) {
issue , err := models . GetIssueByIndex ( repo . ID , index )
if err != nil {
if models . IsErrIssueNotExist ( err ) {
return nil , nil
}
return nil , err
}
return issue , nil
}
2019-12-16 05:57:34 +08:00
func changeIssueStatus ( repo * models . Repository , issue * models . Issue , doer * models . User , closed bool ) error {
2019-12-07 23:52:36 +08:00
stopTimerIfAvailable := func ( doer * models . User , issue * models . Issue ) error {
if models . StopwatchExists ( doer . ID , issue . ID ) {
if err := models . CreateOrStopIssueStopwatch ( doer , issue ) ; err != nil {
return err
}
}
return nil
}
issue . Repo = repo
2019-12-16 05:57:34 +08:00
comment , err := issue . ChangeStatus ( doer , closed )
if err != nil {
2019-12-07 23:52:36 +08:00
// Don't return an error when dependencies are open as this would let the push fail
if models . IsErrDependenciesLeft ( err ) {
return stopTimerIfAvailable ( doer , issue )
}
return err
}
2019-12-16 05:57:34 +08:00
notification . NotifyIssueChangeStatus ( doer , issue , comment , closed )
2019-12-07 23:52:36 +08:00
return stopTimerIfAvailable ( doer , issue )
}
// UpdateIssuesCommit checks if issues are manipulated by commit message.
2020-01-10 17:34:21 +08:00
func UpdateIssuesCommit ( doer * models . User , repo * models . Repository , commits [ ] * repository . PushCommit , branchName string ) error {
2019-12-07 23:52:36 +08:00
// Commits are appended in the reverse order.
for i := len ( commits ) - 1 ; i >= 0 ; i -- {
c := commits [ i ]
type markKey struct {
ID int64
Action references . XRefAction
}
refMarked := make ( map [ markKey ] bool )
var refRepo * models . Repository
var refIssue * models . Issue
var err error
for _ , ref := range references . FindAllIssueReferences ( c . Message ) {
// issue is from another repo
if len ( ref . Owner ) > 0 && len ( ref . Name ) > 0 {
refRepo , err = models . GetRepositoryFromMatch ( ref . Owner , ref . Name )
if err != nil {
continue
}
} else {
refRepo = repo
}
if refIssue , err = getIssueFromRef ( refRepo , ref . Index ) ; err != nil {
return err
}
if refIssue == nil {
continue
}
perm , err := models . GetUserRepoPermission ( refRepo , doer )
if err != nil {
return err
}
key := markKey { ID : refIssue . ID , Action : ref . Action }
if refMarked [ key ] {
continue
}
refMarked [ key ] = true
// FIXME: this kind of condition is all over the code, it should be consolidated in a single place
2020-01-20 20:00:32 +08:00
canclose := perm . IsAdmin ( ) || perm . IsOwner ( ) || perm . CanWriteIssuesOrPulls ( refIssue . IsPull ) || refIssue . PosterID == doer . ID
cancomment := canclose || perm . CanReadIssuesOrPulls ( refIssue . IsPull )
2019-12-07 23:52:36 +08:00
// Don't proceed if the user can't comment
if ! cancomment {
continue
}
2020-06-19 09:19:56 +02:00
message := fmt . Sprintf ( ` <a href="%s/commit/%s">%s</a> ` , repo . Link ( ) , c . Sha1 , html . EscapeString ( strings . SplitN ( c . Message , "\n" , 2 ) [ 0 ] ) )
2019-12-07 23:52:36 +08:00
if err = models . CreateRefComment ( doer , refRepo , refIssue , message , c . Sha1 ) ; err != nil {
return err
}
// Only issues can be closed/reopened this way, and user needs the correct permissions
if refIssue . IsPull || ! canclose {
continue
}
// Only process closing/reopening keywords
if ref . Action != references . XRefActionCloses && ref . Action != references . XRefActionReopens {
continue
}
if ! repo . CloseIssuesViaCommitInAnyBranch {
// If the issue was specified to be in a particular branch, don't allow commits in other branches to close it
if refIssue . Ref != "" {
if branchName != refIssue . Ref {
continue
}
// Otherwise, only process commits to the default branch
} else if branchName != repo . DefaultBranch {
continue
}
}
2020-01-10 22:20:11 -03:00
close := ( ref . Action == references . XRefActionCloses )
if close != refIssue . IsClosed {
if err := changeIssueStatus ( refRepo , refIssue , doer , close ) ; err != nil {
return err
}
2019-12-07 23:52:36 +08:00
}
}
}
return nil
}
2019-07-30 09:59:10 +08:00
// CommitRepoActionOptions represent options of a new commit action.
type CommitRepoActionOptions struct {
2020-02-03 04:27:34 +08:00
PushUpdateOptions
2019-07-30 09:59:10 +08:00
RepoOwnerID int64
2020-01-10 17:34:21 +08:00
Commits * repository . PushCommits
2019-07-30 09:59:10 +08:00
}
// CommitRepoAction adds new commit action to the repository, and prepare
// corresponding webhooks.
2019-12-26 11:29:45 +00:00
func CommitRepoAction ( optsList ... * CommitRepoActionOptions ) error {
var pusher * models . User
var repo * models . Repository
actions := make ( [ ] * models . Action , len ( optsList ) )
for i , opts := range optsList {
if pusher == nil || pusher . Name != opts . PusherName {
var err error
pusher , err = models . GetUserByName ( opts . PusherName )
if err != nil {
return fmt . Errorf ( "GetUserByName [%s]: %v" , opts . PusherName , err )
}
}
2019-07-30 09:59:10 +08:00
2019-12-26 11:29:45 +00:00
if repo == nil || repo . OwnerID != opts . RepoOwnerID || repo . Name != opts . RepoName {
var err error
if repo != nil {
// Change repository empty status and update last updated time.
if err := models . UpdateRepository ( repo , false ) ; err != nil {
return fmt . Errorf ( "UpdateRepository: %v" , err )
}
}
repo , err = models . GetRepositoryByName ( opts . RepoOwnerID , opts . RepoName )
2019-07-30 09:59:10 +08:00
if err != nil {
2019-12-26 11:29:45 +00:00
return fmt . Errorf ( "GetRepositoryByName [owner_id: %d, name: %s]: %v" , opts . RepoOwnerID , opts . RepoName , err )
2019-07-30 09:59:10 +08:00
}
2019-12-26 11:29:45 +00:00
}
refName := git . RefEndName ( opts . RefFullName )
// Change default branch and empty status only if pushed ref is non-empty branch.
2020-02-03 04:27:34 +08:00
if repo . IsEmpty && opts . IsBranch ( ) && ! opts . IsDelRef ( ) {
2019-12-26 11:29:45 +00:00
repo . DefaultBranch = refName
repo . IsEmpty = false
if refName != "master" {
gitRepo , err := git . OpenRepository ( repo . RepoPath ( ) )
if err != nil {
2019-07-30 09:59:10 +08:00
return err
}
2019-12-26 11:29:45 +00:00
if err := gitRepo . SetDefaultBranch ( repo . DefaultBranch ) ; err != nil {
if ! git . IsErrUnsupportedVersion ( err ) {
gitRepo . Close ( )
return err
}
}
gitRepo . Close ( )
2019-07-30 09:59:10 +08:00
}
}
2019-12-26 11:29:45 +00:00
opType := models . ActionCommitRepo
2019-07-30 09:59:10 +08:00
2019-12-26 11:29:45 +00:00
// Check it's tag push or branch.
2020-02-03 04:27:34 +08:00
if opts . IsTag ( ) {
2019-12-26 11:29:45 +00:00
opType = models . ActionPushTag
2020-02-03 04:27:34 +08:00
if opts . IsDelRef ( ) {
2019-12-26 11:29:45 +00:00
opType = models . ActionDeleteTag
}
2020-01-10 17:34:21 +08:00
opts . Commits = & repository . PushCommits { }
2020-02-03 04:27:34 +08:00
} else if opts . IsDelRef ( ) {
2019-12-26 11:29:45 +00:00
opType = models . ActionDeleteBranch
2020-01-10 17:34:21 +08:00
opts . Commits = & repository . PushCommits { }
2019-07-30 09:59:10 +08:00
} else {
2019-12-26 11:29:45 +00:00
// if not the first commit, set the compare URL.
2020-02-03 04:27:34 +08:00
if ! opts . IsNewRef ( ) {
2019-12-26 11:29:45 +00:00
opts . Commits . CompareURL = repo . ComposeCompareURL ( opts . OldCommitID , opts . NewCommitID )
}
2019-07-30 09:59:10 +08:00
2019-12-26 11:29:45 +00:00
if err := UpdateIssuesCommit ( pusher , repo , opts . Commits . Commits , refName ) ; err != nil {
log . Error ( "updateIssuesCommit: %v" , err )
}
2019-07-30 09:59:10 +08:00
}
2019-12-26 11:29:45 +00:00
if len ( opts . Commits . Commits ) > setting . UI . FeedMaxCommitNum {
opts . Commits . Commits = opts . Commits . Commits [ : setting . UI . FeedMaxCommitNum ]
}
2019-07-30 09:59:10 +08:00
2019-12-26 11:29:45 +00:00
data , err := json . Marshal ( opts . Commits )
if err != nil {
return fmt . Errorf ( "Marshal: %v" , err )
}
2019-07-30 09:59:10 +08:00
2019-12-26 11:29:45 +00:00
actions [ i ] = & models . Action {
ActUserID : pusher . ID ,
ActUser : pusher ,
OpType : opType ,
Content : string ( data ) ,
RepoID : repo . ID ,
Repo : repo ,
RefName : refName ,
IsPrivate : repo . IsPrivate ,
2019-07-30 09:59:10 +08:00
}
2020-05-13 08:50:56 +01:00
var isHookEventPush = true
2019-12-26 11:29:45 +00:00
switch opType {
case models . ActionCommitRepo : // Push
2020-02-03 04:27:34 +08:00
if opts . IsNewBranch ( ) {
2019-12-26 11:29:45 +00:00
notification . NotifyCreateRef ( pusher , repo , "branch" , opts . RefFullName )
}
case models . ActionDeleteBranch : // Delete Branch
notification . NotifyDeleteRef ( pusher , repo , "branch" , opts . RefFullName )
case models . ActionPushTag : // Create
notification . NotifyCreateRef ( pusher , repo , "tag" , opts . RefFullName )
2019-07-30 09:59:10 +08:00
2019-12-26 11:29:45 +00:00
case models . ActionDeleteTag : // Delete Tag
notification . NotifyDeleteRef ( pusher , repo , "tag" , opts . RefFullName )
2020-05-13 08:50:56 +01:00
default :
isHookEventPush = false
}
if isHookEventPush {
notification . NotifyPushCommits ( pusher , repo , opts . RefFullName , opts . OldCommitID , opts . NewCommitID , opts . Commits )
2019-12-26 11:29:45 +00:00
}
2019-07-30 09:59:10 +08:00
}
2019-12-26 11:29:45 +00:00
if repo != nil {
// Change repository empty status and update last updated time.
if err := models . UpdateRepository ( repo , false ) ; err != nil {
return fmt . Errorf ( "UpdateRepository: %v" , err )
}
2019-07-30 09:59:10 +08:00
}
2019-12-26 11:29:45 +00:00
if err := models . NotifyWatchers ( actions ... ) ; err != nil {
return fmt . Errorf ( "NotifyWatchers: %v" , err )
}
2019-07-30 09:59:10 +08:00
return nil
}