mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into xormigrate
This commit is contained in:
commit
3bad0647c5
|
@ -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:
|
||||
|
|
|
@ -198,11 +198,12 @@ 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:
|
||||
# some images before 2024-04 can't run on new kernels
|
||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||
image: mcr.microsoft.com/mssql/server:2017-latest
|
||||
env:
|
||||
ACCEPT_EULA: Y
|
||||
MSSQL_PID: Standard
|
||||
|
|
11
cmd/serv.go
11
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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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`)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
.zig-cache/
|
||||
zig-out/
|
|
@ -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.
|
|
@ -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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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 のバグが疑われる場合は、<a href="%s" target="_blank">GitHub</a>でIssueを検索して、見つからなければ新しいIssueを作成してください。
|
||||
not_found=ターゲットが見つかりませんでした。
|
||||
network_error=ネットワークエラー
|
||||
|
||||
[startpage]
|
||||
app_desc=自分で立てる、超簡単 Git サービス
|
||||
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_desc=Giteaは<a target="_blank" rel="noopener noreferrer" href="%s">Go</a>がコンパイル可能なあらゆる環境で動きます: Windows、macOS、Linux、ARMなど。 あなたの好きなものを選んでください!
|
||||
lightweight=軽量
|
||||
lightweight_desc=Gitea の最小動作要件は小さいため、安価な Raspberry Pi でも動きます。エネルギーを節約しましょう!
|
||||
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=インストール
|
||||
|
@ -450,6 +457,7 @@ authorize_title=`"%s"にあなたのアカウントへのアクセスを許可
|
|||
authorization_failed=認可失敗
|
||||
authorization_failed_desc=無効なリクエストを検出したため認可が失敗しました。 認可しようとしたアプリの開発者に連絡してください。
|
||||
sspi_auth_failed=SSPI認証に失敗しました
|
||||
password_pwned=あなたが選択したパスワードは、過去の情報漏洩事件で流出した<a target="_blank" rel="noopener noreferrer" href="%s">盗まれたパスワードのリスト</a>に含まれています。 別のパスワードでもう一度試してください。 また他の登録でもこのパスワードからの変更を検討してください。
|
||||
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要素認証に使用できます。 セキュリティキーは<a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a>規格をサポートしている必要があります。
|
||||
webauthn_register_key=セキュリティキーを追加
|
||||
webauthn_nickname=ニックネーム
|
||||
webauthn_delete_key=セキュリティキーの登録解除
|
||||
|
@ -967,7 +977,7 @@ orgs_none=あなたはどの組織のメンバーでもありません。
|
|||
repos_none=あなたはリポジトリを所有していません。
|
||||
|
||||
delete_account=アカウントを削除
|
||||
delete_prompt=この操作により、あなたのユーザーアカウントは恒久的に抹消されます。 取り消すことは<strong>できません</strong>。
|
||||
delete_prompt=この操作により、あなたのユーザーアカウントは恒久的に抹消されます。 元に戻すことは<strong>できません</strong>。
|
||||
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=<strong>%d%%</strong>消化
|
||||
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パターンで指定するホワイトリストです。 空か<code>*</code>のときは、すべてのブランチのイベントを通知します。 文法については <a href="%[1]s">%[2]s</a> を参照してください。 例: <code>master</code> 、 <code>{master,release*}</code>
|
||||
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=ブランチ '<b>%s</b>' の保護ルール
|
||||
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=保護ブランチ名のパターン。書き方については <a href="%s">ドキュメント</a> を参照してください。例: main, release/**
|
||||
settings.protect_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_desc=保護しないファイルは、ユーザーに書き込み権限があればプッシュ制限をバイパスして直接変更できます。 セミコロン(';')で区切って複数のパターンを指定できます。 パターンの文法については <a href='%[1]s'>%[2]s</a> を参照してください。 例: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
|
||||
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パターンまたは正規表現を使用できます。 詳しくは<a target="_blank" rel="noopener" href="%s">タグの保護ガイド</a> をご覧ください。
|
||||
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=組織は恒久的に削除され、元に戻すことは<strong>できません</strong>。 続行しますか?
|
||||
settings.delete_prompt=組織は恒久的に削除されます。 元に戻すことは<strong>できません</strong>!
|
||||
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 です。 詳細は <a target="_blank" rel="noreferrer" href="%s">ブログ</a> を確認してください。
|
||||
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はデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくは<a target="_blank" rel="noopener" href="%s">Webhooksガイド</a>をご覧下さい。
|
||||
defaulthooks.add_webhook=デフォルトWebhookの追加
|
||||
defaulthooks.update_webhook=デフォルトWebhookの更新
|
||||
|
||||
systemhooks=システムWebhook
|
||||
systemhooks.desc=Webhookは、特定のGiteaイベントが発生したときに、サーバーにHTTP POSTリクエストを自動的に送信するものです。 ここで定義したWebhookは、システム内のすべてのリポジトリで呼び出されます。 そのため、パフォーマンスに及ぼす影響を考慮したうえで設定してください。 詳しくは<a target="_blank" rel="noopener" href="%s">Webhooksガイド</a>をご覧下さい。
|
||||
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=キャンセル: <strong>%s</strong>?
|
||||
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=パッケージの削除は恒久的で元に戻すことはできません。
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
// <BlockList>
|
||||
// <Latest>blockId1</Latest>
|
||||
// <Latest>blockId2</Latest>
|
||||
// </BlockList>
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1286,6 +1286,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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
if err != nil {
|
||||
if err != nil && !git_model.IsErrBranchNotExist(err) {
|
||||
return fmt.Errorf("GetBranch: %vc", err)
|
||||
}
|
||||
|
||||
if rawBranch.IsDeleted {
|
||||
return nil
|
||||
}
|
||||
// database branch record not exist or it's a deleted branch
|
||||
notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted
|
||||
|
||||
commit, err := gitRepo.GetBranchCommit(branchName)
|
||||
if err != nil {
|
||||
|
@ -497,8 +496,10 @@ 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 := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
|
||||
return err
|
||||
if !notExist {
|
||||
if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -240,7 +240,7 @@
|
|||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.settings.rename_branch"}}
|
||||
</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">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="field default-branch-warning">
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<div class="ui secondary filter menu">
|
||||
{{if not .Repository.IsArchived}}
|
||||
<!-- 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>
|
||||
{{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>
|
||||
{{end}}
|
||||
{{if $.IsRepoAdmin}}
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<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))}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -50,9 +50,14 @@
|
|||
{{if .Issue.IsPull}}
|
||||
{{$headHref := .HeadTarget}}
|
||||
{{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}}
|
||||
{{$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}}
|
||||
{{if .BaseBranchLink}}
|
||||
{{$baseHref = HTMLFormat `<a href="%s">%s</a>` .BaseBranchLink $baseHref}}
|
||||
|
|
|
@ -15,19 +15,17 @@
|
|||
<form class="tw-flex" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<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">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
|
||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
|
||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
<button class="ui primary button"{{if .Repository.IsEmpty}} disabled{{end}}>{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -13,11 +13,9 @@
|
|||
</a>
|
||||
{{end}}
|
||||
{{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeCode}}
|
||||
{{if not .Repository.IsEmpty}}
|
||||
<a class="{{if .PageIsSettingsBranches}}active {{end}}item" href="{{.RepoLink}}/settings/branches">
|
||||
{{ctx.Locale.Tr "repo.settings.branches"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="{{if .PageIsSettingsBranches}}active {{end}}item" href="{{.RepoLink}}/settings/branches">
|
||||
{{ctx.Locale.Tr "repo.settings.branches"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSettingsTags}}active {{end}}item" href="{{.RepoLink}}/settings/tags">
|
||||
{{ctx.Locale.Tr "repo.settings.tags"}}
|
||||
</a>
|
||||
|
|
|
@ -5446,7 +5446,7 @@
|
|||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Get the pull request of the commit",
|
||||
"summary": "Get the merged pull request of the commit",
|
||||
"operationId": "repoGetCommitPullRequest",
|
||||
"parameters": [
|
||||
{
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/routers/api/actions"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
@ -170,6 +172,134 @@ func TestActionsArtifactV4UploadSingleFileWithRetentionDays(t *testing.T) {
|
|||
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) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ func TestPackageComposer(t *testing.T) {
|
|||
packageType := "composer-plugin"
|
||||
packageAuthor := "Gitea Authors"
|
||||
packageLicense := "MIT"
|
||||
packageBin := "./bin/script"
|
||||
|
||||
var buf bytes.Buffer
|
||||
archive := zip.NewWriter(&buf)
|
||||
|
@ -49,6 +50,9 @@ func TestPackageComposer(t *testing.T) {
|
|||
{
|
||||
"name": "` + packageAuthor + `"
|
||||
}
|
||||
],
|
||||
"bin": [
|
||||
"` + packageBin + `"
|
||||
]
|
||||
}`))
|
||||
archive.Close()
|
||||
|
@ -210,6 +214,8 @@ func TestPackageComposer(t *testing.T) {
|
|||
assert.Len(t, pkgs[0].Authors, 1)
|
||||
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
|
||||
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])
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -252,3 +253,35 @@ func TestPackageMaven(t *testing.T) {
|
|||
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()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ func testRenameBranch(t *testing.T, u *url.URL) {
|
|||
|
||||
// get branch setting page
|
||||
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)
|
||||
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(),
|
||||
"from": "master",
|
||||
"to": "main",
|
||||
|
@ -76,7 +76,7 @@ func testRenameBranch(t *testing.T, u *url.URL) {
|
|||
assert.Equal(t, "branch2", branch2.Name)
|
||||
|
||||
// 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(),
|
||||
"from": "branch2",
|
||||
"to": "branch1",
|
||||
|
@ -103,7 +103,7 @@ func testRenameBranch(t *testing.T, u *url.URL) {
|
|||
assert.True(t, branch1.IsDeleted) // virtual deletion
|
||||
|
||||
// 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(),
|
||||
"from": "branch2",
|
||||
"to": "branch1",
|
||||
|
|
|
@ -78,7 +78,6 @@ const sfc = {
|
|||
searchURL() {
|
||||
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
|
||||
}${this.reposFilter !== 'all' ? '&exclusive=1' : ''
|
||||
}${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
|
||||
}${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : ''
|
||||
}`;
|
||||
|
|
Loading…
Reference in New Issue