2019-09-20 08:45:38 +03: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 models
import (
2019-11-18 16:13:07 +03:00
"fmt"
2019-09-20 08:45:38 +03:00
"code.gitea.io/gitea/modules/log"
2019-10-14 01:29:10 +03:00
"code.gitea.io/gitea/modules/references"
2019-09-20 08:45:38 +03:00
"github.com/unknwon/com"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2019-09-20 08:45:38 +03:00
)
type crossReference struct {
Issue * Issue
2019-10-14 01:29:10 +03:00
Action references . XRefAction
2019-09-20 08:45:38 +03:00
}
// crossReferencesContext is context to pass along findCrossReference functions
type crossReferencesContext struct {
Type CommentType
Doer * User
OrigIssue * Issue
OrigComment * Comment
}
func neuterCrossReferences ( e Engine , issueID int64 , commentID int64 ) error {
active := make ( [ ] * Comment , 0 , 10 )
2019-11-18 16:13:07 +03:00
sess := e . Where ( "`ref_action` IN (?, ?, ?)" , references . XRefActionNone , references . XRefActionCloses , references . XRefActionReopens ) .
And ( "`ref_issue_id` = ?" , issueID ) .
And ( "`ref_comment_id` = ?" , commentID )
2019-09-20 08:45:38 +03:00
if err := sess . Find ( & active ) ; err != nil || len ( active ) == 0 {
return err
}
ids := make ( [ ] int64 , len ( active ) )
for i , c := range active {
ids [ i ] = c . ID
}
2019-10-14 01:29:10 +03:00
_ , err := e . In ( "id" , ids ) . Cols ( "`ref_action`" ) . Update ( & Comment { RefAction : references . XRefActionNeutered } )
2019-09-20 08:45:38 +03:00
return err
}
// .___
// | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \
// | |\___ \ \___ \| | /\ ___/
// |___/____ >____ >____/ \___ >
// \/ \/ \/
//
func ( issue * Issue ) addCrossReferences ( e * xorm . Session , doer * User ) error {
var commentType CommentType
if issue . IsPull {
commentType = CommentTypePullRef
} else {
commentType = CommentTypeIssueRef
}
ctx := & crossReferencesContext {
Type : commentType ,
Doer : doer ,
OrigIssue : issue ,
}
2019-10-14 01:29:10 +03:00
return issue . createCrossReferences ( e , ctx , issue . Title , issue . Content )
2019-09-20 08:45:38 +03:00
}
2019-10-14 01:29:10 +03:00
func ( issue * Issue ) createCrossReferences ( e * xorm . Session , ctx * crossReferencesContext , plaincontent , mdcontent string ) error {
xreflist , err := ctx . OrigIssue . getCrossReferences ( e , ctx , plaincontent , mdcontent )
2019-09-20 08:45:38 +03:00
if err != nil {
return err
}
for _ , xref := range xreflist {
2019-11-15 21:18:09 +03:00
var refCommentID int64
if ctx . OrigComment != nil {
refCommentID = ctx . OrigComment . ID
}
if _ , err := createComment ( e , & CreateCommentOptions {
Type : ctx . Type ,
Doer : ctx . Doer ,
Repo : xref . Issue . Repo ,
Issue : xref . Issue ,
RefRepoID : ctx . OrigIssue . RepoID ,
RefIssueID : ctx . OrigIssue . ID ,
RefCommentID : refCommentID ,
RefAction : xref . Action ,
2019-11-18 16:13:07 +03:00
RefIsPull : ctx . OrigIssue . IsPull ,
2019-11-15 21:18:09 +03:00
} ) ; err != nil {
2019-09-20 08:45:38 +03:00
return err
}
}
return nil
}
2019-10-14 01:29:10 +03:00
func ( issue * Issue ) getCrossReferences ( e * xorm . Session , ctx * crossReferencesContext , plaincontent , mdcontent string ) ( [ ] * crossReference , error ) {
2019-09-20 08:45:38 +03:00
xreflist := make ( [ ] * crossReference , 0 , 5 )
2019-10-14 01:29:10 +03:00
var (
2019-11-18 16:13:07 +03:00
refRepo * Repository
refIssue * Issue
refAction references . XRefAction
err error
2019-10-14 01:29:10 +03:00
)
allrefs := append ( references . FindAllIssueReferences ( plaincontent ) , references . FindAllIssueReferencesMarkdown ( mdcontent ) ... )
for _ , ref := range allrefs {
if ref . Owner == "" && ref . Name == "" {
// Issues in the same repository
if err := ctx . OrigIssue . loadRepo ( e ) ; err != nil {
2019-09-20 08:45:38 +03:00
return nil , err
}
2019-10-14 01:29:10 +03:00
refRepo = ctx . OrigIssue . Repo
} else {
// Issues in other repositories
refRepo , err = getRepositoryByOwnerAndName ( e , ref . Owner , ref . Name )
2019-09-20 08:45:38 +03:00
if err != nil {
if IsErrRepoNotExist ( err ) {
continue
}
return nil , err
}
2019-10-14 01:29:10 +03:00
}
2019-11-18 16:13:07 +03:00
if refIssue , refAction , err = ctx . OrigIssue . verifyReferencedIssue ( e , ctx , refRepo , ref ) ; err != nil {
2019-10-14 01:29:10 +03:00
return nil , err
}
if refIssue != nil {
xreflist = ctx . OrigIssue . updateCrossReferenceList ( xreflist , & crossReference {
2019-11-18 16:13:07 +03:00
Issue : refIssue ,
Action : refAction ,
2019-10-14 01:29:10 +03:00
} )
2019-09-20 08:45:38 +03:00
}
}
return xreflist , nil
}
func ( issue * Issue ) updateCrossReferenceList ( list [ ] * crossReference , xref * crossReference ) [ ] * crossReference {
if xref . Issue . ID == issue . ID {
return list
}
for i , r := range list {
if r . Issue . ID == xref . Issue . ID {
2019-10-14 01:29:10 +03:00
if xref . Action != references . XRefActionNone {
2019-09-20 08:45:38 +03:00
list [ i ] . Action = xref . Action
}
return list
}
}
return append ( list , xref )
}
2019-11-18 16:13:07 +03:00
// verifyReferencedIssue will check if the referenced issue exists, and whether the doer has permission to do what
func ( issue * Issue ) verifyReferencedIssue ( e Engine , ctx * crossReferencesContext , repo * Repository ,
ref references . IssueReference ) ( * Issue , references . XRefAction , error ) {
refIssue := & Issue { RepoID : repo . ID , Index : ref . Index }
refAction := ref . Action
2019-09-20 08:45:38 +03:00
if has , _ := e . Get ( refIssue ) ; ! has {
2019-11-18 16:13:07 +03:00
return nil , references . XRefActionNone , nil
2019-09-20 08:45:38 +03:00
}
if err := refIssue . loadRepo ( e ) ; err != nil {
2019-11-18 16:13:07 +03:00
return nil , references . XRefActionNone , err
2019-09-20 08:45:38 +03:00
}
2019-11-18 16:13:07 +03:00
// Close/reopen actions can only be set from pull requests to issues
if refIssue . IsPull || ! issue . IsPull {
refAction = references . XRefActionNone
}
// Check doer permissions; set action to None if the doer can't change the destination
if refIssue . RepoID != ctx . OrigIssue . RepoID || ref . Action != references . XRefActionNone {
2019-09-20 08:45:38 +03:00
perm , err := getUserRepoPermission ( e , refIssue . Repo , ctx . Doer )
if err != nil {
2019-11-18 16:13:07 +03:00
return nil , references . XRefActionNone , err
2019-09-20 08:45:38 +03:00
}
if ! perm . CanReadIssuesOrPulls ( refIssue . IsPull ) {
2019-11-18 16:13:07 +03:00
return nil , references . XRefActionNone , nil
}
2019-11-18 20:03:49 +03:00
// Accept close/reopening actions only if the poster is able to close the
// referenced issue manually at this moment. The only exception is
// the poster of a new PR referencing an issue on the same repo: then the merger
// should be responsible for checking whether the reference should resolve.
2019-11-18 16:13:07 +03:00
if ref . Action != references . XRefActionNone &&
ctx . Doer . ID != refIssue . PosterID &&
2019-11-18 20:03:49 +03:00
! perm . CanWriteIssuesOrPulls ( refIssue . IsPull ) &&
( refIssue . RepoID != ctx . OrigIssue . RepoID || ctx . OrigComment != nil ) {
2019-11-18 16:13:07 +03:00
refAction = references . XRefActionNone
2019-09-20 08:45:38 +03:00
}
}
2019-11-18 16:13:07 +03:00
return refIssue , refAction , nil
2019-09-20 08:45:38 +03:00
}
func ( issue * Issue ) neuterCrossReferences ( e Engine ) error {
return neuterCrossReferences ( e , issue . ID , 0 )
}
// _________ __
// \_ ___ \ ____ _____ _____ ____ _____/ |_
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
// \______ /\____/|__|_| /__|_| /\___ >___| /__|
// \/ \/ \/ \/ \/
//
func ( comment * Comment ) addCrossReferences ( e * xorm . Session , doer * User ) error {
if comment . Type != CommentTypeCode && comment . Type != CommentTypeComment {
return nil
}
if err := comment . loadIssue ( e ) ; err != nil {
return err
}
ctx := & crossReferencesContext {
Type : CommentTypeCommentRef ,
Doer : doer ,
OrigIssue : comment . Issue ,
OrigComment : comment ,
}
2019-10-14 01:29:10 +03:00
return comment . Issue . createCrossReferences ( e , ctx , "" , comment . Content )
2019-09-20 08:45:38 +03:00
}
func ( comment * Comment ) neuterCrossReferences ( e Engine ) error {
2019-11-18 16:13:07 +03:00
return neuterCrossReferences ( e , comment . IssueID , comment . ID )
2019-09-20 08:45:38 +03:00
}
// LoadRefComment loads comment that created this reference from database
func ( comment * Comment ) LoadRefComment ( ) ( err error ) {
if comment . RefComment != nil {
return nil
}
comment . RefComment , err = GetCommentByID ( comment . RefCommentID )
return
}
// LoadRefIssue loads comment that created this reference from database
func ( comment * Comment ) LoadRefIssue ( ) ( err error ) {
if comment . RefIssue != nil {
return nil
}
comment . RefIssue , err = GetIssueByID ( comment . RefIssueID )
if err == nil {
err = comment . RefIssue . loadRepo ( x )
}
return
}
// CommentTypeIsRef returns true if CommentType is a reference from another issue
func CommentTypeIsRef ( t CommentType ) bool {
return t == CommentTypeCommentRef || t == CommentTypePullRef || t == CommentTypeIssueRef
}
// RefCommentHTMLURL returns the HTML URL for the comment that created this reference
func ( comment * Comment ) RefCommentHTMLURL ( ) string {
if err := comment . LoadRefComment ( ) ; err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadRefComment(%d): %v" , comment . RefCommentID , err )
return ""
}
return comment . RefComment . HTMLURL ( )
}
// RefIssueHTMLURL returns the HTML URL of the issue where this reference was created
func ( comment * Comment ) RefIssueHTMLURL ( ) string {
if err := comment . LoadRefIssue ( ) ; err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadRefIssue(%d): %v" , comment . RefCommentID , err )
return ""
}
return comment . RefIssue . HTMLURL ( )
}
// RefIssueTitle returns the title of the issue where this reference was created
func ( comment * Comment ) RefIssueTitle ( ) string {
if err := comment . LoadRefIssue ( ) ; err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadRefIssue(%d): %v" , comment . RefCommentID , err )
return ""
}
return comment . RefIssue . Title
}
// RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created
func ( comment * Comment ) RefIssueIdent ( ) string {
if err := comment . LoadRefIssue ( ) ; err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadRefIssue(%d): %v" , comment . RefCommentID , err )
return ""
}
// FIXME: check this name for cross-repository references (#7901 if it gets merged)
return "#" + com . ToStr ( comment . RefIssue . Index )
}
2019-11-18 16:13:07 +03:00
// __________ .__ .__ __________ __
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
// | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
// \/ \/ |__| \/ \/
// ResolveCrossReferences will return the list of references to close/reopen by this PR
func ( pr * PullRequest ) ResolveCrossReferences ( ) ( [ ] * Comment , error ) {
unfiltered := make ( [ ] * Comment , 0 , 5 )
if err := x .
Where ( "ref_repo_id = ? AND ref_issue_id = ?" , pr . Issue . RepoID , pr . Issue . ID ) .
In ( "ref_action" , [ ] references . XRefAction { references . XRefActionCloses , references . XRefActionReopens } ) .
OrderBy ( "id" ) .
Find ( & unfiltered ) ; err != nil {
return nil , fmt . Errorf ( "get reference: %v" , err )
}
refs := make ( [ ] * Comment , 0 , len ( unfiltered ) )
for _ , ref := range unfiltered {
found := false
for i , r := range refs {
if r . IssueID == ref . IssueID {
// Keep only the latest
refs [ i ] = ref
found = true
break
}
}
if ! found {
refs = append ( refs , ref )
}
}
return refs , nil
}