Merge branch 'main' into xormigrate

This commit is contained in:
qwerty287 2024-09-24 17:49:49 +03:00 committed by GitHub
commit 3bad0647c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 589 additions and 105 deletions

3
.github/labeler.yml vendored
View File

@ -70,10 +70,11 @@ modifies/go:
- any-glob-to-any-file: - any-glob-to-any-file:
- "**/*.go" - "**/*.go"
modifies/js: modifies/frontend:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- "**/*.js" - "**/*.js"
- "**/*.ts"
- "**/*.vue" - "**/*.vue"
docs-update-needed: docs-update-needed:

View File

@ -198,11 +198,12 @@ jobs:
test-mssql: test-mssql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed 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: services:
mssql: mssql:
# some images before 2024-04 can't run on new kernels image: mcr.microsoft.com/mssql/server:2017-latest
image: mcr.microsoft.com/mssql/server:2019-latest
env: env:
ACCEPT_EULA: Y ACCEPT_EULA: Y
MSSQL_PID: Standard MSSQL_PID: Standard

View File

@ -143,6 +143,12 @@ func runServ(c *cli.Context) error {
return nil 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(), "-") keys := strings.Split(c.Args().First(), "-")
if len(keys) != 2 || keys[0] != "key" { if len(keys) != 2 || keys[0] != "key" {
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First()) 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] verb := words[0]
repoPath := words[1] repoPath := strings.TrimPrefix(words[1], "/")
if repoPath[0] == '/' {
repoPath = repoPath[1:]
}
var lfsVerb string var lfsVerb string
if verb == lfsAuthenticateVerb { if verb == lfsAuthenticateVerb {

View File

@ -526,7 +526,8 @@ INTERNAL_TOKEN =
;; HMAC to encode urls with, it **is required** if camo is enabled. ;; HMAC to encode urls with, it **is required** if camo is enabled.
;HMAC_KEY = ;HMAC_KEY =
;; Set to true to use camo for https too lese only non https urls are proxyed ;; 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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -34,6 +34,7 @@ type ActivityStats struct {
OpenedPRAuthorCount int64 OpenedPRAuthorCount int64
MergedPRs issues_model.PullRequestList MergedPRs issues_model.PullRequestList
MergedPRAuthorCount int64 MergedPRAuthorCount int64
ActiveIssues issues_model.IssueList
OpenedIssues issues_model.IssueList OpenedIssues issues_model.IssueList
OpenedIssueAuthorCount int64 OpenedIssueAuthorCount int64
ClosedIssues issues_model.IssueList ClosedIssues issues_model.IssueList
@ -172,7 +173,7 @@ func (stats *ActivityStats) MergedPRPerc() int {
// ActiveIssueCount returns total active issue count // ActiveIssueCount returns total active issue count
func (stats *ActivityStats) ActiveIssueCount() int { func (stats *ActivityStats) ActiveIssueCount() int {
return stats.OpenedIssueCount() + stats.ClosedIssueCount() return len(stats.ActiveIssues)
} }
// OpenedIssueCount returns open issue count // OpenedIssueCount returns open issue count
@ -285,13 +286,21 @@ func (stats *ActivityStats) FillIssues(ctx context.Context, repoID int64, fromTi
stats.ClosedIssueAuthorCount = count stats.ClosedIssueAuthorCount = count
// New issues // New issues
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false) sess = newlyCreatedIssues(ctx, repoID, fromTime)
sess.OrderBy("issue.created_unix ASC") sess.OrderBy("issue.created_unix ASC")
stats.OpenedIssues = make(issues_model.IssueList, 0) stats.OpenedIssues = make(issues_model.IssueList, 0)
if err = sess.Find(&stats.OpenedIssues); err != nil { if err = sess.Find(&stats.OpenedIssues); err != nil {
return err 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 // Opened issue authors
sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false) sess = issuesForActivityStatement(ctx, repoID, fromTime, false, false)
if _, err = sess.Select("count(distinct issue.poster_id) as `count`").Table("issue").Get(&count); err != nil { 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) 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 { func issuesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time, closed, unresolved bool) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID). sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_closed = ?", closed) And("issue.is_closed = ?", closed)

View File

@ -268,6 +268,10 @@ func (pr *PullRequest) LoadAttributes(ctx context.Context) (err error) {
return nil 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 // LoadHeadRepo loads the head repository, pr.HeadRepo will remain nil if it does not exist
// and thus ErrRepoNotExist will never be returned // and thus ErrRepoNotExist will never be returned
func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) { func (pr *PullRequest) LoadHeadRepo(ctx context.Context) (err error) {

View File

@ -75,7 +75,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
w.Header().Set("Etag", etag) w.Header().Set("Etag", etag)
} }
if lastModified != nil && !lastModified.IsZero() { 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 { if len(etag) > 0 {

View File

@ -79,6 +79,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
httpcache.SetCacheControlInHeader(header, duration) httpcache.SetCacheControlInHeader(header, duration)
if !opts.LastModified.IsZero() { 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)) header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
} }
} }

View File

@ -38,7 +38,7 @@ func camoHandleLink(link string) string {
if setting.Camo.Enabled { if setting.Camo.Enabled {
lnkURL, err := url.Parse(link) lnkURL, err := url.Parse(link)
if err == nil && lnkURL.IsAbs() && !strings.HasPrefix(link, setting.AppURL) && 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) return CamoEncode(link)
} }
} }

View File

@ -28,7 +28,7 @@ func TestCamoHandleLink(t *testing.T) {
"https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc", "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc",
camoHandleLink("http://testimages.org/img.jpg")) camoHandleLink("http://testimages.org/img.jpg"))
setting.Camo.Allways = true setting.Camo.Always = true
assert.Equal(t, assert.Equal(t,
"https://gitea.com/img.jpg", "https://gitea.com/img.jpg",
camoHandleLink("https://gitea.com/img.jpg")) camoHandleLink("https://gitea.com/img.jpg"))

View File

