gitea

Зеркало из https://github.com/go-gitea/gitea
Форк
0
315 строк · 8.2 Кб
1
// Copyright 2022 The Gitea Authors. All rights reserved.
2
// SPDX-License-Identifier: MIT
3

4
package cargo
5

6
import (
7
	"bytes"
8
	"context"
9
	"errors"
10
	"fmt"
11
	"io"
12
	"path"
13
	"strconv"
14
	"time"
15

16
	packages_model "code.gitea.io/gitea/models/packages"
17
	repo_model "code.gitea.io/gitea/models/repo"
18
	user_model "code.gitea.io/gitea/models/user"
19
	"code.gitea.io/gitea/modules/git"
20
	"code.gitea.io/gitea/modules/json"
21
	cargo_module "code.gitea.io/gitea/modules/packages/cargo"
22
	"code.gitea.io/gitea/modules/setting"
23
	"code.gitea.io/gitea/modules/structs"
24
	"code.gitea.io/gitea/modules/util"
25
	repo_service "code.gitea.io/gitea/services/repository"
26
	files_service "code.gitea.io/gitea/services/repository/files"
27
)
28

29
const (
30
	IndexRepositoryName = "_cargo-index"
31
	ConfigFileName      = "config.json"
32
)
33

34
// https://doc.rust-lang.org/cargo/reference/registries.html#index-format
35

36
func BuildPackagePath(name string) string {
37
	switch len(name) {
38
	case 0:
39
		panic("Cargo package name can not be empty")
40
	case 1:
41
		return path.Join("1", name)
42
	case 2:
43
		return path.Join("2", name)
44
	case 3:
45
		return path.Join("3", string(name[0]), name)
46
	default:
47
		return path.Join(name[0:2], name[2:4], name)
48
	}
49
}
50

51
func InitializeIndexRepository(ctx context.Context, doer, owner *user_model.User) error {
52
	repo, err := getOrCreateIndexRepository(ctx, doer, owner)
53
	if err != nil {
54
		return err
55
	}
56

57
	if err := createOrUpdateConfigFile(ctx, repo, doer, owner); err != nil {
58
		return fmt.Errorf("createOrUpdateConfigFile: %w", err)
59
	}
60

61
	return nil
62
}
63

64
func RebuildIndex(ctx context.Context, doer, owner *user_model.User) error {
65
	repo, err := getOrCreateIndexRepository(ctx, doer, owner)
66
	if err != nil {
67
		return err
68
	}
69

70
	ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeCargo)
71
	if err != nil {
72
		return fmt.Errorf("GetPackagesByType: %w", err)
73
	}
74

75
	return alterRepositoryContent(
76
		ctx,
77
		doer,
78
		repo,
79
		"Rebuild Cargo Index",
80
		func(t *files_service.TemporaryUploadRepository) error {
81
			// Remove all existing content but the Cargo config
82
			files, err := t.LsFiles()
83
			if err != nil {
84
				return err
85
			}
86
			for i, file := range files {
87
				if file == ConfigFileName {
88
					files[i] = files[len(files)-1]
89
					files = files[:len(files)-1]
90
					break
91
				}
92
			}
93
			if err := t.RemoveFilesFromIndex(files...); err != nil {
94
				return err
95
			}
96

97
			// Add all packages
98
			for _, p := range ps {
99
				if err := addOrUpdatePackageIndex(ctx, t, p); err != nil {
100
					return err
101
				}
102
			}
103

104
			return nil
105
		},
106
	)
107
}
108

109
func UpdatePackageIndexIfExists(ctx context.Context, doer, owner *user_model.User, packageID int64) error {
110
	// We do not want to force the creation of the repo here
111
	// cargo http index does not rely on the repo itself,
112
	// so if the repo does not exist, we just do nothing.
113
	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
114
	if err != nil {
115
		if errors.Is(err, util.ErrNotExist) {
116
			return nil
117
		}
118
		return fmt.Errorf("GetRepositoryByOwnerAndName: %w", err)
119
	}
120

121
	p, err := packages_model.GetPackageByID(ctx, packageID)
122
	if err != nil {
123
		return fmt.Errorf("GetPackageByID[%d]: %w", packageID, err)
124
	}
125

126
	return alterRepositoryContent(
127
		ctx,
128
		doer,
129
		repo,
130
		"Update "+p.Name,
131
		func(t *files_service.TemporaryUploadRepository) error {
132
			return addOrUpdatePackageIndex(ctx, t, p)
133
		},
134
	)
135
}
136

137
type IndexVersionEntry struct {
138
	Name         string                     `json:"name"`
139
	Version      string                     `json:"vers"`
140
	Dependencies []*cargo_module.Dependency `json:"deps"`
141
	FileChecksum string                     `json:"cksum"`
142
	Features     map[string][]string        `json:"features"`
143
	Yanked       bool                       `json:"yanked"`
144
	Links        string                     `json:"links,omitempty"`
145
}
146

147
func BuildPackageIndex(ctx context.Context, p *packages_model.Package) (*bytes.Buffer, error) {
148
	pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
149
		PackageID: p.ID,
150
		Sort:      packages_model.SortVersionAsc,
151
	})
152
	if err != nil {
153
		return nil, fmt.Errorf("SearchVersions[%s]: %w", p.Name, err)
154
	}
