diff --git a/.github/labeler.yml b/.github/labeler.yml index 265616baed..46efbcb194 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -70,10 +70,11 @@ modifies/go: - any-glob-to-any-file: - "**/*.go" -modifies/js: +modifies/frontend: - changed-files: - any-glob-to-any-file: - "**/*.js" + - "**/*.ts" - "**/*.vue" docs-update-needed: diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index 246884f24b..22cb784245 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -198,7 +198,9 @@ jobs: test-mssql: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed - runs-on: ubuntu-latest + # specifying the version of ubuntu in use as mssql fails on newer kernels + # pending resolution from vendor + runs-on: ubuntu-20.04 services: mssql: image: mcr.microsoft.com/mssql/server:2017-latest diff --git a/cmd/serv.go b/cmd/serv.go index 2bfd111061..f74a8fd3d0 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -143,6 +143,12 @@ func runServ(c *cli.Context) error { return nil } + defer func() { + if err := recover(); err != nil { + _ = fail(ctx, "Internal Server Error", "Panic: %v\n%s", err, log.Stack(2)) + } + }() + keys := strings.Split(c.Args().First(), "-") if len(keys) != 2 || keys[0] != "key" { return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First()) @@ -189,10 +195,7 @@ func runServ(c *cli.Context) error { } verb := words[0] - repoPath := words[1] - if repoPath[0] == '/' { - repoPath = repoPath[1:] - } + repoPath := strings.TrimPrefix(words[1], "/") var lfsVerb string if verb == lfsAuthenticateVerb { diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index a2dd92b105..ad5d3e1aba 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -526,7 +526,8 @@ INTERNAL_TOKEN = ;; HMAC to encode urls with, it **is required** if camo is enabled. ;HMAC_KEY = ;; Set to true to use camo for https too lese only non https urls are proxyed -;ALLWAYS = false +;; ALLWAYS is deprecated and will be removed in the future +;ALWAYS = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go index ba5e4959f0..3ffad035b7 100644 --- a/models/activities/repo_activity.go +++ b/models/activities/repo_activity.go @@ -34,6 +34,7 @@ type ActivityStats struct { OpenedPRAuthorCount int64 MergedPRs issues_model.PullRequestList MergedPRAuthorCount int64 + ActiveIssues issues_model.IssueList OpenedIssues issues_model.IssueList OpenedIssueAuthorCount int64 ClosedIssues issues_model.IssueList @@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int { // ActiveIssueCount returns total active issue count func (stats *ActivityStats) ActiveIssueCount() int { - return stats.OpenedIssueCount() + stats.ClosedIssueCount() + return len(stats.ActiveIssues) } // OpenedIssueCount returns open issue count @@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi stats.ClosedIssueAuthorCount = count // New issues - sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false) + sess = newlyCreatedIssues(ctx, repoID, fromTime) sess.OrderBy("issue.created_unix ASC") stats.OpenedIssues = make(issues_model.IssueList, 0) if err = sess.Find(&stats.OpenedIssues); err != nil { return err } + // Active issues + sess = activeIssues(ctx, repoID, fromTime) + sess.OrderBy("issue.created_unix ASC") + stats.ActiveIssues = make(issues_model.IssueList, 0) + if err = sess.Find(&stats.ActiveIssues); err != nil { + return err + } + // Opened issue authors sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false) if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil { @@ -317,6 +326,23 @@ func (stats *ActivityStats) FillUnresolvedIssues(ctx context.Context, repoID int return sess.Find(&stats.UnresolvedIssues) } +func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { + sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). + And("issue.is_pull = ?", false). // Retain the is_pull check to exclude pull requests + And("issue.created_unix >= ?", fromTime.Unix()) // Include all issues created after fromTime + + return sess +} + +func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { + sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). + And("issue.is_pull = ?", false). + And("issue.created_unix >= ?", fromTime.Unix()). + Or("issue.closed_unix >= ?", fromTime.Unix()) + + return sess +} + func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session { sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). And("issue.is_closed = ?", closed) diff --git a/models/issues/pull.go b/models/issues/pull.go index 5fe95d56cc..4a5782f836 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -268,6 +268,10 @@ func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) { return nil } +func (pr *PullRequest) IsAgitFlow() bool { + return pr.Flow == PullRequestFlowAGit +} + // LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist // and thus ErrRepoNotExist will never be returned func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) { diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index 40458dfc33..2c9af94405 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -75,7 +75,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s w.Header().Set("Etag", etag) } if lastModified != nil && !lastModified.IsZero() { - w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat)) + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat + w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat)) } if len(etag) > 0 { diff --git a/modules/httplib/serve.go b/modules/httplib/serve.go index 6e147d76f5..2e3e6a7c42 100644 --- a/modules/httplib/serve.go +++ b/modules/httplib/serve.go @@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { httpcache.SetCacheControlInHeader(header, duration) if !opts.LastModified.IsZero() { + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat)) } } diff --git a/modules/httplib/url.go b/modules/httplib/url.go index 219dfe695c..e3bad1e5fb 100644 --- a/modules/httplib/url.go +++ b/modules/httplib/url.go @@ -52,11 +52,6 @@ func getRequestScheme(req *http.Request) string { return "" } -func getForwardedHost(req *http.Request) string { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host - return req.Header.Get("X-Forwarded-Host") -} - // GuessCurrentAppURL tries to guess the current full app URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL func GuessCurrentAppURL(ctx context.Context) string { return GuessCurrentHostURL(ctx) + setting.AppSubURL + "/" @@ -81,11 +76,9 @@ func GuessCurrentHostURL(ctx context.Context) string { if reqScheme == "" { return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/") } - reqHost := getForwardedHost(req) - if reqHost == "" { - reqHost = req.Host - } - return reqScheme + "://" + reqHost + // X-Forwarded-Host has many problems: non-standard, not well-defined (X-Forwarded-Port or not), conflicts with Host header. + // So do not use X-Forwarded-Host, just use Host header directly. + return reqScheme + "://" + req.Host } // MakeAbsoluteURL tries to make a link to an absolute URL: diff --git a/modules/httplib/url_test.go b/modules/httplib/url_test.go index 28aaee6e12..fc6c91cd3a 100644 --- a/modules/httplib/url_test.go +++ b/modules/httplib/url_test.go @@ -70,7 +70,7 @@ func TestMakeAbsoluteURL(t *testing.T) { "X-Forwarded-Proto": {"https"}, }, }) - assert.Equal(t, "https://forwarded-host/foo", MakeAbsoluteURL(ctx, "/foo")) + assert.Equal(t, "https://user-host/foo", MakeAbsoluteURL(ctx, "/foo")) } func TestIsCurrentGiteaSiteURL(t *testing.T) { @@ -119,5 +119,6 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) { }, }) assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000")) - assert.True(t, IsCurrentGiteaSiteURL(ctx, "https://forwarded-host")) + assert.True(t, IsCurrentGiteaSiteURL(ctx, "https://user-host")) + assert.False(t, IsCurrentGiteaSiteURL(ctx, "https://forwarded-host")) } diff --git a/modules/markup/camo.go b/modules/markup/camo.go index e93797de2b..7e2583469d 100644 --- a/modules/markup/camo.go +++ b/modules/markup/camo.go @@ -38,7 +38,7 @@ func camoHandleLink(link string) string { if setting.Camo.Enabled { lnkURL, err := url.Parse(link) if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) && - (setting.Camo.Allways || lnkURL.Scheme != "https") { + (setting.Camo.Always || lnkURL.Scheme != "https") { return CamoEncode(link) } } diff --git a/modules/markup/camo_test.go b/modules/markup/camo_test.go index ba58835221..3c5d40afa0 100644 --- a/modules/markup/camo_test.go +++ b/modules/markup/camo_test.go @@ -28,7 +28,7 @@ func TestCamoHandleLink(t *testing.T) { "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc", camoHandleLink("http://testimages.org/img.jpg")) - setting.Camo.Allways = true + setting.Camo.Always = true assert.Equal(t, "https://gitea.com/img.jpg", camoHandleLink("https://gitea.com/img.jpg")) diff --git a/modules/packages/composer/metadata.go b/modules/packages/composer/metadata.go index 2c2e9ebf27..6035eae8ca 100644 --- a/modules/packages/composer/metadata.go +++ b/modules/packages/composer/metadata.go @@ -48,6 +48,7 @@ type Metadata struct { Homepage string `json:"homepage,omitempty"` License Licenses `json:"license,omitempty"` Authors []Author `json:"authors,omitempty"` + Bin []string `json:"bin,omitempty"` Autoload map[string]any `json:"autoload,omitempty"` AutoloadDev map[string]any `json:"autoload-dev,omitempty"` Extra map[string]any `json:"extra,omitempty"` diff --git a/modules/setting/camo.go b/modules/setting/camo.go index 366e9a116c..608ecf8363 100644 --- a/modules/setting/camo.go +++ b/modules/setting/camo.go @@ -3,18 +3,28 @@ package setting -import "code.gitea.io/gitea/modules/log" +import ( + "strconv" + + "code.gitea.io/gitea/modules/log" +) var Camo = struct { Enabled bool ServerURL string `ini:"SERVER_URL"` HMACKey string `ini:"HMAC_KEY"` - Allways bool + Always bool }{} func loadCamoFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "camo", &Camo) if Camo.Enabled { + oldValue := rootCfg.Section("camo").Key("ALLWAYS").MustString("") + if oldValue != "" { + log.Warn("camo.ALLWAYS is deprecated, use camo.ALWAYS instead") + Camo.Always, _ = strconv.ParseBool(oldValue) + } + if Camo.ServerURL == "" || Camo.HMACKey == "" { log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`) } diff --git a/options/gitignore/Zig b/options/gitignore/Zig new file mode 100644 index 0000000000..3389c86c99 --- /dev/null +++ b/options/gitignore/Zig @@ -0,0 +1,2 @@ +.zig-cache/ +zig-out/ diff --git a/options/license/Boehm-GC-without-fee b/options/license/Boehm-GC-without-fee new file mode 100644 index 0000000000..354d47017e --- /dev/null +++ b/options/license/Boehm-GC-without-fee @@ -0,0 +1,14 @@ +Copyright (c) 2000 +SWsoft company + +Modifications copyright (c) 2001, 2013. Oracle and/or its affiliates. +All rights reserved. + +This material is provided "as is", with absolutely no warranty expressed +or implied. Any use is at your own risk. + +Permission to use or copy this software for any purpose is hereby granted +without fee, provided the above notices are retained on all copies. +Permission to modify the code and to distribute modified code is granted, +provided the above notices are retained, and a notice that the code was +modified is included with the above copyright notice. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 957e73b171..f77fd203a2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1927,6 +1927,7 @@ pulls.delete.text = Do you really want to delete this pull request? (This will p pulls.recently_pushed_new_branches = You pushed on branch %[1]s %[2]s pull.deleted_branch = (deleted):%s +pull.agit_documentation = Review documentation about AGit comments.edit.already_changed = Unable to save changes to the comment. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 3edf68e943..3a80c741db 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -159,6 +159,7 @@ filter.public=公開 filter.private=プライベート no_results_found=見つかりません。 +internal_error_skipped=内部エラーが発生しましたがスキップされました: %s [search] search=検索… @@ -177,6 +178,8 @@ code_search_by_git_grep=現在のコード検索は "git grep" によって行 package_kind=パッケージを検索... project_kind=プロジェクトを検索... branch_kind=ブランチを検索... +tag_kind=タグを検索... +tag_tooltip=一致するタグを検索します。任意のシーケンスに一致させるには '%' を使用してください。 commit_kind=コミットを検索... runner_kind=ランナーを検索... no_results=一致する結果が見つかりませんでした @@ -218,16 +221,20 @@ string.desc=Z - A [error] occurred=エラーが発生しました +report_message=Gitea のバグが疑われる場合は、GitHubでIssueを検索して、見つからなければ新しいIssueを作成してください。 not_found=ターゲットが見つかりませんでした。 network_error=ネットワークエラー [startpage] app_desc=自分で立てる、超簡単 Git サービス install=簡単インストール +install_desc=シンプルに、プラットフォームに応じてバイナリを実行したり、Dockerで動かしたり、パッケージを使うだけ。 platform=クロスプラットフォーム +platform_desc=GiteaはGoがコンパイル可能なあらゆる環境で動きます: Windows、macOS、Linux、ARMなど。 あなたの好きなものを選んでください! lightweight=軽量 lightweight_desc=Gitea の最小動作要件は小さいため、安価な Raspberry Pi でも動きます。エネルギーを節約しましょう! license=オープンソース +license_desc=Go get %[2]s! このプロジェクトをさらに向上させるため、ぜひ貢献して参加してください。 貢献者になることを恥ずかしがらないで! [install] install=インストール @@ -450,6 +457,7 @@ authorize_title=`"%s"にあなたのアカウントへのアクセスを許可 authorization_failed=認可失敗 authorization_failed_desc=無効なリクエストを検出したため認可が失敗しました。 認可しようとしたアプリの開発者に連絡してください。 sspi_auth_failed=SSPI認証に失敗しました +password_pwned=あなたが選択したパスワードは、過去の情報漏洩事件で流出した盗まれたパスワードのリストに含まれています。 別のパスワードでもう一度試してください。 また他の登録でもこのパスワードからの変更を検討してください。 password_pwned_err=HaveIBeenPwnedへのリクエストを完了できませんでした last_admin=最後の管理者は削除できません。少なくとも一人の管理者が必要です。 signin_passkey=パスキーでサインイン @@ -919,6 +927,7 @@ oauth2_client_secret_hint=このページから移動したりページを更新 oauth2_application_edit=編集 oauth2_application_create_description=OAuth2アプリケーションで、サードパーティアプリケーションがこのインスタンス上のユーザーアカウントにアクセスできるようになります。 oauth2_application_remove_description=OAuth2アプリケーションを削除すると、このインスタンス上の許可されたユーザーアカウントへのアクセスができなくなります。 続行しますか? +oauth2_application_locked=設定で有効にされた場合、Giteaは起動時にいくつかのOAuth2アプリケーションを事前登録します。 想定されていない動作を防ぐため、これらは編集も削除もできません。 詳細についてはOAuth2のドキュメントを参照してください。 authorized_oauth2_applications=許可済みOAuth2アプリケーション authorized_oauth2_applications_description=これらのサードパーティ アプリケーションに、あなたのGiteaアカウントへのアクセスを許可しています。 不要になったアプリケーションはアクセス権を取り消すようにしてください。 @@ -946,6 +955,7 @@ passcode_invalid=パスコードが間違っています。 再度お試しく twofa_enrolled=あなたのアカウントは正常に登録されました。 一回限りのリカバリキー (%s) は安全な場所に保存してください。 これは二度と表示されません。 twofa_failed_get_secret=シークレットが取得できません。 +webauthn_desc=セキュリティキーは暗号化キーを内蔵するハードウェア ・ デバイスです。 2要素認証に使用できます。 セキュリティキーはWebAuthn Authenticator規格をサポートしている必要があります。 webauthn_register_key=セキュリティキーを追加 webauthn_nickname=ニックネーム webauthn_delete_key=セキュリティキーの登録解除 @@ -967,7 +977,7 @@ orgs_none=あなたはどの組織のメンバーでもありません。 repos_none=あなたはリポジトリを所有していません。 delete_account=アカウントを削除 -delete_prompt=この操作により、あなたのユーザーアカウントは恒久的に抹消されます。 取り消すことはできません。 +delete_prompt=この操作により、あなたのユーザーアカウントは恒久的に抹消されます。 元に戻すことはできません。 delete_with_all_comments=あなたのアカウントは作成からまだ %s 経過していません。 幽霊コメント回避のため、イシューやPRのすべてのコメントは一緒に削除されます。 confirm_delete_account=削除の続行 delete_account_title=ユーザーアカウントの削除 @@ -1090,7 +1100,9 @@ tree_path_not_found_branch=パス %[1]s はブランチ %[2]s に存在しませ tree_path_not_found_tag=パス %[1]s はタグ %[2]s に存在しません transfer.accept=移転を承認 +transfer.accept_desc=`"%s" に移転` transfer.reject=移転を拒否 +transfer.reject_desc=`"%s" への移転をキャンセル` transfer.no_permission_to_accept=この移転を承認する権限がありません。 transfer.no_permission_to_reject=この移転を拒否する権限がありません。 @@ -1165,6 +1177,11 @@ migrate.gogs.description=notabug.org やその他の Gogs インスタンスか migrate.onedev.description=code.onedev.io やその他の OneDev インスタンスからデータを移行します。 migrate.codebase.description=codebasehq.com からデータを移行します。 migrate.gitbucket.description=GitBucket インスタンスからデータを移行します。 +migrate.codecommit.description=AWS CodeCommitからデータを移行します。 +migrate.codecommit.aws_access_key_id=AWS アクセスキー ID +migrate.codecommit.aws_secret_access_key=AWSシークレットアクセスキー +migrate.codecommit.https_git_credentials_username=HTTPS Git 認証情報 ユーザー名 +migrate.codecommit.https_git_credentials_password=HTTPS Git 認証情報 パスワード migrate.migrating_git=Gitデータ移行中 migrate.migrating_topics=トピック移行中 migrate.migrating_milestones=マイルストーン移行中 @@ -1225,6 +1242,7 @@ releases=リリース tag=タグ released_this=がこれをリリース tagged_this=がタグ付け +file.title=%s at %s file_raw=Raw file_history=履歴 file_view_source=ソースを表示 @@ -1241,6 +1259,7 @@ ambiguous_runes_header=このファイルには曖昧(ambiguous)なUnicode文字 ambiguous_runes_description=このファイルには、他の文字と見間違える可能性があるUnicode文字が含まれています。 それが意図的なものと考えられる場合は、この警告を無視して構いません。 それらの文字を表示するにはエスケープボタンを使用します。 invisible_runes_line=`この行には不可視のUnicode文字があります` ambiguous_runes_line=`この行には曖昧(ambiguous)なUnicode文字があります` +ambiguous_character=`%[1]c [U+%04[1]X] は %[2]c [U+%04[2]X] と混同するおそれがあります` escape_control_characters=エスケープ unescape_control_characters=エスケープ解除 @@ -1712,6 +1731,7 @@ issues.dependency.add_error_dep_not_same_repo=両方とも同じリポジトリ issues.review.self.approval=自分のプルリクエストを承認することはできません。 issues.review.self.rejection=自分のプルリクエストに対して修正を要求することはできません。 issues.review.approve=が変更を承認 %s +issues.review.comment=がレビュー %s issues.review.dismissed=が %s のレビューを棄却 %s issues.review.dismissed_label=棄却 issues.review.left_comment=がコメント @@ -1737,6 +1757,11 @@ issues.review.resolve_conversation=解決済みにする issues.review.un_resolve_conversation=未解決にする issues.review.resolved_by=がこの会話を解決済みにしました issues.review.commented=コメント +issues.review.official=承認済み +issues.review.requested=レビュー待ち +issues.review.rejected=変更要請済み +issues.review.stale=承認後に更新されました +issues.review.unofficial=カウントされない承認 issues.assignee.error=予期しないエラーにより、一部の担当者を追加できませんでした。 issues.reference_issue.body=内容 issues.content_history.deleted=削除しました @@ -1810,6 +1835,8 @@ pulls.is_empty=このブランチの変更は既にターゲットブランチ pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。 pulls.required_status_check_missing=必要なチェックがいくつか抜けています。 pulls.required_status_check_administrator=管理者であるため、このプルリクエストをマージすることは可能です。 +pulls.blocked_by_approvals=このプルリクエストはまだ必要な承認数を満たしていません。 公式の承認を %[1]d / %[2]d 得ています。 +pulls.blocked_by_approvals_whitelisted=このプルリクエストはまだ必要な承認数を満たしていません。 許可リストのユーザーまたはチームからの承認を %[1]d / %[2]d 得ています。 pulls.blocked_by_rejection=このプルリクエストは公式レビューアにより変更要請されています。 pulls.blocked_by_official_review_requests=このプルリクエストには公式レビュー依頼があります。 pulls.blocked_by_outdated_branch=このプルリクエストは遅れのためブロックされています。 @@ -1851,7 +1878,9 @@ pulls.unrelated_histories=マージ失敗: マージHEADとベースには共通 pulls.merge_out_of_date=マージ失敗: マージの生成中にベースが更新されました。 ヒント: もう一度試してみてください pulls.head_out_of_date=マージ失敗: マージの生成中に head が更新されました。 ヒント: もう一度試してみてください pulls.has_merged=失敗: プルリクエストはマージされていました。再度マージしたり、ターゲットブランチを変更することはできません。 +pulls.push_rejected=プッシュ失敗: プッシュは拒否されました。 このリポジトリのGitフックを見直してください。 pulls.push_rejected_summary=拒否メッセージ全体: +pulls.push_rejected_no_message=プッシュ失敗: プッシュは拒否されましたが、リモートからのメッセージがありません。このリポジトリのGitフックを見直してください pulls.open_unmerged_pull_exists=`同じ条件のプルリクエスト (#%d) が未処理のため、再オープンはできません。` pulls.status_checking=いくつかのステータスチェックが待機中です pulls.status_checks_success=ステータスチェックはすべて成功しました @@ -1907,6 +1936,7 @@ milestones.no_due_date=期日なし milestones.open=オープン milestones.close=クローズ milestones.new_subheader=マイルストーンを使うとイシューの整理や進捗確認がしやすくなります。 +milestones.completeness=%d%%消化 milestones.create=マイルストーンを作成 milestones.title=タイトル milestones.desc=説明 @@ -2306,6 +2336,7 @@ settings.event_pull_request_merge=プルリクエストのマージ settings.event_package=パッケージ settings.event_package_desc=リポジトリにパッケージが作成または削除されたとき。 settings.branch_filter=ブランチ フィルター +settings.branch_filter_desc=プッシュ、ブランチ作成、ブランチ削除のイベントを通知するブランチを、globパターンで指定するホワイトリストです。 空か*のときは、すべてのブランチのイベントを通知します。 文法については %[2]s を参照してください。 例: master{master,release*} settings.authorization_header=Authorizationヘッダー settings.authorization_header_desc=入力した場合、リクエストにAuthorizationヘッダーとして付加します。 例: %s settings.active=有効 @@ -2356,6 +2387,7 @@ settings.protected_branch.save_rule=ルールを保存 settings.protected_branch.delete_rule=ルールを削除 settings.protected_branch_can_push=プッシュを許可する settings.protected_branch_can_push_yes=プッシュできます +settings.protected_branch_can_push_no=プッシュできません settings.branch_protection=ブランチ '%s' の保護ルール settings.protect_this_branch=ブランチの保護を有効にする settings.protect_this_branch_desc=ブランチの削除を防ぎ、ブランチへのプッシュやマージを制限します。 @@ -2392,6 +2424,7 @@ settings.protect_status_check_matched=マッチ settings.protect_invalid_status_check_pattern=`不正なステータスチェックパターン: "%s"` settings.protect_no_valid_status_check_patterns=有効なステータスチェックパターンがありません。 settings.protect_required_approvals=必要な承認数: +settings.protect_required_approvals_desc=必要な承認数を満たしたプルリクエストしかマージできないようにします。 必要となる承認とは、許可リストにあるユーザーやチーム、もしくは書き込み権限を持つ誰かからのものです。 settings.protect_approvals_whitelist_enabled=許可リストに登録したユーザーやチームに承認を制限 settings.protect_approvals_whitelist_enabled_desc=許可リストに登録したユーザーまたはチームによるレビューのみを、必要な承認数にカウントします。 承認の許可リストが無い場合は、書き込み権限を持つ人によるレビューを必要な承認数にカウントします。 settings.protect_approvals_whitelist_users=許可リストに含めるレビューア: @@ -2403,9 +2436,12 @@ settings.ignore_stale_approvals_desc=古いコミットに対して行われた settings.require_signed_commits=コミット署名必須 settings.require_signed_commits_desc=署名されていない場合、または署名が検証できなかった場合は、このブランチへのプッシュを拒否します。 settings.protect_branch_name_pattern=保護ブランチ名のパターン +settings.protect_branch_name_pattern_desc=保護ブランチ名のパターン。書き方については ドキュメント を参照してください。例: main, release/** settings.protect_patterns=パターン settings.protect_protected_file_patterns=保護されるファイルのパターン (セミコロン';'で区切る): +settings.protect_protected_file_patterns_desc=保護されたファイルは、このブランチにファイルを追加・編集・削除する権限を持つユーザーであっても、直接変更することができなくなります。 セミコロン(';')で区切って複数のパターンを指定できます。 パターンの文法については %[2]s を参照してください。 例: .drone.yml, /docs/**/*.txt settings.protect_unprotected_file_patterns=保護しないファイルのパターン (セミコロン';'で区切る): +settings.protect_unprotected_file_patterns_desc=保護しないファイルは、ユーザーに書き込み権限があればプッシュ制限をバイパスして直接変更できます。 セミコロン(';')で区切って複数のパターンを指定できます。 パターンの文法については %[2]s を参照してください。 例: .drone.yml, /docs/**/*.txt settings.add_protected_branch=保護を有効にする settings.delete_protected_branch=保護を無効にする settings.update_protect_branch_success=ルール "%s" に対するブランチ保護を更新しました。 @@ -2437,6 +2473,7 @@ settings.tags.protection.allowed.teams=許可するチーム settings.tags.protection.allowed.noone=なし settings.tags.protection.create=タグを保護 settings.tags.protection.none=タグは保護されていません。 +settings.tags.protection.pattern.description=ひとつのタグ名か、複数のタグにマッチするglobパターンまたは正規表現を使用できます。 詳しくはタグの保護ガイド をご覧ください。 settings.bot_token=Botトークン settings.chat_id=チャットID settings.thread_id=スレッドID @@ -2651,6 +2688,7 @@ tag.create_success=タグ "%s" を作成しました。 topic.manage_topics=トピックの管理 topic.done=完了 +topic.count_prompt=選択できるのは25トピックまでです。 topic.format_prompt=トピック名は英字または数字で始め、ダッシュ('-')やドット('.')を含めることができます。最大35文字までです。文字は小文字でなければなりません。 find_file.go_to_file=ファイルへ移動 @@ -2719,7 +2757,7 @@ settings.change_orgname_redirect_prompt=古い名前は、再使用されてい settings.update_avatar_success=組織のアバターを更新しました。 settings.delete=組織を削除 settings.delete_account=この組織を削除 -settings.delete_prompt=組織は恒久的に削除され、元に戻すことはできません。 続行しますか? +settings.delete_prompt=組織は恒久的に削除されます。 元に戻すことはできません! settings.confirm_delete_account=削除を確認 settings.delete_org_title=組織の削除 settings.delete_org_desc=組織を恒久的に削除します。 続行しますか? @@ -2748,6 +2786,7 @@ teams.leave.detail=%s から脱退しますか? teams.can_create_org_repo=リポジトリを作成 teams.can_create_org_repo_helper=メンバーは組織のリポジトリを新たに作成できます。作成者には新しいリポジトリの管理者権限が与えられます。 teams.none_access=アクセスなし +teams.none_access_helper=メンバーは、このユニットを表示したり他の操作を行うことはできません。 公開リポジトリには適用されません。 teams.general_access=一般的なアクセス teams.general_access_helper=メンバーの権限は下記の権限テーブルで決定されます。 teams.read_access=読み取り @@ -2816,6 +2855,7 @@ last_page=最後 total=合計: %d settings=管理設定 +dashboard.new_version_hint=Gitea %s が入手可能になりました。 現在実行しているのは %s です。 詳細は ブログ を確認してください。 dashboard.statistic=サマリー dashboard.maintenance_operations=メンテナンス操作 dashboard.system_status=システム状況 @@ -3007,10 +3047,12 @@ packages.size=サイズ packages.published=配布 defaulthooks=デフォルトWebhook +defaulthooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくはWebhooksガイドをご覧下さい。 defaulthooks.add_webhook=デフォルトWebhookの追加 defaulthooks.update_webhook=デフォルトWebhookの更新 systemhooks=システムWebhook +systemhooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookは、システム内のすべてのリポジトリで呼び出されます。 そのため、パフォーマンスに及ぼす影響を考慮したうえで設定してください。 詳しくはWebhooksガイドをご覧下さい。 systemhooks.add_webhook=システムWebhookを追加 systemhooks.update_webhook=システムWebhookを更新 @@ -3105,8 +3147,18 @@ auths.tips=ヒント auths.tips.oauth2.general=OAuth2認証 auths.tips.oauth2.general.tip=新しいOAuth2認証を登録するときは、コールバック/リダイレクトURLは以下になります: auths.tip.oauth2_provider=OAuth2プロバイダー +auths.tip.bitbucket=新しいOAuthコンシューマーを %s から登録し、"アカウント" に "読み取り" 権限を追加してください。 auths.tip.nextcloud=新しいOAuthコンシューマーを、インスタンスのメニュー "Settings -> Security -> OAuth 2.0 client" から登録してください。 +auths.tip.dropbox=新しいアプリケーションを %s から登録してください。 +auths.tip.facebook=新しいアプリケーションを %s で登録し、"Facebook Login"を追加してください。 +auths.tip.github=新しいOAuthアプリケーションを %s から登録してください。 +auths.tip.gitlab_new=新しいアプリケーションを %s から登録してください。 +auths.tip.google_plus=OAuth2クライアント資格情報を、Google APIコンソール %s から取得してください。 auths.tip.openid_connect=OpenID Connect DiscoveryのURL "https://{server}/.well-known/openid-configuration" をエンドポイントとして指定してください +auths.tip.twitter=%s へアクセスしてアプリケーションを作成し、“Allow this application to be used to Sign in with Twitter”オプションを有効にしてください。 +auths.tip.discord=新しいアプリケーションを %s から登録してください。 +auths.tip.gitea=新しいOAuthアプリケーションを登録してください。 利用ガイドは %s にあります +auths.tip.yandex=`%s で新しいアプリケーションを作成してください。 "Yandex.Passport API" セクションで次の項目を許可します: "Access to email address"、"Access to user avatar"、"Access to username, first name and surname, gender"` auths.tip.mastodon=認証したいMastodonインスタンスのカスタムURLを入力してください (入力しない場合はデフォルトのURLを使用します) auths.edit=認証ソースの編集 auths.activated=認証ソースはアクティベート済み @@ -3272,6 +3324,7 @@ monitor.next=次回 monitor.previous=前回 monitor.execute_times=実行回数 monitor.process=実行中のプロセス +monitor.stacktrace=スタックトレース monitor.processes_count=%d プロセス monitor.download_diagnosis_report=診断レポートをダウンロード monitor.desc=説明 @@ -3279,6 +3332,8 @@ monitor.start=開始日時 monitor.execute_time=実行時間 monitor.last_execution_result=結果 monitor.process.cancel=処理をキャンセル +monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります +monitor.process.cancel_notices=キャンセル: %s? monitor.process.children=子プロセス monitor.queues=キュー @@ -3380,6 +3435,7 @@ raw_minutes=分 [dropzone] default_message=ファイルをここにドロップ、またはここをクリックしてアップロード +invalid_input_type=この種類のファイルはアップロードできません。 file_too_big=アップロードされたファイルのサイズ ({{filesize}} MB) は、最大サイズ ({{maxFilesize}} MB) を超えています。 remove_file=ファイル削除 @@ -3527,6 +3583,7 @@ settings.link=このパッケージをリポジトリにリンク settings.link.description=パッケージをリポジトリにリンクすると、リポジトリのパッケージリストに表示されるようになります。 settings.link.select=リポジトリを選択 settings.link.button=リポジトリのリンクを更新 +settings.link.success=リポジトリのリンクが正常に更新されました。 settings.link.error=リポジトリのリンクの更新に失敗しました。 settings.delete=パッケージ削除 settings.delete.description=パッケージの削除は恒久的で元に戻すことはできません。 diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go index 3d1a3891d9..cf48da12aa 100644 --- a/routers/api/actions/artifacts_chunks.go +++ b/routers/api/actions/artifacts_chunks.go @@ -123,6 +123,54 @@ func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chun return chunksMap, nil } +func listChunksByRunIDV4(st storage.ObjectStorage, runID, artifactID int64, blist *BlockList) ([]*chunkFileItem, error) { + storageDir := fmt.Sprintf("tmpv4%d", runID) + var chunks []*chunkFileItem + chunkMap := map[string]*chunkFileItem{} + dummy := &chunkFileItem{} + for _, name := range blist.Latest { + chunkMap[name] = dummy + } + if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error { + baseName := filepath.Base(fpath) + if !strings.HasPrefix(baseName, "block-") { + return nil + } + // when read chunks from storage, it only contains storage dir and basename, + // no matter the subdirectory setting in storage config + item := chunkFileItem{Path: storageDir + "/" + baseName, ArtifactID: artifactID} + var size int64 + var b64chunkName string + if _, err := fmt.Sscanf(baseName, "block-%d-%d-%s", &item.RunID, &size, &b64chunkName); err != nil { + return fmt.Errorf("parse content range error: %v", err) + } + rchunkName, err := base64.URLEncoding.DecodeString(b64chunkName) + if err != nil { + return fmt.Errorf("failed to parse chunkName: %v", err) + } + chunkName := string(rchunkName) + item.End = item.Start + size - 1 + if _, ok := chunkMap[chunkName]; ok { + chunkMap[chunkName] = &item + } + return nil + }); err != nil { + return nil, err + } + for i, name := range blist.Latest { + chunk, ok := chunkMap[name] + if !ok || chunk.Path == "" { + return nil, fmt.Errorf("missing Chunk (%d/%d): %s", i, len(blist.Latest), name) + } + chunks = append(chunks, chunk) + if i > 0 { + chunk.Start = chunkMap[blist.Latest[i-1]].End + 1 + chunk.End += chunk.Start + } + } + return chunks, nil +} + func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int64, artifactName string) error { // read all db artifacts by name artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{ @@ -230,7 +278,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st rawChecksum := hash.Sum(nil) actualChecksum := hex.EncodeToString(rawChecksum) if !strings.HasSuffix(checksum, actualChecksum) { - return fmt.Errorf("update artifact error checksum is invalid") + return fmt.Errorf("update artifact error checksum is invalid %v vs %v", checksum, actualChecksum) } } diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go index e78ed7a0c2..9e463cceeb 100644 --- a/routers/api/actions/artifactsv4.go +++ b/routers/api/actions/artifactsv4.go @@ -24,8 +24,15 @@ package actions // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=block // 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock -// 1.4. Unknown xml payload to Blobstorage (unauthenticated request), ignored for now +// 1.4. BlockList xml payload to Blobstorage (unauthenticated request) +// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to enshure the correct order // PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList +// Request +// +// +// blockId1 +// blockId2 +// // 1.5. FinalizeArtifact // Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact // Request @@ -82,6 +89,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" + "encoding/xml" "fmt" "io" "net/http" @@ -152,31 +160,34 @@ func ArtifactsV4Routes(prefix string) *web.Router { return m } -func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID int64) []byte { +func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID, artifactID int64) []byte { mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret()) mac.Write([]byte(endp)) mac.Write([]byte(expires)) mac.Write([]byte(artifactName)) mac.Write([]byte(fmt.Sprint(taskID))) + mac.Write([]byte(fmt.Sprint(artifactID))) return mac.Sum(nil) } -func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID int64) string { +func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID, artifactID int64) string { expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST") uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") + - "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "&artifactID=" + fmt.Sprint(artifactID) return uploadURL } func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) { rawTaskID := ctx.Req.URL.Query().Get("taskID") + rawArtifactID := ctx.Req.URL.Query().Get("artifactID") sig := ctx.Req.URL.Query().Get("sig") expires := ctx.Req.URL.Query().Get("expires") artifactName := ctx.Req.URL.Query().Get("artifactName") dsig, _ := base64.URLEncoding.DecodeString(sig) taskID, _ := strconv.ParseInt(rawTaskID, 10, 64) + artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) - expecedsig := r.buildSignature(endp, expires, artifactName, taskID) + expecedsig := r.buildSignature(endp, expires, artifactName, taskID, artifactID) if !hmac.Equal(dsig, expecedsig) { log.Error("Error unauthorized") ctx.Error(http.StatusUnauthorized, "Error unauthorized") @@ -271,6 +282,8 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { return } artifact.ContentEncoding = ArtifactV4ContentEncoding + artifact.FileSize = 0 + artifact.FileCompressedSize = 0 if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { log.Error("Error UpdateArtifactByID: %v", err) ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") @@ -279,7 +292,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { respData := CreateArtifactResponse{ Ok: true, - SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID), + SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID), } r.sendProtbufBody(ctx, &respData) } @@ -293,38 +306,77 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) { comp := ctx.Req.URL.Query().Get("comp") switch comp { case "block", "appendBlock": - // get artifact by name - artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) - if err != nil { - log.Error("Error artifact not found: %v", err) - ctx.Error(http.StatusNotFound, "Error artifact not found") - return - } + blockid := ctx.Req.URL.Query().Get("blockid") + if blockid == "" { + // get artifact by name + artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) + if err != nil { + log.Error("Error artifact not found: %v", err) + ctx.Error(http.StatusNotFound, "Error artifact not found") + return + } - if comp == "block" { - artifact.FileSize = 0 - artifact.FileCompressedSize = 0 + _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) + if err != nil { + log.Error("Error runner api getting task: task is not running") + ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") + return + } + artifact.FileCompressedSize += ctx.Req.ContentLength + artifact.FileSize += ctx.Req.ContentLength + if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { + log.Error("Error UpdateArtifactByID: %v", err) + ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") + return + } + } else { + _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/block-%d-%d-%s", task.Job.RunID, task.Job.RunID, ctx.Req.ContentLength, base64.URLEncoding.EncodeToString([]byte(blockid))), ctx.Req.Body, -1) + if err != nil { + log.Error("Error runner api getting task: task is not running") + ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") + return + } } - - _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) + ctx.JSON(http.StatusCreated, "appended") + case "blocklist": + rawArtifactID := ctx.Req.URL.Query().Get("artifactID") + artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) + _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/%d-%d-blocklist", task.Job.RunID, task.Job.RunID, artifactID), ctx.Req.Body, -1) if err != nil { log.Error("Error runner api getting task: task is not running") ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") return } - artifact.FileCompressedSize += ctx.Req.ContentLength - artifact.FileSize += ctx.Req.ContentLength - if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { - log.Error("Error UpdateArtifactByID: %v", err) - ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") - return - } - ctx.JSON(http.StatusCreated, "appended") - case "blocklist": ctx.JSON(http.StatusCreated, "created") } } +type BlockList struct { + Latest []string `xml:"Latest"` +} + +type Latest struct { + Value string `xml:",chardata"` +} + +func (r *artifactV4Routes) readBlockList(runID, artifactID int64) (*BlockList, error) { + blockListName := fmt.Sprintf("tmpv4%d/%d-%d-blocklist", runID, runID, artifactID) + s, err := r.fs.Open(blockListName) + if err != nil { + return nil, err + } + + xdec := xml.NewDecoder(s) + blockList := &BlockList{} + err = xdec.Decode(blockList) + + delerr := r.fs.Delete(blockListName) + if delerr != nil { + log.Warn("Failed to delete blockList %s: %v", blockListName, delerr) + } + return blockList, err +} + func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { var req FinalizeArtifactRequest @@ -343,18 +395,34 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { ctx.Error(http.StatusNotFound, "Error artifact not found") return } - chunkMap, err := listChunksByRunID(r.fs, runID) + + var chunks []*chunkFileItem + blockList, err := r.readBlockList(runID, artifact.ID) if err != nil { - log.Error("Error merge chunks: %v", err) - ctx.Error(http.StatusInternalServerError, "Error merge chunks") - return - } - chunks, ok := chunkMap[artifact.ID] - if !ok { - log.Error("Error merge chunks") - ctx.Error(http.StatusInternalServerError, "Error merge chunks") - return + log.Warn("Failed to read BlockList, fallback to old behavior: %v", err) + chunkMap, err := listChunksByRunID(r.fs, runID) + if err != nil { + log.Error("Error merge chunks: %v", err) + ctx.Error(http.StatusInternalServerError, "Error merge chunks") + return + } + chunks, ok = chunkMap[artifact.ID] + if !ok { + log.Error("Error merge chunks") + ctx.Error(http.StatusInternalServerError, "Error merge chunks") + return + } + } else { + chunks, err = listChunksByRunIDV4(r.fs, runID, artifact.ID, blockList) + if err != nil { + log.Error("Error merge chunks: %v", err) + ctx.Error(http.StatusInternalServerError, "Error merge chunks") + return + } + artifact.FileSize = chunks[len(chunks)-1].End + 1 + artifact.FileCompressedSize = chunks[len(chunks)-1].End + 1 } + checksum := "" if req.Hash != nil { checksum = req.Hash.Value @@ -455,7 +523,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) { } } if respData.SignedUrl == "" { - respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID) + respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID) } r.sendProtbufBody(ctx, &respData) } diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index 1486e83c57..343705990a 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -20,6 +20,7 @@ import ( "strings" packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" packages_module "code.gitea.io/gitea/modules/packages" @@ -114,7 +115,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) { xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...) latest := pds[len(pds)-1] - ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat)) + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat + lastModifed := latest.Version.CreatedUnix.AsTime().UTC().Format(http.TimeFormat) + ctx.Resp.Header().Set("Last-Modified", lastModifed) ext := strings.ToLower(filepath.Ext(params.Filename)) if isChecksumExtension(ext) { @@ -223,6 +226,10 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool helper.ServePackageFile(ctx, s, u, pf, opts) } +func mavenPkgNameKey(packageName string) string { + return "pkg_maven_" + packageName +} + // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. func UploadPackageFile(ctx *context.Context) { params, err := extractPathParameters(ctx) @@ -241,6 +248,14 @@ func UploadPackageFile(ctx *context.Context) { packageName := params.GroupID + "-" + params.ArtifactID + // for the same package, only one upload at a time + releaser, err := globallock.Lock(ctx, mavenPkgNameKey(packageName)) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer releaser() + buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body) if err != nil { apiError(ctx, http.StatusInternalServerError, err) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index a2cae73bee..308e7755b6 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1307,6 +1307,8 @@ func Routes() *web.Router { m.Group("/{ref}", func() { m.Get("/status", repo.GetCombinedCommitStatusByRef) m.Get("/statuses", repo.GetCommitStatusesByRef) + }, context.ReferencesGitRepo()) + m.Group("/{sha}", func() { m.Get("/pull", repo.GetCommitPullRequest) }, context.ReferencesGitRepo()) }, reqRepoReader(unit.TypeCode)) diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index cec7c3d534..788c75fab2 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -325,11 +325,11 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) { } } -// GetCommitPullRequest returns the pull request of the commit +// GetCommitPullRequest returns the merged pull request of the commit func GetCommitPullRequest(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest // --- - // summary: Get the pull request of the commit + // summary: Get the merged pull request of the commit // produces: // - application/json // parameters: @@ -354,7 +354,7 @@ func GetCommitPullRequest(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.PathParam(":sha")) + pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.PathParam("sha")) if err != nil { if issues_model.IsErrPullRequestNotExist(err) { ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err) diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 5c28dd878d..9583bb548c 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -118,6 +118,10 @@ func CreateAccessToken(ctx *context.APIContext) { ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err)) return } + if scope == "" { + ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope") + return + } t.Scope = scope if err := auth_model.NewAccessToken(ctx, t); err != nil { @@ -129,6 +133,7 @@ func CreateAccessToken(ctx *context.APIContext) { Token: t.Token, ID: t.ID, TokenLastEight: t.TokenLastEight, + Scopes: t.Scope.StringSlice(), }) } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index bb85df1a86..ee1ec1fd0c 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -395,7 +395,8 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string ctx.Resp.Header().Set("Content-Type", contentType) ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) - ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat)) + // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat + ctx.Resp.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) http.ServeFile(ctx.Resp, ctx.Req, reqFile) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 596abb4b9c..507b5af9d9 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -467,6 +467,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["AssigneeID"] = assigneeID ctx.Data["PosterID"] = posterID ctx.Data["Keyword"] = keyword + ctx.Data["IsShowClosed"] = isShowClosed switch { case isShowClosed.Value(): ctx.Data["State"] = "closed" diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 95299b44d5..ced0bbc15a 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -164,7 +164,19 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) { ctx.Data["HeadTarget"] = pull.MustHeadUserName(ctx) + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch } ctx.Data["BaseTarget"] = pull.BaseBranch - ctx.Data["HeadBranchLink"] = pull.GetHeadBranchLink(ctx) + headBranchLink := "" + if pull.Flow == issues_model.PullRequestFlowGithub { + b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, pull.HeadBranch) + switch { + case err == nil: + if !b.IsDeleted { + headBranchLink = pull.GetHeadBranchLink(ctx) + } + case !git_model.IsErrBranchNotExist(err): + log.Error("GetBranch: %v", err) + } + } + ctx.Data["HeadBranchLink"] = headBranchLink ctx.Data["BaseBranchLink"] = pull.GetBaseBranchLink(ctx) } diff --git a/routers/web/web.go b/routers/web/web.go index f1e941a84e..af46c36fe7 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1069,9 +1069,7 @@ func registerRoutes(m *web.Router) { m.Combo("/edit").Get(repo_setting.SettingsProtectedBranch). Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.SettingsProtectedBranchPost) m.Post("/{id}/delete", repo_setting.DeleteProtectedBranchRulePost) - }, repo.MustBeNotEmpty) - - m.Post("/rename_branch", web.Bind(forms.RenameBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.RenameBranchPost) + }) m.Group("/tags", func() { m.Get("", repo_setting.ProtectedTags) @@ -1304,6 +1302,7 @@ func registerRoutes(m *web.Router) { }, web.Bind(forms.NewBranchForm{})) m.Post("/delete", repo.DeleteBranchPost) m.Post("/restore", repo.RestoreBranchPost) + m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost) }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) diff --git a/services/webhook/discord.go b/services/webhook/discord.go index f27b39dac3..59e87a7e1f 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -11,6 +11,7 @@ import ( "net/url" "strconv" "strings" + "unicode/utf8" webhook_model "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" @@ -154,8 +155,14 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) { var text string // for each commit, generate attachment text for i, commit := range p.Commits { - text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, - strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name) + // limit the commit message display to just the summary, otherwise it would be hard to read + message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 1)[0], "\r") + + // a limit of 50 is set because GitHub does the same + if utf8.RuneCountInString(message) > 50 { + message = fmt.Sprintf("%.47s...", message) + } + text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, message, commit.Author.Name) // add linebreak to each commit but the last if i < len(p.Commits)-1 { text += "\n" diff --git a/services/webhook/discord_test.go b/services/webhook/discord_test.go index c04b95383b..fbb4b24ef1 100644 --- a/services/webhook/discord_test.go +++ b/services/webhook/discord_test.go @@ -80,6 +80,20 @@ func TestDiscordPayload(t *testing.T) { assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) }) + t.Run("PushWithLongCommitMessage", func(t *testing.T) { + p := pushTestMultilineCommitMessagePayload() + + pl, err := dc.Push(p) + require.NoError(t, err) + + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title) + assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1", pl.Embeds[0].Description) + assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + }) + t.Run("Issue", func(t *testing.T) { p := issueTestPayload() diff --git a/services/webhook/general_test.go b/services/webhook/general_test.go index fc6e7140e7..d6a77c9442 100644 --- a/services/webhook/general_test.go +++ b/services/webhook/general_test.go @@ -64,9 +64,17 @@ func forkTestPayload() *api.ForkPayload { } func pushTestPayload() *api.PushPayload { + return pushTestPayloadWithCommitMessage("commit message") +} + +func pushTestMultilineCommitMessagePayload() *api.PushPayload { + return pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body.") +} + +func pushTestPayloadWithCommitMessage(message string) *api.PushPayload { commit := &api.PayloadCommit{ ID: "2020558fe2e34debb818a514715839cabd25e778", - Message: "commit message", + Message: message, URL: "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778", Author: &api.PayloadUser{ Name: "user1", diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index 3c8e5846d7..f5d709bb16 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -240,7 +240,7 @@
{{ctx.Locale.Tr "repo.settings.rename_branch"}}
-
+
{{.CsrfTokenHtml}}
diff --git a/templates/repo/issue/filter_actions.tmpl b/templates/repo/issue/filter_actions.tmpl index 88d0653f7d..831ab17bea 100644 --- a/templates/repo/issue/filter_actions.tmpl +++ b/templates/repo/issue/filter_actions.tmpl @@ -1,9 +1,9 @@
- + {{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 1243681f3a..7fa77b3f1c 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -50,9 +50,14 @@ {{if .Issue.IsPull}} {{$headHref := .HeadTarget}} {{if .HeadBranchLink}} - {{$headHref = HTMLFormat `%s` .HeadBranchLink $headHref}} + {{$headHref = HTMLFormat `%s ` .HeadBranchLink $headHref (ctx.Locale.Tr "copy_branch") .HeadTarget (svg "octicon-copy" 14)}} + {{else}} + {{if .Issue.PullRequest.IsAgitFlow}} + {{$headHref = HTMLFormat `%s AGit` $headHref "https://docs.gitea.com/usage/agit" (ctx.Locale.Tr "repo.pull.agit_documentation")}} + {{else}} + {{$headHref = HTMLFormat `%s` (ctx.Locale.Tr "form.target_branch_not_exist") $headHref}} + {{end}} {{end}} - {{$headHref = HTMLFormat `%s ` $headHref (ctx.Locale.Tr "copy_branch") .HeadTarget (svg "octicon-copy" 14)}} {{$baseHref := .BaseTarget}} {{if .BaseBranchLink}} {{$baseHref = HTMLFormat `%s` .BaseBranchLink $baseHref}} diff --git a/templates/repo/settings/branches.tmpl b/templates/repo/settings/branches.tmpl index 52c0c2c800..6f070ba61c 100644 --- a/templates/repo/settings/branches.tmpl +++ b/templates/repo/settings/branches.tmpl @@ -15,19 +15,17 @@ {{.CsrfTokenHtml}} - {{if not .Repository.IsEmpty}} - diff --git a/templates/repo/settings/navbar.tmpl b/templates/repo/settings/navbar.tmpl index b9105bb8ed..e46273aff0 100644 --- a/templates/repo/settings/navbar.tmpl +++ b/templates/repo/settings/navbar.tmpl @@ -13,11 +13,9 @@ {{end}} {{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeCode}} - {{if not .Repository.IsEmpty}} - - {{ctx.Locale.Tr "repo.settings.branches"}} - - {{end}} + + {{ctx.Locale.Tr "repo.settings.branches"}} + {{ctx.Locale.Tr "repo.settings.tags"}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index ae4384f723..268f9b3747 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -5727,7 +5727,7 @@ "tags": [ "repository" ], - "summary": "Get the pull request of the commit", + "summary": "Get the merged pull request of the commit", "operationId": "repoGetCommitPullRequest", "parameters": [ { diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index b0a52d2c64..ec61e56f4d 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -14,20 +14,22 @@ {{.CsrfTokenHtml}}
- +
{{if or (not .DisablePassword) .LinkAccountMode}} {{end}} {{if not .LinkAccountMode}}
- +
{{end}} @@ -35,7 +37,7 @@ {{template "user/auth/captcha" .}}
-