gitea源码

notifier_helper.go 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "bytes"
  6. "context"
  7. "fmt"
  8. "slices"
  9. "strings"
  10. actions_model "code.gitea.io/gitea/models/actions"
  11. "code.gitea.io/gitea/models/db"
  12. issues_model "code.gitea.io/gitea/models/issues"
  13. packages_model "code.gitea.io/gitea/models/packages"
  14. access_model "code.gitea.io/gitea/models/perm/access"
  15. repo_model "code.gitea.io/gitea/models/repo"
  16. unit_model "code.gitea.io/gitea/models/unit"
  17. user_model "code.gitea.io/gitea/models/user"
  18. actions_module "code.gitea.io/gitea/modules/actions"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/gitrepo"
  21. "code.gitea.io/gitea/modules/json"
  22. "code.gitea.io/gitea/modules/log"
  23. "code.gitea.io/gitea/modules/setting"
  24. api "code.gitea.io/gitea/modules/structs"
  25. webhook_module "code.gitea.io/gitea/modules/webhook"
  26. "code.gitea.io/gitea/services/convert"
  27. notify_service "code.gitea.io/gitea/services/notify"
  28. "github.com/nektos/act/pkg/jobparser"
  29. "github.com/nektos/act/pkg/model"
  30. )
  31. type methodCtxKeyType struct{}
  32. var methodCtxKey methodCtxKeyType
  33. // withMethod sets the notification method that this context currently executes.
  34. // Used for debugging/ troubleshooting purposes.
  35. func withMethod(ctx context.Context, method string) context.Context {
  36. // don't overwrite
  37. if v := ctx.Value(methodCtxKey); v != nil {
  38. if _, ok := v.(string); ok {
  39. return ctx
  40. }
  41. }
  42. return context.WithValue(ctx, methodCtxKey, method)
  43. }
  44. // getMethod gets the notification method that this context currently executes.
  45. // Default: "notify"
  46. // Used for debugging/ troubleshooting purposes.
  47. func getMethod(ctx context.Context) string {
  48. if v := ctx.Value(methodCtxKey); v != nil {
  49. if s, ok := v.(string); ok {
  50. return s
  51. }
  52. }
  53. return "notify"
  54. }
  55. type notifyInput struct {
  56. // required
  57. Repo *repo_model.Repository
  58. Doer *user_model.User
  59. Event webhook_module.HookEventType
  60. // optional
  61. Ref git.RefName
  62. Payload api.Payloader
  63. PullRequest *issues_model.PullRequest
  64. }
  65. func newNotifyInput(repo *repo_model.Repository, doer *user_model.User, event webhook_module.HookEventType) *notifyInput {
  66. return &notifyInput{
  67. Repo: repo,
  68. Doer: doer,
  69. Event: event,
  70. }
  71. }
  72. func newNotifyInputForSchedules(repo *repo_model.Repository) *notifyInput {
  73. // the doer here will be ignored as we force using action user when handling schedules
  74. return newNotifyInput(repo, user_model.NewActionsUser(), webhook_module.HookEventSchedule)
  75. }
  76. func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput {
  77. input.Doer = doer
  78. return input
  79. }
  80. func (input *notifyInput) WithRef(ref string) *notifyInput {
  81. input.Ref = git.RefName(ref)
  82. return input
  83. }
  84. func (input *notifyInput) WithPayload(payload api.Payloader) *notifyInput {
  85. input.Payload = payload
  86. return input
  87. }
  88. func (input *notifyInput) WithPullRequest(pr *issues_model.PullRequest) *notifyInput {
  89. input.PullRequest = pr
  90. if input.Ref == "" {
  91. input.Ref = git.RefName(pr.GetGitHeadRefName())
  92. }
  93. return input
  94. }
  95. func (input *notifyInput) Notify(ctx context.Context) {
  96. log.Trace("execute %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
  97. if err := notify(ctx, input); err != nil {
  98. log.Error("an error occurred while executing the %s actions method: %v", getMethod(ctx), err)
  99. }
  100. }
  101. func notify(ctx context.Context, input *notifyInput) error {
  102. shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
  103. if input.Doer.IsGiteaActions() {
  104. // avoiding triggering cyclically, for example:
  105. // a comment of an issue will trigger the runner to add a new comment as reply,
  106. // and the new comment will trigger the runner again.
  107. log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
  108. // we should update schedule tasks in this case, because
  109. // 1. schedule tasks cannot be triggered by other events, so cyclic triggering will not occur
  110. // 2. some schedule tasks may update the repo periodically, so the refs of schedule tasks need to be updated
  111. if shouldDetectSchedules {
  112. return DetectAndHandleSchedules(ctx, input.Repo)
  113. }
  114. return nil
  115. }
  116. if input.Repo.IsEmpty || input.Repo.IsArchived {
  117. return nil
  118. }
  119. if unit_model.TypeActions.UnitGlobalDisabled() {
  120. if err := CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
  121. log.Error("CleanRepoScheduleTasks: %v", err)
  122. }
  123. return nil
  124. }
  125. if err := input.Repo.LoadUnits(ctx); err != nil {
  126. return fmt.Errorf("repo.LoadUnits: %w", err)
  127. } else if !input.Repo.UnitEnabled(ctx, unit_model.TypeActions) {
  128. return nil
  129. }
  130. gitRepo, err := gitrepo.OpenRepository(context.Background(), input.Repo)
  131. if err != nil {
  132. return fmt.Errorf("git.OpenRepository: %w", err)
  133. }
  134. defer gitRepo.Close()
  135. ref := input.Ref
  136. if ref.BranchName() != input.Repo.DefaultBranch && actions_module.IsDefaultBranchWorkflow(input.Event) {
  137. if ref != "" {
  138. log.Warn("Event %q should only trigger workflows on the default branch, but its ref is %q. Will fall back to the default branch",
  139. input.Event, ref)
  140. }
  141. ref = git.RefNameFromBranch(input.Repo.DefaultBranch)
  142. }
  143. if ref == "" {
  144. log.Warn("Ref of event %q is empty, will fall back to the default branch", input.Event)
  145. ref = git.RefNameFromBranch(input.Repo.DefaultBranch)
  146. }
  147. commitID, err := gitRepo.GetRefCommitID(ref.String())
  148. if err != nil {
  149. return fmt.Errorf("gitRepo.GetRefCommitID: %w", err)
  150. }
  151. // Get the commit object for the ref
  152. commit, err := gitRepo.GetCommit(commitID)
  153. if err != nil {
  154. return fmt.Errorf("gitRepo.GetCommit: %w", err)
  155. }
  156. if skipWorkflows(ctx, input, commit) {
  157. return nil
  158. }
  159. var detectedWorkflows []*actions_module.DetectedWorkflow
  160. actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
  161. workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
  162. input.Event,
  163. input.Payload,
  164. shouldDetectSchedules,
  165. )
  166. if err != nil {
  167. return fmt.Errorf("DetectWorkflows: %w", err)
  168. }
  169. log.Trace("repo %s with commit %s event %s find %d workflows and %d schedules",
  170. input.Repo.RepoPath(),
  171. commit.ID,
  172. input.Event,
  173. len(workflows),
  174. len(schedules),
  175. )
  176. for _, wf := range workflows {
  177. if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
  178. log.Trace("repo %s has disable workflows %s", input.Repo.RepoPath(), wf.EntryName)
  179. continue
  180. }
  181. if wf.TriggerEvent.Name != actions_module.GithubEventPullRequestTarget {
  182. detectedWorkflows = append(detectedWorkflows, wf)
  183. }
  184. }
  185. if input.PullRequest != nil {
  186. // detect pull_request_target workflows
  187. baseRef := git.BranchPrefix + input.PullRequest.BaseBranch
  188. baseCommit, err := gitRepo.GetCommit(baseRef)
  189. if err != nil {
  190. return fmt.Errorf("gitRepo.GetCommit: %w", err)
  191. }
  192. baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false)
  193. if err != nil {
  194. return fmt.Errorf("DetectWorkflows: %w", err)
  195. }
  196. if len(baseWorkflows) == 0 {
  197. log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID)
  198. } else {
  199. for _, wf := range baseWorkflows {
  200. if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
  201. detectedWorkflows = append(detectedWorkflows, wf)
  202. }
  203. }
  204. }
  205. }
  206. if shouldDetectSchedules {
  207. if err := handleSchedules(ctx, schedules, commit, input, ref.String()); err != nil {
  208. return err
  209. }
  210. }
  211. return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
  212. }
  213. func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool {
  214. // skip workflow runs with a configured skip-ci string in commit message or pr title if the event is push or pull_request(_sync)
  215. // https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
  216. skipWorkflowEvents := []webhook_module.HookEventType{
  217. webhook_module.HookEventPush,
  218. webhook_module.HookEventPullRequest,
  219. webhook_module.HookEventPullRequestSync,
  220. }
  221. if slices.Contains(skipWorkflowEvents, input.Event) {
  222. for _, s := range setting.Actions.SkipWorkflowStrings {
  223. if input.PullRequest != nil && strings.Contains(input.PullRequest.Issue.Title, s) {
  224. log.Debug("repo %s: skipped run for pr %v because of %s string", input.Repo.RepoPath(), input.PullRequest.Issue.ID, s)
  225. return true
  226. }
  227. if strings.Contains(commit.CommitMessage, s) {
  228. log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RepoPath(), commit.ID, s)
  229. return true
  230. }
  231. }
  232. }
  233. if input.Event == webhook_module.HookEventWorkflowRun {
  234. wrun, ok := input.Payload.(*api.WorkflowRunPayload)
  235. for i := 0; i < 5 && ok && wrun.WorkflowRun != nil; i++ {
  236. if wrun.WorkflowRun.Event != "workflow_run" {
  237. return false
  238. }
  239. r, err := actions_model.GetRunByRepoAndID(ctx, input.Repo.ID, wrun.WorkflowRun.ID)
  240. if err != nil {
  241. log.Error("GetRunByRepoAndID: %v", err)
  242. return true
  243. }
  244. wrun, err = r.GetWorkflowRunEventPayload()
  245. if err != nil {
  246. log.Error("GetWorkflowRunEventPayload: %v", err)
  247. return true
  248. }
  249. }
  250. // skip workflow runs events exceeding the maximum of 5 recursive events
  251. log.Debug("repo %s: skipped workflow_run because of recursive event of 5", input.Repo.RepoPath())
  252. return true
  253. }
  254. return false
  255. }
  256. func handleWorkflows(
  257. ctx context.Context,
  258. detectedWorkflows []*actions_module.DetectedWorkflow,
  259. commit *git.Commit,
  260. input *notifyInput,
  261. ref string,
  262. ) error {
  263. if len(detectedWorkflows) == 0 {
  264. log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
  265. return nil
  266. }
  267. p, err := json.Marshal(input.Payload)
  268. if err != nil {
  269. return fmt.Errorf("json.Marshal: %w", err)
  270. }
  271. isForkPullRequest := false
  272. if pr := input.PullRequest; pr != nil {
  273. switch pr.Flow {
  274. case issues_model.PullRequestFlowGithub:
  275. isForkPullRequest = pr.IsFromFork()
  276. case issues_model.PullRequestFlowAGit:
  277. // There is no fork concept in agit flow, anyone with read permission can push refs/for/<target-branch>/<topic-branch> to the repo.
  278. // So we can treat it as a fork pull request because it may be from an untrusted user
  279. isForkPullRequest = true
  280. default:
  281. // unknown flow, assume it's a fork pull request to be safe
  282. isForkPullRequest = true
  283. }
  284. }
  285. for _, dwf := range detectedWorkflows {
  286. run := &actions_model.ActionRun{
  287. Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
  288. RepoID: input.Repo.ID,
  289. Repo: input.Repo,
  290. OwnerID: input.Repo.OwnerID,
  291. WorkflowID: dwf.EntryName,
  292. TriggerUserID: input.Doer.ID,
  293. TriggerUser: input.Doer,
  294. Ref: ref,
  295. CommitSHA: commit.ID.String(),
  296. IsForkPullRequest: isForkPullRequest,
  297. Event: input.Event,
  298. EventPayload: string(p),
  299. TriggerEvent: dwf.TriggerEvent.Name,
  300. Status: actions_model.StatusWaiting,
  301. }
  302. need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer)
  303. if err != nil {
  304. log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err)
  305. continue
  306. }
  307. run.NeedApproval = need
  308. if err := run.LoadAttributes(ctx); err != nil {
  309. log.Error("LoadAttributes: %v", err)
  310. continue
  311. }
  312. vars, err := actions_model.GetVariablesOfRun(ctx, run)
  313. if err != nil {
  314. log.Error("GetVariablesOfRun: %v", err)
  315. continue
  316. }
  317. giteaCtx := GenerateGiteaContext(run, nil)
  318. jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
  319. if err != nil {
  320. log.Error("jobparser.Parse: %v", err)
  321. continue
  322. }
  323. if len(jobs) > 0 && jobs[0].RunName != "" {
  324. run.Title = jobs[0].RunName
  325. }
  326. // cancel running jobs if the event is push or pull_request_sync
  327. if run.Event == webhook_module.HookEventPush ||
  328. run.Event == webhook_module.HookEventPullRequestSync {
  329. if err := CancelPreviousJobs(
  330. ctx,
  331. run.RepoID,
  332. run.Ref,
  333. run.WorkflowID,
  334. run.Event,
  335. ); err != nil {
  336. log.Error("CancelPreviousJobs: %v", err)
  337. }
  338. }
  339. if err := actions_model.InsertRun(ctx, run, jobs); err != nil {
  340. log.Error("InsertRun: %v", err)
  341. continue
  342. }
  343. alljobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID})
  344. if err != nil {
  345. log.Error("FindRunJobs: %v", err)
  346. continue
  347. }
  348. CreateCommitStatus(ctx, alljobs...)
  349. if len(alljobs) > 0 {
  350. job := alljobs[0]
  351. err := job.LoadRun(ctx)
  352. if err != nil {
  353. log.Error("LoadRun: %v", err)
  354. continue
  355. }
  356. notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
  357. }
  358. for _, job := range alljobs {
  359. notify_service.WorkflowJobStatusUpdate(ctx, input.Repo, input.Doer, job, nil)
  360. }
  361. }
  362. return nil
  363. }
  364. func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
  365. return newNotifyInput(issue.Repo, issue.Poster, event)
  366. }
  367. func notifyRelease(ctx context.Context, doer *user_model.User, rel *repo_model.Release, action api.HookReleaseAction) {
  368. if err := rel.LoadAttributes(ctx); err != nil {
  369. log.Error("LoadAttributes: %v", err)
  370. return
  371. }
  372. permission, _ := access_model.GetUserRepoPermission(ctx, rel.Repo, doer)
  373. newNotifyInput(rel.Repo, doer, webhook_module.HookEventRelease).
  374. WithRef(git.RefNameFromTag(rel.TagName).String()).
  375. WithPayload(&api.ReleasePayload{
  376. Action: action,
  377. Release: convert.ToAPIRelease(ctx, rel.Repo, rel),
  378. Repository: convert.ToRepo(ctx, rel.Repo, permission),
  379. Sender: convert.ToUser(ctx, doer, nil),
  380. }).
  381. Notify(ctx)
  382. }
  383. func notifyPackage(ctx context.Context, sender *user_model.User, pd *packages_model.PackageDescriptor, action api.HookPackageAction) {
  384. if pd.Repository == nil {
  385. // When a package is uploaded to an organization, it could trigger an event to notify.
  386. // So the repository could be nil, however, actions can't support that yet.
  387. // See https://github.com/go-gitea/gitea/pull/17940
  388. return
  389. }
  390. apiPackage, err := convert.ToPackage(ctx, pd, sender)
  391. if err != nil {
  392. log.Error("Error converting package: %v", err)
  393. return
  394. }
  395. newNotifyInput(pd.Repository, sender, webhook_module.HookEventPackage).
  396. WithPayload(&api.PackagePayload{
  397. Action: action,
  398. Package: apiPackage,
  399. Sender: convert.ToUser(ctx, sender, nil),
  400. }).
  401. Notify(ctx)
  402. }
  403. func ifNeedApproval(ctx context.Context, run *actions_model.ActionRun, repo *repo_model.Repository, user *user_model.User) (bool, error) {
  404. // 1. don't need approval if it's not a fork PR
  405. // 2. don't need approval if the event is `pull_request_target` since the workflow will run in the context of base branch
  406. // see https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
  407. if !run.IsForkPullRequest || run.TriggerEvent == actions_module.GithubEventPullRequestTarget {
  408. return false, nil
  409. }
  410. // always need approval if the user is restricted
  411. if user.IsRestricted {
  412. log.Trace("need approval because user %d is restricted", user.ID)
  413. return true, nil
  414. }
  415. // don't need approval if the user can write
  416. if perm, err := access_model.GetUserRepoPermission(ctx, repo, user); err != nil {
  417. return false, fmt.Errorf("GetUserRepoPermission: %w", err)
  418. } else if perm.CanWrite(unit_model.TypeActions) {
  419. log.Trace("do not need approval because user %d can write", user.ID)
  420. return false, nil
  421. }
  422. // don't need approval if the user has been approved before
  423. if count, err := db.Count[actions_model.ActionRun](ctx, actions_model.FindRunOptions{
  424. RepoID: repo.ID,
  425. TriggerUserID: user.ID,
  426. Approved: true,
  427. }); err != nil {
  428. return false, fmt.Errorf("CountRuns: %w", err)
  429. } else if count > 0 {
  430. log.Trace("do not need approval because user %d has been approved before", user.ID)
  431. return false, nil
  432. }
  433. // otherwise, need approval
  434. log.Trace("need approval because it's the first time user %d triggered actions", user.ID)
  435. return true, nil
  436. }
  437. func handleSchedules(
  438. ctx context.Context,
  439. detectedWorkflows []*actions_module.DetectedWorkflow,
  440. commit *git.Commit,
  441. input *notifyInput,
  442. ref string,
  443. ) error {
  444. branch, err := commit.GetBranchName()
  445. if err != nil {
  446. return err
  447. }
  448. if branch != input.Repo.DefaultBranch {
  449. log.Trace("commit branch is not default branch in repo")
  450. return nil
  451. }
  452. if count, err := db.Count[actions_model.ActionSchedule](ctx, actions_model.FindScheduleOptions{RepoID: input.Repo.ID}); err != nil {
  453. log.Error("CountSchedules: %v", err)
  454. return err
  455. } else if count > 0 {
  456. if err := CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
  457. log.Error("CleanRepoScheduleTasks: %v", err)
  458. }
  459. }
  460. if len(detectedWorkflows) == 0 {
  461. log.Trace("repo %s with commit %s couldn't find schedules", input.Repo.RepoPath(), commit.ID)
  462. return nil
  463. }
  464. p, err := json.Marshal(input.Payload)
  465. if err != nil {
  466. return fmt.Errorf("json.Marshal: %w", err)
  467. }
  468. crons := make([]*actions_model.ActionSchedule, 0, len(detectedWorkflows))
  469. for _, dwf := range detectedWorkflows {
  470. // Check cron job condition. Only working in default branch
  471. workflow, err := model.ReadWorkflow(bytes.NewReader(dwf.Content))
  472. if err != nil {
  473. log.Error("ReadWorkflow: %v", err)
  474. continue
  475. }
  476. schedules := workflow.OnSchedule()
  477. if len(schedules) == 0 {
  478. log.Warn("no schedule event")
  479. continue
  480. }
  481. run := &actions_model.ActionSchedule{
  482. Title: strings.SplitN(commit.CommitMessage, "\n", 2)[0],
  483. RepoID: input.Repo.ID,
  484. Repo: input.Repo,
  485. OwnerID: input.Repo.OwnerID,
  486. WorkflowID: dwf.EntryName,
  487. TriggerUserID: user_model.ActionsUserID,
  488. TriggerUser: user_model.NewActionsUser(),
  489. Ref: ref,
  490. CommitSHA: commit.ID.String(),
  491. Event: input.Event,
  492. EventPayload: string(p),
  493. Specs: schedules,
  494. Content: dwf.Content,
  495. }
  496. vars, err := actions_model.GetVariablesOfRun(ctx, run.ToActionRun())
  497. if err != nil {
  498. log.Error("GetVariablesOfRun: %v", err)
  499. continue
  500. }
  501. giteaCtx := GenerateGiteaContext(run.ToActionRun(), nil)
  502. jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars), jobparser.WithGitContext(giteaCtx.ToGitHubContext()))
  503. if err != nil {
  504. log.Error("jobparser.Parse: %v", err)
  505. continue
  506. }
  507. if len(jobs) > 0 && jobs[0].RunName != "" {
  508. run.Title = jobs[0].RunName
  509. }
  510. crons = append(crons, run)
  511. }
  512. return actions_model.CreateScheduleTask(ctx, crons)
  513. }
  514. // DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
  515. func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
  516. if repo.IsEmpty || repo.IsArchived {
  517. return nil
  518. }
  519. gitRepo, err := gitrepo.OpenRepository(context.Background(), repo)
  520. if err != nil {
  521. return fmt.Errorf("git.OpenRepository: %w", err)
  522. }
  523. defer gitRepo.Close()
  524. // Only detect schedule workflows on the default branch
  525. commit, err := gitRepo.GetCommit(repo.DefaultBranch)
  526. if err != nil {
  527. return fmt.Errorf("gitRepo.GetCommit: %w", err)
  528. }
  529. scheduleWorkflows, err := actions_module.DetectScheduledWorkflows(gitRepo, commit)
  530. if err != nil {
  531. return fmt.Errorf("detect schedule workflows: %w", err)
  532. }
  533. if len(scheduleWorkflows) == 0 {
  534. return nil
  535. }
  536. // We need a notifyInput to call handleSchedules
  537. // if repo is a mirror, commit author maybe an external user,
  538. // so we use action user as the Doer of the notifyInput
  539. notifyInput := newNotifyInputForSchedules(repo)
  540. return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
  541. }