diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index b64f6dcd87dc..9c46b25eafc0 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -2120,6 +2120,7 @@
"pub",
"pypi",
"rubygems",
+ "swift",
"vagrant"
],
"type": "string",
diff --git a/tests/integration/api_packages_swift_test.go b/tests/integration/api_packages_swift_test.go
new file mode 100644
index 000000000000..a3035ea60485
--- /dev/null
+++ b/tests/integration/api_packages_swift_test.go
@@ -0,0 +1,326 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "archive/zip"
+ "bytes"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/packages"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ swift_module "code.gitea.io/gitea/modules/packages/swift"
+ "code.gitea.io/gitea/modules/setting"
+ swift_router "code.gitea.io/gitea/routers/api/packages/swift"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPackageSwift(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ packageScope := "test-scope"
+ packageName := "test_package"
+ packageID := packageScope + "." + packageName
+ packageVersion := "1.0.3"
+ packageAuthor := "KN4CK3R"
+ packageDescription := "Gitea Test Package"
+ packageRepositoryURL := "https://gitea.io/gitea/gitea"
+ contentManifest1 := "// swift-tools-version:5.7\n//\n// Package.swift"
+ contentManifest2 := "// swift-tools-version:5.6\n//\n// Package@swift-5.6.swift"
+
+ url := fmt.Sprintf("/api/packages/%s/swift", user.Name)
+
+ t.Run("CheckAcceptMediaType", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ for _, sub := range []string{
+ "/scope/package",
+ "/scope/package.json",
+ "/scope/package/1.0.0",
+ "/scope/package/1.0.0.json",
+ "/scope/package/1.0.0.zip",
+ "/scope/package/1.0.0/Package.swift",
+ "/identifiers",
+ } {
+ req := NewRequest(t, "GET", url+sub)
+ req.Header.Add("Accept", "application/unknown")
+ resp := MakeRequest(t, req, http.StatusBadRequest)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type"))
+ }
+
+ req := NewRequestWithBody(t, "PUT", url+"/scope/package/1.0.0", strings.NewReader(""))
+ req = AddBasicAuthHeader(req, user.Name)
+ req.Header.Add("Accept", "application/unknown")
+ resp := MakeRequest(t, req, http.StatusBadRequest)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type"))
+ })
+
+ t.Run("Upload", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) {
+ var body bytes.Buffer
+ mpw := multipart.NewWriter(&body)
+
+ part, _ := mpw.CreateFormFile("source-archive", "source-archive.zip")
+ io.Copy(part, sr)
+
+ if metadata != "" {
+ mpw.WriteField("metadata", metadata)
+ }
+
+ mpw.Close()
+
+ req := NewRequestWithBody(t, "PUT", url, &body)
+ req.Header.Add("Content-Type", mpw.FormDataContentType())
+ req.Header.Add("Accept", swift_router.AcceptJSON)
+ req = AddBasicAuthHeader(req, user.Name)
+ MakeRequest(t, req, expectedStatus)
+ }
+
+ createArchive := func(files map[string]string) *bytes.Buffer {
+ var buf bytes.Buffer
+ zw := zip.NewWriter(&buf)
+ for filename, content := range files {
+ w, _ := zw.Create(filename)
+ w.Write([]byte(content))
+ }
+ zw.Close()
+ return &buf
+ }
+
+ for _, triple := range []string{"/sc_ope/package/1.0.0", "/scope/pack~age/1.0.0", "/scope/package/1_0.0"} {
+ req := NewRequestWithBody(t, "PUT", url+triple, bytes.NewReader([]byte{}))
+ req = AddBasicAuthHeader(req, user.Name)
+ resp := MakeRequest(t, req, http.StatusBadRequest)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type"))
+ }
+
+ uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion)
+
+ req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
+ MakeRequest(t, req, http.StatusUnauthorized)
+
+ uploadPackage(
+ t,
+ uploadURL,
+ http.StatusCreated,
+ createArchive(map[string]string{
+ "Package.swift": contentManifest1,
+ "Package@swift-5.6.swift": contentManifest2,
+ }),
+ `{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
+ )
+
+ pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
+ assert.NoError(t, err)
+ assert.Len(t, pvs, 1)
+
+ pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
+ assert.NoError(t, err)
+ assert.NotNil(t, pd.SemVer)
+ assert.Equal(t, packageID, pd.Package.Name)
+ assert.Equal(t, packageVersion, pd.Version.Version)
+ assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
+ metadata := pd.Metadata.(*swift_module.Metadata)
+ assert.Equal(t, packageDescription, metadata.Description)
+ assert.Len(t, metadata.Manifests, 2)
+ assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
+ assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
+ assert.Len(t, pd.VersionProperties, 1)
+ assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
+
+ pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
+ assert.NoError(t, err)
+ assert.Len(t, pfs, 1)
+ assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion), pfs[0].Name)
+ assert.True(t, pfs[0].IsLead)
+
+ uploadPackage(
+ t,
+ uploadURL,
+ http.StatusConflict,
+ createArchive(map[string]string{
+ "Package.swift": contentManifest1,
+ }),
+ "",
+ )
+ })
+
+ t.Run("Download", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s.zip", url, packageScope, packageName, packageVersion))
+ req = AddBasicAuthHeader(req, user.Name)
+ req.Header.Add("Accept", swift_router.AcceptZip)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "application/zip", resp.Header().Get("Content-Type"))
+
+ pv, err := packages.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages.TypeSwift, packageID, packageVersion)
+ assert.NotNil(t, pv)
+ assert.NoError(t, err)
+
+ pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv)
+ assert.NoError(t, err)
+ assert.Equal(t, "sha256="+pd.Files[0].Blob.HashSHA256, resp.Header().Get("Digest"))
+ })
+
+ t.Run("EnumeratePackageVersions", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", url, packageScope, packageName))
+ req = AddBasicAuthHeader(req, user.Name)
+ req.Header.Add("Accept", swift_router.AcceptJSON)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
+
+ body := resp.Body.String()
+
+ var result *swift_router.EnumeratePackageVersionsResponse
+ DecodeJSON(t, resp, &result)
+
+ assert.Len(t, result.Releases, 1)
+ assert.Contains(t, result.Releases, packageVersion)
+ assert.Equal(t, versionURL, result.Releases[packageVersion].URL)
+
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName))
+ req = AddBasicAuthHeader(req, user.Name)
+ resp = MakeRequest(t, req, http.StatusOK)
+
+ assert.Equal(t, body, resp.Body.String())
+ })
+
+ t.Run("PackageVersionMetadata", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion))
+ req = AddBasicAuthHeader(req, user.Name)
+ req.Header.Add("Accept", swift_router.AcceptJSON)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+
+ body := resp.Body.String()
+
+ var result *swift_router.PackageVersionMetadataResponse
+ DecodeJSON(t, resp, &result)
+
+ pv, err := packages.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages.TypeSwift, packageID, packageVersion)
+ assert.NotNil(t, pv)
+ assert.NoError(t, err)
+
+ pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv)
+ assert.NoError(t, err)
+
+ assert.Equal(t, packageID, result.ID)
+ assert.Equal(t, packageVersion, result.Version)
+ assert.Len(t, result.Resources, 1)
+ assert.Equal(t, "source-archive", result.Resources[0].Name)
+ assert.Equal(t, "application/zip", result.Resources[0].Type)
+ assert.Equal(t, pd.Files[0].Blob.HashSHA256, result.Resources[0].Checksum)
+ assert.Equal(t, "SoftwareSourceCode", result.Metadata.Type)
+ assert.Equal(t, packageName, result.Metadata.Name)
+ assert.Equal(t, packageVersion, result.Metadata.Version)
+ assert.Equal(t, packageDescription, result.Metadata.Description)
+ assert.Equal(t, "Swift", result.Metadata.ProgrammingLanguage.Name)
+ assert.Equal(t, packageAuthor, result.Metadata.Author.GivenName)
+
+ req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s.json", url, packageScope, packageName, packageVersion))
+ req = AddBasicAuthHeader(req, user.Name)
+ resp = MakeRequest(t, req, http.StatusOK)
+
+ assert.Equal(t, body, resp.Body.String())
+ })
+
+ t.Run("DownloadManifest", func(t *testing.T) {
+ manifestURL := fmt.Sprintf("%s/%s/%s/%s/Package.swift", url, packageScope, packageName, packageVersion)
+
+ t.Run("Default", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", manifestURL)
+ req = AddBasicAuthHeader(req, user.Name)
+ req.Header.Add("Accept", swift_router.AcceptSwift)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "text/x-swift", resp.Header().Get("Content-Type"))
+ assert.Equal(t, contentManifest1, resp.Body.String())
+ })
+
+ t.Run("DifferentVersion", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", manifestURL+"?swift-version=5.6")
+ req = AddBasicAuthHeader(req, user.Name)
+ resp := MakeRequest(t, req, http.StatusOK)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "text/x-swift", resp.Header().Get("Content-Type"))
+ assert.Equal(t, contentManifest2, resp.Body.String())
+
+ req = NewRequest(t, "GET", manifestURL+"?swift-version=5.6.0")
+ req = AddBasicAuthHeader(req, user.Name)
+ MakeRequest(t, req, http.StatusOK)
+ })
+
+ t.Run("Redirect", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", manifestURL+"?swift-version=1.0")
+ req = AddBasicAuthHeader(req, user.Name)
+ resp := MakeRequest(t, req, http.StatusSeeOther)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, setting.AppURL+url[1:]+fmt.Sprintf("/%s/%s/%s/Package.swift", packageScope, packageName, packageVersion), resp.Header().Get("Location"))
+ })
+ })
+
+ t.Run("LookupPackageIdentifiers", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+
+ req := NewRequest(t, "GET", url+"/identifiers")
+ req.Header.Add("Accept", swift_router.AcceptJSON)
+ resp := MakeRequest(t, req, http.StatusBadRequest)
+
+ assert.Equal(t, "1", resp.Header().Get("Content-Version"))
+ assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type"))
+
+ req = NewRequest(t, "GET", url+"/identifiers?url=https://unknown.host/")
+ MakeRequest(t, req, http.StatusNotFound)
+
+ req = NewRequest(t, "GET", url+"/identifiers?url="+packageRepositoryURL)
+ req.Header.Add("Accept", swift_router.AcceptJSON)
+ resp = MakeRequest(t, req, http.StatusOK)
+
+ var result *swift_router.LookupPackageIdentifiersResponse
+ DecodeJSON(t, resp, &result)
+
+ assert.Len(t, result.Identifiers, 1)
+ assert.Equal(t, packageID, result.Identifiers[0])
+ })
+}
diff --git a/web_src/svg/gitea-swift.svg b/web_src/svg/gitea-swift.svg
new file mode 100644
index 000000000000..8af43d32e466
--- /dev/null
+++ b/web_src/svg/gitea-swift.svg
@@ -0,0 +1,5 @@
+
+
From 5eea61dbc8f8e82e0dd05addf76751ee517459a0 Mon Sep 17 00:00:00 2001
From: sillyguodong <33891828+sillyguodong@users.noreply.github.com>
Date: Tue, 14 Mar 2023 05:05:19 +0800
Subject: [PATCH 03/11] Fix missing commit status in PR which from forked repo
(#23351)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
close: #23347
### Reference and Inference
According to Github REST API
[doc](https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#list-commit-statuses-for-a-reference):
1. The `Drone CI` that can create some commit status by
[API](https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#create-a-commit-status)
is enabled in `go-gitea/gitea`. So I tried to call the API to get a
commit status list of a PR which is commited to upstream
repo(`go-gitea/gitea`). As a result, the API returned a array of commit
status.
![image](https://user-images.githubusercontent.com/33891828/223913371-313d047a-5e2e-484c-b13e-dcd38748703e.png)
2. Then I tried to call the API to get commit status list of the
reference which of the `SHA` is the same as step 1 in the repo which is
forked from `go-gitea/gitea`. But I got a empty array.
![image](https://user-images.githubusercontent.com/33891828/223930827-17a64d3c-f466-4980-897c-77fe386c4d3b.png)
So, I believe it that:
1. The commit status is not shared between upstream repo and forked
repo.
2. The coomit status is bound to a repo that performs actions. (Gitea's
logic is the same)
### Cause
During debugging, I found it that commit status are not stored in the DB
as expected.
So, I located the following code:
https://github.com/go-gitea/gitea/blob/8cadd51bf295e6ff36ac36efed68cc5de34c9382/services/actions/commit_status.go#L18-L26
When I create a PR, the type of `event` is `pull request`, not `push`.
So the code return function directly.
### Screenshot
![image](https://user-images.githubusercontent.com/33891828/223939339-dadf539c-1fdd-40c4-96e9-2e4fa733f531.png)
![image](https://user-images.githubusercontent.com/33891828/223939519-edb02bf0-2478-4ea5-9366-be85468f02db.png)
![image](https://user-images.githubusercontent.com/33891828/223939557-ec6f1375-5536-400e-8987-fb7d2fd452fa.png)
### Other
In this PR, I also fix the problem of missing icon which represents
running in PRs list.
![image](https://user-images.githubusercontent.com/33891828/223939898-2a0339e4-713f-4c7b-9d99-2250a43f3457.png)
![image](https://user-images.githubusercontent.com/33891828/223939979-037a975f-5ced-480c-bac7-0ee00ebfff4b.png)
---
models/actions/run.go | 11 +++++
services/actions/commit_status.go | 71 +++++++++++++++++++++----------
templates/repo/commit_status.tmpl | 3 ++
3 files changed, 62 insertions(+), 23 deletions(-)
diff --git a/models/actions/run.go b/models/actions/run.go
index d5ab45a51958..a711cfee2ecd 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -128,6 +128,17 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
return nil, fmt.Errorf("event %s is not a push event", run.Event)
}
+func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
+ if run.Event == webhook_module.HookEventPullRequest {
+ var payload api.PullRequestPayload
+ if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
+ return nil, err
+ }
+ return &payload, nil
+ }
+ return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
+}
+
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).
SetExpr("num_action_runs",
diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go
index 4f313493523e..84de106eeca3 100644
--- a/services/actions/commit_status.go
+++ b/services/actions/commit_status.go
@@ -21,35 +21,60 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
}
run := job.Run
- if run.Event != webhook_module.HookEventPush {
- return nil
- }
+ var (
+ sha string
+ creatorID int64
+ )
- payload, err := run.GetPushEventPayload()
- if err != nil {
- return fmt.Errorf("GetPushEventPayload: %w", err)
- }
+ switch run.Event {
+ case webhook_module.HookEventPush:
+ payload, err := run.GetPushEventPayload()
+ if err != nil {
+ return fmt.Errorf("GetPushEventPayload: %w", err)
+ }
- // Since the payload comes from json data, we should check if it's broken, or it will cause panic
- switch {
- case payload.Repo == nil:
- return fmt.Errorf("repo is missing in event payload")
- case payload.Pusher == nil:
- return fmt.Errorf("pusher is missing in event payload")
- case payload.HeadCommit == nil:
- return fmt.Errorf("head commit is missing in event payload")
- }
+ // Since the payload comes from json data, we should check if it's broken, or it will cause panic
+ switch {
+ case payload.Repo == nil:
+ return fmt.Errorf("repo is missing in event payload")
+ case payload.Pusher == nil:
+ return fmt.Errorf("pusher is missing in event payload")
+ case payload.HeadCommit == nil:
+ return fmt.Errorf("head commit is missing in event payload")
+ }
- creator, err := user_model.GetUserByID(ctx, payload.Pusher.ID)
- if err != nil {
- return fmt.Errorf("GetUserByID: %w", err)
+ sha = payload.HeadCommit.ID
+ creatorID = payload.Pusher.ID
+ case webhook_module.HookEventPullRequest:
+ payload, err := run.GetPullRequestEventPayload()
+ if err != nil {
+ return fmt.Errorf("GetPullRequestEventPayload: %w", err)
+ }
+
+ switch {
+ case payload.PullRequest == nil:
+ return fmt.Errorf("pull request is missing in event payload")
+ case payload.PullRequest.Head == nil:
+ return fmt.Errorf("head of pull request is missing in event payload")
+ case payload.PullRequest.Head.Repository == nil:
+ return fmt.Errorf("head repository of pull request is missing in event payload")
+ case payload.PullRequest.Head.Repository.Owner == nil:
+ return fmt.Errorf("owner of head repository of pull request is missing in evnt payload")
+ }
+
+ sha = payload.PullRequest.Head.Sha
+ creatorID = payload.PullRequest.Head.Repository.Owner.ID
+ default:
+ return nil
}
repo := run.Repo
- sha := payload.HeadCommit.ID
ctxname := job.Name
state := toCommitStatus(job.Status)
-
+ creator, err := user_model.GetUserByID(ctx, creatorID)
+ if err != nil {
+ return fmt.Errorf("GetUserByID: %w", err)
+ }
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil {
for _, v := range statuses {
if v.Context == ctxname {
@@ -65,14 +90,14 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
Repo: repo,
- SHA: payload.HeadCommit.ID,
+ SHA: sha,
Creator: creator,
CommitStatus: &git_model.CommitStatus{
SHA: sha,
TargetURL: run.Link(),
Description: "",
Context: ctxname,
- CreatorID: payload.Pusher.ID,
+ CreatorID: creatorID,
State: state,
},
}); err != nil {
diff --git a/templates/repo/commit_status.tmpl b/templates/repo/commit_status.tmpl
index fbf064527da4..470869b381c0 100644
--- a/templates/repo/commit_status.tmpl
+++ b/templates/repo/commit_status.tmpl
@@ -1,6 +1,9 @@
{{if eq .State "pending"}}
{{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}
{{end}}
+{{if eq .State "running"}}
+ {{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}
+{{end}}
{{if eq .State "success"}}
{{svg "octicon-check" 18 "commit-status icon text green"}}
{{end}}
From 8421b8264fd7715ec93b13f37be31a945faec556 Mon Sep 17 00:00:00 2001
From: Zettat123
Date: Tue, 14 Mar 2023 05:55:30 +0800
Subject: [PATCH 04/11] Handle missing `README` in create repos API (#23387)
Close #22934
In `/user/repos` API (and other APIs related to creating repos), user
can specify a readme template for auto init. At present, if the
specified template does not exist, a `500` will be returned . This PR
improved the logic and will return a `400` instead of `500`.
---
routers/api/v1/admin/repo.go | 2 ++
routers/api/v1/repo/repo.go | 11 +++++++++++
templates/swagger/v1_json.tmpl | 9 +++++++++
3 files changed, 22 insertions(+)
diff --git a/routers/api/v1/admin/repo.go b/routers/api/v1/admin/repo.go
index 83ed06e49bce..a4895f260bec 100644
--- a/routers/api/v1/admin/repo.go
+++ b/routers/api/v1/admin/repo.go
@@ -32,6 +32,8 @@ func CreateRepo(ctx *context.APIContext) {
// responses:
// "201":
// "$ref": "#/responses/Repository"
+ // "400":
+ // "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 397600dc50a9..16608e5bbbdb 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -231,6 +231,13 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
if opt.AutoInit && opt.Readme == "" {
opt.Readme = "Default"
}
+
+ // If the readme template does not exist, a 400 will be returned.
+ if opt.AutoInit && len(opt.Readme) > 0 && !util.SliceContains(repo_module.Readmes, opt.Readme) {
+ ctx.Error(http.StatusBadRequest, "", fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes))
+ return
+ }
+
repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_module.CreateRepoOptions{
Name: opt.Name,
Description: opt.Description,
@@ -283,6 +290,8 @@ func Create(ctx *context.APIContext) {
// responses:
// "201":
// "$ref": "#/responses/Repository"
+ // "400":
+ // "$ref": "#/responses/error"
// "409":
// description: The repository with the same name already exists.
// "422":
@@ -464,6 +473,8 @@ func CreateOrgRepo(ctx *context.APIContext) {
// responses:
// "201":
// "$ref": "#/responses/Repository"
+ // "400":
+ // "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
// "403":
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 9c46b25eafc0..c304a7a4979c 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -713,6 +713,9 @@
"201": {
"$ref": "#/responses/Repository"
},
+ "400": {
+ "$ref": "#/responses/error"
+ },
"403": {
"$ref": "#/responses/forbidden"
},
@@ -1926,6 +1929,9 @@
"201": {
"$ref": "#/responses/Repository"
},
+ "400": {
+ "$ref": "#/responses/error"
+ },
"403": {
"$ref": "#/responses/forbidden"
},
@@ -13382,6 +13388,9 @@
"201": {
"$ref": "#/responses/Repository"
},
+ "400": {
+ "$ref": "#/responses/error"
+ },
"409": {
"description": "The repository with the same name already exists."
},
From 8570593d5526812ee9adc95485a11f51d55575d6 Mon Sep 17 00:00:00 2001
From: KN4CK3R
Date: Mon, 13 Mar 2023 23:15:09 +0100
Subject: [PATCH 05/11] Add package registry architecture overview (#23445)
As announced in #22810 I added a readme file to help understanding how
the package registry archictecture works and how the go packages are
related.
---
routers/api/packages/README.md | 50 ++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 routers/api/packages/README.md
diff --git a/routers/api/packages/README.md b/routers/api/packages/README.md
new file mode 100644
index 000000000000..533a0d32f066
--- /dev/null
+++ b/routers/api/packages/README.md
@@ -0,0 +1,50 @@
+# Gitea Package Registry
+
+This document gives a brief overview how the package registry is organized in code.
+
+## Structure
+
+The package registry code is divided into multiple modules to split the functionality and make code reuse possible.
+
+| Module | Description |
+| - | - |
+| `models/packages` | Common methods and models used by all registry types |
+| `models/packages/` | Methods used by specific registry type. There should be no need to use type specific models. |
+| `modules/packages` | Common methods and types used by multiple registry types |
+| `modules/packages/` | Registry type specific methods and types (e.g. metadata extraction of package files) |
+| `routers/api/packages` | Route definitions for all registry types |
+| `routers/api/packages/` | Route implementation for a specific registry type |
+| `services/packages` | Helper methods used by registry types to handle common tasks like package creation and deletion in `routers` |
+| `services/packages/` | Registry type specific methods used by `routers` and `services` |
+
+## Models
+
+Every package registry implementation uses the same underlaying models:
+
+| Model | Description |
+| - | - |
+| `Package` | The root of a package providing values fixed for every version (e.g. the package name) |
+| `PackageVersion` | A version of a package containing metadata (e.g. the package description) |
+| `PackageFile` | A file of a package describing its content (e.g. file name) |
+| `PackageBlob` | The content of a file (may be shared by multiple files) |
+| `PackageProperty` | Additional properties attached to `Package`, `PackageVersion` or `PackageFile` (e.g. used if metadata is needed for routing) |
+
+The following diagram shows the relationship between the models:
+```
+Package <1---*> PackageVersion <1---*> PackageFile <*---1> PackageBlob
+```
+
+## Adding a new package registry type
+
+Before adding a new package registry type have a look at the existing implementation to get an impression of how it could work.
+Most registry types offer endpoints to retrieve the metadata, upload and download package files.
+The upload endpoint is often the heavy part because it must validate the uploaded blob, extract metadata and create the models.
+The methods to validate and extract the metadata should be added in the `modules/packages/` package.
+If the upload is valid the methods in `services/packages` allow to store the upload and create the corresponding models.
+It depends if the registry type allows multiple files per package version which method should be called:
+- `CreatePackageAndAddFile`: error if package version already exists
+- `CreatePackageOrAddFileToExisting`: error if file already exists
+- `AddFileToExistingPackage`: error if package version does not exist or file already exists
+
+`services/packages` also contains helper methods to download a file or to remove a package version.
+There are no helper methods for metadata endpoints because they are very type specific.
From 605fd15ad6eda19dba8f5e8a8f2e595e34e6c6ee Mon Sep 17 00:00:00 2001
From: GiteaBot
Date: Tue, 14 Mar 2023 00:16:09 +0000
Subject: [PATCH 06/11] [skip ci] Updated translations via Crowdin
---
options/locale/locale_pt-BR.ini | 58 +++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 55529df8829a..b34b5642dc74 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -247,6 +247,7 @@ default_enable_timetracking_popup=Habilitar o cronômetro para novos repositóri
no_reply_address=Domínio de e-mail oculto
no_reply_address_helper=Nome de domínio para usuários com um endereço de e-mail oculto. Por exemplo, o nome de usuário 'joe' será registrado no Git como 'joe@noreply.example.org' se o domínio de e-mail oculto estiver definido como 'noreply.example.org'.
password_algorithm=Algoritmo Hash de Senha
+invalid_password_algorithm=Algoritmo de hash de senha inválido
password_algorithm_helper=Escolha o algoritmo de hash para as senhas. Diferentes algoritmos têm requerimentos e forças diversos. O `Argon2` possui boa qualidade, porém usa muita memória e pode ser inapropriado para sistemas com menos recursos.
enable_update_checker=Habilitar Verificador de Atualizações
enable_update_checker_helper=Procura por novas versões periodicamente conectando-se ao gitea.io.
@@ -284,6 +285,7 @@ users=Usuários
organizations=Organizações
search=Pesquisar
code=Código
+search.type.tooltip=Tipo de pesquisa
search.fuzzy=Similar
search.fuzzy.tooltip=Incluir resultados que sejam próximos ao termo de busca
search.match=Correspondência
@@ -819,6 +821,7 @@ remove_account_link=Remover conta vinculada
remove_account_link_desc=A exclusão da chave SSH revogará o acesso à sua conta. Continuar?
remove_account_link_success=A conta vinculada foi removida.
+hooks.desc=Adicionar webhooks que serão acionados para todos os repositórios pertencentes a este usuário.
orgs_none=Você não é membro de nenhuma organização.
repos_none=Você não possui nenhum repositório
@@ -1231,6 +1234,7 @@ projects.column.color=Colorido
projects.open=Abrir
projects.close=Fechar
projects.column.assigned_to=Atribuído a
+projects.card_type.desc=Pré-visualizações de Cards
projects.card_type.images_and_text=Imagens e Texto
projects.card_type.text_only=Somente texto
@@ -1399,6 +1403,7 @@ issues.label_title=Nome da etiqueta
issues.label_description=Descrição da etiqueta
issues.label_color=Cor da etiqueta
issues.label_exclusive=Exclusivo
+issues.label_exclusive_desc=Nomeie o rótulo escopo/item para torná-lo mutuamente exclusivo com outros rótulos do escopo/.
issues.label_exclusive_warning=Quaisquer rótulos com escopo conflitantes serão removidos ao editar os rótulos de uma issue ou pull request.
issues.label_count=%d etiquetas
issues.label_open_issues=%d issues abertas
@@ -1655,6 +1660,7 @@ pulls.merge_instruction_hint=`Você também pode ver as *, eventos para todos os branches serão relatados. Veja github.com/gobwas/glob documentação da sintaxe. Exemplos: master, {master,release*}.
settings.authorization_header=Header de Autorização
+settings.authorization_header_desc=Será incluído como header de autorização para solicitações quando estiver presente. Exemplos: %s.
settings.active=Ativo
settings.active_helper=Informações sobre eventos disparados serão enviadas para esta URL do webhook.
settings.add_hook_success=O webhook foi adicionado.
@@ -2124,6 +2132,7 @@ settings.dismiss_stale_approvals=Descartar aprovações obsoletas
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
settings.require_signed_commits=Exibir commits assinados
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
+settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
settings.protect_protected_file_patterns=Padrões de arquivos protegidos (separados usando ponto e vírgula '\;'):
settings.protect_protected_file_patterns_desc=Arquivos protegidos que não têm permissão para serem alterados diretamente, mesmo se o usuário tiver permissão para adicionar, editar ou apagar arquivos neste branch. Vários padrões podem ser separados usando ponto e vírgula ('\;'). Veja github.com/gobwas/glob documentação para sintaxe de padrões. Exemplos: .drone.yml, /docs/**/*.txt.
settings.protect_unprotected_file_patterns=Padrões de arquivos desprotegidos (separados usando ponto e vírgula '\;'):
@@ -2132,6 +2141,7 @@ settings.add_protected_branch=Habilitar proteção
settings.delete_protected_branch=Desabilitar proteção
settings.update_protect_branch_success=Proteção do branch '%s' foi atualizada.
settings.remove_protected_branch_success=Proteção do branch '%s' foi desabilitada.
+settings.remove_protected_branch_failed=Removendo regra de proteção de branch '%s' falhou.
settings.protected_branch_deletion=Desabilitar proteção de branch
settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar?
settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas
@@ -2146,6 +2156,8 @@ settings.default_merge_style_desc=Estilo de merge padrão para pull requests:
settings.choose_branch=Escolha um branch...
settings.no_protected_branch=Não há branches protegidos.
settings.edit_protected_branch=Editar
+settings.protected_branch_required_rule_name=Nome da regra é obrigatório
+settings.protected_branch_duplicate_rule_name=Regra com nome duplicado
settings.protected_branch_required_approvals_min=Aprovações necessárias não podem ser negativas.
settings.tags=Tags
settings.tags.protection=Proteção das Tags
@@ -2278,6 +2290,8 @@ release.edit_subheader=Lançamentos organizam versões do projeto.
release.tag_name=Nome da tag
release.target=Destino
release.tag_helper=Escolha uma tag existente, ou crie uma nova tag.
+release.tag_helper_new=Nova tag. Esta tag será criada a partir do alvo.
+release.tag_helper_existing=Tag existente.
release.title=Título
release.content=Conteúdo
release.prerelease_desc=Marcar como pré-lançamento
@@ -2570,6 +2584,10 @@ dashboard.delete_old_actions=Excluir todas as ações antigas do banco de dados
dashboard.delete_old_actions.started=A exclusão de todas as ações antigas do banco de dados foi iniciada.
dashboard.update_checker=Verificador de atualização
dashboard.delete_old_system_notices=Excluir todos os avisos de sistema antigos do banco de dados
+dashboard.gc_lfs=Coletar lixos dos meta-objetos LFS
+dashboard.stop_zombie_tasks=Parar tarefas zumbi
+dashboard.stop_endless_tasks=Parar tarefas infinitas
+dashboard.cancel_abandoned_jobs=Cancelar trabalhos abandonados
users.user_manage_panel=Gerenciamento de conta de usuário
users.new_account=Criar conta de usuário
@@ -2658,6 +2676,7 @@ repos.size=Tamanho
packages.package_manage_panel=Gerenciamento de Pacotes
packages.total_size=Tamanho Total: %s
+packages.unreferenced_size=Tamanho Não Referenciado: %s
packages.owner=Proprietário
packages.creator=Criador
packages.name=Nome
@@ -2751,6 +2770,8 @@ auths.oauth2_required_claim_value_helper=Defina este valor para permitir o login
auths.oauth2_group_claim_name=Nome do claim que fornece os nomes dos grupos para esta fonte. (Opcional)
auths.oauth2_admin_group=Valor do Claim de Grupo para os usuários administradores. (Opcional - requer nome do claim acima)
auths.oauth2_restricted_group=Valor do Claim de Grupo para os usuários restritos. (Opcional - requer nome do claim acima)
+auths.oauth2_map_group_to_team=Mapear grupos para Organizações. (Opcional - requer nome do claim acima)
+auths.oauth2_map_group_to_team_removal=Remover usuários de equipes sincronizadas se o usuário não pertence ao grupo correspondente.
auths.enable_auto_register=Habilitar cadastro automático
auths.sspi_auto_create_users=Criar usuários automaticamente
auths.sspi_auto_create_users_helper=Permitir que o método de autenticação SSPI crie automaticamente novas contas para usuários que fazem o login pela primeira vez
@@ -2791,6 +2812,8 @@ auths.still_in_used=A fonte de autenticação ainda está em uso. Converta ou ex
auths.deletion_success=A fonte de autenticação foi excluída.
auths.login_source_exist=A fonte de autenticação '%s' já existe.
auths.login_source_of_type_exist=Uma fonte de autenticação deste tipo já existe.
+auths.unable_to_initialize_openid=Não é possível inicializar o Provedor OpenID Connect: %s
+auths.invalid_openIdConnectAutoDiscoveryURL=URL do Auto Discovery inválida (deve ser uma URL válida, começando com http:// ou https://)
config.server_config=Configuração do servidor
config.app_name=Nome do servidor
@@ -3039,6 +3062,7 @@ reopen_pull_request=`reabriu o pull request %[3]s#%[2]s`
comment_issue=`comentou na issue %[3]s#%[2]s`
comment_pull=`comentou no pull request %[3]s#%[2]s`
merge_pull_request=`fez merge do pull request %[3]s#%[2]s`
+auto_merge_pull_request=`fez merge automático do pull request %[3]s#%[2]s`
transfer_repo=transferiu repositório de %s para %s
push_tag=fez push da tag %[3]s to %[4]s
delete_tag=excluiu tag %[2]s de %[3]s
@@ -3148,10 +3172,12 @@ dependency.id=ID
dependency.version=Versão
cargo.registry=Configurar este registro no arquivo de configuração de Cargo (por exemplo ~/.cargo/config.toml):
cargo.install=Para instalar o pacote usando Cargo, execute o seguinte comando:
+cargo.documentation=Para obter mais informações sobre o registro Cargo, consulte a documentação.
cargo.details.repository_site=Site do Repositório
cargo.details.documentation_site=Site da Documentação
chef.registry=Configure este registro em seu arquivo ~/.chef/config.rb:
chef.install=Para instalar o pacote, execute o seguinte comando:
+chef.documentation=Para obter mais informações sobre o registro Chef, consulte a documentação.
composer.registry=Configure este registro em seu arquivo ~/.composer/config.json:
composer.install=Para instalar o pacote usando o Composer, execute o seguinte comando:
composer.documentation=Para obter mais informações sobre o registro do Composer, consulte a documentação.
@@ -3224,6 +3250,15 @@ settings.delete.description=A exclusão de um pacote é permanente e não pode s
settings.delete.notice=Você está prestes a excluir %s (%s). Esta operação é irreversível, tem certeza?
settings.delete.success=O pacote foi excluído.
settings.delete.error=Falha ao excluir o pacote.
+owner.settings.cargo.title=Índice do Registro Cargo
+owner.settings.cargo.initialize=Iniciar Índice
+owner.settings.cargo.initialize.description=Para usar o registro Cargo é necessário um repositório git especial. Aqui você pode (re)criá-lo com a configuração necessária.
+owner.settings.cargo.initialize.error=Falha ao inicializar índice Cargo: %v
+owner.settings.cargo.initialize.success=O índice Cargo foi criado com sucesso.
+owner.settings.cargo.rebuild=Reconstruir Índice
+owner.settings.cargo.rebuild.description=Se o índice está fora de sincronia com os pacotes Cargo, você pode reconstruí-lo aqui.
+owner.settings.cargo.rebuild.error=Falha ao reconstruir índice Cargo: %v
+owner.settings.cargo.rebuild.success=O índice Cargo foi reconstruído com sucesso.
owner.settings.cleanuprules.title=Gerenciar Regras de Limpeza
owner.settings.cleanuprules.add=Adicionar Regra de Limpeza
owner.settings.cleanuprules.edit=Editar Regra de Limpeza
@@ -3232,6 +3267,7 @@ owner.settings.cleanuprules.preview=Pré-visualizar Regra de Limpeza
owner.settings.cleanuprules.preview.overview=%d pacotes agendados para serem removidos.
owner.settings.cleanuprules.preview.none=A regra de limpeza não corresponde a nenhum pacote.
owner.settings.cleanuprules.enabled=Habilitado
+owner.settings.cleanuprules.pattern_full_match=Aplicar padrão ao nome completo do pacote
owner.settings.cleanuprules.keep.title=Versões que correspondem a estas regras são mantidas, mesmo se corresponderem a uma regra de remoção abaixo.
owner.settings.cleanuprules.keep.count=Manter o mais recente
owner.settings.cleanuprules.keep.count.1=1 versão por pacote
@@ -3245,6 +3281,7 @@ owner.settings.cleanuprules.success.update=Regra de limpeza foi atualizada.
owner.settings.cleanuprules.success.delete=Regra de limpeza foi excluída.
owner.settings.chef.title=Registro Chef
owner.settings.chef.keypair=Gerar par de chaves
+owner.settings.chef.keypair.description=Gerar um par de chaves usado para autenticar no registro Chef. A chave anterior não pode ser usada depois.
[secrets]
secrets=Segredos
@@ -3253,6 +3290,8 @@ none=Não há segredos ainda.
value=Valor
name=Nome
creation=Adicionar Segredo
+creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
+creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
creation.success=O segredo '%s' foi adicionado.
creation.failed=Falha ao adicionar segredo.
deletion=Excluir segredo
@@ -3274,6 +3313,10 @@ status.cancelled=Cancelado
status.skipped=Ignorado
status.blocked=Bloqueado
+runners=Runners
+runners.runner_manage_panel=Gerenciamento de Runners
+runners.new=Criar novo Runner
+runners.new_notice=Como iniciar um runner
runners.status=Status
runners.id=ID
runners.name=Nome
@@ -3281,21 +3324,36 @@ runners.owner_type=Tipo
runners.description=Descrição
runners.labels=Rótulos
runners.last_online=Última Vez Online
+runners.agent_labels=Etiquetas do Agente
runners.custom_labels=Etiquetas Personalizadas
runners.custom_labels_helper=Etiquetas personalizadas são etiquetas que são adicionadas manualmente por um administrador. Separe as etiquetas com vírgula. Espaço em branco no começo ou no final de cada etiqueta é ignorado.
+runners.runner_title=Runner
+runners.task_list=Tarefas recentes neste runner
runners.task_list.run=Executar
runners.task_list.status=Status
runners.task_list.repository=Repositório
runners.task_list.commit=Commit
+runners.task_list.done_at=Feito em
+runners.edit_runner=Editar Runner
runners.update_runner=Atualizar as Alterações
+runners.update_runner_success=Runner atualizado com sucesso
+runners.update_runner_failed=Falha ao atualizar runner
+runners.delete_runner=Deletar esse runner
+runners.delete_runner_success=Runner excluído com sucesso
+runners.delete_runner_failed=Falha ao excluir runner
+runners.delete_runner_header=Confirme para excluir este runner
+runners.delete_runner_notice=Se uma tarefa estiver sendo executada neste runner, ela será encerrada e marcada como falha. Pode quebrar o workflow de construção.
+runners.none=Nenhum runner disponível
runners.status.unspecified=Desconhecido
runners.status.idle=Inativo
runners.status.active=Ativo
runners.status.offline=Offiline
+runs.all_workflows=Todos os Workflows
runs.open_tab=%d Aberto
runs.closed_tab=%d Fechado
runs.commit=Commit
runs.pushed_by=Push realizado por
+need_approval_desc=Precisa de aprovação para executar workflowa para pull request do fork.
From 81fe5d61851c0e586af7d32c29171ceff9a571bb Mon Sep 17 00:00:00 2001
From: delvh
Date: Tue, 14 Mar 2023 04:34:09 +0100
Subject: [PATCH 07/11] Convert `