155
	if len(pvs) == 0 {
156
		return nil, nil
157
	}
158

159
	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
160
	if err != nil {
161
		return nil, fmt.Errorf("GetPackageDescriptors[%s]: %w", p.Name, err)
162
	}
163

164
	var b bytes.Buffer
165
	for _, pd := range pds {
166
		metadata := pd.Metadata.(*cargo_module.Metadata)
167

168
		dependencies := metadata.Dependencies
169
		if dependencies == nil {
170
			dependencies = make([]*cargo_module.Dependency, 0)
171
		}
172

173
		features := metadata.Features
174
		if features == nil {
175
			features = make(map[string][]string)
176
		}
177

178
		yanked, _ := strconv.ParseBool(pd.VersionProperties.GetByName(cargo_module.PropertyYanked))
179
		entry, err := json.Marshal(&IndexVersionEntry{
180
			Name:         pd.Package.Name,
181
			Version:      pd.Version.Version,
182
			Dependencies: dependencies,
183
			FileChecksum: pd.Files[0].Blob.HashSHA256,
184
			Features:     features,
185
			Yanked:       yanked,
186
			Links:        metadata.Links,
187
		})
188
		if err != nil {
189
			return nil, err
190
		}
191

192
		b.Write(entry)
193
		b.WriteString("\n")
194
	}
195

196
	return &b, nil
197
}
198

199
func addOrUpdatePackageIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, p *packages_model.Package) error {
200
	b, err := BuildPackageIndex(ctx, p)
201
	if err != nil {
202
		return err
203
	}
204
	if b == nil {
205
		return nil
206
	}
207

208
	return writeObjectToIndex(t, BuildPackagePath(p.LowerName), b)
209
}
210

211
func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.User) (*repo_model.Repository, error) {
212
	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
213
	if err != nil {
214
		if errors.Is(err, util.ErrNotExist) {
215
			repo, err = repo_service.CreateRepositoryDirectly(ctx, doer, owner, repo_service.CreateRepoOptions{
216
				Name: IndexRepositoryName,
217
			})
218
			if err != nil {
219
				return nil, fmt.Errorf("CreateRepository: %w", err)
220
			}
221
		} else {
222
			return nil, fmt.Errorf("GetRepositoryByOwnerAndName: %w", err)
223
		}
224
	}
225

226
	return repo, nil
227
}
228

229
type Config struct {
230
	DownloadURL  string `json:"dl"`
231
	APIURL       string `json:"api"`
232
	AuthRequired bool   `json:"auth-required"`
233
}
234

235
func BuildConfig(owner *user_model.User, isPrivate bool) *Config {
236
	return &Config{
237
		DownloadURL:  setting.AppURL + "api/packages/" + owner.Name + "/cargo/api/v1/crates",
238
		APIURL:       setting.AppURL + "api/packages/" + owner.Name + "/cargo",
239
		AuthRequired: isPrivate,
240
	}
241
}
242

243
func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, doer, owner *user_model.User) error {
244
	return alterRepositoryContent(
245
		ctx,
246
		doer,
247
		repo,
248
		"Initialize Cargo Config",
249
		func(t *files_service.TemporaryUploadRepository) error {
250
			var b bytes.Buffer
251
			err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInView || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate))
252
			if err != nil {
253
				return err
254
			}
255

256
			return writeObjectToIndex(t, ConfigFileName, &b)
257
		},
258
	)
259
}
260

261
// This is a shorter version of CreateOrUpdateRepoFile which allows to perform multiple actions on a git repository
262
func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitMessage string, fn func(*files_service.TemporaryUploadRepository) error) error {
263
	t, err := files_service.NewTemporaryUploadRepository(ctx, repo)
264
	if err != nil {
265
		return err
266
	}
267
	defer t.Close()
268

269
	var lastCommitID string
270
	if err := t.Clone(repo.DefaultBranch, true); err != nil {
271
		if !git.IsErrBranchNotExist(err) || !repo.IsEmpty {
272
			return err
273
		}
274
		if err := t.Init(repo.ObjectFormatName); err != nil {
275
			return err
276
		}
277
	} else {
278
		if err := t.SetDefaultIndex(); err != nil {
279
			return err
280
		}
281

282
		commit, err := t.GetBranchCommit(repo.DefaultBranch)
283
		if err != nil {
284
			return err
285
		}
286

287
		lastCommitID = commit.ID.String()
288
	}
289

290
	if err := fn(t); err != nil {
291
		return err
292
	}
293

294
	treeHash, err := t.WriteTree()
295
	if err != nil {
296
		return err
297
	}
298

299
	now := time.Now()
300
	commitHash, err := t.CommitTreeWithDate(lastCommitID, doer, doer, treeHash, commitMessage, false, now, now)
301
	if err != nil {
302
		return err
303
	}
304

305
	return t.Push(doer, commitHash, repo.DefaultBranch)
306
}
307

308
func writeObjectToIndex(t *files_service.TemporaryUploadRepository, path string, r io.Reader) error {
309
	hash, err := t.HashObject(r)
310
	if err != nil {
311
		return err
312
	}
313

314
	return t.AddObjectToIndex("100644", hash, path)
315
}
316

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.