gitea源码

index.go 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cargo
  4. import (
  5. "bytes"
  6. "context"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "path"
  11. "strconv"
  12. packages_model "code.gitea.io/gitea/models/packages"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/json"
  17. cargo_module "code.gitea.io/gitea/modules/packages/cargo"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. repo_service "code.gitea.io/gitea/services/repository"
  22. files_service "code.gitea.io/gitea/services/repository/files"
  23. )
  24. const (
  25. IndexRepositoryName = "_cargo-index"
  26. ConfigFileName = "config.json"
  27. )
  28. // https://doc.rust-lang.org/cargo/reference/registries.html#index-format
  29. func BuildPackagePath(name string) string {
  30. switch len(name) {
  31. case 0:
  32. panic("Cargo package name can not be empty")
  33. case 1:
  34. return path.Join("1", name)
  35. case 2:
  36. return path.Join("2", name)
  37. case 3:
  38. return path.Join("3", string(name[0]), name)
  39. default:
  40. return path.Join(name[0:2], name[2:4], name)
  41. }
  42. }
  43. func InitializeIndexRepository(ctx context.Context, doer, owner *user_model.User) error {
  44. repo, err := getOrCreateIndexRepository(ctx, doer, owner)
  45. if err != nil {
  46. return err
  47. }
  48. if err := createOrUpdateConfigFile(ctx, repo, doer, owner); err != nil {
  49. return fmt.Errorf("createOrUpdateConfigFile: %w", err)
  50. }
  51. return nil
  52. }
  53. func RebuildIndex(ctx context.Context, doer, owner *user_model.User) error {
  54. repo, err := getOrCreateIndexRepository(ctx, doer, owner)
  55. if err != nil {
  56. return err
  57. }
  58. ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeCargo)
  59. if err != nil {
  60. return fmt.Errorf("GetPackagesByType: %w", err)
  61. }
  62. return alterRepositoryContent(
  63. ctx,
  64. doer,
  65. repo,
  66. "Rebuild Cargo Index",
  67. func(t *files_service.TemporaryUploadRepository) error {
  68. // Remove all existing content but the Cargo config
  69. files, err := t.LsFiles(ctx)
  70. if err != nil {
  71. return err
  72. }
  73. for i, file := range files {
  74. if file == ConfigFileName {
  75. files[i] = files[len(files)-1]
  76. files = files[:len(files)-1]
  77. break
  78. }
  79. }
  80. if err := t.RemoveFilesFromIndex(ctx, files...); err != nil {
  81. return err
  82. }
  83. // Add all packages
  84. for _, p := range ps {
  85. if err := addOrUpdatePackageIndex(ctx, t, p); err != nil {
  86. return err
  87. }
  88. }
  89. return nil
  90. },
  91. )
  92. }
  93. func UpdatePackageIndexIfExists(ctx context.Context, doer, owner *user_model.User, packageID int64) error {
  94. // We do not want to force the creation of the repo here
  95. // cargo http index does not rely on the repo itself,
  96. // so if the repo does not exist, we just do nothing.
  97. repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
  98. if err != nil {
  99. if errors.Is(err, util.ErrNotExist) {
  100. return nil
  101. }
  102. return fmt.Errorf("GetRepositoryByOwnerAndName: %w", err)
  103. }
  104. p, err := packages_model.GetPackageByID(ctx, packageID)
  105. if err != nil {
  106. return fmt.Errorf("GetPackageByID[%d]: %w", packageID, err)
  107. }
  108. return alterRepositoryContent(
  109. ctx,
  110. doer,
  111. repo,
  112. "Update "+p.Name,
  113. func(t *files_service.TemporaryUploadRepository) error {
  114. return addOrUpdatePackageIndex(ctx, t, p)
  115. },
  116. )
  117. }
  118. type IndexVersionEntry struct {
  119. Name string `json:"name"`
  120. Version string `json:"vers"`
  121. Dependencies []*cargo_module.Dependency `json:"deps"`
  122. FileChecksum string `json:"cksum"`
  123. Features map[string][]string `json:"features"`
  124. Yanked bool `json:"yanked"`
  125. Links string `json:"links,omitempty"`
  126. }
  127. func BuildPackageIndex(ctx context.Context, p *packages_model.Package) (*bytes.Buffer, error) {
  128. pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
  129. PackageID: p.ID,
  130. Sort: packages_model.SortVersionAsc,
  131. })
  132. if err != nil {
  133. return nil, fmt.Errorf("SearchVersions[%s]: %w", p.Name, err)
  134. }
  135. if len(pvs) == 0 {
  136. return nil, nil
  137. }
  138. pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
  139. if err != nil {
  140. return nil, fmt.Errorf("GetPackageDescriptors[%s]: %w", p.Name, err)
  141. }
  142. var b bytes.Buffer
  143. for _, pd := range pds {
  144. metadata := pd.Metadata.(*cargo_module.Metadata)
  145. dependencies := metadata.Dependencies
  146. if dependencies == nil {
  147. dependencies = make([]*cargo_module.Dependency, 0)
  148. }
  149. features := metadata.Features
  150. if features == nil {
  151. features = make(map[string][]string)
  152. }
  153. yanked, _ := strconv.ParseBool(pd.VersionProperties.GetByName(cargo_module.PropertyYanked))
  154. entry, err := json.Marshal(&IndexVersionEntry{
  155. Name: pd.Package.Name,
  156. Version: pd.Version.Version,
  157. Dependencies: dependencies,
  158. FileChecksum: pd.Files[0].Blob.HashSHA256,
  159. Features: features,
  160. Yanked: yanked,
  161. Links: metadata.Links,
  162. })
  163. if err != nil {
  164. return nil, err
  165. }
  166. b.Write(entry)
  167. b.WriteString("\n")
  168. }
  169. return &b, nil
  170. }
  171. func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, p *packages_model.Package) error {
  172. b, err := BuildPackageIndex(ctx, p)
  173. if err != nil {
  174. return err
  175. }
  176. if b == nil {
  177. return nil
  178. }
  179. return writeObjectToIndex(ctx, t, BuildPackagePath(p.LowerName), b)
  180. }
  181. func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.User) (*repo_model.Repository, error) {
  182. repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
  183. if err != nil {
  184. if errors.Is(err, util.ErrNotExist) {
  185. repo, err = repo_service.CreateRepositoryDirectly(ctx, doer, owner, repo_service.CreateRepoOptions{
  186. Name: IndexRepositoryName,
  187. }, true)
  188. if err != nil {
  189. return nil, fmt.Errorf("CreateRepository: %w", err)
  190. }
  191. } else {
  192. return nil, fmt.Errorf("GetRepositoryByOwnerAndName: %w", err)
  193. }
  194. }
  195. return repo, nil
  196. }
  197. type Config struct {
  198. DownloadURL string `json:"dl"`
  199. APIURL string `json:"api"`
  200. AuthRequired bool `json:"auth-required"`
  201. }
  202. func BuildConfig(owner *user_model.User, isPrivate bool) *Config {
  203. return &Config{
  204. DownloadURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
  205. APIURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo",
  206. AuthRequired: isPrivate,
  207. }
  208. }
  209. func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, doer, owner *user_model.User) error {
  210. return alterRepositoryContent(
  211. ctx,
  212. doer,
  213. repo,
  214. "Initialize Cargo Config",
  215. func(t *files_service.TemporaryUploadRepository) error {
  216. var b bytes.Buffer
  217. err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInViewStrict || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate))
  218. if err != nil {
  219. return err
  220. }
  221. return writeObjectToIndex(ctx, t, ConfigFileName, &b)
  222. },
  223. )
  224. }
  225. // This is a shorter version of CreateOrUpdateRepoFile which allows to perform multiple actions on a git repository
  226. func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitMessage string, fn func(*files_service.TemporaryUploadRepository) error) error {
  227. t, err := files_service.NewTemporaryUploadRepository(repo)
  228. if err != nil {
  229. return err
  230. }
  231. defer t.Close()
  232. var lastCommitID string
  233. if err := t.Clone(ctx, repo.DefaultBranch, true); err != nil {
  234. if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
  235. return err
  236. }
  237. if err := t.Init(ctx, repo.ObjectFormatName); err != nil {
  238. return err
  239. }
  240. } else {
  241. if err := t.SetDefaultIndex(ctx); err != nil {
  242. return err
  243. }
  244. commit, err := t.GetBranchCommit(repo.DefaultBranch)
  245. if err != nil {
  246. return err
  247. }
  248. lastCommitID = commit.ID.String()
  249. }
  250. if err := fn(t); err != nil {
  251. return err
  252. }
  253. treeHash, err := t.WriteTree(ctx)
  254. if err != nil {
  255. return err
  256. }
  257. commitOpts := &files_service.CommitTreeUserOptions{
  258. ParentCommitID: lastCommitID,
  259. TreeHash: treeHash,
  260. CommitMessage: commitMessage,
  261. DoerUser: doer,
  262. }
  263. commitHash, err := t.CommitTree(ctx, commitOpts)
  264. if err != nil {
  265. return err
  266. }
  267. return t.Push(ctx, doer, commitHash, repo.DefaultBranch)
  268. }
  269. func writeObjectToIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, path string, r io.Reader) error {
  270. hash, err := t.HashObjectAndWrite(ctx, r)
  271. if err != nil {
  272. return err
  273. }
  274. return t.AddObjectToIndex(ctx, "100644", hash, path)
  275. }