@ -48,6 +48,7 @@ type Metadata struct {
Homepage string `json:"homepage,omitempty"` Homepage string `json:"homepage,omitempty"`
License Licenses `json:"license,omitempty"` License Licenses `json:"license,omitempty"`
Authors []Author `json:"authors,omitempty"` Authors []Author `json:"authors,omitempty"`
Bin []string `json:"bin,omitempty"`
Autoload map[string]any `json:"autoload,omitempty"` Autoload map[string]any `json:"autoload,omitempty"`
AutoloadDev map[string]any `json:"autoload-dev,omitempty"` AutoloadDev map[string]any `json:"autoload-dev,omitempty"`
Extra map[string]any `json:"extra,omitempty"` Extra map[string]any `json:"extra,omitempty"`

View File

@ -3,18 +3,28 @@
package setting package setting
import "code.gitea.io/gitea/modules/log" import (
"strconv"
"code.gitea.io/gitea/modules/log"
)
var Camo = struct { var Camo = struct {
Enabled bool Enabled bool
ServerURL string `ini:"SERVER_URL"` ServerURL string `ini:"SERVER_URL"`
HMACKey string `ini:"HMAC_KEY"` HMACKey string `ini:"HMAC_KEY"`
Allways bool Always bool
}{} }{}
func loadCamoFrom(rootCfg ConfigProvider) { func loadCamoFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "camo", &Camo) mustMapSetting(rootCfg, "camo", &Camo)
if Camo.Enabled { 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 == "" { if Camo.ServerURL == "" || Camo.HMACKey == "" {
log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`) log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`)
} }

2
options/gitignore/Zig Normal file
View File

@ -0,0 +1,2 @@
.zig-cache/
zig-out/

View File

@ -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.

View File

@ -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 <strong>%[1]s</strong> %[2]s pulls.recently_pushed_new_branches = You pushed on branch <strong>%[1]s</strong> %[2]s
pull.deleted_branch = (deleted):%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 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

View File

@ -159,6 +159,7 @@ filter.public=公開
filter.private=プライベート filter.private=プライベート
no_results_found=見つかりません。 no_results_found=見つかりません。
internal_error_skipped=内部エラーが発生しましたがスキップされました: %s
[search] [search]
search=検索… search=検索…
@ -177,6 +178,8 @@ code_search_by_git_grep=現在のコード検索は "git grep" によって行
package_kind=パッケージを検索... package_kind=パッケージを検索...
project_kind=プロジェクトを検索... project_kind=プロジェクトを検索...
branch_kind=ブランチを検索... branch_kind=ブランチを検索...
tag_kind=タグを検索...
tag_tooltip=一致するタグを検索します。任意のシーケンスに一致させるには '%' を使用してください。
commit_kind=コミットを検索... commit_kind=コミットを検索...
runner_kind=ランナーを検索... runner_kind=ランナーを検索...
no_results=一致する結果が見つかりませんでした no_results=一致する結果が見つかりませんでした
@ -218,16 +221,20 @@ string.desc=Z - A
[error] [error]
occurred=エラーが発生しました occurred=エラーが発生しました
report_message=Gitea のバグが疑われる場合は、<a href="%s" target="_blank">GitHub</a>でIssueを検索して、見つからなければ新しいIssueを作成してください。
not_found=ターゲットが見つかりませんでした。 not_found=ターゲットが見つかりませんでした。
network_error=ネットワークエラー network_error=ネットワークエラー
[startpage] [startpage]
app_desc=自分で立てる、超簡単 Git サービス app_desc=自分で立てる、超簡単 Git サービス
install=簡単インストール install=簡単インストール
install_desc=シンプルに、プラットフォームに応じて<a target="_blank" rel="noopener noreferrer" href="%[1]s">バイナリを実行</a>したり、<a target="_blank" rel="noopener noreferrer" href="%[2]s">Docker</a>で動かしたり、<a target="_blank" rel="noopener noreferrer" href="%[3]s">パッケージ</a>を使うだけ。
platform=クロスプラットフォーム platform=クロスプラットフォーム
platform_desc=Giteaは<a target="_blank" rel="noopener noreferrer" href="%s">Go</a>がコンパイル可能なあらゆる環境で動きます: Windows、macOS、Linux、ARMなど。 あなたの好きなものを選んでください!
lightweight=軽量 lightweight=軽量
lightweight_desc=Gitea の最小動作要件は小さいため、安価な Raspberry Pi でも動きます。エネルギーを節約しましょう! lightweight_desc=Gitea の最小動作要件は小さいため、安価な Raspberry Pi でも動きます。エネルギーを節約しましょう!
license=オープンソース license=オープンソース
license_desc=Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">%[2]s</a>! このプロジェクトをさらに向上させるため、ぜひ<a target="_blank" rel="noopener noreferrer" href="%[3]s">貢献</a>して参加してください。 貢献者になることを恥ずかしがらないで!
[install] [install]
install=インストール install=インストール
@ -450,6 +457,7 @@ authorize_title=`"%s"にあなたのアカウントへのアクセスを許可
authorization_failed=認可失敗 authorization_failed=認可失敗
authorization_failed_desc=無効なリクエストを検出したため認可が失敗しました。 認可しようとしたアプリの開発者に連絡してください。 authorization_failed_desc=無効なリクエストを検出したため認可が失敗しました。 認可しようとしたアプリの開発者に連絡してください。
sspi_auth_failed=SSPI認証に失敗しました sspi_auth_failed=SSPI認証に失敗しました
password_pwned=あなたが選択したパスワードは、過去の情報漏洩事件で流出した<a target="_blank" rel="noopener noreferrer" href="%s">盗まれたパスワードのリスト</a>に含まれています。 別のパスワードでもう一度試してください。 また他の登録でもこのパスワードからの変更を検討してください。
password_pwned_err=HaveIBeenPwnedへのリクエストを完了できませんでした password_pwned_err=HaveIBeenPwnedへのリクエストを完了できませんでした
last_admin=最後の管理者は削除できません。少なくとも一人の管理者が必要です。 last_admin=最後の管理者は削除できません。少なくとも一人の管理者が必要です。
signin_passkey=パスキーでサインイン signin_passkey=パスキーでサインイン
@ -919,6 +927,7 @@ oauth2_client_secret_hint=このページから移動したりページを更新
oauth2_application_edit=編集 oauth2_application_edit=編集
oauth2_application_create_description=OAuth2アプリケーションで、サードパーティアプリケーションがこのインスタンス上のユーザーアカウントにアクセスできるようになります。 oauth2_application_create_description=OAuth2アプリケーションで、サードパーティアプリケーションがこのインスタンス上のユーザーアカウントにアクセスできるようになります。
oauth2_application_remove_description=OAuth2アプリケーションを削除すると、このインスタンス上の許可されたユーザーアカウントへのアクセスができなくなります。 続行しますか? oauth2_application_remove_description=OAuth2アプリケーションを削除すると、このインスタンス上の許可されたユーザーアカウントへのアクセスができなくなります。 続行しますか?
oauth2_application_locked=設定で有効にされた場合、Giteaは起動時にいくつかのOAuth2アプリケーションを事前登録します。 想定されていない動作を防ぐため、これらは編集も削除もできません。 詳細についてはOAuth2のドキュメントを参照してください。
authorized_oauth2_applications=許可済みOAuth2アプリケーション authorized_oauth2_applications=許可済みOAuth2アプリケーション
authorized_oauth2_applications_description=これらのサードパーティ アプリケーションに、あなたのGiteaアカウントへのアクセスを許可しています。 不要になったアプリケーションはアクセス権を取り消すようにしてください。 authorized_oauth2_applications_description=これらのサードパーティ アプリケーションに、あなたのGiteaアカウントへのアクセスを許可しています。 不要になったアプリケーションはアクセス権を取り消すようにしてください。
@ -946,6 +955,7 @@ passcode_invalid=パスコードが間違っています。 再度お試しく
twofa_enrolled=あなたのアカウントは正常に登録されました。 一回限りのリカバリキー (%s) は安全な場所に保存してください。 これは二度と表示されません。 twofa_enrolled=あなたのアカウントは正常に登録されました。 一回限りのリカバリキー (%s) は安全な場所に保存してください。 これは二度と表示されません。
twofa_failed_get_secret=シークレットが取得できません。 twofa_failed_get_secret=シークレットが取得できません。
webauthn_desc=セキュリティキーは暗号化キーを内蔵するハードウェア ・ デバイスです。 2要素認証に使用できます。 セキュリティキーは<a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a>規格をサポートしている必要があります。
webauthn_register_key=セキュリティキーを追加 webauthn_register_key=セキュリティキーを追加
webauthn_nickname=ニックネーム webauthn_nickname=ニックネーム
webauthn_delete_key=セキュリティキーの登録解除 webauthn_delete_key=セキュリティキーの登録解除
@ -967,7 +977,7 @@ orgs_none=あなたはどの組織のメンバーでもありません。
repos_none=あなたはリポジトリを所有していません。 repos_none=あなたはリポジトリを所有していません。
delete_account=アカウントを削除 delete_account=アカウントを削除
delete_prompt=この操作により、あなたのユーザーアカウントは恒久的に抹消されます。 取り消すことは<strong>できません</strong>。 delete_prompt=この操作により、あなたのユーザーアカウントは恒久的に抹消されます。 元に戻すことは<strong>できません</strong>。
delete_with_all_comments=あなたのアカウントは作成からまだ %s 経過していません。 幽霊コメント回避のため、イシューやPRのすべてのコメントは一緒に削除されます。 delete_with_all_comments=あなたのアカウントは作成からまだ %s 経過していません。 幽霊コメント回避のため、イシューやPRのすべてのコメントは一緒に削除されます。
confirm_delete_account=削除の続行 confirm_delete_account=削除の続行
delete_account_title=ユーザーアカウントの削除 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 に存在しません tree_path_not_found_tag=パス %[1]s はタグ %[2]s に存在しません
transfer.accept=移転を承認 transfer.accept=移転を承認
transfer.accept_desc=`"%s" に移転`
transfer.reject=移転を拒否 transfer.reject=移転を拒否
transfer.reject_desc=`"%s" への移転をキャンセル`
transfer.no_permission_to_accept=この移転を承認する権限がありません。 transfer.no_permission_to_accept=この移転を承認する権限がありません。
transfer.no_permission_to_reject=この移転を拒否する権限がありません。 transfer.no_permission_to_reject=この移転を拒否する権限がありません。
@ -1165,6 +1177,11 @@ migrate.gogs.description=notabug.org やその他の Gogs インスタンスか
migrate.onedev.description=code.onedev.io やその他の OneDev インスタンスからデータを移行します。 migrate.onedev.description=code.onedev.io やその他の OneDev インスタンスからデータを移行します。
migrate.codebase.description=codebasehq.com からデータを移行します。 migrate.codebase.description=codebasehq.com からデータを移行します。
migrate.gitbucket.description=GitBucket インスタンスからデータを移行します。 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_git=Gitデータ移行中
migrate.migrating_topics=トピック移行中 migrate.migrating_topics=トピック移行中
migrate.migrating_milestones=マイルストーン移行中 migrate.migrating_milestones=マイルストーン移行中
@ -1225,6 +1242,7 @@ releases=リリース
tag=タグ tag=タグ
released_this=がこれをリリース released_this=がこれをリリース
tagged_this=がタグ付け tagged_this=がタグ付け
file.title=%s at %s
file_raw=Raw file_raw=Raw
file_history=履歴 file_history=履歴
file_view_source=ソースを表示 file_view_source=ソースを表示
@ -1241,6 +1259,7 @@ ambiguous_runes_header=このファイルには曖昧(ambiguous)なUnicode文字
ambiguous_runes_description=このファイルには、他の文字と見間違える可能性があるUnicode文字が含まれています。 それが意図的なものと考えられる場合は、この警告を無視して構いません。 それらの文字を表示するにはエスケープボタンを使用します。 ambiguous_runes_description=このファイルには、他の文字と見間違える可能性があるUnicode文字が含まれています。 それが意図的なものと考えられる場合は、この警告を無視して構いません。 それらの文字を表示するにはエスケープボタンを使用します。
invisible_runes_line=`この行には不可視のUnicode文字があります` invisible_runes_line=`この行には不可視のUnicode文字があります`
ambiguous_runes_line=`この行には曖昧(ambiguous)なUnicode文字があります` ambiguous_runes_line=`この行には曖昧(ambiguous)なUnicode文字があります`
ambiguous_character=`%[1]c [U+%04[1]X] は %[2]c [U+%04[2]X] と混同するおそれがあります`
escape_control_characters=エスケープ escape_control_characters=エスケープ
unescape_control_characters=エスケープ解除 unescape_control_characters=エスケープ解除
@ -1712,6 +1731,7 @@ issues.dependency.add_error_dep_not_same_repo=両方とも同じリポジトリ
issues.review.self.approval=自分のプルリクエストを承認することはできません。 issues.review.self.approval=自分のプルリクエストを承認することはできません。
issues.review.self.rejection=自分のプルリクエストに対して修正を要求することはできません。 issues.review.self.rejection=自分のプルリクエストに対して修正を要求することはできません。
issues.review.approve=が変更を承認 %s issues.review.approve=が変更を承認 %s
issues.review.comment=がレビュー %s
issues.review.dismissed=が %s のレビューを棄却 %s issues.review.dismissed=が %s のレビューを棄却 %s
issues.review.dismissed_label=棄却 issues.review.dismissed_label=棄却
issues.review.left_comment=がコメント issues.review.left_comment=がコメント
@ -1737,6 +1757,11 @@ issues.review.resolve_conversation=解決済みにする
issues.review.un_resolve_conversation=未解決にする issues.review.un_resolve_conversation=未解決にする
issues.review.resolved_by=がこの会話を解決済みにしました issues.review.resolved_by=がこの会話を解決済みにしました
issues.review.commented=コメント issues.review.commented=コメント
issues.review.official=承認済み
issues.review.requested=レビュー待ち
issues.review.rejected=変更要請済み
issues.review.stale=承認後に更新されました
issues.review.unofficial=カウントされない承認
issues.assignee.error=予期しないエラーにより、一部の担当者を追加できませんでした。 issues.assignee.error=予期しないエラーにより、一部の担当者を追加できませんでした。
issues.reference_issue.body=内容 issues.reference_issue.body=内容
issues.content_history.deleted=削除しました issues.content_history.deleted=削除しました
@ -1810,6 +1835,8 @@ pulls.is_empty=このブランチの変更は既にターゲットブランチ
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。 pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
pulls.required_status_check_missing=必要なチェックがいくつか抜けています。 pulls.required_status_check_missing=必要なチェックがいくつか抜けています。
pulls.required_status_check_administrator=管理者であるため、このプルリクエストをマージすることは可能です。 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_rejection=このプルリクエストは公式レビューアにより変更要請されています。
pulls.blocked_by_official_review_requests=このプルリクエストには公式レビュー依頼があります。 pulls.blocked_by_official_review_requests=このプルリクエストには公式レビュー依頼があります。
pulls.blocked_by_outdated_branch=このプルリクエストは遅れのためブロックされています。 pulls.blocked_by_outdated_branch=このプルリクエストは遅れのためブロックされています。
@ -1851,7 +1878,9 @@ pulls.unrelated_histories=マージ失敗: マージHEADとベースには共通
pulls.merge_out_of_date=マージ失敗: マージの生成中にベースが更新されました。 ヒント: もう一度試してみてください pulls.merge_out_of_date=マージ失敗: マージの生成中にベースが更新されました。 ヒント: もう一度試してみてください
pulls.head_out_of_date=マージ失敗: マージの生成中に head が更新されました。 ヒント: もう一度試してみてください pulls.head_out_of_date=マージ失敗: マージの生成中に head が更新されました。 ヒント: もう一度試してみてください
pulls.has_merged=失敗: プルリクエストはマージされていました。再度マージしたり、ターゲットブランチを変更することはできません。 pulls.has_merged=失敗: プルリクエストはマージされていました。再度マージしたり、ターゲットブランチを変更することはできません。
pulls.push_rejected=プッシュ失敗: プッシュは拒否されました。 このリポジトリのGitフックを見直してください。
pulls.push_rejected_summary=拒否メッセージ全体: pulls.push_rejected_summary=拒否メッセージ全体:
pulls.push_rejected_no_message=プッシュ失敗: プッシュは拒否されましたが、リモートからのメッセージがありません。このリポジトリのGitフックを見直してください
pulls.open_unmerged_pull_exists=`同じ条件のプルリクエスト (#%d) が未処理のため、再オープンはできません。` pulls.open_unmerged_pull_exists=`同じ条件のプルリクエスト (#%d) が未処理のため、再オープンはできません。`
pulls.status_checking=いくつかのステータスチェックが待機中です pulls.status_checking=いくつかのステータスチェックが待機中です
pulls.status_checks_success=ステータスチェックはすべて成功しました pulls.status_checks_success=ステータスチェックはすべて成功しました
@ -1907,6 +1936,7 @@ milestones.no_due_date=期日なし
milestones.open=オープン milestones.open=オープン
milestones.close=クローズ milestones.close=クローズ
milestones.new_subheader=マイルストーンを使うとイシューの整理や進捗確認がしやすくなります。 milestones.new_subheader=マイルストーンを使うとイシューの整理や進捗確認がしやすくなります。
milestones.completeness=<strong>%d%%</strong>消化
milestones.create=マイルストーンを作成 milestones.create=マイルストーンを作成
milestones.title=タイトル milestones.title=タイトル
milestones.desc=説明 milestones.desc=説明
@ -2306,6 +2336,7 @@ settings.event_pull_request_merge=プルリクエストのマージ
settings.event_package=パッケージ settings.event_package=パッケージ
settings.event_package_desc=リポジトリにパッケージが作成または削除されたとき。 settings.event_package_desc=リポジトリにパッケージが作成または削除されたとき。
settings.branch_filter=ブランチ フィルター settings.branch_filter=ブランチ フィルター
settings.branch_filter_desc=プッシュ、ブランチ作成、ブランチ削除のイベントを通知するブランチを、globパターンで指定するホワイトリストです。 空か<code>*</code>のときは、すべてのブランチのイベントを通知します。 文法については <a href="%[1]s">%[2]s</a> を参照してください。 例: <code>master</code> 、 <code>{master,release*}</code>
settings.authorization_header=Authorizationヘッダー settings.authorization_header=Authorizationヘッダー
settings.authorization_header_desc=入力した場合、リクエストにAuthorizationヘッダーとして付加します。 例: %s settings.authorization_header_desc=入力した場合、リクエストにAuthorizationヘッダーとして付加します。 例: %s
settings.active=有効 settings.active=有効
@ -2356,6 +2387,7 @@ settings.protected_branch.save_rule=ルールを保存
settings.protected_branch.delete_rule=ルールを削除 settings.protected_branch.delete_rule=ルールを削除
settings.protected_branch_can_push=プッシュを許可する settings.protected_branch_can_push=プッシュを許可する
settings.protected_branch_can_push_yes=プッシュできます settings.protected_branch_can_push_yes=プッシュできます
settings.protected_branch_can_push_no=プッシュできません
settings.branch_protection=ブランチ '<b>%s</b>' の保護ルール settings.branch_protection=ブランチ '<b>%s</b>' の保護ルール
settings.protect_this_branch=ブランチの保護を有効にする settings.protect_this_branch=ブランチの保護を有効にする
settings.protect_this_branch_desc=ブランチの削除を防ぎ、ブランチへのプッシュやマージを制限します。 settings.protect_this_branch_desc=ブランチの削除を防ぎ、ブランチへのプッシュやマージを制限します。
@ -2392,6 +2424,7 @@ settings.protect_status_check_matched=マッチ
settings.protect_invalid_status_check_pattern=`不正なステータスチェックパターン: "%s"` settings.protect_invalid_status_check_pattern=`不正なステータスチェックパターン: "%s"`
settings.protect_no_valid_status_check_patterns=有効なステータスチェックパターンがありません。 settings.protect_no_valid_status_check_patterns=有効なステータスチェックパターンがありません。
settings.protect_required_approvals=必要な承認数: settings.protect_required_approvals=必要な承認数:
settings.protect_required_approvals_desc=必要な承認数を満たしたプルリクエストしかマージできないようにします。 必要となる承認とは、許可リストにあるユーザーやチーム、もしくは書き込み権限を持つ誰かからのものです。
settings.protect_approvals_whitelist_enabled=許可リストに登録したユーザーやチームに承認を制限 settings.protect_approvals_whitelist_enabled=許可リストに登録したユーザーやチームに承認を制限
settings.protect_approvals_whitelist_enabled_desc=許可リストに登録したユーザーまたはチームによるレビューのみを、必要な承認数にカウントします。 承認の許可リストが無い場合は、書き込み権限を持つ人によるレビューを必要な承認数にカウントします。 settings.protect_approvals_whitelist_enabled_desc=許可リストに登録したユーザーまたはチームによるレビューのみを、必要な承認数にカウントします。 承認の許可リストが無い場合は、書き込み権限を持つ人によるレビューを必要な承認数にカウントします。
settings.protect_approvals_whitelist_users=許可リストに含めるレビューア: settings.protect_approvals_whitelist_users=許可リストに含めるレビューア:
@ -2403,9 +2436,12 @@ settings.ignore_stale_approvals_desc=古いコミットに対して行われた
settings.require_signed_commits=コミット署名必須 settings.require_signed_commits=コミット署名必須
settings.require_signed_commits_desc=署名されていない場合、または署名が検証できなかった場合は、このブランチへのプッシュを拒否します。 settings.require_signed_commits_desc=署名されていない場合、または署名が検証できなかった場合は、このブランチへのプッシュを拒否します。
settings.protect_branch_name_pattern=保護ブランチ名のパターン settings.protect_branch_name_pattern=保護ブランチ名のパターン
settings.protect_branch_name_pattern_desc=保護ブランチ名のパターン。書き方については <a href="%s">ドキュメント</a> を参照してください。例: main, release/**
settings.protect_patterns=パターン settings.protect_patterns=パターン
settings.protect_protected_file_patterns=保護されるファイルのパターン (セミコロン';'で区切る): settings.protect_protected_file_patterns=保護されるファイルのパターン (セミコロン';'で区切る):
settings.protect_protected_file_patterns_desc=保護されたファイルは、このブランチにファイルを追加・編集・削除する権限を持つユーザーであっても、直接変更することができなくなります。 セミコロン(';')で区切って複数のパターンを指定できます。 パターンの文法については <a href='%[1]s'>%[2]s</a> を参照してください。 例: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
settings.protect_unprotected_file_patterns=保護しないファイルのパターン (セミコロン';'で区切る): settings.protect_unprotected_file_patterns=保護しないファイルのパターン (セミコロン';'で区切る):
settings.protect_unprotected_file_patterns_desc=保護しないファイルは、ユーザーに書き込み権限があればプッシュ制限をバイパスして直接変更できます。 セミコロン(';')で区切って複数のパターンを指定できます。 パターンの文法については <a href='%[1]s'>%[2]s</a> を参照してください。 例: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
settings.add_protected_branch=保護を有効にする settings.add_protected_branch=保護を有効にする
settings.delete_protected_branch=保護を無効にする settings.delete_protected_branch=保護を無効にする
settings.update_protect_branch_success=ルール "%s" に対するブランチ保護を更新しました。 settings.update_protect_branch_success=ルール "%s" に対するブランチ保護を更新しました。
@ -2437,6 +2473,7 @@ settings.tags.protection.allowed.teams=許可するチーム
settings.tags.protection.allowed.noone=なし settings.tags.protection.allowed.noone=なし
settings.tags.protection.create=タグを保護 settings.tags.protection.create=タグを保護
settings.tags.protection.none=タグは保護されていません。 settings.tags.protection.none=タグは保護されていません。
settings.tags.protection.pattern.description=ひとつのタグ名か、複数のタグにマッチするglobパターンまたは正規表現を使用できます。 詳しくは<a target="_blank" rel="noopener" href="%s">タグの保護ガイド</a> をご覧ください。
settings.bot_token=Botトークン settings.bot_token=Botトークン
settings.chat_id=チャットID settings.chat_id=チャットID
settings.thread_id=スレッドID settings.thread_id=スレッドID
@ -2651,6 +2688,7 @@ tag.create_success=タグ "%s" を作成しました。
topic.manage_topics=トピックの管理 topic.manage_topics=トピックの管理
topic.done=完了 topic.done=完了
topic.count_prompt=選択できるのは25トピックまでです。
topic.format_prompt=トピック名は英字または数字で始め、ダッシュ('-')やドット('.')を含めることができます。最大35文字までです。文字は小文字でなければなりません。 topic.format_prompt=トピック名は英字または数字で始め、ダッシュ('-')やドット('.')を含めることができます。最大35文字までです。文字は小文字でなければなりません。
find_file.go_to_file=ファイルへ移動 find_file.go_to_file=ファイルへ移動
@ -2719,7 +2757,7 @@ settings.change_orgname_redirect_prompt=古い名前は、再使用されてい
settings.update_avatar_success=組織のアバターを更新しました。 settings.update_avatar_success=組織のアバターを更新しました。
settings.delete=組織を削除 settings.delete=組織を削除
settings.delete_account=この組織を削除 settings.delete_account=この組織を削除
settings.delete_prompt=組織は恒久的に削除され、元に戻すことは<strong>できません</strong>。 続行しますか? settings.delete_prompt=組織は恒久的に削除されます。 元に戻すことは<strong>できません</strong>
settings.confirm_delete_account=削除を確認 settings.confirm_delete_account=削除を確認
settings.delete_org_title=組織の削除 settings.delete_org_title=組織の削除
settings.delete_org_desc=組織を恒久的に削除します。 続行しますか? settings.delete_org_desc=組織を恒久的に削除します。 続行しますか?
@ -2748,6 +2786,7 @@ teams.leave.detail=%s から脱退しますか?
teams.can_create_org_repo=リポジトリを作成 teams.can_create_org_repo=リポジトリを作成
teams.can_create_org_repo_helper=メンバーは組織のリポジトリを新たに作成できます。作成者には新しいリポジトリの管理者権限が与えられます。 teams.can_create_org_repo_helper=メンバーは組織のリポジトリを新たに作成できます。作成者には新しいリポジトリの管理者権限が与えられます。
teams.none_access=アクセスなし teams.none_access=アクセスなし
teams.none_access_helper=メンバーは、このユニットを表示したり他の操作を行うことはできません。 公開リポジトリには適用されません。
teams.general_access=一般的なアクセス teams.general_access=一般的なアクセス
teams.general_access_helper=メンバーの権限は下記の権限テーブルで決定されます。 teams.general_access_helper=メンバーの権限は下記の権限テーブルで決定されます。
teams.read_access=読み取り teams.read_access=読み取り
@ -2816,6 +2855,7 @@ last_page=最後
total=合計: %d total=合計: %d
settings=管理設定 settings=管理設定
dashboard.new_version_hint=Gitea %s が入手可能になりました。 現在実行しているのは %s です。 詳細は <a target="_blank" rel="noreferrer" href="%s">ブログ</a> を確認してください。
dashboard.statistic=サマリー dashboard.statistic=サマリー
dashboard.maintenance_operations=メンテナンス操作 dashboard.maintenance_operations=メンテナンス操作
dashboard.system_status=システム状況 dashboard.system_status=システム状況
@ -3007,10 +3047,12 @@ packages.size=サイズ
packages.published=配布 packages.published=配布
defaulthooks=デフォルトWebhook defaulthooks=デフォルトWebhook
defaulthooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくは<a target="_blank" rel="noopener" href="%s">Webhooksガイド</a>をご覧下さい。
defaulthooks.add_webhook=デフォルトWebhookの追加 defaulthooks.add_webhook=デフォルトWebhookの追加
defaulthooks.update_webhook=デフォルトWebhookの更新 defaulthooks.update_webhook=デフォルトWebhookの更新
systemhooks=システムWebhook systemhooks=システムWebhook
systemhooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookは、システム内のすべてのリポジトリで呼び出されます。 そのため、パフォーマンスに及ぼす影響を考慮したうえで設定してください。 詳しくは<a target="_blank" rel="noopener" href="%s">Webhooksガイド</a>をご覧下さい。
systemhooks.add_webhook=システムWebhookを追加 systemhooks.add_webhook=システムWebhookを追加
systemhooks.update_webhook=システムWebhookを更新 systemhooks.update_webhook=システムWebhookを更新
@ -3105,8 +3147,18 @@ auths.tips=ヒント
auths.tips.oauth2.general=OAuth2認証 auths.tips.oauth2.general=OAuth2認証
auths.tips.oauth2.general.tip=新しいOAuth2認証を登録するときは、コールバック/リダイレクトURLは以下になります: auths.tips.oauth2.general.tip=新しいOAuth2認証を登録するときは、コールバック/リダイレクトURLは以下になります:
auths.tip.oauth2_provider=OAuth2プロバイダー auths.tip.oauth2_provider=OAuth2プロバイダー
auths.tip.bitbucket=新しいOAuthコンシューマーを %s から登録し、"アカウント" に "読み取り" 権限を追加してください。
auths.tip.nextcloud=新しいOAuthコンシューマーを、インスタンスのメニュー "Settings -> Security -> OAuth 2.0 client" から登録してください。 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.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.tip.mastodon=認証したいMastodonインスタンスのカスタムURLを入力してください (入力しない場合はデフォルトのURLを使用します)
auths.edit=認証ソースの編集 auths.edit=認証ソースの編集
auths.activated=認証ソースはアクティベート済み auths.activated=認証ソースはアクティベート済み
@ -3272,6 +3324,7 @@ monitor.next=次回
monitor.previous=前回 monitor.previous=前回
monitor.execute_times=実行回数 monitor.execute_times=実行回数
monitor.process=実行中のプロセス monitor.process=実行中のプロセス
monitor.stacktrace=スタックトレース
monitor.processes_count=%d プロセス monitor.processes_count=%d プロセス
monitor.download_diagnosis_report=診断レポートをダウンロード monitor.download_diagnosis_report=診断レポートをダウンロード
monitor.desc=説明 monitor.desc=説明
@ -3279,6 +3332,8 @@ monitor.start=開始日時
monitor.execute_time=実行時間 monitor.execute_time=実行時間
monitor.last_execution_result=結果 monitor.last_execution_result=結果
monitor.process.cancel=処理をキャンセル monitor.process.cancel=処理をキャンセル
monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります
monitor.process.cancel_notices=キャンセル: <strong>%s</strong>?
monitor.process.children=子プロセス monitor.process.children=子プロセス
monitor.queues=キュー monitor.queues=キュー
@ -3380,6 +3435,7 @@ raw_minutes=分
[dropzone] [dropzone]
default_message=ファイルをここにドロップ、またはここをクリックしてアップロード default_message=ファイルをここにドロップ、またはここをクリックしてアップロード
invalid_input_type=この種類のファイルはアップロードできません。
file_too_big=アップロードされたファイルのサイズ ({{filesize}} MB) は、最大サイズ ({{maxFilesize}} MB) を超えています。 file_too_big=アップロードされたファイルのサイズ ({{filesize}} MB) は、最大サイズ ({{maxFilesize}} MB) を超えています。
remove_file=ファイル削除 remove_file=ファイル削除
@ -3527,6 +3583,7 @@ settings.link=このパッケージをリポジトリにリンク
settings.link.description=パッケージをリポジトリにリンクすると、リポジトリのパッケージリストに表示されるようになります。 settings.link.description=パッケージをリポジトリにリンクすると、リポジトリのパッケージリストに表示されるようになります。
settings.link.select=リポジトリを選択 settings.link.select=リポジトリを選択
settings.link.button=リポジトリのリンクを更新 settings.link.button=リポジトリのリンクを更新
settings.link.success=リポジトリのリンクが正常に更新されました。
settings.link.error=リポジトリのリンクの更新に失敗しました。 settings.link.error=リポジトリのリンクの更新に失敗しました。
settings.delete=パッケージ削除 settings.delete=パッケージ削除
settings.delete.description=パッケージの削除は恒久的で元に戻すことはできません。 settings.delete.description=パッケージの削除は恒久的で元に戻すことはできません。

View File

@ -123,6 +123,54 @@ func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chun
return chunksMap, nil 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 { func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int64, artifactName string) error {
// read all db artifacts by name // read all db artifacts by name
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{ 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) rawChecksum := hash.Sum(nil)
actualChecksum := hex.EncodeToString(rawChecksum) actualChecksum := hex.EncodeToString(rawChecksum)
if !strings.HasSuffix(checksum, actualChecksum) { 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)
} }
} }

View File

@ -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 // 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 // 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 // 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 // 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
// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
// <BlockList>
// <Latest>blockId1</Latest>
// <Latest>blockId2</Latest>
// </BlockList>
// 1.5. FinalizeArtifact // 1.5. FinalizeArtifact
// Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact // Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact
// Request // Request
@ -82,6 +89,7 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/xml"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -152,31 +160,34 @@ func ArtifactsV4Routes(prefix string) *web.Router {
return m 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 := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
mac.Write([]byte(endp)) mac.Write([]byte(endp))
mac.Write([]byte(expires)) mac.Write([]byte(expires))
mac.Write([]byte(artifactName)) mac.Write([]byte(artifactName))
mac.Write([]byte(fmt.Sprint(taskID))) mac.Write([]byte(fmt.Sprint(taskID)))
mac.Write([]byte(fmt.Sprint(artifactID)))
return mac.Sum(nil) 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") 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, "/") + 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 return uploadURL
} }
func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) { func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) {
rawTaskID := ctx.Req.URL.Query().Get("taskID") rawTaskID := ctx.Req.URL.Query().Get("taskID")
rawArtifactID := ctx.Req.URL.Query().Get("artifactID")
sig := ctx.Req.URL.Query().Get("sig") sig := ctx.Req.URL.Query().Get("sig")
expires := ctx.Req.URL.Query().Get("expires") expires := ctx.Req.URL.Query().Get("expires")
artifactName := ctx.Req.URL.Query().Get("artifactName") artifactName := ctx.Req.URL.Query().Get("artifactName")
dsig, _ := base64.URLEncoding.DecodeString(sig) dsig, _ := base64.URLEncoding.DecodeString(sig)
taskID, _ := strconv.ParseInt(rawTaskID, 10, 64) 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) { if !hmac.Equal(dsig, expecedsig) {
log.Error("Error unauthorized") log.Error("Error unauthorized")
ctx.Error(http.StatusUnauthorized, "Error unauthorized") ctx.Error(http.StatusUnauthorized, "Error unauthorized")
@ -271,6 +282,8 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
return return
} }
artifact.ContentEncoding = ArtifactV4ContentEncoding artifact.ContentEncoding = ArtifactV4ContentEncoding
artifact.FileSize = 0
artifact.FileCompressedSize = 0
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil {
log.Error("Error UpdateArtifactByID: %v", err) log.Error("Error UpdateArtifactByID: %v", err)
ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
@ -279,7 +292,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
respData := CreateArtifactResponse{ respData := CreateArtifactResponse{
Ok: true, 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) r.sendProtbufBody(ctx, &respData)
} }
@ -293,6 +306,8 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
comp := ctx.Req.URL.Query().Get("comp") comp := ctx.Req.URL.Query().Get("comp")
switch comp { switch comp {
case "block", "appendBlock": case "block", "appendBlock":
blockid := ctx.Req.URL.Query().Get("blockid")
if blockid == "" {
// get artifact by name // get artifact by name
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName)
if err != nil { if err != nil {
@ -301,11 +316,6 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
return return
} }
if comp == "block" {
artifact.FileSize = 0
artifact.FileCompressedSize = 0
}
_, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID)
if err != nil { if err != nil {
log.Error("Error runner api getting task: task is not running") log.Error("Error runner api getting task: task is not running")
@ -319,12 +329,54 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID")
return 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
}
}
ctx.JSON(http.StatusCreated, "appended") ctx.JSON(http.StatusCreated, "appended")
case "blocklist": 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
}
ctx.JSON(http.StatusCreated, "created") 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) { func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
var req FinalizeArtifactRequest var req FinalizeArtifactRequest
@ -343,18 +395,34 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
ctx.Error(http.StatusNotFound, "Error artifact not found") ctx.Error(http.StatusNotFound, "Error artifact not found")
return return
} }
var chunks []*chunkFileItem
blockList, err := r.readBlockList(runID, artifact.ID)
if err != nil {
log.Warn("Failed to read BlockList, fallback to old behavior: %v", err)
chunkMap, err := listChunksByRunID(r.fs, runID) chunkMap, err := listChunksByRunID(r.fs, runID)
if err != nil { if err != nil {
log.Error("Error merge chunks: %v", err) log.Error("Error merge chunks: %v", err)
ctx.Error(http.StatusInternalServerError, "Error merge chunks") ctx.Error(http.StatusInternalServerError, "Error merge chunks")
return return
} }
chunks, ok := chunkMap[artifact.ID] chunks, ok = chunkMap[artifact.ID]
if !ok { if !ok {
log.Error("Error merge chunks") log.Error("Error merge chunks")
ctx.Error(http.StatusInternalServerError, "Error merge chunks") ctx.Error(http.StatusInternalServerError, "Error merge chunks")
return 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 := "" checksum := ""
if req.Hash != nil { if req.Hash != nil {
checksum = req.Hash.Value checksum = req.Hash.Value
@ -455,7 +523,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
} }
} }
if respData.SignedUrl == "" { 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) r.sendProtbufBody(ctx, &respData)
} }

View File

@ -20,6 +20,7 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" 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/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" 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...) xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
latest := pds[len(pds)-1] 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)) ext := strings.ToLower(filepath.Ext(params.Filename))
if isChecksumExtension(ext) { if isChecksumExtension(ext) {
@ -223,6 +226,10 @@ func servePackageFile(ctx *context.Context, params parameters, serveContent bool
helper.ServePackageFile(ctx, s, u, pf, opts) 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. // UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
func UploadPackageFile(ctx *context.Context) { func UploadPackageFile(ctx *context.Context) {
params, err := extractPathParameters(ctx) params, err := extractPathParameters(ctx)
@ -241,6 +248,14 @@ func UploadPackageFile(ctx *context.Context) {
packageName := params.GroupID + "-" + params.ArtifactID 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) buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body)
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)

View File

@ -1286,6 +1286,8 @@ func Routes() *web.Router {
m.Group("/{ref}", func() { m.Group("/{ref}", func() {
m.Get("/status", repo.GetCombinedCommitStatusByRef) m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef) m.Get("/statuses", repo.GetCommitStatusesByRef)
}, context.ReferencesGitRepo())
m.Group("/{sha}", func() {
m.Get("/pull", repo.GetCommitPullRequest) m.Get("/pull", repo.GetCommitPullRequest)
}, context.ReferencesGitRepo()) }, context.ReferencesGitRepo())
}, reqRepoReader(unit.TypeCode)) }, reqRepoReader(unit.TypeCode))

View File

@ -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) { func GetCommitPullRequest(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/pull repository repoGetCommitPullRequest // 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: // produces:
// - application/json // - application/json
// parameters: // parameters:
@ -354,7 +354,7 @@ func GetCommitPullRequest(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$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 err != nil {
if issues_model.IsErrPullRequestNotExist(err) { if issues_model.IsErrPullRequestNotExist(err) {
ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err) ctx.Error(http.StatusNotFound, "GetPullRequestByMergedCommit", err)

View File

@ -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-Type", contentType)
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) 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) http.ServeFile(ctx.Resp, ctx.Req, reqFile)
} }

View File

@ -467,6 +467,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
ctx.Data["AssigneeID"] = assigneeID ctx.Data["AssigneeID"] = assigneeID
ctx.Data["PosterID"] = posterID ctx.Data["PosterID"] = posterID
ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword
ctx.Data["IsShowClosed"] = isShowClosed
switch { switch {
case isShowClosed.Value(): case isShowClosed.Value():
ctx.Data["State"] = "closed" ctx.Data["State"] = "closed"

View File

@ -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["HeadTarget"] = pull.MustHeadUserName(ctx) + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
} }
ctx.Data["BaseTarget"] = pull.BaseBranch 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) ctx.Data["BaseBranchLink"] = pull.GetBaseBranchLink(ctx)
} }

View File

@ -1069,9 +1069,7 @@ func registerRoutes(m *web.Router) {
m.Combo("/edit").Get(repo_setting.SettingsProtectedBranch). m.Combo("/edit").Get(repo_setting.SettingsProtectedBranch).
Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.SettingsProtectedBranchPost) Post(web.Bind(forms.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo_setting.SettingsProtectedBranchPost)
m.Post("/{id}/delete", repo_setting.DeleteProtectedBranchRulePost) 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.Group("/tags", func() {
m.Get("", repo_setting.ProtectedTags) m.Get("", repo_setting.ProtectedTags)
@ -1304,6 +1302,7 @@ func registerRoutes(m *web.Router) {
}, web.Bind(forms.NewBranchForm{})) }, web.Bind(forms.NewBranchForm{}))
m.Post("/delete", repo.DeleteBranchPost) m.Post("/delete", repo.DeleteBranchPost)
m.Post("/restore", repo.RestoreBranchPost) m.Post("/restore", repo.RestoreBranchPost)
m.Post("/rename", web.Bind(forms.RenameBranchForm{}), repo_setting.RenameBranchPost)
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)

View File

@ -483,13 +483,12 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
} }
rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
if err != nil { if err != nil && !git_model.IsErrBranchNotExist(err) {
return fmt.Errorf("GetBranch: %vc", err) return fmt.Errorf("GetBranch: %vc", err)
} }
if rawBranch.IsDeleted { // database branch record not exist or it's a deleted branch
return nil notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted
}
commit, err := gitRepo.GetBranchCommit(branchName) commit, err := gitRepo.GetBranchCommit(branchName)
if err != nil { if err != nil {
@ -497,9 +496,11 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
} }
if err := db.WithTx(ctx, func(ctx context.Context) error { if err := db.WithTx(ctx, func(ctx context.Context) error {
if !notExist {
if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
return err return err
} }
}
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
Force: true, Force: true,

View File

@ -11,6 +11,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8"
webhook_model "code.gitea.io/gitea/models/webhook" webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -154,8 +155,14 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
var text string var text string
// for each commit, generate attachment text // for each commit, generate attachment text
for i, commit := range p.Commits { for i, commit := range p.Commits {
text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, // limit the commit message display to just the summary, otherwise it would be hard to read
strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name) 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 // add linebreak to each commit but the last
if i < len(p.Commits)-1 { if i < len(p.Commits)-1 {
text += "\n" text += "\n"

View File

@ -80,6 +80,20 @@ func TestDiscordPayload(t *testing.T) {
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) 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) { t.Run("Issue", func(t *testing.T) {
p := issueTestPayload() p := issueTestPayload()

View File

@ -64,9 +64,17 @@ func forkTestPayload() *api.ForkPayload {
} }
func pushTestPayload() *api.PushPayload { 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{ commit := &api.PayloadCommit{
ID: "2020558fe2e34debb818a514715839cabd25e778", ID: "2020558fe2e34debb818a514715839cabd25e778",
Message: "commit message", Message: message,
URL: "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778", URL: "http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778",
Author: &api.PayloadUser{ Author: &api.PayloadUser{
Name: "user1", Name: "user1",

View File

@ -240,7 +240,7 @@
<div class="header"> <div class="header">
{{ctx.Locale.Tr "repo.settings.rename_branch"}} {{ctx.Locale.Tr "repo.settings.rename_branch"}}
</div> </div>
<form class="ui form" action="{{$.Repository.Link}}/settings/rename_branch" method="post"> <form class="ui form" action="{{$.Repository.Link}}/branches/rename" method="post">
<div class="content"> <div class="content">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="field default-branch-warning"> <div class="field default-branch-warning">

View File

@ -1,9 +1,9 @@
<div class="ui secondary filter menu"> <div class="ui secondary filter menu">
{{if not .Repository.IsArchived}} {{if not .Repository.IsArchived}}
<!-- Action Button --> <!-- Action Button -->
{{if .IsShowClosed}} {{if and .IsShowClosed.Has .IsShowClosed.Value}}
<button class="ui primary basic button issue-action" data-action="open" data-url="{{$.RepoLink}}/issues/status">{{ctx.Locale.Tr "repo.issues.action_open"}}</button> <button class="ui primary basic button issue-action" data-action="open" data-url="{{$.RepoLink}}/issues/status">{{ctx.Locale.Tr "repo.issues.action_open"}}</button>
{{else}} {{else if and .IsShowClosed.Has (not .IsShowClosed.Value)}}
<button class="ui red basic button issue-action" data-action="close" data-url="{{$.RepoLink}}/issues/status">{{ctx.Locale.Tr "repo.issues.action_close"}}</button> <button class="ui red basic button issue-action" data-action="close" data-url="{{$.RepoLink}}/issues/status">{{ctx.Locale.Tr "repo.issues.action_close"}}</button>
{{end}} {{end}}
{{if $.IsRepoAdmin}} {{if $.IsRepoAdmin}}

View File

@ -109,7 +109,7 @@
</a> </a>
</div> </div>
<div class="tw-flex tw-items-center tw-gap-2"> <div class="tw-flex tw-items-center tw-gap-2">
<span {{if .Review.TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .Review.TooltipContent}}"{{end}}> <span {{if .TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .TooltipContent}}"{{end}}>
{{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}} {{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
</span> </span>
</div> </div>

View File

@ -50,9 +50,14 @@
{{if .Issue.IsPull}} {{if .Issue.IsPull}}
{{$headHref := .HeadTarget}} {{$headHref := .HeadTarget}}
{{if .HeadBranchLink}} {{if .HeadBranchLink}}
{{$headHref = HTMLFormat `<a href="%s">%s</a>` .HeadBranchLink $headHref}} {{$headHref = HTMLFormat `<a href="%s">%s</a> <button class="btn interact-fg" data-tooltip-content="%s" data-clipboard-text="%s">%s</button>` .HeadBranchLink $headHref (ctx.Locale.Tr "copy_branch") .HeadTarget (svg "octicon-copy" 14)}}
{{else}}
{{if .Issue.PullRequest.IsAgitFlow}}
{{$headHref = HTMLFormat `%s <a href="%s" target="_blank"><span class="ui label basic tiny" data-tooltip-content="%s">AGit</span></a>` $headHref "https://docs.gitea.com/usage/agit" (ctx.Locale.Tr "repo.pull.agit_documentation")}}
{{else}}
{{$headHref = HTMLFormat `<span data-tooltip-content="%s">%s</span>` (ctx.Locale.Tr "form.target_branch_not_exist") $headHref}}
{{end}}
{{end}} {{end}}
{{$headHref = HTMLFormat `%s <button class="btn interact-fg" data-tooltip-content="%s" data-clipboard-text="%s">%s</button>` $headHref (ctx.Locale.Tr "copy_branch") .HeadTarget (svg "octicon-copy" 14)}}
{{$baseHref := .BaseTarget}} {{$baseHref := .BaseTarget}}
{{if .BaseBranchLink}} {{if .BaseBranchLink}}
{{$baseHref = HTMLFormat `<a href="%s">%s</a>` .BaseBranchLink $baseHref}} {{$baseHref = HTMLFormat `<a href="%s">%s</a>` .BaseBranchLink $baseHref}}

View File

@ -15,7 +15,6 @@
<form class="tw-flex" action="{{.Link}}" method="post"> <form class="tw-flex" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="action" value="default_branch"> <input type="hidden" name="action" value="default_branch">
{{if not .Repository.IsEmpty}}
<div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96"> <div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}"> <input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
@ -26,8 +25,7 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button> <button class="ui primary button"{{if .Repository.IsEmpty}} disabled{{end}}>{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
{{end}}
</form> </form>
</div> </div>

View File

@ -13,11 +13,9 @@
</a> </a>
{{end}} {{end}}
{{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeCode}} {{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeCode}}
{{if not .Repository.IsEmpty}}
<a class="{{if .PageIsSettingsBranches}}active {{end}}item" href="{{.RepoLink}}/settings/branches"> <a class="{{if .PageIsSettingsBranches}}active {{end}}item" href="{{.RepoLink}}/settings/branches">
{{ctx.Locale.Tr "repo.settings.branches"}} {{ctx.Locale.Tr "repo.settings.branches"}}
</a> </a>
{{end}}
<a class="{{if .PageIsSettingsTags}}active {{end}}item" href="{{.RepoLink}}/settings/tags"> <a class="{{if .PageIsSettingsTags}}active {{end}}item" href="{{.RepoLink}}/settings/tags">
{{ctx.Locale.Tr "repo.settings.tags"}} {{ctx.Locale.Tr "repo.settings.tags"}}
</a> </a>

View File

@ -5446,7 +5446,7 @@
"tags": [ "tags": [
"repository" "repository"
], ],
"summary": "Get the pull request of the commit", "summary": "Get the merged pull request of the commit",
"operationId": "repoGetCommitPullRequest", "operationId": "repoGetCommitPullRequest",
"parameters": [ "parameters": [
{ {

View File

@ -7,12 +7,14 @@ import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/xml"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"time" "time"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/routers/api/actions" "code.gitea.io/gitea/routers/api/actions"
actions_service "code.gitea.io/gitea/services/actions" actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -170,6 +172,134 @@ func TestActionsArtifactV4UploadSingleFileWithRetentionDays(t *testing.T) {
assert.True(t, finalizeResp.Ok) assert.True(t, finalizeResp.Ok)
} }
func TestActionsArtifactV4UploadSingleFileWithPotentialHarmfulBlockID(t *testing.T) {
defer tests.PrepareTestEnv(t)()
token, err := actions_service.CreateAuthorizationToken(48, 792, 193)
assert.NoError(t, err)
// acquire artifact upload url
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{
Version: 4,
Name: "artifactWithPotentialHarmfulBlockID",
WorkflowRunBackendId: "792",
WorkflowJobRunBackendId: "193",
})).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var uploadResp actions.CreateArtifactResponse
protojson.Unmarshal(resp.Body.Bytes(), &uploadResp)
assert.True(t, uploadResp.Ok)
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
// get upload urls
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
url := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=%2f..%2fmyfile"
blockListURL := uploadResp.SignedUploadUrl[idx:] + "&comp=blocklist"
// upload artifact chunk
body := strings.Repeat("A", 1024)
req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body))
MakeRequest(t, req, http.StatusCreated)
// verify that the exploit didn't work
_, err = storage.Actions.Stat("myfile")
assert.Error(t, err)
// upload artifact blockList
blockList := &actions.BlockList{
Latest: []string{
"/../myfile",
},
}
rawBlockList, err := xml.Marshal(blockList)
assert.NoError(t, err)
req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList))
MakeRequest(t, req, http.StatusCreated)
t.Logf("Create artifact confirm")
sha := sha256.Sum256([]byte(body))
// confirm artifact upload
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{
Name: "artifactWithPotentialHarmfulBlockID",
Size: 1024,
Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])),
WorkflowRunBackendId: "792",
WorkflowJobRunBackendId: "193",
})).
AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
var finalizeResp actions.FinalizeArtifactResponse
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
assert.True(t, finalizeResp.Ok)
}
func TestActionsArtifactV4UploadSingleFileWithChunksOutOfOrder(t *testing.T) {
defer tests.PrepareTestEnv(t)()
token, err := actions_service.CreateAuthorizationToken(48, 792, 193)
assert.NoError(t, err)
// acquire artifact upload url
req := NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact", toProtoJSON(&actions.CreateArtifactRequest{
Version: 4,
Name: "artifactWithChunksOutOfOrder",
WorkflowRunBackendId: "792",
WorkflowJobRunBackendId: "193",
})).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var uploadResp actions.CreateArtifactResponse
protojson.Unmarshal(resp.Body.Bytes(), &uploadResp)
assert.True(t, uploadResp.Ok)
assert.Contains(t, uploadResp.SignedUploadUrl, "/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact")
// get upload urls
idx := strings.Index(uploadResp.SignedUploadUrl, "/twirp/")
block1URL := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=block1"
block2URL := uploadResp.SignedUploadUrl[idx:] + "&comp=block&blockid=block2"
blockListURL := uploadResp.SignedUploadUrl[idx:] + "&comp=blocklist"
// upload artifact chunks
bodyb := strings.Repeat("B", 1024)
req = NewRequestWithBody(t, "PUT", block2URL, strings.NewReader(bodyb))
MakeRequest(t, req, http.StatusCreated)
bodya := strings.Repeat("A", 1024)
req = NewRequestWithBody(t, "PUT", block1URL, strings.NewReader(bodya))
MakeRequest(t, req, http.StatusCreated)
// upload artifact blockList
blockList := &actions.BlockList{
Latest: []string{
"block1",
"block2",
},
}
rawBlockList, err := xml.Marshal(blockList)
assert.NoError(t, err)
req = NewRequestWithBody(t, "PUT", blockListURL, bytes.NewReader(rawBlockList))
MakeRequest(t, req, http.StatusCreated)
t.Logf("Create artifact confirm")
sha := sha256.Sum256([]byte(bodya + bodyb))
// confirm artifact upload
req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact", toProtoJSON(&actions.FinalizeArtifactRequest{
Name: "artifactWithChunksOutOfOrder",
Size: 2048,
Hash: wrapperspb.String("sha256:" + hex.EncodeToString(sha[:])),
WorkflowRunBackendId: "792",
WorkflowJobRunBackendId: "193",
})).
AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
var finalizeResp actions.FinalizeArtifactResponse
protojson.Unmarshal(resp.Body.Bytes(), &finalizeResp)
assert.True(t, finalizeResp.Ok)
}
func TestActionsArtifactV4DownloadSingle(t *testing.T) { func TestActionsArtifactV4DownloadSingle(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()

View File

@ -36,6 +36,7 @@ func TestPackageComposer(t *testing.T) {
packageType := "composer-plugin" packageType := "composer-plugin"
packageAuthor := "Gitea Authors" packageAuthor := "Gitea Authors"
packageLicense := "MIT" packageLicense := "MIT"
packageBin := "./bin/script"
var buf bytes.Buffer var buf bytes.Buffer
archive := zip.NewWriter(&buf) archive := zip.NewWriter(&buf)
@ -49,6 +50,9 @@ func TestPackageComposer(t *testing.T) {
{ {
"name": "` + packageAuthor + `" "name": "` + packageAuthor + `"
} }
],
"bin": [
"` + packageBin + `"
] ]
}`)) }`))
archive.Close() archive.Close()
@ -210,6 +214,8 @@ func TestPackageComposer(t *testing.T) {
assert.Len(t, pkgs[0].Authors, 1) assert.Len(t, pkgs[0].Authors, 1)
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
assert.Equal(t, "zip", pkgs[0].Dist.Type) assert.Equal(t, "zip", pkgs[0].Dist.Type)
assert.Equal(t, "7b40bfd6da811b2b78deec1e944f156dbb2c747b", pkgs[0].Dist.Checksum) assert.Equal(t, "4f5fa464c3cb808a1df191dbf6cb75363f8b7072", pkgs[0].Dist.Checksum)
assert.Len(t, pkgs[0].Bin, 1)
assert.Equal(t, packageBin, pkgs[0].Bin[0])
}) })
} }

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"sync"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -252,3 +253,35 @@ func TestPackageMaven(t *testing.T) {
assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
}) })
} }
func TestPackageMavenConcurrent(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
groupID := "com.gitea"
artifactID := "test-project"
packageVersion := "1.0.1"
root := fmt.Sprintf("/api/packages/%s/maven/%s/%s", user.Name, strings.ReplaceAll(groupID, ".", "/"), artifactID)
putFile := func(t *testing.T, path, content string, expectedStatus int) {
req := NewRequestWithBody(t, "PUT", root+path, strings.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, expectedStatus)
}
t.Run("Concurrent Upload", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
putFile(t, fmt.Sprintf("/%s/%s.jar", packageVersion, strconv.Itoa(i)), "test", http.StatusCreated)
wg.Done()
}(i)
}
wg.Wait()
})
}

View File

@ -334,3 +334,19 @@ func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*t
} }
} }
} }
func TestAPICommitPullRequest(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository)
mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3"
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token)
ctx.Session.MakeRequest(t, req, http.StatusOK)
invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234"
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token)
ctx.Session.MakeRequest(t, req, http.StatusNotFound)
}

View File

@ -28,11 +28,11 @@ func testRenameBranch(t *testing.T, u *url.URL) {
// get branch setting page // get branch setting page
session := loginUser(t, "user2") session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/settings/branches") req := NewRequest(t, "GET", "/user2/repo1/branches")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", map[string]string{ req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/rename", map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"from": "master", "from": "master",
"to": "main", "to": "main",
@ -76,7 +76,7 @@ func testRenameBranch(t *testing.T, u *url.URL) {
assert.Equal(t, "branch2", branch2.Name) assert.Equal(t, "branch2", branch2.Name)
// rename branch2 to branch1 // rename branch2 to branch1
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", map[string]string{ req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/rename", map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"from": "branch2", "from": "branch2",
"to": "branch1", "to": "branch1",
@ -103,7 +103,7 @@ func testRenameBranch(t *testing.T, u *url.URL) {
assert.True(t, branch1.IsDeleted) // virtual deletion assert.True(t, branch1.IsDeleted) // virtual deletion
// rename branch2 to branch1 again // rename branch2 to branch1 again
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/rename_branch", map[string]string{ req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/rename", map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"from": "branch2", "from": "branch2",
"to": "branch1", "to": "branch1",

View File

@ -78,7 +78,6 @@ const sfc = {
searchURL() { searchURL() {
return `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery return `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery
}&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode }&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode
}${this.reposFilter !== 'all' ? '&exclusive=1' : ''
}${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : '' }${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
}${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : '' }${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : ''
}`; }`;