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.

release.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package release
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/models/db"
  11. git_model "code.gitea.io/gitea/models/git"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/container"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/repository"
  18. "code.gitea.io/gitea/modules/storage"
  19. "code.gitea.io/gitea/modules/timeutil"
  20. "code.gitea.io/gitea/modules/util"
  21. notify_service "code.gitea.io/gitea/services/notify"
  22. )
  23. func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Release, msg string) (bool, error) {
  24. err := rel.LoadAttributes(ctx)
  25. if err != nil {
  26. return false, err
  27. }
  28. err = rel.Repo.MustNotBeArchived()
  29. if err != nil {
  30. return false, err
  31. }
  32. var created bool
  33. // Only actual create when publish.
  34. if !rel.IsDraft {
  35. if !gitRepo.IsTagExist(rel.TagName) {
  36. if err := rel.LoadAttributes(ctx); err != nil {
  37. log.Error("LoadAttributes: %v", err)
  38. return false, err
  39. }
  40. protectedTags, err := git_model.GetProtectedTags(ctx, rel.Repo.ID)
  41. if err != nil {
  42. return false, fmt.Errorf("GetProtectedTags: %w", err)
  43. }
  44. // Trim '--' prefix to prevent command line argument vulnerability.
  45. rel.TagName = strings.TrimPrefix(rel.TagName, "--")
  46. isAllowed, err := git_model.IsUserAllowedToControlTag(ctx, protectedTags, rel.TagName, rel.PublisherID)
  47. if err != nil {
  48. return false, err
  49. }
  50. if !isAllowed {
  51. return false, models.ErrProtectedTagName{
  52. TagName: rel.TagName,
  53. }
  54. }
  55. commit, err := gitRepo.GetCommit(rel.Target)
  56. if err != nil {
  57. return false, fmt.Errorf("createTag::GetCommit[%v]: %w", rel.Target, err)
  58. }
  59. if len(msg) > 0 {
  60. if err = gitRepo.CreateAnnotatedTag(rel.TagName, msg, commit.ID.String()); err != nil {
  61. if strings.Contains(err.Error(), "is not a valid tag name") {
  62. return false, models.ErrInvalidTagName{
  63. TagName: rel.TagName,
  64. }
  65. }
  66. return false, err
  67. }
  68. } else if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
  69. if strings.Contains(err.Error(), "is not a valid tag name") {
  70. return false, models.ErrInvalidTagName{
  71. TagName: rel.TagName,
  72. }
  73. }
  74. return false, err
  75. }
  76. created = true
  77. rel.LowerTagName = strings.ToLower(rel.TagName)
  78. commits := repository.NewPushCommits()
  79. commits.HeadCommit = repository.CommitToPushCommit(commit)
  80. commits.CompareURL = rel.Repo.ComposeCompareURL(git.EmptySHA, commit.ID.String())
  81. refFullName := git.RefNameFromTag(rel.TagName)
  82. notify_service.PushCommits(
  83. ctx, rel.Publisher, rel.Repo,
  84. &repository.PushUpdateOptions{
  85. RefFullName: refFullName,
  86. OldCommitID: git.EmptySHA,
  87. NewCommitID: commit.ID.String(),
  88. }, commits)
  89. notify_service.CreateRef(ctx, rel.Publisher, rel.Repo, refFullName, commit.ID.String())
  90. rel.CreatedUnix = timeutil.TimeStampNow()
  91. }
  92. commit, err := gitRepo.GetTagCommit(rel.TagName)
  93. if err != nil {
  94. return false, fmt.Errorf("GetTagCommit: %w", err)
  95. }
  96. rel.Sha1 = commit.ID.String()
  97. rel.NumCommits, err = commit.CommitsCount()
  98. if err != nil {
  99. return false, fmt.Errorf("CommitsCount: %w", err)
  100. }
  101. if rel.PublisherID <= 0 {
  102. u, err := user_model.GetUserByEmail(ctx, commit.Author.Email)
  103. if err == nil {
  104. rel.PublisherID = u.ID
  105. }
  106. }
  107. } else {
  108. rel.CreatedUnix = timeutil.TimeStampNow()
  109. }
  110. return created, nil
  111. }
  112. // CreateRelease creates a new release of repository.
  113. func CreateRelease(gitRepo *git.Repository, rel *repo_model.Release, attachmentUUIDs []string, msg string) error {
  114. has, err := repo_model.IsReleaseExist(gitRepo.Ctx, rel.RepoID, rel.TagName)
  115. if err != nil {
  116. return err
  117. } else if has {
  118. return repo_model.ErrReleaseAlreadyExist{
  119. TagName: rel.TagName,
  120. }
  121. }
  122. if _, err = createTag(gitRepo.Ctx, gitRepo, rel, msg); err != nil {
  123. return err
  124. }
  125. rel.LowerTagName = strings.ToLower(rel.TagName)
  126. if err = db.Insert(gitRepo.Ctx, rel); err != nil {
  127. return err
  128. }
  129. if err = repo_model.AddReleaseAttachments(gitRepo.Ctx, rel.ID, attachmentUUIDs); err != nil {
  130. return err
  131. }
  132. if !rel.IsDraft {
  133. notify_service.NewRelease(gitRepo.Ctx, rel)
  134. }
  135. return nil
  136. }
  137. // CreateNewTag creates a new repository tag
  138. func CreateNewTag(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commit, tagName, msg string) error {
  139. has, err := repo_model.IsReleaseExist(ctx, repo.ID, tagName)
  140. if err != nil {
  141. return err
  142. } else if has {
  143. return models.ErrTagAlreadyExists{
  144. TagName: tagName,
  145. }
  146. }
  147. gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath())
  148. if err != nil {
  149. return err
  150. }
  151. defer closer.Close()
  152. rel := &repo_model.Release{
  153. RepoID: repo.ID,
  154. Repo: repo,
  155. PublisherID: doer.ID,
  156. Publisher: doer,
  157. TagName: tagName,
  158. Target: commit,
  159. IsDraft: false,
  160. IsPrerelease: false,
  161. IsTag: true,
  162. }
  163. if _, err = createTag(ctx, gitRepo, rel, msg); err != nil {
  164. return err
  165. }
  166. return db.Insert(ctx, rel)
  167. }
  168. // UpdateRelease updates information, attachments of a release and will create tag if it's not a draft and tag not exist.
  169. // addAttachmentUUIDs accept a slice of new created attachments' uuids which will be reassigned release_id as the created release
  170. // delAttachmentUUIDs accept a slice of attachments' uuids which will be deleted from the release
  171. // editAttachments accept a map of attachment uuid to new attachment name which will be updated with attachments.
  172. func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, rel *repo_model.Release,
  173. addAttachmentUUIDs, delAttachmentUUIDs []string, editAttachments map[string]string,
  174. ) error {
  175. if rel.ID == 0 {
  176. return errors.New("UpdateRelease only accepts an exist release")
  177. }
  178. isCreated, err := createTag(gitRepo.Ctx, gitRepo, rel, "")
  179. if err != nil {
  180. return err
  181. }
  182. rel.LowerTagName = strings.ToLower(rel.TagName)
  183. ctx, committer, err := db.TxContext(ctx)
  184. if err != nil {
  185. return err
  186. }
  187. defer committer.Close()
  188. if err = repo_model.UpdateRelease(ctx, rel); err != nil {
  189. return err
  190. }
  191. if err = repo_model.AddReleaseAttachments(ctx, rel.ID, addAttachmentUUIDs); err != nil {
  192. return fmt.Errorf("AddReleaseAttachments: %w", err)
  193. }
  194. deletedUUIDs := make(container.Set[string])
  195. if len(delAttachmentUUIDs) > 0 {
  196. // Check attachments
  197. attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, delAttachmentUUIDs)
  198. if err != nil {
  199. return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %w", delAttachmentUUIDs, err)
  200. }
  201. for _, attach := range attachments {
  202. if attach.ReleaseID != rel.ID {
  203. return util.SilentWrap{
  204. Message: "delete attachment of release permission denied",
  205. Err: util.ErrPermissionDenied,
  206. }
  207. }
  208. deletedUUIDs.Add(attach.UUID)
  209. }
  210. if _, err := repo_model.DeleteAttachments(ctx, attachments, true); err != nil {
  211. return fmt.Errorf("DeleteAttachments [uuids: %v]: %w", delAttachmentUUIDs, err)
  212. }
  213. }
  214. if len(editAttachments) > 0 {
  215. updateAttachmentsList := make([]string, 0, len(editAttachments))
  216. for k := range editAttachments {
  217. updateAttachmentsList = append(updateAttachmentsList, k)
  218. }
  219. // Check attachments
  220. attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, updateAttachmentsList)
  221. if err != nil {
  222. return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %w", updateAttachmentsList, err)
  223. }
  224. for _, attach := range attachments {
  225. if attach.ReleaseID != rel.ID {
  226. return util.SilentWrap{
  227. Message: "update attachment of release permission denied",
  228. Err: util.ErrPermissionDenied,
  229. }
  230. }
  231. }
  232. for uuid, newName := range editAttachments {
  233. if !deletedUUIDs.Contains(uuid) {
  234. if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{
  235. UUID: uuid,
  236. Name: newName,
  237. }, "name"); err != nil {
  238. return err
  239. }
  240. }
  241. }
  242. }
  243. if err := committer.Commit(); err != nil {
  244. return err
  245. }
  246. for _, uuid := range delAttachmentUUIDs {
  247. if err := storage.Attachments.Delete(repo_model.AttachmentRelativePath(uuid)); err != nil {
  248. // Even delete files failed, but the attachments has been removed from database, so we
  249. // should not return error but only record the error on logs.
  250. // users have to delete this attachments manually or we should have a
  251. // synchronize between database attachment table and attachment storage
  252. log.Error("delete attachment[uuid: %s] failed: %v", uuid, err)
  253. }
  254. }
  255. if !isCreated {
  256. notify_service.UpdateRelease(gitRepo.Ctx, doer, rel)
  257. return nil
  258. }
  259. if !rel.IsDraft {
  260. notify_service.NewRelease(gitRepo.Ctx, rel)
  261. }
  262. return nil
  263. }
  264. // DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
  265. func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, delTag bool) error {
  266. rel, err := repo_model.GetReleaseByID(ctx, id)
  267. if err != nil {
  268. return fmt.Errorf("GetReleaseByID: %w", err)
  269. }
  270. repo, err := repo_model.GetRepositoryByID(ctx, rel.RepoID)
  271. if err != nil {
  272. return fmt.Errorf("GetRepositoryByID: %w", err)
  273. }
  274. if delTag {
  275. protectedTags, err := git_model.GetProtectedTags(ctx, rel.RepoID)
  276. if err != nil {
  277. return fmt.Errorf("GetProtectedTags: %w", err)
  278. }
  279. isAllowed, err := git_model.IsUserAllowedToControlTag(ctx, protectedTags, rel.TagName, rel.PublisherID)
  280. if err != nil {
  281. return err
  282. }
  283. if !isAllowed {
  284. return models.ErrProtectedTagName{
  285. TagName: rel.TagName,
  286. }
  287. }
  288. if stdout, _, err := git.NewCommand(ctx, "tag", "-d").AddDashesAndList(rel.TagName).
  289. SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)).
  290. RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") {
  291. log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err)
  292. return fmt.Errorf("git tag -d: %w", err)
  293. }
  294. refName := git.RefNameFromTag(rel.TagName)
  295. notify_service.PushCommits(
  296. ctx, doer, repo,
  297. &repository.PushUpdateOptions{
  298. RefFullName: refName,
  299. OldCommitID: rel.Sha1,
  300. NewCommitID: git.EmptySHA,
  301. }, repository.NewPushCommits())
  302. notify_service.DeleteRef(ctx, doer, repo, refName)
  303. if err := repo_model.DeleteReleaseByID(ctx, id); err != nil {
  304. return fmt.Errorf("DeleteReleaseByID: %w", err)
  305. }
  306. } else {
  307. rel.IsTag = true
  308. if err = repo_model.UpdateRelease(ctx, rel); err != nil {
  309. return fmt.Errorf("Update: %w", err)
  310. }
  311. }
  312. rel.Repo = repo
  313. if err = rel.LoadAttributes(ctx); err != nil {
  314. return fmt.Errorf("LoadAttributes: %w", err)
  315. }
  316. if err := repo_model.DeleteAttachmentsByRelease(ctx, rel.ID); err != nil {
  317. return fmt.Errorf("DeleteAttachments: %w", err)
  318. }
  319. for i := range rel.Attachments {
  320. attachment := rel.Attachments[i]
  321. if err := storage.Attachments.Delete(attachment.RelativePath()); err != nil {
  322. log.Error("Delete attachment %s of release %s failed: %v", attachment.UUID, rel.ID, err)
  323. }
  324. }
  325. notify_service.DeleteRelease(ctx, doer, rel)
  326. return nil
  327. }