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

action.go 21KB

10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
8 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
7 jaren geleden
7 jaren geleden
8 jaren geleden
7 jaren geleden
8 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
10 jaren geleden
7 jaren geleden
10 jaren geleden
10 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
8 jaren geleden
8 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
7 jaren geleden
10 jaren geleden
8 jaren geleden
8 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. // Copyright 2014 The Gogs 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. "encoding/json"
  7. "fmt"
  8. "path"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "unicode"
  14. "code.gitea.io/git"
  15. "code.gitea.io/gitea/modules/base"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/util"
  19. api "code.gitea.io/sdk/gitea"
  20. "github.com/Unknwon/com"
  21. "github.com/go-xorm/builder"
  22. )
  23. // ActionType represents the type of an action.
  24. type ActionType int
  25. // Possible action types.
  26. const (
  27. ActionCreateRepo ActionType = iota + 1 // 1
  28. ActionRenameRepo // 2
  29. ActionStarRepo // 3
  30. ActionWatchRepo // 4
  31. ActionCommitRepo // 5
  32. ActionCreateIssue // 6
  33. ActionCreatePullRequest // 7
  34. ActionTransferRepo // 8
  35. ActionPushTag // 9
  36. ActionCommentIssue // 10
  37. ActionMergePullRequest // 11
  38. ActionCloseIssue // 12
  39. ActionReopenIssue // 13
  40. ActionClosePullRequest // 14
  41. ActionReopenPullRequest // 15
  42. ActionDeleteTag // 16
  43. ActionDeleteBranch // 17
  44. )
  45. var (
  46. // Same as Github. See
  47. // https://help.github.com/articles/closing-issues-via-commit-messages
  48. issueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
  49. issueReopenKeywords = []string{"reopen", "reopens", "reopened"}
  50. issueCloseKeywordsPat, issueReopenKeywordsPat *regexp.Regexp
  51. issueReferenceKeywordsPat *regexp.Regexp
  52. )
  53. const issueRefRegexpStr = `(?:\S+/\S=)?#\d+`
  54. func assembleKeywordsPattern(words []string) string {
  55. return fmt.Sprintf(`(?i)(?:%s) %s`, strings.Join(words, "|"), issueRefRegexpStr)
  56. }
  57. func init() {
  58. issueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueCloseKeywords))
  59. issueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueReopenKeywords))
  60. issueReferenceKeywordsPat = regexp.MustCompile(issueRefRegexpStr)
  61. }
  62. // Action represents user operation type and other information to
  63. // repository. It implemented interface base.Actioner so that can be
  64. // used in template render.
  65. type Action struct {
  66. ID int64 `xorm:"pk autoincr"`
  67. UserID int64 `xorm:"INDEX"` // Receiver user id.
  68. OpType ActionType
  69. ActUserID int64 `xorm:"INDEX"` // Action user id.
  70. ActUser *User `xorm:"-"`
  71. RepoID int64 `xorm:"INDEX"`
  72. Repo *Repository `xorm:"-"`
  73. CommentID int64 `xorm:"INDEX"`
  74. Comment *Comment `xorm:"-"`
  75. IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"`
  76. RefName string
  77. IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"`
  78. Content string `xorm:"TEXT"`
  79. CreatedUnix util.TimeStamp `xorm:"INDEX created"`
  80. }
  81. // GetOpType gets the ActionType of this action.
  82. func (a *Action) GetOpType() ActionType {
  83. return a.OpType
  84. }
  85. func (a *Action) loadActUser() {
  86. if a.ActUser != nil {
  87. return
  88. }
  89. var err error
  90. a.ActUser, err = GetUserByID(a.ActUserID)
  91. if err == nil {
  92. return
  93. } else if IsErrUserNotExist(err) {
  94. a.ActUser = NewGhostUser()
  95. } else {
  96. log.Error(4, "GetUserByID(%d): %v", a.ActUserID, err)
  97. }
  98. }
  99. func (a *Action) loadRepo() {
  100. if a.Repo != nil {
  101. return
  102. }
  103. var err error
  104. a.Repo, err = GetRepositoryByID(a.RepoID)
  105. if err != nil {
  106. log.Error(4, "GetRepositoryByID(%d): %v", a.RepoID, err)
  107. }
  108. }
  109. // GetActFullName gets the action's user full name.
  110. func (a *Action) GetActFullName() string {
  111. a.loadActUser()
  112. return a.ActUser.FullName
  113. }
  114. // GetActUserName gets the action's user name.
  115. func (a *Action) GetActUserName() string {
  116. a.loadActUser()
  117. return a.ActUser.Name
  118. }
  119. // ShortActUserName gets the action's user name trimmed to max 20
  120. // chars.
  121. func (a *Action) ShortActUserName() string {
  122. return base.EllipsisString(a.GetActUserName(), 20)
  123. }
  124. // GetActAvatar the action's user's avatar link
  125. func (a *Action) GetActAvatar() string {
  126. a.loadActUser()
  127. return a.ActUser.RelAvatarLink()
  128. }
  129. // GetRepoUserName returns the name of the action repository owner.
  130. func (a *Action) GetRepoUserName() string {
  131. a.loadRepo()
  132. return a.Repo.MustOwner().Name
  133. }
  134. // ShortRepoUserName returns the name of the action repository owner
  135. // trimmed to max 20 chars.
  136. func (a *Action) ShortRepoUserName() string {
  137. return base.EllipsisString(a.GetRepoUserName(), 20)
  138. }
  139. // GetRepoName returns the name of the action repository.
  140. func (a *Action) GetRepoName() string {
  141. a.loadRepo()
  142. return a.Repo.Name
  143. }
  144. // ShortRepoName returns the name of the action repository
  145. // trimmed to max 33 chars.
  146. func (a *Action) ShortRepoName() string {
  147. return base.EllipsisString(a.GetRepoName(), 33)
  148. }
  149. // GetRepoPath returns the virtual path to the action repository.
  150. func (a *Action) GetRepoPath() string {
  151. return path.Join(a.GetRepoUserName(), a.GetRepoName())
  152. }
  153. // ShortRepoPath returns the virtual path to the action repository
  154. // trimmed to max 20 + 1 + 33 chars.
  155. func (a *Action) ShortRepoPath() string {
  156. return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
  157. }
  158. // GetRepoLink returns relative link to action repository.
  159. func (a *Action) GetRepoLink() string {
  160. if len(setting.AppSubURL) > 0 {
  161. return path.Join(setting.AppSubURL, a.GetRepoPath())
  162. }
  163. return "/" + a.GetRepoPath()
  164. }
  165. // GetCommentLink returns link to action comment.
  166. func (a *Action) GetCommentLink() string {
  167. if a == nil {
  168. return "#"
  169. }
  170. if a.Comment == nil && a.CommentID != 0 {
  171. a.Comment, _ = GetCommentByID(a.CommentID)
  172. }
  173. if a.Comment != nil {
  174. return a.Comment.HTMLURL()
  175. }
  176. if len(a.GetIssueInfos()) == 0 {
  177. return "#"
  178. }
  179. //Return link to issue
  180. issueIDString := a.GetIssueInfos()[0]
  181. issueID, err := strconv.ParseInt(issueIDString, 10, 64)
  182. if err != nil {
  183. return "#"
  184. }
  185. issue, err := GetIssueByID(issueID)
  186. if err != nil {
  187. return "#"
  188. }
  189. return issue.HTMLURL()
  190. }
  191. // GetBranch returns the action's repository branch.
  192. func (a *Action) GetBranch() string {
  193. return a.RefName
  194. }
  195. // GetContent returns the action's content.
  196. func (a *Action) GetContent() string {
  197. return a.Content
  198. }
  199. // GetCreate returns the action creation time.
  200. func (a *Action) GetCreate() time.Time {
  201. return a.CreatedUnix.AsTime()
  202. }
  203. // GetIssueInfos returns a list of issues associated with
  204. // the action.
  205. func (a *Action) GetIssueInfos() []string {
  206. return strings.SplitN(a.Content, "|", 2)
  207. }
  208. // GetIssueTitle returns the title of first issue associated
  209. // with the action.
  210. func (a *Action) GetIssueTitle() string {
  211. index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
  212. issue, err := GetIssueByIndex(a.RepoID, index)
  213. if err != nil {
  214. log.Error(4, "GetIssueByIndex: %v", err)
  215. return "500 when get issue"
  216. }
  217. return issue.Title
  218. }
  219. // GetIssueContent returns the content of first issue associated with
  220. // this action.
  221. func (a *Action) GetIssueContent() string {
  222. index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
  223. issue, err := GetIssueByIndex(a.RepoID, index)
  224. if err != nil {
  225. log.Error(4, "GetIssueByIndex: %v", err)
  226. return "500 when get issue"
  227. }
  228. return issue.Content
  229. }
  230. func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
  231. if err = notifyWatchers(e, &Action{
  232. ActUserID: u.ID,
  233. ActUser: u,
  234. OpType: ActionCreateRepo,
  235. RepoID: repo.ID,
  236. Repo: repo,
  237. IsPrivate: repo.IsPrivate,
  238. }); err != nil {
  239. return fmt.Errorf("notify watchers '%d/%d': %v", u.ID, repo.ID, err)
  240. }
  241. log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
  242. return err
  243. }
  244. // NewRepoAction adds new action for creating repository.
  245. func NewRepoAction(u *User, repo *Repository) (err error) {
  246. return newRepoAction(x, u, repo)
  247. }
  248. func renameRepoAction(e Engine, actUser *User, oldRepoName string, repo *Repository) (err error) {
  249. if err = notifyWatchers(e, &Action{
  250. ActUserID: actUser.ID,
  251. ActUser: actUser,
  252. OpType: ActionRenameRepo,
  253. RepoID: repo.ID,
  254. Repo: repo,
  255. IsPrivate: repo.IsPrivate,
  256. Content: oldRepoName,
  257. }); err != nil {
  258. return fmt.Errorf("notify watchers: %v", err)
  259. }
  260. log.Trace("action.renameRepoAction: %s/%s", actUser.Name, repo.Name)
  261. return nil
  262. }
  263. // RenameRepoAction adds new action for renaming a repository.
  264. func RenameRepoAction(actUser *User, oldRepoName string, repo *Repository) error {
  265. return renameRepoAction(x, actUser, oldRepoName, repo)
  266. }
  267. func issueIndexTrimRight(c rune) bool {
  268. return !unicode.IsDigit(c)
  269. }
  270. // PushCommit represents a commit in a push operation.
  271. type PushCommit struct {
  272. Sha1 string
  273. Message string
  274. AuthorEmail string
  275. AuthorName string
  276. CommitterEmail string
  277. CommitterName string
  278. Timestamp time.Time
  279. }
  280. // PushCommits represents list of commits in a push operation.
  281. type PushCommits struct {
  282. Len int
  283. Commits []*PushCommit
  284. CompareURL string
  285. avatars map[string]string
  286. }
  287. // NewPushCommits creates a new PushCommits object.
  288. func NewPushCommits() *PushCommits {
  289. return &PushCommits{
  290. avatars: make(map[string]string),
  291. }
  292. }
  293. // ToAPIPayloadCommits converts a PushCommits object to
  294. // api.PayloadCommit format.
  295. func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit {
  296. commits := make([]*api.PayloadCommit, len(pc.Commits))
  297. for i, commit := range pc.Commits {
  298. authorUsername := ""
  299. author, err := GetUserByEmail(commit.AuthorEmail)
  300. if err == nil {
  301. authorUsername = author.Name
  302. }
  303. committerUsername := ""
  304. committer, err := GetUserByEmail(commit.CommitterEmail)
  305. if err == nil {
  306. // TODO: check errors other than email not found.
  307. committerUsername = committer.Name
  308. }
  309. commits[i] = &api.PayloadCommit{
  310. ID: commit.Sha1,
  311. Message: commit.Message,
  312. URL: fmt.Sprintf("%s/commit/%s", repoLink, commit.Sha1),
  313. Author: &api.PayloadUser{
  314. Name: commit.AuthorName,
  315. Email: commit.AuthorEmail,
  316. UserName: authorUsername,
  317. },
  318. Committer: &api.PayloadUser{
  319. Name: commit.CommitterName,
  320. Email: commit.CommitterEmail,
  321. UserName: committerUsername,
  322. },
  323. Timestamp: commit.Timestamp,
  324. }
  325. }
  326. return commits
  327. }
  328. // AvatarLink tries to match user in database with e-mail
  329. // in order to show custom avatar, and falls back to general avatar link.
  330. func (pc *PushCommits) AvatarLink(email string) string {
  331. _, ok := pc.avatars[email]
  332. if !ok {
  333. u, err := GetUserByEmail(email)
  334. if err != nil {
  335. pc.avatars[email] = base.AvatarLink(email)
  336. if !IsErrUserNotExist(err) {
  337. log.Error(4, "GetUserByEmail: %v", err)
  338. }
  339. } else {
  340. pc.avatars[email] = u.RelAvatarLink()
  341. }
  342. }
  343. return pc.avatars[email]
  344. }
  345. // getIssueFromRef returns the issue referenced by a ref. Returns a nil *Issue
  346. // if the provided ref is misformatted or references a non-existent issue.
  347. func getIssueFromRef(repo *Repository, ref string) (*Issue, error) {
  348. ref = ref[strings.IndexByte(ref, ' ')+1:]
  349. ref = strings.TrimRightFunc(ref, issueIndexTrimRight)
  350. var refRepo *Repository
  351. poundIndex := strings.IndexByte(ref, '#')
  352. if poundIndex < 0 {
  353. return nil, nil
  354. } else if poundIndex == 0 {
  355. refRepo = repo
  356. } else {
  357. slashIndex := strings.IndexByte(ref, '/')
  358. if slashIndex < 0 || slashIndex >= poundIndex {
  359. return nil, nil
  360. }
  361. ownerName := ref[:slashIndex]
  362. repoName := ref[slashIndex+1 : poundIndex]
  363. var err error
  364. refRepo, err = GetRepositoryByOwnerAndName(ownerName, repoName)
  365. if err != nil {
  366. if IsErrRepoNotExist(err) {
  367. return nil, nil
  368. }
  369. return nil, err
  370. }
  371. }
  372. issueIndex, err := strconv.ParseInt(ref[poundIndex+1:], 10, 64)
  373. if err != nil {
  374. return nil, nil
  375. }
  376. issue, err := GetIssueByIndex(refRepo.ID, int64(issueIndex))
  377. if err != nil {
  378. if IsErrIssueNotExist(err) {
  379. return nil, nil
  380. }
  381. return nil, err
  382. }
  383. return issue, nil
  384. }
  385. // UpdateIssuesCommit checks if issues are manipulated by commit message.
  386. func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) error {
  387. // Commits are appended in the reverse order.
  388. for i := len(commits) - 1; i >= 0; i-- {
  389. c := commits[i]
  390. refMarked := make(map[int64]bool)
  391. for _, ref := range issueReferenceKeywordsPat.FindAllString(c.Message, -1) {
  392. issue, err := getIssueFromRef(repo, ref)
  393. if err != nil {
  394. return err
  395. }
  396. if issue == nil || refMarked[issue.ID] {
  397. continue
  398. }
  399. refMarked[issue.ID] = true
  400. message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, c.Message)
  401. if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil {
  402. return err
  403. }
  404. }
  405. refMarked = make(map[int64]bool)
  406. // FIXME: can merge this one and next one to a common function.
  407. for _, ref := range issueCloseKeywordsPat.FindAllString(c.Message, -1) {
  408. issue, err := getIssueFromRef(repo, ref)
  409. if err != nil {
  410. return err
  411. }
  412. if issue == nil || refMarked[issue.ID] {
  413. continue
  414. }
  415. refMarked[issue.ID] = true
  416. if issue.RepoID != repo.ID || issue.IsClosed {
  417. continue
  418. }
  419. if err = issue.ChangeStatus(doer, repo, true); err != nil {
  420. return err
  421. }
  422. }
  423. // It is conflict to have close and reopen at same time, so refsMarked doesn't need to reinit here.
  424. for _, ref := range issueReopenKeywordsPat.FindAllString(c.Message, -1) {
  425. issue, err := getIssueFromRef(repo, ref)
  426. if err != nil {
  427. return err
  428. }
  429. if issue == nil || refMarked[issue.ID] {
  430. continue
  431. }
  432. refMarked[issue.ID] = true
  433. if issue.RepoID != repo.ID || !issue.IsClosed {
  434. continue
  435. }
  436. if err = issue.ChangeStatus(doer, repo, false); err != nil {
  437. return err
  438. }
  439. }
  440. }
  441. return nil
  442. }
  443. // CommitRepoActionOptions represent options of a new commit action.
  444. type CommitRepoActionOptions struct {
  445. PusherName string
  446. RepoOwnerID int64
  447. RepoName string
  448. RefFullName string
  449. OldCommitID string
  450. NewCommitID string
  451. Commits *PushCommits
  452. }
  453. // CommitRepoAction adds new commit action to the repository, and prepare
  454. // corresponding webhooks.
  455. func CommitRepoAction(opts CommitRepoActionOptions) error {
  456. pusher, err := GetUserByName(opts.PusherName)
  457. if err != nil {
  458. return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err)
  459. }
  460. repo, err := GetRepositoryByName(opts.RepoOwnerID, opts.RepoName)
  461. if err != nil {
  462. return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err)
  463. }
  464. refName := git.RefEndName(opts.RefFullName)
  465. if repo.IsBare && refName != repo.DefaultBranch {
  466. repo.DefaultBranch = refName
  467. }
  468. // Change repository bare status and update last updated time.
  469. repo.IsBare = repo.IsBare && opts.Commits.Len <= 0
  470. if err = UpdateRepository(repo, false); err != nil {
  471. return fmt.Errorf("UpdateRepository: %v", err)
  472. }
  473. isNewBranch := false
  474. opType := ActionCommitRepo
  475. // Check it's tag push or branch.
  476. if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
  477. opType = ActionPushTag
  478. if opts.NewCommitID == git.EmptySHA {
  479. opType = ActionDeleteTag
  480. }
  481. opts.Commits = &PushCommits{}
  482. } else if opts.NewCommitID == git.EmptySHA {
  483. opType = ActionDeleteBranch
  484. opts.Commits = &PushCommits{}
  485. } else {
  486. // if not the first commit, set the compare URL.
  487. if opts.OldCommitID == git.EmptySHA {
  488. isNewBranch = true
  489. } else {
  490. opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
  491. }
  492. if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil {
  493. log.Error(4, "updateIssuesCommit: %v", err)
  494. }
  495. }
  496. if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum {
  497. opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum]
  498. }
  499. data, err := json.Marshal(opts.Commits)
  500. if err != nil {
  501. return fmt.Errorf("Marshal: %v", err)
  502. }
  503. if err = NotifyWatchers(&Action{
  504. ActUserID: pusher.ID,
  505. ActUser: pusher,
  506. OpType: opType,
  507. Content: string(data),
  508. RepoID: repo.ID,
  509. Repo: repo,
  510. RefName: refName,
  511. IsPrivate: repo.IsPrivate,
  512. }); err != nil {
  513. return fmt.Errorf("NotifyWatchers: %v", err)
  514. }
  515. defer func() {
  516. go HookQueue.Add(repo.ID)
  517. }()
  518. apiPusher := pusher.APIFormat()
  519. apiRepo := repo.APIFormat(AccessModeNone)
  520. var shaSum string
  521. var isHookEventPush = false
  522. switch opType {
  523. case ActionCommitRepo: // Push
  524. isHookEventPush = true
  525. if isNewBranch {
  526. gitRepo, err := git.OpenRepository(repo.RepoPath())
  527. if err != nil {
  528. log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
  529. }
  530. shaSum, err = gitRepo.GetBranchCommitID(refName)
  531. if err != nil {
  532. log.Error(4, "GetBranchCommitID[%s]: %v", opts.RefFullName, err)
  533. }
  534. if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{
  535. Ref: refName,
  536. Sha: shaSum,
  537. RefType: "branch",
  538. Repo: apiRepo,
  539. Sender: apiPusher,
  540. }); err != nil {
  541. return fmt.Errorf("PrepareWebhooks: %v", err)
  542. }
  543. }
  544. case ActionDeleteBranch: // Delete Branch
  545. isHookEventPush = true
  546. if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{
  547. Ref: refName,
  548. RefType: "branch",
  549. PusherType: api.PusherTypeUser,
  550. Repo: apiRepo,
  551. Sender: apiPusher,
  552. }); err != nil {
  553. return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err)
  554. }
  555. case ActionPushTag: // Create
  556. isHookEventPush = true
  557. gitRepo, err := git.OpenRepository(repo.RepoPath())
  558. if err != nil {
  559. log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
  560. }
  561. shaSum, err = gitRepo.GetTagCommitID(refName)
  562. if err != nil {
  563. log.Error(4, "GetTagCommitID[%s]: %v", opts.RefFullName, err)
  564. }
  565. if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{
  566. Ref: refName,
  567. Sha: shaSum,
  568. RefType: "tag",
  569. Repo: apiRepo,
  570. Sender: apiPusher,
  571. }); err != nil {
  572. return fmt.Errorf("PrepareWebhooks: %v", err)
  573. }
  574. case ActionDeleteTag: // Delete Tag
  575. isHookEventPush = true
  576. if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{
  577. Ref: refName,
  578. RefType: "tag",
  579. PusherType: api.PusherTypeUser,
  580. Repo: apiRepo,
  581. Sender: apiPusher,
  582. }); err != nil {
  583. return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err)
  584. }
  585. }
  586. if isHookEventPush {
  587. if err = PrepareWebhooks(repo, HookEventPush, &api.PushPayload{
  588. Ref: opts.RefFullName,
  589. Before: opts.OldCommitID,
  590. After: opts.NewCommitID,
  591. CompareURL: setting.AppURL + opts.Commits.CompareURL,
  592. Commits: opts.Commits.ToAPIPayloadCommits(repo.HTMLURL()),
  593. Repo: apiRepo,
  594. Pusher: apiPusher,
  595. Sender: apiPusher,
  596. }); err != nil {
  597. return fmt.Errorf("PrepareWebhooks: %v", err)
  598. }
  599. }
  600. return nil
  601. }
  602. func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err error) {
  603. if err = notifyWatchers(e, &Action{
  604. ActUserID: doer.ID,
  605. ActUser: doer,
  606. OpType: ActionTransferRepo,
  607. RepoID: repo.ID,
  608. Repo: repo,
  609. IsPrivate: repo.IsPrivate,
  610. Content: path.Join(oldOwner.Name, repo.Name),
  611. }); err != nil {
  612. return fmt.Errorf("notifyWatchers: %v", err)
  613. }
  614. // Remove watch for organization.
  615. if oldOwner.IsOrganization() {
  616. if err = watchRepo(e, oldOwner.ID, repo.ID, false); err != nil {
  617. return fmt.Errorf("watchRepo [false]: %v", err)
  618. }
  619. }
  620. return nil
  621. }
  622. // TransferRepoAction adds new action for transferring repository,
  623. // the Owner field of repository is assumed to be new owner.
  624. func TransferRepoAction(doer, oldOwner *User, repo *Repository) error {
  625. return transferRepoAction(x, doer, oldOwner, repo)
  626. }
  627. func mergePullRequestAction(e Engine, doer *User, repo *Repository, issue *Issue) error {
  628. return notifyWatchers(e, &Action{
  629. ActUserID: doer.ID,
  630. ActUser: doer,
  631. OpType: ActionMergePullRequest,
  632. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
  633. RepoID: repo.ID,
  634. Repo: repo,
  635. IsPrivate: repo.IsPrivate,
  636. })
  637. }
  638. // MergePullRequestAction adds new action for merging pull request.
  639. func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error {
  640. return mergePullRequestAction(x, actUser, repo, pull)
  641. }
  642. // GetFeedsOptions options for retrieving feeds
  643. type GetFeedsOptions struct {
  644. RequestedUser *User
  645. RequestingUserID int64
  646. IncludePrivate bool // include private actions
  647. OnlyPerformedBy bool // only actions performed by requested user
  648. IncludeDeleted bool // include deleted actions
  649. }
  650. // GetFeeds returns actions according to the provided options
  651. func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
  652. cond := builder.NewCond()
  653. var repoIDs []int64
  654. if opts.RequestedUser.IsOrganization() {
  655. env, err := opts.RequestedUser.AccessibleReposEnv(opts.RequestingUserID)
  656. if err != nil {
  657. return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
  658. }
  659. if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
  660. return nil, fmt.Errorf("GetUserRepositories: %v", err)
  661. }
  662. cond = cond.And(builder.In("repo_id", repoIDs))
  663. }
  664. cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
  665. if opts.OnlyPerformedBy {
  666. cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
  667. }
  668. if !opts.IncludePrivate {
  669. cond = cond.And(builder.Eq{"is_private": false})
  670. }
  671. if !opts.IncludeDeleted {
  672. cond = cond.And(builder.Eq{"is_deleted": false})
  673. }
  674. actions := make([]*Action, 0, 20)
  675. if err := x.Limit(20).Desc("id").Where(cond).Find(&actions); err != nil {
  676. return nil, fmt.Errorf("Find: %v", err)
  677. }
  678. if err := ActionList(actions).LoadAttributes(); err != nil {
  679. return nil, fmt.Errorf("LoadAttributes: %v", err)
  680. }
  681. return actions, nil
  682. }