diff --git a/.editorconfig b/.editorconfig
index c0946ac9975cd..e23e4cd649a42 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -17,6 +17,7 @@ insert_final_newline = false
[templates/swagger/v1_json.tmpl]
indent_style = space
+insert_final_newline = false
[templates/user/auth/oidc_wellknown.tmpl]
indent_style = space
diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml
index 7c1fb024421bc..b3ee93e6f8331 100644
--- a/.github/workflows/files-changed.yml
+++ b/.github/workflows/files-changed.yml
@@ -85,6 +85,7 @@ jobs:
swagger:
- "templates/swagger/v1_json.tmpl"
+ - "templates/swagger/v1_input.json"
- "Makefile"
- "package.json"
- "package-lock.json"
diff --git a/Makefile b/Makefile
index 89a6f1261fff8..e38fb801c318a 100644
--- a/Makefile
+++ b/Makefile
@@ -165,10 +165,8 @@ ifdef DEPS_PLAYWRIGHT
endif
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
-SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
-SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
+SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
SWAGGER_EXCLUDE := code.gitea.io/sdk
-SWAGGER_NEWLINE_COMMAND := -e '$$a\'
TEST_MYSQL_HOST ?= mysql:3306
TEST_MYSQL_DBNAME ?= testgitea
@@ -271,10 +269,8 @@ endif
.PHONY: generate-swagger
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
-$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA)
- $(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
- $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
- $(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
+$(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA) $(SWAGGER_SPEC_INPUT)
+ $(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
.PHONY: swagger-check
swagger-check: generate-swagger
@@ -287,9 +283,11 @@ swagger-check: generate-swagger
.PHONY: swagger-validate
swagger-validate: ## check if the swagger spec is valid
- $(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
+ @# swagger "validate" requires that the "basePath" must start with a slash, but we are using Golang template "{{...}}"
+ @$(SED_INPLACE) -E -e 's|"basePath":( *)"(.*)"|"basePath":\1"/\2"|g' './$(SWAGGER_SPEC)' # add a prefix slash to basePath
+ @# FIXME: there are some warnings
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
- $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
+ @$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
.PHONY: checks
checks: checks-frontend checks-backend ## run various consistency checks
@@ -380,6 +378,7 @@ lint-go-gopls: ## lint go files with gopls
.PHONY: lint-editorconfig
lint-editorconfig:
+ @echo "Running editorconfig check..."
@$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES)
.PHONY: lint-actions
diff --git a/cmd/web_acme.go b/cmd/web_acme.go
index 5daf0f55f243f..bca4ae021217b 100644
--- a/cmd/web_acme.go
+++ b/cmd/web_acme.go
@@ -54,10 +54,6 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p
}
- // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
- // Ideally it should migrate to AppDataPath write to "AppDataPath/https"
- certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
- magic := certmagic.NewDefault()
// Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool
if setting.AcmeCARoot != "" {
@@ -67,7 +63,13 @@ func runACME(listenAddr string, m http.Handler) error {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
}
}
- myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
+ // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
+ // Ideally it should migrate to AppDataPath write to "AppDataPath/https"
+ // And one more thing, no idea why we should set the global default variables here
+ // But it seems that the current ACME code needs these global variables to make renew work.
+ // Otherwise, "renew" will use incorrect storage path
+ certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
+ certmagic.DefaultACME = certmagic.ACMEIssuer{
CA: setting.AcmeURL,
TrustedRoots: certPool,
Email: setting.AcmeEmail,
@@ -77,8 +79,10 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort,
- })
+ }
+ magic := certmagic.NewDefault()
+ myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary
diff --git a/models/organization/org_list.go b/models/organization/org_list.go
index 4c4168af1f826..78ac0e704a1fb 100644
--- a/models/organization/org_list.go
+++ b/models/organization/org_list.go
@@ -124,6 +124,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg,
if err := db.GetEngine(ctx).Select(columnsStr).
Table("user").
Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))).
+ OrderBy("`user`.lower_name ASC").
Find(&orgs); err != nil {
return nil, err
}
diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go
index a9b1360df1410..232087d86594e 100644
--- a/models/repo/user_repo.go
+++ b/models/repo/user_repo.go
@@ -5,6 +5,7 @@ package repo
import (
"context"
+ "strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
@@ -149,9 +150,9 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
// If isShowFullName is set to true, also include full name prefix search
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
users := make([]*user_model.User, 0, 30)
- var prefixCond builder.Cond = builder.Like{"name", search + "%"}
+ var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"}
if isShowFullName {
- prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"})
+ prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%"))
}
cond := builder.In("`user`.id",
diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go
index 44ebe5f214c6e..50c970344cb3f 100644
--- a/models/repo/user_repo_test.go
+++ b/models/repo/user_repo_test.go
@@ -12,6 +12,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRepoAssignees(t *testing.T) {
@@ -38,3 +39,19 @@ func TestRepoAssignees(t *testing.T) {
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
}
}
+
+func TestGetIssuePostersWithSearch(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
+
+ users, err := repo_model.GetIssuePostersWithSearch(db.DefaultContext, repo2, false, "USER", false /* full name */)
+ require.NoError(t, err)
+ require.Len(t, users, 1)
+ assert.Equal(t, "user2", users[0].Name)
+
+ users, err = repo_model.GetIssuePostersWithSearch(db.DefaultContext, repo2, false, "TW%O", true /* full name */)
+ require.NoError(t, err)
+ require.Len(t, users, 1)
+ assert.Equal(t, "user2", users[0].Name)
+}
diff --git a/modules/setting/server.go b/modules/setting/server.go
index d7a71578d4ab6..e15b790906738 100644
--- a/modules/setting/server.go
+++ b/modules/setting/server.go
@@ -169,20 +169,24 @@ func loadServerFrom(rootCfg ConfigProvider) {
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
+ // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
+ // if these are removed, the warning will not be shown
+ if sec.HasKey("ENABLE_ACME") {
+ EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
+ } else {
+ deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
+ EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
+ }
+
Protocol = HTTP
protocolCfg := sec.Key("PROTOCOL").String()
+ if protocolCfg != "https" && EnableAcme {
+ log.Fatal("ACME could only be used with HTTPS protocol")
+ }
+
switch protocolCfg {
case "https":
Protocol = HTTPS
-
- // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
- // if these are removed, the warning will not be shown
- if sec.HasKey("ENABLE_ACME") {
- EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
- } else {
- deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
- EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
- }
if EnableAcme {
AcmeURL = sec.Key("ACME_URL").MustString("")
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
@@ -210,6 +214,9 @@ func loadServerFrom(rootCfg ConfigProvider) {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
}
+ if AcmeEmail == "" {
+ log.Fatal("ACME Email is not set (ACME_EMAIL).")
+ }
} else {
CertFile = sec.Key("CERT_FILE").String()
KeyFile = sec.Key("KEY_FILE").String()
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index bc29d530b497a..ed2a16cf90ff4 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -385,6 +385,12 @@ show_only_public=公開のみ表示
issues.in_your_repos=あなたのリポジトリ
+guide_title=アクティビティはありません
+guide_desc=現在フォロー中のリポジトリやユーザーがないため、表示するコンテンツがありません。 以下のリンクから、興味のあるリポジトリやユーザーを探すことができます。
+explore_repos=リポジトリを探す
+explore_users=ユーザーを探す
+empty_org=組織はまだありません。
+empty_repo=リポジトリはまだありません。
[explore]
repos=リポジトリ
@@ -1348,6 +1354,8 @@ editor.new_branch_name_desc=新しいブランチ名…
editor.cancel=キャンセル
editor.filename_cannot_be_empty=ファイル名は空にできません。
editor.filename_is_invalid=`ファイル名が不正です: "%s"`
+editor.commit_email=コミット メールアドレス
+editor.invalid_commit_email=コミットに使うメールアドレスが正しくありません。
editor.branch_does_not_exist=このリポジトリにブランチ "%s" は存在しません。
editor.branch_already_exists=ブランチ "%s" は、このリポジトリに既に存在します。
editor.directory_is_a_file=ディレクトリ名 "%s" はすでにリポジトリ内のファイルで使用されています。
@@ -1693,7 +1701,9 @@ issues.time_estimate_invalid=見積時間のフォーマットが不正です
issues.start_tracking_history=が作業を開始 %s
issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します
issues.tracking_already_started=`別のイシューで既にタイムトラッキングを開始しています!`
+issues.stop_tracking=タイマー終了
issues.stop_tracking_history=が %[1]s の作業を終了 %[2]s
+issues.cancel_tracking=破棄
issues.cancel_tracking_history=`がタイムトラッキングを中止 %s`
issues.del_time=このタイムログを削除
issues.add_time_history=が作業時間 %[1]s を追加 %[2]s
@@ -2329,6 +2339,8 @@ settings.event_fork=フォーク
settings.event_fork_desc=リポジトリがフォークされたとき。
settings.event_wiki=Wiki
settings.event_wiki_desc=Wikiページが作成・名前変更・編集・削除されたとき。
+settings.event_statuses=ステータス
+settings.event_statuses_desc=APIによってコミットのステータスが更新されたとき。
settings.event_release=リリース
settings.event_release_desc=リポジトリでリリースが作成・更新・削除されたとき。
settings.event_push=プッシュ
@@ -2876,6 +2888,14 @@ view_as_role=表示: %s
view_as_public_hint=READMEを公開ユーザーとして見ています。
view_as_member_hint=READMEをこの組織のメンバーとして見ています。
+worktime=作業時間
+worktime.date_range_start=期間 (自)
+worktime.date_range_end=期間 (至)
+worktime.query=集計
+worktime.time=時間
+worktime.by_repositories=リポジトリ別
+worktime.by_milestones=マイルストーン別
+worktime.by_members=メンバー別
[admin]
maintenance=メンテナンス
diff --git a/routers/api/packages/maven/api.go b/routers/api/packages/maven/api.go
index 167fe42b56d51..ec6b9cfb0e05b 100644
--- a/routers/api/packages/maven/api.go
+++ b/routers/api/packages/maven/api.go
@@ -8,7 +8,6 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
- maven_module "code.gitea.io/gitea/modules/packages/maven"
)
// MetadataResponse https://maven.apache.org/ref/3.2.5/maven-repository-metadata/repository-metadata.html
@@ -22,7 +21,7 @@ type MetadataResponse struct {
}
// pds is expected to be sorted ascending by CreatedUnix
-func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
+func createMetadataResponse(pds []*packages_model.PackageDescriptor, groupID, artifactID string) *MetadataResponse {
var release *packages_model.PackageDescriptor
versions := make([]string, 0, len(pds))
@@ -35,11 +34,9 @@ func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataRe
latest := pds[len(pds)-1]
- metadata := latest.Metadata.(*maven_module.Metadata)
-
resp := &MetadataResponse{
- GroupID: metadata.GroupID,
- ArtifactID: metadata.ArtifactID,
+ GroupID: groupID,
+ ArtifactID: artifactID,
Latest: latest.Version.Version,
Version: versions,
}
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index 4d04d4d1e9f32..4f9ced25b4bba 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -84,20 +84,19 @@ func handlePackageFile(ctx *context.Context, serveContent bool) {
}
func serveMavenMetadata(ctx *context.Context, params parameters) {
- // /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
-
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
- if errors.Is(err, util.ErrNotExist) {
- pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
- }
+ // path pattern: /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
+ // in case there are legacy package names ("GroupID-ArtifactID") we need to check both, new packages always use ":" as separator("GroupID:ArtifactID")
+ pvsLegacy, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
- if len(pvs) == 0 {
- apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist)
+ pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
+ if err != nil {
+ apiError(ctx, http.StatusInternalServerError, err)
return
}
+ pvs = append(pvsLegacy, pvs...)
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil {
@@ -110,7 +109,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
})
- xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
+ xmlMetadata, err := xml.Marshal(createMetadataResponse(pds, params.GroupID, params.ArtifactID))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 907a2f08fe280..bc76b5285e5a2 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -7,8 +7,6 @@
// This documentation describes the Gitea API.
//
// Schemes: https, http
-// BasePath: /api/v1
-// Version: {{AppVer | JSEscape}}
// License: MIT http://opensource.org/licenses/MIT
//
// Consumes:
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index c397d7972b56d..a54225f0fd76d 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -7,6 +7,7 @@ package repo
import (
"errors"
"net/http"
+ "strings"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
@@ -274,12 +275,13 @@ func GetRepoPermissions(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
- if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam("collaborator") && !ctx.IsUserRepoAdmin() {
+ collaboratorUsername := ctx.PathParam("collaborator")
+ if !ctx.Doer.IsAdmin && ctx.Doer.LowerName != strings.ToLower(collaboratorUsername) && !ctx.IsUserRepoAdmin() {
ctx.APIError(http.StatusForbidden, "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return
}
- collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
+ collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.APIError(http.StatusNotFound, err)
diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go
index e27040edc6512..5b7b0188dc13b 100644
--- a/routers/web/repo/githttp.go
+++ b/routers/web/repo/githttp.go
@@ -78,7 +78,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
isPull = true
} else {
- isPull = ctx.Req.Method == "GET"
+ isPull = ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
}
var accessMode perm.AccessMode
diff --git a/services/context/api.go b/services/context/api.go
index 230c3456d1996..c163de036c719 100644
--- a/services/context/api.go
+++ b/services/context/api.go
@@ -291,6 +291,11 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := GetAPIContext(req)
+ if ctx.Repo.Repository.IsEmpty {
+ ctx.APIErrorNotFound("repository is empty")
+ return
+ }
+
if ctx.Repo.GitRepo == nil {
ctx.APIErrorInternal(fmt.Errorf("no open git repo"))
return
diff --git a/services/repository/delete.go b/services/repository/delete.go
index fb3fffdca71a0..3b953d3ec7f37 100644
--- a/services/repository/delete.go
+++ b/services/repository/delete.go
@@ -14,6 +14,7 @@ import (
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
+ packages_model "code.gitea.io/gitea/models/packages"
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
@@ -267,6 +268,11 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
return err
}
+ // unlink packages linked to this repository
+ if err = packages_model.UnlinkRepositoryFromAllPackages(ctx, repoID); err != nil {
+ return err
+ }
+
if err = committer.Commit(); err != nil {
return err
}
diff --git a/services/repository/repository.go b/services/repository/repository.go
index 59b4491132da9..fcc617979ef29 100644
--- a/services/repository/repository.go
+++ b/services/repository/repository.go
@@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
- packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
@@ -63,11 +62,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod
notify_service.DeleteRepository(ctx, doer, repo)
}
- if err := DeleteRepositoryDirectly(ctx, doer, repo.ID); err != nil {
- return err
- }
-
- return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID)
+ return DeleteRepositoryDirectly(ctx, doer, repo.ID)
}
// PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace
diff --git a/templates/swagger/v1_input.json b/templates/swagger/v1_input.json
new file mode 100644
index 0000000000000..1979febebb9db
--- /dev/null
+++ b/templates/swagger/v1_input.json
@@ -0,0 +1,6 @@
+{
+ "info": {
+ "version": "{{AppVer | JSEscape}}"
+ },
+ "basePath": "{{AppSubUrl | JSEscape}}/api/v1"
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index d173f3161b553..fd3e2a70f1337 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -27580,4 +27580,4 @@
"TOTPHeader": []
}
]
-}
+}
\ No newline at end of file
diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go
index 486a5af93e17b..408c8805c2412 100644
--- a/tests/integration/api_packages_maven_test.go
+++ b/tests/integration/api_packages_maven_test.go
@@ -80,6 +80,7 @@ func TestPackageMaven(t *testing.T) {
t.Run("UploadLegacy", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
+ // try to upload a package with legacy package name (will be saved as "GroupID-ArtifactID")
legacyRootLink := "/api/packages/user2/maven/com/gitea/legacy-project"
req := NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.2/any-file-name?use_legacy_package_name=1", strings.NewReader("test-content")).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
@@ -97,6 +98,13 @@ func TestPackageMaven(t *testing.T) {
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
MakeRequest(t, req, http.StatusNotFound)
+ // legacy package names should also be able to be listed
+ req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name)
+ resp := MakeRequest(t, req, http.StatusOK)
+ respBody := resp.Body.String()
+ assert.Contains(t, respBody, "1.0.2")
+
+ // then upload a package with correct package name (will be saved as "GroupID:ArtifactID")
req = NewRequestWithBody(t, "PUT", legacyRootLink+"/1.0.3/any-file-name", strings.NewReader("test-content")).AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
_, err = packages.GetPackageByName(db.DefaultContext, user.ID, packages.TypeMaven, "com.gitea-legacy-project")
@@ -114,6 +122,12 @@ func TestPackageMaven(t *testing.T) {
req = NewRequest(t, "GET", "/user2/-/packages/maven/com.gitea%3Alegacy-project/1.0.2")
MakeRequest(t, req, http.StatusOK)
+ // now 2 packages should be listed
+ req = NewRequest(t, "GET", legacyRootLink+"/maven-metadata.xml").AddBasicAuth(user.Name)
+ resp = MakeRequest(t, req, http.StatusOK)
+ respBody = resp.Body.String()
+ assert.Contains(t, respBody, "1.0.2")
+ assert.Contains(t, respBody, "1.0.3")
require.NoError(t, packages.DeletePackageByID(db.DefaultContext, p.ID))
})
diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go
index 463db1dfb1305..11e2924e8423e 100644
--- a/tests/integration/api_repo_collaborator_test.go
+++ b/tests/integration/api_repo_collaborator_test.go
@@ -5,7 +5,6 @@ package integration
import (
"net/http"
- "net/url"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
@@ -14,132 +13,145 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPIRepoCollaboratorPermission(t *testing.T) {
- onGiteaRun(t, func(t *testing.T, u *url.URL) {
- repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
- repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
+ defer tests.PrepareTestEnv(t)()
+ repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
+ repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
- user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
- user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
- user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
- user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11})
- user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
+ user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+ user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
+ user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11})
+ user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
- testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
+ testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
- t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) {
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name).
- AddTokenAuth(testCtx.Token)
- resp := MakeRequest(t, req, http.StatusOK)
+ t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) {
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, repo2Owner.Name).
+ AddTokenAuth(testCtx.Token)
+ resp := MakeRequest(t, req, http.StatusOK)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- assert.Equal(t, "owner", repoPermission.Permission)
- })
+ assert.Equal(t, "owner", repoPermission.Permission)
+ })
- t.Run("CollaboratorWithReadAccess", func(t *testing.T) {
- t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead))
+ t.Run("CollaboratorWithReadAccess", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead))
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
- AddTokenAuth(testCtx.Token)
- resp := MakeRequest(t, req, http.StatusOK)
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
+ AddTokenAuth(testCtx.Token)
+ resp := MakeRequest(t, req, http.StatusOK)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- assert.Equal(t, "read", repoPermission.Permission)
- })
+ assert.Equal(t, "read", repoPermission.Permission)
+ })
- t.Run("CollaboratorWithWriteAccess", func(t *testing.T) {
- t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite))
+ t.Run("CollaboratorWithWriteAccess", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite))
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
- AddTokenAuth(testCtx.Token)
- resp := MakeRequest(t, req, http.StatusOK)
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
+ AddTokenAuth(testCtx.Token)
+ resp := MakeRequest(t, req, http.StatusOK)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- assert.Equal(t, "write", repoPermission.Permission)
- })
+ assert.Equal(t, "write", repoPermission.Permission)
+ })
- t.Run("CollaboratorWithAdminAccess", func(t *testing.T) {
- t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin))
+ t.Run("CollaboratorWithAdminAccess", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin))
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
- AddTokenAuth(testCtx.Token)
- resp := MakeRequest(t, req, http.StatusOK)
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name).
+ AddTokenAuth(testCtx.Token)
+ resp := MakeRequest(t, req, http.StatusOK)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- assert.Equal(t, "admin", repoPermission.Permission)
- })
+ assert.Equal(t, "admin", repoPermission.Permission)
+ })
- t.Run("CollaboratorNotFound", func(t *testing.T) {
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user").
- AddTokenAuth(testCtx.Token)
- MakeRequest(t, req, http.StatusNotFound)
- })
+ t.Run("CollaboratorNotFound", func(t *testing.T) {
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user").
+ AddTokenAuth(testCtx.Token)
+ MakeRequest(t, req, http.StatusNotFound)
+ })
- t.Run("CollaboratorBlocked", func(t *testing.T) {
- ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
- ctx.ExpectedCode = http.StatusForbidden
- doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t)
- })
+ t.Run("CollaboratorBlocked", func(t *testing.T) {
+ ctx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository)
+ ctx.ExpectedCode = http.StatusForbidden
+ doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t)
+ })
+
+ t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
+
+ _session := loginUser(t, user5.Name)
+ _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
+
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
+ AddTokenAuth(_testCtx.Token)
+ resp := _session.MakeRequest(t, req, http.StatusOK)
- t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
- t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- _session := loginUser(t, user5.Name)
- _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
+ assert.Equal(t, "read", repoPermission.Permission)
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
- AddTokenAuth(_testCtx.Token)
- resp := _session.MakeRequest(t, req, http.StatusOK)
+ t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) {
+ session := loginUser(t, user5.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token)
+ resp = MakeRequest(t, req, http.StatusOK)
- assert.Equal(t, "read", repoPermission.Permission)
+ repoCollPerm := api.RepoCollaboratorPermission{}
+ DecodeJSON(t, resp, &repoCollPerm)
+
+ assert.Equal(t, "read", repoCollPerm.Permission)
})
+ })
- t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
- t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
+ t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
- _session := loginUser(t, user5.Name)
- _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
+ _session := loginUser(t, user5.Name)
+ _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
- AddTokenAuth(_testCtx.Token)
- resp := _session.MakeRequest(t, req, http.StatusOK)
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).
+ AddTokenAuth(_testCtx.Token)
+ resp := _session.MakeRequest(t, req, http.StatusOK)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- assert.Equal(t, "read", repoPermission.Permission)
- })
+ assert.Equal(t, "read", repoPermission.Permission)
+ })
- t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) {
- t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin))
- t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead))
+ t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin))
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead))
- _session := loginUser(t, user10.Name)
- _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
+ _session := loginUser(t, user10.Name)
+ _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository)
- req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name).
- AddTokenAuth(_testCtx.Token)
- resp := _session.MakeRequest(t, req, http.StatusOK)
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user11.Name).
+ AddTokenAuth(_testCtx.Token)
+ resp := _session.MakeRequest(t, req, http.StatusOK)
- var repoPermission api.RepoCollaboratorPermission
- DecodeJSON(t, resp, &repoPermission)
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
- assert.Equal(t, "read", repoPermission.Permission)
- })
+ assert.Equal(t, "read", repoPermission.Permission)
})
}
diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go
index b19774a826814..e122531dc9877 100644
--- a/tests/integration/empty_repo_test.go
+++ b/tests/integration/empty_repo_test.go
@@ -60,12 +60,20 @@ func TestEmptyRepoAddFile(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user30")
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
+
+ // test web page
req := NewRequest(t, "GET", "/user30/empty")
resp := session.MakeRequest(t, req, http.StatusOK)
bodyString := resp.Body.String()
assert.Contains(t, bodyString, "empty-repo-guide")
assert.True(t, test.IsNormalPageCompleted(bodyString))
+ // test api
+ req = NewRequest(t, "GET", "/api/v1/repos/user30/empty/raw/main/README.md").AddTokenAuth(token)
+ session.MakeRequest(t, req, http.StatusNotFound)
+
+ // create a new file
req = NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch)
resp = session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`)
diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go
index 15336b9b81f39..55d647672a2ba 100644
--- a/tests/integration/git_smart_http_test.go
+++ b/tests/integration/git_smart_http_test.go
@@ -9,7 +9,10 @@ import (
"net/url"
"testing"
+ "code.gitea.io/gitea/modules/util"
+
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestGitSmartHTTP(t *testing.T) {
@@ -18,51 +21,55 @@ func TestGitSmartHTTP(t *testing.T) {
func testGitSmartHTTP(t *testing.T, u *url.URL) {
kases := []struct {
- p string
- code int
+ method, path string
+ code int
}{
{
- p: "user2/repo1/info/refs",
+ path: "user2/repo1/info/refs",
code: http.StatusOK,
},
{
- p: "user2/repo1/HEAD",
+ method: "HEAD",
+ path: "user2/repo1/info/refs",
+ code: http.StatusOK,
+ },
+ {
+ path: "user2/repo1/HEAD",
code: http.StatusOK,
},
{
- p: "user2/repo1/objects/info/alternates",
+ path: "user2/repo1/objects/info/alternates",
code: http.StatusNotFound,
},
{
- p: "user2/repo1/objects/info/http-alternates",
+ path: "user2/repo1/objects/info/http-alternates",
code: http.StatusNotFound,
},
{
- p: "user2/repo1/../../custom/conf/app.ini",
+ path: "user2/repo1/../../custom/conf/app.ini",
code: http.StatusNotFound,
},
{
- p: "user2/repo1/objects/info/../../../../custom/conf/app.ini",
+ path: "user2/repo1/objects/info/../../../../custom/conf/app.ini",
code: http.StatusNotFound,
},
{
- p: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`,
+ path: `user2/repo1/objects/info/..\..\..\..\custom\conf\app.ini`,
code: http.StatusBadRequest,
},
}
for _, kase := range kases {
- t.Run(kase.p, func(t *testing.T) {
- p := u.String() + kase.p
- req, err := http.NewRequest("GET", p, nil)
- assert.NoError(t, err)
+ t.Run(kase.path, func(t *testing.T) {
+ req, err := http.NewRequest(util.IfZero(kase.method, "GET"), u.String()+kase.path, nil)
+ require.NoError(t, err)
req.SetBasicAuth("user2", userPassword)
resp, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
+ require.NoError(t, err)
defer resp.Body.Close()
assert.EqualValues(t, kase.code, resp.StatusCode)
_, err = io.ReadAll(resp.Body)
- assert.NoError(t, err)
+ require.NoError(t, err)
})
}
}
diff --git a/web_src/js/features/repo-common.test.ts b/web_src/js/features/repo-common.test.ts
new file mode 100644
index 0000000000000..009dfc86b1016
--- /dev/null
+++ b/web_src/js/features/repo-common.test.ts
@@ -0,0 +1,7 @@
+import {substituteRepoOpenWithUrl} from './repo-common.ts';
+
+test('substituteRepoOpenWithUrl', () => {
+ // For example: "x-github-client://openRepo/https://github.com/go-gitea/gitea"
+ expect(substituteRepoOpenWithUrl('proto://a/{url}', 'https://gitea')).toEqual('proto://a/https://gitea');
+ expect(substituteRepoOpenWithUrl('proto://a?link={url}', 'https://gitea')).toEqual('proto://a?link=https%3A%2F%2Fgitea');
+});
diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts
index 2f62d51597c44..444cb163bf140 100644
--- a/web_src/js/features/repo-common.ts
+++ b/web_src/js/features/repo-common.ts
@@ -42,6 +42,14 @@ export function initRepoActivityTopAuthorsChart() {
}
}
+export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
+ const pos = tmpl.indexOf('{url}');
+ if (pos === -1) return tmpl;
+ const posQuestionMark = tmpl.indexOf('?');
+ const needEncode = posQuestionMark >= 0 && posQuestionMark < pos;
+ return tmpl.replace('{url}', needEncode ? encodeURIComponent(url) : url);
+}
+
function initCloneSchemeUrlSelection(parent: Element) {
const elCloneUrlInput = parent.querySelector('.repo-clone-url');
@@ -70,7 +78,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
}
}
for (const el of parent.querySelectorAll('.js-clone-url-editor')) {
- el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
+ el.href = substituteRepoOpenWithUrl(el.getAttribute('data-href-template'), link);
}
};