gitea源码

convert.go 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package convert
  5. import (
  6. "bytes"
  7. "context"
  8. "fmt"
  9. "net/url"
  10. "path"
  11. "strconv"
  12. "strings"
  13. "time"
  14. actions_model "code.gitea.io/gitea/models/actions"
  15. asymkey_model "code.gitea.io/gitea/models/asymkey"
  16. "code.gitea.io/gitea/models/auth"
  17. "code.gitea.io/gitea/models/db"
  18. git_model "code.gitea.io/gitea/models/git"
  19. issues_model "code.gitea.io/gitea/models/issues"
  20. "code.gitea.io/gitea/models/organization"
  21. "code.gitea.io/gitea/models/perm"
  22. access_model "code.gitea.io/gitea/models/perm/access"
  23. repo_model "code.gitea.io/gitea/models/repo"
  24. "code.gitea.io/gitea/models/unit"
  25. user_model "code.gitea.io/gitea/models/user"
  26. "code.gitea.io/gitea/modules/actions"
  27. "code.gitea.io/gitea/modules/container"
  28. "code.gitea.io/gitea/modules/git"
  29. "code.gitea.io/gitea/modules/log"
  30. "code.gitea.io/gitea/modules/setting"
  31. api "code.gitea.io/gitea/modules/structs"
  32. "code.gitea.io/gitea/modules/util"
  33. asymkey_service "code.gitea.io/gitea/services/asymkey"
  34. "code.gitea.io/gitea/services/gitdiff"
  35. runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
  36. "github.com/nektos/act/pkg/model"
  37. )
  38. // ToEmail convert models.EmailAddress to api.Email
  39. func ToEmail(email *user_model.EmailAddress) *api.Email {
  40. return &api.Email{
  41. Email: email.Email,
  42. Verified: email.IsActivated,
  43. Primary: email.IsPrimary,
  44. }
  45. }
  46. // ToEmail convert models.EmailAddress to api.Email
  47. func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email {
  48. return &api.Email{
  49. Email: email.Email,
  50. Verified: email.IsActivated,
  51. Primary: email.IsPrimary,
  52. UserID: email.UID,
  53. UserName: email.Name,
  54. }
  55. }
  56. // ToBranch convert a git.Commit and git.Branch to an api.Branch
  57. func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
  58. if bp == nil {
  59. var hasPerm bool
  60. var canPush bool
  61. var err error
  62. if user != nil {
  63. hasPerm, err = access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
  64. if err != nil {
  65. return nil, err
  66. }
  67. perms, err := access_model.GetUserRepoPermission(ctx, repo, user)
  68. if err != nil {
  69. return nil, err
  70. }
  71. canPush = issues_model.CanMaintainerWriteToBranch(ctx, perms, branchName, user)
  72. }
  73. return &api.Branch{
  74. Name: branchName,
  75. Commit: ToPayloadCommit(ctx, repo, c),
  76. Protected: false,
  77. RequiredApprovals: 0,
  78. EnableStatusCheck: false,
  79. StatusCheckContexts: []string{},
  80. UserCanPush: canPush,
  81. UserCanMerge: hasPerm,
  82. }, nil
  83. }
  84. branch := &api.Branch{
  85. Name: branchName,
  86. Commit: ToPayloadCommit(ctx, repo, c),
  87. Protected: true,
  88. RequiredApprovals: bp.RequiredApprovals,
  89. EnableStatusCheck: bp.EnableStatusCheck,
  90. StatusCheckContexts: bp.StatusCheckContexts,
  91. }
  92. if isRepoAdmin {
  93. branch.EffectiveBranchProtectionName = bp.RuleName
  94. }
  95. if user != nil {
  96. permission, err := access_model.GetUserRepoPermission(ctx, repo, user)
  97. if err != nil {
  98. return nil, err
  99. }
  100. bp.Repo = repo
  101. branch.UserCanPush = bp.CanUserPush(ctx, user)
  102. branch.UserCanMerge = git_model.IsUserMergeWhitelisted(ctx, bp, user.ID, permission)
  103. }
  104. return branch, nil
  105. }
  106. // getWhitelistEntities returns the names of the entities that are in the whitelist
  107. func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T, whitelistIDs []int64) []string {
  108. whitelistUserIDsSet := container.SetOf(whitelistIDs...)
  109. whitelistNames := make([]string, 0)
  110. for _, entity := range entities {
  111. switch v := any(entity).(type) {
  112. case *user_model.User:
  113. if whitelistUserIDsSet.Contains(v.ID) {
  114. whitelistNames = append(whitelistNames, v.Name)
  115. }
  116. case *organization.Team:
  117. if whitelistUserIDsSet.Contains(v.ID) {
  118. whitelistNames = append(whitelistNames, v.Name)
  119. }
  120. }
  121. }
  122. return whitelistNames
  123. }
  124. // ToBranchProtection convert a ProtectedBranch to api.BranchProtection
  125. func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
  126. readers, err := access_model.GetRepoReaders(ctx, repo)
  127. if err != nil {
  128. log.Error("GetRepoReaders: %v", err)
  129. }
  130. pushWhitelistUsernames := getWhitelistEntities(readers, bp.WhitelistUserIDs)
  131. forcePushAllowlistUsernames := getWhitelistEntities(readers, bp.ForcePushAllowlistUserIDs)
  132. mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs)
  133. approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs)
  134. teamReaders, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.Owner.ID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
  135. if err != nil {
  136. log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
  137. }
  138. pushWhitelistTeams := getWhitelistEntities(teamReaders, bp.WhitelistTeamIDs)
  139. forcePushAllowlistTeams := getWhitelistEntities(teamReaders, bp.ForcePushAllowlistTeamIDs)
  140. mergeWhitelistTeams := getWhitelistEntities(teamReaders, bp.MergeWhitelistTeamIDs)
  141. approvalsWhitelistTeams := getWhitelistEntities(teamReaders, bp.ApprovalsWhitelistTeamIDs)
  142. branchName := ""
  143. if !git_model.IsRuleNameSpecial(bp.RuleName) {
  144. branchName = bp.RuleName
  145. }
  146. return &api.BranchProtection{
  147. BranchName: branchName,
  148. RuleName: bp.RuleName,
  149. Priority: bp.Priority,
  150. EnablePush: bp.CanPush,
  151. EnablePushWhitelist: bp.EnableWhitelist,
  152. PushWhitelistUsernames: pushWhitelistUsernames,
  153. PushWhitelistTeams: pushWhitelistTeams,
  154. PushWhitelistDeployKeys: bp.WhitelistDeployKeys,
  155. EnableForcePush: bp.CanForcePush,
  156. EnableForcePushAllowlist: bp.EnableForcePushAllowlist,
  157. ForcePushAllowlistUsernames: forcePushAllowlistUsernames,
  158. ForcePushAllowlistTeams: forcePushAllowlistTeams,
  159. ForcePushAllowlistDeployKeys: bp.ForcePushAllowlistDeployKeys,
  160. EnableMergeWhitelist: bp.EnableMergeWhitelist,
  161. MergeWhitelistUsernames: mergeWhitelistUsernames,
  162. MergeWhitelistTeams: mergeWhitelistTeams,
  163. EnableStatusCheck: bp.EnableStatusCheck,
  164. StatusCheckContexts: bp.StatusCheckContexts,
  165. RequiredApprovals: bp.RequiredApprovals,
  166. EnableApprovalsWhitelist: bp.EnableApprovalsWhitelist,
  167. ApprovalsWhitelistUsernames: approvalsWhitelistUsernames,
  168. ApprovalsWhitelistTeams: approvalsWhitelistTeams,
  169. BlockOnRejectedReviews: bp.BlockOnRejectedReviews,
  170. BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
  171. BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch,
  172. DismissStaleApprovals: bp.DismissStaleApprovals,
  173. IgnoreStaleApprovals: bp.IgnoreStaleApprovals,
  174. RequireSignedCommits: bp.RequireSignedCommits,
  175. ProtectedFilePatterns: bp.ProtectedFilePatterns,
  176. UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
  177. BlockAdminMergeOverride: bp.BlockAdminMergeOverride,
  178. Created: bp.CreatedUnix.AsTime(),
  179. Updated: bp.UpdatedUnix.AsTime(),
  180. }
  181. }
  182. // ToTag convert a git.Tag to an api.Tag
  183. func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
  184. tarballURL := util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz")
  185. zipballURL := util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip")
  186. // Archive URLs are "" if the download feature is disabled
  187. if setting.Repository.DisableDownloadSourceArchives {
  188. tarballURL = ""
  189. zipballURL = ""
  190. }
  191. return &api.Tag{
  192. Name: t.Name,
  193. Message: strings.TrimSpace(t.Message),
  194. ID: t.ID.String(),
  195. Commit: ToCommitMeta(repo, t),
  196. ZipballURL: zipballURL,
  197. TarballURL: tarballURL,
  198. }
  199. }
  200. // ToActionTask convert a actions_model.ActionTask to an api.ActionTask
  201. func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.ActionTask, error) {
  202. if err := t.LoadAttributes(ctx); err != nil {
  203. return nil, err
  204. }
  205. url := strings.TrimSuffix(setting.AppURL, "/") + t.GetRunLink()
  206. return &api.ActionTask{
  207. ID: t.ID,
  208. Name: t.Job.Name,
  209. HeadBranch: t.Job.Run.PrettyRef(),
  210. HeadSHA: t.Job.CommitSHA,
  211. RunNumber: t.Job.Run.Index,
  212. Event: t.Job.Run.TriggerEvent,
  213. DisplayTitle: t.Job.Run.Title,
  214. Status: t.Status.String(),
  215. WorkflowID: t.Job.Run.WorkflowID,
  216. URL: url,
  217. CreatedAt: t.Created.AsLocalTime(),
  218. UpdatedAt: t.Updated.AsLocalTime(),
  219. RunStartedAt: t.Started.AsLocalTime(),
  220. }, nil
  221. }
  222. func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun) (*api.ActionWorkflowRun, error) {
  223. err := run.LoadAttributes(ctx)
  224. if err != nil {
  225. return nil, err
  226. }
  227. status, conclusion := ToActionsStatus(run.Status)
  228. return &api.ActionWorkflowRun{
  229. ID: run.ID,
  230. URL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), run.ID),
  231. HTMLURL: run.HTMLURL(),
  232. RunNumber: run.Index,
  233. StartedAt: run.Started.AsLocalTime(),
  234. CompletedAt: run.Stopped.AsLocalTime(),
  235. Event: string(run.Event),
  236. DisplayTitle: run.Title,
  237. HeadBranch: git.RefName(run.Ref).BranchName(),
  238. HeadSha: run.CommitSHA,
  239. Status: status,
  240. Conclusion: conclusion,
  241. Path: fmt.Sprintf("%s@%s", run.WorkflowID, run.Ref),
  242. Repository: ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
  243. TriggerActor: ToUser(ctx, run.TriggerUser, nil),
  244. // We do not have a way to get a different User for the actor than the trigger user
  245. Actor: ToUser(ctx, run.TriggerUser, nil),
  246. }, nil
  247. }
  248. func ToWorkflowRunAction(status actions_model.Status) string {
  249. var action string
  250. switch status {
  251. case actions_model.StatusWaiting, actions_model.StatusBlocked:
  252. action = "requested"
  253. case actions_model.StatusRunning:
  254. action = "in_progress"
  255. }
  256. if status.IsDone() {
  257. action = "completed"
  258. }
  259. return action
  260. }
  261. func ToActionsStatus(status actions_model.Status) (string, string) {
  262. var action string
  263. var conclusion string
  264. switch status {
  265. // This is a naming conflict of the webhook between Gitea and GitHub Actions
  266. case actions_model.StatusWaiting:
  267. action = "queued"
  268. case actions_model.StatusBlocked:
  269. action = "waiting"
  270. case actions_model.StatusRunning:
  271. action = "in_progress"
  272. }
  273. if status.IsDone() {
  274. action = "completed"
  275. switch status {
  276. case actions_model.StatusSuccess:
  277. conclusion = "success"
  278. case actions_model.StatusCancelled:
  279. conclusion = "cancelled"
  280. case actions_model.StatusFailure:
  281. conclusion = "failure"
  282. case actions_model.StatusSkipped:
  283. conclusion = "skipped"
  284. }
  285. }
  286. return action, conclusion
  287. }
  288. // ToActionWorkflowJob convert a actions_model.ActionRunJob to an api.ActionWorkflowJob
  289. // task is optional and can be nil
  290. func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task *actions_model.ActionTask, job *actions_model.ActionRunJob) (*api.ActionWorkflowJob, error) {
  291. err := job.LoadAttributes(ctx)
  292. if err != nil {
  293. return nil, err
  294. }
  295. jobIndex := 0
  296. jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID)
  297. if err != nil {
  298. return nil, err
  299. }
  300. for i, j := range jobs {
  301. if j.ID == job.ID {
  302. jobIndex = i
  303. break
  304. }
  305. }
  306. status, conclusion := ToActionsStatus(job.Status)
  307. var runnerID int64
  308. var runnerName string
  309. var steps []*api.ActionWorkflowStep
  310. if job.TaskID != 0 {
  311. if task == nil {
  312. task, _, err = db.GetByID[actions_model.ActionTask](ctx, job.TaskID)
  313. if err != nil {
  314. return nil, err
  315. }
  316. }
  317. runnerID = task.RunnerID
  318. if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
  319. runnerName = runner.Name
  320. }
  321. for i, step := range task.Steps {
  322. stepStatus, stepConclusion := ToActionsStatus(job.Status)
  323. steps = append(steps, &api.ActionWorkflowStep{
  324. Name: step.Name,
  325. Number: int64(i),
  326. Status: stepStatus,
  327. Conclusion: stepConclusion,
  328. StartedAt: step.Started.AsTime().UTC(),
  329. CompletedAt: step.Stopped.AsTime().UTC(),
  330. })
  331. }
  332. }
  333. return &api.ActionWorkflowJob{
  334. ID: job.ID,
  335. // missing api endpoint for this location
  336. URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID),
  337. HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex),
  338. RunID: job.RunID,
  339. // Missing api endpoint for this location, artifacts are available under a nested url
  340. RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID),
  341. Name: job.Name,
  342. Labels: job.RunsOn,
  343. RunAttempt: job.Attempt,
  344. HeadSha: job.Run.CommitSHA,
  345. HeadBranch: git.RefName(job.Run.Ref).BranchName(),
  346. Status: status,
  347. Conclusion: conclusion,
  348. RunnerID: runnerID,
  349. RunnerName: runnerName,
  350. Steps: steps,
  351. CreatedAt: job.Created.AsTime().UTC(),
  352. StartedAt: job.Started.AsTime().UTC(),
  353. CompletedAt: job.Stopped.AsTime().UTC(),
  354. }, nil
  355. }
  356. func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, commit *git.Commit, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
  357. cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions)
  358. cfg := cfgUnit.ActionsConfig()
  359. defaultBranch, _ := commit.GetBranchName()
  360. workflowURL := fmt.Sprintf("%s/actions/workflows/%s", repo.APIURL(), util.PathEscapeSegments(entry.Name()))
  361. workflowRepoURL := fmt.Sprintf("%s/src/branch/%s/%s/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(defaultBranch), util.PathEscapeSegments(folder), util.PathEscapeSegments(entry.Name()))
  362. badgeURL := fmt.Sprintf("%s/actions/workflows/%s/badge.svg?branch=%s", repo.HTMLURL(ctx), util.PathEscapeSegments(entry.Name()), url.QueryEscape(repo.DefaultBranch))
  363. // See https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#get-a-workflow
  364. // State types:
  365. // - active
  366. // - deleted
  367. // - disabled_fork
  368. // - disabled_inactivity
  369. // - disabled_manually
  370. state := "active"
  371. if cfg.IsWorkflowDisabled(entry.Name()) {
  372. state = "disabled_manually"
  373. }
  374. // The CreatedAt and UpdatedAt fields currently reflect the timestamp of the latest commit, which can later be refined
  375. // by retrieving the first and last commits for the file history. The first commit would indicate the creation date,
  376. // while the last commit would represent the modification date. The DeletedAt could be determined by identifying
  377. // the last commit where the file existed. However, this implementation has not been done here yet, as it would likely
  378. // cause a significant performance degradation.
  379. createdAt := commit.Author.When
  380. updatedAt := commit.Author.When
  381. content, err := actions.GetContentFromEntry(entry)
  382. name := entry.Name()
  383. if err == nil {
  384. workflow, err := model.ReadWorkflow(bytes.NewReader(content))
  385. if err == nil {
  386. // Only use the name when specified in the workflow file
  387. if workflow.Name != "" {
  388. name = workflow.Name
  389. }
  390. } else {
  391. log.Error("getActionWorkflowEntry: Failed to parse workflow: %v", err)
  392. }
  393. } else {
  394. log.Error("getActionWorkflowEntry: Failed to get content from entry: %v", err)
  395. }
  396. return &api.ActionWorkflow{
  397. ID: entry.Name(),
  398. Name: name,
  399. Path: path.Join(folder, entry.Name()),
  400. State: state,
  401. CreatedAt: createdAt,
  402. UpdatedAt: updatedAt,
  403. URL: workflowURL,
  404. HTMLURL: workflowRepoURL,
  405. BadgeURL: badgeURL,
  406. }
  407. }
  408. func ListActionWorkflows(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository) ([]*api.ActionWorkflow, error) {
  409. defaultBranchCommit, err := gitrepo.GetBranchCommit(repo.DefaultBranch)
  410. if err != nil {
  411. return nil, err
  412. }
  413. folder, entries, err := actions.ListWorkflows(defaultBranchCommit)
  414. if err != nil {
  415. return nil, err
  416. }
  417. workflows := make([]*api.ActionWorkflow, len(entries))
  418. for i, entry := range entries {
  419. workflows[i] = getActionWorkflowEntry(ctx, repo, defaultBranchCommit, folder, entry)
  420. }
  421. return workflows, nil
  422. }
  423. func GetActionWorkflow(ctx context.Context, gitrepo *git.Repository, repo *repo_model.Repository, workflowID string) (*api.ActionWorkflow, error) {
  424. entries, err := ListActionWorkflows(ctx, gitrepo, repo)
  425. if err != nil {
  426. return nil, err
  427. }
  428. for _, entry := range entries {
  429. if entry.ID == workflowID {
  430. return entry, nil
  431. }
  432. }
  433. return nil, util.NewNotExistErrorf("workflow %q not found", workflowID)
  434. }
  435. // ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact
  436. func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) {
  437. url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID)
  438. return &api.ActionArtifact{
  439. ID: art.ID,
  440. Name: art.ArtifactName,
  441. SizeInBytes: art.FileSize,
  442. Expired: art.Status == actions_model.ArtifactStatusExpired,
  443. URL: url,
  444. ArchiveDownloadURL: url + "/zip",
  445. CreatedAt: art.CreatedUnix.AsLocalTime(),
  446. UpdatedAt: art.UpdatedUnix.AsLocalTime(),
  447. ExpiresAt: art.ExpiredUnix.AsLocalTime(),
  448. WorkflowRun: &api.ActionWorkflowRun{
  449. ID: art.RunID,
  450. RepositoryID: art.RepoID,
  451. HeadSha: art.CommitSHA,
  452. },
  453. }, nil
  454. }
  455. func ToActionRunner(ctx context.Context, runner *actions_model.ActionRunner) *api.ActionRunner {
  456. status := runner.Status()
  457. apiStatus := "offline"
  458. if runner.IsOnline() {
  459. apiStatus = "online"
  460. }
  461. labels := make([]*api.ActionRunnerLabel, len(runner.AgentLabels))
  462. for i, label := range runner.AgentLabels {
  463. labels[i] = &api.ActionRunnerLabel{
  464. ID: int64(i),
  465. Name: label,
  466. Type: "custom",
  467. }
  468. }
  469. return &api.ActionRunner{
  470. ID: runner.ID,
  471. Name: runner.Name,
  472. Status: apiStatus,
  473. Busy: status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE,
  474. Ephemeral: runner.Ephemeral,
  475. Labels: labels,
  476. }
  477. }
  478. // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
  479. func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
  480. verif := asymkey_service.ParseCommitWithSignature(ctx, c)
  481. commitVerification := &api.PayloadCommitVerification{
  482. Verified: verif.Verified,
  483. Reason: verif.Reason,
  484. }
  485. if c.Signature != nil {
  486. commitVerification.Signature = c.Signature.Signature
  487. commitVerification.Payload = c.Signature.Payload
  488. }
  489. if verif.SigningUser != nil {
  490. commitVerification.Signer = &api.PayloadUser{
  491. Name: verif.SigningUser.Name,
  492. Email: verif.SigningUser.Email,
  493. }
  494. }
  495. return commitVerification
  496. }
  497. // ToPublicKey convert asymkey_model.PublicKey to api.PublicKey
  498. func ToPublicKey(apiLink string, key *asymkey_model.PublicKey) *api.PublicKey {
  499. return &api.PublicKey{
  500. ID: key.ID,
  501. Key: key.Content,
  502. URL: fmt.Sprintf("%s%d", apiLink, key.ID),
  503. Title: key.Name,
  504. Fingerprint: key.Fingerprint,
  505. Created: key.CreatedUnix.AsTime(),
  506. Updated: key.UpdatedUnix.AsTime(),
  507. }
  508. }
  509. // ToGPGKey converts models.GPGKey to api.GPGKey
  510. func ToGPGKey(key *asymkey_model.GPGKey) *api.GPGKey {
  511. subkeys := make([]*api.GPGKey, len(key.SubsKey))
  512. for id, k := range key.SubsKey {
  513. subkeys[id] = &api.GPGKey{
  514. ID: k.ID,
  515. PrimaryKeyID: k.PrimaryKeyID,
  516. KeyID: k.KeyID,
  517. PublicKey: k.Content,
  518. Created: k.CreatedUnix.AsTime(),
  519. Expires: k.ExpiredUnix.AsTime(),
  520. CanSign: k.CanSign,
  521. CanEncryptComms: k.CanEncryptComms,
  522. CanEncryptStorage: k.CanEncryptStorage,
  523. CanCertify: k.CanSign,
  524. Verified: k.Verified,
  525. }
  526. }
  527. emails := make([]*api.GPGKeyEmail, len(key.Emails))
  528. for i, e := range key.Emails {
  529. emails[i] = ToGPGKeyEmail(e)
  530. }
  531. return &api.GPGKey{
  532. ID: key.ID,
  533. PrimaryKeyID: key.PrimaryKeyID,
  534. KeyID: key.KeyID,
  535. PublicKey: key.Content,
  536. Created: key.CreatedUnix.AsTime(),
  537. Expires: key.ExpiredUnix.AsTime(),
  538. Emails: emails,
  539. SubsKey: subkeys,
  540. CanSign: key.CanSign,
  541. CanEncryptComms: key.CanEncryptComms,
  542. CanEncryptStorage: key.CanEncryptStorage,
  543. CanCertify: key.CanSign,
  544. Verified: key.Verified,
  545. }
  546. }
  547. // ToGPGKeyEmail convert models.EmailAddress to api.GPGKeyEmail
  548. func ToGPGKeyEmail(email *user_model.EmailAddress) *api.GPGKeyEmail {
  549. return &api.GPGKeyEmail{
  550. Email: email.Email,
  551. Verified: email.IsActivated,
  552. }
  553. }
  554. // ToGitHook convert git.Hook to api.GitHook
  555. func ToGitHook(h *git.Hook) *api.GitHook {
  556. return &api.GitHook{
  557. Name: h.Name(),
  558. IsActive: h.IsActive,
  559. Content: h.Content,
  560. }
  561. }
  562. // ToDeployKey convert asymkey_model.DeployKey to api.DeployKey
  563. func ToDeployKey(apiLink string, key *asymkey_model.DeployKey) *api.DeployKey {
  564. return &api.DeployKey{
  565. ID: key.ID,
  566. KeyID: key.KeyID,
  567. Key: key.Content,
  568. Fingerprint: key.Fingerprint,
  569. URL: fmt.Sprintf("%s%d", apiLink, key.ID),
  570. Title: key.Name,
  571. Created: key.CreatedUnix.AsTime(),
  572. ReadOnly: key.Mode == perm.AccessModeRead, // All deploy keys are read-only.
  573. }
  574. }
  575. // ToOrganization convert user_model.User to api.Organization
  576. func ToOrganization(ctx context.Context, org *organization.Organization) *api.Organization {
  577. return &api.Organization{
  578. ID: org.ID,
  579. AvatarURL: org.AsUser().AvatarLink(ctx),
  580. Name: org.Name,
  581. UserName: org.Name,
  582. FullName: org.FullName,
  583. Email: org.Email,
  584. Description: org.Description,
  585. Website: org.Website,
  586. Location: org.Location,
  587. Visibility: org.Visibility.String(),
  588. RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
  589. }
  590. }
  591. // ToTeam convert models.Team to api.Team
  592. func ToTeam(ctx context.Context, team *organization.Team, loadOrg ...bool) (*api.Team, error) {
  593. teams, err := ToTeams(ctx, []*organization.Team{team}, len(loadOrg) != 0 && loadOrg[0])
  594. if err != nil || len(teams) == 0 {
  595. return nil, err
  596. }
  597. return teams[0], nil
  598. }
  599. // ToTeams convert models.Team list to api.Team list
  600. func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) {
  601. cache := make(map[int64]*api.Organization)
  602. apiTeams := make([]*api.Team, 0, len(teams))
  603. for _, t := range teams {
  604. if err := t.LoadUnits(ctx); err != nil {
  605. return nil, err
  606. }
  607. apiTeam := &api.Team{
  608. ID: t.ID,
  609. Name: t.Name,
  610. Description: t.Description,
  611. IncludesAllRepositories: t.IncludesAllRepositories,
  612. CanCreateOrgRepo: t.CanCreateOrgRepo,
  613. Permission: t.AccessMode.ToString(),
  614. Units: t.GetUnitNames(),
  615. UnitsMap: t.GetUnitsMap(),
  616. }
  617. if loadOrgs {
  618. apiOrg, ok := cache[t.OrgID]
  619. if !ok {
  620. org, err := organization.GetOrgByID(ctx, t.OrgID)
  621. if err != nil {
  622. return nil, err
  623. }
  624. apiOrg = ToOrganization(ctx, org)
  625. cache[t.OrgID] = apiOrg
  626. }
  627. apiTeam.Organization = apiOrg
  628. }
  629. apiTeams = append(apiTeams, apiTeam)
  630. }
  631. return apiTeams, nil
  632. }
  633. // ToAnnotatedTag convert git.Tag to api.AnnotatedTag
  634. func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag {
  635. return &api.AnnotatedTag{
  636. Tag: t.Name,
  637. SHA: t.ID.String(),
  638. Object: ToAnnotatedTagObject(repo, c),
  639. Message: t.Message,
  640. URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()),
  641. Tagger: ToCommitUser(t.Tagger),
  642. Verification: ToVerification(ctx, c),
  643. }
  644. }
  645. // ToAnnotatedTagObject convert a git.Commit to an api.AnnotatedTagObject
  646. func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.AnnotatedTagObject {
  647. return &api.AnnotatedTagObject{
  648. SHA: commit.ID.String(),
  649. Type: string(git.ObjectCommit),
  650. URL: util.URLJoin(repo.APIURL(), "git/commits", commit.ID.String()),
  651. }
  652. }
  653. // ToTagProtection convert a git.ProtectedTag to an api.TagProtection
  654. func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
  655. readers, err := access_model.GetRepoReaders(ctx, repo)
  656. if err != nil {
  657. log.Error("GetRepoReaders: %v", err)
  658. }
  659. whitelistUsernames := getWhitelistEntities(readers, pt.AllowlistUserIDs)
  660. teamReaders, err := organization.GetTeamsWithAccessToAnyRepoUnit(ctx, repo.Owner.ID, repo.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
  661. if err != nil {
  662. log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
  663. }
  664. whitelistTeams := getWhitelistEntities(teamReaders, pt.AllowlistTeamIDs)
  665. return &api.TagProtection{
  666. ID: pt.ID,
  667. NamePattern: pt.NamePattern,
  668. WhitelistUsernames: whitelistUsernames,
  669. WhitelistTeams: whitelistTeams,
  670. Created: pt.CreatedUnix.AsTime(),
  671. Updated: pt.UpdatedUnix.AsTime(),
  672. }
  673. }
  674. // ToTopicResponse convert from models.Topic to api.TopicResponse
  675. func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
  676. return &api.TopicResponse{
  677. ID: topic.ID,
  678. Name: topic.Name,
  679. RepoCount: topic.RepoCount,
  680. Created: topic.CreatedUnix.AsTime(),
  681. Updated: topic.UpdatedUnix.AsTime(),
  682. }
  683. }
  684. // ToOAuth2Application convert from auth.OAuth2Application to api.OAuth2Application
  685. func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
  686. return &api.OAuth2Application{
  687. ID: app.ID,
  688. Name: app.Name,
  689. ClientID: app.ClientID,
  690. ClientSecret: app.ClientSecret,
  691. ConfidentialClient: app.ConfidentialClient,
  692. SkipSecondaryAuthorization: app.SkipSecondaryAuthorization,
  693. RedirectURIs: app.RedirectURIs,
  694. Created: app.CreatedUnix.AsTime(),
  695. }
  696. }
  697. // ToLFSLock convert a LFSLock to api.LFSLock
  698. func ToLFSLock(ctx context.Context, l *git_model.LFSLock) *api.LFSLock {
  699. u, err := user_model.GetUserByID(ctx, l.OwnerID)
  700. if err != nil {
  701. return nil
  702. }
  703. return &api.LFSLock{
  704. ID: strconv.FormatInt(l.ID, 10),
  705. Path: l.Path,
  706. LockedAt: l.Created.Round(time.Second),
  707. Owner: &api.LFSLockOwner{
  708. Name: u.Name,
  709. },
  710. }
  711. }
  712. // ToChangedFile convert a gitdiff.DiffFile to api.ChangedFile
  713. func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile {
  714. status := "changed"
  715. previousFilename := ""
  716. if f.IsDeleted {
  717. status = "deleted"
  718. } else if f.IsCreated {
  719. status = "added"
  720. } else if f.IsRenamed && f.Type == gitdiff.DiffFileCopy {
  721. status = "copied"
  722. } else if f.IsRenamed && f.Type == gitdiff.DiffFileRename {
  723. status = "renamed"
  724. previousFilename = f.OldName
  725. } else if f.Addition == 0 && f.Deletion == 0 {
  726. status = "unchanged"
  727. }
  728. file := &api.ChangedFile{
  729. Filename: f.GetDiffFileName(),
  730. Status: status,
  731. Additions: f.Addition,
  732. Deletions: f.Deletion,
  733. Changes: f.Addition + f.Deletion,
  734. PreviousFilename: previousFilename,
  735. HTMLURL: fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
  736. ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
  737. RawURL: fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
  738. }
  739. return file
  740. }