123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- // Copyright 2022 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package cargo
-
- import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "io"
- "path"
- "strconv"
- "time"
-
- packages_model "code.gitea.io/gitea/models/packages"
- repo_model "code.gitea.io/gitea/models/repo"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
- cargo_module "code.gitea.io/gitea/modules/packages/cargo"
- repo_module "code.gitea.io/gitea/modules/repository"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
- files_service "code.gitea.io/gitea/services/repository/files"
- )
-
- const (
- IndexRepositoryName = "_cargo-index"
- ConfigFileName = "config.json"
- )
-
- // https://doc.rust-lang.org/cargo/reference/registries.html#index-format
-
- func BuildPackagePath(name string) string {
- switch len(name) {
- case 0:
- panic("Cargo package name can not be empty")
- case 1:
- return path.Join("1", name)
- case 2:
- return path.Join("2", name)
- case 3:
- return path.Join("3", string(name[0]), name)
- default:
- return path.Join(name[0:2], name[2:4], name)
- }
- }
-
- func InitializeIndexRepository(ctx context.Context, doer, owner *user_model.User) error {
- repo, err := getOrCreateIndexRepository(ctx, doer, owner)
- if err != nil {
- return err
- }
-
- if err := createOrUpdateConfigFile(ctx, repo, doer, owner); err != nil {
- return fmt.Errorf("createOrUpdateConfigFile: %w", err)
- }
-
- return nil
- }
-
- func RebuildIndex(ctx context.Context, doer, owner *user_model.User) error {
- repo, err := getOrCreateIndexRepository(ctx, doer, owner)
- if err != nil {
- return err
- }
-
- ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeCargo)
- if err != nil {
- return fmt.Errorf("GetPackagesByType: %w", err)
- }
-
- return alterRepositoryContent(
- ctx,
- doer,
- repo,
- "Rebuild Cargo Index",
- func(t *files_service.TemporaryUploadRepository) error {
- // Remove all existing content but the Cargo config
- files, err := t.LsFiles()
- if err != nil {
- return err
- }
- for i, file := range files {
- if file == ConfigFileName {
- files[i] = files[len(files)-1]
- files = files[:len(files)-1]
- break
- }
- }
- if err := t.RemoveFilesFromIndex(files...); err != nil {
- return err
- }
-
- // Add all packages
- for _, p := range ps {
- if err := addOrUpdatePackageIndex(ctx, t, p); err != nil {
- return err
- }
- }
-
- return nil
- },
- )
- }
-
- func AddOrUpdatePackageIndex(ctx context.Context, doer, owner *user_model.User, packageID int64) error {
- repo, err := getOrCreateIndexRepository(ctx, doer, owner)
- if err != nil {
- return err
- }
-
- p, err := packages_model.GetPackageByID(ctx, packageID)
- if err != nil {
- return fmt.Errorf("GetPackageByID[%d]: %w", packageID, err)
- }
-
- return alterRepositoryContent(
- ctx,
- doer,
- repo,
- "Update "+p.Name,
- func(t *files_service.TemporaryUploadRepository) error {
- return addOrUpdatePackageIndex(ctx, t, p)
- },
- )
- }
-
- type IndexVersionEntry struct {
- Name string `json:"name"`
- Version string `json:"vers"`
- Dependencies []*cargo_module.Dependency `json:"deps"`
- FileChecksum string `json:"cksum"`
- Features map[string][]string `json:"features"`
- Yanked bool `json:"yanked"`
- Links string `json:"links,omitempty"`
- }
-
- func BuildPackageIndex(ctx context.Context, p *packages_model.Package) (*bytes.Buffer, error) {
- pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
- PackageID: p.ID,
- Sort: packages_model.SortVersionAsc,
- })
- if err != nil {
- return nil, fmt.Errorf("SearchVersions[%s]: %w", p.Name, err)
- }
- if len(pvs) == 0 {
- return nil, nil
- }
-
- pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
- if err != nil {
- return nil, fmt.Errorf("GetPackageDescriptors[%s]: %w", p.Name, err)
- }
-
- var b bytes.Buffer
- for _, pd := range pds {
- metadata := pd.Metadata.(*cargo_module.Metadata)
-
- dependencies := metadata.Dependencies
- if dependencies == nil {
- dependencies = make([]*cargo_module.Dependency, 0)
- }
-
- features := metadata.Features
- if features == nil {
- features = make(map[string][]string)
- }
-
- yanked, _ := strconv.ParseBool(pd.VersionProperties.GetByName(cargo_module.PropertyYanked))
- entry, err := json.Marshal(&IndexVersionEntry{
- Name: pd.Package.Name,
- Version: pd.Version.Version,
- Dependencies: dependencies,
- FileChecksum: pd.Files[0].Blob.HashSHA256,
- Features: features,
- Yanked: yanked,
- Links: metadata.Links,
- })
- if err != nil {
- return nil, err
- }
-
- b.Write(entry)
- b.WriteString("\n")
- }
-
- return &b, nil
- }
-
- func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, p *packages_model.Package) error {
- b, err := BuildPackageIndex(ctx, p)
- if err != nil {
- return err
- }
- if b == nil {
- return nil
- }
-
- return writeObjectToIndex(t, BuildPackagePath(p.LowerName), b)
- }
-
- func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.User) (*repo_model.Repository, error) {
- repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- repo, err = repo_module.CreateRepository(doer, owner, repo_module.CreateRepoOptions{
- Name: IndexRepositoryName,
- })
- if err != nil {
- return nil, fmt.Errorf("CreateRepository: %w", err)
- }
- } else {
- return nil, fmt.Errorf("GetRepositoryByOwnerAndName: %w", err)
- }
- }
-
- return repo, nil
- }
-
- type Config struct {
- DownloadURL string `json:"dl"`
- APIURL string `json:"api"`
- }
-
- func BuildConfig(owner *user_model.User) *Config {
- return &Config{
- DownloadURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
- APIURL: setting.AppURL + "api/packages/" + owner.Name + "/cargo",
- }
- }
-
- func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, doer, owner *user_model.User) error {
- return alterRepositoryContent(
- ctx,
- doer,
- repo,
- "Initialize Cargo Config",
- func(t *files_service.TemporaryUploadRepository) error {
- var b bytes.Buffer
- err := json.NewEncoder(&b).Encode(BuildConfig(owner))
- if err != nil {
- return err
- }
-
- return writeObjectToIndex(t, ConfigFileName, &b)
- },
- )
- }
-
- // This is a shorter version of CreateOrUpdateRepoFile which allows to perform multiple actions on a git repository
- func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitMessage string, fn func(*files_service.TemporaryUploadRepository) error) error {
- t, err := files_service.NewTemporaryUploadRepository(ctx, repo)
- if err != nil {
- return err
- }
- defer t.Close()
-
- var lastCommitID string
- if err := t.Clone(repo.DefaultBranch); err != nil {
- if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
- return err
- }
- if err := t.Init(); err != nil {
- return err
- }
- } else {
- if err := t.SetDefaultIndex(); err != nil {
- return err
- }
-
- commit, err := t.GetBranchCommit(repo.DefaultBranch)
- if err != nil {
- return err
- }
-
- lastCommitID = commit.ID.String()
- }
-
- if err := fn(t); err != nil {
- return err
- }
-
- treeHash, err := t.WriteTree()
- if err != nil {
- return err
- }
-
- now := time.Now()
- commitHash, err := t.CommitTreeWithDate(lastCommitID, doer, doer, treeHash, commitMessage, false, now, now)
- if err != nil {
- return err
- }
-
- return t.Push(doer, commitHash, repo.DefaultBranch)
- }
-
- func writeObjectToIndex(t *files_service.TemporaryUploadRepository, path string, r io.Reader) error {
- hash, err := t.HashObject(r)
- if err != nil {
- return err
- }
-
- return t.AddObjectToIndex("100644", hash, path)
- }
|