mirror of
https://github.com/go-gitea/gitea.git
synced 2024-09-01 14:56:30 +00:00
Merge branch 'main' into lunny/lock_per_package
This commit is contained in:
commit
c34cc96f92
3
go.mod
3
go.mod
@ -49,6 +49,7 @@ require (
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-swagger/go-swagger v0.31.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.11.0
|
||||
@ -218,7 +219,9 @@ require (
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
|
19
go.sum
19
go.sum
@ -342,6 +342,14 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
|
||||
github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-redsync/redsync/v4 v4.13.0 h1:49X6GJfnbLGaIpBBREM/zA4uIMDXKAh1NDkvQ1EkZKA=
|
||||
github.com/go-redsync/redsync/v4 v4.13.0/go.mod h1:HMW4Q224GZQz6x1Xc7040Yfgacukdzu7ifTDAKiyErQ=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
@ -397,6 +405,8 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@ -449,10 +459,15 @@ github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@ -674,6 +689,8 @@ github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKc
|
||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.6.0 h1:NLck+Rab3AOTHw21CGRpvQpgTrAU4sgdCswqGtlhGRA=
|
||||
github.com/redis/go-redis/v9 v9.6.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
|
||||
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rhysd/actionlint v1.7.1 h1:WJaDzyT1StBWVKGSsZPYnbV0HF9Y9/vD6KFdZQL42qE=
|
||||
@ -765,6 +782,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
|
@ -28,7 +28,6 @@ func Test_SSHParsePublicKey(t *testing.T) {
|
||||
length int
|
||||
content string
|
||||
}{
|
||||
{"dsa-1024", false, "dsa", 1024, "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"},
|
||||
{"rsa-1024", false, "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
|
||||
{"rsa-2048", false, "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
|
||||
{"ecdsa-256", false, "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
|
||||
@ -172,7 +171,6 @@ func Test_calcFingerprint(t *testing.T) {
|
||||
fp string
|
||||
content string
|
||||
}{
|
||||
{"dsa-1024", false, "SHA256:fSIHQlpKMDsGPVAXI8BPYfRp+e2sfvSt1sMrPsFiXrc", "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"},
|
||||
{"rsa-1024", false, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"},
|
||||
{"rsa-2048", false, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"},
|
||||
{"ecdsa-256", false, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"},
|
||||
|
@ -26,7 +26,7 @@
|
||||
fork_id: 0
|
||||
is_template: false
|
||||
template_id: 0
|
||||
size: 7320
|
||||
size: 7597
|
||||
is_fsck_enabled: true
|
||||
close_issues_via_commit_in_any_branch: false
|
||||
|
||||
|
@ -599,6 +599,8 @@ var migrations = []Migration{
|
||||
NewMigration("Add index to action_task stopped log_expired", v1_23.AddIndexToActionTaskStoppedLogExpired),
|
||||
// v303 -> v304
|
||||
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
|
||||
// v304 -> v305
|
||||
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
13
models/migrations/v1_23/v304.go
Normal file
13
models/migrations/v1_23/v304.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddIndexForReleaseSha1(x *xorm.Engine) error {
|
||||
type Release struct {
|
||||
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
||||
}
|
||||
return x.Sync(new(Release))
|
||||
}
|
@ -77,7 +77,7 @@ type Release struct {
|
||||
Target string
|
||||
TargetBehind string `xorm:"-"` // to handle non-existing or empty target
|
||||
Title string
|
||||
Sha1 string `xorm:"VARCHAR(64)"`
|
||||
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
||||
NumCommits int64
|
||||
NumCommitsBehind int64 `xorm:"-"`
|
||||
Note string `xorm:"TEXT"`
|
||||
@ -537,3 +537,17 @@ func InsertReleases(ctx context.Context, rels ...*Release) error {
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func FindTagsByCommitIDs(ctx context.Context, repoID int64, commitIDs ...string) (map[string][]*Release, error) {
|
||||
releases := make([]*Release, 0, len(commitIDs))
|
||||
if err := db.GetEngine(ctx).Where("repo_id=?", repoID).
|
||||
In("sha1", commitIDs).
|
||||
Find(&releases); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make(map[string][]*Release, len(releases))
|
||||
for _, r := range releases {
|
||||
res[r.Sha1] = append(res[r.Sha1], r)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
@ -25,3 +25,16 @@ func TestMigrate_InsertReleases(t *testing.T) {
|
||||
err := InsertReleases(db.DefaultContext, r)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_FindTagsByCommitIDs(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
sha1Rels, err := FindTagsByCommitIDs(db.DefaultContext, 1, "65f1bf27bc3bf70f64657658635e66094edbcb4d")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, sha1Rels, 1)
|
||||
rels := sha1Rels["65f1bf27bc3bf70f64657658635e66094edbcb4d"]
|
||||
assert.Len(t, rels, 3)
|
||||
assert.Equal(t, "v1.1", rels[0].TagName)
|
||||
assert.Equal(t, "delete-tag", rels[1].TagName)
|
||||
assert.Equal(t, "v1.0", rels[2].TagName)
|
||||
}
|
||||
|
46
modules/git/batch.go
Normal file
46
modules/git/batch.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
)
|
||||
|
||||
type Batch struct {
|
||||
cancel context.CancelFunc
|
||||
Reader *bufio.Reader
|
||||
Writer WriteCloserError
|
||||
}
|
||||
|
||||
func (repo *Repository) NewBatch(ctx context.Context) (*Batch, error) {
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var batch Batch
|
||||
batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repo.Path)
|
||||
return &batch, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) NewBatchCheck(ctx context.Context) (*Batch, error) {
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var check Batch
|
||||
check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repo.Path)
|
||||
return &check, nil
|
||||
}
|
||||
|
||||
func (b *Batch) Close() {
|
||||
if b.cancel != nil {
|
||||
b.cancel()
|
||||
b.Reader = nil
|
||||
b.Writer = nil
|
||||
b.cancel = nil
|
||||
}
|
||||
}
|
@ -26,10 +26,10 @@ type WriteCloserError interface {
|
||||
CloseWithError(err error) error
|
||||
}
|
||||
|
||||
// EnsureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository.
|
||||
// ensureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository.
|
||||
// Run before opening git cat-file.
|
||||
// This is needed otherwise the git cat-file will hang for invalid repositories.
|
||||
func EnsureValidGitRepository(ctx context.Context, repoPath string) error {
|
||||
func ensureValidGitRepository(ctx context.Context, repoPath string) error {
|
||||
stderr := strings.Builder{}
|
||||
err := NewCommand(ctx, "rev-parse").
|
||||
SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)).
|
||||
@ -43,8 +43,8 @@ func EnsureValidGitRepository(ctx context.Context, repoPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CatFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
|
||||
func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
|
||||
// catFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
|
||||
func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
|
||||
batchStdinReader, batchStdinWriter := io.Pipe()
|
||||
batchStdoutReader, batchStdoutWriter := io.Pipe()
|
||||
ctx, ctxCancel := context.WithCancel(ctx)
|
||||
@ -93,8 +93,8 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
|
||||
return batchStdinWriter, batchReader, cancel
|
||||
}
|
||||
|
||||
// CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
|
||||
func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
|
||||
// catFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
|
||||
func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
|
||||
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
|
||||
// so let's create a batch stdin and stdout
|
||||
batchStdinReader, batchStdinWriter := io.Pipe()
|
||||
|
@ -26,9 +26,12 @@ type Blob struct {
|
||||
// DataAsync gets a ReadCloser for the contents of a blob without reading it all.
|
||||
// Calling the Close function on the result will discard all unread output.
|
||||
func (b *Blob) DataAsync() (io.ReadCloser, error) {
|
||||
wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx)
|
||||
wr, rd, cancel, err := b.repo.CatFileBatch(b.repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err := wr.Write([]byte(b.ID.String() + "\n"))
|
||||
_, err = wr.Write([]byte(b.ID.String() + "\n"))
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
@ -64,9 +67,13 @@ func (b *Blob) Size() int64 {
|
||||
return b.size
|
||||
}
|
||||
|
||||
wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx)
|
||||
wr, rd, cancel, err := b.repo.CatFileBatchCheck(b.repo.Ctx)
|
||||
if err != nil {
|
||||
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err)
|
||||
return 0
|
||||
}
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(b.ID.String() + "\n"))
|
||||
_, err = wr.Write([]byte(b.ID.String() + "\n"))
|
||||
if err != nil {
|
||||
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err)
|
||||
return 0
|
||||
|
@ -124,7 +124,10 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx)
|
||||
batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
commitsMap := map[string]*Commit{}
|
||||
|
@ -46,7 +46,10 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
||||
|
||||
// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
|
||||
// so let's create a batch stdin and stdout
|
||||
batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
// We'll use a scanner for the revList because it's simpler than a bufio.Reader
|
||||
|
@ -25,15 +25,11 @@ type Repository struct {
|
||||
|
||||
gpgSettings *GPGSettings
|
||||
|
||||
batchInUse bool
|
||||
batchCancel context.CancelFunc
|
||||
batchReader *bufio.Reader
|
||||
batchWriter WriteCloserError
|
||||
batchInUse bool
|
||||
batch *Batch
|
||||
|
||||
checkInUse bool
|
||||
checkCancel context.CancelFunc
|
||||
checkReader *bufio.Reader
|
||||
checkWriter WriteCloserError
|
||||
checkInUse bool
|
||||
check *Batch
|
||||
|
||||
Ctx context.Context
|
||||
LastCommitCache *LastCommitCache
|
||||
@ -55,63 +51,75 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
||||
return nil, util.NewNotExistErrorf("no such file or directory")
|
||||
}
|
||||
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := EnsureValidGitRepository(ctx, repoPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := &Repository{
|
||||
return &Repository{
|
||||
Path: repoPath,
|
||||
tagCache: newObjectCache(),
|
||||
Ctx: ctx,
|
||||
}
|
||||
|
||||
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
|
||||
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
|
||||
|
||||
return repo, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CatFileBatch obtains a CatFileBatch for this repository
|
||||
func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
|
||||
if repo.batchCancel == nil || repo.batchInUse {
|
||||
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
|
||||
return CatFileBatch(ctx, repo.Path)
|
||||
func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
|
||||
if repo.batch == nil {
|
||||
var err error
|
||||
repo.batch, err = repo.NewBatch(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
repo.batchInUse = true
|
||||
return repo.batchWriter, repo.batchReader, func() {
|
||||
repo.batchInUse = false
|
||||
|
||||
if !repo.batchInUse {
|
||||
repo.batchInUse = true
|
||||
return repo.batch.Writer, repo.batch.Reader, func() {
|
||||
repo.batchInUse = false
|
||||
}, nil
|
||||
}
|
||||
|
||||
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
|
||||
tempBatch, err := repo.NewBatch(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return tempBatch.Writer, tempBatch.Reader, tempBatch.Close, nil
|
||||
}
|
||||
|
||||
// CatFileBatchCheck obtains a CatFileBatchCheck for this repository
|
||||
func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) {
|
||||
if repo.checkCancel == nil || repo.checkInUse {
|
||||
log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
|
||||
return CatFileBatchCheck(ctx, repo.Path)
|
||||
func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
|
||||
if repo.check == nil {
|
||||
var err error
|
||||
repo.check, err = repo.NewBatchCheck(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
repo.checkInUse = true
|
||||
return repo.checkWriter, repo.checkReader, func() {
|
||||
repo.checkInUse = false
|
||||
|
||||
if !repo.checkInUse {
|
||||
repo.checkInUse = true
|
||||
return repo.check.Writer, repo.check.Reader, func() {
|
||||
repo.checkInUse = false
|
||||
}, nil
|
||||
}
|
||||
|
||||
log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
|
||||
tempBatchCheck, err := repo.NewBatchCheck(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return tempBatchCheck.Writer, tempBatchCheck.Reader, tempBatchCheck.Close, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) Close() error {
|
||||
if repo == nil {
|
||||
return nil
|
||||
}
|
||||
if repo.batchCancel != nil {
|
||||
repo.batchCancel()
|
||||
repo.batchReader = nil
|
||||
repo.batchWriter = nil
|
||||
repo.batchCancel = nil
|
||||
if repo.batch != nil {
|
||||
repo.batch.Close()
|
||||
repo.batch = nil
|
||||
repo.batchInUse = false
|
||||
}
|
||||
if repo.checkCancel != nil {
|
||||
repo.checkCancel()
|
||||
repo.checkCancel = nil
|
||||
repo.checkReader = nil
|
||||
repo.checkWriter = nil
|
||||
if repo.check != nil {
|
||||
repo.check.Close()
|
||||
repo.check = nil
|
||||
repo.checkInUse = false
|
||||
}
|
||||
repo.LastCommitCache = nil
|
||||
|
@ -22,9 +22,13 @@ func (repo *Repository) IsObjectExist(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
|
||||
if err != nil {
|
||||
log.Debug("Error writing to CatFileBatchCheck %v", err)
|
||||
return false
|
||||
}
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(name + "\n"))
|
||||
_, err = wr.Write([]byte(name + "\n"))
|
||||
if err != nil {
|
||||
log.Debug("Error writing to CatFileBatchCheck %v", err)
|
||||
return false
|
||||
@ -39,9 +43,13 @@ func (repo *Repository) IsReferenceExist(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
|
||||
if err != nil {
|
||||
log.Debug("Error writing to CatFileBatchCheck %v", err)
|
||||
return false
|
||||
}
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(name + "\n"))
|
||||
_, err = wr.Write([]byte(name + "\n"))
|
||||
if err != nil {
|
||||
log.Debug("Error writing to CatFileBatchCheck %v", err)
|
||||
return false
|
||||
|
@ -33,9 +33,12 @@ func (repo *Repository) ResolveReference(name string) (string, error) {
|
||||
|
||||
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
|
||||
func (repo *Repository) GetRefCommitID(name string) (string, error) {
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(name + "\n"))
|
||||
_, err = wr.Write([]byte(name + "\n"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -61,12 +64,19 @@ func (repo *Repository) RemoveReference(name string) error {
|
||||
|
||||
// IsCommitExist returns true if given commit exists in current repository.
|
||||
func (repo *Repository) IsCommitExist(name string) bool {
|
||||
if err := ensureValidGitRepository(repo.Ctx, repo.Path); err != nil {
|
||||
log.Error("IsCommitExist: %v", err)
|
||||
return false
|
||||
}
|
||||
_, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
|
||||
wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
_, _ = wr.Write([]byte(id.String() + "\n"))
|
||||
@ -143,7 +153,10 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
|
||||
}
|
||||
}
|
||||
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
_, err = wr.Write([]byte(commitID + "\n"))
|
||||
if err != nil {
|
||||
|
@ -20,7 +20,10 @@ import (
|
||||
func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
|
||||
// We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary.
|
||||
// so let's create a batch stdin and stdout
|
||||
batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
writeID := func(id string) error {
|
||||
|
@ -31,9 +31,12 @@ func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) {
|
||||
|
||||
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
|
||||
func (repo *Repository) GetTagType(id ObjectID) (string, error) {
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(id.String() + "\n"))
|
||||
_, err = wr.Write([]byte(id.String() + "\n"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -89,7 +92,10 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
|
||||
}
|
||||
|
||||
// The tag is an annotated tag with a message.
|
||||
wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
if _, err := wr.Write([]byte(tagID.String() + "\n")); err != nil {
|
||||
|
@ -10,7 +10,10 @@ import (
|
||||
)
|
||||
|
||||
func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
||||
wr, rd, cancel := repo.CatFileBatch(repo.Ctx)
|
||||
wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
_, _ = wr.Write([]byte(id.String() + "\n"))
|
||||
|
@ -42,9 +42,13 @@ func (te *TreeEntry) Size() int64 {
|
||||
return te.size
|
||||
}
|
||||
|
||||
wr, rd, cancel := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
|
||||
wr, rd, cancel, err := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
|
||||
if err != nil {
|
||||
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
|
||||
return 0
|
||||
}
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(te.ID.String() + "\n"))
|
||||
_, err = wr.Write([]byte(te.ID.String() + "\n"))
|
||||
if err != nil {
|
||||
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
|
||||
return 0
|
||||
|
@ -33,7 +33,10 @@ func (t *Tree) ListEntries() (Entries, error) {
|
||||
}
|
||||
|
||||
if t.repo != nil {
|
||||
wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx)
|
||||
wr, rd, cancel, err := t.repo.CatFileBatch(t.repo.Ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
_, _ = wr.Write([]byte(t.ID.String() + "\n"))
|
||||
|
66
modules/globallock/globallock.go
Normal file
66
modules/globallock/globallock.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLocker Locker
|
||||
initOnce sync.Once
|
||||
initFunc = func() {
|
||||
// TODO: read the setting and initialize the default locker.
|
||||
// Before implementing this, don't use it.
|
||||
} // define initFunc as a variable to make it possible to change it in tests
|
||||
)
|
||||
|
||||
// DefaultLocker returns the default locker.
|
||||
func DefaultLocker() Locker {
|
||||
initOnce.Do(func() {
|
||||
initFunc()
|
||||
})
|
||||
return defaultLocker
|
||||
}
|
||||
|
||||
// Lock tries to acquire a lock for the given key, it uses the default locker.
|
||||
// Read the documentation of Locker.Lock for more information about the behavior.
|
||||
func Lock(ctx context.Context, key string) (context.Context, ReleaseFunc, error) {
|
||||
return DefaultLocker().Lock(ctx, key)
|
||||
}
|
||||
|
||||
// TryLock tries to acquire a lock for the given key, it uses the default locker.
|
||||
// Read the documentation of Locker.TryLock for more information about the behavior.
|
||||
func TryLock(ctx context.Context, key string) (bool, context.Context, ReleaseFunc, error) {
|
||||
return DefaultLocker().TryLock(ctx, key)
|
||||
}
|
||||
|
||||
// LockAndDo tries to acquire a lock for the given key and then calls the given function.
|
||||
// It uses the default locker, and it will return an error if failed to acquire the lock.
|
||||
func LockAndDo(ctx context.Context, key string, f func(context.Context) error) error {
|
||||
ctx, release, err := Lock(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer release()
|
||||
|
||||
return f(ctx)
|
||||
}
|
||||
|
||||
// TryLockAndDo tries to acquire a lock for the given key and then calls the given function.
|
||||
// It uses the default locker, and it will return false if failed to acquire the lock.
|
||||
func TryLockAndDo(ctx context.Context, key string, f func(context.Context) error) (bool, error) {
|
||||
ok, ctx, release, err := TryLock(ctx, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, f(ctx)
|
||||
}
|
96
modules/globallock/globallock_test.go
Normal file
96
modules/globallock/globallock_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLockAndDo(t *testing.T) {
|
||||
t.Run("redis", func(t *testing.T) {
|
||||
url := "redis://127.0.0.1:6379/0"
|
||||
if os.Getenv("CI") == "" {
|
||||
// Make it possible to run tests against a local redis instance
|
||||
url = os.Getenv("TEST_REDIS_URL")
|
||||
if url == "" {
|
||||
t.Skip("TEST_REDIS_URL not set and not running in CI")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
oldDefaultLocker := defaultLocker
|
||||
oldInitFunc := initFunc
|
||||
defer func() {
|
||||
defaultLocker = oldDefaultLocker
|
||||
initFunc = oldInitFunc
|
||||
if defaultLocker == nil {
|
||||
initOnce = sync.Once{}
|
||||
}
|
||||
}()
|
||||
|
||||
initOnce = sync.Once{}
|
||||
initFunc = func() {
|
||||
defaultLocker = NewRedisLocker(url)
|
||||
}
|
||||
|
||||
testLockAndDo(t)
|
||||
require.NoError(t, defaultLocker.(*redisLocker).Close())
|
||||
})
|
||||
t.Run("memory", func(t *testing.T) {
|
||||
oldDefaultLocker := defaultLocker
|
||||
oldInitFunc := initFunc
|
||||
defer func() {
|
||||
defaultLocker = oldDefaultLocker
|
||||
initFunc = oldInitFunc
|
||||
if defaultLocker == nil {
|
||||
initOnce = sync.Once{}
|
||||
}
|
||||
}()
|
||||
|
||||
initOnce = sync.Once{}
|
||||
initFunc = func() {
|
||||
defaultLocker = NewMemoryLocker()
|
||||
}
|
||||
|
||||
testLockAndDo(t)
|
||||
})
|
||||
}
|
||||
|
||||
func testLockAndDo(t *testing.T) {
|
||||
const concurrency = 1000
|
||||
|
||||
ctx := context.Background()
|
||||
count := 0
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := LockAndDo(ctx, "test", func(ctx context.Context) error {
|
||||
count++
|
||||
|
||||
// It's impossible to acquire the lock inner the function
|
||||
ok, err := TryLockAndDo(ctx, "test", func(ctx context.Context) error {
|
||||
assert.Fail(t, "should not acquire the lock")
|
||||
return nil
|
||||
})
|
||||
assert.False(t, ok)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, concurrency, count)
|
||||
}
|
60
modules/globallock/locker.go
Normal file
60
modules/globallock/locker.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Locker interface {
|
||||
// Lock tries to acquire a lock for the given key, it blocks until the lock is acquired or the context is canceled.
|
||||
//
|
||||
// Lock returns a new context which should be used in the following code.
|
||||
// The new context will be canceled when the lock is released or lost - yes, it's possible to lose a lock.
|
||||
// For example, it lost the connection to the redis server while holding the lock.
|
||||
// If it fails to acquire the lock, the returned context will be the same as the input context.
|
||||
//
|
||||
// Lock returns a ReleaseFunc to release the lock, it cannot be nil.
|
||||
// It's always safe to call this function even if it fails to acquire the lock, and it will do nothing in that case.
|
||||
// And it's also safe to call it multiple times, but it will only release the lock once.
|
||||
// That's why it's called ReleaseFunc, not UnlockFunc.
|
||||
// But be aware that it's not safe to not call it at all; it could lead to a memory leak.
|
||||
// So a recommended pattern is to use defer to call it:
|
||||
// ctx, release, err := locker.Lock(ctx, "key")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer release()
|
||||
// The ReleaseFunc will return the original context which was used to acquire the lock.
|
||||
// It's useful when you want to continue to do something after releasing the lock.
|
||||
// At that time, the ctx will be canceled, and you can use the returned context by the ReleaseFunc to continue:
|
||||
// ctx, release, err := locker.Lock(ctx, "key")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer release()
|
||||
// doSomething(ctx)
|
||||
// ctx = release()
|
||||
// doSomethingElse(ctx)
|
||||
// Please ignore it and use `defer release()` instead if you don't need this, to avoid forgetting to release the lock.
|
||||
//
|
||||
// Lock returns an error if failed to acquire the lock.
|
||||
// Be aware that even the context is not canceled, it's still possible to fail to acquire the lock.
|
||||
// For example, redis is down, or it reached the maximum number of tries.
|
||||
Lock(ctx context.Context, key string) (context.Context, ReleaseFunc, error)
|
||||
|
||||
// TryLock tries to acquire a lock for the given key, it returns immediately.
|
||||
// It follows the same pattern as Lock, but it doesn't block.
|
||||
// And if it fails to acquire the lock because it's already locked, not other reasons like redis is down,
|
||||
// it will return false without any error.
|
||||
TryLock(ctx context.Context, key string) (bool, context.Context, ReleaseFunc, error)
|
||||
}
|
||||
|
||||
// ReleaseFunc is a function that releases a lock.
|
||||
// It returns the original context which was used to acquire the lock.
|
||||
type ReleaseFunc func() context.Context
|
||||
|
||||
// ErrLockReleased is used as context cause when a lock is released
|
||||
var ErrLockReleased = fmt.Errorf("lock released")
|
211
modules/globallock/locker_test.go
Normal file
211
modules/globallock/locker_test.go
Normal file
@ -0,0 +1,211 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLocker(t *testing.T) {
|
||||
t.Run("redis", func(t *testing.T) {
|
||||
url := "redis://127.0.0.1:6379/0"
|
||||
if os.Getenv("CI") == "" {
|
||||
// Make it possible to run tests against a local redis instance
|
||||
url = os.Getenv("TEST_REDIS_URL")
|
||||
if url == "" {
|
||||
t.Skip("TEST_REDIS_URL not set and not running in CI")
|
||||
return
|
||||
}
|
||||
}
|
||||
oldExpiry := redisLockExpiry
|
||||
redisLockExpiry = 5 * time.Second // make it shorter for testing
|
||||
defer func() {
|
||||
redisLockExpiry = oldExpiry
|
||||
}()
|
||||
|
||||
locker := NewRedisLocker(url)
|
||||
testLocker(t, locker)
|
||||
testRedisLocker(t, locker.(*redisLocker))
|
||||
require.NoError(t, locker.(*redisLocker).Close())
|
||||
})
|
||||
t.Run("memory", func(t *testing.T) {
|
||||
locker := NewMemoryLocker()
|
||||
testLocker(t, locker)
|
||||
testMemoryLocker(t, locker.(*memoryLocker))
|
||||
})
|
||||
}
|
||||
|
||||
func testLocker(t *testing.T, locker Locker) {
|
||||
t.Run("lock", func(t *testing.T) {
|
||||
parentCtx := context.Background()
|
||||
ctx, release, err := locker.Lock(parentCtx, "test")
|
||||
defer release()
|
||||
|
||||
assert.NotEqual(t, parentCtx, ctx) // new context should be returned
|
||||
assert.NoError(t, err)
|
||||
|
||||
func() {
|
||||
parentCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
ctx, release, err := locker.Lock(parentCtx, "test")
|
||||
defer release()
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, parentCtx, ctx) // should return the same context
|
||||
}()
|
||||
|
||||
release()
|
||||
assert.Error(t, ctx.Err())
|
||||
|
||||
func() {
|
||||
_, release, err := locker.Lock(context.Background(), "test")
|
||||
defer release()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
})
|
||||
|
||||
t.Run("try lock", func(t *testing.T) {
|
||||
parentCtx := context.Background()
|
||||
ok, ctx, release, err := locker.TryLock(parentCtx, "test")
|
||||
defer release()
|
||||
|
||||
assert.True(t, ok)
|
||||
assert.NotEqual(t, parentCtx, ctx) // new context should be returned
|
||||
assert.NoError(t, err)
|
||||
|
||||
func() {
|
||||
parentCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
ok, ctx, release, err := locker.TryLock(parentCtx, "test")
|
||||
defer release()
|
||||
|
||||
assert.False(t, ok)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, parentCtx, ctx) // should return the same context
|
||||
}()
|
||||
|
||||
release()
|
||||
assert.Error(t, ctx.Err())
|
||||
|
||||
func() {
|
||||
ok, _, release, _ := locker.TryLock(context.Background(), "test")
|
||||
defer release()
|
||||
|
||||
assert.True(t, ok)
|
||||
}()
|
||||
})
|
||||
|
||||
t.Run("wait and acquired", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, release, err := locker.Lock(ctx, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
started := time.Now()
|
||||
_, release, err := locker.Lock(context.Background(), "test") // should be blocked for seconds
|
||||
defer release()
|
||||
assert.Greater(t, time.Since(started), time.Second)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
release()
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
t.Run("continue after release", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
ctxBeforeLock := ctx
|
||||
ctx, release, err := locker.Lock(ctx, "test")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, ctx.Err())
|
||||
assert.NotEqual(t, ctxBeforeLock, ctx)
|
||||
|
||||
ctxBeforeRelease := ctx
|
||||
ctx = release()
|
||||
|
||||
assert.NoError(t, ctx.Err())
|
||||
assert.Error(t, ctxBeforeRelease.Err())
|
||||
|
||||
// so it can continue with ctx to do more work
|
||||
})
|
||||
|
||||
t.Run("multiple release", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
_, release1, err := locker.Lock(ctx, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
release1()
|
||||
|
||||
_, release2, err := locker.Lock(ctx, "test")
|
||||
defer release2()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call release1 again,
|
||||
// it should not panic or block,
|
||||
// and it shouldn't affect the other lock
|
||||
release1()
|
||||
|
||||
ok, _, release3, err := locker.TryLock(ctx, "test")
|
||||
defer release3()
|
||||
require.NoError(t, err)
|
||||
// It should be able to acquire the lock;
|
||||
// otherwise, it means the lock has been released by release1
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
// testMemoryLocker does specific tests for memoryLocker
|
||||
func testMemoryLocker(t *testing.T, locker *memoryLocker) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
// testRedisLocker does specific tests for redisLocker
|
||||
func testRedisLocker(t *testing.T, locker *redisLocker) {
|
||||
defer func() {
|
||||
// This case should be tested at the end.
|
||||
// Otherwise, it will affect other tests.
|
||||
t.Run("close", func(t *testing.T) {
|
||||
assert.NoError(t, locker.Close())
|
||||
_, _, err := locker.Lock(context.Background(), "test")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}()
|
||||
|
||||
t.Run("failed extend", func(t *testing.T) {
|
||||
ctx, release, err := locker.Lock(context.Background(), "test")
|
||||
defer release()
|
||||
require.NoError(t, err)
|
||||
|
||||
// It simulates that there are some problems with extending like network issues or redis server down.
|
||||
v, ok := locker.mutexM.Load("test")
|
||||
require.True(t, ok)
|
||||
m := v.(*redisMutex)
|
||||
_, _ = m.mutex.Unlock() // release it to make it impossible to extend
|
||||
|
||||
select {
|
||||
case <-time.After(redisLockExpiry + time.Second):
|
||||
t.Errorf("lock should be expired")
|
||||
case <-ctx.Done():
|
||||
var errTaken *redsync.ErrTaken
|
||||
assert.ErrorAs(t, context.Cause(ctx), &errTaken)
|
||||
}
|
||||
})
|
||||
}
|
80
modules/globallock/memory_locker.go
Normal file
80
modules/globallock/memory_locker.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type memoryLocker struct {
|
||||
locks sync.Map
|
||||
}
|
||||
|
||||
var _ Locker = &memoryLocker{}
|
||||
|
||||
func NewMemoryLocker() Locker {
|
||||
return &memoryLocker{}
|
||||
}
|
||||
|
||||
func (l *memoryLocker) Lock(ctx context.Context, key string) (context.Context, ReleaseFunc, error) {
|
||||
originalCtx := ctx
|
||||
|
||||
if l.tryLock(key) {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
releaseOnce := sync.Once{}
|
||||
return ctx, func() context.Context {
|
||||
releaseOnce.Do(func() {
|
||||
l.locks.Delete(key)
|
||||
cancel(ErrLockReleased)
|
||||
})
|
||||
return originalCtx
|
||||
}, nil
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx, func() context.Context { return originalCtx }, ctx.Err()
|
||||
case <-ticker.C:
|
||||
if l.tryLock(key) {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
releaseOnce := sync.Once{}
|
||||
return ctx, func() context.Context {
|
||||
releaseOnce.Do(func() {
|
||||
l.locks.Delete(key)
|
||||
cancel(ErrLockReleased)
|
||||
})
|
||||
return originalCtx
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *memoryLocker) TryLock(ctx context.Context, key string) (bool, context.Context, ReleaseFunc, error) {
|
||||
originalCtx := ctx
|
||||
|
||||
if l.tryLock(key) {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
releaseOnce := sync.Once{}
|
||||
return true, ctx, func() context.Context {
|
||||
releaseOnce.Do(func() {
|
||||
cancel(ErrLockReleased)
|
||||
l.locks.Delete(key)
|
||||
})
|
||||
return originalCtx
|
||||
}, nil
|
||||
}
|
||||
|
||||
return false, ctx, func() context.Context { return originalCtx }, nil
|
||||
}
|
||||
|
||||
func (l *memoryLocker) tryLock(key string) bool {
|
||||
_, loaded := l.locks.LoadOrStore(key, struct{}{})
|
||||
return !loaded
|
||||
}
|
154
modules/globallock/redis_locker.go
Normal file
154
modules/globallock/redis_locker.go
Normal file
@ -0,0 +1,154 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package globallock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/nosql"
|
||||
|
||||
"github.com/go-redsync/redsync/v4"
|
||||
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
|
||||
)
|
||||
|
||||
const redisLockKeyPrefix = "gitea:globallock:"
|
||||
|
||||
// redisLockExpiry is the default expiry time for a lock.
|
||||
// Define it as a variable to make it possible to change it in tests.
|
||||
var redisLockExpiry = 30 * time.Second
|
||||
|
||||
type redisLocker struct {
|
||||
rs *redsync.Redsync
|
||||
|
||||
mutexM sync.Map
|
||||
closed atomic.Bool
|
||||
extendWg sync.WaitGroup
|
||||
}
|
||||
|
||||
var _ Locker = &redisLocker{}
|
||||
|
||||
func NewRedisLocker(connection string) Locker {
|
||||
l := &redisLocker{
|
||||
rs: redsync.New(
|
||||
goredis.NewPool(
|
||||
nosql.GetManager().GetRedisClient(connection),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
l.extendWg.Add(1)
|
||||
l.startExtend()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *redisLocker) Lock(ctx context.Context, key string) (context.Context, ReleaseFunc, error) {
|
||||
return l.lock(ctx, key, 0)
|
||||
}
|
||||
|
||||
func (l *redisLocker) TryLock(ctx context.Context, key string) (bool, context.Context, ReleaseFunc, error) {
|
||||
ctx, f, err := l.lock(ctx, key, 1)
|
||||
|
||||
var (
|
||||
errTaken *redsync.ErrTaken
|
||||
errNodeTaken *redsync.ErrNodeTaken
|
||||
)
|
||||
if errors.As(err, &errTaken) || errors.As(err, &errNodeTaken) {
|
||||
return false, ctx, f, nil
|
||||
}
|
||||
return err == nil, ctx, f, err
|
||||
}
|
||||
|
||||
// Close closes the locker.
|
||||
// It will stop extending the locks and refuse to acquire new locks.
|
||||
// In actual use, it is not necessary to call this function.
|
||||
// But it's useful in tests to release resources.
|
||||
// It could take some time since it waits for the extending goroutine to finish.
|
||||
func (l *redisLocker) Close() error {
|
||||
l.closed.Store(true)
|
||||
l.extendWg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
type redisMutex struct {
|
||||
mutex *redsync.Mutex
|
||||
cancel context.CancelCauseFunc
|
||||
}
|
||||
|
||||
func (l *redisLocker) lock(ctx context.Context, key string, tries int) (context.Context, ReleaseFunc, error) {
|
||||
if l.closed.Load() {
|
||||
return ctx, func() context.Context { return ctx }, fmt.Errorf("locker is closed")
|
||||
}
|
||||
|
||||
originalCtx := ctx
|
||||
|
||||
options := []redsync.Option{
|
||||
redsync.WithExpiry(redisLockExpiry),
|
||||
}
|
||||
if tries > 0 {
|
||||
options = append(options, redsync.WithTries(tries))
|
||||
}
|
||||
mutex := l.rs.NewMutex(redisLockKeyPrefix+key, options...)
|
||||
if err := mutex.LockContext(ctx); err != nil {
|
||||
return ctx, func() context.Context { return originalCtx }, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
|
||||
l.mutexM.Store(key, &redisMutex{
|
||||
mutex: mutex,
|
||||
cancel: cancel,
|
||||
})
|
||||
|
||||
releaseOnce := sync.Once{}
|
||||
return ctx, func() context.Context {
|
||||
releaseOnce.Do(func() {
|
||||
l.mutexM.Delete(key)
|
||||
|
||||
// It's safe to ignore the error here,
|
||||
// if it failed to unlock, it will be released automatically after the lock expires.
|
||||
// Do not call mutex.UnlockContext(ctx) here, or it will fail to release when ctx has timed out.
|
||||
_, _ = mutex.Unlock()
|
||||
|
||||
cancel(ErrLockReleased)
|
||||
})
|
||||
return originalCtx
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *redisLocker) startExtend() {
|
||||
if l.closed.Load() {
|
||||
l.extendWg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
toExtend := make([]*redisMutex, 0)
|
||||
l.mutexM.Range(func(_, value any) bool {
|
||||
m := value.(*redisMutex)
|
||||
|
||||
// Extend the lock if it is not expired.
|
||||
// Although the mutex will be removed from the map before it is released,
|
||||
// it still can be expired because of a failed extension.
|
||||
// If it happens, the cancel function should have been called,
|
||||
// so it does not need to be extended anymore.
|
||||
if time.Now().After(m.mutex.Until()) {
|
||||
return true
|
||||
}
|
||||
|
||||
toExtend = append(toExtend, m)
|
||||
return true
|
||||
})
|
||||
for _, v := range toExtend {
|
||||
if ok, err := v.mutex.Extend(); !ok {
|
||||
v.cancel(err)
|
||||
}
|
||||
}
|
||||
|
||||
time.AfterFunc(redisLockExpiry/2, l.startExtend)
|
||||
}
|
@ -16,10 +16,10 @@ import (
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/indexer/code/internal"
|
||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
@ -189,21 +189,23 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch
|
||||
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
||||
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
|
||||
if len(changes.Updates) > 0 {
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
|
||||
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
|
||||
r, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
batchWriter, batchReader, cancel := git.CatFileBatch(ctx, repo.RepoPath())
|
||||
defer cancel()
|
||||
defer r.Close()
|
||||
gitBatch, err := r.NewBatch(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gitBatch.Close()
|
||||
|
||||
for _, update := range changes.Updates {
|
||||
if err := b.addUpdate(ctx, batchWriter, batchReader, sha, update, repo, batch); err != nil {
|
||||
if err := b.addUpdate(ctx, gitBatch.Writer, gitBatch.Reader, sha, update, repo, batch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
gitBatch.Close()
|
||||
}
|
||||
for _, filename := range changes.RemovedFilenames {
|
||||
if err := b.addDelete(filename, repo, batch); err != nil {
|
||||
|
@ -15,11 +15,11 @@ import (
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/indexer/code/internal"
|
||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
@ -154,17 +154,19 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository) elasti
|
||||
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
||||
reqs := make([]elastic.BulkableRequest, 0)
|
||||
if len(changes.Updates) > 0 {
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
|
||||
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
|
||||
r, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
batchWriter, batchReader, cancel := git.CatFileBatch(ctx, repo.RepoPath())
|
||||
defer cancel()
|
||||
defer r.Close()
|
||||
batch, err := r.NewBatch(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer batch.Close()
|
||||
|
||||
for _, update := range changes.Updates {
|
||||
updateReqs, err := b.addUpdate(ctx, batchWriter, batchReader, sha, update, repo)
|
||||
updateReqs, err := b.addUpdate(ctx, batch.Writer, batch.Reader, sha, update, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -172,7 +174,7 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
|
||||
reqs = append(reqs, updateReqs...)
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
batch.Close()
|
||||
}
|
||||
|
||||
for _, filename := range changes.RemovedFilenames {
|
||||
|
@ -45,7 +45,7 @@ func (p *PullRequest) GetContext() DownloaderContext { return p.Context }
|
||||
|
||||
// IsForkPullRequest returns true if the pull request from a forked repository but not the same repository
|
||||
func (p *PullRequest) IsForkPullRequest() bool {
|
||||
return p.Head.RepoPath() != p.Base.RepoPath()
|
||||
return p.Head.RepoFullName() != p.Base.RepoFullName()
|
||||
}
|
||||
|
||||
// GetGitRefName returns pull request relative path to head
|
||||
@ -62,8 +62,8 @@ type PullRequestBranch struct {
|
||||
OwnerName string `yaml:"owner_name"`
|
||||
}
|
||||
|
||||
// RepoPath returns pull request repo path
|
||||
func (p PullRequestBranch) RepoPath() string {
|
||||
// RepoFullName returns pull request repo full name
|
||||
func (p PullRequestBranch) RepoFullName() string {
|
||||
return fmt.Sprintf("%s/%s", p.OwnerName, p.RepoName)
|
||||
}
|
||||
|
||||
|
@ -494,3 +494,17 @@ type PackagePayload struct {
|
||||
func (p *PackagePayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
||||
// WorkflowDispatchPayload represents a workflow dispatch payload
|
||||
type WorkflowDispatchPayload struct {
|
||||
Workflow string `json:"workflow"`
|
||||
Ref string `json:"ref"`
|
||||
Inputs map[string]any `json:"inputs"`
|
||||
Repository *Repository `json:"repository"`
|
||||
Sender *User `json:"sender"`
|
||||
}
|
||||
|
||||
// JSONPayload implements Payload
|
||||
func (p *WorkflowDispatchPayload) JSONPayload() ([]byte, error) {
|
||||
return json.MarshalIndent(p, "", " ")
|
||||
}
|
||||
|
22
options/license/DocBook-Schema
Normal file
22
options/license/DocBook-Schema
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright 1992-2011 HaL Computer Systems, Inc.,
|
||||
O'Reilly & Associates, Inc., ArborText, Inc., Fujitsu Software
|
||||
Corporation, Norman Walsh, Sun Microsystems, Inc., and the
|
||||
Organization for the Advancement of Structured Information
|
||||
Standards (OASIS).
|
||||
|
||||
Permission to use, copy, modify and distribute the DocBook schema
|
||||
and its accompanying documentation for any purpose and without fee
|
||||
is hereby granted in perpetuity, provided that the above copyright
|
||||
notice and this paragraph appear in all copies. The copyright
|
||||
holders make no representation about the suitability of the schema
|
||||
for any purpose. It is provided "as is" without expressed or implied
|
||||
warranty.
|
||||
|
||||
If you modify the DocBook schema in any way, label your schema as a
|
||||
variant of DocBook. See the reference documentation
|
||||
(http://docbook.org/tdg5/en/html/ch05.html#s-notdocbook)
|
||||
for more information.
|
||||
|
||||
Please direct all questions, bug reports, or suggestions for changes
|
||||
to the docbook@lists.oasis-open.org mailing list. For more
|
||||
information, see http://www.oasis-open.org/docbook/.
|
48
options/license/DocBook-XML
Normal file
48
options/license/DocBook-XML
Normal file
@ -0,0 +1,48 @@
|
||||
Copyright
|
||||
---------
|
||||
Copyright (C) 1999-2007 Norman Walsh
|
||||
Copyright (C) 2003 Jiří Kosek
|
||||
Copyright (C) 2004-2007 Steve Ball
|
||||
Copyright (C) 2005-2014 The DocBook Project
|
||||
Copyright (C) 2011-2012 O'Reilly Media
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the ``Software''), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
Except as contained in this notice, the names of individuals
|
||||
credited with contribution to this software shall not be used in
|
||||
advertising or otherwise to promote the sale, use or other
|
||||
dealings in this Software without prior written authorization
|
||||
from the individuals in question.
|
||||
|
||||
Any stylesheet derived from this Software that is publically
|
||||
distributed will be identified with a different name and the
|
||||
version strings in any derived Software will be changed so that
|
||||
no possibility of confusion between the derived package and this
|
||||
Software will exist.
|
||||
|
||||
Warranty
|
||||
--------
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL NORMAN WALSH OR ANY OTHER
|
||||
CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Contacting the Author
|
||||
---------------------
|
||||
The DocBook XSL stylesheets are maintained by Norman Walsh,
|
||||
<ndw@nwalsh.com>, and members of the DocBook Project,
|
||||
<docbook-developers@sf.net>
|
96
options/license/Ubuntu-font-1.0
Normal file
96
options/license/Ubuntu-font-1.0
Normal file
@ -0,0 +1,96 @@
|
||||
-------------------------------
|
||||
UBUNTU FONT LICENCE Version 1.0
|
||||
-------------------------------
|
||||
|
||||
PREAMBLE
|
||||
This licence allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely. The fonts, including any derivative works, can be
|
||||
bundled, embedded, and redistributed provided the terms of this licence
|
||||
are met. The fonts and derivatives, however, cannot be released under
|
||||
any other licence. The requirement for fonts to remain under this
|
||||
licence does not require any document created using the fonts or their
|
||||
derivatives to be published under this licence, as long as the primary
|
||||
purpose of the document is not to be a vehicle for the distribution of
|
||||
the fonts.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this licence and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as received under this licence.
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to
|
||||
a new environment.
|
||||
|
||||
"Copyright Holder(s)" refers to all individuals and companies who have a
|
||||
copyright ownership of the Font Software.
|
||||
|
||||
"Substantially Changed" refers to Modified Versions which can be easily
|
||||
identified as dissimilar to the Font Software by users of the Font
|
||||
Software comparing the Original Version with the Modified Version.
|
||||
|
||||
To "Propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification and with or without charging
|
||||
a redistribution fee), making available to the public, and in some
|
||||
countries other activities as well.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
This licence does not grant any rights under trademark law and all such
|
||||
rights are reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to propagate the Font Software, subject to
|
||||
the below conditions:
|
||||
|
||||
1) Each copy of the Font Software must contain the above copyright
|
||||
notice and this licence. These can be included either as stand-alone
|
||||
text files, human-readable headers or in the appropriate machine-
|
||||
readable metadata fields within text or binary files as long as those
|
||||
fields can be easily viewed by the user.
|
||||
|
||||
2) The font name complies with the following:
|
||||
(a) The Original Version must retain its name, unmodified.
|
||||
(b) Modified Versions which are Substantially Changed must be renamed to
|
||||
avoid use of the name of the Original Version or similar names entirely.
|
||||
(c) Modified Versions which are not Substantially Changed must be
|
||||
renamed to both (i) retain the name of the Original Version and (ii) add
|
||||
additional naming elements to distinguish the Modified Version from the
|
||||
Original Version. The name of such Modified Versions must be the name of
|
||||
the Original Version, with "derivative X" where X represents the name of
|
||||
the new work, appended to that name.
|
||||
|
||||
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
||||
Font Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except (i) as required by this licence, (ii) to
|
||||
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
||||
their explicit written permission.
|
||||
|
||||
4) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this licence, and must not be distributed
|
||||
under any other licence. The requirement for fonts to remain under this
|
||||
licence does not affect any document created using the Font Software,
|
||||
except any version of the Font Software extracted from a document
|
||||
created using the Font Software may only be distributed under this
|
||||
licence.
|
||||
|
||||
TERMINATION
|
||||
This licence becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
@ -229,7 +229,7 @@ app_desc=Snadno přístupný vlastní Git
|
||||
install=Jednoduchá na instalaci
|
||||
install_desc=Jednoduše <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">spusťte jako binární program</a> pro vaši platformu, nasaďte jej pomocí <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a>, nebo jej stáhněte jako <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">balíček</a>.
|
||||
platform=Multiplatformní
|
||||
platform_desc=Gitea běží všude, kde <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete!
|
||||
platform_desc=Gitea běží všude, kde <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete!
|
||||
lightweight=Lehká
|
||||
lightweight_desc=Gitea má minimální požadavky a může běžet na Raspberry Pi. Šetřete energii vašeho stroje!
|
||||
license=Open Source
|
||||
@ -686,11 +686,11 @@ applications=Aplikace
|
||||
orgs=Spravovat organizace
|
||||
repos=Repozitáře
|
||||
delete=Smazat účet
|
||||
twofa=Dvoufaktorové ověřování
|
||||
twofa=Dvoufaktorové ověřování (TOTP)
|
||||
account_link=Propojené účty
|
||||
organization=Organizace
|
||||
uid=UID
|
||||
webauthn=Bezpečnostní klíče
|
||||
webauthn=Dvoufaktorové ověření (Bezpečnostní klíče)
|
||||
|
||||
public_profile=Veřejný profil
|
||||
biography_placeholder=Řekněte nám něco o sobě! (Můžete použít Markdown)
|
||||
@ -926,23 +926,19 @@ revoke_oauth2_grant=Zrušit přístup
|
||||
revoke_oauth2_grant_description=Zrušením přístupu této aplikaci třetí strany ji zabráníte v přístupu k vašim datům. Jste si jisti?
|
||||
revoke_oauth2_grant_success=Přístup byl úspěšně zrušen.
|
||||
|
||||
twofa_desc=Dvoufaktorový způsob ověřování zvýší zabezpečení vašeho účtu.
|
||||
twofa_recovery_tip=Pokud ztratíte své zařízení, budete moci použít jednorázový obnovovací klíč k získání přístupu k vašemu účtu.
|
||||
twofa_is_enrolled=Váš účet aktuálně <strong>používá</strong> dvoufaktorové ověřování.
|
||||
twofa_not_enrolled=Váš účet aktuálně nepoužívá dvoufaktorové ověřování.
|
||||
twofa_disable=Zakázat dvoufaktorové ověřování
|
||||
twofa_scratch_token_regenerate=Obnovit pomocný token
|
||||
twofa_scratch_token_regenerated=Váš jednorázový obnovovací klíč je nyní %s. Uložte jej na bezpečném místě, protože se znovu nezobrazí.
|
||||
twofa_enroll=Povolit dvoufaktorové ověřování
|
||||
twofa_disable_note=Dvoufaktorové ověřování můžete zakázat, když bude potřeba.
|
||||
twofa_disable_desc=Zakážete-li dvoufaktorové ověřování, bude váš účet méně zabezpečený. Pokračovat?
|
||||
regenerate_scratch_token_desc=Jestli jste někam založili váš pomocný token nebo jste jej již použili k přihlášení, můžete jej resetovat zde.
|
||||
twofa_disabled=Dvoufaktorové ověřování bylo zakázáno.
|
||||
scan_this_image=Naskenujte tento obrázek s vaší ověřovací aplikací:
|
||||
or_enter_secret=Nebo zadejte tajný kód: %s
|
||||
then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci:
|
||||
passcode_invalid=Přístupový kód není platný. Zkuste to znovu.
|
||||
twofa_enrolled=Ve vašem účtu bylo povoleno dvoufaktorové ověřování. Uložte si pomocný token (%s) na bezpečném místě, protože bude zobrazen pouze jednou!
|
||||
twofa_failed_get_secret=Nepodařilo se získat tajemství.
|
||||
|
||||
webauthn_desc=Bezpečnostní klíče jsou hardwarová zařízení obsahující kryptografické klíče. Mohou být použity pro dvoufaktorové ověřování. Bezpečnostní klíče musí podporovat <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> standard.
|
||||
@ -1090,9 +1086,7 @@ tree_path_not_found_branch=Cesta %[1]s ve větvi %[2]s neexistuje
|
||||
tree_path_not_found_tag=Cesta %[1]s ve značce %[2]s neexistuje
|
||||
|
||||
transfer.accept=Přijmout převod
|
||||
transfer.accept_desc=Převést do „%s“
|
||||
transfer.reject=Odmítnout převod
|
||||
transfer.reject_desc=Zrušit převod do „%s“
|
||||
transfer.no_permission_to_accept=Nemáte oprávnění k přijetí tohoto převodu.
|
||||
transfer.no_permission_to_reject=Nemáte oprávnění k odmítnutí tohoto převodu.
|
||||
|
||||
@ -1227,7 +1221,6 @@ releases=Vydání
|
||||
tag=Značka
|
||||
released_this=vydal/a toto
|
||||
tagged_this=označil/a
|
||||
file.title=%s v %s
|
||||
file_raw=Surový
|
||||
file_history=Historie
|
||||
file_view_source=Zobrazit zdroj
|
||||
@ -1244,7 +1237,6 @@ ambiguous_runes_header=`Tento soubor obsahuje nejednoznačné znaky Unicode`
|
||||
ambiguous_runes_description=`Tento soubor obsahuje znaky Unicode, které mohou být zaměněny s jinými znaky. Pokud si myslíte, že je to záměrné, můžete toto varování bezpečně ignorovat. Použijte tlačítko Escape sekvence k jejich zobrazení.`
|
||||
invisible_runes_line=`Tento řádek má neviditelné znaky Unicode`
|
||||
ambiguous_runes_line=`Tento řádek má nejednoznačné znaky Unicode`
|
||||
ambiguous_character=`%[1]c [U+%04[1]X] je zaměnitelný s %[2]c [U+%04[2]X]`
|
||||
|
||||
escape_control_characters=Escape sekvence
|
||||
unescape_control_characters=Bez escape sekvencí
|
||||
@ -1854,9 +1846,7 @@ pulls.unrelated_histories=Sloučení selhalo: Hlavní a základní revize nesdí
|
||||
pulls.merge_out_of_date=Sloučení selhalo: Základ byl aktualizován při generování sloučení. Tip: Zkuste to znovu.
|
||||
pulls.head_out_of_date=Sloučení selhalo: Hlavní revize byla aktualizován při generování sloučení. Tip: Zkuste to znovu.
|
||||
pulls.has_merged=Chyba: Pull request byl sloučen, nelze znovu sloučit nebo změnit cílovou větev.
|
||||
pulls.push_rejected=Sloučení selhalo: Nahrání bylo zamítnuto. Zkontrolujte háčky Gitu pro tento repozitář.
|
||||
pulls.push_rejected_summary=Úplná zpráva o odmítnutí
|
||||
pulls.push_rejected_no_message=Sloučení se nezdařilo: Nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva.<br>Zkontrolujte háčky gitu pro tento repozitář
|
||||
pulls.open_unmerged_pull_exists=`Nemůžete provést operaci znovuotevření protože je tu čekající pull request (#%d) s identickými vlastnostmi.`
|
||||
pulls.status_checking=Některé kontroly jsou nedořešeny
|
||||
pulls.status_checks_success=Všechny kontroly byly úspěšné
|
||||
@ -1911,7 +1901,6 @@ milestones.no_due_date=Bez lhůty dokončení
|
||||
milestones.open=Otevřít
|
||||
milestones.close=Zavřít
|
||||
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
|
||||
milestones.completeness=%d%% Dokončeno
|
||||
milestones.create=Vytvořit milník
|
||||
milestones.title=Název
|
||||
milestones.desc=Popis
|
||||
@ -2134,7 +2123,6 @@ settings.pulls.default_delete_branch_after_merge=Ve výchozím nastavení mazat
|
||||
settings.pulls.default_allow_edits_from_maintainers=Ve výchozím nastavení povolit úpravy od správců
|
||||
settings.releases_desc=Povolit vydání v repozitáři
|
||||
settings.packages_desc=Povolit registr balíčků repozitáře
|
||||
settings.projects_desc=Povolit projekty v repozitáři
|
||||
settings.projects_mode_desc=Režim projektů (druhy projektů k zobrazení)
|
||||
settings.projects_mode_repo=Pouze projekty repozitáře
|
||||
settings.projects_mode_owner=Pouze projekty uživatele nebo organizace
|
||||
@ -2360,8 +2348,6 @@ settings.protected_branch.save_rule=Uložit pravidlo
|
||||
settings.protected_branch.delete_rule=Odstranit pravidlo
|
||||
settings.protected_branch_can_push=Povolit nahrání?
|
||||
settings.protected_branch_can_push_yes=Můžete nahrávat
|
||||
settings.protected_branch_can_push_no=Nemůžete nahrávat
|
||||
settings.branch_protection=Ochrana větví pro větev „<b>%s</b>“
|
||||
settings.protect_this_branch=Povolit ochranu větví
|
||||
settings.protect_this_branch_desc=Zabraňuje smazání a omezuje gitu nahrávání a slučování do větve.
|
||||
settings.protect_disable_push=Zakázat nahrávání
|
||||
@ -2373,7 +2359,6 @@ settings.protect_enable_merge_desc=Každému, kdo má přístup k zápisu, bude
|
||||
settings.protect_check_status_contexts=Povolit kontrolu stavu
|
||||
settings.protect_status_check_patterns=Vzorce kontroly stavu:
|
||||
settings.protect_status_check_patterns_desc=Zadejte vzory pro určení, které kontroly stavu musí projít před sloučením větví do větve, která odpovídá tomuto pravidlu. Každý řádek určuje vzor. Vzory nemohou být prázdné.
|
||||
settings.protect_check_status_contexts_desc=Požadovat kontrolu stavu před sloučením. Vyberte, jaké kontroly stavu musí projít před tím, než je možné větev sloučit do větve, která vyhovuje tomuto pravidlu. Pokud je povoleno, revize musí být nejprve nahrány do jiné větve, projít kontrolou stavu, a následné sloučeny nebo přímo nahrány do větve, která vyhovuje tomuto pravidlu. Pokud nejsou vybrány žádné kontexty, musí být poslední potvrzení úspěšné bez ohledu na kontext.
|
||||
settings.protect_check_status_contexts_list=Kontroly stavu pro tento repozitář zjištěné během posledního týdne
|
||||
settings.protect_status_check_matched=Odpovídá
|
||||
settings.protect_invalid_status_check_pattern=Neplatný vzor kontroly stavu: „%s“.
|
||||
@ -2398,7 +2383,6 @@ settings.delete_protected_branch=Vypnout ochranu
|
||||
settings.update_protect_branch_success=Ochrana větví pro větev „%s“ byla aktualizována.
|
||||
settings.remove_protected_branch_success=Ochrana větví pro větev „%s“ byla zakázána.
|
||||
settings.remove_protected_branch_failed=Odstranění ochranného pravidla větve „%s“ se nezdařilo.
|
||||
settings.protected_branch_deletion=Zakázat ochranu větví
|
||||
settings.protected_branch_deletion_desc=Zakázání ochrany větví umožní uživatelům s právem zápisu nahrávat do této větve. Pokračovat?
|
||||
settings.block_rejected_reviews=Blokovat sloučení při zamítavých posouzeních
|
||||
settings.block_rejected_reviews_desc=Slučování nebude možné, pokud o změny požádají oficiální posuzovatelé, i když je k dispozici dostatek schválení.
|
||||
@ -2408,7 +2392,6 @@ settings.block_outdated_branch=Blokovat sloučení, pokud je pull request zastar
|
||||
settings.block_outdated_branch_desc=Slučování nebude možné, pokud je hlavní větev za základní větví.
|
||||
settings.default_branch_desc=Vybrat výchozí větev repozitáře pro pull requesty a revize kódu:
|
||||
settings.merge_style_desc=Sloučit styly
|
||||
settings.default_merge_style_desc=Výchozí styl sloučení pro požadavky na natažení:
|
||||
settings.choose_branch=Vyberte větev…
|
||||
settings.no_protected_branch=Nejsou tu žádné chráněné větve.
|
||||
settings.edit_protected_branch=Upravit
|
||||
@ -2627,7 +2610,6 @@ tag.create_success=Značka „%s“ byla vytvořena.
|
||||
|
||||
topic.manage_topics=Spravovat témata
|
||||
topic.done=Hotovo
|
||||
topic.count_prompt=Nelze vybrat více než 25 témat
|
||||
topic.format_prompt=Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a tečky („.“) a může být dlouhé až 35 znaků. Písmena musí být malá.
|
||||
|
||||
find_file.go_to_file=Přejít na soubor
|
||||
@ -2725,7 +2707,6 @@ teams.leave.detail=Opustit %s?
|
||||
teams.can_create_org_repo=Vytvořit repozitáře
|
||||
teams.can_create_org_repo_helper=Členové mohou vytvářet nové repozitáře v organizaci. Tvůrce získá přístup správce do nového repozitáře.
|
||||
teams.none_access=Bez přístupu
|
||||
teams.none_access_helper=Členové nemohou prohlížet ani dělat žádnou jinou akci pro tuto jednotku.
|
||||
teams.general_access=Obecný přístup
|
||||
teams.general_access_helper=O oprávnění členů bude rozhodnuto níže uvedenou tabulkou oprávnění.
|
||||
teams.read_access=Čtení
|
||||
@ -3251,7 +3232,6 @@ monitor.next=Příští čas spuštění
|
||||
monitor.previous=Předešlý čas spuštění
|
||||
monitor.execute_times=Vykonání
|
||||
monitor.process=Spuštěné procesy
|
||||
monitor.stacktrace=Výpisy zásobníku
|
||||
monitor.processes_count=%d procesů
|
||||
monitor.download_diagnosis_report=Stáhnout diagnosttickou zprávu
|
||||
monitor.desc=Popis
|
||||
@ -3259,8 +3239,6 @@ monitor.start=Čas zahájení
|
||||
monitor.execute_time=Doba provádění
|
||||
monitor.last_execution_result=Výsledek
|
||||
monitor.process.cancel=Zrušit proces
|
||||
monitor.process.cancel_desc=Zrušení procesu může způsobit ztrátu dat
|
||||
monitor.process.cancel_notices=Zrušit: <strong>%s</strong>?
|
||||
monitor.process.children=Potomek
|
||||
|
||||
monitor.queues=Fronty
|
||||
@ -3362,7 +3340,6 @@ raw_minutes=minut
|
||||
|
||||
[dropzone]
|
||||
default_message=Přetáhněte soubory nebo klikněte sem pro nahrání.
|
||||
invalid_input_type=Nemůžete nahrávat soubory tohoto typu.
|
||||
file_too_big=Velikost souboru ({{filesize}} MB) je vyšší než maximální velikost ({{maxFilesize}} MB).
|
||||
remove_file=Smazat soubor
|
||||
|
||||
|
@ -779,7 +779,7 @@ add_email_success=Die neue E-Mail-Addresse wurde hinzugefügt.
|
||||
email_preference_set_success=E-Mail-Einstellungen wurden erfolgreich aktualisiert.
|
||||
add_openid_success=Die neue OpenID-Adresse wurde hinzugefügt.
|
||||
keep_email_private=E-Mail-Adresse verbergen
|
||||
keep_email_private_popup=Dies wird Deine E-Mail-Adresse nicht nur in Deinem Profil ausblenden, sondern auch, wenn Du einen Pull Request erstellst oder eine Datei über das Web-Interface bearbeitest. Gepushte Commits werden nicht geändert.
|
||||
keep_email_private_popup=Dies wird Deine E-Mail-Adresse nicht nur in Deinem Profil ausblenden, sondern auch, wenn Du einen Pull Request erstellst oder eine Datei über das Web-Interface bearbeitest. Gepushte Commits werden nicht geändert. Benutze %s in Commits, um sie Deinem Profil zuzuordnen.
|
||||
openid_desc=Mit OpenID kannst du dich über einen Drittanbieter authentifizieren.
|
||||
|
||||
manage_ssh_keys=SSH-Schlüssel verwalten
|
||||
@ -907,7 +907,6 @@ oauth2_client_secret_hint=Das Secret wird nach dem Verlassen oder Aktualisieren
|
||||
oauth2_application_edit=Bearbeiten
|
||||
oauth2_application_create_description=OAuth2 Anwendungen geben deiner Drittanwendung Zugriff auf Benutzeraccounts dieser Gitea-Instanz.
|
||||
oauth2_application_remove_description=Das Entfernen einer OAuth2-Anwendung hat zur Folge, dass diese nicht mehr auf autorisierte Benutzeraccounts auf dieser Instanz zugreifen kann. Möchtest Du fortfahren?
|
||||
oauth2_application_locked=Wenn es in der Konfiguration aktiviert ist, registriert Gitea einige OAuth2-Anwendungen beim Starten vor. Um unerwartetes Verhalten zu verhindern, können diese weder bearbeitet noch entfernt werden. Weitere Informationen findest Du in der OAuth2-Dokumentation.
|
||||
|
||||
authorized_oauth2_applications=Autorisierte OAuth2-Anwendungen
|
||||
authorized_oauth2_applications_description=Den folgenden Drittanbieter-Apps hast Du Zugriff auf Deinen persönlichen Gitea-Account gewährt. Bitte widerrufe die Autorisierung für Apps, die Du nicht mehr nutzt.
|
||||
@ -916,23 +915,18 @@ revoke_oauth2_grant=Autorisierung widerrufen
|
||||
revoke_oauth2_grant_description=Wenn du die Autorisierung widerrufst, kann die Anwendung nicht mehr auf deine Daten zugreifen. Bist du dir sicher?
|
||||
revoke_oauth2_grant_success=Zugriff erfolgreich widerrufen.
|
||||
|
||||
twofa_desc=Zwei-Faktor-Authentifizierung trägt zu einer höheren Accountsicherheit bei.
|
||||
twofa_recovery_tip=Wenn du dein Gerät verlierst, kannst du einen einmalig verwendbaren Wiederherstellungsschlüssel nutzen, um den Zugriff auf dein Konto wiederherzustellen.
|
||||
twofa_is_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung <strong>eingeschaltet</strong>.
|
||||
twofa_not_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung momentan nicht eingeschaltet.
|
||||
twofa_disable=Zwei-Faktor-Authentifizierung deaktivieren
|
||||
twofa_scratch_token_regenerate=Neues Einmalpasswort erstellen
|
||||
twofa_scratch_token_regenerated=Dein temporärer Token ist jetzt %s. Speichere ihn an einem sicheren Ort, er wird nie wieder angezeigt.
|
||||
twofa_enroll=Zwei-Faktor-Authentifizierung aktivieren
|
||||
twofa_disable_note=Du kannst die Zwei-Faktor-Authentifizierung auch wieder deaktivieren.
|
||||
twofa_disable_desc=Wenn du die Zwei-Faktor-Authentifizierung deaktivierst, wird die Sicherheit deines Kontos verringert. Fortfahren?
|
||||
regenerate_scratch_token_desc=Wenn du dein Einmalpasswort verlegt oder es bereits benutzt hast, kannst du es hier zurücksetzen.
|
||||
twofa_disabled=Zwei-Faktor-Authentifizierung wurde deaktiviert.
|
||||
scan_this_image=Scanne diese Grafik mit deiner Authentifizierungs-App:
|
||||
or_enter_secret=Oder gib das Secret ein: %s
|
||||
then_enter_passcode=Und gebe dann die angezeigte PIN der Anwendung ein:
|
||||
passcode_invalid=Die PIN ist falsch. Probiere es erneut.
|
||||
twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird.
|
||||
twofa_failed_get_secret=Fehler beim Abrufen des Secrets.
|
||||
|
||||
webauthn_desc=Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beeinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard „<a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn</a>“ unterstützen.
|
||||
@ -1080,9 +1074,7 @@ tree_path_not_found_branch=Pfad %[1]s existiert nicht in Branch %[2]s
|
||||
tree_path_not_found_tag=Pfad %[1]s existiert nicht in Tag %[2]s
|
||||
|
||||
transfer.accept=Übertragung Akzeptieren
|
||||
transfer.accept_desc=`Übertragung nach "%s"`
|
||||
transfer.reject=Übertragung Ablehnen
|
||||
transfer.reject_desc=Übertragung nach "%s " abbrechen
|
||||
transfer.no_permission_to_accept=Du hast keine Berechtigung, diesen Transfer anzunehmen.
|
||||
transfer.no_permission_to_reject=Du hast keine Berechtigung, diesen Transfer abzulehnen.
|
||||
|
||||
@ -1217,7 +1209,6 @@ releases=Releases
|
||||
tag=Tag
|
||||
released_this=hat released
|
||||
tagged_this=hat getaggt
|
||||
file.title=%s an %s
|
||||
file_raw=Originalformat
|
||||
file_history=Verlauf
|
||||
file_view_source=Quelltext anzeigen
|
||||
@ -1231,7 +1222,6 @@ ambiguous_runes_header=`Diese Datei enthält mehrdeutige Unicode-Zeichen`
|
||||
ambiguous_runes_description=`Diese Datei enthält Unicode-Zeichen, die mit anderen Zeichen verwechselt werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen.`
|
||||
invisible_runes_line=`Diese Zeile enthält unsichtbare Unicode-Zeichen`
|
||||
ambiguous_runes_line=`Diese Zeile enthält mehrdeutige Unicode-Zeichen`
|
||||
ambiguous_character=`%[1]c [U+%04[1]X] kann mit %[2]c [U+%04[2]X] verwechselt werden`
|
||||
|
||||
escape_control_characters=Escapen
|
||||
unescape_control_characters=Unescapen
|
||||
@ -1835,9 +1825,7 @@ pulls.unrelated_histories=Merge fehlgeschlagen: Der Head des Merges und die Basi
|
||||
pulls.merge_out_of_date=Merge fehlgeschlagen: Während des Mergens wurde die Basis aktualisiert. Hinweis: Versuche es erneut.
|
||||
pulls.head_out_of_date=Mergen fehlgeschlagen: Der Head wurde aktualisiert während der Merge erstellt wurde. Tipp: Versuche es erneut.
|
||||
pulls.has_merged=Fehler: Der Pull-Request wurde gemerged, du kannst den Zielbranch nicht wieder mergen oder ändern.
|
||||
pulls.push_rejected=Mergen fehlgeschlagen: Der Push wurde abgelehnt. Überprüfe die Git Hooks für dieses Repository.
|
||||
pulls.push_rejected_summary=Vollständige Ablehnungsmeldung
|
||||
pulls.push_rejected_no_message=Mergen fehlgeschlagen: Der Push wurde abgelehnt, aber es gab keine Fehlermeldung.<br>Überprüfe die Git Hooks für dieses Repository
|
||||
pulls.open_unmerged_pull_exists=`Du kannst diesen Pull-Request nicht erneut öffnen, da noch ein anderer (#%d) mit identischen Eigenschaften offen ist.`
|
||||
pulls.status_checking=Einige Prüfungen sind noch ausstehend
|
||||
pulls.status_checks_success=Alle Prüfungen waren erfolgreich
|
||||
@ -1891,7 +1879,6 @@ milestones.no_due_date=Kein Fälligkeitsdatum
|
||||
milestones.open=Öffnen
|
||||
milestones.close=Schließen
|
||||
milestones.new_subheader=Benutze Meilensteine, um Issues zu organisieren und den Fortschritt darzustellen.
|
||||
milestones.completeness=%d%% abgeschlossen
|
||||
milestones.create=Meilenstein erstellen
|
||||
milestones.title=Titel
|
||||
milestones.desc=Beschreibung
|
||||
@ -2076,7 +2063,6 @@ settings.push_mirror_sync_in_progress=Aktuell werden Änderungen auf %s gepusht.
|
||||
settings.site=Webseite
|
||||
settings.update_settings=Einstellungen speichern
|
||||
settings.update_mirror_settings=Mirror-Einstellungen aktualisieren
|
||||
settings.branches.switch_default_branch=Standardbranch wechseln
|
||||
settings.branches.update_default_branch=Standardbranch aktualisieren
|
||||
settings.branches.add_new_rule=Neue Regel hinzufügen
|
||||
settings.advanced_settings=Erweiterte Einstellungen
|
||||
@ -2113,7 +2099,6 @@ settings.pulls.default_delete_branch_after_merge=Standardmäßig bei Pull-Reques
|
||||
settings.pulls.default_allow_edits_from_maintainers=Änderungen von Maintainern standardmäßig erlauben
|
||||
settings.releases_desc=Repository-Releases aktivieren
|
||||
settings.packages_desc=Repository Packages Registry aktivieren
|
||||
settings.projects_desc=Repository-Projekte aktivieren
|
||||
settings.projects_mode_desc=Projekte-Modus (welche Art Projekte angezeigt werden sollen)
|
||||
settings.projects_mode_repo=Nur Repo-Projekte
|
||||
settings.projects_mode_owner=Nur Benutzer- oder Organisations-Projekte
|
||||
@ -2337,10 +2322,6 @@ settings.branches=Branches
|
||||
settings.protected_branch=Branch-Schutz
|
||||
settings.protected_branch.save_rule=Regel speichern
|
||||
settings.protected_branch.delete_rule=Regel löschen
|
||||
settings.protected_branch_can_push=Push erlauben?
|
||||
settings.protected_branch_can_push_yes=Du kannst pushen
|
||||
settings.protected_branch_can_push_no=Du kannst nicht pushen
|
||||
settings.branch_protection=Branch-Schutz für Branch „<b>%s</b>“
|
||||
settings.protect_this_branch=Branch-Schutz aktivieren
|
||||
settings.protect_this_branch_desc=Verhindert das Löschen und schränkt Git auf Push- und Merge-Änderungen auf dem Branch ein.
|
||||
settings.protect_disable_push=Push deaktivieren
|
||||
@ -2352,7 +2333,6 @@ settings.protect_enable_merge_desc=Jeder mit Schreibzugriff darf die Pull-Reques
|
||||
settings.protect_check_status_contexts=Statusprüfungen aktivieren
|
||||
settings.protect_status_check_patterns=Statuscheck-Muster:
|
||||
settings.protect_status_check_patterns_desc=Gib Muster ein, um festzulegen, welche Statusüberprüfungen durchgeführt werden müssen, bevor Branches in einen Branch, der dieser Regel entspricht, gemerged werden können. Jede Zeile gibt ein Muster an. Muster dürfen nicht leer sein.
|
||||
settings.protect_check_status_contexts_desc=Vor dem Mergen müssen Statusprüfungen bestanden werden. Wähle aus, welche Statusprüfungen erfolgreich durchgeführt werden müssen, bevor Branches in einen anderen gemergt werden können, der dieser Regel entspricht. Wenn aktiviert, müssen Commits zuerst auf einen anderen Branch gepusht werden, dann nach bestandener Statusprüfung gemergt oder direkt auf einen Branch gepusht werden, der dieser Regel entspricht. Wenn kein Kontext ausgewählt ist, muss der letzte Commit unabhängig vom Kontext erfolgreich sein.
|
||||
settings.protect_check_status_contexts_list=Statusprüfungen, die in der letzten Woche für dieses Repository gefunden wurden
|
||||
settings.protect_status_check_matched=Übereinstimmung
|
||||
settings.protect_invalid_status_check_pattern=Ungültiges Muster: "%s".
|
||||
@ -2372,12 +2352,9 @@ settings.protect_protected_file_patterns=Geschützte Dateimuster (durch Semikolo
|
||||
settings.protect_protected_file_patterns_desc=Geschützte Dateien dürfen nicht direkt geändert werden, auch wenn der Benutzer Rechte hat, Dateien in diesem Branch hinzuzufügen, zu bearbeiten oder zu löschen. Mehrere Muster können mit Semikolon (';') getrennt werden. Siehe <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> Dokumentation zur Mustersyntax. Beispiele: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Ungeschützte Dateimuster (durch Semikolon ';' getrennt):
|
||||
settings.protect_unprotected_file_patterns_desc=Ungeschützte Dateien, die direkt geändert werden dürfen, wenn der Benutzer Schreibzugriff hat, können die Push-Beschränkung umgehen. Mehrere Muster können mit Semikolon (';') getrennt werden. Siehe <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> Dokumentation zur Mustersyntax. Beispiele: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.add_protected_branch=Schutz aktivieren
|
||||
settings.delete_protected_branch=Schutz deaktivieren
|
||||
settings.update_protect_branch_success=Branchschutzregel "%s" wurde geändert.
|
||||
settings.remove_protected_branch_success=Branchschutzregel "%s" wurde deaktiviert.
|
||||
settings.remove_protected_branch_failed=Entfernen der Branchschutzregel "%s" fehlgeschlagen.
|
||||
settings.protected_branch_deletion=Branch-Schutz deaktivieren
|
||||
settings.protected_branch_deletion_desc=Wenn du den Branch-Schutz deaktivierst, können alle Nutzer mit Schreibrechten auf den Branch pushen. Fortfahren?
|
||||
settings.block_rejected_reviews=Merge bei abgelehnten Reviews blockieren
|
||||
settings.block_rejected_reviews_desc=Mergen ist nicht möglich, wenn Änderungen durch offizielle Reviewer angefragt werden, auch wenn es genügend Zustimmungen gibt.
|
||||
@ -2387,7 +2364,6 @@ settings.block_outdated_branch=Merge blockieren, wenn der Pull-Request veraltet
|
||||
settings.block_outdated_branch_desc=Mergen ist nicht möglich, wenn der Head-Branch hinter dem Basis-Branch ist.
|
||||
settings.default_branch_desc=Wähle einen Standardbranch für Pull-Requests und Code-Commits:
|
||||
settings.merge_style_desc=Merge-Styles
|
||||
settings.default_merge_style_desc=Standard Mergeverhalten für Pull Requests:
|
||||
settings.choose_branch=Branch wählen…
|
||||
settings.no_protected_branch=Es gibt keine geschützten Branches.
|
||||
settings.edit_protected_branch=Bearbeiten
|
||||
@ -2606,7 +2582,6 @@ tag.create_success=Tag "%s" wurde erstellt.
|
||||
|
||||
topic.manage_topics=Themen verwalten
|
||||
topic.done=Fertig
|
||||
topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen
|
||||
topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) und Punkte ('.') enthalten und bis zu 35 Zeichen lang sein. Nur Kleinbuchstaben sind zulässig.
|
||||
|
||||
find_file.go_to_file=Datei suchen
|
||||
@ -2704,7 +2679,6 @@ teams.leave.detail=%s verlassen?
|
||||
teams.can_create_org_repo=Repositories erstellen
|
||||
teams.can_create_org_repo_helper=Mitglieder können neue Repositories in der Organisation erstellen. Der Ersteller erhält Administrator-Zugriff auf das neue Repository.
|
||||
teams.none_access=Kein Zugriff
|
||||
teams.none_access_helper=Teammitglieder haben keinen Zugriff auf diese Einheit.
|
||||
teams.general_access=Allgemeiner Zugriff
|
||||
teams.general_access_helper=Mitgliederberechtigungen werden durch folgende Berechtigungstabelle festgelegt.
|
||||
teams.read_access=Lesen
|
||||
@ -3223,7 +3197,6 @@ monitor.next=Nächste Ausführung
|
||||
monitor.previous=Letzte Ausführung
|
||||
monitor.execute_times=Ausführungen
|
||||
monitor.process=Laufende Prozesse
|
||||
monitor.stacktrace=Stacktraces
|
||||
monitor.processes_count=%d Prozesse
|
||||
monitor.download_diagnosis_report=Diagnosebericht herunterladen
|
||||
monitor.desc=Beschreibung
|
||||
@ -3231,8 +3204,6 @@ monitor.start=Startzeit
|
||||
monitor.execute_time=Ausführungszeit
|
||||
monitor.last_execution_result=Ergebnis
|
||||
monitor.process.cancel=Prozess abbrechen
|
||||
monitor.process.cancel_desc=Abbrechen eines Prozesses kann Datenverlust verursachen
|
||||
monitor.process.cancel_notices=Abbrechen: <strong>%s</strong>?
|
||||
monitor.process.children=Subprozesse
|
||||
|
||||
monitor.queues=Warteschlangen
|
||||
@ -3331,8 +3302,6 @@ raw_minutes=Minuten
|
||||
|
||||
[dropzone]
|
||||
default_message=Zum Hochladen hier klicken oder Datei ablegen.
|
||||
invalid_input_type=Dateien dieses Dateityps können nicht hochgeladen werden.
|
||||
file_too_big=Dateigröße ({{filesize}} MB) überschreitet die Maximalgröße ({{maxFilesize}} MB).
|
||||
remove_file=Datei entfernen
|
||||
|
||||
[notification]
|
||||
|
@ -195,7 +195,7 @@ app_desc=Μια ανώδυνη, αυτο-φιλοξενούμενη υπηρεσ
|
||||
install=Εύκολο στην εγκατάσταση
|
||||
install_desc=Απλά <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">εκτελέστε το αρχείο προγράμματος</a> για την πλατφόρμα σας, χρήσιμοποιήστε το με το <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a>, ή εγκαταστήστε το <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">πακέτο</a>.
|
||||
platform=Πολυπλατφορμικό
|
||||
platform_desc=Ο Gitea τρέχει οπουδήποτε <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> μπορεί να γίνει compile για: Windows, macOS, Linux, ARM, κλπ. Επιλέξτε αυτό που αγαπάτε!
|
||||
platform_desc=Ο Gitea τρέχει οπουδήποτε <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> μπορεί να γίνει compile για: Windows, macOS, Linux, ARM, κλπ. Επιλέξτε αυτό που αγαπάτε!
|
||||
lightweight=Ελαφρύ
|
||||
lightweight_desc=Gitea έχει χαμηλές ελάχιστες απαιτήσεις και μπορεί να τρέξει σε ένα οικονομικό Raspberry Pi. Εξοικονομήστε ενέργεια!
|
||||
license=Ανοικτού κώδικα
|
||||
@ -619,11 +619,9 @@ applications=Εφαρμογές
|
||||
orgs=Διαχείριση Οργανισμών
|
||||
repos=Αποθετήρια
|
||||
delete=Διαγραφή Λογαριασμού
|
||||
twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων
|
||||
account_link=Συνδεδεμένοι Λογαριασμοί
|
||||
organization=Οργανισμοί
|
||||
uid=UID
|
||||
webauthn=Κλειδιά Ασφαλείας
|
||||
|
||||
public_profile=Δημόσιο Προφίλ
|
||||
biography_placeholder=Πείτε μας λίγο για τον εαυτό σας! (Μπορείτε να γράψετε με Markdown)
|
||||
@ -855,23 +853,19 @@ revoke_oauth2_grant=Ανάκληση Πρόσβασης
|
||||
revoke_oauth2_grant_description=Η ανάκληση πρόσβασης για αυτή την εξωτερική εφαρμογή θα αποτρέψει αυτή την εφαρμογή από την πρόσβαση στα δεδομένα σας. Σίγουρα;
|
||||
revoke_oauth2_grant_success=Η πρόσβαση ανακλήθηκε επιτυχώς.
|
||||
|
||||
twofa_desc=Ο έλεγχος ταυτότητας δύο παραγόντων ενισχύει την ασφάλεια του λογαριασμού σας.
|
||||
twofa_recovery_tip=Αν χάσετε τη συσκευή σας, θα είστε σε θέση να χρησιμοποιήσετε ένα κλειδί ανάκτησης μιας χρήσης για να ανακτήσετε την πρόσβαση στο λογαριασμό σας.
|
||||
twofa_is_enrolled=Ο λογαριασμός σας είναι <strong>εγγεγραμμένος</strong> σε έλεγχο ταυτότητας δύο παραγόντων.
|
||||
twofa_not_enrolled=Ο λογαριασμός σας δεν είναι εγγεγραμμένος σε έλεγχο ταυτότητας δύο παραγόντων.
|
||||
twofa_disable=Απενεργοποίηση Ταυτοποίησης Δύο Παραμέτρων
|
||||
twofa_scratch_token_regenerate=Αναδημιουργία Διακριτικού Μίας Χρήσης
|
||||
twofa_scratch_token_regenerated=Το κλειδί ανάκτησης μιας χρήσης είναι τώρα %s. Αποθηκεύστε το σε ασφαλές μέρος, καθώς δε θα εμφανιστεί ξανά.
|
||||
twofa_enroll=Εγγραφή στην ταυτοποίηση δύο παραγόντων
|
||||
twofa_disable_note=Μπορείτε να απενεργοποιήσετε την ταυτοποίηση δύο παραγόντων αν χρειαστεί.
|
||||
twofa_disable_desc=Η απενεργοποίηση της ταυτοποίησης δύο παραγόντων θα καταστήσει τον λογαριασμό σας λιγότερο ασφαλή. Συνέχεια;
|
||||
regenerate_scratch_token_desc=Αν χάσατε το διακριτικό μίας χρήσης σας ή το έχετε ήδη χρησιμοποιήσει για να συνδεθείτε μπορείτε να το επαναφέρετε εδώ.
|
||||
twofa_disabled=Η ταυτοποίηση δύο παραγόντων έχει απενεργοποιηθεί.
|
||||
scan_this_image=Σαρώστε αυτή την εικόνα με την εφαρμογή ταυτοποίησης:
|
||||
or_enter_secret=Ή εισάγετε το μυστικό: %s
|
||||
then_enter_passcode=Και εισάγετε τον κωδικό που εμφανίζεται στην εφαρμογή:
|
||||
passcode_invalid=Ο κωδικός είναι λάθος. Δοκιμάστε ξανά.
|
||||
twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ασφαλές μέρος καθώς εμφανίζεται μόνο μία φορά!
|
||||
twofa_failed_get_secret=Αποτυχία λήψης μυστικού.
|
||||
|
||||
webauthn_desc=Τα κλειδιά ασφαλείας είναι συσκευές που περιέχουν κρυπτογραφικά κλειδιά. Μπορούν να χρησιμοποιηθούν για έλεγχο ταυτότητας δύο παραγόντων. Τα κλειδιά ασφαλείας πρέπει να υποστηρίζουν το <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">πρότυπο WebAuthn Authn Authenticator</a>.
|
||||
@ -1013,9 +1007,7 @@ tree_path_not_found_branch=Η διαδρομή %[1]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=Δεν έχετε άδεια να απορρίψετε αυτή τη μεταφορά.
|
||||
|
||||
@ -1148,7 +1140,6 @@ releases=Κυκλοφορίες
|
||||
tag=Ετικέτα
|
||||
released_this=κυκλοφόρησε αυτό
|
||||
tagged_this=πρόσθεσε ετικέτα για το
|
||||
file.title=%s στο %s
|
||||
file_raw=Ακατέργαστο
|
||||
file_history=Ιστορικό
|
||||
file_view_source=Προβολή Πηγαίου
|
||||
@ -1162,7 +1153,6 @@ ambiguous_runes_header=`Αυτό το αρχείο περιέχει ασαφεί
|
||||
ambiguous_runes_description=`Αυτό το αρχείο περιέχει χαρακτήρες Unicode που μπορεί να συγχέονται με άλλους χαρακτήρες. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.`
|
||||
invisible_runes_line=`Αυτή η γραμμή έχει αόρατους χαρακτήρες unicode `
|
||||
ambiguous_runes_line=`Αυτή η γραμμή έχει ασαφείς χαρακτήρες unicode `
|
||||
ambiguous_character=`ο %[1]c [U+%04[1]X] μπορεί να μπερδευτεί με τον %[2]c [U+%04[2]X]`
|
||||
|
||||
escape_control_characters=Escape
|
||||
unescape_control_characters=Unescape
|
||||
@ -1754,9 +1744,7 @@ pulls.unrelated_histories=H Συγχώνευση Απέτυχε: Η κεφαλή
|
||||
pulls.merge_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δημιουργία της συγχώνευσης, η βάση ενημερώθηκε. Συμβουλή: Δοκιμάστε ξανά.
|
||||
pulls.head_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δημιουργία της συγχώνευσης, το HEAD ενημερώθηκε. Συμβουλή: Δοκιμάστε ξανά.
|
||||
pulls.has_merged=Αποτυχία: Το pull request έχει συγχωνευθεί, δεν είναι δυνατή η συγχώνευση ξανά ή να αλλάξει ο κλάδος προορισμού.
|
||||
pulls.push_rejected=Η συγχώνευση απέτυχε: Η ώθηση απορρίφθηκε. Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο.
|
||||
pulls.push_rejected_summary=Μήνυμα Πλήρους Απόρριψης
|
||||
pulls.push_rejected_no_message=H Συγχώνευση Aπέτυχε: Η ώθηση απορρίφθηκε, αλλά δεν υπήρχε απομακρυσμένο μήνυμα.<br>Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο
|
||||
pulls.open_unmerged_pull_exists=`Δεν μπορείτε να ανοίξετε εκ νέου, επειδή υπάρχει ένα εκκρεμές pull request (#%d) με πανομοιότυπες ιδιότητες.`
|
||||
pulls.status_checking=Μερικοί έλεγχοι εκκρεμούν
|
||||
pulls.status_checks_success=Όλοι οι έλεγχοι ήταν επιτυχείς
|
||||
@ -1810,7 +1798,6 @@ milestones.no_due_date=Δεν υπάρχει ημερομηνία παράδοσ
|
||||
milestones.open=Άνοιγμα
|
||||
milestones.close=Κλείσιμο
|
||||
milestones.new_subheader=Τα ορόσημα μπορούν να σας βοηθήσουν να οργανώσετε τα ζητήματα και να παρακολουθείτε την πρόοδό τους.
|
||||
milestones.completeness=%d%% Ολοκληρώθηκε
|
||||
milestones.create=Δημιουργία Ορόσημου
|
||||
milestones.title=Τίτλος
|
||||
milestones.desc=Περιγραφή
|
||||
@ -2022,7 +2009,6 @@ settings.pulls.default_delete_branch_after_merge=Διαγραφή του κλά
|
||||
settings.pulls.default_allow_edits_from_maintainers=Να επιτρέπονται από προεπιλογή οι αλλαγές από τους συντηρητές
|
||||
settings.releases_desc=Ενεργοποίηση Κυκλοφοριών Αποθετηρίου
|
||||
settings.packages_desc=Ενεργοποίηση Μητρώου Πακέτων Αποθετηρίου
|
||||
settings.projects_desc=Ενεργοποίηση Έργων Αποθετηρίου
|
||||
settings.projects_mode_all=Όλα τα έργα
|
||||
settings.actions_desc=Ενεργοποίηση Δράσεων Αποθετηρίου
|
||||
settings.admin_settings=Ρυθμίσεις Διαχειριστή
|
||||
@ -2243,8 +2229,6 @@ 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=Αποτρέπει τη διαγραφή και περιορίζει το Git push και συγχώνευση στον κλάδο.
|
||||
settings.protect_disable_push=Απενεργοποίηση Ώθησης
|
||||
@ -2256,7 +2240,6 @@ settings.protect_enable_merge_desc=Οποιοσδήποτε έχει πρόσβ
|
||||
settings.protect_check_status_contexts=Ενεργοποίηση Ελέγχου Κατάστασης
|
||||
settings.protect_status_check_patterns=Μοτίβα ελέγχου κατάστασης:
|
||||
settings.protect_status_check_patterns_desc=Ορίστε μοτίβα για να καθορίσετε ποιοι έλεγχοι κατάστασης πρέπει να περάσουν πριν οι κλάδοι να μπορούν να συγχωνευτούν σε έναν κλάδο που ταιριάζει με αυτόν τον κανόνα. Κάθε γραμμή καθορίζει ένα μοτίβο. Τα μοτίβα δεν μπορούν να είναι κενά.
|
||||
settings.protect_check_status_contexts_desc=Απαιτείται έλεγχος κατάστασης για να περάσει το pull request πριν από τη συγχώνευση. Επιλέξτε ποιοι έλεγχοι κατάστασης πρέπει να περάσουν πριν κλάδοι μπορούν να συγχωνευτούν σε έναν κλάδο που ταιριάζει με αυτόν τον κανόνα. Όταν είναι ενεργοποιημένο, οι υποβολές πρέπει πρώτα να γίνονται push σε άλλο κλάδο, στη συνέχεια, να συγχωνεύονται ή γίνονται push απευθείας σε ένα κλάδο που ταιριάζει με αυτόν τον κανόνα, αφού έχουν ολοκληρωθεί οι έλεγχοι κατάστασης. Αν δεν επιλεχθεί κανένα πλαίσιο, η τελευταία υποβολή πρέπει να είναι επιτυχής ανεξάρτητα από το πλαίσιο.
|
||||
settings.protect_check_status_contexts_list=Έλεγχοι κατάστασης που βρέθηκαν την τελευταία εβδομάδα για αυτό το αποθετήριο
|
||||
settings.protect_status_check_matched=Ταιριάζει
|
||||
settings.protect_invalid_status_check_pattern=Μη έγκυρο μοτίβο ελέγχου κατάστασης: "%s".
|
||||
@ -2279,7 +2262,6 @@ settings.delete_protected_branch=Απενεργοποίηση προστασία
|
||||
settings.update_protect_branch_success=Η προστασία κλάδου για τον κανόνα "%s" ενημερώθηκε.
|
||||
settings.remove_protected_branch_success=Η προστασία κλάδου για τον κανόνα "%s" αφαιρέθηκε.
|
||||
settings.remove_protected_branch_failed=Η αφαίρεση του κανόνα προστασίας κλάδου "%s" απέτυχε.
|
||||
settings.protected_branch_deletion=Απενεργοποίηση Προστασίας Κλάδου
|
||||
settings.protected_branch_deletion_desc=Η απενεργοποίηση της προστασίας του κλάδου επιτρέπει στους χρήστες με άδεια εγγραφής να κάνουν push στον κλάδο. Συνέχεια;
|
||||
settings.block_rejected_reviews=Φραγή συγχώνευσης αν υπάρχουν απορριπτικές αξιολογήσεις
|
||||
settings.block_rejected_reviews_desc=Η συγχώνευση δεν θα είναι δυνατή όταν οι αλλαγές ζητούνται από τους επίσημους εξεταστές, ακόμη και αν υπάρχουν αρκετές εγκρίσεις.
|
||||
@ -2289,7 +2271,6 @@ settings.block_outdated_branch=Φραγή συγχώνευσης αν το pull
|
||||
settings.block_outdated_branch_desc=Η συγχώνευση δεν θα είναι δυνατή όταν ο κλάδος κεφαλής είναι πίσω από τον βασικό κλάδο.
|
||||
settings.default_branch_desc=Επιλέξτε έναν προεπιλεγμένο κλάδο αποθετηρίου για pull requests και υποβολές κώδικα:
|
||||
settings.merge_style_desc=Συγχώνευση Στυλ
|
||||
settings.default_merge_style_desc=Προεπιλεγμένο στυλ συγχώνευσης για pull requests:
|
||||
settings.choose_branch=Επιλέξτε έναν κλάδο…
|
||||
settings.no_protected_branch=Δεν υπάρχουν προστατευμένοι κλάδοι.
|
||||
settings.edit_protected_branch=Επεξεργασία
|
||||
@ -2507,7 +2488,6 @@ tag.create_success=Η ετικέτα "%s" δημιουργήθηκε.
|
||||
|
||||
topic.manage_topics=Διαχείριση Θεμάτων
|
||||
topic.done=Ολοκληρώθηκε
|
||||
topic.count_prompt=Δεν μπορείτε να επιλέξετε περισσότερα από 25 θέματα
|
||||
topic.format_prompt=Τα θέματα πρέπει να ξεκινούν με γράμμα ή αριθμό, μπορούν να περιλαμβάνουν παύλες ('-') και τελείες ('.'), μπορεί να είναι μέχρι 35 χαρακτήρες. Τα γράμματα πρέπει να είναι πεζά.
|
||||
|
||||
find_file.go_to_file=Αναζήτηση αρχείου
|
||||
@ -2597,7 +2577,6 @@ 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=Ανάγνωση
|
||||
@ -3110,7 +3089,6 @@ monitor.next=Επόμενη Ώρα
|
||||
monitor.previous=Προηγούμενη Ώρα
|
||||
monitor.execute_times=Εκτελέσεις
|
||||
monitor.process=Εκτελούμενες Διεργασίες
|
||||
monitor.stacktrace=Ιχνηλατήσεις Στοίβας
|
||||
monitor.processes_count=%d Διεργασίες
|
||||
monitor.download_diagnosis_report=Λήψη αναφοράς διάγνωσης
|
||||
monitor.desc=Περιγραφή
|
||||
@ -3118,8 +3096,6 @@ 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=Ουρές
|
||||
@ -3212,7 +3188,6 @@ raw_minutes=λεπτά
|
||||
|
||||
[dropzone]
|
||||
default_message=Σύρετε αρχεία ή κάντε κλικ εδώ για να τα ανεβάσετε.
|
||||
invalid_input_type=Δεν μπορείτε να ανεβάσετε αρχεία αυτού του τύπου.
|
||||
file_too_big=Το μέγεθος αρχείου ({{filesize}} MB) υπερβαίνει το μέγιστο μέγεθος ({{maxFilesize}} MB).
|
||||
remove_file=Αφαίρεση αρχείου
|
||||
|
||||
|
@ -628,6 +628,7 @@ org_still_own_repo = "This organization still owns one or more repositories, del
|
||||
org_still_own_packages = "This organization still owns one or more packages, delete them first."
|
||||
|
||||
target_branch_not_exist = Target branch does not exist.
|
||||
target_ref_not_exist = Target ref does not exist %s
|
||||
|
||||
admin_cannot_delete_self = You cannot delete yourself when you are an admin. Please remove your admin privileges first.
|
||||
|
||||
@ -1273,6 +1274,7 @@ commit_graph.color = Color
|
||||
commit.contained_in = This commit is contained in:
|
||||
commit.contained_in_default_branch = This commit is part of the default branch
|
||||
commit.load_referencing_branches_and_tags = Load branches and tags referencing this commit
|
||||
commit.load_tags_failed = Load tags failed because of internal error
|
||||
blame = Blame
|
||||
download_file = Download file
|
||||
normal_view = Normal View
|
||||
@ -3701,6 +3703,11 @@ workflow.disable_success = Workflow '%s' disabled successfully.
|
||||
workflow.enable = Enable Workflow
|
||||
workflow.enable_success = Workflow '%s' enabled successfully.
|
||||
workflow.disabled = Workflow is disabled.
|
||||
workflow.run = Run Workflow
|
||||
workflow.not_found = Workflow '%s' not found.
|
||||
workflow.run_success = Workflow '%s' run successfully.
|
||||
workflow.from_ref = Use workflow from
|
||||
workflow.has_workflow_dispatch = This workflow has a workflow_dispatch event trigger.
|
||||
|
||||
need_approval_desc = Need approval to run workflows for fork pull request.
|
||||
|
||||
|
@ -193,7 +193,7 @@ app_desc=Un servicio de Git autoalojado y sin complicaciones
|
||||
install=Fácil de instalar
|
||||
install_desc=Simplemente <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">ejecuta el binario</a> para tu plataforma, lánzalo con <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a>o consíguelo <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">empaquetado</a>.
|
||||
platform=Multiplataforma
|
||||
platform_desc=Gitea funciona en cualquier platforma <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> puede compilarlo en: Windows, macOS, Linux, ARM, etc. ¡Elige tu favorita!
|
||||
platform_desc=Gitea funciona en cualquier platforma <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> puede compilarlo en: Windows, macOS, Linux, ARM, etc. ¡Elige tu favorita!
|
||||
lightweight=Ligero
|
||||
lightweight_desc=Gitea tiene pocos requisitos y puede funcionar en una Raspberry Pi barata. ¡Ahorra energía!
|
||||
license=Código abierto
|
||||
@ -616,11 +616,9 @@ applications=Aplicaciones
|
||||
orgs=Administrar organizaciones
|
||||
repos=Repositorios
|
||||
delete=Eliminar cuenta
|
||||
twofa=Autenticación de doble factor
|
||||
account_link=Cuentas vinculadas
|
||||
organization=Organizaciones
|
||||
uid=UID
|
||||
webauthn=Llaves de Seguridad
|
||||
|
||||
public_profile=Perfil público
|
||||
biography_placeholder=¡Cuéntanos un poco sobre ti mismo! (Puedes usar Markdown)
|
||||
@ -715,7 +713,6 @@ add_email_success=La nueva dirección de correo electrónico ha sido añadida.
|
||||
email_preference_set_success=La preferencia de correo electrónico se ha establecido correctamente.
|
||||
add_openid_success=La nueva dirección OpenID ha sido añadida.
|
||||
keep_email_private=Ocultar dirección de correo electrónico
|
||||
keep_email_private_popup=Esto ocultará su dirección de correo electrónico de su perfil, así como cuando haga un pull request o edite un archivo usando la interfaz web. Los commits enviados no serán modificados.
|
||||
openid_desc=OpenID le permite delegar la autenticación a un proveedor externo.
|
||||
|
||||
manage_ssh_keys=Gestionar Claves SSH
|
||||
@ -843,7 +840,6 @@ oauth2_client_secret_hint=El secreto no se mostrará de nuevo después de salir
|
||||
oauth2_application_edit=Editar
|
||||
oauth2_application_create_description=Las aplicaciones OAuth2 le dan acceso a su aplicación de terceros a cuentas de usuario en esta instancia.
|
||||
oauth2_application_remove_description=Eliminar una aplicación de OAuth2 evitará que acceda a cuentas de usuario autorizadas en esta instancia. ¿Continuar?
|
||||
oauth2_application_locked=Gitea pre-registra algunas aplicaciones de OAuth2 en el arranque si están habilitadas en la configuración. Para prevenir un comportamiento inesperado, estos no pueden ser editados ni removidos. Por favor, consulte la documentación de OAuth2 para más información.
|
||||
|
||||
authorized_oauth2_applications=Aplicaciones OAuth2 autorizadas
|
||||
authorized_oauth2_applications_description=Has concedido acceso a tu cuenta personal de Gitea a estas aplicaciones de terceros. Por favor, revoca el acceso para las aplicaciones que ya no necesitas.
|
||||
@ -852,22 +848,17 @@ revoke_oauth2_grant=Revocar acceso
|
||||
revoke_oauth2_grant_description=Revocar el acceso a esta aplicación impedirá que esta aplicación acceda a sus datos. ¿Está seguro?
|
||||
revoke_oauth2_grant_success=Acceso revocado correctamente.
|
||||
|
||||
twofa_desc=La autenticación de doble factor mejora la seguridad de su cuenta.
|
||||
twofa_is_enrolled=Su cuenta actualmente está <strong>registrada</strong> en la autenticación de doble factor.
|
||||
twofa_not_enrolled=Tu cuenta no está actualmente inscrita en la autenticación de doble factor.
|
||||
twofa_disable=Deshabilitar autenticación de doble factor
|
||||
twofa_scratch_token_regenerate=Regenerar código de respaldo
|
||||
twofa_scratch_token_regenerated=Tu token de scratch es ahora %s. Guárdelo en un lugar seguro, nunca se volverá a mostrar.
|
||||
twofa_enroll=Inscribirse en la autenticación de doble factor
|
||||
twofa_disable_note=Puede deshabilitar la autenticación de doble factor si lo necesita.
|
||||
twofa_disable_desc=Deshabilitar la autenticación de doble factor hará su cuenta menos segura. ¿Continuar?
|
||||
regenerate_scratch_token_desc=Si extravió su código de respaldo, o ya lo usó para iniciar sesión, puede restablecerlo aquí.
|
||||
twofa_disabled=La autenticación de doble factor ha sido deshabilitada.
|
||||
scan_this_image=Escanee esta imagen con su aplicación de autenticación:
|
||||
or_enter_secret=O introduzca el secreto: %s
|
||||
then_enter_passcode=E introduzca el código de acceso mostrado en la aplicación:
|
||||
passcode_invalid=El código de acceso es incorrecto. Vuelva a intentarlo.
|
||||
twofa_enrolled=Su cuenta ha sido inscrita en la autenticación de doble factor. ¡Guarde su código de respaldo (%s) en un lugar seguro, ya que sólo se muestra una vez!
|
||||
twofa_failed_get_secret=No se pudo obtener el secreto.
|
||||
|
||||
webauthn_desc=Las claves de seguridad son dispositivos hardware que contienen claves criptográficas. Pueden ser usados para la autenticación de doble factor. Las claves de seguridad deben soportar el estándar <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a>.
|
||||
@ -1006,9 +997,7 @@ tree_path_not_found_branch=La ruta %[1]s no existe en la rama %[2]s
|
||||
tree_path_not_found_tag=La ruta %[1]s no existe en la etiqueta %[2]s
|
||||
|
||||
transfer.accept=Aceptar transferencia
|
||||
transfer.accept_desc=`Transferir a "%s"`
|
||||
transfer.reject=Rechazar transferencia
|
||||
transfer.reject_desc=`Cancelar transferencia a "%s"`
|
||||
transfer.no_permission_to_accept=No tienes permiso para aceptar esta transferencia.
|
||||
transfer.no_permission_to_reject=No tienes permiso para rechazar esta transferencia.
|
||||
|
||||
@ -1141,7 +1130,6 @@ releases=Lanzamientos
|
||||
tag=Etiqueta
|
||||
released_this=publicó esto
|
||||
tagged_this=etiquetó esto
|
||||
file.title=%s de %s
|
||||
file_raw=Original
|
||||
file_history=Histórico
|
||||
file_view_source=Ver código fuente
|
||||
@ -1155,7 +1143,6 @@ ambiguous_runes_header=`Este archivo contiene caracteres Unicode ambiguos`
|
||||
ambiguous_runes_description=`Este archivo contiene caracteres Unicode que pueden confundirse con otros caracteres. Si crees que esto es intencional, puedes ignorar esta advertencia. Usa el botón de Escape para revelarlos.`
|
||||
invisible_runes_line=`Esta línea tiene caracteres unicode invisibles`
|
||||
ambiguous_runes_line=`Esta línea tiene caracteres unicode ambiguos`
|
||||
ambiguous_character=`%[1]c [U+%04[1]X] es confusable con %[2]c [U+%04[2]X]`
|
||||
|
||||
escape_control_characters=Escapar
|
||||
unescape_control_characters=No Escapar
|
||||
@ -1747,9 +1734,7 @@ pulls.unrelated_histories=Fusionar Fallidos: El jefe de fusión y la base no com
|
||||
pulls.merge_out_of_date=Fusión fallida: Mientras se generaba la fusión, la base fue actualizada. Pista: Inténtelo de nuevo.
|
||||
pulls.head_out_of_date=Fusión fallida: Mientras se generaba la fusión, la cabeza fue actualizada. Pista: Inténtelo de nuevo.
|
||||
pulls.has_merged=Error: La pull request ha sido fusionada, no puedes fusionarla de nuevo ni cambiar la rama objetivo.
|
||||
pulls.push_rejected=Fusión fallida: El push fue rechazado. Revise los Git Hooks para este repositorio.
|
||||
pulls.push_rejected_summary=Mensaje completo de rechazo
|
||||
pulls.push_rejected_no_message=Fusión fallida: El push fue rechazado pero no hubo mensaje remoto.<br>Revise los Git Hooks para este repositorio
|
||||
pulls.open_unmerged_pull_exists=`No puede realizar la reapertura porque hay un pull request pendiente (#%d) con propiedades idénticas.`
|
||||
pulls.status_checking=Algunas comprobaciones están pendientes
|
||||
pulls.status_checks_success=Todas las comprobaciones han sido exitosas
|
||||
@ -1796,7 +1781,6 @@ milestones.no_due_date=Sin fecha límite
|
||||
milestones.open=Abrir
|
||||
milestones.close=Cerrar
|
||||
milestones.new_subheader=Los hitos pueden ayudarle a organizar los problemas y monitorizar su progreso.
|
||||
milestones.completeness=%d%% Completado
|
||||
milestones.create=Crear hito
|
||||
milestones.title=Título
|
||||
milestones.desc=Descripción
|
||||
@ -2008,7 +1992,6 @@ settings.pulls.default_delete_branch_after_merge=Eliminar por defecto la rama de
|
||||
settings.pulls.default_allow_edits_from_maintainers=Permitir ediciones de mantenedores por defecto
|
||||
settings.releases_desc=Activar lanzamientos del repositorio
|
||||
settings.packages_desc=Habilitar registro de paquetes de repositorio
|
||||
settings.projects_desc=Activar Proyectos de Repositorio
|
||||
settings.projects_mode_all=Todos los proyectos
|
||||
settings.actions_desc=Activar Acciones del repositorio
|
||||
settings.admin_settings=Ajustes de administrador
|
||||
@ -2229,8 +2212,6 @@ settings.protected_branch.save_rule=Guardar regla
|
||||
settings.protected_branch.delete_rule=Eliminar regla
|
||||
settings.protected_branch_can_push=¿Permitir hacer push?
|
||||
settings.protected_branch_can_push_yes=Puede hacer push
|
||||
settings.protected_branch_can_push_no=No puede hacer push
|
||||
settings.branch_protection=Proteccion de la rama '<b>%s</b>'
|
||||
settings.protect_this_branch=Activar protección de rama
|
||||
settings.protect_this_branch_desc=Evita la eliminación y restringe hacer push y fusionar contra la rama.
|
||||
settings.protect_disable_push=Deshabilitar Push
|
||||
@ -2242,7 +2223,6 @@ settings.protect_enable_merge_desc=Cualquiera con acceso de escritura podrá fus
|
||||
settings.protect_check_status_contexts=Habilitar comprobación de estado
|
||||
settings.protect_status_check_patterns=Patrones de verificación de estado:
|
||||
settings.protect_status_check_patterns_desc=Introduzca los patrones para especificar qué comprobaciones de estado deben pasar antes de que las ramas puedan ser fusionadas en una rama que coincida con esta regla. Cada línea especifica un patrón. Los patrones no pueden estar vacíos.
|
||||
settings.protect_check_status_contexts_desc=Requiere verificaciones de estado para pasar antes de fusionar. Elija qué verificaciones de estado deben pasar antes de que las ramas puedan fusionarse en una rama que coincida con esta regla. Cuando se active, los commits primero deben ser empujados a otra rama, y luego fusionados o empujados directamente a una rama que coincida con esta regla luego de que las verificaciones de estado hayan pasado. Si no se selecciona ningún contexto, el último commit debe ser exitoso sin importar el contexto.
|
||||
settings.protect_check_status_contexts_list=Comprobaciones de estado para este repositorio encontradas durante la semana pasada
|
||||
settings.protect_status_check_matched=Coincide
|
||||
settings.protect_invalid_status_check_pattern=Patrón de verificación de estado no válido: "%s".
|
||||
@ -2259,12 +2239,9 @@ settings.protect_protected_file_patterns=Patrones de archivos protegidos (separa
|
||||
settings.protect_protected_file_patterns_desc=No está permitido cambiar archivos directamente incluso si el usuario tiene permiso para agregar, editar o borrar archivos en esta rama. Múltiples patrones pueden separarse usando punto y coma (';'). Refvisa la documentación de <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para la sintaxis de patrones. Ejemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Patrones de archivos sin protección (separados con punto y coma ';'):
|
||||
settings.protect_unprotected_file_patterns_desc=Los archivos sin protección se pueden cambiar directamente si el usuario tiene acceso de escritura, evitando la restricción push. Múltiples patrones pueden separarse usando punto y coma (';'). Vea la documentación de <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para la sintaxis de patrones. Ejemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
|
||||
settings.add_protected_branch=Activar protección
|
||||
settings.delete_protected_branch=Desactivar protección
|
||||
settings.update_protect_branch_success=Se ha actualizado la protección de la rama para la regla "%s".
|
||||
settings.remove_protected_branch_success=Se ha eliminado la protección de la rama para la regla "%s".
|
||||
settings.remove_protected_branch_failed=Error al eliminar la regla de protección de rama "%s".
|
||||
settings.protected_branch_deletion=Desactivar protección de rama
|
||||
settings.protected_branch_deletion_desc=Desactivar la protección de la rama permite a los usuarios con permiso de escritura hacer push a la rama. ¿Continuar?
|
||||
settings.block_rejected_reviews=Bloquear fusión en las revisión rechazadas
|
||||
settings.block_rejected_reviews_desc=Fusionar no será posible cuando los revisores oficiales soliciten cambios, incluso si hay suficientes aprobaciones.
|
||||
@ -2274,7 +2251,6 @@ settings.block_outdated_branch=Bloquear fusión si la pull request está desactu
|
||||
settings.block_outdated_branch_desc=La fusión no será posible cuando la rama principal esté detrás de la rama base.
|
||||
settings.default_branch_desc=Seleccione una rama de repositorio por defecto para los pull request y los commits:
|
||||
settings.merge_style_desc=Estilos de fusión
|
||||
settings.default_merge_style_desc=Estilo de fusión por defecto para pull requests:
|
||||
settings.choose_branch=Elija una rama…
|
||||
settings.no_protected_branch=No hay ramas protegidas.
|
||||
settings.edit_protected_branch=Editar
|
||||
@ -2492,7 +2468,6 @@ tag.create_success=La etiqueta "%s" ha sido creada.
|
||||
|
||||
topic.manage_topics=Administrar temas
|
||||
topic.done=Hecho
|
||||
topic.count_prompt=No puede seleccionar más de 25 temas
|
||||
topic.format_prompt=Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y puntos ('. ), puede tener hasta 35 caracteres de largo. Las letras deben estar en minúsculas.
|
||||
|
||||
find_file.go_to_file=Ir al archivo
|
||||
@ -2582,7 +2557,6 @@ teams.leave.detail=¿Irse de %s?
|
||||
teams.can_create_org_repo=Crear repositorios
|
||||
teams.can_create_org_repo_helper=Los miembros pueden crear nuevos repositorios en la organización. El creador obtendrá acceso al administrador del nuevo repositorio.
|
||||
teams.none_access=Sin acceso
|
||||
teams.none_access_helper=Los miembros no pueden ver o hacer ninguna otra acción en esta unidad.
|
||||
teams.general_access=Acceso general
|
||||
teams.general_access_helper=Los permisos de los miembros se decidirán por debajo de la tabla de permisos.
|
||||
teams.read_access=Leer
|
||||
@ -3093,7 +3067,6 @@ monitor.next=Siguiente
|
||||
monitor.previous=Anterior
|
||||
monitor.execute_times=Ejecuciones
|
||||
monitor.process=Procesos en ejecución
|
||||
monitor.stacktrace=Rastros de pila
|
||||
monitor.processes_count=%d procesos
|
||||
monitor.download_diagnosis_report=Descargar informe de diagnóstico
|
||||
monitor.desc=Descripción
|
||||
@ -3101,8 +3074,6 @@ monitor.start=Hora de Inicio
|
||||
monitor.execute_time=Tiempo de ejecución
|
||||
monitor.last_execution_result=Resultado
|
||||
monitor.process.cancel=Cancelar el proceso
|
||||
monitor.process.cancel_desc=Cancelar un proceso puede ocasionar una pérdida de datos
|
||||
monitor.process.cancel_notices=Cancelar: <strong>%s</strong>?
|
||||
monitor.process.children=Hijos
|
||||
|
||||
monitor.queues=Colas
|
||||
@ -3195,7 +3166,6 @@ raw_minutes=minutos
|
||||
|
||||
[dropzone]
|
||||
default_message=Suelte archivos o haga clic aquí para subir.
|
||||
invalid_input_type=No puede subir archivos de este tipo.
|
||||
file_too_big=El tamaño del archivo ({{filesize}} MB) excede el tamaño máximo de ({{maxFilesize}} MB).
|
||||
remove_file=Eliminar archivo
|
||||
|
||||
|
@ -124,7 +124,7 @@ missing_csrf=درخواست بد: بلیط CSRF ندارد
|
||||
app_desc=یک سرویس گیت بیدرد سر و راحت
|
||||
install=راهاندازی ساده
|
||||
platform=مستقل از سکو
|
||||
platform_desc=گیت همه جا اجرا میشود <a target="_blank" rel="noopener noreferrer" href="http://golang.org/"> بریم!</a> میتوانید Windows, macOS, Linux, ARM و ... هر کدام را دوست داشتید انتخاب کنید!
|
||||
platform_desc=گیت همه جا اجرا میشود <a target="_blank" rel="noopener noreferrer" href="https://go.dev/"> بریم!</a> میتوانید Windows, macOS, Linux, ARM و ... هر کدام را دوست داشتید انتخاب کنید!
|
||||
lightweight=ابزارک سبک
|
||||
lightweight_desc=گیتی با حداقل منابع میتوانید برای روی دستگاه Raspberry Pi اجرا شود و مصرف انرژی شما را کاهش دهد!
|
||||
license=متن باز
|
||||
@ -490,7 +490,6 @@ applications=برنامهها
|
||||
orgs=مدیریت سازمانها
|
||||
repos=مخازن
|
||||
delete=حذف حساب کاربری
|
||||
twofa=احراز هویت دوگانه
|
||||
account_link=حسابهای مرتبط
|
||||
organization=سازمان ها
|
||||
|
||||
@ -661,21 +660,17 @@ revoke_key=ابطال
|
||||
revoke_oauth2_grant=ابطال دسترسی
|
||||
revoke_oauth2_grant_description=لغو دسترسی برای این برنامه شخص ثالث از دسترسی این برنامه به داده های شما جلوگیری می کند. شما مطمئن هستید؟
|
||||
|
||||
twofa_desc=احراز هویت دو مرحله ای امنیت حساب شما را افزایش میدهد.
|
||||
twofa_is_enrolled=احراز هویت دو مرحله ای برای حساب شما <strong>اجرا</strong>میشود.
|
||||
twofa_not_enrolled=حساب کاربری شما اکنون احراز هویت دو مرحله ای ندارد.
|
||||
twofa_disable=غیرفعالکردن احراز هویت دو مرحله ای
|
||||
twofa_scratch_token_regenerate=ساخت مجدد Scratch Token
|
||||
twofa_enroll=فعالکردن احراز هویت دوگانه
|
||||
twofa_disable_note=در صورت لزوم، شما می توانید احراز هویت دو مرحله ای را غیر فعال کنید.
|
||||
twofa_disable_desc=غیر فعال کردن احراز هویت دو مرحله ای امنیت حساب کاربری شما را کمتر میکند. آیا ادامه میدهید؟
|
||||
regenerate_scratch_token_desc=اگر شما token چکنویس خود را گم کرده اید میتوانید پس از ورود آنرا reset کنید.
|
||||
twofa_disabled=احراز هویت دو مرحله ای غیر فعال گشت.
|
||||
scan_this_image=این تصویر را با برنامه احراز هویت خود اسکن نمایید:
|
||||
or_enter_secret=و یا رمز را وارد کنید: %s
|
||||
then_enter_passcode=و کد ورود نمایش داده شده در درخواست را وارد کنید:
|
||||
passcode_invalid=کد ورود نامعتبر است. مجددا تلاش نمایید.
|
||||
twofa_enrolled=ورود به حسابت کاربری دو مرحله ای فعال شد. لطفا token خود را (%s) نگهداری کنید. لذا فقط یک بار نمایش داده میشود!
|
||||
twofa_failed_get_secret=خطا در دریافت رمز.
|
||||
|
||||
|
||||
@ -785,9 +780,7 @@ blame_prior=قبل از این تغییر، سرزنش را مشاهده کنی
|
||||
|
||||
|
||||
transfer.accept=تایید انتقال
|
||||
transfer.accept_desc=انتقال به %s
|
||||
transfer.reject=رد انتقال
|
||||
transfer.reject_desc=رد انتقال به %s
|
||||
|
||||
desc.private=خصوصی
|
||||
desc.public=عمومی
|
||||
@ -899,7 +892,6 @@ release=نسخه منتشر شده
|
||||
releases=انتشارها
|
||||
tag=برچسب
|
||||
released_this=منتشر کرده است
|
||||
file.title=%s در %s
|
||||
file_raw=خام
|
||||
file_history=تاریخچه
|
||||
file_view_source=نمایش منبع
|
||||
@ -1375,7 +1367,6 @@ milestones.closed=%s بسته شد
|
||||
milestones.no_due_date=بدون موعد مقرر
|
||||
milestones.open=باز
|
||||
milestones.close=بستن
|
||||
milestones.completeness=%d%% کامل شده است
|
||||
milestones.create=ایجاد نقطه عطف
|
||||
milestones.title=عنوان
|
||||
milestones.desc=شرح
|
||||
@ -1539,7 +1530,6 @@ settings.pulls_desc=فعال کردن تقاضای واکشی مخزن
|
||||
settings.pulls.ignore_whitespace=نادیده گرفتن لیست سفید برای تداخل ها
|
||||
settings.pulls.enable_autodetect_manual_merge=ادغام دستی تشخیص خودکار را فعال کنید (توجه: در برخی موارد خاص، قضاوت نادرست ممکن است رخ دهد)
|
||||
settings.pulls.default_delete_branch_after_merge=پس از ادغام به طور پیش فرض شاخه درخواست pull را حذف کنید
|
||||
settings.projects_desc=فعال کردن پروژه های انبار
|
||||
settings.admin_settings=تنظیمات مدیران
|
||||
settings.admin_enable_health_check=فعال کردن بررسی سلامت مخزن (git fsck)
|
||||
settings.admin_enable_close_issues_via_commit_in_any_branch=اسنداد یک مسئله با کامیت آن شاخه را با غیر پیش فرض تبدیل میکند
|
||||
@ -1714,8 +1704,6 @@ settings.branches=شاخهها
|
||||
settings.protected_branch=حفاظت از شاخه
|
||||
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=جلوگیری از حذف و محدود کردن درج و ادغام Git به شاخه.
|
||||
settings.protect_disable_push=غیرفعال کردن درج
|
||||
@ -1723,7 +1711,6 @@ settings.protect_disable_push_desc=هیچ موردی اجازه درج در ای
|
||||
settings.protect_enable_push=فعال کردن درج
|
||||
settings.protect_enable_push_desc=هرکسی که دسترسی به نوشتن داشته باشد مجاز خواهد بود به این شاخه درج کند (اما نه درج اجباری).
|
||||
settings.protect_check_status_contexts=فعال کردن حالات بررسی
|
||||
settings.protect_check_status_contexts_desc=قبل از ادغام باید بررسی وضعیت انجام شود. انتخاب کنید که بررسی وضعیت باید قبل از ادغام شاخهها در شاخهای که با این قانون مطابقت دارد، انجام شود. هنگامی که فعال است، commit ها باید ابتدا به شاخه دیگری منتقل شوند، سپس پس از بررسی وضعیت، ادغام یا مستقیماً به شاخه ای که با این قانون مطابقت دارد، هدایت شوند. اگر هیچ زمینه ای انتخاب نشده باشد، آخرین commit بدون توجه به زمینه باید موفقیت آمیز باشد.
|
||||
settings.protect_check_status_contexts_list=آخرین بررسی حالات این مخزن در هفته گذشته اتفاق افتاده است
|
||||
settings.protect_required_approvals=نیازمند تاییدیه:
|
||||
settings.protect_required_approvals_desc=فقط مجاز به ادغام تقاضای واکشی با بررسی های مثبت کافی می شوند.
|
||||
@ -1733,7 +1720,6 @@ settings.require_signed_commits=نیاز به commit های امضا شده
|
||||
settings.require_signed_commits_desc=در صورتی که push ها به این شاخه بدون امضا یا غیرقابل تأیید باشند، رد شوند.
|
||||
settings.add_protected_branch=فعالسازی محافظ
|
||||
settings.delete_protected_branch=غیر فعالسازی محافظ
|
||||
settings.protected_branch_deletion=غیرفعالکردن حفاظت از شاخه
|
||||
settings.protected_branch_deletion_desc=غیرفعال کردن محافظت از شاخه به کاربرانی که دسترسی نوشتن دارند اجازه درج در شاخه را میدهد. آیا ادامه میدهید؟
|
||||
settings.block_rejected_reviews=ادغام نظرات رد شده را مسدود شود
|
||||
settings.block_rejected_reviews_desc=ادغام در صورت درخواست تغییرات توسط بازبینان رسمی امکان پذیر نخواهد بود، حتی اگر تأییدیه های کافی وجود داشته باشد.
|
||||
@ -1742,7 +1728,6 @@ settings.block_on_official_review_requests_desc=زمانی که درخواست
|
||||
settings.block_outdated_branch=اگر درخواست pull قدیمی است، ادغام را مسدود شود
|
||||
settings.block_outdated_branch_desc=وقتی سرشاخه پشت شاخه پایه باشد، ادغام امکان پذیر نخواهد بود.
|
||||
settings.default_branch_desc=شاخه اصلی مخزن را برای تقاضا واکشی و کامیت کد ها انتخاب نمایید:
|
||||
settings.default_merge_style_desc=سبک ادغام پیش فرض برای درخواست های pull:
|
||||
settings.choose_branch=شاخه اصلی مخزن را برای تقاضا واکشی و کامیت کد ها انتخاب نمایید:
|
||||
settings.no_protected_branch=اینجا هیچ شاخه محافظت شده ای وجود ندارد.
|
||||
settings.edit_protected_branch=ویرایش
|
||||
@ -1910,7 +1895,6 @@ tag.create_tag=تگ <strong>%s</strong> ایجاد کنید
|
||||
|
||||
topic.manage_topics=مدیریت موضوعات
|
||||
topic.done=انجام شد
|
||||
topic.count_prompt=شما نمی توانید بیش از 25 موضوع انتخاب کنید
|
||||
|
||||
|
||||
error.csv.too_large=نمی توان این فایل را رندر کرد زیرا بسیار بزرگ است.
|
||||
@ -2429,8 +2413,6 @@ monitor.desc=شرح
|
||||
monitor.start=زمان شروع
|
||||
monitor.execute_time=زمان مورد نیاز برای اجرا
|
||||
monitor.process.cancel=لغو فرآیند
|
||||
monitor.process.cancel_desc=لغو کردن یک فرآیند ممکن است باعث از دست رفتن داده ها شود
|
||||
monitor.process.cancel_notices=لغو: <strong>%s</strong>؟
|
||||
monitor.process.children=فرزندان
|
||||
|
||||
monitor.queues=صف ها
|
||||
@ -2515,7 +2497,6 @@ raw_minutes=دقیقه
|
||||
|
||||
[dropzone]
|
||||
default_message=فایل را در این محل رها کنید یا دکمه ی آپلود یا بارگزاری را فشار دهید.
|
||||
invalid_input_type=شما قادر به ارسال فایل های از این نوع نیستید.
|
||||
file_too_big=حجم فایل ({{filesize}} MB) بیش از حداکثر مجاز است ({{maxFilesize}} مگابایت).
|
||||
remove_file=حذف پرونده
|
||||
|
||||
|
@ -142,7 +142,7 @@ network_error=Verkkovirhe
|
||||
app_desc=Kivuton, itsehostattu Git-palvelu
|
||||
install=Helppo asentaa
|
||||
platform=Alustariippumaton
|
||||
platform_desc=Gitea käy missä tahansa alustassa, johon <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> kykenee kääntämään. Windows, macOS, Linux, ARM, jne. Valitse omasi!
|
||||
platform_desc=Gitea käy missä tahansa alustassa, johon <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> kykenee kääntämään. Windows, macOS, Linux, ARM, jne. Valitse omasi!
|
||||
lightweight=Kevyt
|
||||
lightweight_desc=Gitealla on vähäiset vähimmäisvaatimukset, joten se toimii jopa halvassa Raspberry Pi:ssä. Säästä koneesi energiaa!
|
||||
license=Avoin lähdekoodi
|
||||
@ -451,10 +451,8 @@ applications=Sovellukset
|
||||
orgs=Hallitse organisaatioita
|
||||
repos=Repot
|
||||
delete=Poista tili
|
||||
twofa=Kaksivaiheinen todennus
|
||||
account_link=Linkitetyt tilit
|
||||
organization=Organisaatiot
|
||||
webauthn=Turva-avaimet
|
||||
|
||||
public_profile=Julkinen profiili
|
||||
password_username_disabled=Ei-paikalliset käyttäjät eivät voi muuttaa käyttäjätunnustaan. Ole hyvä ja ota yhteyttä sivuston ylläpitäjään saadaksesi lisätietoa.
|
||||
@ -606,14 +604,12 @@ oauth2_regenerate_secret_hint=Kadotitko secretin?
|
||||
oauth2_application_edit=Muokkaa
|
||||
|
||||
|
||||
twofa_desc=Kaksivaiheinen todennus parantaa tilisi turvallisuutta.
|
||||
twofa_is_enrolled=Tilisi <strong>käyttää</strong> kaksivaiheista vahvistusta.
|
||||
twofa_not_enrolled=Tilisi ei tällä hetkellä käytä kaksivaiheista vahvistusta.
|
||||
twofa_enroll=Ota kaksivaiheinen vahvistus käyttöön
|
||||
twofa_disabled=Kaksivaiheinen todennus on otettu pois käytöstä.
|
||||
scan_this_image=Skannaa tämä kuva tunnistautumissovelluksellasi:
|
||||
or_enter_secret=Tai kirjoita salainen avain: %s
|
||||
twofa_enrolled=Tiliisi on otettu käyttöön kaksivaiheinen vahvistus. Ota palautustunnus (%s) talteen turvalliseen paikkaan, sillä se näytetään vain kerran!
|
||||
|
||||
webauthn_nickname=Nimimerkki
|
||||
|
||||
@ -1184,7 +1180,6 @@ settings.deploy_key_deletion_desc=Julkaisuavaimen poistaminen kumoaa sen pääsy
|
||||
settings.deploy_key_deletion_success=Julkaisuavain on poistettu.
|
||||
settings.branches=Haarat
|
||||
settings.protected_branch=Haaran suojaus
|
||||
settings.branch_protection=Haaran '<b>%s</b>' suojaus
|
||||
settings.protect_this_branch=Ota haaran suojaus käyttöön
|
||||
settings.protect_required_approvals=Vaadittavat hyväksynnät:
|
||||
settings.choose_branch=Valitse haara…
|
||||
@ -1672,7 +1667,6 @@ raw_minutes=minuuttia
|
||||
|
||||
[dropzone]
|
||||
default_message=Pudota tiedostot tähän tai klikkaa aluetta ladataksesi tiedoston.
|
||||
invalid_input_type=Tämäntyyppisiä tiedostoja ei voi ladata.
|
||||
remove_file=Poista tiedosto
|
||||
|
||||
[notification]
|
||||
|
@ -31,7 +31,6 @@ username=Nom d'utilisateur
|
||||
email=Courriel
|
||||
password=Mot de passe
|
||||
access_token=Jeton d’accès
|
||||
re_type=Confirmez le mot de passe
|
||||
captcha=CAPTCHA
|
||||
twofa=Authentification à deux facteurs
|
||||
twofa_scratch=Code de secours pour l'authentification à deux facteurs
|
||||
@ -229,7 +228,7 @@ app_desc=Un service Git auto-hébergé sans prise de tête
|
||||
install=Facile à installer
|
||||
install_desc=Il suffit de <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">lancer l’exécutable</a> adapté à votre plateforme, le déployer avec <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a> ou de l’installer depuis un <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">gestionnaire de paquet</a>.
|
||||
platform=Multi-plateforme
|
||||
platform_desc=Gitea tourne partout où <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> peut être compilé : Windows, macOS, Linux, ARM, etc. Choisissez votre préféré !
|
||||
platform_desc=Gitea tourne partout où <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> peut être compilé : Windows, macOS, Linux, ARM, etc. Choisissez votre préféré !
|
||||
lightweight=Léger
|
||||
lightweight_desc=Gitea utilise peu de ressources. Il peut même tourner sur un Raspberry Pi très bon marché. Économisez l'énergie de vos serveurs !
|
||||
license=Open Source
|
||||
@ -317,7 +316,6 @@ admin_setting_desc=La création d'un compte administrateur est facultative. Le p
|
||||
admin_title=Paramètres de compte administrateur
|
||||
admin_name=Nom d’utilisateur administrateur
|
||||
admin_password=Mot de passe
|
||||
confirm_password=Confirmez le mot de passe
|
||||
admin_email=Courriel
|
||||
install_btn_confirm=Installer Gitea
|
||||
test_git_failed=Le test de la commande "git" a échoué : %v
|
||||
@ -540,7 +538,6 @@ UserName=Nom d'utilisateur
|
||||
RepoName=Nom du dépôt
|
||||
Email=Courriel
|
||||
Password=Mot de passe
|
||||
Retype=Confirmez le mot de passe
|
||||
SSHTitle=Nom de la clé SSH
|
||||
HttpsUrl=URL HTTPS
|
||||
PayloadUrl=URL des données utiles
|
||||
@ -693,11 +690,9 @@ applications=Applications
|
||||
orgs=Gérer les organisations
|
||||
repos=Dépôts
|
||||
delete=Supprimer le compte
|
||||
twofa=Authentification à deux facteurs
|
||||
account_link=Comptes liés
|
||||
organization=Organisations
|
||||
uid=UID
|
||||
webauthn=Clés de sécurité
|
||||
|
||||
public_profile=Profil public
|
||||
biography_placeholder=Parlez-nous un peu de vous ! (Vous pouvez utiliser Markdown)
|
||||
@ -795,7 +790,6 @@ add_email_success=La nouvelle adresse e-mail a été ajoutée.
|
||||
email_preference_set_success=L'e-mail de préférence a été défini avec succès.
|
||||
add_openid_success=La nouvelle adresse OpenID a été ajoutée.
|
||||
keep_email_private=Cacher l'adresse e-mail
|
||||
keep_email_private_popup=Ceci masquera votre adresse e-mail de votre profil, de vos demandes d'ajout et des fichiers modifiés depuis l'interface Web. Les révisions déjà soumises ne seront pas modifiés.
|
||||
openid_desc=OpenID vous permet de confier l'authentification à une tierce partie.
|
||||
|
||||
manage_ssh_keys=Gérer les clés SSH
|
||||
@ -934,23 +928,18 @@ revoke_oauth2_grant=Révoquer l'accès
|
||||
revoke_oauth2_grant_description=La révocation de l'accès à cette application tierce l'empêchera d'accéder à vos données. Vous êtes sûr ?
|
||||
revoke_oauth2_grant_success=Accès révoqué avec succès.
|
||||
|
||||
twofa_desc=L'authentification à deux facteurs améliore la sécurité de votre compte.
|
||||
twofa_recovery_tip=Si vous perdez votre appareil, vous pourrez utiliser une clé de récupération à usage unique pour obtenir l’accès à votre compte.
|
||||
twofa_is_enrolled=Votre compte est <strong>inscrit</strong> à l'authentification à deux facteurs.
|
||||
twofa_not_enrolled=Votre compte n'est pas inscrit à l'authentification à deux facteurs.
|
||||
twofa_disable=Désactiver l'authentification à deux facteurs
|
||||
twofa_scratch_token_regenerate=Régénérer un jeton de secours
|
||||
twofa_scratch_token_regenerated=Votre jeton de secours est désormais « %s ». Stockez-le dans un endroit sûr, il ne sera plus jamais affiché.
|
||||
twofa_enroll=Activer l'authentification à deux facteurs
|
||||
twofa_disable_note=Vous pouvez désactiver l'authentification à deux facteurs si nécessaire.
|
||||
twofa_disable_desc=Désactiver l'authentification à deux facteurs rendra votre compte plus vulnérable. Confirmer ?
|
||||
regenerate_scratch_token_desc=Si vous avez perdu votre jeton de secours, ou avez dû l'utiliser pour vous authentifier, vous pouvez le réinitialiser.
|
||||
twofa_disabled=L'authentification à deux facteurs a été désactivée.
|
||||
scan_this_image=Scannez cette image avec votre application d'authentification :
|
||||
or_enter_secret=Ou saisissez le code %s
|
||||
then_enter_passcode=Et entrez le code de passe s'affichant dans l'application :
|
||||
passcode_invalid=Le mot de passe est invalide. Réessayez.
|
||||
twofa_enrolled=L'authentification à deux facteurs a été activée pour votre compte. Gardez votre jeton de secours (%s) en lieu sûr, car il ne vous sera montré qu'une seule fois !
|
||||
twofa_failed_get_secret=Impossible d'obtenir le secret.
|
||||
|
||||
webauthn_desc=Les clefs de sécurité sont des dispositifs matériels contenant des clefs cryptographiques. Elles peuvent être utilisées pour l'authentification à deux facteurs. La clef de sécurité doit supporter le standard <a rel="noreferrer" target="_blank" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a>.
|
||||
@ -1098,9 +1087,7 @@ tree_path_not_found_branch=Le chemin %[1]s n’existe pas dans la branche %[2]s.
|
||||
tree_path_not_found_tag=Le chemin %[1]s n’existe pas dans l’étiquette %[2]s.
|
||||
|
||||
transfer.accept=Accepter le transfert
|
||||
transfer.accept_desc=`Transférer à "%s"`
|
||||
transfer.reject=Refuser le transfert
|
||||
transfer.reject_desc=`Annuler le transfert à "%s"`
|
||||
transfer.no_permission_to_accept=Vous n’êtes pas autorisé à accepter ce transfert.
|
||||
transfer.no_permission_to_reject=Vous n’êtes pas autorisé à rejeter ce transfert.
|
||||
|
||||
@ -1235,7 +1222,6 @@ releases=Publications
|
||||
tag=Étiquette
|
||||
released_this=a publié ceci
|
||||
tagged_this=a étiqueté
|
||||
file.title=%s sur %s
|
||||
file_raw=Brut
|
||||
file_history=Historique
|
||||
file_view_source=Voir le code source
|
||||
@ -1863,9 +1849,7 @@ pulls.unrelated_histories=Échec de la fusion: La tête de fusion et la base ne
|
||||
pulls.merge_out_of_date=Échec de la fusion: La base a été mise à jour en cours de fusion. Indice : Réessayez.
|
||||
pulls.head_out_of_date=Échec de la fusion : L’en-tête a été mis à jour pendant la fusion. Conseil : réessayez.
|
||||
pulls.has_merged=Échec : La demande d’ajout est déjà fusionnée, vous ne pouvez plus la fusionner, ni modifier sa branche cible.
|
||||
pulls.push_rejected=Échec de la fusion : la soumission a été rejetée. Revoyez les déclencheurs Git pour ce dépôt.
|
||||
pulls.push_rejected_summary=Message de rejet complet
|
||||
pulls.push_rejected_no_message=Échec de la fusion : la soumission a été rejetée sans raison. Revoyez les déclencheurs Git pour ce dépôt.
|
||||
pulls.open_unmerged_pull_exists=`Vous ne pouvez pas rouvrir ceci car la demande d’ajout #%d, en attente, a des propriétés identiques.`
|
||||
pulls.status_checking=Certains contrôles sont en attente
|
||||
pulls.status_checks_success=Tous les contrôles ont réussi
|
||||
@ -1920,7 +1904,6 @@ milestones.no_due_date=Aucune date d'échéance
|
||||
milestones.open=Ouvrir
|
||||
milestones.close=Fermer
|
||||
milestones.new_subheader=Les jalons peuvent vous aider à organiser vos tickets et à suivre leurs progrès.
|
||||
milestones.completeness=%d%% complété
|
||||
milestones.create=Créer un Jalon
|
||||
milestones.title=Titre
|
||||
milestones.desc=Description
|
||||
@ -2105,7 +2088,6 @@ settings.push_mirror_sync_in_progress=Versement des changements vers le miroir d
|
||||
settings.site=Site Web
|
||||
settings.update_settings=Appliquer
|
||||
settings.update_mirror_settings=Mettre à jour les paramètres du miroir
|
||||
settings.branches.switch_default_branch=Changer de branche
|
||||
settings.branches.update_default_branch=Changer la Branche par Défaut
|
||||
settings.branches.add_new_rule=Ajouter une nouvelle règle
|
||||
settings.advanced_settings=Paramètres avancés
|
||||
@ -2143,7 +2125,6 @@ settings.pulls.default_delete_branch_after_merge=Supprimer la branche après la
|
||||
settings.pulls.default_allow_edits_from_maintainers=Autoriser les modifications par les mainteneurs par défaut
|
||||
settings.releases_desc=Activer les publications du dépôt
|
||||
settings.packages_desc=Activer le registre des paquets du dépôt
|
||||
settings.projects_desc=Activer les projets de dépôt
|
||||
settings.projects_mode_desc=Mode Projets (type de projets à afficher)
|
||||
settings.projects_mode_repo=Projets de dépôt uniquement
|
||||
settings.projects_mode_owner=Projets d’utilisateur ou d’organisation uniquement
|
||||
@ -2365,13 +2346,9 @@ settings.deploy_key_deletion=Supprimer une clef de déploiement
|
||||
settings.deploy_key_deletion_desc=La suppression d'une clef de déploiement révoque son accès à ce dépôt. Continuer ?
|
||||
settings.deploy_key_deletion_success=La clé de déploiement a été supprimée.
|
||||
settings.branches=Branches
|
||||
settings.protected_branch=Protection de branche
|
||||
settings.protected_branch.save_rule=Enregistrer la règle
|
||||
settings.protected_branch.delete_rule=Supprimer la règle
|
||||
settings.protected_branch_can_push=Autoriser la soumission ?
|
||||
settings.protected_branch_can_push_yes=Vous pouvez pousser
|
||||
settings.protected_branch_can_push_no=Vous ne pouvez pas pousser
|
||||
settings.branch_protection=Paramètres de protection pour les branches du motif <b>%s</b>
|
||||
settings.protect_this_branch=Activer la protection de branche
|
||||
settings.protect_this_branch_desc=Empêche les suppressions et limite les poussées et fusions sur cette branche.
|
||||
settings.protect_disable_push=Désactiver la soumission
|
||||
@ -2401,7 +2378,6 @@ settings.protect_merge_whitelist_teams=Équipes autorisées à fusionner :
|
||||
settings.protect_check_status_contexts=Activer le Contrôle Qualité
|
||||
settings.protect_status_check_patterns=Motifs de vérification des statuts :
|
||||
settings.protect_status_check_patterns_desc=Entrez des motifs pour spécifier quelles vérifications doivent réussir avant que des branches puissent être fusionnées. Un motif par ligne. Un motif ne peut être vide.
|
||||
settings.protect_check_status_contexts_desc=Exiger le status « succès » avant de fusionner. Quand activée, une branche protégée ne peux accepter que des soumissions ou des fusions ayant le status « succès ». Lorsqu'il n'y a pas de contexte, la dernière révision fait foi.
|
||||
settings.protect_check_status_contexts_list=Contrôles qualité trouvés au cours de la semaine dernière pour ce dépôt
|
||||
settings.protect_status_check_matched=Correspondant
|
||||
settings.protect_invalid_status_check_pattern=Motif de vérification des statuts incorrect : « %s ».
|
||||
@ -2425,12 +2401,9 @@ settings.protect_protected_file_patterns=Liste des fichiers et motifs protégés
|
||||
settings.protect_protected_file_patterns_desc=Liste de fichiers et de motifs, séparés par un point-virgule « ; », qui ne pourront pas être modifiés même si les utilisateurs disposent des droits sur la branche. Voir la <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>syntaxe glob</a>. Exemples : <code>.drone.yml ; /docs/**/*.txt</code>.
|
||||
settings.protect_unprotected_file_patterns=Liste des fichiers et motifs exclus
|
||||
settings.protect_unprotected_file_patterns_desc=Liste de fichiers et de motifs globs, séparés par un point-virgule « ; », qui pourront être modifiés malgré la protection de branche, par les utilisateurs autorisés. Voir la <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>syntaxe Glob</a>. Exemples : <code>.drone.yml ; /docs/**/*.txt</code>.
|
||||
settings.add_protected_branch=Activer la protection
|
||||
settings.delete_protected_branch=Désactiver la protection
|
||||
settings.update_protect_branch_success=La règle de protection de branche "%s" a été mise à jour.
|
||||
settings.remove_protected_branch_success=La règle de protection de branche "%s" a été retirée.
|
||||
settings.remove_protected_branch_failed=Impossible de retirer la règle de protection de branche "%s".
|
||||
settings.protected_branch_deletion=Désactiver la protection de branche
|
||||
settings.protected_branch_deletion_desc=Désactiver la protection de branche permet aux utilisateurs ayant accès en écriture de pousser des modifications sur la branche. Continuer ?
|
||||
settings.block_rejected_reviews=Bloquer la fusion en cas d’évaluations négatives
|
||||
settings.block_rejected_reviews_desc=La fusion ne sera pas possible lorsque des modifications sont demandées par les évaluateurs officiels, même s'il y a suffisamment d’approbations.
|
||||
@ -2440,7 +2413,6 @@ settings.block_outdated_branch=Bloquer la fusion si la demande d'ajout est obsol
|
||||
settings.block_outdated_branch_desc=La fusion ne sera pas possible lorsque la branche principale est derrière la branche de base.
|
||||
settings.default_branch_desc=Sélectionnez une branche par défaut pour les demandes de fusion et les révisions :
|
||||
settings.merge_style_desc=Styles de fusion
|
||||
settings.default_merge_style_desc=Style de fusion par défaut
|
||||
settings.choose_branch=Choisissez une branche…
|
||||
settings.no_protected_branch=Il n'y a pas de branche protégée.
|
||||
settings.edit_protected_branch=Éditer
|
||||
@ -2659,7 +2631,6 @@ tag.create_success=L'étiquette "%s" a été créée.
|
||||
|
||||
topic.manage_topics=Gérer les sujets
|
||||
topic.done=Terminé
|
||||
topic.count_prompt=Vous ne pouvez pas sélectionner plus de 25 sujets
|
||||
topic.format_prompt=Les sujets doivent commencer par un caractère alphanumérique, peuvent inclure des traits d’union « - » et des points « . », et mesurer jusqu'à 35 caractères. Les lettres doivent être en minuscules.
|
||||
|
||||
find_file.go_to_file=Aller au fichier
|
||||
@ -3296,8 +3267,6 @@ monitor.start=Heure de démarrage
|
||||
monitor.execute_time=Heure d'Éxécution
|
||||
monitor.last_execution_result=Résultat
|
||||
monitor.process.cancel=Annuler le processus
|
||||
monitor.process.cancel_desc=L'annulation d'un processus peut entraîner une perte de données
|
||||
monitor.process.cancel_notices=Annuler : <strong>%s</strong>?
|
||||
monitor.process.children=Enfant
|
||||
|
||||
monitor.queues=Files d'attente
|
||||
@ -3399,7 +3368,6 @@ raw_minutes=minutes
|
||||
|
||||
[dropzone]
|
||||
default_message=Déposez les fichiers ou cliquez ici pour téléverser.
|
||||
invalid_input_type=Vous ne pouvez pas téléverser des fichiers de ce type.
|
||||
file_too_big=La taille du fichier ({{filesize}} Mo) dépasse la taille maximale ({{maxFilesize}} Mo).
|
||||
remove_file=Supprimer le fichier
|
||||
|
||||
|
@ -112,7 +112,6 @@ filter.private=Privát
|
||||
app_desc=Fájdalommentes, saját gépre telepíthető Git szolgáltatás
|
||||
install=Könnyen telepíthető
|
||||
platform=Keresztplatformos
|
||||
platform_desc=A Gitea minden platformon fut, ahol a <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> fordíthat: Windows, macOS, Linux, ARM, stb. Válassza azt, amelyet szereti!
|
||||
lightweight=Könnyűsúlyú
|
||||
license=Nyílt forráskódú
|
||||
|
||||
@ -394,7 +393,6 @@ applications=Alkalmazások
|
||||
orgs=Szervezetek kezelése
|
||||
repos=Tárolók
|
||||
delete=Fiók törlése
|
||||
twofa=Kétlépcsős hitelesítés
|
||||
account_link=Kapcsolt fiókok
|
||||
organization=Szervezetek
|
||||
|
||||
@ -527,11 +525,9 @@ authorized_oauth2_applications=Engedélyezett OAuth2 alkalmazások
|
||||
revoke_key=Visszavonás
|
||||
revoke_oauth2_grant=Hozzáférés megvonása
|
||||
|
||||
twofa_desc=A kétlépcsős hitelesítés növeli a fiók biztonságát.
|
||||
twofa_is_enrolled=A fiókja jelenleg <strong>használ</strong> kétlépcsős hitelesítést.
|
||||
twofa_not_enrolled=A fiókja jelenleg nem használ kétlépcsős hitelesítést.
|
||||
twofa_disable=Kétlépcsős hitelesítés letiltása
|
||||
twofa_scratch_token_regenerate=Kaparós kód újragenerálása
|
||||
twofa_enroll=Kétlépcsős hitelesítés használata
|
||||
twofa_disable_note=A kétlépcsős azonosítás szükség esetén letiltható.
|
||||
twofa_disable_desc=A kétlépcsős hitelesítés letiltása a fiókot kevésbé biztonságossá teszi. Folytatható?
|
||||
@ -948,7 +944,6 @@ milestones.closed=Lezárva: %s
|
||||
milestones.no_due_date=Nincs határidő
|
||||
milestones.open=Megnyitás
|
||||
milestones.close=Lezárás
|
||||
milestones.completeness=%d%% Kész
|
||||
milestones.create=Mérföldkő létrehozása
|
||||
milestones.title=Cím
|
||||
milestones.desc=Leírás
|
||||
@ -1399,8 +1394,7 @@ auths.tip.facebook=Vegyen fel új alkalmazást itt: https://developers.facebook.
|
||||
auths.tip.github=Vegyen fel új OAuth alkalmazást itt: https://github.com/settings/applications/new
|
||||
auths.tip.google_plus=Szerezzen OAuth2 kliens hitelesítési adatokat a Google API konzolban (https://console.developers.google.com/)
|
||||
auths.tip.twitter=Menyjen ide: https://dev.twitter.com/apps, hozzon létre egy alkalmazást és győződjön meg róla, hogy az “Allow this application to be used to Sign in with Twitter” opció be van kapcsolva
|
||||
auths.tip.discord=Vegyen fel új alkalmazást itt:
|
||||
https://discordapp.com/developers/applications/me
|
||||
auths.tip.discord=Vegyen fel új alkalmazást itt: https://discordapp.com/developers/applications/me
|
||||
auths.edit=Hitelesítési forrás szerkesztése
|
||||
auths.activated=A hitelesítési forrás aktiválva lett
|
||||
auths.update_success=A hitelesítési forrás frissítve lett.
|
||||
@ -1536,8 +1530,6 @@ monitor.desc=Leírás
|
||||
monitor.start=Kezdés Időpontja
|
||||
monitor.execute_time=Végrehajtási Idő
|
||||
monitor.process.cancel=Folyamat megszakítása
|
||||
monitor.process.cancel_desc=Egy folyamat megszakítása adatvesztést okozhat
|
||||
monitor.process.cancel_notices=Megszakítás: <strong>%s</strong>?
|
||||
|
||||
monitor.queue.name=Név
|
||||
monitor.queue.type=Típus
|
||||
|
@ -103,7 +103,7 @@ filter.private=Pribadi
|
||||
app_desc=Sebuah layanan hosting Git sendiri yang tanpa kesulitan
|
||||
install=Mudah dipasang
|
||||
platform=Lintas platform
|
||||
platform_desc=Gitea bisa digunakan di mana <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> bisa dijalankan: Windows, macOS, Linux, ARM, dll. Silahkan pilih yang Anda suka!
|
||||
platform_desc=Gitea bisa digunakan di mana <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> bisa dijalankan: Windows, macOS, Linux, ARM, dll. Silahkan pilih yang Anda suka!
|
||||
lightweight=Ringan
|
||||
lightweight_desc=Gitea hanya membutuhkan persyaratan minimal dan bisa berjalan pada Raspberry Pi yang murah. Bisa menghemat listrik!
|
||||
license=Sumber Terbuka
|
||||
@ -315,7 +315,6 @@ applications=Aplikasi
|
||||
orgs=Kelola organisasi
|
||||
repos=Repositori
|
||||
delete=Hapus Akun
|
||||
twofa=Otentikasi Dua-Faktor
|
||||
account_link=Akun Tertaut
|
||||
organization=Organisasi
|
||||
|
||||
@ -451,15 +450,12 @@ revoke_key=Cabut
|
||||
revoke_oauth2_grant=Cabut Akses
|
||||
revoke_oauth2_grant_description=Mencabut akses untuk aplikasi pihak ketiga ini akan mencegahnya mengakses data Anda. Lanjutkan?
|
||||
|
||||
twofa_desc=Autentikasi dua faktor menambah keamanan akun Anda.
|
||||
twofa_is_enrolled=Akun anda saat ini <strong>terdaftar</strong> dalam otentikasi dua-faktor.
|
||||
twofa_not_enrolled=Akun anda saat ini tidak terdaftar dalam otentikasi dua-faktor.
|
||||
twofa_disable=Matikan Autentikasi Dua Faktor
|
||||
twofa_scratch_token_regenerate=Buat Ulang Token Gosok
|
||||
twofa_enroll=Daftarkan ke Autentikasi Dua-Faktor
|
||||
twofa_disable_note=Anda bisa mematikan autentikasi dua-faktor bila diperlukan.
|
||||
twofa_disable_desc=Mematikan autentikasi dua-faktor akan membuat akun Anda kurang aman. Lanjutkan?
|
||||
regenerate_scratch_token_desc=Jika Anda salah tempatkan token gosok Anda atau sudah menggunakannya, Anda bisa setel ulang di sini.
|
||||
twofa_disabled=Otentikasi dua-faktor telah dinonaktifkan.
|
||||
scan_this_image=Pindai gambar ini dengan aplikasi otentikasi:
|
||||
or_enter_secret=Atau masukkan rahasia: %s
|
||||
@ -895,13 +891,6 @@ settings.deploy_keys=Kunci Deploy
|
||||
settings.add_deploy_key=Tambahkan Kunci Deploy
|
||||
settings.title=Judul
|
||||
settings.deploy_key_content=Konten
|
||||
settings.branches=Cabang
|
||||
settings.protected_branch=Perlindungan cabang
|
||||
settings.protected_branch_can_push=Mengizinkan mendorong?
|
||||
settings.protected_branch_can_push_yes=Anda dapat mendorong
|
||||
settings.protected_branch_can_push_no=Anda tidak dapat mendorong
|
||||
settings.add_protected_branch=Aktifkan perlindungan
|
||||
settings.delete_protected_branch=Nonaktifkan perlindungan
|
||||
settings.choose_branch=Pilih branch…
|
||||
settings.edit_protected_branch=Edit
|
||||
|
||||
@ -1221,7 +1210,6 @@ monitor.desc=Deskripsi
|
||||
monitor.start=Waktu mulai
|
||||
monitor.execute_time=Waktu pelaksanaan
|
||||
monitor.process.cancel=Batalkan proses
|
||||
monitor.process.cancel_desc=Membatalkan sebuah proses bisa mengakibatkan hilangnya data
|
||||
|
||||
monitor.queues=Antrian
|
||||
monitor.queue=Antrian: %s
|
||||
@ -1280,7 +1268,6 @@ raw_minutes=menit
|
||||
|
||||
[dropzone]
|
||||
default_message=Jatuhkan berkas disini atau klik untuk mengunggah.
|
||||
invalid_input_type=Anda tidak bisa mengunggah berkas jenis ini.
|
||||
file_too_big=Ukuran berkas ({{filesize}} MB) melebihi ukuran maksimum ({{maxFilesize}} MB).
|
||||
remove_file=Hilangkan berkas
|
||||
|
||||
@ -1305,7 +1292,6 @@ error.unit_not_allowed=Anda tidak diizinkan untuk mengunjungi unit repositori in
|
||||
|
||||
[packages]
|
||||
filter.type=Jenis
|
||||
alpine.repository.branches=Cabang
|
||||
alpine.repository.repositories=Repositori
|
||||
conan.details.repository=Repositori
|
||||
owner.settings.cleanuprules.enabled=Aktif
|
||||
|
@ -138,7 +138,7 @@ network_error=Netkerfisvilla
|
||||
app_desc=Þrautalaus og sjálfhýst Git þjónusta
|
||||
install=Einföld uppsetning
|
||||
platform=Fjölvettvangur
|
||||
platform_desc=Gitea virkar hvar sem að <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> gerir: Linux, macOS, Windows, ARM o. s. frv. Veldu það sem þú vilt!
|
||||
platform_desc=Gitea virkar hvar sem að <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> gerir: Linux, macOS, Windows, ARM o. s. frv. Veldu það sem þú vilt!
|
||||
lightweight=Létt
|
||||
lightweight_desc=Gitea hefur lágar lágmarkskröfur og getur keyrt á ódýrum Raspberry Pi. Sparaðu orku!
|
||||
license=Frjáls Hugbúnaður
|
||||
@ -428,10 +428,8 @@ applications=Forrit
|
||||
orgs=Stjórna Stofnunum
|
||||
repos=Hugbúnaðarsöfn
|
||||
delete=Eyða Reikningi
|
||||
twofa=Tvíþætt Auðkenning
|
||||
account_link=Tengdir Reikningar
|
||||
organization=Stofnanir
|
||||
webauthn=Öryggislyklar
|
||||
|
||||
public_profile=Opinber Notandasíða
|
||||
password_username_disabled=Notendum utan staðarins er ekki heimilt að breyta notendanafni sínu. Vinsamlegast hafðu samband við síðustjórann þinn til að fá frekari upplýsingar.
|
||||
@ -544,7 +542,6 @@ revoke_key=Afturkalla
|
||||
revoke_oauth2_grant=Afturkalla Aðgang
|
||||
|
||||
twofa_disable=Óvirkja Tveggja-Þátta Auðkenningu
|
||||
twofa_scratch_token_regenerate=Endurgera Skrapkóða
|
||||
or_enter_secret=Eða sláðu inn leyndarmálið: %s
|
||||
|
||||
webauthn_nickname=Gælunafn
|
||||
@ -666,7 +663,6 @@ commit=Framlag
|
||||
release=Útgáfa
|
||||
releases=Útgáfur
|
||||
tag=Merki
|
||||
file.title=%s í %s
|
||||
file_raw=Hrátt
|
||||
file_history=Saga
|
||||
file_view_source=Skoða Frumkóða
|
||||
|
@ -2465,6 +2465,18 @@ settings.thread_id=スレッドID
|
||||
settings.matrix.homeserver_url=ホームサーバー URL
|
||||
settings.matrix.room_id=ルーム ID
|
||||
settings.matrix.message_type=メッセージ種別
|
||||
settings.visibility.private.button=プライベートにする
|
||||
settings.visibility.private.text=プライベートに変更した場合、リポジトリを許可されたメンバーのみが閲覧できるようにするだけでなく、フォーク、ウォッチャー、スターとの関係を解除する可能性もあります。
|
||||
settings.visibility.private.bullet_title=<strong>プライベートに変更すると:</strong>
|
||||
settings.visibility.private.bullet_one=リポジトリを許可されたメンバーのみが閲覧できるようにします。
|
||||
settings.visibility.private.bullet_two=<strong>フォーク</strong>、<strong>ウォッチャー</strong>、<strong>スター</strong>との関係を解除する可能性があります。
|
||||
settings.visibility.public.button=公開する
|
||||
settings.visibility.public.text=公開に変更すると、リポジトリを誰でも閲覧できるようにします。
|
||||
settings.visibility.public.bullet_title=<strong>公開に変更すると:</strong>
|
||||
settings.visibility.public.bullet_one=リポジトリを誰でも閲覧できるようにします。
|
||||
settings.visibility.success=リポジトリの公開設定を変更しました。
|
||||
settings.visibility.error=リポジトリの公開設定の変更中にエラーが発生しました。
|
||||
settings.visibility.fork_error=フォークされたリポジトリの公開設定は変更できません。
|
||||
settings.archive.button=アーカイブ
|
||||
settings.archive.header=このリポジトリをアーカイブ
|
||||
settings.archive.text=リポジトリをアーカイブするとリポジトリ全体が読み出し専用となります。 ダッシュボードにも表示されなくなります。 新たなコミット、あるいは、イシューやプルリクエストの作成は、誰もできなくなります (あなたでさえも!)。
|
||||
|
@ -628,6 +628,7 @@ org_still_own_repo=Esta organização ainda possui um ou mais repositórios, eli
|
||||
org_still_own_packages=Esta organização ainda possui um ou mais pacotes, elimine-os primeiro.
|
||||
|
||||
target_branch_not_exist=O ramo de destino não existe.
|
||||
target_ref_not_exist=A referência de destino não existe %s
|
||||
|
||||
admin_cannot_delete_self=Não se pode auto-remover quando tem privilégios de administração. Remova esses privilégios primeiro.
|
||||
|
||||
@ -1273,6 +1274,7 @@ commit_graph.color=Colorido
|
||||
commit.contained_in=Este cometimento está contido em:
|
||||
commit.contained_in_default_branch=Este cometimento é parte do ramo principal
|
||||
commit.load_referencing_branches_and_tags=Carregar ramos e etiquetas que referenciem este cometimento
|
||||
commit.load_tags_failed=O carregamento das etiquetas falhou por causa de um erro interno
|
||||
blame=Responsabilidade
|
||||
download_file=Descarregar ficheiro
|
||||
normal_view=Vista normal
|
||||
@ -3700,6 +3702,11 @@ workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com suce
|
||||
workflow.enable=Habilitar sequência de trabalho
|
||||
workflow.enable_success=A sequência de trabalho '%s' foi habilitada com sucesso.
|
||||
workflow.disabled=A sequência de trabalho está desabilitada.
|
||||
workflow.run=Executar sequência de trabalho
|
||||
workflow.not_found=A sequência de trabalho '%s' não foi encontrada.
|
||||
workflow.run_success=A sequência de trabalho '%s' foi executada com sucesso.
|
||||
workflow.from_ref=Usar sequência de trabalho de
|
||||
workflow.has_workflow_dispatch=Esta sequência de trabalho tem um despoletador de eventos workflow_dispatch.
|
||||
|
||||
need_approval_desc=É necessária aprovação para executar sequências de trabalho para a derivação do pedido de integração.
|
||||
|
||||
|
@ -1117,7 +1117,7 @@ projects=Проекты
|
||||
packages=Пакеты
|
||||
actions=Действия
|
||||
labels=Метки
|
||||
org_labels_desc=Метки уровня организации, которые можно использовать с <strong>всеми репозиториями< / strong> в этой организации
|
||||
org_labels_desc=Метки уровня организации, которые можно использовать с <strong>всеми репозиториями</strong> в этой организации
|
||||
org_labels_desc_manage=управлять
|
||||
|
||||
milestones=Этапы
|
||||
|
@ -1217,7 +1217,7 @@ clear_ref='Geçerli referansı temizle'
|
||||
filter_branch_and_tag=Dal veya biçim imini filtrele
|
||||
find_tag=Etiketi bul
|
||||
branches=Dal
|
||||
tags=Etiket
|
||||
tags=Etiketler
|
||||
issues=Konular
|
||||
pulls=Değişiklik İstekleri
|
||||
projects=Projeler
|
||||
|
877
package-lock.json
generated
877
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
||||
"@citation-js/plugin-csl": "0.7.14",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/relative-time-element": "4.4.2",
|
||||
"@github/relative-time-element": "4.4.3",
|
||||
"@github/text-expander-element": "2.7.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.11.0",
|
||||
@ -33,7 +33,7 @@
|
||||
"jquery": "3.7.1",
|
||||
"katex": "0.16.11",
|
||||
"license-checker-webpack-plugin": "0.2.1",
|
||||
"mermaid": "10.9.1",
|
||||
"mermaid": "11.0.2",
|
||||
"mini-css-extract-plugin": "2.9.0",
|
||||
"minimatch": "10.0.1",
|
||||
"monaco-editor": "0.50.0",
|
||||
|
@ -40,6 +40,7 @@ func LinkAccount(ctx *context.Context) {
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
@ -132,6 +133,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
@ -219,6 +221,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
|
@ -307,6 +307,7 @@ func RegisterOpenID(ctx *context.Context) {
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
|
||||
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
|
||||
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
|
||||
ctx.Data["OpenID"] = oid
|
||||
userName, _ := ctx.Session.Get("openid_determined_username").(string)
|
||||
if userName != "" {
|
||||
|
@ -7,22 +7,28 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -58,8 +64,13 @@ func MustEnableActions(ctx *context.Context) {
|
||||
func List(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("actions.actions")
|
||||
ctx.Data["PageIsActions"] = true
|
||||
workflowID := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
ctx.Data["CurWorkflow"] = workflowID
|
||||
|
||||
var workflows []Workflow
|
||||
var curWorkflow *model.Workflow
|
||||
if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
|
||||
ctx.ServerError("IsEmpty", err)
|
||||
return
|
||||
@ -140,6 +151,10 @@ func List(ctx *context.Context) {
|
||||
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job")
|
||||
}
|
||||
workflows = append(workflows, workflow)
|
||||
|
||||
if workflow.Entry.Name() == workflowID {
|
||||
curWorkflow = wf
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Data["workflows"] = workflows
|
||||
@ -150,17 +165,46 @@ func List(ctx *context.Context) {
|
||||
page = 1
|
||||
}
|
||||
|
||||
workflow := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
ctx.Data["CurWorkflow"] = workflow
|
||||
|
||||
actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||
ctx.Data["ActionsConfig"] = actionsConfig
|
||||
|
||||
if len(workflow) > 0 && ctx.Repo.IsAdmin() {
|
||||
if len(workflowID) > 0 && ctx.Repo.IsAdmin() {
|
||||
ctx.Data["AllowDisableOrEnableWorkflow"] = true
|
||||
ctx.Data["CurWorkflowDisabled"] = actionsConfig.IsWorkflowDisabled(workflow)
|
||||
isWorkflowDisabled := actionsConfig.IsWorkflowDisabled(workflowID)
|
||||
ctx.Data["CurWorkflowDisabled"] = isWorkflowDisabled
|
||||
|
||||
if !isWorkflowDisabled && curWorkflow != nil {
|
||||
workflowDispatchConfig := workflowDispatchConfig(curWorkflow)
|
||||
if workflowDispatchConfig != nil {
|
||||
ctx.Data["WorkflowDispatchConfig"] = workflowDispatchConfig
|
||||
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
}
|
||||
branches, err := git_model.FindBranchNames(ctx, branchOpts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindBranchNames", err)
|
||||
return
|
||||
}
|
||||
// always put default branch on the top if it exists
|
||||
if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
|
||||
branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
|
||||
branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
|
||||
}
|
||||
ctx.Data["Branches"] = branches
|
||||
|
||||
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTagNamesByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
|
||||
@ -177,7 +221,7 @@ func List(ctx *context.Context) {
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
},
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowID: workflow,
|
||||
WorkflowID: workflowID,
|
||||
TriggerUserID: actorID,
|
||||
}
|
||||
|
||||
@ -214,7 +258,7 @@ func List(ctx *context.Context) {
|
||||
|
||||
pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParamString("workflow", workflow)
|
||||
pager.AddParamString("workflow", workflowID)
|
||||
pager.AddParamString("actor", fmt.Sprint(actorID))
|
||||
pager.AddParamString("status", fmt.Sprint(status))
|
||||
ctx.Data["Page"] = pager
|
||||
@ -222,3 +266,86 @@ func List(ctx *context.Context) {
|
||||
|
||||
ctx.HTML(http.StatusOK, tplListActions)
|
||||
}
|
||||
|
||||
type WorkflowDispatchInput struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Required bool `yaml:"required"`
|
||||
Default string `yaml:"default"`
|
||||
Type string `yaml:"type"`
|
||||
Options []string `yaml:"options"`
|
||||
}
|
||||
|
||||
type WorkflowDispatch struct {
|
||||
Inputs []WorkflowDispatchInput
|
||||
}
|
||||
|
||||
func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch {
|
||||
switch w.RawOn.Kind {
|
||||
case yaml.ScalarNode:
|
||||
var val string
|
||||
if !decodeNode(w.RawOn, &val) {
|
||||
return nil
|
||||
}
|
||||
if val == "workflow_dispatch" {
|
||||
return &WorkflowDispatch{}
|
||||
}
|
||||
case yaml.SequenceNode:
|
||||
var val []string
|
||||
if !decodeNode(w.RawOn, &val) {
|
||||
return nil
|
||||
}
|
||||
for _, v := range val {
|
||||
if v == "workflow_dispatch" {
|
||||
return &WorkflowDispatch{}
|
||||
}
|
||||
}
|
||||
case yaml.MappingNode:
|
||||
var val map[string]yaml.Node
|
||||
if !decodeNode(w.RawOn, &val) {
|
||||
return nil
|
||||
}
|
||||
|
||||
workflowDispatchNode, found := val["workflow_dispatch"]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
var workflowDispatch WorkflowDispatch
|
||||
var workflowDispatchVal map[string]yaml.Node
|
||||
if !decodeNode(workflowDispatchNode, &workflowDispatchVal) {
|
||||
return &workflowDispatch
|
||||
}
|
||||
|
||||
inputsNode, found := workflowDispatchVal["inputs"]
|
||||
if !found || inputsNode.Kind != yaml.MappingNode {
|
||||
return &workflowDispatch
|
||||
}
|
||||
|
||||
i := 0
|
||||
for {
|
||||
if i+1 >= len(inputsNode.Content) {
|
||||
break
|
||||
}
|
||||
var input WorkflowDispatchInput
|
||||
if decodeNode(*inputsNode.Content[i+1], &input) {
|
||||
input.Name = inputsNode.Content[i].Value
|
||||
workflowDispatch.Inputs = append(workflowDispatch.Inputs, input)
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
return &workflowDispatch
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeNode(node yaml.Node, out any) bool {
|
||||
if err := node.Decode(out); err != nil {
|
||||
log.Warn("Failed to decode node %v into %T: %v", node, out, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
156
routers/web/repo/actions/actions_test.go
Normal file
156
routers/web/repo/actions/actions_test.go
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
act_model "github.com/nektos/act/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) {
|
||||
yaml := `
|
||||
name: local-action-docker-url
|
||||
`
|
||||
workflow, err := act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch := workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: push
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: workflow_dispatch
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: [push, pull_request]
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.Nil(t, workflowDispatch)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on: [push, workflow_dispatch]
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
- push
|
||||
- workflow_dispatch
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
`
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Nil(t, workflowDispatch.Inputs)
|
||||
|
||||
yaml = `
|
||||
name: local-action-docker-url
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
type: choice
|
||||
options:
|
||||
- info
|
||||
- warning
|
||||
- debug
|
||||
boolean_default_true:
|
||||
description: 'Test scenario tags'
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
boolean_default_false:
|
||||
description: 'Test scenario tags'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
`
|
||||
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml))
|
||||
assert.NoError(t, err, "read workflow should succeed")
|
||||
workflowDispatch = workflowDispatchConfig(workflow)
|
||||
assert.NotNil(t, workflowDispatch)
|
||||
assert.Equal(t, WorkflowDispatchInput{
|
||||
Name: "logLevel",
|
||||
Default: "warning",
|
||||
Description: "Log level",
|
||||
Options: []string{
|
||||
"info",
|
||||
"warning",
|
||||
"debug",
|
||||
},
|
||||
Required: true,
|
||||
Type: "choice",
|
||||
}, workflowDispatch.Inputs[0])
|
||||
assert.Equal(t, WorkflowDispatchInput{
|
||||
Name: "boolean_default_true",
|
||||
Default: "true",
|
||||
Description: "Test scenario tags",
|
||||
Required: true,
|
||||
Type: "boolean",
|
||||
}, workflowDispatch.Inputs[1])
|
||||
assert.Equal(t, WorkflowDispatchInput{
|
||||
Name: "boolean_default_false",
|
||||
Default: "false",
|
||||
Description: "Test scenario tags",
|
||||
Required: true,
|
||||
Type: "boolean",
|
||||
}, workflowDispatch.Inputs[2])
|
||||
}
|
@ -18,18 +18,26 @@ import (
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
context_module "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
"github.com/nektos/act/pkg/model"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@ -745,3 +753,164 @@ func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) {
|
||||
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
|
||||
ctx.JSONRedirect(redirectURL)
|
||||
}
|
||||
|
||||
func Run(ctx *context_module.Context) {
|
||||
redirectURL := fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s", ctx.Repo.RepoLink, url.QueryEscape(ctx.FormString("workflow")),
|
||||
url.QueryEscape(ctx.FormString("actor")), url.QueryEscape(ctx.FormString("status")))
|
||||
|
||||
workflowID := ctx.FormString("workflow")
|
||||
if len(workflowID) == 0 {
|
||||
ctx.ServerError("workflow", nil)
|
||||
return
|
||||
}
|
||||
|
||||
ref := ctx.FormString("ref")
|
||||
if len(ref) == 0 {
|
||||
ctx.ServerError("ref", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// can not rerun job when workflow is disabled
|
||||
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
|
||||
cfg := cfgUnit.ActionsConfig()
|
||||
if cfg.IsWorkflowDisabled(workflowID) {
|
||||
ctx.Flash.Error(ctx.Tr("actions.workflow.disabled"))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
// get target commit of run from specified ref
|
||||
refName := git.RefName(ref)
|
||||
var runTargetCommit *git.Commit
|
||||
var err error
|
||||
if refName.IsTag() {
|
||||
runTargetCommit, err = ctx.Repo.GitRepo.GetTagCommit(refName.TagName())
|
||||
} else if refName.IsBranch() {
|
||||
runTargetCommit, err = ctx.Repo.GitRepo.GetBranchCommit(refName.BranchName())
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("form.git_ref_name_error", ref))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("form.target_ref_not_exist", ref))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
// get workflow entry from default branch commit
|
||||
defaultBranchCommit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
entries, err := actions.ListWorkflows(defaultBranchCommit)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// find workflow from commit
|
||||
var workflows []*jobparser.SingleWorkflow
|
||||
for _, entry := range entries {
|
||||
if entry.Name() == workflowID {
|
||||
content, err := actions.GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
workflows, err = jobparser.Parse(content)
|
||||
if err != nil {
|
||||
ctx.ServerError("workflow", err)
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(workflows) == 0 {
|
||||
ctx.Flash.Error(ctx.Tr("actions.workflow.not_found", workflowID))
|
||||
ctx.Redirect(redirectURL)
|
||||
return
|
||||
}
|
||||
|
||||
// get inputs from post
|
||||
workflow := &model.Workflow{
|
||||
RawOn: workflows[0].RawOn,
|
||||
}
|
||||
inputs := make(map[string]any)
|
||||
if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil {
|
||||
for name, config := range workflowDispatch.Inputs {
|
||||
value := ctx.Req.PostForm.Get(name)
|
||||
if config.Type == "boolean" {
|
||||
// https://www.w3.org/TR/html401/interact/forms.html
|
||||
// https://stackoverflow.com/questions/11424037/do-checkbox-inputs-only-post-data-if-theyre-checked
|
||||
// Checkboxes (and radio buttons) are on/off switches that may be toggled by the user.
|
||||
// A switch is "on" when the control element's checked attribute is set.
|
||||
// When a form is submitted, only "on" checkbox controls can become successful.
|
||||
inputs[name] = strconv.FormatBool(value == "on")
|
||||
} else if value != "" {
|
||||
inputs[name] = value
|
||||
} else {
|
||||
inputs[name] = config.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ctx.Req.PostForm -> WorkflowDispatchPayload.Inputs -> ActionRun.EventPayload -> runner: ghc.Event
|
||||
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
|
||||
// https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_dispatch
|
||||
workflowDispatchPayload := &api.WorkflowDispatchPayload{
|
||||
Workflow: workflowID,
|
||||
Ref: ref,
|
||||
Repository: convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||
Inputs: inputs,
|
||||
Sender: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone),
|
||||
}
|
||||
var eventPayload []byte
|
||||
if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil {
|
||||
ctx.ServerError("JSONPayload", err)
|
||||
return
|
||||
}
|
||||
|
||||
run := &actions_model.ActionRun{
|
||||
Title: strings.SplitN(runTargetCommit.CommitMessage, "\n", 2)[0],
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||
WorkflowID: workflowID,
|
||||
TriggerUserID: ctx.Doer.ID,
|
||||
Ref: ref,
|
||||
CommitSHA: runTargetCommit.ID.String(),
|
||||
IsForkPullRequest: false,
|
||||
Event: "workflow_dispatch",
|
||||
TriggerEvent: "workflow_dispatch",
|
||||
EventPayload: string(eventPayload),
|
||||
Status: actions_model.StatusWaiting,
|
||||
}
|
||||
|
||||
// cancel running jobs of the same workflow
|
||||
if err := actions_model.CancelPreviousJobs(
|
||||
ctx,
|
||||
run.RepoID,
|
||||
run.Ref,
|
||||
run.WorkflowID,
|
||||
run.Event,
|
||||
); err != nil {
|
||||
log.Error("CancelRunningJobs: %v", err)
|
||||
}
|
||||
|
||||
// Insert the action run and its associated jobs into the database
|
||||
if err := actions_model.InsertRun(ctx, run, workflows); err != nil {
|
||||
ctx.ServerError("workflow", err)
|
||||
return
|
||||
}
|
||||
|
||||
alljobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: run.ID})
|
||||
if err != nil {
|
||||
log.Error("FindRunJobs: %v", err)
|
||||
}
|
||||
actions_service.CreateCommitStatus(ctx, alljobs...)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("actions.workflow.run_success", workflowID))
|
||||
ctx.Redirect(redirectURL)
|
||||
}
|
||||
|
@ -83,7 +83,17 @@ func Commits(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["Commits"] = processGitCommits(ctx, commits)
|
||||
|
||||
commitIDs := make([]string, 0, len(commits))
|
||||
for _, c := range commits {
|
||||
commitIDs = append(commitIDs, c.ID.String())
|
||||
}
|
||||
commitsTagsMap, err := repo_model.FindTagsByCommitIDs(ctx, ctx.Repo.Repository.ID, commitIDs...)
|
||||
if err != nil {
|
||||
log.Error("FindTagsByCommitIDs: %v", err)
|
||||
ctx.Flash.Error(ctx.Tr("repo.commit.load_tags_failed"))
|
||||
} else {
|
||||
ctx.Data["CommitsTagsMap"] = commitsTagsMap
|
||||
}
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
|
@ -240,7 +240,8 @@ func SettingsPost(ctx *context.Context) {
|
||||
|
||||
remoteAddress, err := util.SanitizeURL(form.MirrorAddress)
|
||||
if err != nil {
|
||||
ctx.ServerError("SanitizeURL", err)
|
||||
ctx.Data["Err_MirrorAddress"] = true
|
||||
handleSettingRemoteAddrError(ctx, err, form)
|
||||
return
|
||||
}
|
||||
pullMirror.RemoteAddress = remoteAddress
|
||||
@ -401,7 +402,8 @@ func SettingsPost(ctx *context.Context) {
|
||||
|
||||
remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress)
|
||||
if err != nil {
|
||||
ctx.ServerError("SanitizeURL", err)
|
||||
ctx.Data["Err_PushMirrorAddress"] = true
|
||||
handleSettingRemoteAddrError(ctx, err, form)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1384,6 +1384,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("", actions.List)
|
||||
m.Post("/disable", reqRepoAdmin, actions.DisableWorkflowFile)
|
||||
m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile)
|
||||
m.Post("/run", reqRepoAdmin, actions.Run)
|
||||
|
||||
m.Group("/runs/{run}", func() {
|
||||
m.Combo("").
|
||||
|
@ -386,7 +386,7 @@ func (n *actionsNotifier) ForkRepository(ctx context.Context, doer *user_model.U
|
||||
// Add to hook queue for created repo after session commit.
|
||||
if u.IsOrganization() {
|
||||
newNotifyInput(repo, doer, webhook_module.HookEventRepository).
|
||||
WithRef(oldRepo.DefaultBranch).
|
||||
WithRef(git.RefNameFromBranch(oldRepo.DefaultBranch).String()).
|
||||
WithPayload(&api.RepositoryPayload{
|
||||
Action: api.HookRepoCreated,
|
||||
Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm_model.AccessModeOwner}),
|
||||
|
@ -65,7 +65,7 @@ type notifyInput struct {
|
||||
Event webhook_module.HookEventType
|
||||
|
||||
// optional
|
||||
Ref string
|
||||
Ref git.RefName
|
||||
Payload api.Payloader
|
||||
PullRequest *issues_model.PullRequest
|
||||
}
|
||||
@ -89,7 +89,7 @@ func (input *notifyInput) WithDoer(doer *user_model.User) *notifyInput {
|
||||
}
|
||||
|
||||
func (input *notifyInput) WithRef(ref string) *notifyInput {
|
||||
input.Ref = ref
|
||||
input.Ref = git.RefName(ref)
|
||||
return input
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ func (input *notifyInput) WithPayload(payload api.Payloader) *notifyInput {
|
||||
func (input *notifyInput) WithPullRequest(pr *issues_model.PullRequest) *notifyInput {
|
||||
input.PullRequest = pr
|
||||
if input.Ref == "" {
|
||||
input.Ref = pr.GetGitRefName()
|
||||
input.Ref = git.RefName(pr.GetGitRefName())
|
||||
}
|
||||
return input
|
||||
}
|
||||
@ -144,20 +144,25 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
defer gitRepo.Close()
|
||||
|
||||
ref := input.Ref
|
||||
if ref != input.Repo.DefaultBranch && actions_module.IsDefaultBranchWorkflow(input.Event) {
|
||||
if ref.BranchName() != input.Repo.DefaultBranch && actions_module.IsDefaultBranchWorkflow(input.Event) {
|
||||
if ref != "" {
|
||||
log.Warn("Event %q should only trigger workflows on the default branch, but its ref is %q. Will fall back to the default branch",
|
||||
input.Event, ref)
|
||||
}
|
||||
ref = input.Repo.DefaultBranch
|
||||
ref = git.RefNameFromBranch(input.Repo.DefaultBranch)
|
||||
}
|
||||
if ref == "" {
|
||||
log.Warn("Ref of event %q is empty, will fall back to the default branch", input.Event)
|
||||
ref = input.Repo.DefaultBranch
|
||||
ref = git.RefNameFromBranch(input.Repo.DefaultBranch)
|
||||
}
|
||||
|
||||
commitID, err := gitRepo.GetRefCommitID(ref.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("gitRepo.GetRefCommitID: %w", err)
|
||||
}
|
||||
|
||||
// Get the commit object for the ref
|
||||
commit, err := gitRepo.GetCommit(ref)
|
||||
commit, err := gitRepo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
||||
}
|
||||
@ -168,7 +173,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
|
||||
var detectedWorkflows []*actions_module.DetectedWorkflow
|
||||
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && git.RefName(input.Ref).BranchName() == input.Repo.DefaultBranch
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
|
||||
input.Event,
|
||||
input.Payload,
|
||||
@ -220,12 +225,12 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
}
|
||||
|
||||
if shouldDetectSchedules {
|
||||
if err := handleSchedules(ctx, schedules, commit, input, ref); err != nil {
|
||||
if err := handleSchedules(ctx, schedules, commit, input, ref.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
|
||||
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref.String())
|
||||
}
|
||||
|
||||
func skipWorkflows(input *notifyInput, commit *git.Commit) bool {
|
||||
|
@ -245,9 +245,21 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
|
||||
defer headGitRepo.Close()
|
||||
}
|
||||
|
||||
headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)
|
||||
if pr.HeadRepo == nil || !headBranchExist {
|
||||
log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
|
||||
switch pr.Flow {
|
||||
case issues_model.PullRequestFlowGithub:
|
||||
headBranchExist := headGitRepo.IsBranchExist(pr.HeadBranch)
|
||||
if pr.HeadRepo == nil || !headBranchExist {
|
||||
log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch: %s]", pr, pr.HeadRepoID, pr.HeadBranch)
|
||||
return
|
||||
}
|
||||
case issues_model.PullRequestFlowAGit:
|
||||
headBranchExist := git.IsReferenceExist(ctx, baseGitRepo.Path, pr.GetGitRefName())
|
||||
if !headBranchExist {
|
||||
log.Warn("Head branch of auto merge %-v does not exist [HeadRepoID: %d, Branch(Agit): %s]", pr, pr.HeadRepoID, pr.HeadBranch)
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Error("wrong flow type %d", pr.Flow)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -219,6 +219,10 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
||||
// Reset cached commit count
|
||||
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
|
||||
|
||||
return handleCloseCrossReferences(ctx, pr, doer)
|
||||
}
|
||||
|
||||
func handleCloseCrossReferences(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) error {
|
||||
// Resolve cross references
|
||||
refs, err := pr.ResolveCrossReferences(ctx)
|
||||
if err != nil {
|
||||
@ -542,5 +546,6 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
||||
|
||||
notify_service.MergePullRequest(baseGitRepo.Ctx, doer, pr)
|
||||
log.Info("manuallyMerged[%d]: Marked as manually merged into %s/%s by commit id: %s", pr.ID, pr.BaseRepo.Name, pr.BaseBranch, commitID)
|
||||
return nil
|
||||
|
||||
return handleCloseCrossReferences(ctx, pr, doer)
|
||||
}
|
||||
|
@ -76,6 +76,11 @@
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .WorkflowDispatchConfig}}
|
||||
{{template "repo/actions/workflow_dispatch" .}}
|
||||
{{end}}
|
||||
|
||||
{{template "repo/actions/runs_list" .}}
|
||||
</div>
|
||||
</div>
|
||||
|
78
templates/repo/actions/workflow_dispatch.tmpl
Normal file
78
templates/repo/actions/workflow_dispatch.tmpl
Normal file
@ -0,0 +1,78 @@
|
||||
<div class="ui blue info message tw-flex tw-justify-between tw-items-center">
|
||||
<span class="ui text middle">{{ctx.Locale.Tr "actions.workflow.has_workflow_dispatch"}}</span>
|
||||
<button class="ui mini button show-modal" data-modal="#runWorkflowDispatchModal">{{ctx.Locale.Tr "actions.workflow.run"}}{{svg "octicon-triangle-down" 14 "dropdown icon"}}</button>
|
||||
</div>
|
||||
<div id="runWorkflowDispatchModal" class="ui tiny modal">
|
||||
<div class="content">
|
||||
<form id="runWorkflowDispatchForm" class="ui form" action="{{$.Link}}/run?workflow={{$.CurWorkflow}}&actor={{$.CurActor}}&status={{.Status}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="ui inline field required tw-flex tw-items-center">
|
||||
<span class="ui inline required field">
|
||||
<label>{{ctx.Locale.Tr "actions.workflow.from_ref"}}:</label>
|
||||
</span>
|
||||
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-items-nowrap">
|
||||
<input type="hidden" name="ref" value="refs/heads/{{index .Branches 0}}">
|
||||
{{svg "octicon-git-branch" 14}}
|
||||
<div class="default text">{{index .Branches 0}}</div>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu transition">
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-filter" 16}}</i>
|
||||
<input name="search" type="text" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}...">
|
||||
</div>
|
||||
<div class="branch-tag-tab">
|
||||
<a class="branch-tag-item reference column muted active" href="#" data-target="#branch-list">
|
||||
{{svg "octicon-git-branch" 16 "tw-mr-1"}} {{ctx.Locale.Tr "repo.branches"}}
|
||||
</a>
|
||||
<a class="branch-tag-item reference column muted" href="#" data-target="#tag-list">
|
||||
{{svg "octicon-tag" 16 "tw-mr-1"}} {{ctx.Locale.Tr "repo.tags"}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="branch-tag-divider"></div>
|
||||
<div id="branch-list" class="scrolling menu reference-list-menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="refs/heads/{{.}}" title="{{.}}">{{.}}</div>
|
||||
{{else}}
|
||||
<div class="item">{{ctx.Locale.Tr "no_results_found"}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="tag-list" class="scrolling menu reference-list-menu tw-hidden">
|
||||
{{range .Tags}}
|
||||
<div class="item" data-value="refs/tags/{{.}}" title="{{.}}">{{.}}</div>
|
||||
{{else}}
|
||||
<div class="item">{{ctx.Locale.Tr "no_results_found"}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
{{range $item := .WorkflowDispatchConfig.Inputs}}
|
||||
<div class="ui field {{if .Required}}required{{end}}">
|
||||
{{if eq .Type "choice"}}
|
||||
<label>{{.Description}}:</label>
|
||||
<select class="ui selection type dropdown" name="{{.Name}}">
|
||||
{{range .Options}}
|
||||
<option value="{{.}}" {{if eq $item.Default .}}selected{{end}} >{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
{{else if eq .Type "boolean"}}
|
||||
<div class="ui inline checkbox">
|
||||
<label>{{.Description}}</label>
|
||||
<input type="checkbox" name="{{.Name}}" {{if eq .Default "true"}}checked{{end}}>
|
||||
</div>
|
||||
{{else if eq .Type "number"}}
|
||||
<label>{{.Description}}:</label>
|
||||
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
|
||||
{{else}}
|
||||
<label>{{.Description}}:</label>
|
||||
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<button class="ui tiny primary button" type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -20,9 +20,11 @@
|
||||
<tr>
|
||||
<td>
|
||||
<div class="flex-text-block">
|
||||
{{if .DefaultBranchBranch.IsProtected}}{{svg "octicon-shield-lock"}}{{end}}
|
||||
<a class="gt-ellipsis" href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
|
||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
|
||||
{{if .DefaultBranchBranch.IsProtected}}
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock"}}</span>
|
||||
{{end}}
|
||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
||||
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
|
||||
</div>
|
||||
<p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
|
||||
@ -39,7 +41,7 @@
|
||||
</button>
|
||||
{{end}}
|
||||
{{if .EnableFeed}}
|
||||
<a role="button" class="btn interact-bg tw-p-2" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a>
|
||||
<a role="button" class="btn interact-bg tw-p-2" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">{{svg "octicon-rss"}}</a>
|
||||
{{end}}
|
||||
{{if not $.DisableDownloadSourceArchives}}
|
||||
<div class="ui dropdown btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}">
|
||||
@ -88,14 +90,16 @@
|
||||
{{if .DBBranch.IsDeleted}}
|
||||
<div class="flex-text-block">
|
||||
<span class="gt-ellipsis">{{.DBBranch.Name}}</span>
|
||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
|
||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
<p class="info">{{ctx.Locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix ctx.Locale}}</p>
|
||||
{{else}}
|
||||
<div class="flex-text-block">
|
||||
{{if .IsProtected}}{{svg "octicon-shield-lock"}}{{end}}
|
||||
<a class="gt-ellipsis" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
|
||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
|
||||
{{if .IsProtected}}
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock"}}</span>
|
||||
{{end}}
|
||||
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
|
||||
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
|
||||
</div>
|
||||
<p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
|
||||
@ -156,7 +160,7 @@
|
||||
</button>
|
||||
{{end}}
|
||||
{{if $.EnableFeed}}
|
||||
<a role="button" class="btn interact-bg tw-p-2" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a>
|
||||
<a role="button" class="btn interact-bg tw-p-2" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">{{svg "octicon-rss"}}</a>
|
||||
{{end}}
|
||||
{{if and (not .DBBranch.IsDeleted) (not $.DisableDownloadSourceArchives)}}
|
||||
<div class="ui dropdown btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.download" (.DBBranch.Name)}}">
|
||||
|
@ -72,6 +72,11 @@
|
||||
{{if IsMultilineCommitMessage .Message}}
|
||||
<pre class="commit-body tw-hidden">{{RenderCommitBody $.Context .Message ($.Repository.ComposeMetas ctx)}}</pre>
|
||||
{{end}}
|
||||
{{if $.CommitsTagsMap}}
|
||||
{{range (index $.CommitsTagsMap .ID.String)}}
|
||||
{{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .TagName "IsRelease" (not .IsTag) -}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</td>
|
||||
{{if .Committer}}
|
||||
<td class="text right aligned">{{TimeSince .Committer.When ctx.Locale}}</td>
|
||||
|
@ -42,9 +42,7 @@
|
||||
</a>
|
||||
{{end}}
|
||||
{{else if eq $refGroup "tags"}}
|
||||
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/src/tag/{{.ShortName|PathEscape}}">
|
||||
{{svg "octicon-tag"}} {{.ShortName}}
|
||||
</a>
|
||||
{{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .ShortName -}}
|
||||
{{else if eq $refGroup "remotes"}}
|
||||
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/src/commit/{{$commit.Rev|PathEscape}}">
|
||||
{{svg "octicon-cross-reference"}} {{.ShortName}}
|
||||
|
3
templates/repo/tag/name.tmpl
Normal file
3
templates/repo/tag/name.tmpl
Normal file
@ -0,0 +1,3 @@
|
||||
<a class="ui label basic tiny button{{if .IsRelease}} primary{{end}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
|
||||
{{svg "octicon-tag"}} {{.TagName}}
|
||||
</a>
|
7
tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive
Executable file
7
tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
ORI_DIR=`pwd`
|
||||
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
|
||||
cd "$ORI_DIR"
|
||||
for i in `ls "$SHELL_FOLDER/proc-receive.d"`; do
|
||||
sh "$SHELL_FOLDER/proc-receive.d/$i"
|
||||
done
|
2
tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive.d/gitea
Executable file
2
tests/gitea-repositories-meta/user2/repo1.git/hooks/proc-receive.d/gitea
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" proc-receive
|
@ -427,7 +427,7 @@ func TestCreateDeleteRefEvent(t *testing.T) {
|
||||
Title: "add workflow",
|
||||
RepoID: repo.ID,
|
||||
Event: "delete",
|
||||
Ref: "main",
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "createdelete.yml",
|
||||
CommitSHA: branch.CommitID,
|
||||
})
|
||||
@ -442,7 +442,7 @@ func TestCreateDeleteRefEvent(t *testing.T) {
|
||||
Title: "add workflow",
|
||||
RepoID: repo.ID,
|
||||
Event: "delete",
|
||||
Ref: "main",
|
||||
Ref: "refs/heads/main",
|
||||
WorkflowID: "createdelete.yml",
|
||||
CommitSHA: branch.CommitID,
|
||||
})
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
@ -846,3 +847,132 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
|
||||
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
// create a pull request
|
||||
baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = baseAPITestContext.GitPath()
|
||||
u.User = url.UserPassword("user2", userPassword)
|
||||
|
||||
t.Run("Clone", doGitClone(dstPath, u))
|
||||
|
||||
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.AddChanges(dstPath, true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = git.CommitChanges(dstPath, git.CommitChangesOptions{
|
||||
Committer: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
When: time.Now(),
|
||||
},
|
||||
Author: &git.Signature{
|
||||
Email: "user2@example.com",
|
||||
Name: "user2",
|
||||
When: time.Now(),
|
||||
},
|
||||
Message: "Testing commit 1",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
stderrBuf := &bytes.Buffer{}
|
||||
|
||||
err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").
|
||||
AddDynamicArguments(`topic=test/head2`).
|
||||
AddArguments("-o").
|
||||
AddDynamicArguments(`title="create a test pull request with agit"`).
|
||||
AddArguments("-o").
|
||||
AddDynamicArguments(`description="This PR is a test pull request which created with agit"`).
|
||||
Run(&git.RunOpts{Dir: dstPath, Stderr: stderrBuf})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6")
|
||||
|
||||
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
|
||||
Flow: issues_model.PullRequestFlowAGit,
|
||||
BaseRepoID: baseRepo.ID,
|
||||
BaseBranch: "master",
|
||||
HeadRepoID: baseRepo.ID,
|
||||
HeadBranch: "user2/test/head2",
|
||||
})
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
// add protected branch for commit status
|
||||
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
|
||||
// Change master branch to protected
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
||||
"_csrf": csrf,
|
||||
"rule_name": "master",
|
||||
"enable_push": "true",
|
||||
"enable_status_check": "true",
|
||||
"status_check_contexts": "gitea/actions",
|
||||
"required_approvals": "1",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
// first time insert automerge record, return true
|
||||
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, scheduled)
|
||||
|
||||
// second time insert automerge record, return false because it does exist
|
||||
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
||||
assert.Error(t, err)
|
||||
assert.False(t, scheduled)
|
||||
|
||||
// reload pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.Empty(t, pr.MergedCommitID)
|
||||
|
||||
// update commit status to success, then it should be merged automatically
|
||||
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
|
||||
assert.NoError(t, err)
|
||||
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
assert.NoError(t, err)
|
||||
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
|
||||
assert.NoError(t, err)
|
||||
baseGitRepo.Close()
|
||||
defer func() {
|
||||
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
|
||||
}()
|
||||
|
||||
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
|
||||
State: api.CommitStatusSuccess,
|
||||
TargetURL: "https://gitea.com",
|
||||
Context: "gitea/actions",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// reload pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.Empty(t, pr.MergedCommitID)
|
||||
|
||||
// approve the PR from non-author
|
||||
approveSession := loginUser(t, "user1")
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/user2/repo1/pulls/%d", pr.Index))
|
||||
resp := approveSession.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// realod pr again
|
||||
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
||||
assert.True(t, pr.HasMerged)
|
||||
assert.NotEmpty(t, pr.MergedCommitID)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
})
|
||||
}
|
||||
|
@ -43,6 +43,19 @@ function reloadConfirmDraftComment() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
export function initBranchSelectorTabs() {
|
||||
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
||||
if (!elSelectBranch) return;
|
||||
|
||||
$(elSelectBranch).find('.reference.column').on('click', function () {
|
||||
hideElem($(elSelectBranch).find('.scrolling.reference-list-menu'));
|
||||
showElem(this.getAttribute('data-target'));
|
||||
queryElemChildren(this.parentNode, '.branch-tag-item', (el) => el.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoCommentForm() {
|
||||
const $commentForm = $('.comment.form');
|
||||
if (!$commentForm.length) return;
|
||||
@ -81,13 +94,6 @@ export function initRepoCommentForm() {
|
||||
elSelectBranch.querySelector('.text-branch-name').textContent = selectedText;
|
||||
}
|
||||
});
|
||||
$selectBranch.find('.reference.column').on('click', function () {
|
||||
hideElem($selectBranch.find('.scrolling.reference-list-menu'));
|
||||
showElem(this.getAttribute('data-target'));
|
||||
queryElemChildren(this.parentNode, '.branch-tag-item', (el) => el.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
initBranchSelector();
|
||||
|
@ -60,7 +60,7 @@ import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts';
|
||||
import {initRepoBranchButton} from './features/repo-branch.ts';
|
||||
import {initCommonOrganization} from './features/common-organization.ts';
|
||||
import {initRepoWikiForm} from './features/repo-wiki.ts';
|
||||
import {initRepoCommentForm, initRepository} from './features/repo-legacy.ts';
|
||||
import {initRepoCommentForm, initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts';
|
||||
import {initCopyContent} from './features/copycontent.ts';
|
||||
import {initCaptcha} from './features/captcha.ts';
|
||||
import {initRepositoryActionView} from './components/RepoActionView.vue';
|
||||
@ -182,6 +182,7 @@ onDomReady(() => {
|
||||
initRepoBranchButton,
|
||||
initRepoCodeView,
|
||||
initRepoCommentForm,
|
||||
initBranchSelectorTabs,
|
||||
initRepoEllipsisButton,
|
||||
initRepoDiffCommitBranchesAndTags,
|
||||
initRepoEditor,
|
||||
|
@ -20,6 +20,7 @@ export async function renderMermaid() {
|
||||
startOnLoad: false,
|
||||
theme: isDarkTheme() ? 'dark' : 'neutral',
|
||||
securityLevel: 'strict',
|
||||
suppressErrorRendering: true,
|
||||
});
|
||||
|
||||
for (const el of els) {
|
||||
|
Loading…
Reference in New Issue
Block a user