You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue_xref.go 10 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "fmt"
  7. "code.gitea.io/gitea/modules/log"
  8. "code.gitea.io/gitea/modules/references"
  9. "github.com/unknwon/com"
  10. "xorm.io/xorm"
  11. )
  12. type crossReference struct {
  13. Issue *Issue
  14. Action references.XRefAction
  15. }
  16. // crossReferencesContext is context to pass along findCrossReference functions
  17. type crossReferencesContext struct {
  18. Type CommentType
  19. Doer *User
  20. OrigIssue *Issue
  21. OrigComment *Comment
  22. }
  23. func neuterCrossReferences(e Engine, issueID int64, commentID int64) error {
  24. active := make([]*Comment, 0, 10)
  25. sess := e.Where("`ref_action` IN (?, ?, ?)", references.XRefActionNone, references.XRefActionCloses, references.XRefActionReopens).
  26. And("`ref_issue_id` = ?", issueID).
  27. And("`ref_comment_id` = ?", commentID)
  28. if err := sess.Find(&active); err != nil || len(active) == 0 {
  29. return err
  30. }
  31. ids := make([]int64, len(active))
  32. for i, c := range active {
  33. ids[i] = c.ID
  34. }
  35. _, err := e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: references.XRefActionNeutered})
  36. return err
  37. }
  38. // .___
  39. // | | ______ ________ __ ____
  40. // | |/ ___// ___/ | \_/ __ \
  41. // | |\___ \ \___ \| | /\ ___/
  42. // |___/____ >____ >____/ \___ >
  43. // \/ \/ \/
  44. //
  45. func (issue *Issue) addCrossReferences(e *xorm.Session, doer *User) error {
  46. var commentType CommentType
  47. if issue.IsPull {
  48. commentType = CommentTypePullRef
  49. } else {
  50. commentType = CommentTypeIssueRef
  51. }
  52. ctx := &crossReferencesContext{
  53. Type: commentType,
  54. Doer: doer,
  55. OrigIssue: issue,
  56. }
  57. return issue.createCrossReferences(e, ctx, issue.Title, issue.Content)
  58. }
  59. func (issue *Issue) createCrossReferences(e *xorm.Session, ctx *crossReferencesContext, plaincontent, mdcontent string) error {
  60. xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, plaincontent, mdcontent)
  61. if err != nil {
  62. return err
  63. }
  64. for _, xref := range xreflist {
  65. var refCommentID int64
  66. if ctx.OrigComment != nil {
  67. refCommentID = ctx.OrigComment.ID
  68. }
  69. if _, err := createComment(e, &CreateCommentOptions{
  70. Type: ctx.Type,
  71. Doer: ctx.Doer,
  72. Repo: xref.Issue.Repo,
  73. Issue: xref.Issue,
  74. RefRepoID: ctx.OrigIssue.RepoID,
  75. RefIssueID: ctx.OrigIssue.ID,
  76. RefCommentID: refCommentID,
  77. RefAction: xref.Action,
  78. RefIsPull: ctx.OrigIssue.IsPull,
  79. }); err != nil {
  80. return err
  81. }
  82. }
  83. return nil
  84. }
  85. func (issue *Issue) getCrossReferences(e *xorm.Session, ctx *crossReferencesContext, plaincontent, mdcontent string) ([]*crossReference, error) {
  86. xreflist := make([]*crossReference, 0, 5)
  87. var (
  88. refRepo *Repository
  89. refIssue *Issue
  90. refAction references.XRefAction
  91. err error
  92. )
  93. allrefs := append(references.FindAllIssueReferences(plaincontent), references.FindAllIssueReferencesMarkdown(mdcontent)...)
  94. for _, ref := range allrefs {
  95. if ref.Owner == "" && ref.Name == "" {
  96. // Issues in the same repository
  97. if err := ctx.OrigIssue.loadRepo(e); err != nil {
  98. return nil, err
  99. }
  100. refRepo = ctx.OrigIssue.Repo
  101. } else {
  102. // Issues in other repositories
  103. refRepo, err = getRepositoryByOwnerAndName(e, ref.Owner, ref.Name)
  104. if err != nil {
  105. if IsErrRepoNotExist(err) {
  106. continue
  107. }
  108. return nil, err
  109. }
  110. }
  111. if refIssue, refAction, err = ctx.OrigIssue.verifyReferencedIssue(e, ctx, refRepo, ref); err != nil {
  112. return nil, err
  113. }
  114. if refIssue != nil {
  115. xreflist = ctx.OrigIssue.updateCrossReferenceList(xreflist, &crossReference{
  116. Issue: refIssue,
  117. Action: refAction,
  118. })
  119. }
  120. }
  121. return xreflist, nil
  122. }
  123. func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *crossReference) []*crossReference {
  124. if xref.Issue.ID == issue.ID {
  125. return list
  126. }
  127. for i, r := range list {
  128. if r.Issue.ID == xref.Issue.ID {
  129. if xref.Action != references.XRefActionNone {
  130. list[i].Action = xref.Action
  131. }
  132. return list
  133. }
  134. }
  135. return append(list, xref)
  136. }
  137. // verifyReferencedIssue will check if the referenced issue exists, and whether the doer has permission to do what
  138. func (issue *Issue) verifyReferencedIssue(e Engine, ctx *crossReferencesContext, repo *Repository,
  139. ref references.IssueReference) (*Issue, references.XRefAction, error) {
  140. refIssue := &Issue{RepoID: repo.ID, Index: ref.Index}
  141. refAction := ref.Action
  142. if has, _ := e.Get(refIssue); !has {
  143. return nil, references.XRefActionNone, nil
  144. }
  145. if err := refIssue.loadRepo(e); err != nil {
  146. return nil, references.XRefActionNone, err
  147. }
  148. // Close/reopen actions can only be set from pull requests to issues
  149. if refIssue.IsPull || !issue.IsPull {
  150. refAction = references.XRefActionNone
  151. }
  152. // Check doer permissions; set action to None if the doer can't change the destination
  153. if refIssue.RepoID != ctx.OrigIssue.RepoID || ref.Action != references.XRefActionNone {
  154. perm, err := getUserRepoPermission(e, refIssue.Repo, ctx.Doer)
  155. if err != nil {
  156. return nil, references.XRefActionNone, err
  157. }
  158. if !perm.CanReadIssuesOrPulls(refIssue.IsPull) {
  159. return nil, references.XRefActionNone, nil
  160. }
  161. if ref.Action != references.XRefActionNone &&
  162. ctx.Doer.ID != refIssue.PosterID &&
  163. !perm.CanWriteIssuesOrPulls(refIssue.IsPull) {
  164. refAction = references.XRefActionNone
  165. }
  166. }
  167. return refIssue, refAction, nil
  168. }
  169. func (issue *Issue) neuterCrossReferences(e Engine) error {
  170. return neuterCrossReferences(e, issue.ID, 0)
  171. }
  172. // _________ __
  173. // \_ ___ \ ____ _____ _____ ____ _____/ |_
  174. // / \ \/ / _ \ / \ / \_/ __ \ / \ __\
  175. // \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
  176. // \______ /\____/|__|_| /__|_| /\___ >___| /__|
  177. // \/ \/ \/ \/ \/
  178. //
  179. func (comment *Comment) addCrossReferences(e *xorm.Session, doer *User) error {
  180. if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment {
  181. return nil
  182. }
  183. if err := comment.loadIssue(e); err != nil {
  184. return err
  185. }
  186. ctx := &crossReferencesContext{
  187. Type: CommentTypeCommentRef,
  188. Doer: doer,
  189. OrigIssue: comment.Issue,
  190. OrigComment: comment,
  191. }
  192. return comment.Issue.createCrossReferences(e, ctx, "", comment.Content)
  193. }
  194. func (comment *Comment) neuterCrossReferences(e Engine) error {
  195. return neuterCrossReferences(e, comment.IssueID, comment.ID)
  196. }
  197. // LoadRefComment loads comment that created this reference from database
  198. func (comment *Comment) LoadRefComment() (err error) {
  199. if comment.RefComment != nil {
  200. return nil
  201. }
  202. comment.RefComment, err = GetCommentByID(comment.RefCommentID)
  203. return
  204. }
  205. // LoadRefIssue loads comment that created this reference from database
  206. func (comment *Comment) LoadRefIssue() (err error) {
  207. if comment.RefIssue != nil {
  208. return nil
  209. }
  210. comment.RefIssue, err = GetIssueByID(comment.RefIssueID)
  211. if err == nil {
  212. err = comment.RefIssue.loadRepo(x)
  213. }
  214. return
  215. }
  216. // CommentTypeIsRef returns true if CommentType is a reference from another issue
  217. func CommentTypeIsRef(t CommentType) bool {
  218. return t == CommentTypeCommentRef || t == CommentTypePullRef || t == CommentTypeIssueRef
  219. }
  220. // RefCommentHTMLURL returns the HTML URL for the comment that created this reference
  221. func (comment *Comment) RefCommentHTMLURL() string {
  222. if err := comment.LoadRefComment(); err != nil { // Silently dropping errors :unamused:
  223. log.Error("LoadRefComment(%d): %v", comment.RefCommentID, err)
  224. return ""
  225. }
  226. return comment.RefComment.HTMLURL()
  227. }
  228. // RefIssueHTMLURL returns the HTML URL of the issue where this reference was created
  229. func (comment *Comment) RefIssueHTMLURL() string {
  230. if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
  231. log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err)
  232. return ""
  233. }
  234. return comment.RefIssue.HTMLURL()
  235. }
  236. // RefIssueTitle returns the title of the issue where this reference was created
  237. func (comment *Comment) RefIssueTitle() string {
  238. if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
  239. log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err)
  240. return ""
  241. }
  242. return comment.RefIssue.Title
  243. }
  244. // RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created
  245. func (comment *Comment) RefIssueIdent() string {
  246. if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
  247. log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err)
  248. return ""
  249. }
  250. // FIXME: check this name for cross-repository references (#7901 if it gets merged)
  251. return "#" + com.ToStr(comment.RefIssue.Index)
  252. }
  253. // __________ .__ .__ __________ __
  254. // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
  255. // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
  256. // | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
  257. // |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
  258. // \/ \/ |__| \/ \/
  259. // ResolveCrossReferences will return the list of references to close/reopen by this PR
  260. func (pr *PullRequest) ResolveCrossReferences() ([]*Comment, error) {
  261. unfiltered := make([]*Comment, 0, 5)
  262. if err := x.
  263. Where("ref_repo_id = ? AND ref_issue_id = ?", pr.Issue.RepoID, pr.Issue.ID).
  264. In("ref_action", []references.XRefAction{references.XRefActionCloses, references.XRefActionReopens}).
  265. OrderBy("id").
  266. Find(&unfiltered); err != nil {
  267. return nil, fmt.Errorf("get reference: %v", err)
  268. }
  269. refs := make([]*Comment, 0, len(unfiltered))
  270. for _, ref := range unfiltered {
  271. found := false
  272. for i, r := range refs {
  273. if r.IssueID == ref.IssueID {
  274. // Keep only the latest
  275. refs[i] = ref
  276. found = true
  277. break
  278. }
  279. }
  280. if !found {
  281. refs = append(refs, ref)
  282. }
  283. }
  284. return refs, nil
  285. }