Compare commits
70 commits
forgejo
...
bp-v7.0/fo
Author | SHA1 | Date | |
---|---|---|---|
|
71e63f541d | ||
|
c0fb79b436 | ||
|
6051722460 | ||
|
f3ce65a3fc | ||
|
96b2ec888a | ||
|
f3b6759ab7 | ||
|
2b8d95c8c2 | ||
|
470886bf52 | ||
|
d2cd1342bf | ||
|
e5212c8c96 | ||
|
24552ee9ee | ||
|
7b24b669ed | ||
|
482658a4d0 | ||
|
a89e146cb0 | ||
|
7befc34e68 | ||
|
69d9d66dda | ||
|
11feddc21d | ||
|
b4f566fdf5 | ||
|
5a23ce083d | ||
|
ba4f17b1d9 | ||
|
f25d2ce223 | ||
57b19874b8 | |||
|
46eeb884b4 | ||
|
0abf358c94 | ||
2937333e2d | |||
|
7b97ea7154 | ||
|
59e42fee1c | ||
e13854c305 | |||
|
71153ef8b4 | ||
|
7db4e374ca | ||
|
2e5aa42f20 | ||
|
7a783c3132 | ||
|
029bcd361a | ||
1a0c9df87f | |||
ce74e66b95 | |||
cf460b8b5f | |||
4706b644f8 | |||
b6dccc0fd4 | |||
966faddee4 | |||
|
c01935e9d0 | ||
|
22aedc6c96 | ||
|
4dd475dfe5 | ||
|
923035e418 | ||
|
9ecd041975 | ||
|
9f80081795 | ||
|
4cb3f331a2 | ||
|
fd8f51f2b6 | ||
|
e628d0e54b | ||
|
ece5c97931 | ||
|
b9dbd93ebc | ||
|
66d1cd89d1 | ||
|
45f39ce839 | ||
|
f0a2da40ff | ||
|
60a82b0890 | ||
|
6b560544fb | ||
|
4a4bd75989 | ||
84eeab59af | |||
7f03fdf9f9 | |||
|
6b2d02528f | ||
92acbb0a8e | |||
|
5503db386e | ||
005a9c7850 | |||
|
d0dccaec66 | ||
|
ea0e8caa6f | ||
|
9b4d32446c | ||
|
ce5f1b942f | ||
|
e428231b38 | ||
|
03df59ec95 | ||
|
a1bce73f5c | ||
19f7eb657b |
165 changed files with 3863 additions and 1375 deletions
|
@ -295,6 +295,7 @@ package "code.gitea.io/gitea/modules/translation"
|
||||||
func (MockLocale).TrString
|
func (MockLocale).TrString
|
||||||
func (MockLocale).Tr
|
func (MockLocale).Tr
|
||||||
func (MockLocale).TrN
|
func (MockLocale).TrN
|
||||||
|
func (MockLocale).TrSize
|
||||||
func (MockLocale).PrettyNumber
|
func (MockLocale).PrettyNumber
|
||||||
|
|
||||||
package "code.gitea.io/gitea/modules/util/filebuffer"
|
package "code.gitea.io/gitea/modules/util/filebuffer"
|
||||||
|
@ -341,5 +342,4 @@ package "code.gitea.io/gitea/services/repository/files"
|
||||||
|
|
||||||
package "code.gitea.io/gitea/services/webhook"
|
package "code.gitea.io/gitea/services/webhook"
|
||||||
func NewNotifier
|
func NewNotifier
|
||||||
func List
|
|
||||||
|
|
||||||
|
|
|
@ -33,22 +33,18 @@ jobs:
|
||||||
if: >
|
if: >
|
||||||
!startsWith(vars.ROLE, 'forgejo-') && (
|
!startsWith(vars.ROLE, 'forgejo-') && (
|
||||||
github.event.pull_request.merged
|
github.event.pull_request.merged
|
||||||
&& (
|
&&
|
||||||
(
|
|
||||||
github.event.action == 'closed' &&
|
|
||||||
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
contains(toJSON(github.event.pull_request.labels), 'backport/v')
|
||||||
)
|
)
|
||||||
||
|
|
||||||
(
|
|
||||||
github.event.action == 'labeled' &&
|
|
||||||
contains(github.event.label.name, 'backport/v')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: 'docker.io/node:20-bookworm'
|
image: 'docker.io/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
|
- name: event info
|
||||||
|
run: |
|
||||||
|
cat <<'EOF'
|
||||||
|
${{ toJSON(github) }}
|
||||||
|
EOF
|
||||||
- name: Fetch labels
|
- name: Fetch labels
|
||||||
id: fetch-labels
|
id: fetch-labels
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
@ -14,6 +14,11 @@ jobs:
|
||||||
container:
|
container:
|
||||||
image: 'docker.io/node:20-bookworm'
|
image: 'docker.io/node:20-bookworm'
|
||||||
steps:
|
steps:
|
||||||
|
- name: event info
|
||||||
|
run: |
|
||||||
|
cat <<'EOF'
|
||||||
|
${{ toJSON(github) }}
|
||||||
|
EOF
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: https://code.forgejo.org/actions/checkout@v3
|
||||||
- uses: https://code.forgejo.org/actions/setup-go@v4
|
- uses: https://code.forgejo.org/actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -88,8 +88,13 @@ STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
|
||||||
ifneq ($(STORED_VERSION),)
|
ifneq ($(STORED_VERSION),)
|
||||||
FORGEJO_VERSION ?= $(STORED_VERSION)
|
FORGEJO_VERSION ?= $(STORED_VERSION)
|
||||||
else
|
else
|
||||||
|
ifneq ($(GITEA_VERSION),)
|
||||||
|
FORGEJO_VERSION ?= $(GITEA_VERSION)
|
||||||
|
FORGEJO_VERSION_API ?= $(GITEA_VERSION)+${GITEA_COMPATIBILITY}
|
||||||
|
else
|
||||||
# drop the "g" prefix prepended by git describe to the commit hash
|
# drop the "g" prefix prepended by git describe to the commit hash
|
||||||
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
|
FORGEJO_VERSION ?= $(shell git describe --exclude '*-test' --tags --always | sed 's/^v//' | sed 's/\-g/-/')+${GITEA_COMPATIBILITY}
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
FORGEJO_VERSION_MAJOR=$(shell echo $(FORGEJO_VERSION) | sed -e 's/\..*//')
|
||||||
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
|
FORGEJO_VERSION_MINOR=$(shell echo $(FORGEJO_VERSION) | sed -E -e 's/^([0-9]+\.[0-9]+).*/\1/')
|
||||||
|
@ -106,7 +111,12 @@ show-version-minor:
|
||||||
RELEASE_VERSION ?= ${FORGEJO_VERSION}
|
RELEASE_VERSION ?= ${FORGEJO_VERSION}
|
||||||
VERSION ?= ${RELEASE_VERSION}
|
VERSION ?= ${RELEASE_VERSION}
|
||||||
|
|
||||||
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"
|
FORGEJO_VERSION_API ?= ${FORGEJO_VERSION}
|
||||||
|
|
||||||
|
show-version-api:
|
||||||
|
@echo ${FORGEJO_VERSION_API}
|
||||||
|
|
||||||
|
LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(FORGEJO_VERSION)" -X "main.Tags=$(TAGS)" -X "main.ForgejoVersion=$(FORGEJO_VERSION_API)"
|
||||||
|
|
||||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||||
|
|
||||||
|
|
|
@ -347,11 +347,10 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
var out io.Writer
|
var out io.Writer
|
||||||
var dWriter *delayWriter
|
|
||||||
out = &nilWriter{}
|
out = &nilWriter{}
|
||||||
if setting.Git.VerbosePush {
|
if setting.Git.VerbosePush {
|
||||||
if setting.Git.VerbosePushDelay > 0 {
|
if setting.Git.VerbosePushDelay > 0 {
|
||||||
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
|
||||||
defer dWriter.Close()
|
defer dWriter.Close()
|
||||||
out = dWriter
|
out = dWriter
|
||||||
} else {
|
} else {
|
||||||
|
@ -414,7 +413,6 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
hookOptions.RefFullNames = refFullNames
|
hookOptions.RefFullNames = refFullNames
|
||||||
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||||
if extra.HasError() {
|
if extra.HasError() {
|
||||||
_ = dWriter.Close()
|
|
||||||
hookPrintResults(results)
|
hookPrintResults(results)
|
||||||
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
||||||
}
|
}
|
||||||
|
@ -434,7 +432,6 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
fmt.Fprintf(out, "Processed %d references in total\n", total)
|
||||||
|
|
||||||
_ = dWriter.Close()
|
|
||||||
hookPrintResults(results)
|
hookPrintResults(results)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -447,7 +444,6 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
|
|
||||||
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
resp, extra := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
_ = dWriter.Close()
|
|
||||||
hookPrintResults(results)
|
hookPrintResults(results)
|
||||||
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "HookPostReceive failed: %v", extra.Error)
|
||||||
}
|
}
|
||||||
|
@ -463,9 +459,8 @@ Forgejo or set your environment appropriately.`, "")
|
||||||
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
return fail(ctx, extra.UserMsg, "SetDefaultBranch failed: %v", extra.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = dWriter.Close()
|
|
||||||
hookPrintResults(results)
|
|
||||||
|
|
||||||
|
hookPrintResults(results)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,20 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPktLine(t *testing.T) {
|
func TestPktLine(t *testing.T) {
|
||||||
|
@ -83,3 +93,72 @@ func TestPktLine(t *testing.T) {
|
||||||
assert.Empty(t, w.Bytes())
|
assert.Empty(t, w.Bytes())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDelayWriter(t *testing.T) {
|
||||||
|
// Setup the environment.
|
||||||
|
defer test.MockVariableValue(&setting.InternalToken, "Random")()
|
||||||
|
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||||
|
defer test.MockVariableValue(&setting.Git.VerbosePush, true)()
|
||||||
|
require.NoError(t, os.Setenv("SSH_ORIGINAL_COMMAND", "true"))
|
||||||
|
|
||||||
|
// Setup the Stdin.
|
||||||
|
f, err := os.OpenFile(t.TempDir()+"/stdin", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = f.Write([]byte("00000000000000000000 00000000000000000001 refs/head/main\n"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = f.Seek(0, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer test.MockVariableValue(os.Stdin, *f)()
|
||||||
|
|
||||||
|
// Setup the server that processes the hooks.
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(time.Millisecond * 600)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Commands = []*cli.Command{subcmdHookPreReceive}
|
||||||
|
|
||||||
|
// Capture what's being written into stdout
|
||||||
|
captureStdout := func(t *testing.T) (finish func() (output string)) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
require.NoError(t, err)
|
||||||
|
resetStdout := test.MockVariableValue(os.Stdout, *w)
|
||||||
|
|
||||||
|
return func() (output string) {
|
||||||
|
w.Close()
|
||||||
|
resetStdout()
|
||||||
|
|
||||||
|
out, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Should delay", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
|
||||||
|
finish := captureStdout(t)
|
||||||
|
|
||||||
|
err = app.Run([]string{"./forgejo", "pre-receive"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
out := finish()
|
||||||
|
|
||||||
|
require.Contains(t, out, "* Checking 1 references")
|
||||||
|
require.Contains(t, out, "Checked 1 references in total")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Shouldn't delay", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
|
||||||
|
finish := captureStdout(t)
|
||||||
|
|
||||||
|
err = app.Run([]string{"./forgejo", "pre-receive"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
out := finish()
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, out)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2338,6 +2338,8 @@ LEVEL = Info
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
||||||
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
||||||
|
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
|
||||||
|
;FILEPREVIEW_MAX_LINES = 50
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -31,7 +31,7 @@ require (
|
||||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1
|
github.com/editorconfig/editorconfig-core-go/v2 v2.6.1
|
||||||
github.com/emersion/go-imap v1.2.1
|
github.com/emersion/go-imap v1.2.1
|
||||||
github.com/emirpasic/gods v1.18.1
|
github.com/emirpasic/gods v1.18.1
|
||||||
github.com/felixge/fgprof v0.9.3
|
github.com/felixge/fgprof v0.9.4
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/gliderlabs/ssh v0.3.7
|
github.com/gliderlabs/ssh v0.3.7
|
||||||
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
|
||||||
|
@ -44,7 +44,7 @@ require (
|
||||||
github.com/go-git/go-billy/v5 v5.5.0
|
github.com/go-git/go-billy/v5 v5.5.0
|
||||||
github.com/go-git/go-git/v5 v5.11.0
|
github.com/go-git/go-git/v5 v5.11.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.6
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
github.com/go-sql-driver/mysql v1.8.0
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/go-swagger/go-swagger v0.30.5
|
github.com/go-swagger/go-swagger v0.30.5
|
||||||
github.com/go-testfixtures/testfixtures/v3 v3.10.0
|
github.com/go-testfixtures/testfixtures/v3 v3.10.0
|
||||||
github.com/go-webauthn/webauthn v0.10.0
|
github.com/go-webauthn/webauthn v0.10.0
|
||||||
|
@ -53,7 +53,7 @@ require (
|
||||||
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||||
github.com/google/go-github/v57 v57.0.0
|
github.com/google/go-github/v57 v57.0.0
|
||||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/feeds v1.1.2
|
github.com/gorilla/feeds v1.1.2
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.2
|
||||||
|
@ -74,7 +74,7 @@ require (
|
||||||
github.com/meilisearch/meilisearch-go v0.26.1
|
github.com/meilisearch/meilisearch-go v0.26.1
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
github.com/minio/minio-go/v7 v7.0.66
|
github.com/minio/minio-go/v7 v7.0.69
|
||||||
github.com/msteinert/pam v1.2.0
|
github.com/msteinert/pam v1.2.0
|
||||||
github.com/nektos/act v0.2.52
|
github.com/nektos/act v0.2.52
|
||||||
github.com/niklasfasching/go-org v1.7.0
|
github.com/niklasfasching/go-org v1.7.0
|
||||||
|
|
32
go.sum
32
go.sum
|
@ -186,9 +186,15 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
|
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
|
||||||
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
|
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
|
||||||
|
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||||
|
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||||
|
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
|
@ -256,8 +262,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88=
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
|
@ -336,8 +342,8 @@ github.com/go-openapi/validate v0.22.6 h1:+NhuwcEYpWdO5Nm4bmvhGLW0rt1Fcc532Mu3wp
|
||||||
github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM=
|
github.com/go-openapi/validate v0.22.6/go.mod h1:eaddXSqKeTg5XpSmj1dYyFTK/95n/XHwcOY+BMxKMyM=
|
||||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
|
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
|
||||||
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
|
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
|
||||||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
|
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
|
||||||
|
@ -353,6 +359,9 @@ github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
|
||||||
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
|
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
@ -441,9 +450,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
@ -494,7 +502,7 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||||
|
@ -567,6 +575,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||||
|
@ -614,8 +623,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
|
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||||
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
|
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
|
@ -670,6 +679,7 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||||
|
@ -1034,9 +1044,9 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
|
@ -5,18 +5,10 @@ package asymkey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
|
|
||||||
"github.com/keybase/go-crypto/openpgp/packet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// __________________ ________ ____ __.
|
// __________________ ________ ____ __.
|
||||||
|
@ -40,45 +32,22 @@ import (
|
||||||
|
|
||||||
// This file provides functions relating commit verification
|
// This file provides functions relating commit verification
|
||||||
|
|
||||||
// CommitVerification represents a commit validation of signature
|
|
||||||
type CommitVerification struct {
|
|
||||||
Verified bool
|
|
||||||
Warning bool
|
|
||||||
Reason string
|
|
||||||
SigningUser *user_model.User
|
|
||||||
CommittingUser *user_model.User
|
|
||||||
SigningEmail string
|
|
||||||
SigningKey *GPGKey
|
|
||||||
SigningSSHKey *PublicKey
|
|
||||||
TrustStatus string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignCommit represents a commit with validation of signature.
|
// SignCommit represents a commit with validation of signature.
|
||||||
type SignCommit struct {
|
type SignCommit struct {
|
||||||
Verification *CommitVerification
|
Verification *ObjectVerification
|
||||||
*user_model.UserCommit
|
*user_model.UserCommit
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// BadSignature is used as the reason when the signature has a KeyID that is in the db
|
|
||||||
// but no key that has that ID verifies the signature. This is a suspicious failure.
|
|
||||||
BadSignature = "gpg.error.probable_bad_signature"
|
|
||||||
// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
|
|
||||||
// default Key but is not verified by the default key. This is a suspicious failure.
|
|
||||||
BadDefaultSignature = "gpg.error.probable_bad_default_signature"
|
|
||||||
// NoKeyFound is used as the reason when no key can be found to verify the signature.
|
|
||||||
NoKeyFound = "gpg.error.no_gpg_keys_found"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
||||||
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
|
func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) []*SignCommit {
|
||||||
newCommits := make([]*SignCommit, 0, len(oldCommits))
|
newCommits := make([]*SignCommit, 0, len(oldCommits))
|
||||||
keyMap := map[string]bool{}
|
keyMap := map[string]bool{}
|
||||||
|
|
||||||
for _, c := range oldCommits {
|
for _, c := range oldCommits {
|
||||||
|
o := commitToGitObject(c.Commit)
|
||||||
signCommit := &SignCommit{
|
signCommit := &SignCommit{
|
||||||
UserCommit: c,
|
UserCommit: c,
|
||||||
Verification: ParseCommitWithSignature(ctx, c.Commit),
|
Verification: ParseObjectWithSignature(ctx, &o),
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
|
_ = CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap)
|
||||||
|
@ -88,456 +57,7 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use
|
||||||
return newCommits
|
return newCommits
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseCommitWithSignature check if signature is good against keystore.
|
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *ObjectVerification {
|
||||||
func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerification {
|
o := commitToGitObject(c)
|
||||||
var committer *user_model.User
|
return ParseObjectWithSignature(ctx, &o)
|
||||||
if c.Committer != nil {
|
|
||||||
var err error
|
|
||||||
// Find Committer account
|
|
||||||
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
|
|
||||||
if err != nil { // Skipping not user for committer
|
|
||||||
committer = &user_model.User{
|
|
||||||
Name: c.Committer.Name,
|
|
||||||
Email: c.Committer.Email,
|
|
||||||
}
|
|
||||||
// We can expect this to often be an ErrUserNotExist. in the case
|
|
||||||
// it is not, however, it is important to log it.
|
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
|
||||||
log.Error("GetUserByEmail: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.no_committer_account",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no signature just report the committer
|
|
||||||
if c.Signature == nil {
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false, // Default value
|
|
||||||
Reason: "gpg.error.not_signed_commit", // Default value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this a SSH signature handle it differently
|
|
||||||
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
|
|
||||||
return ParseCommitWithSSHSignature(ctx, c, committer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parsing signature
|
|
||||||
sig, err := extractSignature(c.Signature.Signature)
|
|
||||||
if err != nil { // Skipping failed to extract sign
|
|
||||||
log.Error("SignatureRead err: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.extract_sign",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyID := ""
|
|
||||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
|
||||||
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
|
|
||||||
}
|
|
||||||
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
|
||||||
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
|
||||||
}
|
|
||||||
defaultReason := NoKeyFound
|
|
||||||
|
|
||||||
// First check if the sig has a keyID and if so just look at that
|
|
||||||
if commitVerification := hashAndVerifyForKeyID(
|
|
||||||
ctx,
|
|
||||||
sig,
|
|
||||||
c.Signature.Payload,
|
|
||||||
committer,
|
|
||||||
keyID,
|
|
||||||
setting.AppName,
|
|
||||||
""); commitVerification != nil {
|
|
||||||
if commitVerification.Reason == BadSignature {
|
|
||||||
defaultReason = BadSignature
|
|
||||||
} else {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try to associate the signature with the committer, if present
|
|
||||||
if committer.ID != 0 {
|
|
||||||
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
|
||||||
OwnerID: committer.ID,
|
|
||||||
})
|
|
||||||
if err != nil { // Skipping failed to get gpg keys of user
|
|
||||||
log.Error("ListGPGKeys: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
|
|
||||||
log.Error("LoadSubKeys: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
|
|
||||||
activated := false
|
|
||||||
for _, e := range committerEmailAddresses {
|
|
||||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
|
||||||
activated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, k := range keys {
|
|
||||||
// Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
|
|
||||||
canValidate := false
|
|
||||||
email := ""
|
|
||||||
if k.Verified && activated {
|
|
||||||
canValidate = true
|
|
||||||
email = c.Committer.Email
|
|
||||||
}
|
|
||||||
if !canValidate {
|
|
||||||
for _, e := range k.Emails {
|
|
||||||
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
|
||||||
canValidate = true
|
|
||||||
email = e.Email
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !canValidate {
|
|
||||||
continue // Skip this key
|
|
||||||
}
|
|
||||||
|
|
||||||
commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, c.Signature.Payload, k, committer, committer, email)
|
|
||||||
if commitVerification != nil {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
|
||||||
// OK we should try the default key
|
|
||||||
gpgSettings := git.GPGSettings{
|
|
||||||
Sign: true,
|
|
||||||
KeyID: setting.Repository.Signing.SigningKey,
|
|
||||||
Name: setting.Repository.Signing.SigningName,
|
|
||||||
Email: setting.Repository.Signing.SigningEmail,
|
|
||||||
}
|
|
||||||
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
|
|
||||||
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
|
|
||||||
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
|
||||||
if commitVerification.Reason == BadSignature {
|
|
||||||
defaultReason = BadSignature
|
|
||||||
} else {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultGPGSettings, err := c.GetRepositoryDefaultPublicGPGKey(false)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error getting default public gpg key: %v", err)
|
|
||||||
} else if defaultGPGSettings == nil {
|
|
||||||
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.ID.String())
|
|
||||||
} else if defaultGPGSettings.Sign {
|
|
||||||
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
|
||||||
if commitVerification.Reason == BadSignature {
|
|
||||||
defaultReason = BadSignature
|
|
||||||
} else {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CommitVerification{ // Default at this stage
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Warning: defaultReason != NoKeyFound,
|
|
||||||
Reason: defaultReason,
|
|
||||||
SigningKey: &GPGKey{
|
|
||||||
KeyID: keyID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *CommitVerification {
|
|
||||||
// First try to find the key in the db
|
|
||||||
if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise we have to parse the key
|
|
||||||
ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to get default signing key: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.generate_hash",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ekey := range ekeys {
|
|
||||||
pubkey := ekey.PrimaryKey
|
|
||||||
content, err := base64EncPubKey(pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.generate_hash",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
k := &GPGKey{
|
|
||||||
Content: content,
|
|
||||||
CanSign: pubkey.CanSign(),
|
|
||||||
KeyID: pubkey.KeyIdString(),
|
|
||||||
}
|
|
||||||
for _, subKey := range ekey.Subkeys {
|
|
||||||
content, err := base64EncPubKey(subKey.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.generate_hash",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
k.SubsKey = append(k.SubsKey, &GPGKey{
|
|
||||||
Content: content,
|
|
||||||
CanSign: subKey.PublicKey.CanSign(),
|
|
||||||
KeyID: subKey.PublicKey.KeyIdString(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, k, committer, &user_model.User{
|
|
||||||
Name: gpgSettings.Name,
|
|
||||||
Email: gpgSettings.Email,
|
|
||||||
}, gpgSettings.Email); commitVerification != nil {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
if keyID == k.KeyID {
|
|
||||||
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Warning: true,
|
|
||||||
Reason: BadSignature,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
|
|
||||||
// Check if key can sign
|
|
||||||
if !k.CanSign {
|
|
||||||
return fmt.Errorf("key can not sign")
|
|
||||||
}
|
|
||||||
// Decode key
|
|
||||||
pkey, err := base64DecPubKey(k.Content)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pkey.VerifySignature(h, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
|
||||||
// Generating hash of commit
|
|
||||||
hash, err := populateHash(sig.Hash, []byte(payload))
|
|
||||||
if err != nil { // Skipping as failed to generate hash
|
|
||||||
log.Error("PopulateHash: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// We will ignore errors in verification as they don't need to be propagated up
|
|
||||||
err = verifySign(sig, hash, k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
|
||||||
verified, err := hashAndVerify(sig, payload, k)
|
|
||||||
if err != nil || verified != nil {
|
|
||||||
return verified, err
|
|
||||||
}
|
|
||||||
for _, sk := range k.SubsKey {
|
|
||||||
verified, err := hashAndVerify(sig, payload, sk)
|
|
||||||
if err != nil || verified != nil {
|
|
||||||
return verified, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashAndVerifyWithSubKeysCommitVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *CommitVerification {
|
|
||||||
key, err := hashAndVerifyWithSubKeys(sig, payload, k)
|
|
||||||
if err != nil { // Skipping failed to generate hash
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.generate_hash",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if key != nil {
|
|
||||||
return &CommitVerification{ // Everything is ok
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: true,
|
|
||||||
Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
|
|
||||||
SigningUser: signer,
|
|
||||||
SigningKey: key,
|
|
||||||
SigningEmail: email,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *CommitVerification {
|
|
||||||
if keyID == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
|
||||||
KeyID: keyID,
|
|
||||||
IncludeSubKeys: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, key := range keys {
|
|
||||||
var primaryKeys []*GPGKey
|
|
||||||
if key.PrimaryKeyID != "" {
|
|
||||||
primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
|
||||||
KeyID: key.PrimaryKeyID,
|
|
||||||
IncludeSubKeys: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetGPGKeysByKeyID: %v", err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
|
|
||||||
if !activated {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
signer := &user_model.User{
|
|
||||||
Name: name,
|
|
||||||
Email: email,
|
|
||||||
}
|
|
||||||
if key.OwnerID != 0 {
|
|
||||||
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
|
|
||||||
if err == nil {
|
|
||||||
signer = owner
|
|
||||||
} else if !user_model.IsErrUserNotExist(err) {
|
|
||||||
log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Reason: "gpg.error.no_committer_account",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
commitVerification := hashAndVerifyWithSubKeysCommitVerification(sig, payload, key, committer, signer, email)
|
|
||||||
if commitVerification != nil {
|
|
||||||
return commitVerification
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
|
|
||||||
return &CommitVerification{
|
|
||||||
CommittingUser: committer,
|
|
||||||
Verified: false,
|
|
||||||
Warning: true,
|
|
||||||
Reason: BadSignature,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
|
|
||||||
// There are several trust models in Gitea
|
|
||||||
func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
|
|
||||||
if !verification.Verified {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the Committer trust model a signature is trusted if it matches the committer
|
|
||||||
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
|
|
||||||
// NB: This model is commit verification only
|
|
||||||
if repoTrustModel == repo_model.CommitterTrustModel {
|
|
||||||
// default to "unmatched"
|
|
||||||
verification.TrustStatus = "unmatched"
|
|
||||||
|
|
||||||
// We can only verify against users in our database but the default key will match
|
|
||||||
// against by email if it is not in the db.
|
|
||||||
if (verification.SigningUser.ID != 0 &&
|
|
||||||
verification.CommittingUser.ID == verification.SigningUser.ID) ||
|
|
||||||
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
|
|
||||||
verification.SigningUser.Email == verification.CommittingUser.Email) {
|
|
||||||
verification.TrustStatus = "trusted"
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we drop to the more nuanced trust models...
|
|
||||||
verification.TrustStatus = "trusted"
|
|
||||||
|
|
||||||
if verification.SigningUser.ID == 0 {
|
|
||||||
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
|
|
||||||
|
|
||||||
// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
|
|
||||||
// unless the default key matches the email of a non-user.
|
|
||||||
if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
|
|
||||||
verification.SigningUser.Email != verification.CommittingUser.Email) {
|
|
||||||
verification.TrustStatus = "untrusted"
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check we actually have a GPG SigningKey
|
|
||||||
var err error
|
|
||||||
if verification.SigningKey != nil {
|
|
||||||
var isMember bool
|
|
||||||
if keyMap != nil {
|
|
||||||
var has bool
|
|
||||||
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
|
|
||||||
if !has {
|
|
||||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
|
||||||
(*keyMap)[verification.SigningKey.KeyID] = isMember
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isMember {
|
|
||||||
verification.TrustStatus = "untrusted"
|
|
||||||
if verification.CommittingUser.ID != verification.SigningUser.ID {
|
|
||||||
// The committing user and the signing user are not the same
|
|
||||||
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
|
|
||||||
verification.TrustStatus = "unmatched"
|
|
||||||
}
|
|
||||||
} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
|
|
||||||
// The committing user and the signing user are not the same and our trustmodel states that they must match
|
|
||||||
verification.TrustStatus = "unmatched"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
527
models/asymkey/gpg_key_object_verification.go
Normal file
527
models/asymkey/gpg_key_object_verification.go
Normal file
|
@ -0,0 +1,527 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package asymkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/keybase/go-crypto/openpgp/packet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file provides functions related to object (commit, tag) verification
|
||||||
|
|
||||||
|
// ObjectVerification represents a commit validation of signature
|
||||||
|
type ObjectVerification struct {
|
||||||
|
Verified bool
|
||||||
|
Warning bool
|
||||||
|
Reason string
|
||||||
|
SigningUser *user_model.User
|
||||||
|
CommittingUser *user_model.User
|
||||||
|
SigningEmail string
|
||||||
|
SigningKey *GPGKey
|
||||||
|
SigningSSHKey *PublicKey
|
||||||
|
TrustStatus string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BadSignature is used as the reason when the signature has a KeyID that is in the db
|
||||||
|
// but no key that has that ID verifies the signature. This is a suspicious failure.
|
||||||
|
BadSignature = "gpg.error.probable_bad_signature"
|
||||||
|
// BadDefaultSignature is used as the reason when the signature has a KeyID that matches the
|
||||||
|
// default Key but is not verified by the default key. This is a suspicious failure.
|
||||||
|
BadDefaultSignature = "gpg.error.probable_bad_default_signature"
|
||||||
|
// NoKeyFound is used as the reason when no key can be found to verify the signature.
|
||||||
|
NoKeyFound = "gpg.error.no_gpg_keys_found"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GitObject struct {
|
||||||
|
ID git.ObjectID
|
||||||
|
Committer *git.Signature
|
||||||
|
Signature *git.ObjectSignature
|
||||||
|
Commit *git.Commit
|
||||||
|
}
|
||||||
|
|
||||||
|
func commitToGitObject(c *git.Commit) GitObject {
|
||||||
|
return GitObject{
|
||||||
|
ID: c.ID,
|
||||||
|
Committer: c.Committer,
|
||||||
|
Signature: c.Signature,
|
||||||
|
Commit: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagToGitObject(t *git.Tag, gitRepo *git.Repository) GitObject {
|
||||||
|
commit, _ := t.Commit(gitRepo)
|
||||||
|
return GitObject{
|
||||||
|
ID: t.ID,
|
||||||
|
Committer: t.Tagger,
|
||||||
|
Signature: t.Signature,
|
||||||
|
Commit: commit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseObjectWithSignature check if signature is good against keystore.
|
||||||
|
func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerification {
|
||||||
|
var committer *user_model.User
|
||||||
|
if c.Committer != nil {
|
||||||
|
var err error
|
||||||
|
// Find Committer account
|
||||||
|
committer, err = user_model.GetUserByEmail(ctx, c.Committer.Email) // This finds the user by primary email or activated email so commit will not be valid if email is not
|
||||||
|
if err != nil { // Skipping not user for committer
|
||||||
|
committer = &user_model.User{
|
||||||
|
Name: c.Committer.Name,
|
||||||
|
Email: c.Committer.Email,
|
||||||
|
}
|
||||||
|
// We can expect this to often be an ErrUserNotExist. in the case
|
||||||
|
// it is not, however, it is important to log it.
|
||||||
|
if !user_model.IsErrUserNotExist(err) {
|
||||||
|
log.Error("GetUserByEmail: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.no_committer_account",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no signature just report the committer
|
||||||
|
if c.Signature == nil {
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false, // Default value
|
||||||
|
Reason: "gpg.error.not_signed_commit", // Default value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this a SSH signature handle it differently
|
||||||
|
if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") {
|
||||||
|
return ParseObjectWithSSHSignature(ctx, c, committer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing signature
|
||||||
|
sig, err := extractSignature(c.Signature.Signature)
|
||||||
|
if err != nil { // Skipping failed to extract sign
|
||||||
|
log.Error("SignatureRead err: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.extract_sign",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyID := ""
|
||||||
|
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||||
|
keyID = fmt.Sprintf("%X", *sig.IssuerKeyId)
|
||||||
|
}
|
||||||
|
if keyID == "" && sig.IssuerFingerprint != nil && len(sig.IssuerFingerprint) > 0 {
|
||||||
|
keyID = fmt.Sprintf("%X", sig.IssuerFingerprint[12:20])
|
||||||
|
}
|
||||||
|
defaultReason := NoKeyFound
|
||||||
|
|
||||||
|
// First check if the sig has a keyID and if so just look at that
|
||||||
|
if commitVerification := hashAndVerifyForKeyID(
|
||||||
|
ctx,
|
||||||
|
sig,
|
||||||
|
c.Signature.Payload,
|
||||||
|
committer,
|
||||||
|
keyID,
|
||||||
|
setting.AppName,
|
||||||
|
""); commitVerification != nil {
|
||||||
|
if commitVerification.Reason == BadSignature {
|
||||||
|
defaultReason = BadSignature
|
||||||
|
} else {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try to associate the signature with the committer, if present
|
||||||
|
if committer.ID != 0 {
|
||||||
|
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||||
|
OwnerID: committer.ID,
|
||||||
|
})
|
||||||
|
if err != nil { // Skipping failed to get gpg keys of user
|
||||||
|
log.Error("ListGPGKeys: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
|
||||||
|
log.Error("LoadSubKeys: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID)
|
||||||
|
activated := false
|
||||||
|
for _, e := range committerEmailAddresses {
|
||||||
|
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
||||||
|
activated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
// Pre-check (& optimization) that emails attached to key can be attached to the committer email and can validate
|
||||||
|
canValidate := false
|
||||||
|
email := ""
|
||||||
|
if k.Verified && activated {
|
||||||
|
canValidate = true
|
||||||
|
email = c.Committer.Email
|
||||||
|
}
|
||||||
|
if !canValidate {
|
||||||
|
for _, e := range k.Emails {
|
||||||
|
if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) {
|
||||||
|
canValidate = true
|
||||||
|
email = e.Email
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !canValidate {
|
||||||
|
continue // Skip this key
|
||||||
|
}
|
||||||
|
|
||||||
|
commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, c.Signature.Payload, k, committer, committer, email)
|
||||||
|
if commitVerification != nil {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.Repository.Signing.SigningKey != "" && setting.Repository.Signing.SigningKey != "default" && setting.Repository.Signing.SigningKey != "none" {
|
||||||
|
// OK we should try the default key
|
||||||
|
gpgSettings := git.GPGSettings{
|
||||||
|
Sign: true,
|
||||||
|
KeyID: setting.Repository.Signing.SigningKey,
|
||||||
|
Name: setting.Repository.Signing.SigningName,
|
||||||
|
Email: setting.Repository.Signing.SigningEmail,
|
||||||
|
}
|
||||||
|
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
|
||||||
|
log.Error("Error getting default signing key: %s %v", gpgSettings.KeyID, err)
|
||||||
|
} else if commitVerification := verifyWithGPGSettings(ctx, &gpgSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
||||||
|
if commitVerification.Reason == BadSignature {
|
||||||
|
defaultReason = BadSignature
|
||||||
|
} else {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultGPGSettings, err := c.Commit.GetRepositoryDefaultPublicGPGKey(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting default public gpg key: %v", err)
|
||||||
|
} else if defaultGPGSettings == nil {
|
||||||
|
log.Warn("Unable to get defaultGPGSettings for unattached commit: %s", c.Commit.ID.String())
|
||||||
|
} else if defaultGPGSettings.Sign {
|
||||||
|
if commitVerification := verifyWithGPGSettings(ctx, defaultGPGSettings, sig, c.Signature.Payload, committer, keyID); commitVerification != nil {
|
||||||
|
if commitVerification.Reason == BadSignature {
|
||||||
|
defaultReason = BadSignature
|
||||||
|
} else {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ObjectVerification{ // Default at this stage
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Warning: defaultReason != NoKeyFound,
|
||||||
|
Reason: defaultReason,
|
||||||
|
SigningKey: &GPGKey{
|
||||||
|
KeyID: keyID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, sig *packet.Signature, payload string, committer *user_model.User, keyID string) *ObjectVerification {
|
||||||
|
// First try to find the key in the db
|
||||||
|
if commitVerification := hashAndVerifyForKeyID(ctx, sig, payload, committer, gpgSettings.KeyID, gpgSettings.Name, gpgSettings.Email); commitVerification != nil {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise we have to parse the key
|
||||||
|
ekeys, err := checkArmoredGPGKeyString(gpgSettings.PublicKeyContent)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to get default signing key: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.generate_hash",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ekey := range ekeys {
|
||||||
|
pubkey := ekey.PrimaryKey
|
||||||
|
content, err := base64EncPubKey(pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.generate_hash",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k := &GPGKey{
|
||||||
|
Content: content,
|
||||||
|
CanSign: pubkey.CanSign(),
|
||||||
|
KeyID: pubkey.KeyIdString(),
|
||||||
|
}
|
||||||
|
for _, subKey := range ekey.Subkeys {
|
||||||
|
content, err := base64EncPubKey(subKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.generate_hash",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k.SubsKey = append(k.SubsKey, &GPGKey{
|
||||||
|
Content: content,
|
||||||
|
CanSign: subKey.PublicKey.CanSign(),
|
||||||
|
KeyID: subKey.PublicKey.KeyIdString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, k, committer, &user_model.User{
|
||||||
|
Name: gpgSettings.Name,
|
||||||
|
Email: gpgSettings.Email,
|
||||||
|
}, gpgSettings.Email); commitVerification != nil {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
if keyID == k.KeyID {
|
||||||
|
// This is a bad situation ... We have a key id that matches our default key but the signature doesn't match.
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Warning: true,
|
||||||
|
Reason: BadSignature,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
|
||||||
|
// Check if key can sign
|
||||||
|
if !k.CanSign {
|
||||||
|
return fmt.Errorf("key can not sign")
|
||||||
|
}
|
||||||
|
// Decode key
|
||||||
|
pkey, err := base64DecPubKey(k.Content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pkey.VerifySignature(h, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
||||||
|
// Generating hash of commit
|
||||||
|
hash, err := populateHash(sig.Hash, []byte(payload))
|
||||||
|
if err != nil { // Skipping as failed to generate hash
|
||||||
|
log.Error("PopulateHash: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We will ignore errors in verification as they don't need to be propagated up
|
||||||
|
err = verifySign(sig, hash, k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey) (*GPGKey, error) {
|
||||||
|
verified, err := hashAndVerify(sig, payload, k)
|
||||||
|
if err != nil || verified != nil {
|
||||||
|
return verified, err
|
||||||
|
}
|
||||||
|
for _, sk := range k.SubsKey {
|
||||||
|
verified, err := hashAndVerify(sig, payload, sk)
|
||||||
|
if err != nil || verified != nil {
|
||||||
|
return verified, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAndVerifyWithSubKeysObjectVerification(sig *packet.Signature, payload string, k *GPGKey, committer, signer *user_model.User, email string) *ObjectVerification {
|
||||||
|
key, err := hashAndVerifyWithSubKeys(sig, payload, k)
|
||||||
|
if err != nil { // Skipping failed to generate hash
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.generate_hash",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key != nil {
|
||||||
|
return &ObjectVerification{ // Everything is ok
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: true,
|
||||||
|
Reason: fmt.Sprintf("%s / %s", signer.Name, key.KeyID),
|
||||||
|
SigningUser: signer,
|
||||||
|
SigningKey: key,
|
||||||
|
SigningEmail: email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload string, committer *user_model.User, keyID, name, email string) *ObjectVerification {
|
||||||
|
if keyID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||||
|
KeyID: keyID,
|
||||||
|
IncludeSubKeys: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
var primaryKeys []*GPGKey
|
||||||
|
if key.PrimaryKeyID != "" {
|
||||||
|
primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{
|
||||||
|
KeyID: key.PrimaryKeyID,
|
||||||
|
IncludeSubKeys: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetGPGKeysByKeyID: %v", err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activated, email := checkKeyEmails(ctx, email, append([]*GPGKey{key}, primaryKeys...)...)
|
||||||
|
if !activated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := &user_model.User{
|
||||||
|
Name: name,
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
if key.OwnerID != 0 {
|
||||||
|
owner, err := user_model.GetUserByID(ctx, key.OwnerID)
|
||||||
|
if err == nil {
|
||||||
|
signer = owner
|
||||||
|
} else if !user_model.IsErrUserNotExist(err) {
|
||||||
|
log.Error("Failed to user_model.GetUserByID: %d for key ID: %d (%s) %v", key.OwnerID, key.ID, key.KeyID, err)
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Reason: "gpg.error.no_committer_account",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commitVerification := hashAndVerifyWithSubKeysObjectVerification(sig, payload, key, committer, signer, email)
|
||||||
|
if commitVerification != nil {
|
||||||
|
return commitVerification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is a bad situation ... We have a key id that is in our database but the signature doesn't match.
|
||||||
|
return &ObjectVerification{
|
||||||
|
CommittingUser: committer,
|
||||||
|
Verified: false,
|
||||||
|
Warning: true,
|
||||||
|
Reason: BadSignature,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
|
||||||
|
// There are several trust models in Gitea
|
||||||
|
func CalculateTrustStatus(verification *ObjectVerification, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error), keyMap *map[string]bool) error {
|
||||||
|
if !verification.Verified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the Committer trust model a signature is trusted if it matches the committer
|
||||||
|
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
|
||||||
|
// NB: This model is commit verification only
|
||||||
|
if repoTrustModel == repo_model.CommitterTrustModel {
|
||||||
|
// default to "unmatched"
|
||||||
|
verification.TrustStatus = "unmatched"
|
||||||
|
|
||||||
|
// We can only verify against users in our database but the default key will match
|
||||||
|
// against by email if it is not in the db.
|
||||||
|
if (verification.SigningUser.ID != 0 &&
|
||||||
|
verification.CommittingUser.ID == verification.SigningUser.ID) ||
|
||||||
|
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
|
||||||
|
verification.SigningUser.Email == verification.CommittingUser.Email) {
|
||||||
|
verification.TrustStatus = "trusted"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we drop to the more nuanced trust models...
|
||||||
|
verification.TrustStatus = "trusted"
|
||||||
|
|
||||||
|
if verification.SigningUser.ID == 0 {
|
||||||
|
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
|
||||||
|
|
||||||
|
// However in the repo_model.CollaboratorCommitterTrustModel we cannot mark this as trusted
|
||||||
|
// unless the default key matches the email of a non-user.
|
||||||
|
if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
|
||||||
|
verification.SigningUser.Email != verification.CommittingUser.Email) {
|
||||||
|
verification.TrustStatus = "untrusted"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we actually have a GPG SigningKey
|
||||||
|
var err error
|
||||||
|
if verification.SigningKey != nil {
|
||||||
|
var isMember bool
|
||||||
|
if keyMap != nil {
|
||||||
|
var has bool
|
||||||
|
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
|
||||||
|
if !has {
|
||||||
|
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||||
|
(*keyMap)[verification.SigningKey.KeyID] = isMember
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isMember, err = isOwnerMemberCollaborator(verification.SigningUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isMember {
|
||||||
|
verification.TrustStatus = "untrusted"
|
||||||
|
if verification.CommittingUser.ID != verification.SigningUser.ID {
|
||||||
|
// The committing user and the signing user are not the same
|
||||||
|
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
|
||||||
|
verification.TrustStatus = "unmatched"
|
||||||
|
}
|
||||||
|
} else if repoTrustModel == repo_model.CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
|
||||||
|
// The committing user and the signing user are not the same and our trustmodel states that they must match
|
||||||
|
verification.TrustStatus = "unmatched"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
15
models/asymkey/gpg_key_tag_verification.go
Normal file
15
models/asymkey/gpg_key_tag_verification.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package asymkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseTagWithSignature(ctx context.Context, gitRepo *git.Repository, t *git.Tag) *ObjectVerification {
|
||||||
|
o := tagToGitObject(t, gitRepo)
|
||||||
|
return ParseObjectWithSignature(ctx, &o)
|
||||||
|
}
|
|
@ -11,14 +11,13 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/42wim/sshsig"
|
"github.com/42wim/sshsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseCommitWithSSHSignature check if signature is good against keystore.
|
// ParseObjectWithSSHSignature check if signature is good against keystore.
|
||||||
func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification {
|
func ParseObjectWithSSHSignature(ctx context.Context, c *GitObject, committer *user_model.User) *ObjectVerification {
|
||||||
// Now try to associate the signature with the committer, if present
|
// Now try to associate the signature with the committer, if present
|
||||||
if committer.ID != 0 {
|
if committer.ID != 0 {
|
||||||
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{
|
keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{
|
||||||
|
@ -27,7 +26,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
||||||
})
|
})
|
||||||
if err != nil { // Skipping failed to get ssh keys of user
|
if err != nil { // Skipping failed to get ssh keys of user
|
||||||
log.Error("ListPublicKeys: %v", err)
|
log.Error("ListPublicKeys: %v", err)
|
||||||
return &CommitVerification{
|
return &ObjectVerification{
|
||||||
CommittingUser: committer,
|
CommittingUser: committer,
|
||||||
Verified: false,
|
Verified: false,
|
||||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||||
|
@ -55,7 +54,7 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
||||||
|
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if k.Verified && activated {
|
if k.Verified && activated {
|
||||||
commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
|
commitVerification := verifySSHObjectVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email)
|
||||||
if commitVerification != nil {
|
if commitVerification != nil {
|
||||||
return commitVerification
|
return commitVerification
|
||||||
}
|
}
|
||||||
|
@ -63,19 +62,19 @@ func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CommitVerification{
|
return &ObjectVerification{
|
||||||
CommittingUser: committer,
|
CommittingUser: committer,
|
||||||
Verified: false,
|
Verified: false,
|
||||||
Reason: NoKeyFound,
|
Reason: NoKeyFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification {
|
func verifySSHObjectVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *ObjectVerification {
|
||||||
if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil {
|
if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CommitVerification{ // Everything is ok
|
return &ObjectVerification{ // Everything is ok
|
||||||
CommittingUser: committer,
|
CommittingUser: committer,
|
||||||
Verified: true,
|
Verified: true,
|
||||||
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint),
|
Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint),
|
|
@ -22,7 +22,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
|
||||||
sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
|
sshKey := unittest.AssertExistsAndLoadBean(t, &PublicKey{ID: 1000, OwnerID: 2})
|
||||||
|
|
||||||
t.Run("No commiter", func(t *testing.T) {
|
t.Run("No commiter", func(t *testing.T) {
|
||||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{}, &user_model.User{})
|
o := commitToGitObject(&git.Commit{})
|
||||||
|
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, &user_model.User{})
|
||||||
assert.False(t, commitVerification.Verified)
|
assert.False(t, commitVerification.Verified)
|
||||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||||
})
|
})
|
||||||
|
@ -30,7 +31,8 @@ func TestParseCommitWithSSHSignature(t *testing.T) {
|
||||||
t.Run("Commiter without keys", func(t *testing.T) {
|
t.Run("Commiter without keys", func(t *testing.T) {
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, &git.Commit{Committer: &git.Signature{Email: user.Email}}, user)
|
o := commitToGitObject(&git.Commit{Committer: &git.Signature{Email: user.Email}})
|
||||||
|
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user)
|
||||||
assert.False(t, commitVerification.Verified)
|
assert.False(t, commitVerification.Verified)
|
||||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||||
})
|
})
|
||||||
|
@ -57,7 +59,8 @@ AAAAQIMufOuSjZeDUujrkVK4sl7ICa0WwEftas8UAYxx0Thdkiw2qWjR1U1PKfTLm16/w8
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
o := commitToGitObject(gitCommit)
|
||||||
|
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||||
assert.False(t, commitVerification.Verified)
|
assert.False(t, commitVerification.Verified)
|
||||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||||
})
|
})
|
||||||
|
@ -79,7 +82,8 @@ Add content
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
o := commitToGitObject(gitCommit)
|
||||||
|
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||||
assert.False(t, commitVerification.Verified)
|
assert.False(t, commitVerification.Verified)
|
||||||
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
assert.Equal(t, NoKeyFound, commitVerification.Reason)
|
||||||
})
|
})
|
||||||
|
@ -107,7 +111,8 @@ fs9cMpZVM9BfIKNUSO8QY=
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
o := commitToGitObject(gitCommit)
|
||||||
|
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||||
assert.True(t, commitVerification.Verified)
|
assert.True(t, commitVerification.Verified)
|
||||||
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
||||||
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
||||||
|
@ -138,7 +143,8 @@ muPLbvEduU+Ze/1Ol1pgk=
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
commitVerification := ParseCommitWithSSHSignature(db.DefaultContext, gitCommit, user2)
|
o := commitToGitObject(gitCommit)
|
||||||
|
commitVerification := ParseObjectWithSSHSignature(db.DefaultContext, &o, user2)
|
||||||
assert.True(t, commitVerification.Verified)
|
assert.True(t, commitVerification.Verified)
|
||||||
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
assert.Equal(t, "user2 / SHA256:TKfwbZMR7e9OnlV2l1prfah1TXH8CmqR0PvFEXVCXA4", commitVerification.Reason)
|
||||||
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
assert.Equal(t, sshKey, commitVerification.SigningSSHKey)
|
|
@ -155,8 +155,14 @@ func InitEngine(ctx context.Context) error {
|
||||||
Logger: log.GetLogger("xorm"),
|
Logger: log.GetLogger("xorm"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorLogger := log.GetLogger("xorm")
|
||||||
|
if setting.IsInTesting {
|
||||||
|
errorLogger = log.GetLogger(log.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
xormEngine.AddHook(&ErrorQueryHook{
|
xormEngine.AddHook(&ErrorQueryHook{
|
||||||
Logger: log.GetLogger("xorm"),
|
Logger: errorLogger,
|
||||||
})
|
})
|
||||||
|
|
||||||
SetDefaultEngine(ctx, xormEngine)
|
SetDefaultEngine(ctx, xormEngine)
|
||||||
|
|
|
@ -54,6 +54,8 @@ var migrations = []*Migration{
|
||||||
NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
|
NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting),
|
||||||
// v7 -> v8
|
// v7 -> v8
|
||||||
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes),
|
NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes),
|
||||||
|
// v8 -> v9
|
||||||
|
NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||||
|
|
15
models/forgejo_migrations/v1_22/v9.go
Normal file
15
models/forgejo_migrations/v1_22/v9.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_22 //nolint
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
func AddApplyToAdminsSetting(x *xorm.Engine) error {
|
||||||
|
type ProtectedBranch struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Sync(&ProtectedBranch{})
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ type ProtectedBranch struct {
|
||||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
ProtectedFilePatterns string `xorm:"TEXT"`
|
ProtectedFilePatterns string `xorm:"TEXT"`
|
||||||
UnprotectedFilePatterns string `xorm:"TEXT"`
|
UnprotectedFilePatterns string `xorm:"TEXT"`
|
||||||
|
ApplyToAdmins bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
@ -24,6 +23,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
|
@ -234,6 +234,7 @@ type SizeDetail struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SizeDetails forms a struct with various size details about repository
|
// SizeDetails forms a struct with various size details about repository
|
||||||
|
// Note: SizeDetailsString below expects it to have 2 entries
|
||||||
func (repo *Repository) SizeDetails() []SizeDetail {
|
func (repo *Repository) SizeDetails() []SizeDetail {
|
||||||
sizeDetails := []SizeDetail{
|
sizeDetails := []SizeDetail{
|
||||||
{
|
{
|
||||||
|
@ -249,13 +250,9 @@ func (repo *Repository) SizeDetails() []SizeDetail {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SizeDetailsString returns a concatenation of all repository size details as a string
|
// SizeDetailsString returns a concatenation of all repository size details as a string
|
||||||
func (repo *Repository) SizeDetailsString() string {
|
func (repo *Repository) SizeDetailsString(locale translation.Locale) string {
|
||||||
var str strings.Builder
|
|
||||||
sizeDetails := repo.SizeDetails()
|
sizeDetails := repo.SizeDetails()
|
||||||
for _, detail := range sizeDetails {
|
return locale.TrString("repo.size_format", sizeDetails[0].Name, locale.TrSize(sizeDetails[0].Size), sizeDetails[1].Name, locale.TrSize(sizeDetails[1].Size))
|
||||||
str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size)))
|
|
||||||
}
|
|
||||||
return strings.TrimSuffix(str.String(), ", ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *Repository) LogString() string {
|
func (repo *Repository) LogString() string {
|
||||||
|
|
|
@ -4,18 +4,76 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
)
|
)
|
||||||
|
|
||||||
var LinguistAttributes = []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language", "linguist-documentation", "linguist-detectable"}
|
var LinguistAttributes = []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language", "linguist-documentation", "linguist-detectable"}
|
||||||
|
|
||||||
|
// newCheckAttrStdoutReader parses the nul-byte separated output of git check-attr on each call of
|
||||||
|
// the returned function. The first reading error will stop the reading and be returned on all
|
||||||
|
// subsequent calls.
|
||||||
|
func newCheckAttrStdoutReader(r io.Reader, count int) func() (map[string]GitAttribute, error) {
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
|
||||||
|
// adapted from bufio.ScanLines to split on nul-byte \x00
|
||||||
|
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
if i := bytes.IndexByte(data, '\x00'); i >= 0 {
|
||||||
|
// We have a full nul-terminated line.
|
||||||
|
return i + 1, data[0:i], nil
|
||||||
|
}
|
||||||
|
// If we're at EOF, we have a final, non-terminated line. Return it.
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
// Request more data.
|
||||||
|
return 0, nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
var err error
|
||||||
|
nextText := func() string {
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if !scanner.Scan() {
|
||||||
|
err = scanner.Err()
|
||||||
|
if err == nil {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return scanner.Text()
|
||||||
|
}
|
||||||
|
nextAttribute := func() (string, GitAttribute, error) {
|
||||||
|
nextText() // discard filename
|
||||||
|
key := nextText()
|
||||||
|
value := GitAttribute(nextText())
|
||||||
|
return key, value, err
|
||||||
|
}
|
||||||
|
return func() (map[string]GitAttribute, error) {
|
||||||
|
values := make(map[string]GitAttribute, count)
|
||||||
|
for range count {
|
||||||
|
k, v, err := nextAttribute()
|
||||||
|
if err != nil {
|
||||||
|
return values, err
|
||||||
|
}
|
||||||
|
values[k] = v
|
||||||
|
}
|
||||||
|
return values, scanner.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GitAttribute exposes an attribute from the .gitattribute file
|
// GitAttribute exposes an attribute from the .gitattribute file
|
||||||
type GitAttribute string //nolint:revive
|
type GitAttribute string //nolint:revive
|
||||||
|
|
||||||
|
@ -54,29 +112,15 @@ func (ca GitAttribute) Bool() optional.Option[bool] {
|
||||||
return optional.None[bool]()
|
return optional.None[bool]()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitAttributeFirst returns the first specified attribute
|
// gitCheckAttrCommand prepares the "git check-attr" command for later use as one-shot or streaming
|
||||||
//
|
// instanciation.
|
||||||
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
|
|
||||||
func (repo *Repository) GitAttributeFirst(treeish, filename string, attributes ...string) (GitAttribute, error) {
|
|
||||||
values, err := repo.GitAttributes(treeish, filename, attributes...)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, a := range attributes {
|
|
||||||
if values[a].IsSpecified() {
|
|
||||||
return values[a], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string) (*Command, *RunOpts, context.CancelFunc, error) {
|
func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string) (*Command, *RunOpts, context.CancelFunc, error) {
|
||||||
if len(attributes) == 0 {
|
if len(attributes) == 0 {
|
||||||
return nil, nil, nil, fmt.Errorf("no provided attributes to check-attr")
|
return nil, nil, nil, fmt.Errorf("no provided attributes to check-attr")
|
||||||
}
|
}
|
||||||
|
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
var deleteTemporaryFile context.CancelFunc
|
var removeTempFiles context.CancelFunc = func() {}
|
||||||
|
|
||||||
// git < 2.40 cannot run check-attr on bare repo, but needs INDEX + WORK_TREE
|
// git < 2.40 cannot run check-attr on bare repo, but needs INDEX + WORK_TREE
|
||||||
hasIndex := treeish == ""
|
hasIndex := treeish == ""
|
||||||
|
@ -85,7 +129,7 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
deleteTemporaryFile = cancel
|
removeTempFiles = cancel
|
||||||
|
|
||||||
env = append(env, "GIT_INDEX_FILE="+indexFilename, "GIT_WORK_TREE="+worktree)
|
env = append(env, "GIT_INDEX_FILE="+indexFilename, "GIT_WORK_TREE="+worktree)
|
||||||
|
|
||||||
|
@ -94,16 +138,8 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
|
||||||
// clear treeish to read from provided index/work_tree
|
// clear treeish to read from provided index/work_tree
|
||||||
treeish = ""
|
treeish = ""
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(repo.Ctx)
|
|
||||||
if deleteTemporaryFile != nil {
|
|
||||||
ctxCancel := cancel
|
|
||||||
cancel = func() {
|
|
||||||
ctxCancel()
|
|
||||||
deleteTemporaryFile()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := NewCommand(ctx, "check-attr", "-z")
|
cmd := NewCommand(repo.Ctx, "check-attr", "-z")
|
||||||
|
|
||||||
if hasIndex {
|
if hasIndex {
|
||||||
cmd.AddArguments("--cached")
|
cmd.AddArguments("--cached")
|
||||||
|
@ -126,18 +162,34 @@ func (repo *Repository) gitCheckAttrCommand(treeish string, attributes ...string
|
||||||
return cmd, &RunOpts{
|
return cmd, &RunOpts{
|
||||||
Env: env,
|
Env: env,
|
||||||
Dir: repo.Path,
|
Dir: repo.Path,
|
||||||
}, cancel, nil
|
}, removeTempFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitAttributes returns gitattribute.
|
// GitAttributeFirst returns the first specified attribute of the given filename.
|
||||||
|
//
|
||||||
|
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
|
||||||
|
func (repo *Repository) GitAttributeFirst(treeish, filename string, attributes ...string) (GitAttribute, error) {
|
||||||
|
values, err := repo.GitAttributes(treeish, filename, attributes...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, a := range attributes {
|
||||||
|
if values[a].IsSpecified() {
|
||||||
|
return values[a], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitAttributes returns the gitattribute of the given filename.
|
||||||
//
|
//
|
||||||
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
|
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
|
||||||
func (repo *Repository) GitAttributes(treeish, filename string, attributes ...string) (map[string]GitAttribute, error) {
|
func (repo *Repository) GitAttributes(treeish, filename string, attributes ...string) (map[string]GitAttribute, error) {
|
||||||
cmd, runOpts, cancel, err := repo.gitCheckAttrCommand(treeish, attributes...)
|
cmd, runOpts, removeTempFiles, err := repo.gitCheckAttrCommand(treeish, attributes...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cancel()
|
defer removeTempFiles()
|
||||||
|
|
||||||
stdOut := new(bytes.Buffer)
|
stdOut := new(bytes.Buffer)
|
||||||
runOpts.Stdout = stdOut
|
runOpts.Stdout = stdOut
|
||||||
|
@ -151,163 +203,84 @@ func (repo *Repository) GitAttributes(treeish, filename string, attributes ...st
|
||||||
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
|
return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is incorrect on versions < 1.8.5
|
return newCheckAttrStdoutReader(stdOut, len(attributes))()
|
||||||
fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
|
|
||||||
|
|
||||||
if len(fields)%3 != 1 {
|
|
||||||
return nil, fmt.Errorf("wrong number of fields in return from check-attr")
|
|
||||||
}
|
|
||||||
|
|
||||||
values := make(map[string]GitAttribute, len(attributes))
|
|
||||||
for ; len(fields) >= 3; fields = fields[3:] {
|
|
||||||
// filename := string(fields[0])
|
|
||||||
attribute := string(fields[1])
|
|
||||||
value := string(fields[2])
|
|
||||||
values[attribute] = GitAttribute(value)
|
|
||||||
}
|
|
||||||
return values, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type attributeTriple struct {
|
// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID
|
||||||
Filename string
|
// to retrieve the attributes of multiple files. The AttributeChecker must be closed after use.
|
||||||
Attribute string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type nulSeparatedAttributeWriter struct {
|
|
||||||
tmp []byte
|
|
||||||
attributes chan attributeTriple
|
|
||||||
closed chan struct{}
|
|
||||||
working attributeTriple
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
|
|
||||||
l, read := len(p), 0
|
|
||||||
|
|
||||||
nulIdx := bytes.IndexByte(p, '\x00')
|
|
||||||
for nulIdx >= 0 {
|
|
||||||
wr.tmp = append(wr.tmp, p[:nulIdx]...)
|
|
||||||
switch wr.pos {
|
|
||||||
case 0:
|
|
||||||
wr.working = attributeTriple{
|
|
||||||
Filename: string(wr.tmp),
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
wr.working.Attribute = string(wr.tmp)
|
|
||||||
case 2:
|
|
||||||
wr.working.Value = string(wr.tmp)
|
|
||||||
}
|
|
||||||
wr.tmp = wr.tmp[:0]
|
|
||||||
wr.pos++
|
|
||||||
if wr.pos > 2 {
|
|
||||||
wr.attributes <- wr.working
|
|
||||||
wr.pos = 0
|
|
||||||
}
|
|
||||||
read += nulIdx + 1
|
|
||||||
if l > read {
|
|
||||||
p = p[nulIdx+1:]
|
|
||||||
nulIdx = bytes.IndexByte(p, '\x00')
|
|
||||||
} else {
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wr.tmp = append(wr.tmp, p...)
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wr *nulSeparatedAttributeWriter) Close() error {
|
|
||||||
select {
|
|
||||||
case <-wr.closed:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
close(wr.attributes)
|
|
||||||
close(wr.closed)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitAttributeChecker creates an AttributeChecker for the given repository and provided commit ID.
|
|
||||||
//
|
//
|
||||||
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
|
// If treeish is empty, the gitattribute will be read from the current repo (which MUST be a working directory and NOT bare).
|
||||||
func (repo *Repository) GitAttributeChecker(treeish string, attributes ...string) (AttributeChecker, error) {
|
func (repo *Repository) GitAttributeChecker(treeish string, attributes ...string) (AttributeChecker, error) {
|
||||||
cmd, runOpts, cancel, err := repo.gitCheckAttrCommand(treeish, attributes...)
|
cmd, runOpts, removeTempFiles, err := repo.gitCheckAttrCommand(treeish, attributes...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AttributeChecker{}, err
|
return AttributeChecker{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ac := AttributeChecker{
|
|
||||||
attributeNumber: len(attributes),
|
|
||||||
ctx: cmd.parentContext,
|
|
||||||
cancel: cancel, // will be cancelled on Close
|
|
||||||
}
|
|
||||||
|
|
||||||
stdinReader, stdinWriter, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
ac.cancel()
|
|
||||||
return AttributeChecker{}, err
|
|
||||||
}
|
|
||||||
ac.stdinWriter = stdinWriter // will be closed on Close
|
|
||||||
|
|
||||||
lw := new(nulSeparatedAttributeWriter)
|
|
||||||
lw.attributes = make(chan attributeTriple, len(attributes))
|
|
||||||
lw.closed = make(chan struct{})
|
|
||||||
ac.attributesCh = lw.attributes
|
|
||||||
|
|
||||||
cmd.AddArguments("--stdin")
|
cmd.AddArguments("--stdin")
|
||||||
|
|
||||||
|
// os.Pipe is needed (and not io.Pipe), otherwise cmd.Wait will wait for the stdinReader
|
||||||
|
// to be closed before returning (which would require another goroutine)
|
||||||
|
// https://go.dev/issue/23019
|
||||||
|
stdinReader, stdinWriter, err := os.Pipe() // reader closed in goroutine / writer closed on ac.Close
|
||||||
|
if err != nil {
|
||||||
|
return AttributeChecker{}, err
|
||||||
|
}
|
||||||
|
stdoutReader, stdoutWriter := io.Pipe() // closed in goroutine
|
||||||
|
|
||||||
|
ac := AttributeChecker{
|
||||||
|
removeTempFiles: removeTempFiles, // called on ac.Close
|
||||||
|
stdinWriter: stdinWriter,
|
||||||
|
readStdout: newCheckAttrStdoutReader(stdoutReader, len(attributes)),
|
||||||
|
err: &atomic.Value{},
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer stdinReader.Close()
|
defer stdinReader.Close()
|
||||||
defer lw.Close()
|
defer stdoutWriter.Close() // in case of a panic (no-op if already closed by CloseWithError at the end)
|
||||||
|
|
||||||
stdErr := new(bytes.Buffer)
|
stdErr := new(bytes.Buffer)
|
||||||
runOpts.Stdin = stdinReader
|
runOpts.Stdin = stdinReader
|
||||||
runOpts.Stdout = lw
|
runOpts.Stdout = stdoutWriter
|
||||||
runOpts.Stderr = stdErr
|
runOpts.Stderr = stdErr
|
||||||
|
|
||||||
err := cmd.Run(runOpts)
|
err := cmd.Run(runOpts)
|
||||||
|
|
||||||
if err != nil && // If there is an error we need to return but:
|
// if the context was cancelled, Run error is irrelevant
|
||||||
cmd.parentContext.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
|
if e := cmd.parentContext.Err(); e != nil {
|
||||||
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
|
err = e
|
||||||
log.Error("failed to run attr-check. Error: %v\nStderr: %s", err, stdErr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil { // decorate the returned error
|
||||||
|
err = fmt.Errorf("git check-attr (stderr: %q): %w", strings.TrimSpace(stdErr.String()), err)
|
||||||
|
ac.err.Store(err)
|
||||||
|
}
|
||||||
|
stdoutWriter.CloseWithError(err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ac, nil
|
return ac, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttributeChecker struct {
|
type AttributeChecker struct {
|
||||||
ctx context.Context
|
removeTempFiles context.CancelFunc
|
||||||
cancel context.CancelFunc
|
stdinWriter io.WriteCloser
|
||||||
stdinWriter *os.File
|
readStdout func() (map[string]GitAttribute, error)
|
||||||
attributeNumber int
|
err *atomic.Value
|
||||||
attributesCh <-chan attributeTriple
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac AttributeChecker) CheckPath(path string) (map[string]GitAttribute, error) {
|
func (ac AttributeChecker) CheckPath(path string) (map[string]GitAttribute, error) {
|
||||||
if err := ac.ctx.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ac.stdinWriter.Write([]byte(path + "\x00")); err != nil {
|
if _, err := ac.stdinWriter.Write([]byte(path + "\x00")); err != nil {
|
||||||
return nil, err
|
// try to return the Run error if available, since it is likely more helpful
|
||||||
|
// than just "broken pipe"
|
||||||
|
if aerr, _ := ac.err.Load().(error); aerr != nil {
|
||||||
|
return nil, aerr
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("git check-attr: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := make(map[string]GitAttribute)
|
return ac.readStdout()
|
||||||
for i := 0; i < ac.attributeNumber; i++ {
|
|
||||||
select {
|
|
||||||
case attr, ok := <-ac.attributesCh:
|
|
||||||
if !ok {
|
|
||||||
return nil, ac.ctx.Err()
|
|
||||||
}
|
|
||||||
rs[attr.Attribute] = GitAttribute(attr.Value)
|
|
||||||
case <-ac.ctx.Done():
|
|
||||||
return nil, ac.ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac AttributeChecker) Close() error {
|
func (ac AttributeChecker) Close() error {
|
||||||
ac.cancel()
|
ac.removeTempFiles()
|
||||||
return ac.stdinWriter.Close()
|
return ac.stdinWriter.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,14 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,90 +21,63 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
|
func TestNewCheckAttrStdoutReader(t *testing.T) {
|
||||||
wr := &nulSeparatedAttributeWriter{
|
t.Run("two_times", func(t *testing.T) {
|
||||||
attributes: make(chan attributeTriple, 5),
|
read := newCheckAttrStdoutReader(strings.NewReader(
|
||||||
}
|
".gitignore\x00linguist-vendored\x00unspecified\x00"+
|
||||||
|
".gitignore\x00linguist-vendored\x00specified",
|
||||||
|
), 1)
|
||||||
|
|
||||||
testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00"
|
// first read
|
||||||
|
attr, err := read()
|
||||||
n, err := wr.Write([]byte(testStr))
|
|
||||||
|
|
||||||
assert.Len(t, testStr, n)
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
select {
|
assert.Equal(t, map[string]GitAttribute{
|
||||||
case attr := <-wr.attributes:
|
"linguist-vendored": GitAttribute("unspecified"),
|
||||||
assert.Equal(t, ".gitignore\"\n", attr.Filename)
|
|
||||||
assert.Equal(t, "linguist-vendored", attr.Attribute)
|
|
||||||
assert.Equal(t, "unspecified", attr.Value)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
assert.FailNow(t, "took too long to read an attribute from the list")
|
|
||||||
}
|
|
||||||
// Write a second attribute again
|
|
||||||
n, err = wr.Write([]byte(testStr))
|
|
||||||
|
|
||||||
assert.Len(t, testStr, n)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case attr := <-wr.attributes:
|
|
||||||
assert.Equal(t, ".gitignore\"\n", attr.Filename)
|
|
||||||
assert.Equal(t, "linguist-vendored", attr.Attribute)
|
|
||||||
assert.Equal(t, "unspecified", attr.Value)
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
assert.FailNow(t, "took too long to read an attribute from the list")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a partial attribute
|
|
||||||
_, err = wr.Write([]byte("incomplete-file"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
_, err = wr.Write([]byte("name\x00"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-wr.attributes:
|
|
||||||
assert.FailNow(t, "There should not be an attribute ready to read")
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
}
|
|
||||||
_, err = wr.Write([]byte("attribute\x00"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
select {
|
|
||||||
case <-wr.attributes:
|
|
||||||
assert.FailNow(t, "There should not be an attribute ready to read")
|
|
||||||
case <-time.After(100 * time.Millisecond):
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = wr.Write([]byte("value\x00"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
attr := <-wr.attributes
|
|
||||||
assert.Equal(t, "incomplete-filename", attr.Filename)
|
|
||||||
assert.Equal(t, "attribute", attr.Attribute)
|
|
||||||
assert.Equal(t, "value", attr.Value)
|
|
||||||
|
|
||||||
_, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00"))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
attr = <-wr.attributes
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, attributeTriple{
|
|
||||||
Filename: "shouldbe.vendor",
|
|
||||||
Attribute: "linguist-vendored",
|
|
||||||
Value: "set",
|
|
||||||
}, attr)
|
}, attr)
|
||||||
attr = <-wr.attributes
|
|
||||||
|
// second read
|
||||||
|
attr, err = read()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, attributeTriple{
|
assert.Equal(t, map[string]GitAttribute{
|
||||||
Filename: "shouldbe.vendor",
|
"linguist-vendored": GitAttribute("specified"),
|
||||||
Attribute: "linguist-generated",
|
|
||||||
Value: "unspecified",
|
|
||||||
}, attr)
|
}, attr)
|
||||||
attr = <-wr.attributes
|
})
|
||||||
|
t.Run("incomplete", func(t *testing.T) {
|
||||||
|
read := newCheckAttrStdoutReader(strings.NewReader(
|
||||||
|
"filename\x00linguist-vendored",
|
||||||
|
), 1)
|
||||||
|
|
||||||
|
_, err := read()
|
||||||
|
assert.Equal(t, io.ErrUnexpectedEOF, err)
|
||||||
|
})
|
||||||
|
t.Run("three_times", func(t *testing.T) {
|
||||||
|
read := newCheckAttrStdoutReader(strings.NewReader(
|
||||||
|
"shouldbe.vendor\x00linguist-vendored\x00set\x00"+
|
||||||
|
"shouldbe.vendor\x00linguist-generated\x00unspecified\x00"+
|
||||||
|
"shouldbe.vendor\x00linguist-language\x00unspecified\x00",
|
||||||
|
), 1)
|
||||||
|
|
||||||
|
// first read
|
||||||
|
attr, err := read()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, attributeTriple{
|
assert.Equal(t, map[string]GitAttribute{
|
||||||
Filename: "shouldbe.vendor",
|
"linguist-vendored": GitAttribute("set"),
|
||||||
Attribute: "linguist-language",
|
|
||||||
Value: "unspecified",
|
|
||||||
}, attr)
|
}, attr)
|
||||||
|
|
||||||
|
// second read
|
||||||
|
attr, err = read()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]GitAttribute{
|
||||||
|
"linguist-generated": GitAttribute("unspecified"),
|
||||||
|
}, attr)
|
||||||
|
|
||||||
|
// third read
|
||||||
|
attr, err = read()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]GitAttribute{
|
||||||
|
"linguist-language": GitAttribute("unspecified"),
|
||||||
|
}, attr)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGitAttributeBareNonBare(t *testing.T) {
|
func TestGitAttributeBareNonBare(t *testing.T) {
|
||||||
|
@ -114,25 +94,6 @@ func TestGitAttributeBareNonBare(t *testing.T) {
|
||||||
"8fee858da5796dfb37704761701bb8e800ad9ef3",
|
"8fee858da5796dfb37704761701bb8e800ad9ef3",
|
||||||
"341fca5b5ea3de596dc483e54c2db28633cd2f97",
|
"341fca5b5ea3de596dc483e54c2db28633cd2f97",
|
||||||
} {
|
} {
|
||||||
t.Run("GitAttributeChecker/"+commitID, func(t *testing.T) {
|
|
||||||
bareChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
t.Cleanup(func() { bareChecker.Close() })
|
|
||||||
|
|
||||||
bareStats, err := bareChecker.CheckPath("i-am-a-python.p")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
|
|
||||||
cloneChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
t.Cleanup(func() { cloneChecker.Close() })
|
|
||||||
cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.EqualValues(t, cloneStats, bareStats)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GitAttributes/"+commitID, func(t *testing.T) {
|
|
||||||
bareStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
|
bareStats, err := gitRepo.GitAttributes(commitID, "i-am-a-python.p", LinguistAttributes...)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -141,6 +102,27 @@ func TestGitAttributeBareNonBare(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.EqualValues(t, cloneStats, bareStats)
|
assert.EqualValues(t, cloneStats, bareStats)
|
||||||
|
refStats := cloneStats
|
||||||
|
|
||||||
|
t.Run("GitAttributeChecker/"+commitID+"/SupportBare", func(t *testing.T) {
|
||||||
|
bareChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer bareChecker.Close()
|
||||||
|
|
||||||
|
bareStats, err := bareChecker.CheckPath("i-am-a-python.p")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, refStats, bareStats)
|
||||||
|
})
|
||||||
|
t.Run("GitAttributeChecker/"+commitID+"/NoBareSupport", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&SupportCheckAttrOnBare, false)()
|
||||||
|
cloneChecker, err := gitRepo.GitAttributeChecker(commitID, LinguistAttributes...)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer cloneChecker.Close()
|
||||||
|
|
||||||
|
cloneStats, err := cloneChecker.CheckPath("i-am-a-python.p")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, refStats, cloneStats)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,3 +190,162 @@ func TestGitAttributeStruct(t *testing.T) {
|
||||||
assert.Equal(t, "text?token=Error", GitAttribute("text?token=Error").String())
|
assert.Equal(t, "text?token=Error", GitAttribute("text?token=Error").String())
|
||||||
assert.Equal(t, "text", GitAttribute("text?token=Error").Prefix())
|
assert.Equal(t, "text", GitAttribute("text?token=Error").Prefix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGitAttributeCheckerError(t *testing.T) {
|
||||||
|
prepareRepo := func(t *testing.T) *Repository {
|
||||||
|
t.Helper()
|
||||||
|
path := t.TempDir()
|
||||||
|
|
||||||
|
// we can't use unittest.CopyDir because of an import cycle (git.Init in unittest)
|
||||||
|
require.NoError(t, CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo"))))
|
||||||
|
|
||||||
|
gitRepo, err := openRepositoryWithDefaultContext(path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return gitRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("RemoveAll/BeforeRun", func(t *testing.T) {
|
||||||
|
gitRepo := prepareRepo(t)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, os.RemoveAll(gitRepo.Path))
|
||||||
|
|
||||||
|
ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), `git check-attr (stderr: ""):`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RemoveAll/DuringRun", func(t *testing.T) {
|
||||||
|
gitRepo := prepareRepo(t)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
ac, err := gitRepo.GitAttributeChecker("", "linguist-language")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// calling CheckPath before would allow git to cache part of it and succesfully return later
|
||||||
|
assert.NoError(t, os.RemoveAll(gitRepo.Path))
|
||||||
|
|
||||||
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
|
if err == nil {
|
||||||
|
t.Skip(
|
||||||
|
"git check-attr started too fast and CheckPath was succesful (and likely cached)",
|
||||||
|
"https://codeberg.org/forgejo/forgejo/issues/2948",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Depending on the order of execution, the returned error can be:
|
||||||
|
// - a launch error "fork/exec /usr/bin/git: no such file or directory" (when the removal happens before the Run)
|
||||||
|
// - a git error (stderr: "fatal: Unable to read current working directory: No such file or directory"): exit status 128 (when the removal happens after the Run)
|
||||||
|
// (pipe error "write |1: broken pipe" should be replaced by one of the Run errors above)
|
||||||
|
assert.Contains(t, err.Error(), `git check-attr`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Cancelled/BeforeRun", func(t *testing.T) {
|
||||||
|
gitRepo := prepareRepo(t)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
gitRepo.Ctx, cancel = context.WithCancel(gitRepo.Ctx)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
|
assert.ErrorIs(t, err, context.Canceled)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Cancelled/DuringRun", func(t *testing.T) {
|
||||||
|
gitRepo := prepareRepo(t)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
gitRepo.Ctx, cancel = context.WithCancel(gitRepo.Ctx)
|
||||||
|
|
||||||
|
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
attr, err := ac.CheckPath("i-am-a-python.p")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Python", attr["linguist-language"].String())
|
||||||
|
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
for err == nil {
|
||||||
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
|
runtime.Gosched() // the cancellation must have time to propagate
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Error("CheckPath did not complete within 1s")
|
||||||
|
case err = <-errCh:
|
||||||
|
assert.ErrorIs(t, err, context.Canceled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Closed/BeforeRun", func(t *testing.T) {
|
||||||
|
gitRepo := prepareRepo(t)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, ac.Close())
|
||||||
|
|
||||||
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
|
assert.ErrorIs(t, err, fs.ErrClosed)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Closed/DuringRun", func(t *testing.T) {
|
||||||
|
gitRepo := prepareRepo(t)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
attr, err := ac.CheckPath("i-am-a-python.p")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Python", attr["linguist-language"].String())
|
||||||
|
|
||||||
|
assert.NoError(t, ac.Close())
|
||||||
|
|
||||||
|
_, err = ac.CheckPath("i-am-a-python.p")
|
||||||
|
assert.ErrorIs(t, err, fs.ErrClosed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFS is adapted from https://github.com/golang/go/issues/62484
|
||||||
|
// which should be available with go1.23
|
||||||
|
func CopyFS(dir string, fsys fs.FS) error {
|
||||||
|
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error {
|
||||||
|
targ := filepath.Join(dir, filepath.FromSlash(path))
|
||||||
|
if d.IsDir() {
|
||||||
|
return os.MkdirAll(targ, 0o777)
|
||||||
|
}
|
||||||
|
r, err := fsys.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
info, err := r.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w, err := os.OpenFile(targ, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o666|info.Mode()&0o777)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, r); err != nil {
|
||||||
|
w.Close()
|
||||||
|
return fmt.Errorf("copying %s: %v", path, err)
|
||||||
|
}
|
||||||
|
return w.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -238,7 +238,7 @@ func newRefsFromRefNames(refNames []byte) []git.Reference {
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
Commit *git.Commit
|
Commit *git.Commit
|
||||||
User *user_model.User
|
User *user_model.User
|
||||||
Verification *asymkey_model.CommitVerification
|
Verification *asymkey_model.ObjectVerification
|
||||||
Status *git_model.CommitStatus
|
Status *git_model.CommitStatus
|
||||||
Flow int64
|
Flow int64
|
||||||
Row int
|
Row int
|
||||||
|
|
|
@ -136,7 +136,7 @@ func (g *Manager) doShutdown() {
|
||||||
}
|
}
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
g.shutdownCtxCancel()
|
g.shutdownCtxCancel()
|
||||||
atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown"))
|
atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "post-shutdown"))
|
||||||
pprof.SetGoroutineLabels(atShutdownCtx)
|
pprof.SetGoroutineLabels(atShutdownCtx)
|
||||||
for _, fn := range g.toRunAtShutdown {
|
for _, fn := range g.toRunAtShutdown {
|
||||||
go fn()
|
go fn()
|
||||||
|
@ -167,7 +167,7 @@ func (g *Manager) doHammerTime(d time.Duration) {
|
||||||
default:
|
default:
|
||||||
log.Warn("Setting Hammer condition")
|
log.Warn("Setting Hammer condition")
|
||||||
g.hammerCtxCancel()
|
g.hammerCtxCancel()
|
||||||
atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer"))
|
atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "post-hammer"))
|
||||||
pprof.SetGoroutineLabels(atHammerCtx)
|
pprof.SetGoroutineLabels(atHammerCtx)
|
||||||
}
|
}
|
||||||
g.lock.Unlock()
|
g.lock.Unlock()
|
||||||
|
@ -183,7 +183,7 @@ func (g *Manager) doTerminate() {
|
||||||
default:
|
default:
|
||||||
log.Warn("Terminating")
|
log.Warn("Terminating")
|
||||||
g.terminateCtxCancel()
|
g.terminateCtxCancel()
|
||||||
atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate"))
|
atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "post-terminate"))
|
||||||
pprof.SetGoroutineLabels(atTerminateCtx)
|
pprof.SetGoroutineLabels(atTerminateCtx)
|
||||||
|
|
||||||
for _, fn := range g.toRunAtTerminate {
|
for _, fn := range g.toRunAtTerminate {
|
||||||
|
|
|
@ -65,10 +65,10 @@ func (g *Manager) prepare(ctx context.Context) {
|
||||||
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
|
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
|
||||||
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
|
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
|
||||||
|
|
||||||
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
|
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("gracefulLifecycle", "with-terminate"))
|
||||||
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
|
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("gracefulLifecycle", "with-shutdown"))
|
||||||
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
|
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("gracefulLifecycle", "with-hammer"))
|
||||||
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
|
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("gracefulLifecycle", "with-manager"))
|
||||||
|
|
||||||
if !g.setStateTransition(stateInit, stateRunning) {
|
if !g.setStateTransition(stateInit, stateRunning) {
|
||||||
panic("invalid graceful manager state: transition from init to running failed")
|
panic("invalid graceful manager state: transition from init to running failed")
|
||||||
|
|
|
@ -44,7 +44,7 @@ func (g *Manager) notify(msg systemdNotifyMsg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Manager) start() {
|
func (g *Manager) start() {
|
||||||
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
|
// Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
|
||||||
pprof.SetGoroutineLabels(g.managerCtx)
|
pprof.SetGoroutineLabels(g.managerCtx)
|
||||||
defer pprof.SetGoroutineLabels(g.ctx)
|
defer pprof.SetGoroutineLabels(g.ctx)
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (g *Manager) start() {
|
func (g *Manager) start() {
|
||||||
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
|
// Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
|
||||||
pprof.SetGoroutineLabels(g.managerCtx)
|
pprof.SetGoroutineLabels(g.managerCtx)
|
||||||
defer pprof.SetGoroutineLabels(g.ctx)
|
defer pprof.SetGoroutineLabels(g.ctx)
|
||||||
|
|
||||||
|
|
323
modules/markup/file_preview.go
Normal file
323
modules/markup/file_preview.go
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
// Copyright The Forgejo Authors.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"html/template"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/charset"
|
||||||
|
"code.gitea.io/gitea/modules/highlight"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
"golang.org/x/net/html/atom"
|
||||||
|
)
|
||||||
|
|
||||||
|
// filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2"
|
||||||
|
var filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`)
|
||||||
|
|
||||||
|
type FilePreview struct {
|
||||||
|
fileContent []template.HTML
|
||||||
|
subTitle template.HTML
|
||||||
|
lineOffset int
|
||||||
|
urlFull string
|
||||||
|
filePath string
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
isTruncated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
|
||||||
|
if setting.FilePreviewMaxLines == 0 {
|
||||||
|
// Feature is disabled
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
preview := &FilePreview{}
|
||||||
|
|
||||||
|
m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that every group has a match
|
||||||
|
if slices.Contains(m, -1) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
preview.urlFull = node.Data[m[0]:m[1]]
|
||||||
|
|
||||||
|
// Ensure that we only use links to local repositories
|
||||||
|
if !strings.HasPrefix(preview.urlFull, setting.AppURL+setting.AppSubURL) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
projPath := strings.TrimSuffix(node.Data[m[2]:m[3]], "/")
|
||||||
|
|
||||||
|
commitSha := node.Data[m[4]:m[5]]
|
||||||
|
preview.filePath = node.Data[m[6]:m[7]]
|
||||||
|
hash := node.Data[m[8]:m[9]]
|
||||||
|
|
||||||
|
preview.start = m[0]
|
||||||
|
preview.end = m[1]
|
||||||
|
|
||||||
|
projPathSegments := strings.Split(projPath, "/")
|
||||||
|
var language string
|
||||||
|
fileBlob, err := DefaultProcessorHelper.GetRepoFileBlob(
|
||||||
|
ctx.Ctx,
|
||||||
|
projPathSegments[len(projPathSegments)-2],
|
||||||
|
projPathSegments[len(projPathSegments)-1],
|
||||||
|
commitSha, preview.filePath,
|
||||||
|
&language,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lineSpecs := strings.Split(hash, "-")
|
||||||
|
|
||||||
|
commitLinkBuffer := new(bytes.Buffer)
|
||||||
|
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to render commitLink: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var startLine, endLine int
|
||||||
|
|
||||||
|
if len(lineSpecs) == 1 {
|
||||||
|
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
|
||||||
|
endLine = startLine
|
||||||
|
preview.subTitle = locale.Tr(
|
||||||
|
"markup.filepreview.line", startLine,
|
||||||
|
template.HTML(commitLinkBuffer.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
preview.lineOffset = startLine - 1
|
||||||
|
} else {
|
||||||
|
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
|
||||||
|
endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
|
||||||
|
preview.subTitle = locale.Tr(
|
||||||
|
"markup.filepreview.lines", startLine, endLine,
|
||||||
|
template.HTML(commitLinkBuffer.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
preview.lineOffset = startLine - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
lineCount := endLine - (startLine - 1)
|
||||||
|
if startLine < 1 || endLine < 1 || lineCount < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.FilePreviewMaxLines > 0 && lineCount > setting.FilePreviewMaxLines {
|
||||||
|
preview.isTruncated = true
|
||||||
|
lineCount = setting.FilePreviewMaxLines
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRc, err := fileBlob.DataAsync()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer dataRc.Close()
|
||||||
|
|
||||||
|
reader := bufio.NewReader(dataRc)
|
||||||
|
|
||||||
|
// skip all lines until we find our startLine
|
||||||
|
for i := 1; i < startLine; i++ {
|
||||||
|
_, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// capture the lines we're interested in
|
||||||
|
lineBuffer := new(bytes.Buffer)
|
||||||
|
for i := 0; i < lineCount; i++ {
|
||||||
|
buf, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lineBuffer.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight the file...
|
||||||
|
fileContent, _, err := highlight.File(fileBlob.Name(), language, lineBuffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("highlight.File failed, fallback to plain text: %v", err)
|
||||||
|
fileContent = highlight.PlainText(lineBuffer.Bytes())
|
||||||
|
}
|
||||||
|
preview.fileContent = fileContent
|
||||||
|
|
||||||
|
return preview
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
|
||||||
|
table := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Table.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "file-preview"}},
|
||||||
|
}
|
||||||
|
tbody := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Tbody.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
status := &charset.EscapeStatus{}
|
||||||
|
statuses := make([]*charset.EscapeStatus, len(p.fileContent))
|
||||||
|
for i, line := range p.fileContent {
|
||||||
|
statuses[i], p.fileContent[i] = charset.EscapeControlHTML(line, locale, charset.FileviewContext)
|
||||||
|
status = status.Or(statuses[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, code := range p.fileContent {
|
||||||
|
tr := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Tr.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
lineNum := strconv.Itoa(p.lineOffset + idx + 1)
|
||||||
|
|
||||||
|
tdLinesnum := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Td.String(),
|
||||||
|
Attr: []html.Attribute{
|
||||||
|
{Key: "class", Val: "lines-num"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
spanLinesNum := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Span.String(),
|
||||||
|
Attr: []html.Attribute{
|
||||||
|
{Key: "data-line-number", Val: lineNum},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tdLinesnum.AppendChild(spanLinesNum)
|
||||||
|
tr.AppendChild(tdLinesnum)
|
||||||
|
|
||||||
|
if status.Escaped {
|
||||||
|
tdLinesEscape := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Td.String(),
|
||||||
|
Attr: []html.Attribute{
|
||||||
|
{Key: "class", Val: "lines-escape"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if statuses[idx].Escaped {
|
||||||
|
btnTitle := ""
|
||||||
|
if statuses[idx].HasInvisible {
|
||||||
|
btnTitle += locale.TrString("repo.invisible_runes_line") + " "
|
||||||
|
}
|
||||||
|
if statuses[idx].HasAmbiguous {
|
||||||
|
btnTitle += locale.TrString("repo.ambiguous_runes_line")
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeBtn := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Button.String(),
|
||||||
|
Attr: []html.Attribute{
|
||||||
|
{Key: "class", Val: "toggle-escape-button btn interact-bg"},
|
||||||
|
{Key: "title", Val: btnTitle},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tdLinesEscape.AppendChild(escapeBtn)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.AppendChild(tdLinesEscape)
|
||||||
|
}
|
||||||
|
|
||||||
|
tdCode := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Td.String(),
|
||||||
|
Attr: []html.Attribute{
|
||||||
|
{Key: "class", Val: "lines-code chroma"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
codeInner := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Code.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "code-inner"}},
|
||||||
|
}
|
||||||
|
codeText := &html.Node{
|
||||||
|
Type: html.RawNode,
|
||||||
|
Data: string(code),
|
||||||
|
}
|
||||||
|
codeInner.AppendChild(codeText)
|
||||||
|
tdCode.AppendChild(codeInner)
|
||||||
|
tr.AppendChild(tdCode)
|
||||||
|
|
||||||
|
tbody.AppendChild(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.AppendChild(tbody)
|
||||||
|
|
||||||
|
twrapper := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Div.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "ui table"}},
|
||||||
|
}
|
||||||
|
twrapper.AppendChild(table)
|
||||||
|
|
||||||
|
header := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Div.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "header"}},
|
||||||
|
}
|
||||||
|
afilepath := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.A.String(),
|
||||||
|
Attr: []html.Attribute{
|
||||||
|
{Key: "href", Val: p.urlFull},
|
||||||
|
{Key: "class", Val: "muted"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
afilepath.AppendChild(&html.Node{
|
||||||
|
Type: html.TextNode,
|
||||||
|
Data: p.filePath,
|
||||||
|
})
|
||||||
|
header.AppendChild(afilepath)
|
||||||
|
|
||||||
|
psubtitle := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Span.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "text small grey"}},
|
||||||
|
}
|
||||||
|
psubtitle.AppendChild(&html.Node{
|
||||||
|
Type: html.RawNode,
|
||||||
|
Data: string(p.subTitle),
|
||||||
|
})
|
||||||
|
header.AppendChild(psubtitle)
|
||||||
|
|
||||||
|
node := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Div.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
|
||||||
|
}
|
||||||
|
node.AppendChild(header)
|
||||||
|
|
||||||
|
if p.isTruncated {
|
||||||
|
warning := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Div.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "ui warning message tw-text-left"}},
|
||||||
|
}
|
||||||
|
warning.AppendChild(&html.Node{
|
||||||
|
Type: html.TextNode,
|
||||||
|
Data: locale.TrString("markup.filepreview.truncated"),
|
||||||
|
})
|
||||||
|
node.AppendChild(warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.AppendChild(twrapper)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
|
@ -171,6 +171,7 @@ type processor func(ctx *RenderContext, node *html.Node)
|
||||||
var defaultProcessors = []processor{
|
var defaultProcessors = []processor{
|
||||||
fullIssuePatternProcessor,
|
fullIssuePatternProcessor,
|
||||||
comparePatternProcessor,
|
comparePatternProcessor,
|
||||||
|
filePreviewPatternProcessor,
|
||||||
fullHashPatternProcessor,
|
fullHashPatternProcessor,
|
||||||
shortLinkProcessor,
|
shortLinkProcessor,
|
||||||
linkProcessor,
|
linkProcessor,
|
||||||
|
@ -1054,6 +1055,47 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
|
if ctx.Metas == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if DefaultProcessorHelper.GetRepoFileBlob == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next := node.NextSibling
|
||||||
|
for node != nil && node != next {
|
||||||
|
locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale)
|
||||||
|
if !ok {
|
||||||
|
locale = translation.NewLocale("en-US")
|
||||||
|
}
|
||||||
|
|
||||||
|
preview := NewFilePreview(ctx, node, locale)
|
||||||
|
if preview == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
previewNode := preview.CreateHTML(locale)
|
||||||
|
|
||||||
|
// Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div
|
||||||
|
before := node.Data[:preview.start]
|
||||||
|
after := node.Data[preview.end:]
|
||||||
|
node.Data = before
|
||||||
|
nextSibling := node.NextSibling
|
||||||
|
node.Parent.InsertBefore(&html.Node{
|
||||||
|
Type: html.RawNode,
|
||||||
|
Data: "</p>",
|
||||||
|
}, nextSibling)
|
||||||
|
node.Parent.InsertBefore(previewNode, nextSibling)
|
||||||
|
node.Parent.InsertBefore(&html.Node{
|
||||||
|
Type: html.RawNode,
|
||||||
|
Data: "<p>" + after,
|
||||||
|
}, nextSibling)
|
||||||
|
|
||||||
|
node = node.NextSibling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// emojiShortCodeProcessor for rendering text like :smile: into emoji
|
// emojiShortCodeProcessor for rendering text like :smile: into emoji
|
||||||
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
|
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
start := 0
|
start := 0
|
||||||
|
|
|
@ -17,9 +17,11 @@ import (
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var localMetas = map[string]string{
|
var localMetas = map[string]string{
|
||||||
|
@ -676,3 +678,68 @@ func TestIssue18471(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
|
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRender_FilePreview(t *testing.T) {
|
||||||
|
setting.StaticRootPath = "../../"
|
||||||
|
setting.Names = []string{"english"}
|
||||||
|
setting.Langs = []string{"en-US"}
|
||||||
|
translation.InitLocales(context.Background())
|
||||||
|
|
||||||
|
setting.AppURL = markup.TestAppURL
|
||||||
|
markup.Init(&markup.ProcessorHelper{
|
||||||
|
GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
|
||||||
|
gitRepo, err := git.OpenRepository(git.DefaultContext, "./tests/repo/repo1_filepreview")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
commit, err := gitRepo.GetCommit("HEAD")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
blob, err := commit.GetBlobByPath("path/to/file.go")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return blob, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
sha := "190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||||
|
commitFilePreview := util.URLJoin(markup.TestRepoURL, "src", "commit", sha, "path", "to", "file.go") + "#L2-L3"
|
||||||
|
|
||||||
|
test := func(input, expected string) {
|
||||||
|
buffer, err := markup.RenderString(&markup.RenderContext{
|
||||||
|
Ctx: git.DefaultContext,
|
||||||
|
RelativePath: ".md",
|
||||||
|
Metas: localMetas,
|
||||||
|
}, input)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
commitFilePreview,
|
||||||
|
`<p></p>`+
|
||||||
|
`<div class="file-preview-box">`+
|
||||||
|
`<div class="header">`+
|
||||||
|
`<a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3" class="muted" rel="nofollow">path/to/file.go</a>`+
|
||||||
|
`<span class="text small grey">`+
|
||||||
|
`Lines 2 to 3 in <a href="http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" class="text black" rel="nofollow">190d949</a>`+
|
||||||
|
`</span>`+
|
||||||
|
`</div>`+
|
||||||
|
`<div class="ui table">`+
|
||||||
|
`<table class="file-preview">`+
|
||||||
|
`<tbody>`+
|
||||||
|
`<tr>`+
|
||||||
|
`<td class="lines-num"><span data-line-number="2"></span></td>`+
|
||||||
|
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">B</span>`+"\n"+`</code></td>`+
|
||||||
|
`</tr>`+
|
||||||
|
`<tr>`+
|
||||||
|
`<td class="lines-num"><span data-line-number="3"></span></td>`+
|
||||||
|
`<td class="lines-code chroma"><code class="code-inner"><span class="nx">C</span>`+"\n"+`</code></td>`+
|
||||||
|
`</tr>`+
|
||||||
|
`</tbody>`+
|
||||||
|
`</table>`+
|
||||||
|
`</div>`+
|
||||||
|
`</div>`+
|
||||||
|
`<p></p>`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
19
modules/markup/markdown/color_util.go
Normal file
19
modules/markup/markdown/color_util.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markdown
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
var (
|
||||||
|
hexRGB = regexp.MustCompile(`^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$`)
|
||||||
|
hsl = regexp.MustCompile(`^hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)$`)
|
||||||
|
hsla = regexp.MustCompile(`^hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)$`)
|
||||||
|
rgb = regexp.MustCompile(`^rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)$`)
|
||||||
|
rgba = regexp.MustCompile(`^rgba\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){3}[ ]*(1(\.0)?|0|(0\.[0-9]+))\)$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// matchColor return if color is in the form of hex RGB, HSL(A) or RGB(A).
|
||||||
|
func matchColor(color string) bool {
|
||||||
|
return hexRGB.MatchString(color) || rgb.MatchString(color) || rgba.MatchString(color) || hsl.MatchString(color) || hsla.MatchString(color)
|
||||||
|
}
|
50
modules/markup/markdown/color_util_test.go
Normal file
50
modules/markup/markdown/color_util_test.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package markdown
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchColor(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"#ddeeffa0", true},
|
||||||
|
{"#ddeefe", true},
|
||||||
|
{"#abcdef", true},
|
||||||
|
{"#abcdeg", false},
|
||||||
|
{"#abcdefg0", false},
|
||||||
|
{"black", false},
|
||||||
|
{"violet", false},
|
||||||
|
{"rgb(255, 255, 255)", true},
|
||||||
|
{"rgb(0, 0, 0)", true},
|
||||||
|
{"rgb(256, 0, 0)", false},
|
||||||
|
{"rgb(0, 256, 0)", false},
|
||||||
|
{"rgb(0, 0, 256)", false},
|
||||||
|
{"rgb(0, 0, 0, 1)", false},
|
||||||
|
{"rgba(0, 0, 0)", false},
|
||||||
|
{"rgba(0, 255, 0, 1)", true},
|
||||||
|
{"rgba(32, 255, 12, 0.55)", true},
|
||||||
|
{"rgba(32, 256, 12, 0.55)", false},
|
||||||
|
{"hsl(0, 0%, 0%)", true},
|
||||||
|
{"hsl(360, 100%, 100%)", true},
|
||||||
|
{"hsl(361, 100%, 50%)", false},
|
||||||
|
{"hsl(360, 101%, 50%)", false},
|
||||||
|
{"hsl(360, 100%, 101%)", false},
|
||||||
|
{"hsl(0, 0%, 0%, 0)", false},
|
||||||
|
{"hsla(0, 0%, 0%)", false},
|
||||||
|
{"hsla(0, 0%, 0%, 0)", true},
|
||||||
|
{"hsla(0, 0%, 0%, 1)", true},
|
||||||
|
{"hsla(0, 0%, 0%, 0.5)", true},
|
||||||
|
{"hsla(0, 0%, 0%, 1.5)", false},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
actual := matchColor(testCase.input)
|
||||||
|
assert.Equal(t, testCase.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
giteautil "code.gitea.io/gitea/modules/util"
|
giteautil "code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/microcosm-cc/bluemonday/css"
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
east "github.com/yuin/goldmark/extension/ast"
|
east "github.com/yuin/goldmark/extension/ast"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
|
@ -199,7 +198,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||||
}
|
}
|
||||||
case *ast.CodeSpan:
|
case *ast.CodeSpan:
|
||||||
colorContent := n.Text(reader.Source())
|
colorContent := n.Text(reader.Source())
|
||||||
if css.ColorHandler(strings.ToLower(string(colorContent))) {
|
if matchColor(strings.ToLower(string(colorContent))) {
|
||||||
v.AppendChild(v, NewColorPreview(colorContent))
|
v.AppendChild(v, NewColorPreview(colorContent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ const (
|
||||||
|
|
||||||
type ProcessorHelper struct {
|
type ProcessorHelper struct {
|
||||||
IsUsernameMentionable func(ctx context.Context, username string) bool
|
IsUsernameMentionable func(ctx context.Context, username string) bool
|
||||||
|
GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
|
||||||
|
|
||||||
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
|
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,23 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
||||||
policy.AllowStyles("color", "background-color").OnElements("span", "p")
|
policy.AllowStyles("color", "background-color").OnElements("span", "p")
|
||||||
|
|
||||||
|
// Allow classes for file preview links...
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^(lines-num|lines-code chroma)$")).OnElements("td")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^code-inner$")).OnElements("code")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview-box$")).OnElements("div")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui table$")).OnElements("div")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^header$")).OnElements("div")
|
||||||
|
policy.AllowAttrs("data-line-number").Matching(regexp.MustCompile("^[0-9]+$")).OnElements("span")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^text small grey$")).OnElements("span")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^file-preview*")).OnElements("table")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^lines-escape$")).OnElements("td")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^toggle-escape-button btn interact-bg$")).OnElements("button")
|
||||||
|
policy.AllowAttrs("title").OnElements("button")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
|
||||||
|
policy.AllowAttrs("data-tooltip-content").OnElements("span")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
|
||||||
|
|
||||||
// Allow generally safe attributes
|
// Allow generally safe attributes
|
||||||
generalSafeAttrs := []string{
|
generalSafeAttrs := []string{
|
||||||
"abbr", "accept", "accept-charset",
|
"abbr", "accept", "accept-charset",
|
||||||
|
|
1
modules/markup/tests/repo/repo1_filepreview/HEAD
Normal file
1
modules/markup/tests/repo/repo1_filepreview/HEAD
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ref: refs/heads/master
|
6
modules/markup/tests/repo/repo1_filepreview/config
Normal file
6
modules/markup/tests/repo/repo1_filepreview/config
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = true
|
||||||
|
bare = true
|
||||||
|
[remote "origin"]
|
||||||
|
url = /home/mai/projects/codeark/forgejo/forgejo/modules/markup/tests/repo/repo1_filepreview/../../__test_repo
|
1
modules/markup/tests/repo/repo1_filepreview/description
Normal file
1
modules/markup/tests/repo/repo1_filepreview/description
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
6
modules/markup/tests/repo/repo1_filepreview/info/exclude
Normal file
6
modules/markup/tests/repo/repo1_filepreview/info/exclude
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# git ls-files --others --exclude-from=.git/info/exclude
|
||||||
|
# Lines that start with '#' are comments.
|
||||||
|
# For a project mostly in C, the following would be a good set of
|
||||||
|
# exclude patterns (uncomment them if you want to use them):
|
||||||
|
# *.[oa]
|
||||||
|
# *~
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
x+)JMU06e040031QHËÌIÕKÏghQºÂ/TX'·7潊ç·såË#3‹ô
|
|
@ -0,0 +1 @@
|
||||||
|
190d9492934af498c3f669d6a2431dc5459e5b20
|
|
@ -26,7 +26,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// DescriptionPProfLabel is a label set on goroutines that have a process attached
|
// DescriptionPProfLabel is a label set on goroutines that have a process attached
|
||||||
const DescriptionPProfLabel = "process-description"
|
const DescriptionPProfLabel = "processDescription"
|
||||||
|
|
||||||
// PIDPProfLabel is a label set on goroutines that have a process attached
|
// PIDPProfLabel is a label set on goroutines that have a process attached
|
||||||
const PIDPProfLabel = "pid"
|
const PIDPProfLabel = "pid"
|
||||||
|
@ -35,7 +35,7 @@ const PIDPProfLabel = "pid"
|
||||||
const PPIDPProfLabel = "ppid"
|
const PPIDPProfLabel = "ppid"
|
||||||
|
|
||||||
// ProcessTypePProfLabel is a label set on goroutines that have a process attached
|
// ProcessTypePProfLabel is a label set on goroutines that have a process attached
|
||||||
const ProcessTypePProfLabel = "process-type"
|
const ProcessTypePProfLabel = "processType"
|
||||||
|
|
||||||
// IDType is a pid type
|
// IDType is a pid type
|
||||||
type IDType string
|
type IDType string
|
||||||
|
|
|
@ -15,6 +15,7 @@ var (
|
||||||
ExternalMarkupRenderers []*MarkupRenderer
|
ExternalMarkupRenderers []*MarkupRenderer
|
||||||
ExternalSanitizerRules []MarkupSanitizerRule
|
ExternalSanitizerRules []MarkupSanitizerRule
|
||||||
MermaidMaxSourceCharacters int
|
MermaidMaxSourceCharacters int
|
||||||
|
FilePreviewMaxLines int
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -62,6 +63,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
|
||||||
mustMapSetting(rootCfg, "markdown", &Markdown)
|
mustMapSetting(rootCfg, "markdown", &Markdown)
|
||||||
|
|
||||||
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
|
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
|
||||||
|
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
|
||||||
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
|
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
|
||||||
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
|
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ type BranchProtection struct {
|
||||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
||||||
|
ApplyToAdmins bool `json:"apply_to_admins"`
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
Created time.Time `json:"created_at"`
|
Created time.Time `json:"created_at"`
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
|
@ -80,6 +81,7 @@ type CreateBranchProtectionOption struct {
|
||||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
||||||
|
ApplyToAdmins bool `json:"apply_to_admins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditBranchProtectionOption options for editing a branch protection
|
// EditBranchProtectionOption options for editing a branch protection
|
||||||
|
@ -106,4 +108,5 @@ type EditBranchProtectionOption struct {
|
||||||
RequireSignedCommits *bool `json:"require_signed_commits"`
|
RequireSignedCommits *bool `json:"require_signed_commits"`
|
||||||
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
||||||
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
|
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
|
||||||
|
ApplyToAdmins *bool `json:"apply_to_admins"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ func NewFuncMap() template.FuncMap {
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// time / number / format
|
// time / number / format
|
||||||
"FileSize": base.FileSize,
|
"FileSize": FileSizePanic,
|
||||||
"CountFmt": base.FormatNumberSI,
|
"CountFmt": base.FormatNumberSI,
|
||||||
"TimeSince": timeutil.TimeSince,
|
"TimeSince": timeutil.TimeSince,
|
||||||
"TimeSinceUnix": timeutil.TimeSinceUnix,
|
"TimeSinceUnix": timeutil.TimeSinceUnix,
|
||||||
|
@ -249,3 +249,7 @@ func Eval(tokens ...any) (any, error) {
|
||||||
n, err := eval.Expr(tokens...)
|
n, err := eval.Expr(tokens...)
|
||||||
return n.Value, err
|
return n.Value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FileSizePanic(s int64) string {
|
||||||
|
panic("Usage of FileSize in templates is deprecated in Forgejo. Locale.TrSize should be used instead.")
|
||||||
|
}
|
||||||
|
|
|
@ -43,9 +43,7 @@ func (w *testLoggerWriterCloser) pushT(t testing.TB) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
|
func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
|
||||||
if len(msg) > 0 && msg[len(msg)-1] == '\n' {
|
msg = strings.TrimSpace(msg)
|
||||||
msg = msg[:len(msg)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
w.printMsg(msg)
|
w.printMsg(msg)
|
||||||
if level >= log.ERROR {
|
if level >= log.ERROR {
|
||||||
|
@ -56,10 +54,13 @@ func (w *testLoggerWriterCloser) Log(level log.Level, msg string) {
|
||||||
// list of error message which will not fail the test
|
// list of error message which will not fail the test
|
||||||
// ideally this list should be empty, however ensuring that it does not grow
|
// ideally this list should be empty, however ensuring that it does not grow
|
||||||
// is already a good first step.
|
// is already a good first step.
|
||||||
var ignoredErrorMessageSuffixes = []string{
|
var ignoredErrorMessage = []string{
|
||||||
// only seen on mysql tests https://codeberg.org/forgejo/forgejo/pulls/2657#issuecomment-1693055
|
// only seen on mysql tests https://codeberg.org/forgejo/forgejo/pulls/2657#issuecomment-1693055
|
||||||
`table columns using inconsistent collation, they should use "utf8mb4_0900_ai_ci". Please go to admin panel Self Check page`,
|
`table columns using inconsistent collation, they should use "utf8mb4_0900_ai_ci". Please go to admin panel Self Check page`,
|
||||||
|
|
||||||
|
// TestPullWIPConvertSidebar
|
||||||
|
`:PullRequestPushCommits() [E] comment.LoadIssue: issue does not exist [id:`,
|
||||||
|
|
||||||
// TestAPIDeleteReleaseByTagName
|
// TestAPIDeleteReleaseByTagName
|
||||||
// action notification were a commit cannot be computed (because the commit got deleted)
|
// action notification were a commit cannot be computed (because the commit got deleted)
|
||||||
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
|
`Notify() [E] an error occurred while executing the DeleteRelease actions method: gitRepo.GetCommit: object does not exist [id: refs/tags/release-tag, rel_path: ]`,
|
||||||
|
@ -76,6 +77,14 @@ var ignoredErrorMessageSuffixes = []string{
|
||||||
// TestAPIGenerateRepo
|
// TestAPIGenerateRepo
|
||||||
`Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`,
|
`Notify() [E] an error occurred while executing the CreateRepository actions method: gitRepo.GetCommit: object does not exist [id: , rel_path: ]`,
|
||||||
|
|
||||||
|
// TestAPIPullUpdateByRebase
|
||||||
|
`:testPR() [E] Unable to GetPullRequestByID[`,
|
||||||
|
`:PullRequestSynchronized() [E] LoadAttributes: getRepositoryByID `,
|
||||||
|
`:PullRequestSynchronized() [E] pr.Issue.LoadRepo: getRepositoryByID [`,
|
||||||
|
`:handler() [E] Was unable to create issue notification: issue does not exist [`,
|
||||||
|
`:func1() [E] PullRequestList.LoadAttributes: issues and prs may be not in sync: cannot find issue`,
|
||||||
|
`:func1() [E] checkForInvalidation: GetRepositoryByIDCtx: repository does not exist `,
|
||||||
|
|
||||||
// TestAPIPullReview
|
// TestAPIPullReview
|
||||||
`PullRequestReview() [E] Unsupported review webhook type`,
|
`PullRequestReview() [E] Unsupported review webhook type`,
|
||||||
|
|
||||||
|
@ -111,11 +120,251 @@ var ignoredErrorMessageSuffixes = []string{
|
||||||
|
|
||||||
// TestRebuildCargo
|
// TestRebuildCargo
|
||||||
`RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`,
|
`RebuildCargoIndex() [E] RebuildIndex failed: GetRepositoryByOwnerAndName: repository does not exist [id: 0, uid: 0, owner_name: user2, name: _cargo-index]`,
|
||||||
|
|
||||||
|
// TestDangerZoneConfirmation/Convert_fork/Fail
|
||||||
|
`/gitea-repositories/user20/big_test_public_fork_7.git Error: no such file or directory`,
|
||||||
|
// TestGitSmartHTTP
|
||||||
|
`:sendFile() [E] request file path contains invalid path: objects/info/..\..\..\..\custom\conf\app.ini`,
|
||||||
|
// TestGit/HTTP/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
|
||||||
|
// TestGit/HTTP/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
|
||||||
|
// TestGit/HTTP/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
|
||||||
|
// TestGit/HTTP/MergeFork/CreatePRAndMerge
|
||||||
|
`:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1099 name: user2:master]`, // sqlite
|
||||||
|
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10000 name: user2:master]", // mysql
|
||||||
|
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1060 name: user2:master]", // pgsql
|
||||||
|
// TestGit/HTTP/BranchProtectMerge
|
||||||
|
`:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:func1() [E] PushToBaseRepo: PushRejected Error: exit status 1 - remote: error: cannot lock ref`,
|
||||||
|
// TestGit/SSH/LFS/PushCommit/Little
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/PushCommit/Little
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/PushCommit/Big
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/PushCommit/Big
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/Locks
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/Locks
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/Locks
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/Locks
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/LFS/Locks
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/PushParams/NoParams
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/PushParams/TitleOverride
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/PushParams/DescriptionOverride
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Force_push/Fails
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Force_push/Succeeds
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Force_push
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Force_push
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull/Branch_already_contains_commit
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/CreateAgitFlowPull
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Not allowed to push to protected branch protected. HookPreReceive(last) failed: internal API error response, status=403`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: branch protected is protected from force push. HookPreReceive(last) failed: internal API error response, status=403`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/BranchProtectMerge
|
||||||
|
`:SSHLog() [E] ssh: Unknown git command. Unknown git command git-lfs-transfer`,
|
||||||
|
// TestGit/SSH/MergeFork/CreatePRAndMerge
|
||||||
|
`:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1102 name: user2:master]`, // sqlite
|
||||||
|
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 10003 name: user2:master]", // mysql
|
||||||
|
"s/web/repo/branch.go:108:DeleteBranchPost() [E] DeleteBranch: GetBranch: branch does not exist [repo_id: 1063 name: user2:master]", // pgsql
|
||||||
|
// TestGit/SSH/PushCreate
|
||||||
|
`:SSHLog() [E] ssh: Push to create is not enabled for users. ServCommand failed: internal API error response, status=403`,
|
||||||
|
// TestGit/SSH/PushCreate
|
||||||
|
`:SSHLog() [E] ssh: Cannot find repository: user2/repo-tmp-push-create-ssh. ServCommand failed: internal API error response, status=404`,
|
||||||
|
// TestGit/SSH/PushCreate
|
||||||
|
`:SSHLog() [E] ssh: Invalid repo name. Invalid repo name: invalid/repo-tmp-push-create-ssh`,
|
||||||
|
// TestIssueReaction
|
||||||
|
`:ChangeIssueReaction() [E] ChangeIssueReaction: '8ball' is not an allowed reaction`,
|
||||||
|
// TestIssuePinMove
|
||||||
|
`:IssuePinMove() [E] Issue does not belong to this repository`,
|
||||||
|
// TestLinksLogin
|
||||||
|
`:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
|
||||||
|
// TestLinksLogin
|
||||||
|
`:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
|
||||||
|
// TestLinksLogin
|
||||||
|
`:GetIssuesAllCommitStatus() [E] getAllCommitStatus: cant get commit statuses of pull [6]: object does not exist [id: refs/pull/2/head, rel_path: ]`,
|
||||||
|
// TestLinksLogin
|
||||||
|
`:GetIssuesAllCommitStatus() [E] Cannot open git repository <Repository 23:org17/big_test_public_4> for issue #1[20]. Error: no such file or directory`,
|
||||||
|
// TestMigrate
|
||||||
|
`] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
|
||||||
|
// TestMigrate
|
||||||
|
`:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
|
||||||
|
// TestMigrate
|
||||||
|
`] for OwnerID[2] failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
|
||||||
|
// TestMigrate
|
||||||
|
`:handler() [E] Run task failed: error while listing issues: token does not have at least one of required scope(s): [read:issue]`,
|
||||||
|
// TestMirrorPush
|
||||||
|
`:GetInfoRefs() [E] fork/exec /usr/bin/git: no such file or directory -`,
|
||||||
|
|
||||||
|
// TestOrgMembers
|
||||||
|
`:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
|
||||||
|
// TestOrgMembers
|
||||||
|
`:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
|
||||||
|
// TestOrgMembers
|
||||||
|
`:loadOrganizationOwners() [E] Organization does not have owner team: 25`,
|
||||||
|
// TestRecentlyPushed/unrelated_branches_are_not_shown
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestRecentlyPushed/unrelated_branches_are_not_shown
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
|
||||||
|
// TestRecentlyPushed/unrelated_branches_are_not_shown
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestRecentlyPushed/unrelated_branches_are_not_shown
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
|
||||||
|
// TestRecentlyPushed/unrelated_branches_are_not_shown
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestRecentlyPushed/unrelated_branches_are_not_shown
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
|
||||||
|
// TestCantMergeConflict
|
||||||
|
"]user1/repo1#1[base...conflict]> Unable to merge tracking into base: Merge Conflict Error: exit status 1: \nAuto-merging README.md\nCONFLICT (content): Merge conflict in README.md\nAutomatic merge failed; fix conflicts and then commit the result.",
|
||||||
|
|
||||||
|
// TestCantMergeUnrelated
|
||||||
|
`]user1/repo1#1[base...unrelated]> Unable to merge tracking into base: Merge UnrelatedHistories Error: exit status 128: fatal: refusing to merge unrelated histories`,
|
||||||
|
// TestCantFastForwardOnlyMergeDiverging
|
||||||
|
"]user1/repo1#1[master...diverging]> Unable to merge tracking into base: Merge DivergingFastForwardOnly Error: exit status 128: hint: Diverging branches can't be fast-forwarded, you need to either:\nhint: \nhint: \tgit merge --no-ff\nhint: \nhint: or:\nhint: \nhint: \tgit rebase\nhint: \nhint: Disable this message with \"git config advice.diverging false\"\nfatal: Not possible to fast-forward, aborting.",
|
||||||
|
// TestPullrequestReopen/Base_branch_deleted
|
||||||
|
`fatal: couldn't find remote ref base-branch`,
|
||||||
|
// TestPullrequestReopen/Head_branch_deleted
|
||||||
|
`]user2/reopen-base#1[base-branch...org26/reopen-head:head-branch]>]: branch does not exist [repo_id: 0 name: head-branch]`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'
|
||||||
|
- fatal: bad revision 'master...refs/heads/will-be-missing'`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user30/repo50]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [50] failed: no such file or directory`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user30/repo51]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [51] failed: no such file or directory`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:SyncRepoBranches() [E] OpenRepository[user2/scoped_label]: %!w(*errors.errorString=&{no such file or directory})`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
`:handlerBranchSync() [E] syncRepoBranches [55] failed: no such file or directory`,
|
||||||
|
// TestDatabaseMissingABranch
|
||||||
|
"LoadBranches() [E] loadOneBranch() on repo #1, branch 'will-be-missing' failed: CountDivergingCommits: exit status 128 - fatal: bad revision 'master...refs/heads/will-be-missing'\n - fatal: bad revision 'master...refs/heads/will-be-missing'",
|
||||||
|
|
||||||
|
// TestCreateNewTagProtected/Git
|
||||||
|
`:SSHLog() [E] ssh: Tag v-2 is protected. HookPreReceive(last) failed: internal API error response, status=403`,
|
||||||
|
// TestMarkDownReadmeImage
|
||||||
|
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
|
||||||
|
// TestMarkDownReadmeImage
|
||||||
|
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: home-md-img-check]`,
|
||||||
|
// TestMarkDownReadmeImageSubfolder
|
||||||
|
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
|
||||||
|
// TestMarkDownReadmeImageSubfolder
|
||||||
|
`:checkOutdatedBranch() [E] GetBranch: branch does not exist [repo_id: 1 name: sub-home-md-img-check]`,
|
||||||
|
|
||||||
|
// TestKeyOnlyOneType
|
||||||
|
`:ssh-key-test-repo-push is not authorized to write to user2/ssh-key-test-repo. ServCommand failed: internal API error response, status=401`,
|
||||||
|
|
||||||
|
// TestPullRebase
|
||||||
|
"/gitea-repositories/user2/repo1.git' does not appear to be a git repository\nfatal: Could not read from remote repository.\n\nPlease make sure you have the correct access rights\nand the repository exists.",
|
||||||
|
|
||||||
|
// TestPullRebaseMerge
|
||||||
|
"]user2/repo1#3[master...branch2]>]: branch does not exist [repo_id: 0 name: branch2]",
|
||||||
|
|
||||||
|
// TestAuthorizeNoClientID
|
||||||
|
`TrString() [E] Missing translation "form.ResponseType"`,
|
||||||
|
|
||||||
|
// TestWebhookForms
|
||||||
|
`TrString() [E] Missing translation "form.AuthorizationHeader"`,
|
||||||
|
`TrString() [E] Missing translation "form.Channel"`,
|
||||||
|
`TrString() [E] Missing translation "form.ContentType"`,
|
||||||
|
`TrString() [E] Missing translation "form.HTTPMethod"`,
|
||||||
|
`TrString() [E] Missing translation "form.PayloadURL"`,
|
||||||
|
|
||||||
|
// TestRenameInvalidUsername
|
||||||
|
`TrString() [E] Missing translation "form.Name"`,
|
||||||
|
|
||||||
|
// TestDatabaseCollation
|
||||||
|
`[E] [Error SQL Query] INSERT INTO test_collation_tbl (txt) VALUES ('main') []`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *testLoggerWriterCloser) recordError(msg string) {
|
func (w *testLoggerWriterCloser) recordError(msg string) {
|
||||||
for _, s := range ignoredErrorMessageSuffixes {
|
for _, s := range ignoredErrorMessage {
|
||||||
if strings.HasSuffix(msg, s) {
|
if strings.Contains(msg, s) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +377,11 @@ func (w *testLoggerWriterCloser) recordError(msg string) {
|
||||||
err = w.errs[len(w.errs)-1]
|
err = w.errs[len(w.errs)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(w.t) > 0 {
|
||||||
|
// format error message to easily add it to the ignore list
|
||||||
|
msg = fmt.Sprintf("// %s\n\t%q,", w.t[len(w.t)-1].Name(), msg)
|
||||||
|
}
|
||||||
|
|
||||||
err = errors.Join(err, errors.New(msg))
|
err = errors.Join(err, errors.New(msg))
|
||||||
|
|
||||||
if len(w.errs) > 0 {
|
if len(w.errs) > 0 {
|
||||||
|
@ -231,7 +485,9 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := WriterCloser.popT(); err != nil {
|
if err := WriterCloser.popT(); err != nil {
|
||||||
t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
|
// disable test failure for now (too flacky)
|
||||||
|
_, _ = fmt.Fprintf(os.Stdout, "testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
|
||||||
|
// t.Errorf("testlogger.go:recordError() FATAL ERROR: log.Error has been called: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,10 @@ func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
|
||||||
return template.HTML(key1)
|
return template.HTML(key1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l MockLocale) TrSize(s int64) ReadableSize {
|
||||||
|
return ReadableSize{fmt.Sprint(s), ""}
|
||||||
|
}
|
||||||
|
|
||||||
func (l MockLocale) PrettyNumber(v any) string {
|
func (l MockLocale) PrettyNumber(v any) string {
|
||||||
return fmt.Sprint(v)
|
return fmt.Sprint(v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/translation/i18n"
|
"code.gitea.io/gitea/modules/translation/i18n"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"golang.org/x/text/message"
|
"golang.org/x/text/message"
|
||||||
"golang.org/x/text/number"
|
"golang.org/x/text/number"
|
||||||
|
@ -33,6 +34,8 @@ type Locale interface {
|
||||||
Tr(key string, args ...any) template.HTML
|
Tr(key string, args ...any) template.HTML
|
||||||
TrN(cnt any, key1, keyN string, args ...any) template.HTML
|
TrN(cnt any, key1, keyN string, args ...any) template.HTML
|
||||||
|
|
||||||
|
TrSize(size int64) ReadableSize
|
||||||
|
|
||||||
PrettyNumber(v any) string
|
PrettyNumber(v any) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +255,35 @@ func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
|
||||||
return l.Tr(keyN, args...)
|
return l.Tr(keyN, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReadableSize struct {
|
||||||
|
PrettyNumber string
|
||||||
|
TranslatedUnit string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs ReadableSize) String() string {
|
||||||
|
return bs.PrettyNumber + " " + bs.TranslatedUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrSize returns array containing pretty formatted size and localized output of FileSize
|
||||||
|
// output of humanize.IBytes has to be split in order to be localized
|
||||||
|
func (l *locale) TrSize(s int64) ReadableSize {
|
||||||
|
us := uint64(s)
|
||||||
|
if s < 0 {
|
||||||
|
us = uint64(-s)
|
||||||
|
}
|
||||||
|
untranslated := humanize.IBytes(us)
|
||||||
|
if s < 0 {
|
||||||
|
untranslated = "-" + untranslated
|
||||||
|
}
|
||||||
|
numberVal, unitVal, found := strings.Cut(untranslated, " ")
|
||||||
|
if !found {
|
||||||
|
log.Error("no space in go-humanized size of %d: %q", s, untranslated)
|
||||||
|
}
|
||||||
|
numberVal = l.PrettyNumber(numberVal)
|
||||||
|
unitVal = l.TrString("munits.data." + strings.ToLower(unitVal))
|
||||||
|
return ReadableSize{numberVal, unitVal}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *locale) PrettyNumber(v any) string {
|
func (l *locale) PrettyNumber(v any) string {
|
||||||
// TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format
|
// TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format
|
||||||
if s, ok := v.(string); ok {
|
if s, ok := v.(string); ok {
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
package translation
|
package translation
|
||||||
|
|
||||||
|
// TODO: make this package friendly to testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -11,9 +13,25 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrettyNumber(t *testing.T) {
|
func TestTrSize(t *testing.T) {
|
||||||
// TODO: make this package friendly to testing
|
l := NewLocale("")
|
||||||
|
size := int64(1)
|
||||||
|
assert.EqualValues(t, "1 munits.data.b", l.TrSize(size).String())
|
||||||
|
size *= 2048
|
||||||
|
assert.EqualValues(t, "2 munits.data.kib", l.TrSize(size).String())
|
||||||
|
size *= 2048
|
||||||
|
assert.EqualValues(t, "4 munits.data.mib", l.TrSize(size).String())
|
||||||
|
size *= 2048
|
||||||
|
assert.EqualValues(t, "8 munits.data.gib", l.TrSize(size).String())
|
||||||
|
size *= 2048
|
||||||
|
assert.EqualValues(t, "16 munits.data.tib", l.TrSize(size).String())
|
||||||
|
size *= 2048
|
||||||
|
assert.EqualValues(t, "32 munits.data.pib", l.TrSize(size).String())
|
||||||
|
size *= 128
|
||||||
|
assert.EqualValues(t, "4 munits.data.eib", l.TrSize(size).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrettyNumber(t *testing.T) {
|
||||||
i18n.ResetDefaultLocales()
|
i18n.ResetDefaultLocales()
|
||||||
|
|
||||||
allLangMap = make(map[string]*LangType)
|
allLangMap = make(map[string]*LangType)
|
||||||
|
|
|
@ -680,7 +680,7 @@ issues.self_assign_at = `كلّف نفسه بها %s`
|
||||||
issues.label_deletion = احذف التصنيف
|
issues.label_deletion = احذف التصنيف
|
||||||
issues.filter_milestone_all = كل الأهداف
|
issues.filter_milestone_all = كل الأهداف
|
||||||
issues.unlock.notice_2 = - يمكنك دوما إقفال هذه المسألة من جديد في المستقبل.
|
issues.unlock.notice_2 = - يمكنك دوما إقفال هذه المسألة من جديد في المستقبل.
|
||||||
issues.num_participants = %d متحاور
|
issues.num_participants_few = %d متحاور
|
||||||
release.title = عنوان الإصدار
|
release.title = عنوان الإصدار
|
||||||
issues.closed_at = `أغلق هذه المسألة <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.closed_at = `أغلق هذه المسألة <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.lock.title = إقفال التحاور في هذه المسألة.
|
issues.lock.title = إقفال التحاور في هذه المسألة.
|
||||||
|
|
|
@ -125,10 +125,16 @@ orgs_none = Не сте участник в никакви организаци
|
||||||
repos_none = Не притежавате никакви хранилища.
|
repos_none = Не притежавате никакви хранилища.
|
||||||
blocked_users_none = Няма блокирани потребители.
|
blocked_users_none = Няма блокирани потребители.
|
||||||
profile_desc = Контролирайте как вашият профил се показва на другите потребители. Вашият основен адрес на ел. поща ще се използва за известия, възстановяване на паролата и уеб базирани Git операции.
|
profile_desc = Контролирайте как вашият профил се показва на другите потребители. Вашият основен адрес на ел. поща ще се използва за известия, възстановяване на паролата и уеб базирани Git операции.
|
||||||
permission_write = Четене и Писане
|
permission_write = Четене и писане
|
||||||
twofa_disable = Изключване на двуфакторното удостоверяване
|
twofa_disable = Изключване на двуфакторното удостоверяване
|
||||||
twofa_enroll = Включване на двуфакторно удостоверяване
|
twofa_enroll = Включване на двуфакторно удостоверяване
|
||||||
ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт.
|
ssh_key_name_used = Вече съществува SSH ключ със същото име във вашия акаунт.
|
||||||
|
email_notifications.enable = Включване на известията по ел. поща
|
||||||
|
delete_prompt = Тази операция ще изтрие перманентно потребителския ви акаунт. Това <strong>НЕ МОЖЕ</strong> да бъде отменено.
|
||||||
|
email_notifications.disable = Изключване на известията по ел. поща
|
||||||
|
delete_account = Изтриване на акаунта ви
|
||||||
|
confirm_delete_account = Потвърждаване на изтриването
|
||||||
|
email_notifications.onmention = Ел. поща само при споменаване
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
container.labels.value = Стойност
|
container.labels.value = Стойност
|
||||||
|
@ -258,7 +264,7 @@ new_fork = Ново разклонение на хранилище
|
||||||
unpin = Откачване
|
unpin = Откачване
|
||||||
pin = Закачване
|
pin = Закачване
|
||||||
filter = Филтър
|
filter = Филтър
|
||||||
filter.clear = Изчистване на филтъра
|
filter.clear = Изчистване на филтрите
|
||||||
filter.is_archived = Архивирани
|
filter.is_archived = Архивирани
|
||||||
filter.not_archived = Не архивирани
|
filter.not_archived = Не архивирани
|
||||||
filter.is_fork = Разклонени
|
filter.is_fork = Разклонени
|
||||||
|
@ -269,6 +275,7 @@ filter.not_template = Не шаблони
|
||||||
filter.private = Частни
|
filter.private = Частни
|
||||||
filter.is_mirror = Огледални
|
filter.is_mirror = Огледални
|
||||||
filter.not_mirror = Не огледални
|
filter.not_mirror = Не огледални
|
||||||
|
copy_hash = Копиране на контролната сума
|
||||||
|
|
||||||
[repo]
|
[repo]
|
||||||
issues.context.edit = Редактиране
|
issues.context.edit = Редактиране
|
||||||
|
@ -366,7 +373,7 @@ issues.keyword_search_unavailable = В момента търсенето по к
|
||||||
repo_desc_helper = Въведете кратко описание (опционално)
|
repo_desc_helper = Въведете кратко описание (опционално)
|
||||||
mirror_address = Клониране от URL
|
mirror_address = Клониране от URL
|
||||||
owner_helper = Някои организации може да не се показват в падащото меню поради ограничение за максимален брой хранилища.
|
owner_helper = Някои организации може да не се показват в падащото меню поради ограничение за максимален брой хранилища.
|
||||||
new_repo_helper = Хранилище съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
|
new_repo_helper = Хранилището съдържа всички файлове на проекта, включително хронологията на ревизиите. Вече хоствате хранилище другаде? <a href="%s">Мигрирайте хранилище.</a>
|
||||||
repo_name_helper = Добрите имена на хранилища използват кратки, запомнящи се и уникални ключови думи.
|
repo_name_helper = Добрите имена на хранилища използват кратки, запомнящи се и уникални ключови думи.
|
||||||
migrated_from = Мигрирано от <a href="%[1]s">%[2]s</a>
|
migrated_from = Мигрирано от <a href="%[1]s">%[2]s</a>
|
||||||
visibility_description = Само притежателят или участниците в организацията, ако имат права, ще могат да го видят.
|
visibility_description = Само притежателят или участниците в организацията, ако имат права, ще могат да го видят.
|
||||||
|
@ -448,7 +455,7 @@ fork_from = Разклоняване от
|
||||||
diff.comment.placeholder = Оставете коментар
|
diff.comment.placeholder = Оставете коментар
|
||||||
projects.edit = Редактиране на проекта
|
projects.edit = Редактиране на проекта
|
||||||
projects.modify = Редактиране на проекта
|
projects.modify = Редактиране на проекта
|
||||||
issues.new.no_label = Няма етикет
|
issues.new.no_label = Няма етикети
|
||||||
issues.new.title_empty = Заглавието не може да бъде празно
|
issues.new.title_empty = Заглавието не може да бъде празно
|
||||||
issues.new.projects = Проекти
|
issues.new.projects = Проекти
|
||||||
issues.new.clear_projects = Изчистване на проектите
|
issues.new.clear_projects = Изчистване на проектите
|
||||||
|
@ -534,7 +541,7 @@ settings.collaboration.write = Писане
|
||||||
settings.collaboration.read = Четене
|
settings.collaboration.read = Четене
|
||||||
settings.collaboration.owner = Притежател
|
settings.collaboration.owner = Притежател
|
||||||
settings.basic_settings = Основни настройки
|
settings.basic_settings = Основни настройки
|
||||||
settings.wiki_desc = Включване на уики на хранилището
|
settings.wiki_desc = Включване на уикито за хранилището
|
||||||
settings.use_internal_wiki = Използване на вграденото уики
|
settings.use_internal_wiki = Използване на вграденото уики
|
||||||
settings.wiki_globally_editable = Позволяване на всеки да редактира уикито
|
settings.wiki_globally_editable = Позволяване на всеки да редактира уикито
|
||||||
settings.add_collaborator = Добавяне на сътрудник
|
settings.add_collaborator = Добавяне на сътрудник
|
||||||
|
@ -629,7 +636,7 @@ issues.filter_milestone_all = Всички етапи
|
||||||
issues.filter_milestone_open = Отворени етапи
|
issues.filter_milestone_open = Отворени етапи
|
||||||
issues.filter_milestone_none = Без етапи
|
issues.filter_milestone_none = Без етапи
|
||||||
issues.filter_project = Проект
|
issues.filter_project = Проект
|
||||||
issues.num_participants = %d участващи
|
issues.num_participants_few = %d участващи
|
||||||
issues.filter_assignee = Изпълнител
|
issues.filter_assignee = Изпълнител
|
||||||
issues.filter_milestone_closed = Затворени етапи
|
issues.filter_milestone_closed = Затворени етапи
|
||||||
issues.filter_assginee_no_select = Всички изпълнители
|
issues.filter_assginee_no_select = Всички изпълнители
|
||||||
|
@ -667,10 +674,10 @@ milestones.close = Затваряне
|
||||||
issues.label_templates.use = Използване на набор от етикети
|
issues.label_templates.use = Използване на набор от етикети
|
||||||
issues.add_milestone_at = `добави това към етапа <b>%s</b> %s`
|
issues.add_milestone_at = `добави това към етапа <b>%s</b> %s`
|
||||||
issues.add_label = добави етикета %s %s
|
issues.add_label = добави етикета %s %s
|
||||||
issues.add_labels = добави етикетите %s %s
|
issues.add_labels = добави етикети %s %s
|
||||||
issues.remove_label = премахна етикета %s %s
|
issues.remove_label = премахна етикета %s %s
|
||||||
issues.remove_labels = премахна етикетите %s %s
|
issues.remove_labels = премахна етикетите %s %s
|
||||||
issues.add_remove_labels = добави етикетите %s и премахна %s %s
|
issues.add_remove_labels = добави етикети %s и премахна %s %s
|
||||||
issues.add_project_at = `добави това към проекта <b>%s</b> %s`
|
issues.add_project_at = `добави това към проекта <b>%s</b> %s`
|
||||||
issues.remove_project_at = `премахна това от проекта <b>%s</b> %s`
|
issues.remove_project_at = `премахна това от проекта <b>%s</b> %s`
|
||||||
issues.remove_milestone_at = `премахна това от етапа <b>%s</b> %s`
|
issues.remove_milestone_at = `премахна това от етапа <b>%s</b> %s`
|
||||||
|
@ -702,7 +709,7 @@ more_operations = Още операции
|
||||||
download_archive = Изтегляне на хранилището
|
download_archive = Изтегляне на хранилището
|
||||||
branch = Клон
|
branch = Клон
|
||||||
tree = Дърво
|
tree = Дърво
|
||||||
branches = Клони
|
branches = Клонове
|
||||||
tags = Маркери
|
tags = Маркери
|
||||||
tag = Маркер
|
tag = Маркер
|
||||||
filter_branch_and_tag = Филтр. на клон или маркер
|
filter_branch_and_tag = Филтр. на клон или маркер
|
||||||
|
@ -759,7 +766,7 @@ pulls.merged_by = от <a href="%[2]s">%[3]s</a> бе слята %[1]s
|
||||||
pulls.merged_by_fake = от %[2]s бе слята %[1]s
|
pulls.merged_by_fake = от %[2]s бе слята %[1]s
|
||||||
issues.label_deletion = Изтриване на етикета
|
issues.label_deletion = Изтриване на етикета
|
||||||
issues.label_modify = Редактиране на етикета
|
issues.label_modify = Редактиране на етикета
|
||||||
issues.due_date_added = добави крайния срок %s %s
|
issues.due_date_added = добави краен срок %s %s
|
||||||
issues.due_date_remove = премахна крайния срок %s %s
|
issues.due_date_remove = премахна крайния срок %s %s
|
||||||
release.new_release = Ново издание
|
release.new_release = Ново издание
|
||||||
release.tag_helper_existing = Съществуващ маркер.
|
release.tag_helper_existing = Съществуващ маркер.
|
||||||
|
@ -822,7 +829,7 @@ editor.fail_to_update_file = Неуспешно обновяване/създа
|
||||||
editor.add_subdir = Добавяне на директория…
|
editor.add_subdir = Добавяне на директория…
|
||||||
commits.commits = Подавания
|
commits.commits = Подавания
|
||||||
commits.find = Търсене
|
commits.find = Търсене
|
||||||
commits.search_all = Всички клони
|
commits.search_all = Всички клонове
|
||||||
commits.search = Потърсете подавания…
|
commits.search = Потърсете подавания…
|
||||||
commit.operations = Операции
|
commit.operations = Операции
|
||||||
issues.deleted_milestone = `(изтрит)`
|
issues.deleted_milestone = `(изтрит)`
|
||||||
|
@ -848,7 +855,7 @@ release.edit_release = Обновяване на изданието
|
||||||
diff.committed_by = подадено от
|
diff.committed_by = подадено от
|
||||||
release.downloads = Изтегляния
|
release.downloads = Изтегляния
|
||||||
issues.sign_in_require_desc = <a href="%s">Влезте</a> за да се присъедините към това обсъждане.
|
issues.sign_in_require_desc = <a href="%s">Влезте</a> за да се присъедините към това обсъждане.
|
||||||
activity.git_stats_push_to_all_branches = към всички клони.
|
activity.git_stats_push_to_all_branches = към всички клонове.
|
||||||
release.deletion_tag_success = Маркерът е изтрит.
|
release.deletion_tag_success = Маркерът е изтрит.
|
||||||
release.cancel = Отказ
|
release.cancel = Отказ
|
||||||
release.deletion = Изтриване на изданието
|
release.deletion = Изтриване на изданието
|
||||||
|
@ -928,9 +935,9 @@ settings.web_hook_name_discord = Discord
|
||||||
settings.web_hook_name_telegram = Telegram
|
settings.web_hook_name_telegram = Telegram
|
||||||
settings.web_hook_name_matrix = Matrix
|
settings.web_hook_name_matrix = Matrix
|
||||||
settings.web_hook_name_gogs = Gogs
|
settings.web_hook_name_gogs = Gogs
|
||||||
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
|
settings.web_hook_name_feishu = Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu = Feishu
|
settings.web_hook_name_feishu_only = Feishu
|
||||||
settings.web_hook_name_larksuite = Lark Suite
|
settings.web_hook_name_larksuite_only = Lark Suite
|
||||||
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist = Packagist
|
settings.web_hook_name_packagist = Packagist
|
||||||
diff.file_byte_size = Размер
|
diff.file_byte_size = Размер
|
||||||
|
@ -959,6 +966,110 @@ search.results = Резултати от търсенето на "%s" в <a href
|
||||||
object_format = Формат на обектите
|
object_format = Формат на обектите
|
||||||
release.releases_for = Издания за %s
|
release.releases_for = Издания за %s
|
||||||
release.tags_for = Маркери за %s
|
release.tags_for = Маркери за %s
|
||||||
|
pulls.cmd_instruction_hint = `Вижте <a class="show-instruction">инструкциите за командния ред</a>.`
|
||||||
|
pulls.showing_only_single_commit = Показани са само промените в подаване %[1]s
|
||||||
|
issues.lock_no_reason = заключи и ограничи обсъждането до сътрудници %s
|
||||||
|
pulls.expand_files = Разгъване на всички файлове
|
||||||
|
pulls.title_desc_few = иска да слее %[1]d подавания от <code>%[2]s</code> в <code id="branch_target">%[3]s</code>
|
||||||
|
issues.content_history.deleted = изтрито
|
||||||
|
activity.git_stats_exclude_merges = С изключение на сливанията,
|
||||||
|
activity.navbar.pulse = Последна дейност
|
||||||
|
activity.no_git_activity = Не е имало никаква дейност с подавания през този период.
|
||||||
|
pulls.merged_title_desc_few = сля %[1]d подавания от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
|
||||||
|
diff.stats_desc_file = %d промени: %d добавяния и %d изтривания
|
||||||
|
issues.content_history.created = създадено
|
||||||
|
pulls.status_checks_success = Всички проверки бяха успешни
|
||||||
|
activity.git_stats_pushed_n = са изтласкали
|
||||||
|
pulls.select_commit_hold_shift_for_range = Изберете подаване. Задръжте shift + click, за да изберете обхвата
|
||||||
|
activity.git_stats_addition_1 = %d добавяне
|
||||||
|
activity.git_stats_on_default_branch = В %s,
|
||||||
|
activity.git_stats_files_changed_1 = е променен
|
||||||
|
activity.git_stats_files_changed_n = са променени
|
||||||
|
activity.git_stats_additions = и е имало
|
||||||
|
pulls.collapse_files = Свиване на всички файлове
|
||||||
|
pulls.show_all_commits = Показване на всички подавания
|
||||||
|
diff.whitespace_button = Празни знаци
|
||||||
|
issues.content_history.edited = редактирано
|
||||||
|
pulls.title_desc_one = иска да слее %[1]d подаване от <code>%[2]s</code> в <code id="branch_target">%[3]s</code>
|
||||||
|
pulls.showing_specified_commit_range = Показани са само промените между %[1]s..%[2]s
|
||||||
|
pulls.merged_title_desc_one = сля %[1]d подаване от <code>%[2]s</code> в <code>%[3]s</code> %[4]s
|
||||||
|
pulls.no_merge_access = Не сте упълномощени за сливане на тази заявка за сливане.
|
||||||
|
activity.navbar.code_frequency = Честота на кода
|
||||||
|
activity.git_stats_pushed_1 = е изтласкал
|
||||||
|
activity.git_stats_push_to_branch = към %s и
|
||||||
|
contributors.contribution_type.commits = Подавания
|
||||||
|
stars = Звезди
|
||||||
|
n_commit_few = %s подавания
|
||||||
|
n_branch_one = %s клон
|
||||||
|
n_branch_few = %s клона
|
||||||
|
n_tag_one = %s маркер
|
||||||
|
n_tag_few = %s маркера
|
||||||
|
commit_graph = Граф с подавания
|
||||||
|
commits.renamed_from = Преименувано от %s
|
||||||
|
commits.view_path = Преглед на този момент в историята
|
||||||
|
commits.search_branch = Този клон
|
||||||
|
n_commit_one = %s подаване
|
||||||
|
release.ahead.commits = <strong>%d</strong> подавания
|
||||||
|
release.stable = Стабилно
|
||||||
|
commits.gpg_key_id = ID на GPG ключ
|
||||||
|
diff.options_button = Опции за разликите
|
||||||
|
activity.title.unresolved_conv_1 = %d нерешено обсъждане
|
||||||
|
activity.title.unresolved_conv_n = %d нерешени обсъждания
|
||||||
|
issues.comment_pull_merged_at = сля подаване %[1]s в %[2]s %[3]s
|
||||||
|
issues.comment_manually_pull_merged_at = ръчно сля подаване %[1]s в %[2]s %[3]s
|
||||||
|
issues.dependency.add = Добавяне на зависимост…
|
||||||
|
issues.dependency.cancel = Отказ
|
||||||
|
issues.dependency.add_error_dep_exists = Зависимостта вече съществува.
|
||||||
|
issues.dependency.add_error_dep_not_exist = Зависимостта не съществува.
|
||||||
|
issues.remove_ref_at = `премахна препратката <b>%s</b> %s`
|
||||||
|
issues.ref_pull_from = `<a href="%[3]s">спомена тази заявка за сливане %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
|
issues.dependency.pr_no_dependencies = Няма зададени зависимости.
|
||||||
|
issues.dependency.remove_info = Премахване на тази зависимост
|
||||||
|
issues.dependency.removed_dependency = `премахна зависимост %s`
|
||||||
|
issues.dependency.added_dependency = `добави нова зависимост %s`
|
||||||
|
issues.dependency.issue_closing_blockedby = Затварянето на тази задача е блокирано от следните задачи
|
||||||
|
issues.dependency.issue_close_blocks = Тази задача блокира затварянето на следните задачи
|
||||||
|
issues.dependency.issue_close_blocked = Трябва да затворите всички задачи, блокиращи тази задача, преди да можете да я затворите.
|
||||||
|
issues.dependency.blocks_short = Блокира
|
||||||
|
issues.dependency.remove_header = Премахване на зависимост
|
||||||
|
issues.dependency.issue_remove_text = Това ще премахне зависимостта от тази задача. Продължаване?
|
||||||
|
issues.reference_link = Препратка: %s
|
||||||
|
pulls.closed = Заявката за сливане е затворена
|
||||||
|
pulls.merged_success = Заявката за сливане е успешно слята и затворена
|
||||||
|
branch.confirm_create_branch = Създаване на клон
|
||||||
|
branch.create_branch_operation = Създаване на клон
|
||||||
|
tag.create_tag_operation = Създаване на маркер
|
||||||
|
tag.confirm_create_tag = Създаване на маркер
|
||||||
|
pulls.data_broken = Тази заявка за сливане е повредена поради липсваща информация за разклонението.
|
||||||
|
issues.dependency.pr_closing_blockedby = Затварянето на тази заявка за сливане е блокирано от следните задачи
|
||||||
|
issues.dependency.pr_remove_text = Това ще премахне зависимостта от тази заявка за сливане. Продължаване?
|
||||||
|
issues.dependency.title = Зависимости
|
||||||
|
issues.dependency.issue_no_dependencies = Няма зададени зависимости.
|
||||||
|
issues.dependency.pr_close_blocked = Трябва да затворите всички задачи, блокиращи тази заявка за сливане, преди да можете да я слеете.
|
||||||
|
issues.dependency.pr_close_blocks = Тази заявка за сливане блокира затварянето на следните задачи
|
||||||
|
issues.ref_issue_from = `<a href="%[3]s">спомена тази задача %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
|
issues.commit_ref_at = `спомена тази задача в подаване <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
|
issues.add_ref_at = `добави препратка <b>%s</b> %s`
|
||||||
|
pulls.merged_info_text = Клонът %s вече може да бъде изтрит.
|
||||||
|
pulls.commit_ref_at = `спомена тази заявка за сливане в подаване <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
|
issues.change_ref_at = `промени препратката от <b><strike>%s</strike></b> на <b>%s</b> %s`
|
||||||
|
diff.review.reject = Поискване на промени
|
||||||
|
diff.bin_not_shown = Двоичният файл не е показан.
|
||||||
|
settings.units.units = Елементи на хранилището
|
||||||
|
settings.delete_notices_fork_1 = - Разклоненията на това хранилище ще станат независими след изтриване.
|
||||||
|
settings.actions_desc = Включване на действията за хранилището
|
||||||
|
settings.packages_desc = Включване на регистъра на пакетите за хранилището
|
||||||
|
settings.units.add_more = Добавяне...
|
||||||
|
settings.use_external_issue_tracker = Използване на външен тракер за задачи
|
||||||
|
settings.releases_desc = Включване на изданията за хранилището
|
||||||
|
settings.projects_desc = Включване на проектите за хранилището
|
||||||
|
settings.pulls_desc = Включване на заявките за сливане за хранилището
|
||||||
|
settings.issues_desc = Включване на задачите за хранилището
|
||||||
|
settings.use_internal_issue_tracker = Използване на вградения тракер за задачи
|
||||||
|
pulls.compare_changes_desc = Изберете клона, в който да слеете, и клона, от който да издърпате.
|
||||||
|
pulls.compare_base = сливане в
|
||||||
|
pulls.compare_compare = издърпване от
|
||||||
|
pulls.title_wip_desc = `<a href="#">Започнете заглавието с <strong>%s</strong></a> за да предотвратите случайно сливане на заявката за сливане.`
|
||||||
|
|
||||||
[modal]
|
[modal]
|
||||||
confirm = Потвърждаване
|
confirm = Потвърждаване
|
||||||
|
@ -1053,6 +1164,8 @@ members.owner = Притежател
|
||||||
members.member_role = Роля на участника:
|
members.member_role = Роля на участника:
|
||||||
members.member = Участник
|
members.member = Участник
|
||||||
members.private_helper = да е видим
|
members.private_helper = да е видим
|
||||||
|
teams.no_desc = Този екип няма описание
|
||||||
|
settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване?
|
||||||
|
|
||||||
[install]
|
[install]
|
||||||
admin_password = Парола
|
admin_password = Парола
|
||||||
|
@ -1090,6 +1203,7 @@ sqlite_helper = Път на файла за SQLite3 базата данни.<br>
|
||||||
err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен.
|
err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен.
|
||||||
password_algorithm = Алгоритъм за хеш. на паролите
|
password_algorithm = Алгоритъм за хеш. на паролите
|
||||||
default_keep_email_private = Скриване на адресите на ел. поща по подразбиране
|
default_keep_email_private = Скриване на адресите на ел. поща по подразбиране
|
||||||
|
invalid_password_algorithm = Невалиден алгоритъм за хеш. на паролите
|
||||||
|
|
||||||
[filter]
|
[filter]
|
||||||
string.asc = А - Я
|
string.asc = А - Я
|
||||||
|
@ -1135,6 +1249,7 @@ change_avatar = Променете профилната си снимка…
|
||||||
email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители
|
email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители
|
||||||
disabled_public_activity = Този потребител е изключил публичната видимост на дейността.
|
disabled_public_activity = Този потребител е изключил публичната видимост на дейността.
|
||||||
email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите
|
email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите
|
||||||
|
show_on_map = Показване на това място на картата
|
||||||
|
|
||||||
[home]
|
[home]
|
||||||
filter = Други филтри
|
filter = Други филтри
|
||||||
|
@ -1263,6 +1378,8 @@ SSHTitle = Име на SSH ключ
|
||||||
repo_name_been_taken = Името на хранилището вече е използвано.
|
repo_name_been_taken = Името на хранилището вече е използвано.
|
||||||
team_name_been_taken = Името на екипа вече е заето.
|
team_name_been_taken = Името на екипа вече е заето.
|
||||||
org_name_been_taken = Името на организацията вече е заето.
|
org_name_been_taken = Името на организацията вече е заето.
|
||||||
|
still_own_packages = Вашият акаунт притежава един или повече пакети, първо ги изтрийте.
|
||||||
|
still_own_repo = Вашият акаунт притежава едно или повече хранилища, първо ги изтрийте или прехвърлете.
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
close_issue = `затвори задача <a href="%[1]s">%[3]s#%[2]s</a>`
|
close_issue = `затвори задача <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
|
@ -1288,6 +1405,7 @@ publish_release = `публикува издание <a href="%[2]s"> "%[4]s" </
|
||||||
push_tag = изтласка маркер <a href="%[2]s">%[3]s</a> към <a href="%[1]s">%[4]s</a>
|
push_tag = изтласка маркер <a href="%[2]s">%[3]s</a> към <a href="%[1]s">%[4]s</a>
|
||||||
approve_pull_request = `одобри <a href="%[1]s">%[3]s#%[2]s</a>`
|
approve_pull_request = `одобри <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
reject_pull_request = `предложи промени за <a href="%[1]s">%[3]s#%[2]s</a>`
|
reject_pull_request = `предложи промени за <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
|
compare_branch = Сравняване
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
tab_openid = OpenID
|
tab_openid = OpenID
|
||||||
|
@ -1408,6 +1526,24 @@ component_loading_failed = Неуспешно зареждане на %s
|
||||||
contributors.what = приноси
|
contributors.what = приноси
|
||||||
recent_commits.what = скорошни подавания
|
recent_commits.what = скорошни подавания
|
||||||
component_loading = Зареждане на %s...
|
component_loading = Зареждане на %s...
|
||||||
|
component_loading_info = Това може да отнеме известно време…
|
||||||
|
|
||||||
[projects]
|
[projects]
|
||||||
type-1.display_name = Индивидуален проект
|
type-1.display_name = Индивидуален проект
|
||||||
|
|
||||||
|
|
||||||
|
[search]
|
||||||
|
no_results = Няма намерени съответстващи резултати.
|
||||||
|
team_kind = Търсене на екипи...
|
||||||
|
repo_kind = Търсене на хранилища...
|
||||||
|
org_kind = Търсене на организации...
|
||||||
|
user_kind = Търсене на потребители...
|
||||||
|
code_kind = Търсене на код...
|
||||||
|
commit_kind = Търсене на подавания...
|
||||||
|
project_kind = Търсене на проекти...
|
||||||
|
package_kind = Търсене на пакети...
|
||||||
|
search = Търсене...
|
||||||
|
|
||||||
|
[markup]
|
||||||
|
filepreview.lines = Редове от %[1]d до %[2]d в %[3]s
|
||||||
|
filepreview.line = Ред %[1]d в %[2]s
|
|
@ -73,11 +73,11 @@ all=Vše
|
||||||
sources=Zdrojové kódy
|
sources=Zdrojové kódy
|
||||||
mirrors=Zrcadla
|
mirrors=Zrcadla
|
||||||
collaborative=Spolupráce
|
collaborative=Spolupráce
|
||||||
forks=Rozštěpení
|
forks=Forky
|
||||||
|
|
||||||
activities=Aktivity
|
activities=Aktivity
|
||||||
pull_requests=Požadavky na sloučení
|
pull_requests=Požadavky na sloučení
|
||||||
issues=Úkoly
|
issues=Problémy
|
||||||
milestones=Milníky
|
milestones=Milníky
|
||||||
|
|
||||||
ok=OK
|
ok=OK
|
||||||
|
@ -147,7 +147,7 @@ confirm_delete_artifact = Opravdu chcete odstranit artefakt „%s“?
|
||||||
toggle_menu = Přepnout nabídku
|
toggle_menu = Přepnout nabídku
|
||||||
filter = Filtr
|
filter = Filtr
|
||||||
filter.is_fork = Forknuto
|
filter.is_fork = Forknuto
|
||||||
filter.not_fork = Není forkuto
|
filter.not_fork = Není forknuto
|
||||||
filter.is_mirror = Zrcadleno
|
filter.is_mirror = Zrcadleno
|
||||||
filter.is_template = Šablona
|
filter.is_template = Šablona
|
||||||
filter.not_template = Není šablona
|
filter.not_template = Není šablona
|
||||||
|
@ -156,7 +156,9 @@ filter.private = Soukromé
|
||||||
filter.is_archived = Archivováno
|
filter.is_archived = Archivováno
|
||||||
filter.not_mirror = Není zrcadleno
|
filter.not_mirror = Není zrcadleno
|
||||||
filter.not_archived = Není archivováno
|
filter.not_archived = Není archivováno
|
||||||
filter.clear = Vymazat filtr
|
filter.clear = Vymazat filtry
|
||||||
|
more_items = Další položky
|
||||||
|
invalid_data = Neplatná data: %v
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=Navigační lišta
|
navbar=Navigační lišta
|
||||||
|
@ -217,7 +219,7 @@ license_desc=Vše je na <a target="_blank" rel="noopener noreferrer" href="https
|
||||||
install=Instalace
|
install=Instalace
|
||||||
title=Počáteční konfigurace
|
title=Počáteční konfigurace
|
||||||
docker_helper=Pokud spouštíte Forgejo v Dockeru, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení.
|
docker_helper=Pokud spouštíte Forgejo v Dockeru, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení.
|
||||||
require_db_desc=Forgejo requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
require_db_desc=Forgejo vyžaduje MySQL, PostgreSQL, MSSQL, SQLite3 nebo TiDB (protokol MySQL).
|
||||||
db_title=Nastavení databáze
|
db_title=Nastavení databáze
|
||||||
db_type=Typ databáze
|
db_type=Typ databáze
|
||||||
host=Hostitel
|
host=Hostitel
|
||||||
|
@ -375,7 +377,7 @@ org_no_results=Nebyly nalezeny žádné odpovídající organizace.
|
||||||
code_no_results=Nebyl nalezen žádný zdrojový kód odpovídající hledanému výrazu.
|
code_no_results=Nebyl nalezen žádný zdrojový kód odpovídající hledanému výrazu.
|
||||||
code_search_results=Výsledky hledání pro „%s“
|
code_search_results=Výsledky hledání pro „%s“
|
||||||
code_last_indexed_at=Naposledy indexováno %s
|
code_last_indexed_at=Naposledy indexováno %s
|
||||||
relevant_repositories_tooltip=Repozitáře, které jsou rozštěpení nebo nemají žádné téma, ikonu a žádný popis jsou skryty.
|
relevant_repositories_tooltip=Repozitáře, které jsou forky nebo nemají žádné téma, žádnou ikonu a žádný popis, jsou skryty.
|
||||||
relevant_repositories=Zobrazují se pouze relevantní repositáře, <a href="%s">zobrazit nefiltrované výsledky</a>.
|
relevant_repositories=Zobrazují se pouze relevantní repositáře, <a href="%s">zobrazit nefiltrované výsledky</a>.
|
||||||
forks_one = %d fork
|
forks_one = %d fork
|
||||||
forks_few = %d forků
|
forks_few = %d forků
|
||||||
|
@ -486,8 +488,8 @@ reset_password.text=Pokud jste to byli vy, klikněte na následující odkaz pro
|
||||||
|
|
||||||
register_success=Registrace byla úspěšná
|
register_success=Registrace byla úspěšná
|
||||||
|
|
||||||
issue_assigned.pull=@%[1]s vás přiřadil/a k požadavku na natažení %[2]s repozitáři %[3]s.
|
issue_assigned.pull=@%[1]s vás přiřadil/a k žádosti o sloučení %[2]s v repozitáři %[3]s.
|
||||||
issue_assigned.issue=@%[1]s vás přiřadil/a k úkolu %[2]s repozitáři %[3]s.
|
issue_assigned.issue=@%[1]s vás přiřadil/a k problému %[2]s v repozitáři %[3]s.
|
||||||
|
|
||||||
issue.x_mentioned_you=<b>@%s</b> vás zmínil/a:
|
issue.x_mentioned_you=<b>@%s</b> vás zmínil/a:
|
||||||
issue.action.force_push=<b>%[1]s</b> vynutil/a nahrání <b>%[2]s</b> z %[3]s do %[4]s.
|
issue.action.force_push=<b>%[1]s</b> vynutil/a nahrání <b>%[2]s</b> z %[3]s do %[4]s.
|
||||||
|
@ -496,11 +498,11 @@ issue.action.push_n=<b>@%[1]s</b> nahrál/a %[3]d commity do %[2]s
|
||||||
issue.action.close=<b>@%[1]s</b> uzavřel/a #%[2]d.
|
issue.action.close=<b>@%[1]s</b> uzavřel/a #%[2]d.
|
||||||
issue.action.reopen=<b>@%[1]s</b> znovu otevřel/a #%[2]d.
|
issue.action.reopen=<b>@%[1]s</b> znovu otevřel/a #%[2]d.
|
||||||
issue.action.merge=<b>@%[1]s</b> sloučil/a #%[2]d do %[3]s.
|
issue.action.merge=<b>@%[1]s</b> sloučil/a #%[2]d do %[3]s.
|
||||||
issue.action.approve=<b>@%[1]s</b> schválil/a tento požadavek na natažení.
|
issue.action.approve=<b>@%[1]s</b> schválil/a tuto žádost o sloučení.
|
||||||
issue.action.reject=<b>@%[1]s</b> požadoval/a změny v tomto požadavku na natažení.
|
issue.action.reject=<b>@%[1]s</b> požaduje změny v této žádosti o sloučení.
|
||||||
issue.action.review=<b>@%[1]s</b> okomentoval/a tento požadavek na natažení.
|
issue.action.review=<b>@%[1]s</b> okomentoval/a tuto žádost o sloučení.
|
||||||
issue.action.review_dismissed=<b>@%[1]s</b> odmítl/a poslední kontrolu z %[2]s pro tento požadavek na natažení.
|
issue.action.review_dismissed=<b>@%[1]s</b> odmítl/a poslední kontrolu od %[2]s této žádosti o sloučení.
|
||||||
issue.action.ready_for_review=<b>@%[1]s</b> označil/a tento požadavek na natažení jako připravený ke kontrole.
|
issue.action.ready_for_review=<b>@%[1]s</b> označil/a tuto žádost o sloučení jako připravenou ke kontrole.
|
||||||
issue.action.new=<b>@%[1]s</b> vytvořil/a #%[2]d.
|
issue.action.new=<b>@%[1]s</b> vytvořil/a #%[2]d.
|
||||||
issue.in_tree_path=V %s:
|
issue.in_tree_path=V %s:
|
||||||
|
|
||||||
|
@ -722,7 +724,7 @@ comment_type_group_lock=Stav zámku
|
||||||
comment_type_group_review_request=Žádost o posouzení
|
comment_type_group_review_request=Žádost o posouzení
|
||||||
comment_type_group_pull_request_push=Přidané commity
|
comment_type_group_pull_request_push=Přidané commity
|
||||||
comment_type_group_project=Projekt
|
comment_type_group_project=Projekt
|
||||||
comment_type_group_issue_ref=Referenční číslo úkolu
|
comment_type_group_issue_ref=Referenční číslo problému
|
||||||
saved_successfully=Vaše nastavení bylo úspěšně uloženo.
|
saved_successfully=Vaše nastavení bylo úspěšně uloženo.
|
||||||
privacy=Soukromí
|
privacy=Soukromí
|
||||||
keep_activity_private=Skrýt aktivitu z profilové stránky
|
keep_activity_private=Skrýt aktivitu z profilové stránky
|
||||||
|
@ -811,7 +813,7 @@ gpg_token=Token
|
||||||
gpg_token_help=Podpis můžete vygenerovat pomocí:
|
gpg_token_help=Podpis můžete vygenerovat pomocí:
|
||||||
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
|
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
|
||||||
gpg_token_signature=Zakódovaný podpis GPG
|
gpg_token_signature=Zakódovaný podpis GPG
|
||||||
key_signature_gpg_placeholder=Začíná s „-----BEGIN PGP SIGNATURE-----“
|
key_signature_gpg_placeholder=Začíná textem „-----BEGIN PGP SIGNATURE-----“
|
||||||
verify_gpg_key_success=GPG klíč „%s“ byl ověřen.
|
verify_gpg_key_success=GPG klíč „%s“ byl ověřen.
|
||||||
ssh_key_verified=Ověřený klíč
|
ssh_key_verified=Ověřený klíč
|
||||||
ssh_key_verified_long=Klíč byl ověřen pomocí tokenu a může být použit k ověření commitů shodujících se s libovolnou vaší aktivovanou e-mailovou adresou pro tohoto uživatele.
|
ssh_key_verified_long=Klíč byl ověřen pomocí tokenu a může být použit k ověření commitů shodujících se s libovolnou vaší aktivovanou e-mailovou adresou pro tohoto uživatele.
|
||||||
|
@ -880,7 +882,7 @@ permissions_access_all=Vše (veřejné, soukromé a omezené)
|
||||||
select_permissions=Vyberte oprávnění
|
select_permissions=Vyberte oprávnění
|
||||||
permission_no_access=Bez přístupu
|
permission_no_access=Bez přístupu
|
||||||
permission_read=Přečtené
|
permission_read=Přečtené
|
||||||
permission_write=čtení i zápis
|
permission_write=Čtení a zápis
|
||||||
at_least_one_permission=Musíte vybrat alespoň jedno oprávnění pro vytvoření tokenu
|
at_least_one_permission=Musíte vybrat alespoň jedno oprávnění pro vytvoření tokenu
|
||||||
permissions_list=Oprávnění:
|
permissions_list=Oprávnění:
|
||||||
|
|
||||||
|
@ -931,7 +933,7 @@ scan_this_image=Naskenujte tento obrázek s vaší ověřovací aplikací:
|
||||||
or_enter_secret=Nebo zadejte tajný kód: %s
|
or_enter_secret=Nebo zadejte tajný kód: %s
|
||||||
then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci:
|
then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci:
|
||||||
passcode_invalid=Přístupový kód není platný. Zkuste to znovu.
|
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_enrolled=Ve vašem účtu bylo povoleno dvoufaktorové ověřování. Uložte si jednorázový obnovovací klíč (%s) na bezpečné místo, jelikož již nebude znovu zobrazen.
|
||||||
twofa_failed_get_secret=Nepodařilo se získat tajemství.
|
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.
|
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.
|
||||||
|
@ -957,7 +959,7 @@ repos_none=Nevlastníte žádné repozitáře.
|
||||||
|
|
||||||
delete_account=Odstranit svůj účet
|
delete_account=Odstranit svůj účet
|
||||||
delete_prompt=Tato operace natrvalo odstraní váš uživatelský účet. <strong>NELZE</strong> ji vrátit zpět.
|
delete_prompt=Tato operace natrvalo odstraní váš uživatelský účet. <strong>NELZE</strong> ji vrátit zpět.
|
||||||
delete_with_all_comments=Váš účet je mladší než %s. Aby se zabránilo fantomovým komentářům, všechny komentáře k úkolům/požadavkům na natažení budou smazány.
|
delete_with_all_comments=Váš účet je mladší než %s. Pro zabránění fantomovým komentářům budou společně s ním odstraněny všechny komentáře u problémů a ŽS.
|
||||||
confirm_delete_account=Potvrdit odstranění
|
confirm_delete_account=Potvrdit odstranění
|
||||||
delete_account_title=Odstranit uživatelský účet
|
delete_account_title=Odstranit uživatelský účet
|
||||||
delete_account_desc=Jste si jisti, že chcete trvale smazat tento účet?
|
delete_account_desc=Jste si jisti, že chcete trvale smazat tento účet?
|
||||||
|
@ -1026,7 +1028,7 @@ repo_lang=Jazyk
|
||||||
repo_gitignore_helper=Vyberte šablony .gitignore.
|
repo_gitignore_helper=Vyberte šablony .gitignore.
|
||||||
repo_gitignore_helper_desc=Vyberte soubory, které nechcete sledovat ze seznamu šablon pro běžné jazyky. Typické artefakty generované nástroji pro sestavení každého jazyka jsou ve výchozím stavu součástí .gitignore.
|
repo_gitignore_helper_desc=Vyberte soubory, které nechcete sledovat ze seznamu šablon pro běžné jazyky. Typické artefakty generované nástroji pro sestavení každého jazyka jsou ve výchozím stavu součástí .gitignore.
|
||||||
issue_labels=Štítky problémů
|
issue_labels=Štítky problémů
|
||||||
issue_labels_helper=Vyberte sadu štítků úkolů.
|
issue_labels_helper=Vyberte sadu štítků problémů.
|
||||||
license=Licence
|
license=Licence
|
||||||
license_helper=Vyberte licenční soubor.
|
license_helper=Vyberte licenční soubor.
|
||||||
license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">Zvolte licenci</a>
|
license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">Zvolte licenci</a>
|
||||||
|
@ -1044,7 +1046,7 @@ trust_model_helper_default=Výchozí: Použít výchozí model důvěry pro tuto
|
||||||
create_repo=Vytvořit repozitář
|
create_repo=Vytvořit repozitář
|
||||||
default_branch=Výchozí větev
|
default_branch=Výchozí větev
|
||||||
default_branch_label=výchozí
|
default_branch_label=výchozí
|
||||||
default_branch_helper=Výchozí větev je základní větev pro požadavky na natažení a commity kódu.
|
default_branch_helper=Výchozí větev je základní větev pro žádosti o sloučení a commity kódu.
|
||||||
mirror_prune=Vyčistit
|
mirror_prune=Vyčistit
|
||||||
mirror_prune_desc=Odstranit zastaralé reference na vzdálené sledování
|
mirror_prune_desc=Odstranit zastaralé reference na vzdálené sledování
|
||||||
mirror_interval=Interval zrcadlení (platné časové jednotky jsou „h“, „m“ a „s“). Nastavením na 0 zakážete periodickou synchronizaci. (Minimální interval: %s)
|
mirror_interval=Interval zrcadlení (platné časové jednotky jsou „h“, „m“ a „s“). Nastavením na 0 zakážete periodickou synchronizaci. (Minimální interval: %s)
|
||||||
|
@ -1065,7 +1067,7 @@ mirror_password_help=Změňte uživatelské jméno pro vymazání uloženého he
|
||||||
watchers=Sledující
|
watchers=Sledující
|
||||||
stargazers=Sledující
|
stargazers=Sledující
|
||||||
stars_remove_warning=Tímto odstraníte všechny hvězdičky z tohoto repozitáře.
|
stars_remove_warning=Tímto odstraníte všechny hvězdičky z tohoto repozitáře.
|
||||||
forks=Rozštěpení
|
forks=Forky
|
||||||
reactions_more=a %d dalších
|
reactions_more=a %d dalších
|
||||||
unit_disabled=Správce webu zakázal tuto sekci repozitáře.
|
unit_disabled=Správce webu zakázal tuto sekci repozitáře.
|
||||||
language_other=Jiný
|
language_other=Jiný
|
||||||
|
@ -1112,9 +1114,9 @@ template.one_item=Musíte vybrat alespoň jednu položku šablony
|
||||||
template.invalid=Musíte vybrat repositář šablony
|
template.invalid=Musíte vybrat repositář šablony
|
||||||
|
|
||||||
archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové problémy nebo žádosti o sloučení.
|
archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové problémy nebo žádosti o sloučení.
|
||||||
archive.title_date=Tento repositář byl archivován %s. Můžete zobrazit soubory a klonovat je, ale nemůžete nahrávat ani otevírat problémy nebo požadavky na natažení.
|
archive.title_date=Tento repozitář byl archivován %s. Můžete si prohlížet a klonovat soubory, ale nemůžete nahrávat ani otevírat problémy nebo žádosti o sloučení.
|
||||||
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat úkoly.
|
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat problémy.
|
||||||
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat požadavky na natažení.
|
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat žádosti o sloučení.
|
||||||
|
|
||||||
form.reach_limit_of_creation_1=Již jste dosáhli svůj limit %d repozitář.
|
form.reach_limit_of_creation_1=Již jste dosáhli svůj limit %d repozitář.
|
||||||
form.reach_limit_of_creation_n=Již jste dosáhli svůj limit %d repozitářů.
|
form.reach_limit_of_creation_n=Již jste dosáhli svůj limit %d repozitářů.
|
||||||
|
@ -1134,7 +1136,7 @@ migrate_items=Položky pro migrování
|
||||||
migrate_items_wiki=Wiki
|
migrate_items_wiki=Wiki
|
||||||
migrate_items_milestones=Milníky
|
migrate_items_milestones=Milníky
|
||||||
migrate_items_labels=Štítky
|
migrate_items_labels=Štítky
|
||||||
migrate_items_issues=Úkoly
|
migrate_items_issues=Problémy
|
||||||
migrate_items_pullrequests=Žádosti o sloučení
|
migrate_items_pullrequests=Žádosti o sloučení
|
||||||
migrate_items_merge_requests=Sloučit žádosti
|
migrate_items_merge_requests=Sloučit žádosti
|
||||||
migrate_items_releases=Vydání
|
migrate_items_releases=Vydání
|
||||||
|
@ -1178,12 +1180,12 @@ mirror_from=zrcadlo
|
||||||
forked_from=rozštěpen z
|
forked_from=rozštěpen z
|
||||||
generated_from=generováno z
|
generated_from=generováno z
|
||||||
fork_from_self=Nemůžete rozštěpit váš vlastní repozitář.
|
fork_from_self=Nemůžete rozštěpit váš vlastní repozitář.
|
||||||
fork_guest_user=Přihlaste se pro rozštěpení tohoto repozitáře.
|
fork_guest_user=Přihlaste se pro vytvoření forku tohoto repozitáře.
|
||||||
watch_guest_user=Pro sledování tohoto repozitáře se přihlaste.
|
watch_guest_user=Pro sledování tohoto repozitáře se přihlaste.
|
||||||
star_guest_user=Pro hodnocení tohoto repozitáře se přihlaste.
|
star_guest_user=Pro hodnocení tohoto repozitáře se přihlaste.
|
||||||
unwatch=Přestat sledovat
|
unwatch=Přestat sledovat
|
||||||
watch=Sledovat
|
watch=Sledovat
|
||||||
unstar=Odoblíbit
|
unstar=Zrušit oblíbení
|
||||||
star=Oblíbit
|
star=Oblíbit
|
||||||
fork=Rozštěpit
|
fork=Rozštěpit
|
||||||
download_archive=Stáhnout repozitář
|
download_archive=Stáhnout repozitář
|
||||||
|
@ -1207,7 +1209,7 @@ filter_branch_and_tag=Filtr pro větev nebo značku
|
||||||
find_tag=Najít značku
|
find_tag=Najít značku
|
||||||
branches=Větve
|
branches=Větve
|
||||||
tags=Značky
|
tags=Značky
|
||||||
issues=Úkoly
|
issues=Problémy
|
||||||
pulls=Žádosti o sloučení
|
pulls=Žádosti o sloučení
|
||||||
project_board=Projekty
|
project_board=Projekty
|
||||||
packages=Balíčky
|
packages=Balíčky
|
||||||
|
@ -1284,8 +1286,8 @@ editor.name_your_file=Pojmenujte váš soubor…
|
||||||
editor.filename_help=Přidejte adresář zapsáním jeho jména následovaného lomítkem („/“). Adresář odeberete stiskem backspace na začátku vstupního pole.
|
editor.filename_help=Přidejte adresář zapsáním jeho jména následovaného lomítkem („/“). Adresář odeberete stiskem backspace na začátku vstupního pole.
|
||||||
editor.or=nebo
|
editor.or=nebo
|
||||||
editor.cancel_lower=Zrušit
|
editor.cancel_lower=Zrušit
|
||||||
editor.commit_signed_changes=Odevzdat podepsané změny
|
editor.commit_signed_changes=Commitnout podepsané změny
|
||||||
editor.commit_changes=Odevzdat změny
|
editor.commit_changes=Commitnout změny
|
||||||
editor.add_tmpl=Přidat „<nazevsouboru>“
|
editor.add_tmpl=Přidat „<nazevsouboru>“
|
||||||
editor.add=Přidat %s
|
editor.add=Přidat %s
|
||||||
editor.update=Aktualizovat %s
|
editor.update=Aktualizovat %s
|
||||||
|
@ -1297,7 +1299,7 @@ editor.new_patch=Nová záplata
|
||||||
editor.commit_message_desc=Přidat volitelný rozšířený popis…
|
editor.commit_message_desc=Přidat volitelný rozšířený popis…
|
||||||
editor.signoff_desc=Přidat Signed-off-by podpis přispěvatele na konec zprávy o commitu.
|
editor.signoff_desc=Přidat Signed-off-by podpis přispěvatele na konec zprávy o commitu.
|
||||||
editor.commit_directly_to_this_branch=Odevzdat přímo do větve <strong class="branch-name">%s</strong>.
|
editor.commit_directly_to_this_branch=Odevzdat přímo do větve <strong class="branch-name">%s</strong>.
|
||||||
editor.create_new_branch=Vytvořit <strong>novou větev</strong> pro tento commit a spustit požadavek na natažení.
|
editor.create_new_branch=Vytvořit <strong>novou větev</strong> pro tento commit a vytvořit žádost o sloučení.
|
||||||
editor.create_new_branch_np=Vytvořte <strong>novou větev</strong> z tohoto commitu.
|
editor.create_new_branch_np=Vytvořte <strong>novou větev</strong> z tohoto commitu.
|
||||||
editor.propose_file_change=Navrhnout změnu souboru
|
editor.propose_file_change=Navrhnout změnu souboru
|
||||||
editor.new_branch_name=Pojmenujte novou větev pro tento commit
|
editor.new_branch_name=Pojmenujte novou větev pro tento commit
|
||||||
|
@ -1312,7 +1314,7 @@ editor.file_is_a_symlink=`„%s“ je symbolický odkaz. Symbolické odkazy nemo
|
||||||
editor.filename_is_a_directory=Jméno souboru „%s“ je již použito jako jméno adresáře v tomto repozitáři.
|
editor.filename_is_a_directory=Jméno souboru „%s“ je již použito jako jméno adresáře v tomto repozitáři.
|
||||||
editor.file_editing_no_longer_exists=Upravovaný soubor „%s“ již není součástí tohoto repozitáře.
|
editor.file_editing_no_longer_exists=Upravovaný soubor „%s“ již není součástí tohoto repozitáře.
|
||||||
editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není součástí tohoto repozitáře.
|
editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není součástí tohoto repozitáře.
|
||||||
editor.file_changed_while_editing=Obsah souboru byl změněn od doby, kdy jste začaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte zde</a>, abyste je zobrazili, nebo <strong>potvrďte změny ještě jednou</strong> pro jejich přepsání.
|
editor.file_changed_while_editing=Obsah souboru se od zahájení úprav změnil. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte sem</a> pro jejich zobrazení nebo <strong>proveďte commit změn ještě jednou</strong> pro jejich přepsání.
|
||||||
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
||||||
editor.commit_empty_file_header=Odevzdat prázdný soubor
|
editor.commit_empty_file_header=Odevzdat prázdný soubor
|
||||||
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
|
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
|
||||||
|
@ -1367,10 +1369,10 @@ commitstatus.pending=Čekající
|
||||||
commitstatus.success=Úspěch
|
commitstatus.success=Úspěch
|
||||||
|
|
||||||
ext_issues=Přístup k externím problémům
|
ext_issues=Přístup k externím problémům
|
||||||
ext_issues.desc=Odkaz na externí systém úkolů.
|
ext_issues.desc=Odkaz na externí systém problémů.
|
||||||
|
|
||||||
projects=Projekty
|
projects=Projekty
|
||||||
projects.desc=Spravovat úkoly a požadavky na natažení na projektových nástěnkách.
|
projects.desc=Spravovat problémy a žádosti o sloučení na projektových nástěnkách.
|
||||||
projects.description=Popis (volitelné)
|
projects.description=Popis (volitelné)
|
||||||
projects.description_placeholder=Popis
|
projects.description_placeholder=Popis
|
||||||
projects.create=Vytvořit projekt
|
projects.create=Vytvořit projekt
|
||||||
|
@ -1379,14 +1381,14 @@ projects.new=Nový projekt
|
||||||
projects.new_subheader=Koordinujte, sledujte a aktualizujte svou práci na jednom místě, aby projekty zůstaly transparentní a v plánu.
|
projects.new_subheader=Koordinujte, sledujte a aktualizujte svou práci na jednom místě, aby projekty zůstaly transparentní a v plánu.
|
||||||
projects.create_success=Projekt „%s“ byl vytvořen.
|
projects.create_success=Projekt „%s“ byl vytvořen.
|
||||||
projects.deletion=Odstranit projekt
|
projects.deletion=Odstranit projekt
|
||||||
projects.deletion_desc=Odstranění projektu jej odstraní ze všech souvisejících úkolů. Pokračovat?
|
projects.deletion_desc=Odstraněním projektu jej odstraníte ze všech souvisejících problémů. Pokračovat?
|
||||||
projects.deletion_success=Projekt byl odstraněn.
|
projects.deletion_success=Projekt byl odstraněn.
|
||||||
projects.edit=Upravit projekt
|
projects.edit=Upravit projekt
|
||||||
projects.edit_subheader=Projekty organizují úkoly a sledují pokrok.
|
projects.edit_subheader=Projekty organizují problémy a sledují pokrok.
|
||||||
projects.modify=Upravit projekt
|
projects.modify=Upravit projekt
|
||||||
projects.edit_success=Projekt „%s“ byl aktualizován.
|
projects.edit_success=Projekt „%s“ byl aktualizován.
|
||||||
projects.type.none=Žádný
|
projects.type.none=Žádný
|
||||||
projects.type.basic_kanban=Základní Kanban
|
projects.type.basic_kanban=Základní kanban
|
||||||
projects.type.bug_triage=Třídění chyb
|
projects.type.bug_triage=Třídění chyb
|
||||||
projects.template.desc=Šablona
|
projects.template.desc=Šablona
|
||||||
projects.template.desc_helper=Začněte vybráním šablony projektu
|
projects.template.desc_helper=Začněte vybráním šablony projektu
|
||||||
|
@ -1401,7 +1403,7 @@ projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekat
|
||||||
projects.column.unset_default=Zrušit nastavení jako výchozí
|
projects.column.unset_default=Zrušit nastavení jako výchozí
|
||||||
projects.column.unset_default_desc=Zrušit nastavení tohoto sloupce jako výchozí
|
projects.column.unset_default_desc=Zrušit nastavení tohoto sloupce jako výchozí
|
||||||
projects.column.delete=Odstranit sloupec
|
projects.column.delete=Odstranit sloupec
|
||||||
projects.column.deletion_desc=Odstranění projektového sloupce přesune všechny související problémy do kategorie „Nezařazené“. Pokračovat?
|
projects.column.deletion_desc=Odstranění projektového sloupce přesune všechny související problémy do výchozího sloupce. Pokračovat?
|
||||||
projects.column.color=Barva
|
projects.column.color=Barva
|
||||||
projects.open=Otevřít
|
projects.open=Otevřít
|
||||||
projects.close=Zavřít
|
projects.close=Zavřít
|
||||||
|
@ -1419,7 +1421,7 @@ issues.filter_reviewers=Filtrovat posuzovatele
|
||||||
issues.new=Nový problém
|
issues.new=Nový problém
|
||||||
issues.new.title_empty=Název nesmí být prázdný
|
issues.new.title_empty=Název nesmí být prázdný
|
||||||
issues.new.labels=Štítky
|
issues.new.labels=Štítky
|
||||||
issues.new.no_label=Bez štítku
|
issues.new.no_label=Bez štítků
|
||||||
issues.new.clear_labels=Zrušit štítky
|
issues.new.clear_labels=Zrušit štítky
|
||||||
issues.new.projects=Projekty
|
issues.new.projects=Projekty
|
||||||
issues.new.clear_projects=Vymazat projekty
|
issues.new.clear_projects=Vymazat projekty
|
||||||
|
@ -1439,7 +1441,7 @@ issues.new.no_reviewers=Žádní posuzovatelé
|
||||||
issues.choose.get_started=Začínáme
|
issues.choose.get_started=Začínáme
|
||||||
issues.choose.open_external_link=Otevřít
|
issues.choose.open_external_link=Otevřít
|
||||||
issues.choose.blank=Výchozí
|
issues.choose.blank=Výchozí
|
||||||
issues.choose.blank_about=Vytvořit úkol z výchozí šablony.
|
issues.choose.blank_about=Vytvořit problém z výchozí šablony.
|
||||||
issues.choose.ignore_invalid_templates=Neplatné šablony byly ignorovány
|
issues.choose.ignore_invalid_templates=Neplatné šablony byly ignorovány
|
||||||
issues.choose.invalid_templates=%v nalezených neplatných šablon
|
issues.choose.invalid_templates=%v nalezených neplatných šablon
|
||||||
issues.choose.invalid_config=Nastavení problému obsahuje chyby:
|
issues.choose.invalid_config=Nastavení problému obsahuje chyby:
|
||||||
|
@ -1494,7 +1496,7 @@ issues.filter_assginee_no_assignee=Bez zpracovatele
|
||||||
issues.filter_poster=Autor
|
issues.filter_poster=Autor
|
||||||
issues.filter_poster_no_select=Všichni autoři
|
issues.filter_poster_no_select=Všichni autoři
|
||||||
issues.filter_type=Typ
|
issues.filter_type=Typ
|
||||||
issues.filter_type.all_issues=Všechny úkoly
|
issues.filter_type.all_issues=Všechny problémy
|
||||||
issues.filter_type.assigned_to_you=Přiřazené vám
|
issues.filter_type.assigned_to_you=Přiřazené vám
|
||||||
issues.filter_type.created_by_you=Vytvořené vámi
|
issues.filter_type.created_by_you=Vytvořené vámi
|
||||||
issues.filter_type.mentioning_you=Zmiňující vás
|
issues.filter_type.mentioning_you=Zmiňující vás
|
||||||
|
@ -1511,8 +1513,8 @@ issues.filter_sort.nearduedate=Nejbližší datum dokončení
|
||||||
issues.filter_sort.farduedate=Nejvzdálenější datum dokončení
|
issues.filter_sort.farduedate=Nejvzdálenější datum dokončení
|
||||||
issues.filter_sort.moststars=Nejvíce hvězdiček
|
issues.filter_sort.moststars=Nejvíce hvězdiček
|
||||||
issues.filter_sort.feweststars=Nejméně hvězdiček
|
issues.filter_sort.feweststars=Nejméně hvězdiček
|
||||||
issues.filter_sort.mostforks=Nejvíce rozštěpení
|
issues.filter_sort.mostforks=Nejvíce forků
|
||||||
issues.filter_sort.fewestforks=Nejméně rozštěpení
|
issues.filter_sort.fewestforks=Nejméně forků
|
||||||
issues.keyword_search_unavailable=Hledání podle klíčového slova není momentálně dostupné. Obraťte se na správce webu.
|
issues.keyword_search_unavailable=Hledání podle klíčového slova není momentálně dostupné. Obraťte se na správce webu.
|
||||||
issues.action_open=Otevřít
|
issues.action_open=Otevřít
|
||||||
issues.action_close=Zavřít
|
issues.action_close=Zavřít
|
||||||
|
@ -1531,8 +1533,8 @@ issues.opened_by_fake=otevřeno %[1]s uživatelem %[2]s
|
||||||
issues.closed_by_fake=od %[2]s byl uzavřen %[1]s
|
issues.closed_by_fake=od %[2]s byl uzavřen %[1]s
|
||||||
issues.previous=Předchozí
|
issues.previous=Předchozí
|
||||||
issues.next=Další
|
issues.next=Další
|
||||||
issues.open_title=otevřený
|
issues.open_title=Otevřeno
|
||||||
issues.closed_title=zavřený
|
issues.closed_title=Uzavřeno
|
||||||
issues.draft_title=Koncept
|
issues.draft_title=Koncept
|
||||||
issues.num_comments_1=%d komentář
|
issues.num_comments_1=%d komentář
|
||||||
issues.num_comments=%d komentářů
|
issues.num_comments=%d komentářů
|
||||||
|
@ -1551,15 +1553,15 @@ issues.close_comment_issue=Okomentovat a zavřít
|
||||||
issues.reopen_issue=Znovuotevřít
|
issues.reopen_issue=Znovuotevřít
|
||||||
issues.reopen_comment_issue=Okomentovat a znovu otevřít
|
issues.reopen_comment_issue=Okomentovat a znovu otevřít
|
||||||
issues.create_comment=Okomentovat
|
issues.create_comment=Okomentovat
|
||||||
issues.closed_at=`uzavřel/a tento úkol <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.closed_at=`uzavřel/a tento problém <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.reopened_at=`znovuotevřel/a tento úkol <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.reopened_at=`znovu otevřel/a tento problém <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.commit_ref_at=`odkázal na tento úkol z commitu <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.commit_ref_at=`odkázal/a na tento problém z commitu <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_issue_from=`<a href="%[3]s">odkazoval/a na tento úkol %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_issue_from=`<a href="%[3]s">odkázal/a na tento problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_pull_from=`<a href="%[3]s">odkazoval/a na tento požadavek na natažení %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_pull_from=`<a href="%[3]s">odkázal/a na tuto žádost o sloučení %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_closing_from=`<a href="%[3]s">odkazoval/a na požadavek na natažení %[4]s, který uzavře tento úkol</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_closing_from=`<a href="%[3]s">odkazoval/a na žádost o sloučení %[4]s, která uzavře tento problém</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_reopening_from=`<a href="%[3]s">odkazoval/a na požadavek na natažení %[4]s, který znovu otevře tento úkol</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_reopening_from=`<a href="%[3]s">odkazoval/a na žádost o sloučení %[4]s, která znovu otevře tento problém</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_closed_from=`<a href="%[3]s">uzavřel/a tento úkol %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_closed_from=`<a href="%[3]s">uzavřel/a tento problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_reopened_from=`<a href="%[3]s">znovu otevřel/a tento úkol %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.ref_reopened_from=`<a href="%[3]s">znovu otevřel/a tento problém %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
issues.ref_from=`z %[1]s`
|
issues.ref_from=`z %[1]s`
|
||||||
issues.author=Autor
|
issues.author=Autor
|
||||||
issues.author_helper=Tento uživatel je autor.
|
issues.author_helper=Tento uživatel je autor.
|
||||||
|
@ -1573,7 +1575,7 @@ issues.role.first_time_contributor_helper=Toto je první příspěvek tohoto už
|
||||||
issues.role.contributor=Přispěvatel
|
issues.role.contributor=Přispěvatel
|
||||||
issues.role.contributor_helper=Tento uživatel již dříve přispíval do repozitáře.
|
issues.role.contributor_helper=Tento uživatel již dříve přispíval do repozitáře.
|
||||||
issues.re_request_review=Znovu požádat o posouzení
|
issues.re_request_review=Znovu požádat o posouzení
|
||||||
issues.is_stale=Od tohoto posouzení došlo ke změnám v tomto požadavku na natažení
|
issues.is_stale=Od tohoto posouzení došlo v této žádosti ke změnám
|
||||||
issues.remove_request_review=Odstranit žádost o posouzení
|
issues.remove_request_review=Odstranit žádost o posouzení
|
||||||
issues.remove_request_review_block=Nelze odstranit žádost o posouzení
|
issues.remove_request_review_block=Nelze odstranit žádost o posouzení
|
||||||
issues.dismiss_review=Zamítnout posouzení
|
issues.dismiss_review=Zamítnout posouzení
|
||||||
|
@ -1592,18 +1594,18 @@ issues.label_archive_tooltip=Archivované štítky jsou ve výchozím nastavení
|
||||||
issues.label_exclusive_desc=Pojmenujte štítek <code>rozsah/položka</code>, aby se stal vzájemně exkluzivním s jinými štítky <code>rozsah/</code>.
|
issues.label_exclusive_desc=Pojmenujte štítek <code>rozsah/položka</code>, aby se stal vzájemně exkluzivním s jinými štítky <code>rozsah/</code>.
|
||||||
issues.label_exclusive_warning=Jakékoliv protichůdné rozsahy štítků budou odstraněny při úpravě štítků u úkolů nebo u požadavku na natažení.
|
issues.label_exclusive_warning=Jakékoliv protichůdné rozsahy štítků budou odstraněny při úpravě štítků u úkolů nebo u požadavku na natažení.
|
||||||
issues.label_count=%d štítků
|
issues.label_count=%d štítků
|
||||||
issues.label_open_issues=%d otevřených úkolů
|
issues.label_open_issues=%d otevřených problémů / žádostí o sloučení
|
||||||
issues.label_edit=Upravit
|
issues.label_edit=Upravit
|
||||||
issues.label_delete=Smazat
|
issues.label_delete=Smazat
|
||||||
issues.label_modify=Upravit štítek
|
issues.label_modify=Upravit štítek
|
||||||
issues.label_deletion=Odstranit štítek
|
issues.label_deletion=Odstranit štítek
|
||||||
issues.label_deletion_desc=Odstranění štítku jej smaže ze všech úkolů. Pokračovat?
|
issues.label_deletion_desc=Odstraněním štítku jej odeberete ze všech problémů. Pokračovat?
|
||||||
issues.label_deletion_success=Štítek byl odstraněn.
|
issues.label_deletion_success=Štítek byl odstraněn.
|
||||||
issues.label.filter_sort.alphabetically=Od začátku abecedy
|
issues.label.filter_sort.alphabetically=Od začátku abecedy
|
||||||
issues.label.filter_sort.reverse_alphabetically=Od konce abecedy
|
issues.label.filter_sort.reverse_alphabetically=Od konce abecedy
|
||||||
issues.label.filter_sort.by_size=Nejmenší velikost
|
issues.label.filter_sort.by_size=Nejmenší velikost
|
||||||
issues.label.filter_sort.reverse_by_size=Největší velikost
|
issues.label.filter_sort.reverse_by_size=Největší velikost
|
||||||
issues.num_participants=%d účastníků
|
issues.num_participants_few=%d účastníků
|
||||||
issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
|
issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
|
||||||
issues.attachment.download=`Klikněte pro stažení „%s“`
|
issues.attachment.download=`Klikněte pro stažení „%s“`
|
||||||
issues.subscribe=Odebírat
|
issues.subscribe=Odebírat
|
||||||
|
@ -1614,9 +1616,9 @@ issues.pin_comment=připnul/a tento %s
|
||||||
issues.unpin_comment=odepnul/a tento %s
|
issues.unpin_comment=odepnul/a tento %s
|
||||||
issues.lock=Uzamknout konverzaci
|
issues.lock=Uzamknout konverzaci
|
||||||
issues.unlock=Odemknout konverzaci
|
issues.unlock=Odemknout konverzaci
|
||||||
issues.lock.unknown_reason=Úkol nelze z neznámého důvodu uzamknout.
|
issues.lock.unknown_reason=Problém nelze z neznámého důvodu uzamknout.
|
||||||
issues.lock_duplicate=Úkol nemůže být uzamčený dvakrát.
|
issues.lock_duplicate=Problém nemůže být uzamčený dvakrát.
|
||||||
issues.unlock_error=Nelze odemknout úkol, který je uzamčený.
|
issues.unlock_error=Nelze odemknout problém, který není uzamčený.
|
||||||
issues.lock_with_reason=uzamkl/a jako <strong>%s</strong> a omezil/a konverzaci na spolupracovníky %s
|
issues.lock_with_reason=uzamkl/a jako <strong>%s</strong> a omezil/a konverzaci na spolupracovníky %s
|
||||||
issues.lock_no_reason=uzamkl/a a omezil/a konverzaci na spolupracovníky %s
|
issues.lock_no_reason=uzamkl/a a omezil/a konverzaci na spolupracovníky %s
|
||||||
issues.unlock_comment=odemkl/a tuto konverzaci %s
|
issues.unlock_comment=odemkl/a tuto konverzaci %s
|
||||||
|
@ -1624,22 +1626,22 @@ issues.lock_confirm=Uzamknout
|
||||||
issues.unlock_confirm=Odemknout
|
issues.unlock_confirm=Odemknout
|
||||||
issues.lock.notice_1=- Další uživatelé nemohou komentovat tento problém.
|
issues.lock.notice_1=- Další uživatelé nemohou komentovat tento problém.
|
||||||
issues.lock.notice_2=- Vy a ostatní spolupracovníci s přístupem k tomuto repozitáři můžete stále přidávat komentáře, které ostatní uvidí.
|
issues.lock.notice_2=- Vy a ostatní spolupracovníci s přístupem k tomuto repozitáři můžete stále přidávat komentáře, které ostatní uvidí.
|
||||||
issues.lock.notice_3=- V budoucnu budete moci vždy znovu tento úkol odemknout.
|
issues.lock.notice_3=- Vždy budete moci tento problém znovu odemknout.
|
||||||
issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento úkol.
|
issues.unlock.notice_1=- Všichni budou moci znovu komentovat tento problém.
|
||||||
issues.unlock.notice_2=- V budoucnu budete moci vždy znovu tento úkol uzamknout.
|
issues.unlock.notice_2=- Vždy budete moci tento problém znovu uzamknout.
|
||||||
issues.lock.reason=Důvod pro uzamčení
|
issues.lock.reason=Důvod pro uzamčení
|
||||||
issues.lock.title=Uzamknout konverzaci u tohoto úkolu.
|
issues.lock.title=Uzamknout konverzaci u tohoto problému.
|
||||||
issues.unlock.title=Odemknout konverzaci u tohoto úkolu.
|
issues.unlock.title=Odemknout konverzaci u tohoto problému.
|
||||||
issues.comment_on_locked=Nemůžete komentovat uzamčený úkol.
|
issues.comment_on_locked=Nemůžete komentovat uzamčený problém.
|
||||||
issues.delete=Smazat
|
issues.delete=Smazat
|
||||||
issues.delete.title=Smazat tento úkol?
|
issues.delete.title=Smazat tento problém?
|
||||||
issues.delete.text=Opravdu chcete tento úkol smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.)
|
issues.delete.text=Opravdu chcete smazat tento problém? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření)
|
||||||
issues.tracker=Sledování času
|
issues.tracker=Sledování času
|
||||||
issues.start_tracking_short=Spustit časovač
|
issues.start_tracking_short=Spustit časovač
|
||||||
issues.start_tracking=Spustit sledování času
|
issues.start_tracking=Spustit sledování času
|
||||||
issues.start_tracking_history=`započal/a práci %s`
|
issues.start_tracking_history=`započal/a práci %s`
|
||||||
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu
|
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto problému
|
||||||
issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!`
|
issues.tracking_already_started=`Sledování času jste již spustili u <a href="%s">jiného problému</a>!`
|
||||||
issues.stop_tracking=Zastavit časovač
|
issues.stop_tracking=Zastavit časovač
|
||||||
issues.stop_tracking_history=`ukončil/a práci %s`
|
issues.stop_tracking_history=`ukončil/a práci %s`
|
||||||
issues.cancel_tracking=Zahodit
|
issues.cancel_tracking=Zahodit
|
||||||
|
@ -1686,27 +1688,27 @@ issues.dependency.remove=Odstranit
|
||||||
issues.dependency.remove_info=Odstranit tuto závislost
|
issues.dependency.remove_info=Odstranit tuto závislost
|
||||||
issues.dependency.added_dependency=`přidal/a novou závislost %s`
|
issues.dependency.added_dependency=`přidal/a novou závislost %s`
|
||||||
issues.dependency.removed_dependency=`odstranil/a závislost %s`
|
issues.dependency.removed_dependency=`odstranil/a závislost %s`
|
||||||
issues.dependency.pr_closing_blockedby=Uzavření tohoto požadavku na natažení je blokováno následujícími úkoly
|
issues.dependency.pr_closing_blockedby=Uzavření této žádosti o sloučení je blokováno následujícími problémy
|
||||||
issues.dependency.issue_closing_blockedby=Uzavření tohoto úkolu je blokováno následujícími úkoly
|
issues.dependency.issue_closing_blockedby=Uzavření tohoto problému je blokováno následujícími problémy
|
||||||
issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů
|
issues.dependency.issue_close_blocks=Tento problém blokuje uzavření následujících problémů
|
||||||
issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů
|
issues.dependency.pr_close_blocks=Tato žádost o sloučení blokuje uzavření následujících problémů
|
||||||
issues.dependency.issue_close_blocked=Musíte zavřít všechny úkoly, které blokují tento úkol, aby jej bylo možné zavřít.
|
issues.dependency.issue_close_blocked=Aby bylo možné uzavřít tento problém, musíte uzavřít všechny ostatní problémy, které jej blokují.
|
||||||
issues.dependency.issue_batch_close_blocked=Nelze uzavřít úkoly, které jste vybrali, protože úkol #%d má stále otevřené závislosti
|
issues.dependency.issue_batch_close_blocked=Nelze uzavřít úkoly, které jste vybrali, protože úkol #%d má stále otevřené závislosti
|
||||||
issues.dependency.pr_close_blocked=Musíte zavřít všechny úkoly, které blokují tento požadavek na natažení, aby jej bylo možné sloučit.
|
issues.dependency.pr_close_blocked=Aby bylo možné sloučit tuto žádost, musíte uzavřít všechny problémy, které ji blokují.
|
||||||
issues.dependency.blocks_short=Blokuje
|
issues.dependency.blocks_short=Blokuje
|
||||||
issues.dependency.blocked_by_short=Závisí na
|
issues.dependency.blocked_by_short=Závisí na
|
||||||
issues.dependency.remove_header=Odstranit závislost
|
issues.dependency.remove_header=Odstranit závislost
|
||||||
issues.dependency.issue_remove_text=Tímto krokem odeberete závislost z úkolu. Pokračovat?
|
issues.dependency.issue_remove_text=Tímto krokem odeberete závislost z tohoto problému. Pokračovat?
|
||||||
issues.dependency.pr_remove_text=Tímto krokem odeberete závislost z požadavku na natažení. Pokračovat?
|
issues.dependency.pr_remove_text=Tímto krokem odeberete závislost z této žádosti o sloučení. Pokračovat?
|
||||||
issues.dependency.setting=Povolit závislosti pro problémy a žádosti o sloučení
|
issues.dependency.setting=Povolit závislosti pro problémy a žádosti o sloučení
|
||||||
issues.dependency.add_error_same_issue=Úkol nemůže záviset sám na sobě.
|
issues.dependency.add_error_same_issue=Problém nemůže záviset sám na sobě.
|
||||||
issues.dependency.add_error_dep_issue_not_exist=Související úkol neexistuje.
|
issues.dependency.add_error_dep_issue_not_exist=Závislý problém neexistuje.
|
||||||
issues.dependency.add_error_dep_not_exist=Závislost neexistuje.
|
issues.dependency.add_error_dep_not_exist=Závislost neexistuje.
|
||||||
issues.dependency.add_error_dep_exists=Závislost již existuje.
|
issues.dependency.add_error_dep_exists=Závislost již existuje.
|
||||||
issues.dependency.add_error_cannot_create_circular=Nemůžete vytvořit závislost dvou úkolů, které se vzájemně blokují.
|
issues.dependency.add_error_cannot_create_circular=Nelze vytvořit závislost dvou problémů, které se vzájemně blokují.
|
||||||
issues.dependency.add_error_dep_not_same_repo=Oba úkoly musí být ve stejném repozitáři.
|
issues.dependency.add_error_dep_not_same_repo=Oba problémy musí být ve stejném repozitáři.
|
||||||
issues.review.self.approval=Nemůžete schválit svůj požadavek na natažení.
|
issues.review.self.approval=Nemůžete schválit vlastní žádost o sloučení.
|
||||||
issues.review.self.rejection=Nemůžete požadovat změny ve svém vlastním požadavku na natažení.
|
issues.review.self.rejection=Nemůžete požadovat změny ve své vlastní žádosti o sloučení.
|
||||||
issues.review.approve=schválil/a tyto změny %s
|
issues.review.approve=schválil/a tyto změny %s
|
||||||
issues.review.comment=posoudil/a %s
|
issues.review.comment=posoudil/a %s
|
||||||
issues.review.dismissed=zamítl/a posouzení uživatele %s %s
|
issues.review.dismissed=zamítl/a posouzení uživatele %s %s
|
||||||
|
@ -1746,14 +1748,14 @@ issues.reference_link=Reference: %s
|
||||||
compare.compare_base=základní
|
compare.compare_base=základní
|
||||||
compare.compare_head=porovnat
|
compare.compare_head=porovnat
|
||||||
|
|
||||||
pulls.desc=Povolit požadavky na natažení a posuzování kódu.
|
pulls.desc=Povolit žádosti o sloučení a posuzování kódu.
|
||||||
pulls.new=Nová žádost o sloučení
|
pulls.new=Nová žádost o sloučení
|
||||||
pulls.view=Zobrazit žádost o sloučení
|
pulls.view=Zobrazit žádost o sloučení
|
||||||
pulls.compare_changes=Nová žádost o sloučení
|
pulls.compare_changes=Nová žádost o sloučení
|
||||||
pulls.allow_edits_from_maintainers=Povolit úpravy od správců
|
pulls.allow_edits_from_maintainers=Povolit úpravy od správců
|
||||||
pulls.allow_edits_from_maintainers_desc=Uživatelé s přístupem k zápisu do základní větve mohou také nahrávat do této větve
|
pulls.allow_edits_from_maintainers_desc=Uživatelé s přístupem k zápisu do základní větve mohou také nahrávat do této větve
|
||||||
pulls.allow_edits_from_maintainers_err=Aktualizace se nezdařila
|
pulls.allow_edits_from_maintainers_err=Aktualizace se nezdařila
|
||||||
pulls.compare_changes_desc=Vyberte větev pro sloučení a větev pro natažení.
|
pulls.compare_changes_desc=Vyberte větev pro sloučení a větev, ze které provést pull.
|
||||||
pulls.has_viewed_file=Zobrazeno
|
pulls.has_viewed_file=Zobrazeno
|
||||||
pulls.has_changed_since_last_review=Změněno od vašeho posledního posouzení
|
pulls.has_changed_since_last_review=Změněno od vašeho posledního posouzení
|
||||||
pulls.viewed_files_label=%[1]d / %[2]d souborů zobrazeno
|
pulls.viewed_files_label=%[1]d / %[2]d souborů zobrazeno
|
||||||
|
@ -1772,10 +1774,10 @@ pulls.showing_specified_commit_range=Zobrazují se pouze změny mezi %[1]s..%[2]
|
||||||
pulls.select_commit_hold_shift_for_range=Vyberte commit. Podržte klávesu shift + klepněte pro výběr rozsahu
|
pulls.select_commit_hold_shift_for_range=Vyberte commit. Podržte klávesu shift + klepněte pro výběr rozsahu
|
||||||
pulls.review_only_possible_for_full_diff=Posouzení je možné pouze při zobrazení plného rozlišení
|
pulls.review_only_possible_for_full_diff=Posouzení je možné pouze při zobrazení plného rozlišení
|
||||||
pulls.filter_changes_by_commit=Filtrovat podle commitu
|
pulls.filter_changes_by_commit=Filtrovat podle commitu
|
||||||
pulls.nothing_to_compare=Tyto větve jsou stejné. Není potřeba vytvářet požadavek na natažení.
|
pulls.nothing_to_compare=Tyto větve jsou stejné. Není třeba vytvářet žádost o sloučení.
|
||||||
pulls.nothing_to_compare_have_tag=Vybraná větev/značka je stejná.
|
pulls.nothing_to_compare_have_tag=Vybraná větev/značka je stejná.
|
||||||
pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tento požadavek na natažení bude prázdný.
|
pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tato žádost o sloučení bude prázdná.
|
||||||
pulls.has_pull_request=`Požadavek na natažení mezi těmito větvemi již existuje: <a href="%[1]s">%[2]s#%[3]d</a>`
|
pulls.has_pull_request=`Žádost o sloučení mezi těmito větvemi již existuje: <a href="%[1]s">%[2]s#%[3]d</a>`
|
||||||
pulls.create=Vytvořit žádost o sloučení
|
pulls.create=Vytvořit žádost o sloučení
|
||||||
pulls.title_desc_few=chce sloučit %[1]d commity z větve <code>%[2]s</code> do <code id="branch_target">%[3]s</code>
|
pulls.title_desc_few=chce sloučit %[1]d commity z větve <code>%[2]s</code> do <code id="branch_target">%[3]s</code>
|
||||||
pulls.merged_title_desc_few=sloučil %[1]d commity z větve <code>%[2]s</code> do větve <code>%[3]s</code> před %[4]s
|
pulls.merged_title_desc_few=sloučil %[1]d commity z větve <code>%[2]s</code> do větve <code>%[3]s</code> před %[4]s
|
||||||
|
@ -1783,35 +1785,35 @@ pulls.change_target_branch_at=`změnil/a cílovou větev z <b>%s</b> na <b>%s</b
|
||||||
pulls.tab_conversation=Konverzace
|
pulls.tab_conversation=Konverzace
|
||||||
pulls.tab_commits=Commity
|
pulls.tab_commits=Commity
|
||||||
pulls.tab_files=Změněné soubory
|
pulls.tab_files=Změněné soubory
|
||||||
pulls.reopen_to_merge=Prosíme, otevřete znovu tento požadavek na natažení, aby se provedlo sloučení.
|
pulls.reopen_to_merge=Otevřete znovu tuto žádost pro provedení sloučení.
|
||||||
pulls.cant_reopen_deleted_branch=Tento požadavek na natažení nemůže být znovu otevřen protože větev byla smazána.
|
pulls.cant_reopen_deleted_branch=Tuto žádost o sloučení nelze znovu otevřít, protože větev byla smazána.
|
||||||
pulls.merged=Sloučený
|
pulls.merged=Sloučený
|
||||||
pulls.merged_success=Požadavek na natažení byl úspěšně sloučen a uzavřen
|
pulls.merged_success=Žádost byla úspěšně sloučena a uzavřena
|
||||||
pulls.closed=Požadavek na natažení uzavřen
|
pulls.closed=Žádost o sloučení uzavřena
|
||||||
pulls.manually_merged=Sloučeno ručně
|
pulls.manually_merged=Sloučeno ručně
|
||||||
pulls.merged_info_text=Větev %s může být nyní odstraněna.
|
pulls.merged_info_text=Větev %s může být nyní odstraněna.
|
||||||
pulls.is_closed=Požadavek na natažení byl uzavřen.
|
pulls.is_closed=Žádost o sloučení byla uzavřena.
|
||||||
pulls.title_wip_desc=`<a href="#">Začněte název s <strong>%s</strong></a> a zamezíte tak nechtěnému sloučení požadavku na natažení.`
|
pulls.title_wip_desc=`<a href="#">Začněte název textem <strong>%s</strong></a> pro zamezení nechtěnému sloučení žádosti.`
|
||||||
pulls.cannot_merge_work_in_progress=Tento požadavek na natažení je označen jako probíhající práce.
|
pulls.cannot_merge_work_in_progress=Tato žádost o slolučení je označena jako rozpracovaná.
|
||||||
pulls.still_in_progress=Stále probíhá?
|
pulls.still_in_progress=Stále probíhá?
|
||||||
pulls.add_prefix=Přidat prefix <strong>%s</strong>
|
pulls.add_prefix=Přidat prefix <strong>%s</strong>
|
||||||
pulls.remove_prefix=Odstranit prefix <strong>%s</strong>
|
pulls.remove_prefix=Odstranit prefix <strong>%s</strong>
|
||||||
pulls.data_broken=Tento požadavek na natažení je rozbitý kvůli chybějícím informacím o rozštěpení.
|
pulls.data_broken=Tato žádost o sloučení je rozbitá kvůli chybějícím informacím o forku.
|
||||||
pulls.files_conflicted=Tento požadavek na natažení obsahuje změny, které kolidují s cílovou větví.
|
pulls.files_conflicted=Tato žádost o sloučení obsahuje změny, které jsou v rozporu s cílovou větví.
|
||||||
pulls.is_checking=Právě probíhá kontrola konfliktů při sloučení. Zkuste to za chvíli.
|
pulls.is_checking=Právě probíhá kontrola konfliktů při sloučení. Zkuste to za chvíli.
|
||||||
pulls.is_ancestor=Tato větev je již součástí cílové větve. Není co sloučit.
|
pulls.is_ancestor=Tato větev je již součástí cílové větve. Není co sloučit.
|
||||||
pulls.is_empty=Změny na této větvi jsou již na cílové větvi. Toto bude prázdný commit.
|
pulls.is_empty=Změny na této větvi jsou již na cílové větvi. Toto bude prázdný commit.
|
||||||
pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné.
|
pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné.
|
||||||
pulls.required_status_check_missing=Některé požadované kontroly chybí.
|
pulls.required_status_check_missing=Některé požadované kontroly chybí.
|
||||||
pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tento požadavek na natažení.
|
pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tuto žádost.
|
||||||
pulls.blocked_by_approvals=Tato žádost o sloučení ještě nemá dostatek schválení. Uděleno %d z %d schválení.
|
pulls.blocked_by_approvals=Tato žádost o sloučení ještě nemá dostatek schválení. Uděleno %d z %d schválení.
|
||||||
pulls.blocked_by_rejection=Tato žádost o sloučení obsahuje změny požadované oficiálním posuzovatelem.
|
pulls.blocked_by_rejection=Tato žádost o sloučení obsahuje změny požadované oficiálním posuzovatelem.
|
||||||
pulls.blocked_by_official_review_requests=Tato žádost o sloučení je zablokována, protože jí chybí schválení oficiálních posuzovatelů.
|
pulls.blocked_by_official_review_requests=Tato žádost o sloučení je zablokována, protože jí chybí schválení oficiálních posuzovatelů.
|
||||||
pulls.blocked_by_outdated_branch=Tato žádost o sloučení je zablokována, protože je zastaralá.
|
pulls.blocked_by_outdated_branch=Tato žádost o sloučení je zablokována, protože je zastaralá.
|
||||||
pulls.blocked_by_changed_protected_files_1=Tato žádost o sloučení je zablokována, protože mění chráněný soubor:
|
pulls.blocked_by_changed_protected_files_1=Tato žádost o sloučení je zablokována, protože mění chráněný soubor:
|
||||||
pulls.blocked_by_changed_protected_files_n=Tato žádost o sloučení je zablokována, protože mění chráněné soubory:
|
pulls.blocked_by_changed_protected_files_n=Tato žádost o sloučení je zablokována, protože mění chráněné soubory:
|
||||||
pulls.can_auto_merge_desc=Tento požadavek na natažení může být automaticky sloučen.
|
pulls.can_auto_merge_desc=Tato žádost může být automaticky sloučena.
|
||||||
pulls.cannot_auto_merge_desc=Tento požadavek na natažení nemůže být automaticky sloučen, neboť se v něm nachází konflikty.
|
pulls.cannot_auto_merge_desc=Tato žádost nemůže být automaticky sloučena, neboť se v ní nachází konflikty.
|
||||||
pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční sloučení.
|
pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční sloučení.
|
||||||
pulls.num_conflicting_files_1=%d konfliktní soubor
|
pulls.num_conflicting_files_1=%d konfliktní soubor
|
||||||
pulls.num_conflicting_files_n=%d konfliktních souborů
|
pulls.num_conflicting_files_n=%d konfliktních souborů
|
||||||
|
@ -1823,11 +1825,11 @@ pulls.waiting_count_1=%d čekající posouzení
|
||||||
pulls.waiting_count_n=%d čekajících posouzení
|
pulls.waiting_count_n=%d čekajících posouzení
|
||||||
pulls.wrong_commit_id=id commitu musí být id commitu v cílové větvi
|
pulls.wrong_commit_id=id commitu musí být id commitu v cílové větvi
|
||||||
|
|
||||||
pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány.
|
pulls.no_merge_desc=Tato žádost nemůže být sloučena, protože všechny možnosti repozitáře na sloučení jsou zakázány.
|
||||||
pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně.
|
pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení žádosti ručně.
|
||||||
pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený.
|
pulls.no_merge_wip=Tato žádost nemůže být sloučena, protože je označena jako rozpracovaná.
|
||||||
pulls.no_merge_not_ready=Tento požadavek na natažení není připraven na sloučení, zkontrolujte stav posouzení a kontrolu stavu.
|
pulls.no_merge_not_ready=Tento žádost není připravena na sloučení, zkontrolujte stav posouzení a kontroly stavu.
|
||||||
pulls.no_merge_access=Nemáte oprávnění sloučit tento požadavek na natažení.
|
pulls.no_merge_access=Nemáte oprávnění sloučit tuto žádost.
|
||||||
pulls.merge_pull_request=Vytvořit slučovací commit
|
pulls.merge_pull_request=Vytvořit slučovací commit
|
||||||
pulls.rebase_merge_pull_request=Rebase pak fast-forward
|
pulls.rebase_merge_pull_request=Rebase pak fast-forward
|
||||||
pulls.rebase_merge_commit_pull_request=Rebase a poté vytvořit slučovací commit
|
pulls.rebase_merge_commit_pull_request=Rebase a poté vytvořit slučovací commit
|
||||||
|
@ -1836,7 +1838,7 @@ pulls.merge_manually=Sloučeno ručně
|
||||||
pulls.merge_commit_id=ID slučovacího commitu
|
pulls.merge_commit_id=ID slučovacího commitu
|
||||||
pulls.require_signed_wont_sign=Větev vyžaduje podepsané commity, ale toto sloučení nebude podepsáno
|
pulls.require_signed_wont_sign=Větev vyžaduje podepsané commity, ale toto sloučení nebude podepsáno
|
||||||
|
|
||||||
pulls.invalid_merge_option=Nemůžete použít tuto možnost sloučení pro tento požadavek na natažení.
|
pulls.invalid_merge_option=Pro tuto žádost nemůžete použít tuto možnost sloučení.
|
||||||
pulls.merge_conflict=Sloučení selhalo: Došlo ke konfliktu při sloučení. Tip: Zkuste jinou strategii
|
pulls.merge_conflict=Sloučení selhalo: Došlo ke konfliktu při sloučení. Tip: Zkuste jinou strategii
|
||||||
pulls.merge_conflict_summary=Chybové hlášení
|
pulls.merge_conflict_summary=Chybové hlášení
|
||||||
pulls.rebase_conflict=Sloučení selhalo: Došlo ke konfliktu při rebase commitu: %[1]s. Tip: Zkuste jinou strategii
|
pulls.rebase_conflict=Sloučení selhalo: Došlo ke konfliktu při rebase commitu: %[1]s. Tip: Zkuste jinou strategii
|
||||||
|
@ -1844,7 +1846,7 @@ pulls.rebase_conflict_summary=Chybové hlášení
|
||||||
pulls.unrelated_histories=Sloučení selhalo: Hlavní a základní revize nesdílí společnou historii. Tip: Zkuste jinou strategii
|
pulls.unrelated_histories=Sloučení selhalo: Hlavní a základní revize nesdílí společnou historii. Tip: Zkuste jinou strategii
|
||||||
pulls.merge_out_of_date=Sloučení selhalo: Základ byl aktualizován při generování sloučení. Tip: Zkuste to znovu.
|
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.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: Požadavek na natažení byl sloučen, nelze znovu sloučit nebo změnit cílovou větev.
|
pulls.has_merged=Chyba: žádost byla sloučena, nelze ji znovu sloučit nebo změnit cílovou větev.
|
||||||
pulls.push_rejected=Push selhal: nahrání bylo zamítnuto. Zkontrolujte Git hooky pro tento repozitář.
|
pulls.push_rejected=Push selhal: nahrání bylo zamítnuto. Zkontrolujte Git hooky pro tento repozitář.
|
||||||
pulls.push_rejected_summary=Úplná zpráva o odmítnutí
|
pulls.push_rejected_summary=Úplná zpráva o odmítnutí
|
||||||
pulls.push_rejected_no_message=Push selhal: nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva. Zkontrolujte Git hooky pro tento repozitář
|
pulls.push_rejected_no_message=Push selhal: nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva. Zkontrolujte Git hooky pro tento repozitář
|
||||||
|
@ -1897,28 +1899,28 @@ milestones.no_due_date=Bez lhůty dokončení
|
||||||
milestones.open=Otevřít
|
milestones.open=Otevřít
|
||||||
milestones.close=Zavřít
|
milestones.close=Zavřít
|
||||||
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
|
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
|
||||||
milestones.completeness=%d%% Dokončeno
|
milestones.completeness=Dokončeno <strong>%d%%</strong>
|
||||||
milestones.create=Vytvořit milník
|
milestones.create=Vytvořit milník
|
||||||
milestones.title=Název
|
milestones.title=Název
|
||||||
milestones.desc=Popis
|
milestones.desc=Popis
|
||||||
milestones.due_date=Termín (volitelný)
|
milestones.due_date=Termín (volitelný)
|
||||||
milestones.clear=Zrušit
|
milestones.clear=Vymazat
|
||||||
milestones.invalid_due_date_format=Termín dokončení musí být ve formátu „rrrr-mm-dd“.
|
milestones.invalid_due_date_format=Termín dokončení musí být ve formátu „rrrr-mm-dd“.
|
||||||
milestones.create_success=Milník „%s“ byl vytvořen.
|
milestones.create_success=Milník „%s“ byl vytvořen.
|
||||||
milestones.edit=Upravit milník
|
milestones.edit=Upravit milník
|
||||||
milestones.edit_subheader=Milník organizuje úkoly a sledují pokrok.
|
milestones.edit_subheader=Milníky organizují problémy a sledují pokrok.
|
||||||
milestones.cancel=Zrušit
|
milestones.cancel=Zrušit
|
||||||
milestones.modify=Upravit milník
|
milestones.modify=Upravit milník
|
||||||
milestones.edit_success=Milník „%s“ byl aktualizován.
|
milestones.edit_success=Milník „%s“ byl aktualizován.
|
||||||
milestones.deletion=Odstranit milník
|
milestones.deletion=Odstranit milník
|
||||||
milestones.deletion_desc=Odstranění milníku jej smaže ze všech souvisejících úkolů. Pokračovat?
|
milestones.deletion_desc=Smazáním milníku jej odstraníte ze všech souvisejících problémů. Pokračovat?
|
||||||
milestones.deletion_success=Milník byl odstraněn.
|
milestones.deletion_success=Milník byl odstraněn.
|
||||||
milestones.filter_sort.earliest_due_data=Nejbližší termín dokončení
|
milestones.filter_sort.earliest_due_data=Nejbližší termín dokončení
|
||||||
milestones.filter_sort.latest_due_date=Nejzazší termín dokončení
|
milestones.filter_sort.latest_due_date=Nejzazší termín dokončení
|
||||||
milestones.filter_sort.least_complete=Nejméně dokončené
|
milestones.filter_sort.least_complete=Nejméně dokončené
|
||||||
milestones.filter_sort.most_complete=Nejvíce dokončené
|
milestones.filter_sort.most_complete=Nejvíce dokončené
|
||||||
milestones.filter_sort.most_issues=Nejvíce úkolů
|
milestones.filter_sort.most_issues=Nejvíce problémů
|
||||||
milestones.filter_sort.least_issues=Nejméně úkolů
|
milestones.filter_sort.least_issues=Nejméně problémů
|
||||||
|
|
||||||
signing.will_sign=Tento commit bude podepsána klíčem „%s“.
|
signing.will_sign=Tento commit bude podepsána klíčem „%s“.
|
||||||
signing.wont_sign.error=Došlo k chybě při kontrole, zda může být commit podepsán.
|
signing.wont_sign.error=Došlo k chybě při kontrole, zda může být commit podepsán.
|
||||||
|
@ -2002,7 +2004,7 @@ activity.new_issues_count_n=Nové problémy
|
||||||
activity.new_issue_label=Otevřený
|
activity.new_issue_label=Otevřený
|
||||||
activity.title.unresolved_conv_1=%d nevyřešená konverzace
|
activity.title.unresolved_conv_1=%d nevyřešená konverzace
|
||||||
activity.title.unresolved_conv_n=%d nevyřešených konverzací
|
activity.title.unresolved_conv_n=%d nevyřešených konverzací
|
||||||
activity.unresolved_conv_desc=Tyto nedávno změněné úkolu a požadavky na natažení ještě nebyly vyřešeny.
|
activity.unresolved_conv_desc=Tyto nedávno změněné problémy a žádosti o sloučení zatím nebyly vyřešeny.
|
||||||
activity.unresolved_conv_label=Otevřít
|
activity.unresolved_conv_label=Otevřít
|
||||||
activity.title.releases_1=%d vydání
|
activity.title.releases_1=%d vydání
|
||||||
activity.title.releases_n=%d vydání
|
activity.title.releases_n=%d vydání
|
||||||
|
@ -2093,17 +2095,17 @@ settings.issues_desc=Povolit systém problémů repozitáře
|
||||||
settings.use_internal_issue_tracker=Použít vestavěný systém problémů
|
settings.use_internal_issue_tracker=Použít vestavěný systém problémů
|
||||||
settings.use_external_issue_tracker=Použít externí systém problémů
|
settings.use_external_issue_tracker=Použít externí systém problémů
|
||||||
settings.external_tracker_url=Adresa URL externího systému problémů
|
settings.external_tracker_url=Adresa URL externího systému problémů
|
||||||
settings.external_tracker_url_error=URL externího systému úkolu není platné URL.
|
settings.external_tracker_url_error=Adresa URL externího systému problémů není platnou adresou URL.
|
||||||
settings.external_tracker_url_desc=Když návštěvníci kliknou na záložku úkolů, jsou přesměrování na externí systém úkolů.
|
settings.external_tracker_url_desc=Pokud návštěvníci kliknou na záložku problémů, budou přesměrování na externí systém problémů.
|
||||||
settings.tracker_url_format=Formát adresy URL externího systému problémů
|
settings.tracker_url_format=Formát adresy URL externího systému problémů
|
||||||
settings.tracker_url_format_error=Formát URL externího systému úkolu není platné URL.
|
settings.tracker_url_format_error=Formát adresy URL externího systému problémů není platná adresa URL.
|
||||||
settings.tracker_issue_style=Formát čísel externího systému problémů
|
settings.tracker_issue_style=Formát čísel externího systému problémů
|
||||||
settings.tracker_issue_style.numeric=Číselný
|
settings.tracker_issue_style.numeric=Číselný
|
||||||
settings.tracker_issue_style.alphanumeric=Alfanumerický
|
settings.tracker_issue_style.alphanumeric=Alfanumerický
|
||||||
settings.tracker_issue_style.regexp=Regulární výraz
|
settings.tracker_issue_style.regexp=Regulární výraz
|
||||||
settings.tracker_issue_style.regexp_pattern=Vzor regulárního výrazu
|
settings.tracker_issue_style.regexp_pattern=Vzor regulárního výrazu
|
||||||
settings.tracker_issue_style.regexp_pattern_desc=První zachycená skupina bude použita místo <code>{index}</code>.
|
settings.tracker_issue_style.regexp_pattern_desc=První zachycená skupina bude použita místo <code>{index}</code>.
|
||||||
settings.tracker_url_format_desc=Použijte zástupné symboly <code>{user}</code>, <code>{repo}</code> a <code>{index}</code> pro uživatelské jméno, jméno repozitáře a číslo úkolu.
|
settings.tracker_url_format_desc=Použijte proměnné <code>{user}</code>, <code>{repo}</code> a <code>{index}</code> pro uživatelské jméno, název repozitáře a číslo problému.
|
||||||
settings.enable_timetracker=Povolit sledování času
|
settings.enable_timetracker=Povolit sledování času
|
||||||
settings.allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
|
settings.allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
|
||||||
settings.pulls_desc=Povolit žádosti o sloučení
|
settings.pulls_desc=Povolit žádosti o sloučení
|
||||||
|
@ -2124,7 +2126,7 @@ settings.admin_indexer_commit_sha=Poslední indexovaná SHA
|
||||||
settings.admin_indexer_unindexed=Neindexováno
|
settings.admin_indexer_unindexed=Neindexováno
|
||||||
settings.reindex_button=Přidat do fronty reindexace
|
settings.reindex_button=Přidat do fronty reindexace
|
||||||
settings.reindex_requested=Požadováno reindexování
|
settings.reindex_requested=Požadováno reindexování
|
||||||
settings.admin_enable_close_issues_via_commit_in_any_branch=Zavřít úkol pomocí commitu v jiné než výchozí větvi
|
settings.admin_enable_close_issues_via_commit_in_any_branch=Zavřít problém pomocí commitu v jiné než výchozí větvi
|
||||||
settings.danger_zone=Nebezpečná zóna
|
settings.danger_zone=Nebezpečná zóna
|
||||||
settings.new_owner_has_same_repo=Nový vlastník již repozitář se stejným názvem má. Vyberte prosím jiné jméno.
|
settings.new_owner_has_same_repo=Nový vlastník již repozitář se stejným názvem má. Vyberte prosím jiné jméno.
|
||||||
settings.convert=Převést na běžný repozitář
|
settings.convert=Převést na běžný repozitář
|
||||||
|
@ -2133,10 +2135,10 @@ settings.convert_notices_1=Tato operace převede toto zrcadlo na běžný repozi
|
||||||
settings.convert_confirm=Převést repozitář
|
settings.convert_confirm=Převést repozitář
|
||||||
settings.convert_succeed=Zrcadlo bylo převedeno na běžný repozitář.
|
settings.convert_succeed=Zrcadlo bylo převedeno na běžný repozitář.
|
||||||
settings.convert_fork=Převést na běžný repozitář
|
settings.convert_fork=Převést na běžný repozitář
|
||||||
settings.convert_fork_desc=Můžete převést toto rozštěpení na běžný repozitář. Tuto akci nelze vrátit zpět.
|
settings.convert_fork_desc=Tento fork můžete převést na běžný repozitář. Tato akce je nevratná.
|
||||||
settings.convert_fork_notices_1=Tato operace převede rozštěpení na běžný repozitář a nelze ji vrátit zpět.
|
settings.convert_fork_notices_1=Tato operace převede fork na běžný repozitář a nelze ji vrátit zpět.
|
||||||
settings.convert_fork_confirm=Převést repozitář
|
settings.convert_fork_confirm=Převést repozitář
|
||||||
settings.convert_fork_succeed=Rozštěpení bylo překonvertován na běžný repozitář.
|
settings.convert_fork_succeed=Fork bylo převeden na běžný repozitář.
|
||||||
settings.transfer=Předat vlastnictví
|
settings.transfer=Předat vlastnictví
|
||||||
settings.transfer.rejected=Převod repozitáře byl zamítnut.
|
settings.transfer.rejected=Převod repozitáře byl zamítnut.
|
||||||
settings.transfer.success=Převod repozitáře byl úspěšný.
|
settings.transfer.success=Převod repozitáře byl úspěšný.
|
||||||
|
@ -2174,8 +2176,8 @@ settings.wiki_deletion_success=Wiki data repozitáře byla odstraněna.
|
||||||
settings.delete=Odstranit tento repozitář
|
settings.delete=Odstranit tento repozitář
|
||||||
settings.delete_desc=Smazání repozitáře je trvalé a nemůže být vráceno zpět.
|
settings.delete_desc=Smazání repozitáře je trvalé a nemůže být vráceno zpět.
|
||||||
settings.delete_notices_1=- Tuto operaci <strong>nelze</strong> zvrátit.
|
settings.delete_notices_1=- Tuto operaci <strong>nelze</strong> zvrátit.
|
||||||
settings.delete_notices_2=- Tato operace trvale smaže repozitář <strong>%s</strong> včetně kódu, úkolů, komentářů, Wiki dat a nastavení spolupracovníků.
|
settings.delete_notices_2=- Tato operace trvale smaže repozitář <strong>%s</strong> včetně kódu, problémů, komentářů, dat wiki a nastavení spolupracovníků.
|
||||||
settings.delete_notices_fork_1=- Rozštěpení repozitáře bude nezávislé po smazání.
|
settings.delete_notices_fork_1=- Fork tohoto repozitáře bude po smazání nezávislý.
|
||||||
settings.deletion_success=Repozitář byl odstraněn.
|
settings.deletion_success=Repozitář byl odstraněn.
|
||||||
settings.update_settings_success=Nastavení repozitáře bylo aktualizováno.
|
settings.update_settings_success=Nastavení repozitáře bylo aktualizováno.
|
||||||
settings.update_settings_no_unit=Repozitář by měl povolit alespoň určitý druh interakce.
|
settings.update_settings_no_unit=Repozitář by měl povolit alespoň určitý druh interakce.
|
||||||
|
@ -2253,16 +2255,16 @@ settings.event_push_desc=Nahrání pomocí Gitu do repozitáře.
|
||||||
settings.event_repository=Repozitář
|
settings.event_repository=Repozitář
|
||||||
settings.event_repository_desc=Repozitář vytvořen nebo smazán.
|
settings.event_repository_desc=Repozitář vytvořen nebo smazán.
|
||||||
settings.event_header_issue=Události problémů
|
settings.event_header_issue=Události problémů
|
||||||
settings.event_issues=Úkoly
|
settings.event_issues=Problémy
|
||||||
settings.event_issues_desc=Úkol otevřen, uzavřen, znovu otevřen nebo upraven.
|
settings.event_issues_desc=Problém otevřen, uzavřen, znovu otevřen nebo upraven.
|
||||||
settings.event_issue_assign=Problém přiřazen
|
settings.event_issue_assign=Problém přiřazen
|
||||||
settings.event_issue_assign_desc=Úkol přiřazen nebo nepřiřazen.
|
settings.event_issue_assign_desc=Problém přiřazen nebo nepřiřazen.
|
||||||
settings.event_issue_label=Problém označen
|
settings.event_issue_label=Problém označen
|
||||||
settings.event_issue_label_desc=Štítky úkolu aktualizovány nebo vymazány.
|
settings.event_issue_label_desc=Štítky problému upraveny nebo vymazány.
|
||||||
settings.event_issue_milestone=K problému přidán milník
|
settings.event_issue_milestone=K problému přidán milník
|
||||||
settings.event_issue_milestone_desc=Úkolu přidán nebo odebrán milník.
|
settings.event_issue_milestone_desc=K problému přidán nebo odebrán milník.
|
||||||
settings.event_issue_comment=Komentář k problému
|
settings.event_issue_comment=Komentář k problému
|
||||||
settings.event_issue_comment_desc=Komentář úkolu přidán, upraven nebo smazán.
|
settings.event_issue_comment_desc=Přidán, upraven nebo smazán komentář problému.
|
||||||
settings.event_header_pull_request=Události žádosti o sloučení
|
settings.event_header_pull_request=Události žádosti o sloučení
|
||||||
settings.event_pull_request=Žádost o sloučení
|
settings.event_pull_request=Žádost o sloučení
|
||||||
settings.event_pull_request_desc=Požadavek na natažení otevřen, uzavřen, znovu otevřen nebo upraven.
|
settings.event_pull_request_desc=Požadavek na natažení otevřen, uzavřen, znovu otevřen nebo upraven.
|
||||||
|
@ -2306,9 +2308,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Uživatelské jméno pro Packagist
|
settings.packagist_username=Uživatelské jméno pro Packagist
|
||||||
|
@ -2426,7 +2428,7 @@ settings.archive.branchsettings_unavailable=Nastavení větví není dostupné,
|
||||||
settings.archive.tagsettings_unavailable=Nastavení značek není k dispozici, pokud je repozitář archivován.
|
settings.archive.tagsettings_unavailable=Nastavení značek není k dispozici, pokud je repozitář archivován.
|
||||||
settings.unarchive.button=Zrušit archivaci repozitáře
|
settings.unarchive.button=Zrušit archivaci repozitáře
|
||||||
settings.unarchive.header=Obnovit tento repozitář
|
settings.unarchive.header=Obnovit tento repozitář
|
||||||
settings.unarchive.text=Obnovení repozitáře vrátí možnost přijímání commitů a nahrávání. Stejně tak se obnoví i možnost zadávání nových úkolů a požadavků na natažení.
|
settings.unarchive.text=Obnovení repozitáře vrátí možnost přijímání commitů a nahrávání. Stejně tak se obnoví i možnost vytváření nových problémů a žádostí o sloučení.
|
||||||
settings.unarchive.success=Repozitář byl úspěšně obnoven.
|
settings.unarchive.success=Repozitář byl úspěšně obnoven.
|
||||||
settings.unarchive.error=Nastala chyba při obnovování repozitáře. Prohlédněte si záznam pro více detailů.
|
settings.unarchive.error=Nastala chyba při obnovování repozitáře. Prohlédněte si záznam pro více detailů.
|
||||||
settings.update_avatar_success=Avatar repozitáře byl aktualizován.
|
settings.update_avatar_success=Avatar repozitáře byl aktualizován.
|
||||||
|
@ -2705,6 +2707,20 @@ pulls.title_desc_one = žádá o sloučení %[1]d commitu z <code>%[2]s</code> d
|
||||||
pulls.merged_title_desc_one = sloučil %[1]d commit z <code>%[2]s</code> do <code>%[3]s</code> %[4]s
|
pulls.merged_title_desc_one = sloučil %[1]d commit z <code>%[2]s</code> do <code>%[3]s</code> %[4]s
|
||||||
open_with_editor = Otevřít pomocí %s
|
open_with_editor = Otevřít pomocí %s
|
||||||
commits.search_branch = Tato větev
|
commits.search_branch = Tato větev
|
||||||
|
editor.commit_id_not_matching = ID commitu se neshoduje s ID commitu, který jste upravovali. Proveďte commit do nové větve a poté je slučte.
|
||||||
|
pulls.ready_for_review = Připraveni na posouzení?
|
||||||
|
settings.rename_branch_failed_protected = Nepodařilo se přejmenovat větev %s, jelikož se jedná o chráněnou větev.
|
||||||
|
editor.push_out_of_date = Push je nejspíše zastaralý.
|
||||||
|
stars = Oblíbení
|
||||||
|
n_commit_one = %s commit
|
||||||
|
n_commit_few = %s commitů
|
||||||
|
n_branch_one = %s větev
|
||||||
|
n_tag_one = %s značka
|
||||||
|
n_tag_few = %s značek
|
||||||
|
n_branch_few = %s větví
|
||||||
|
settings.event_pull_request_enforcement = Vynucení
|
||||||
|
settings.enforce_on_admins = Vynutit toto pravidlo pro správce repozitáře
|
||||||
|
settings.enforce_on_admins_desc = Správci repozitáře nemohou obejít toto pravidlo.
|
||||||
|
|
||||||
[graphs]
|
[graphs]
|
||||||
component_loading_info = Tohle může chvíli trvat…
|
component_loading_info = Tohle může chvíli trvat…
|
||||||
|
@ -2770,7 +2786,7 @@ settings.delete_org_title=Odstranit organizaci
|
||||||
settings.delete_org_desc=Tato organizace bude trvale smazána. Pokračovat?
|
settings.delete_org_desc=Tato organizace bude trvale smazána. Pokračovat?
|
||||||
settings.hooks_desc=Přidat webové háčky, které budou spouštěny pro <strong>všechny repozitáře</strong> v této organizaci.
|
settings.hooks_desc=Přidat webové háčky, které budou spouštěny pro <strong>všechny repozitáře</strong> v této organizaci.
|
||||||
|
|
||||||
settings.labels_desc=Přidejte štítky, které mohou být použity pro úkoly <strong>všech repositářů</strong> v rámci této organizace.
|
settings.labels_desc=Přidejte štítky, které mohou být použity pro problémy <strong>všech repozitářů</strong> v rámci této organizace.
|
||||||
|
|
||||||
members.membership_visibility=Viditelnost členství:
|
members.membership_visibility=Viditelnost členství:
|
||||||
members.public=Viditelný
|
members.public=Viditelný
|
||||||
|
@ -3028,7 +3044,7 @@ repos.private=Soukromý
|
||||||
repos.watches=Sledovače
|
repos.watches=Sledovače
|
||||||
repos.stars=Oblíbení
|
repos.stars=Oblíbení
|
||||||
repos.forks=Rozštěpení
|
repos.forks=Rozštěpení
|
||||||
repos.issues=Úkoly
|
repos.issues=Problémy
|
||||||
repos.size=Velikost
|
repos.size=Velikost
|
||||||
repos.lfs_size=Velikost LFS
|
repos.lfs_size=Velikost LFS
|
||||||
|
|
||||||
|
@ -3153,7 +3169,7 @@ auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzo
|
||||||
auths.tip.openid_connect=Použijte OpenID URL pro objevování spojení (<server>/.well-known/openid-configuration) k nastavení koncových bodů
|
auths.tip.openid_connect=Použijte OpenID URL pro objevování spojení (<server>/.well-known/openid-configuration) k nastavení koncových bodů
|
||||||
auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená
|
auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená
|
||||||
auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me
|
auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me
|
||||||
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://docs.gitea.com/development/oauth2-provider
|
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://forgejo.org/docs/latest/user/oauth2-provider
|
||||||
auths.tip.yandex=Vytvořte novou aplikaci na https://oauth.yandex.com/client/new. Vyberte následující oprávnění z „Yandex.Passport API“ sekce: „Přístup k e-mailové adrese“, „Přístup k uživatelskému avataru“ a „Přístup k uživatelskému jménu, jménu a příjmení, pohlaví“
|
auths.tip.yandex=Vytvořte novou aplikaci na https://oauth.yandex.com/client/new. Vyberte následující oprávnění z „Yandex.Passport API“ sekce: „Přístup k e-mailové adrese“, „Přístup k uživatelskému avataru“ a „Přístup k uživatelskému jménu, jménu a příjmení, pohlaví“
|
||||||
auths.tip.mastodon=Vložte vlastní URL instance pro mastodon, kterou se chcete autentizovat (nebo použijte výchozí)
|
auths.tip.mastodon=Vložte vlastní URL instance pro mastodon, kterou se chcete autentizovat (nebo použijte výchozí)
|
||||||
auths.edit=Upravit zdroj ověřování
|
auths.edit=Upravit zdroj ověřování
|
||||||
|
@ -3188,7 +3204,7 @@ config.repo_root_path=Kořenový adresář repozitářů
|
||||||
config.lfs_root_path=Kořenový adresář LFS
|
config.lfs_root_path=Kořenový adresář LFS
|
||||||
config.log_file_root_path=Adresář protokolů
|
config.log_file_root_path=Adresář protokolů
|
||||||
config.script_type=Typ skriptu
|
config.script_type=Typ skriptu
|
||||||
config.reverse_auth_user=Obrátit uživatele ověření
|
config.reverse_auth_user=Obrátit uživatele ověření proxy
|
||||||
|
|
||||||
config.ssh_config=Nastavení SSH
|
config.ssh_config=Nastavení SSH
|
||||||
config.ssh_enabled=Zapnutý
|
config.ssh_enabled=Zapnutý
|
||||||
|
@ -3237,7 +3253,7 @@ config.allow_dots_in_usernames = Povolit uživatelům používat tečky ve svýc
|
||||||
config.default_allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
|
config.default_allow_only_contributors_to_track_time=Povolit sledování času pouze přispěvatelům
|
||||||
config.no_reply_address=Skrytá e-mailová doména
|
config.no_reply_address=Skrytá e-mailová doména
|
||||||
config.default_visibility_organization=Výchozí viditelnost nových organizací
|
config.default_visibility_organization=Výchozí viditelnost nových organizací
|
||||||
config.default_enable_dependencies=Povolit ve výchozím nastavení závislosti úkolů
|
config.default_enable_dependencies=Povolit ve výchozím nastavení závislosti problémů
|
||||||
|
|
||||||
config.webhook_config=Nastavení webhooků
|
config.webhook_config=Nastavení webhooků
|
||||||
config.queue_length=Délka fronty
|
config.queue_length=Délka fronty
|
||||||
|
@ -3391,14 +3407,15 @@ auths.tips.gmail_settings = Nastavení služby Gmail:
|
||||||
config_summary = Souhrn
|
config_summary = Souhrn
|
||||||
config.open_with_editor_app_help = Editory v nabídce „Otevřít pomocí“ v nabídce klonování. Ponechte prázdné pro použití výchozího editoru (zobrazíte jej rozšířením).
|
config.open_with_editor_app_help = Editory v nabídce „Otevřít pomocí“ v nabídce klonování. Ponechte prázdné pro použití výchozího editoru (zobrazíte jej rozšířením).
|
||||||
config_settings = Nastavení
|
config_settings = Nastavení
|
||||||
|
auths.tip.gitlab_new = Zaregistrujte si novou aplikaci na https://gitlab.com/-/profile/applications
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
create_repo=vytvořil/a repozitář <a href="%s">%s</a>
|
create_repo=vytvořil/a repozitář <a href="%s">%s</a>
|
||||||
rename_repo=přejmenoval/a repozitář z <code>%[1]s</code> na <a href="%[2]s">%[3]s</a>
|
rename_repo=přejmenoval/a repozitář z <code>%[1]s</code> na <a href="%[2]s">%[3]s</a>
|
||||||
commit_repo=nahrál/a do <a href="%[2]s">%[3]s</a> v <a href="%[1]s">%[4]s</a>
|
commit_repo=nahrál/a do <a href="%[2]s">%[3]s</a> v <a href="%[1]s">%[4]s</a>
|
||||||
create_issue=`otevřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
|
create_issue=`otevřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
close_issue=`uzavřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
|
close_issue=`uzavřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
reopen_issue=`znovuotevřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
|
reopen_issue=`znovu otevřel/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
create_pull_request=`vytvořil/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
|
create_pull_request=`vytvořil/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
close_pull_request=`uzavřel/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
|
close_pull_request=`uzavřel/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
reopen_pull_request=`znovuotevřel/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
|
reopen_pull_request=`znovuotevřel/a požadavek na natažení <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
|
@ -3745,6 +3762,7 @@ runners = Runnery
|
||||||
runs.pushed_by = pushnuto uživatelem
|
runs.pushed_by = pushnuto uživatelem
|
||||||
need_approval_desc = Potřebovat schválení pro spouštění workflowů pro žádosti o sloučení forků.
|
need_approval_desc = Potřebovat schválení pro spouštění workflowů pro žádosti o sloučení forků.
|
||||||
runners.runner_manage_panel = Správa runnerů
|
runners.runner_manage_panel = Správa runnerů
|
||||||
|
runs.no_job_without_needs = Workflow musí obsahovat alespoň jednu práci bez závislostí.
|
||||||
|
|
||||||
[projects]
|
[projects]
|
||||||
type-1.display_name=Samostatný projekt
|
type-1.display_name=Samostatný projekt
|
||||||
|
@ -3782,3 +3800,18 @@ no_results = Nenalezeny žádné odpovídající výsledky.
|
||||||
fuzzy_tooltip = Zahrnout také výsledky, které úzce odpovídají hledanému výrazu
|
fuzzy_tooltip = Zahrnout také výsledky, které úzce odpovídají hledanému výrazu
|
||||||
search = Hledat...
|
search = Hledat...
|
||||||
keyword_search_unavailable = Hledání pomocí klíčových slov momentálně není dostupné. Kontaktujte prosím administrátora webu.
|
keyword_search_unavailable = Hledání pomocí klíčových slov momentálně není dostupné. Kontaktujte prosím administrátora webu.
|
||||||
|
code_search_by_git_grep = Aktuální výsledky vyhledávání kódu jsou poskytovány službou „git grep“. Lepší výsledky dostanete, když administrátor webu povolí indexování repozitářů.
|
||||||
|
|
||||||
|
[markup]
|
||||||
|
filepreview.lines = Řádky %[1]d až %[2]d v souboru %[3]s
|
||||||
|
filepreview.line = Řádek %[1]d v souboru %[2]s
|
||||||
|
filepreview.truncated = Náhled byl zkrácen
|
||||||
|
|
||||||
|
[munits.data]
|
||||||
|
b = B
|
||||||
|
kib = KiB
|
||||||
|
mib = MiB
|
||||||
|
gib = GiB
|
||||||
|
tib = TiB
|
||||||
|
pib = PiB
|
||||||
|
eib = EiB
|
|
@ -155,6 +155,8 @@ filter.is_template = Vorlage
|
||||||
filter.not_template = Keine Vorlage
|
filter.not_template = Keine Vorlage
|
||||||
filter.public = Öffentlich
|
filter.public = Öffentlich
|
||||||
filter.private = Privat
|
filter.private = Privat
|
||||||
|
more_items = Mehr Einträge
|
||||||
|
invalid_data = Ungültige Daten: %v
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=Navigationsleiste
|
navbar=Navigationsleiste
|
||||||
|
@ -979,10 +981,10 @@ user_unblock_success = Die Blockierung dieses Benutzers wurde erfolgreich zurüc
|
||||||
blocked_users = Blockierte Benutzer
|
blocked_users = Blockierte Benutzer
|
||||||
blocked_since = Blockiert seit %s
|
blocked_since = Blockiert seit %s
|
||||||
change_password = Passwort ändern
|
change_password = Passwort ändern
|
||||||
hints = Tipps
|
hints = Hinweise
|
||||||
additional_repo_units_hint = Zur Aktivierung zusätzlicher Repository-Einheiten ermutigen
|
additional_repo_units_hint = Zur Aktivierung zusätzlicher Repository-Einheiten ermutigen
|
||||||
update_hints = Tipps aktualisieren
|
update_hints = Hinweise aktualisieren
|
||||||
update_hints_success = Tipps wurden aktualisiert.
|
update_hints_success = Hinweise wurden aktualisiert.
|
||||||
additional_repo_units_hint_description = Einen „Mehr Einheiten hinzufügen …“-Button für Repositorys, welche nicht alle verfügbaren Einheiten aktiviert haben, anzeigen.
|
additional_repo_units_hint_description = Einen „Mehr Einheiten hinzufügen …“-Button für Repositorys, welche nicht alle verfügbaren Einheiten aktiviert haben, anzeigen.
|
||||||
|
|
||||||
[repo]
|
[repo]
|
||||||
|
@ -1393,7 +1395,7 @@ projects.column.set_default_desc=Diese Spalte als Standard für nicht kategorisi
|
||||||
projects.column.unset_default=Standard entfernen
|
projects.column.unset_default=Standard entfernen
|
||||||
projects.column.unset_default_desc=Diese Spalte nicht als Standard verwenden
|
projects.column.unset_default_desc=Diese Spalte nicht als Standard verwenden
|
||||||
projects.column.delete=Spalte löschen
|
projects.column.delete=Spalte löschen
|
||||||
projects.column.deletion_desc=Beim Löschen einer Projektspalte werden alle dazugehörigen Issues nach „Nicht kategorisiert“ verschoben. Fortfahren?
|
projects.column.deletion_desc=Beim Löschen einer Projektspalte werden alle dazugehörigen Issues zur Standardspalte verschoben. Fortfahren?
|
||||||
projects.column.color=Farbe
|
projects.column.color=Farbe
|
||||||
projects.open=Öffnen
|
projects.open=Öffnen
|
||||||
projects.close=Schließen
|
projects.close=Schließen
|
||||||
|
@ -1411,7 +1413,7 @@ issues.filter_reviewers=Reviewer filtern
|
||||||
issues.new=Neues Issue
|
issues.new=Neues Issue
|
||||||
issues.new.title_empty=Der Titel kann nicht leer sein
|
issues.new.title_empty=Der Titel kann nicht leer sein
|
||||||
issues.new.labels=Labels
|
issues.new.labels=Labels
|
||||||
issues.new.no_label=Kein Label
|
issues.new.no_label=Keine Label
|
||||||
issues.new.clear_labels=Labels entfernen
|
issues.new.clear_labels=Labels entfernen
|
||||||
issues.new.projects=Projekte
|
issues.new.projects=Projekte
|
||||||
issues.new.clear_projects=Projekte löschen
|
issues.new.clear_projects=Projekte löschen
|
||||||
|
@ -1596,7 +1598,7 @@ issues.label.filter_sort.alphabetically=Alphabetisch
|
||||||
issues.label.filter_sort.reverse_alphabetically=Umgekehrt alphabetisch
|
issues.label.filter_sort.reverse_alphabetically=Umgekehrt alphabetisch
|
||||||
issues.label.filter_sort.by_size=Kleinste Größe
|
issues.label.filter_sort.by_size=Kleinste Größe
|
||||||
issues.label.filter_sort.reverse_by_size=Größte Größe
|
issues.label.filter_sort.reverse_by_size=Größte Größe
|
||||||
issues.num_participants=%d Beteiligte
|
issues.num_participants_few=%d Beteiligte
|
||||||
issues.attachment.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen`
|
issues.attachment.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen`
|
||||||
issues.attachment.download=`Klicken, um „%s“ herunterzuladen`
|
issues.attachment.download=`Klicken, um „%s“ herunterzuladen`
|
||||||
issues.subscribe=Abonnieren
|
issues.subscribe=Abonnieren
|
||||||
|
@ -2302,9 +2304,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Benutzername für Packagist
|
settings.packagist_username=Benutzername für Packagist
|
||||||
|
@ -2690,6 +2692,16 @@ pulls.title_desc_one = möchte %[1]d Commit von <code>%[2]s</code> nach <code id
|
||||||
open_with_editor = Öffnen mit %s
|
open_with_editor = Öffnen mit %s
|
||||||
commits.search_branch = Dieser Branch
|
commits.search_branch = Dieser Branch
|
||||||
pulls.ready_for_review = Bereit zum Review?
|
pulls.ready_for_review = Bereit zum Review?
|
||||||
|
settings.rename_branch_failed_protected = Branch %s kann nicht umbenannt werden, weil er ein geschützter Branch ist.
|
||||||
|
editor.commit_id_not_matching = Die Commit-ID passt nicht zur ID die du bearbeitet hast hast. Committe in einen neuen Branch, dann mach einen Merge.
|
||||||
|
editor.push_out_of_date = Der Push scheint veraltet zu sein.
|
||||||
|
n_commit_few = %s Commits
|
||||||
|
n_branch_one = %s Branch
|
||||||
|
n_branch_few = %s Branches
|
||||||
|
n_tag_one = %s Tag
|
||||||
|
n_tag_few = %s Tags
|
||||||
|
stars = Favorisierungen
|
||||||
|
n_commit_one = %s Commit
|
||||||
|
|
||||||
[graphs]
|
[graphs]
|
||||||
|
|
||||||
|
@ -3134,7 +3146,7 @@ auths.tip.google_plus=Du erhältst die OAuth2-Client-Zugangsdaten in der Google-
|
||||||
auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (<server>/.well-known/openid-configuration), um die Endpunkte zu spezifizieren
|
auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (<server>/.well-known/openid-configuration), um die Endpunkte zu spezifizieren
|
||||||
auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist
|
auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist
|
||||||
auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung.
|
auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung.
|
||||||
auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.com/development/oauth2-provider/
|
auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://forgejo.org/docs/latest/user/oauth2-provider
|
||||||
auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem Abschnitt „Yandex.Passport API“: „Zugriff auf E-Mail-Adresse“, „Zugriff auf Benutzeravatar“ und „Zugriff auf Benutzername, Vor- und Nachname, Geschlecht“`
|
auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem Abschnitt „Yandex.Passport API“: „Zugriff auf E-Mail-Adresse“, „Zugriff auf Benutzeravatar“ und „Zugriff auf Benutzername, Vor- und Nachname, Geschlecht“`
|
||||||
auths.tip.mastodon=Gib eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige)
|
auths.tip.mastodon=Gib eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige)
|
||||||
auths.edit=Authentifikationsquelle bearbeiten
|
auths.edit=Authentifikationsquelle bearbeiten
|
||||||
|
@ -3169,7 +3181,7 @@ config.repo_root_path=Repository-Wurzelpfad
|
||||||
config.lfs_root_path=LFS-Wurzelpfad
|
config.lfs_root_path=LFS-Wurzelpfad
|
||||||
config.log_file_root_path=Logdateipfad
|
config.log_file_root_path=Logdateipfad
|
||||||
config.script_type=Skript-Typ
|
config.script_type=Skript-Typ
|
||||||
config.reverse_auth_user=Nutzer bei Reverse-Authentifizierung
|
config.reverse_auth_user=Nutzer bei Reverse-Proxy-Authentifizierung
|
||||||
|
|
||||||
config.ssh_config=SSH-Konfiguration
|
config.ssh_config=SSH-Konfiguration
|
||||||
config.ssh_enabled=Aktiviert
|
config.ssh_enabled=Aktiviert
|
||||||
|
@ -3358,6 +3370,7 @@ auths.tips.gmail_settings = Gmail-Einstellungen:
|
||||||
config_settings = Einstellungen
|
config_settings = Einstellungen
|
||||||
config.open_with_editor_app_help = Die „Öffnen mit“-Editoren für das Klonmenü. Falls es leer gelassen wird, wird der Standardwert benutzt. Erweitern, um den Standardwert zu sehen.
|
config.open_with_editor_app_help = Die „Öffnen mit“-Editoren für das Klonmenü. Falls es leer gelassen wird, wird der Standardwert benutzt. Erweitern, um den Standardwert zu sehen.
|
||||||
config_summary = Zusammenfassung
|
config_summary = Zusammenfassung
|
||||||
|
auths.tip.gitlab_new = Registriere eine neue Anwendung auf https://gitlab.com/-/profile/applications
|
||||||
|
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
|
@ -3711,6 +3724,7 @@ runs.no_workflows.documentation = Für weitere Informationen über Forgejo Actio
|
||||||
runs.empty_commit_message = (leere Commit-Nachricht)
|
runs.empty_commit_message = (leere Commit-Nachricht)
|
||||||
variables.id_not_exist = Variable mit ID %d existiert nicht.
|
variables.id_not_exist = Variable mit ID %d existiert nicht.
|
||||||
runs.workflow = Workflow
|
runs.workflow = Workflow
|
||||||
|
runs.no_job_without_needs = Der Workflow muss mindestens einen Job ohne Abhängigkeiten enthalten.
|
||||||
|
|
||||||
[projects]
|
[projects]
|
||||||
type-1.display_name=Individuelles Projekt
|
type-1.display_name=Individuelles Projekt
|
||||||
|
@ -3758,3 +3772,9 @@ runner_kind = Runners suchen …
|
||||||
no_results = Keine passenden Ergebnisse gefunden.
|
no_results = Keine passenden Ergebnisse gefunden.
|
||||||
code_search_unavailable = Die Code-Suche ist momentan nicht verfügbar. Bitte kontaktiere den Webseitenadministrator.
|
code_search_unavailable = Die Code-Suche ist momentan nicht verfügbar. Bitte kontaktiere den Webseitenadministrator.
|
||||||
keyword_search_unavailable = Suche nach Schlüsselwörtern ist momentan nicht unterstüzt. Bitte kontaktiere den Webseitenadministrator.
|
keyword_search_unavailable = Suche nach Schlüsselwörtern ist momentan nicht unterstüzt. Bitte kontaktiere den Webseitenadministrator.
|
||||||
|
code_search_by_git_grep = Die derzeitigen Codesuchergebnisse werden durch „git grep“ bereitgestellt. Es könnten bessere Ergebnisse erzielt werden, wenn der Administrator die Repository-Indizierung aktiviert.
|
||||||
|
|
||||||
|
[markup]
|
||||||
|
filepreview.line = Zeile %[1]d in %[2]s
|
||||||
|
filepreview.truncated = Vorschau wurde gekürzt
|
||||||
|
filepreview.lines = Zeilen %[1]d bis %[2]d in %[3]s
|
|
@ -1580,7 +1580,7 @@ issues.label.filter_sort.alphabetically=Αλφαβητικά
|
||||||
issues.label.filter_sort.reverse_alphabetically=Αντίστροφα αλφαβητικά
|
issues.label.filter_sort.reverse_alphabetically=Αντίστροφα αλφαβητικά
|
||||||
issues.label.filter_sort.by_size=Μικρότερο μέγεθος
|
issues.label.filter_sort.by_size=Μικρότερο μέγεθος
|
||||||
issues.label.filter_sort.reverse_by_size=Μεγαλύτερο μέγεθος
|
issues.label.filter_sort.reverse_by_size=Μεγαλύτερο μέγεθος
|
||||||
issues.num_participants=%d Συμμετέχοντες
|
issues.num_participants_few=%d Συμμετέχοντες
|
||||||
issues.attachment.open_tab=`Πατήστε εδώ για να ανοίξετε το «%s» σε μια νέα καρτέλα`
|
issues.attachment.open_tab=`Πατήστε εδώ για να ανοίξετε το «%s» σε μια νέα καρτέλα`
|
||||||
issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»`
|
issues.attachment.download=`Πατήστε εδώ για να κατεβάσετε το «%s»`
|
||||||
issues.subscribe=Εγγραφή
|
issues.subscribe=Εγγραφή
|
||||||
|
@ -2293,9 +2293,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Όνομα χρήστη Packagist
|
settings.packagist_username=Όνομα χρήστη Packagist
|
||||||
|
|
|
@ -146,15 +146,15 @@ name = Name
|
||||||
value = Value
|
value = Value
|
||||||
|
|
||||||
filter = Filter
|
filter = Filter
|
||||||
filter.clear = Clear Filter
|
filter.clear = Clear filters
|
||||||
filter.is_archived = Archived
|
filter.is_archived = Archived
|
||||||
filter.not_archived = Not Archived
|
filter.not_archived = Not archived
|
||||||
filter.is_fork = Forked
|
filter.is_fork = Forked
|
||||||
filter.not_fork = Not Forked
|
filter.not_fork = Not forked
|
||||||
filter.is_mirror = Mirrored
|
filter.is_mirror = Mirrored
|
||||||
filter.not_mirror = Not Mirrored
|
filter.not_mirror = Not mirrored
|
||||||
filter.is_template = Template
|
filter.is_template = Template
|
||||||
filter.not_template = Not Template
|
filter.not_template = Not template
|
||||||
filter.public = Public
|
filter.public = Public
|
||||||
filter.private = Private
|
filter.private = Private
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ missing_csrf = Bad Request: no CSRF token present
|
||||||
invalid_csrf = Bad Request: invalid CSRF token
|
invalid_csrf = Bad Request: invalid CSRF token
|
||||||
not_found = The target couldn't be found.
|
not_found = The target couldn't be found.
|
||||||
network_error = Network error
|
network_error = Network error
|
||||||
server_internal = Internal Server Error
|
server_internal = Internal server error
|
||||||
|
|
||||||
[startpage]
|
[startpage]
|
||||||
app_desc = A painless, self-hosted Git service
|
app_desc = A painless, self-hosted Git service
|
||||||
|
@ -762,7 +762,6 @@ password_incorrect = The current password is incorrect.
|
||||||
change_password_success = Your password has been updated. Sign in using your new password from now on.
|
change_password_success = Your password has been updated. Sign in using your new password from now on.
|
||||||
password_change_disabled = Non-local users cannot update their password through the Forgejo web interface.
|
password_change_disabled = Non-local users cannot update their password through the Forgejo web interface.
|
||||||
|
|
||||||
emails = Email addresses
|
|
||||||
manage_emails = Manage email addresses
|
manage_emails = Manage email addresses
|
||||||
manage_themes = Select default theme
|
manage_themes = Select default theme
|
||||||
manage_openid = Manage OpenID addresses
|
manage_openid = Manage OpenID addresses
|
||||||
|
@ -893,9 +892,9 @@ repo_and_org_access = Repository and Organization Access
|
||||||
permissions_public_only = Public only
|
permissions_public_only = Public only
|
||||||
permissions_access_all = All (public, private, and limited)
|
permissions_access_all = All (public, private, and limited)
|
||||||
select_permissions = Select permissions
|
select_permissions = Select permissions
|
||||||
permission_no_access = No Access
|
permission_no_access = No access
|
||||||
permission_read = Read
|
permission_read = Read
|
||||||
permission_write = Read and Write
|
permission_write = Read and write
|
||||||
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
|
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
|
||||||
at_least_one_permission = You must select at least one permission to create a token
|
at_least_one_permission = You must select at least one permission to create a token
|
||||||
permissions_list = Permissions:
|
permissions_list = Permissions:
|
||||||
|
@ -915,8 +914,8 @@ oauth2_confidential_client = Confidential client. Select for apps that keep the
|
||||||
oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
|
oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
|
||||||
save_application = Save
|
save_application = Save
|
||||||
oauth2_client_id = Client ID
|
oauth2_client_id = Client ID
|
||||||
oauth2_client_secret = Client Secret
|
oauth2_client_secret = Client secret
|
||||||
oauth2_regenerate_secret = Regenerate Secret
|
oauth2_regenerate_secret = Regenerate secret
|
||||||
oauth2_regenerate_secret_hint = Lost your secret?
|
oauth2_regenerate_secret_hint = Lost your secret?
|
||||||
oauth2_client_secret_hint = The secret will not be shown again after you leave or refresh this page. Please ensure that you have saved it.
|
oauth2_client_secret_hint = The secret will not be shown again after you leave or refresh this page. Please ensure that you have saved it.
|
||||||
oauth2_application_edit = Edit
|
oauth2_application_edit = Edit
|
||||||
|
@ -927,7 +926,7 @@ oauth2_application_locked = Forgejo pre-registers some OAuth2 applications on st
|
||||||
authorized_oauth2_applications = Authorized OAuth2 applications
|
authorized_oauth2_applications = Authorized OAuth2 applications
|
||||||
authorized_oauth2_applications_description = You have granted access to your personal Forgejo account to these third party applications. Please revoke access for applications that are no longer in use.
|
authorized_oauth2_applications_description = You have granted access to your personal Forgejo account to these third party applications. Please revoke access for applications that are no longer in use.
|
||||||
revoke_key = Revoke
|
revoke_key = Revoke
|
||||||
revoke_oauth2_grant = Revoke Access
|
revoke_oauth2_grant = Revoke access
|
||||||
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
|
revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure?
|
||||||
revoke_oauth2_grant_success = Access revoked successfully.
|
revoke_oauth2_grant_success = Access revoked successfully.
|
||||||
|
|
||||||
|
@ -1012,6 +1011,7 @@ owner_helper = Some organizations may not show up in the dropdown due to a maxim
|
||||||
repo_name = Repository name
|
repo_name = Repository name
|
||||||
repo_name_helper = Good repository names use short, memorable and unique keywords.
|
repo_name_helper = Good repository names use short, memorable and unique keywords.
|
||||||
repo_size = Repository Size
|
repo_size = Repository Size
|
||||||
|
size_format = %[1]s: %[2]s, %[3]s: %[4]s
|
||||||
template = Template
|
template = Template
|
||||||
template_select = Select a template.
|
template_select = Select a template.
|
||||||
template_helper = Make repository a template
|
template_helper = Make repository a template
|
||||||
|
@ -1143,7 +1143,6 @@ form.name_pattern_not_allowed = The pattern "%s" is not allowed in a repository
|
||||||
|
|
||||||
need_auth = Authorization
|
need_auth = Authorization
|
||||||
migrate_options = Migration options
|
migrate_options = Migration options
|
||||||
migrate_service = Migration service
|
|
||||||
migrate_options_mirror_helper = This repository will be a mirror
|
migrate_options_mirror_helper = This repository will be a mirror
|
||||||
migrate_options_lfs = Migrate LFS files
|
migrate_options_lfs = Migrate LFS files
|
||||||
migrate_options_lfs_endpoint.label = LFS endpoint
|
migrate_options_lfs_endpoint.label = LFS endpoint
|
||||||
|
@ -1254,11 +1253,11 @@ n_tag_few=%s tags
|
||||||
released_this = released this
|
released_this = released this
|
||||||
file.title = %s at %s
|
file.title = %s at %s
|
||||||
file_raw = Raw
|
file_raw = Raw
|
||||||
file_follow = Follow Symlink
|
file_follow = Follow symlink
|
||||||
file_history = History
|
file_history = History
|
||||||
file_view_source = View Source
|
file_view_source = View source
|
||||||
file_view_rendered = View Rendered
|
file_view_rendered = View rendered
|
||||||
file_view_raw = View Raw
|
file_view_raw = View raw
|
||||||
file_permalink = Permalink
|
file_permalink = Permalink
|
||||||
file_too_large = The file is too large to be shown.
|
file_too_large = The file is too large to be shown.
|
||||||
invisible_runes_header = `This file contains invisible Unicode characters`
|
invisible_runes_header = `This file contains invisible Unicode characters`
|
||||||
|
@ -1313,8 +1312,8 @@ editor.name_your_file = Name your file…
|
||||||
editor.filename_help = Add a directory by typing its name followed by a slash ("/"). Remove a directory by typing backspace at the beginning of the input field.
|
editor.filename_help = Add a directory by typing its name followed by a slash ("/"). Remove a directory by typing backspace at the beginning of the input field.
|
||||||
editor.or = or
|
editor.or = or
|
||||||
editor.cancel_lower = Cancel
|
editor.cancel_lower = Cancel
|
||||||
editor.commit_signed_changes = Commit Signed Changes
|
editor.commit_signed_changes = Commit signed changes
|
||||||
editor.commit_changes = Commit Changes
|
editor.commit_changes = Commit changes
|
||||||
editor.add_tmpl = Add "<filename>"
|
editor.add_tmpl = Add "<filename>"
|
||||||
editor.add = Add %s
|
editor.add = Add %s
|
||||||
editor.update = Update %s
|
editor.update = Update %s
|
||||||
|
@ -1342,9 +1341,9 @@ editor.file_is_a_symlink = `"%s" is a symbolic link. Symbolic links cannot be ed
|
||||||
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
|
editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository.
|
||||||
editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
|
editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository.
|
||||||
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
|
editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository.
|
||||||
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
|
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit changes again</strong> to overwrite them.
|
||||||
editor.file_already_exists = A file named "%s" already exists in this repository.
|
editor.file_already_exists = A file named "%s" already exists in this repository.
|
||||||
editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge.
|
editor.commit_id_not_matching = The commit ID does not match the one you was editing. Commit to a new branch and then merge.
|
||||||
editor.push_out_of_date = The push appears to be out of date.
|
editor.push_out_of_date = The push appears to be out of date.
|
||||||
editor.commit_empty_file_header = Commit an empty file
|
editor.commit_empty_file_header = Commit an empty file
|
||||||
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
|
editor.commit_empty_file_text = The file you're about to commit is empty. Proceed?
|
||||||
|
@ -1419,26 +1418,26 @@ projects.edit_subheader = Projects organize issues and track progress.
|
||||||
projects.modify = Edit project
|
projects.modify = Edit project
|
||||||
projects.edit_success = Project "%s" has been updated.
|
projects.edit_success = Project "%s" has been updated.
|
||||||
projects.type.none = None
|
projects.type.none = None
|
||||||
projects.type.basic_kanban = Basic Kanban
|
projects.type.basic_kanban = Basic kanban
|
||||||
projects.type.bug_triage = Bug Triage
|
projects.type.bug_triage = Bug triage
|
||||||
projects.template.desc = Template
|
projects.template.desc = Template
|
||||||
projects.template.desc_helper = Select a project template to get started
|
projects.template.desc_helper = Select a project template to get started
|
||||||
projects.column.edit = Edit Column
|
projects.column.edit = Edit column
|
||||||
projects.column.edit_title = Name
|
projects.column.edit_title = Name
|
||||||
projects.column.new_title = Name
|
projects.column.new_title = Name
|
||||||
projects.column.new_submit = Create Column
|
projects.column.new_submit = Create column
|
||||||
projects.column.new = New Column
|
projects.column.new = New column
|
||||||
projects.column.set_default = Set Default
|
projects.column.set_default = Set default
|
||||||
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
|
projects.column.set_default_desc = Set this column as default for uncategorized issues and pulls
|
||||||
projects.column.delete = Delete Column
|
projects.column.delete = Delete column
|
||||||
projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
|
projects.column.deletion_desc = Deleting a project column moves all related issues to the default column. Continue?
|
||||||
projects.column.color = Color
|
projects.column.color = Color
|
||||||
projects.open = Open
|
projects.open = Open
|
||||||
projects.close = Close
|
projects.close = Close
|
||||||
projects.column.assigned_to = Assigned to
|
projects.column.assigned_to = Assigned to
|
||||||
projects.card_type.desc = Card Previews
|
projects.card_type.desc = Card previews
|
||||||
projects.card_type.images_and_text = Images and Text
|
projects.card_type.images_and_text = Images and text
|
||||||
projects.card_type.text_only = Text Only
|
projects.card_type.text_only = Text only
|
||||||
|
|
||||||
issues.desc = Organize bug reports, tasks and milestones.
|
issues.desc = Organize bug reports, tasks and milestones.
|
||||||
issues.filter_assignees = Filter Assignee
|
issues.filter_assignees = Filter Assignee
|
||||||
|
@ -1449,7 +1448,7 @@ issues.filter_reviewers = Filter Reviewer
|
||||||
issues.new = New issue
|
issues.new = New issue
|
||||||
issues.new.title_empty = Title cannot be empty
|
issues.new.title_empty = Title cannot be empty
|
||||||
issues.new.labels = Labels
|
issues.new.labels = Labels
|
||||||
issues.new.no_label = No label
|
issues.new.no_label = No labels
|
||||||
issues.new.clear_labels = Clear labels
|
issues.new.clear_labels = Clear labels
|
||||||
issues.new.projects = Projects
|
issues.new.projects = Projects
|
||||||
issues.new.clear_projects = Clear projects
|
issues.new.clear_projects = Clear projects
|
||||||
|
@ -1606,7 +1605,7 @@ issues.re_request_review=Re-request review
|
||||||
issues.is_stale = There have been changes to this PR since this review
|
issues.is_stale = There have been changes to this PR since this review
|
||||||
issues.remove_request_review=Remove review request
|
issues.remove_request_review=Remove review request
|
||||||
issues.remove_request_review_block=Can't remove review request
|
issues.remove_request_review_block=Can't remove review request
|
||||||
issues.dismiss_review = Dismiss Review
|
issues.dismiss_review = Dismiss review
|
||||||
issues.dismiss_review_warning = Are you sure you want to dismiss this review?
|
issues.dismiss_review_warning = Are you sure you want to dismiss this review?
|
||||||
issues.sign_in_require_desc = <a href="%s">Sign in</a> to join this conversation.
|
issues.sign_in_require_desc = <a href="%s">Sign in</a> to join this conversation.
|
||||||
issues.edit = Edit
|
issues.edit = Edit
|
||||||
|
@ -1633,7 +1632,8 @@ issues.label.filter_sort.alphabetically = Alphabetically
|
||||||
issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
|
issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically
|
||||||
issues.label.filter_sort.by_size = Smallest size
|
issues.label.filter_sort.by_size = Smallest size
|
||||||
issues.label.filter_sort.reverse_by_size = Largest size
|
issues.label.filter_sort.reverse_by_size = Largest size
|
||||||
issues.num_participants = %d participants
|
issues.num_participants_one = %d participant
|
||||||
|
issues.num_participants_few = %d participants
|
||||||
issues.attachment.open_tab = `Click to see "%s" in a new tab`
|
issues.attachment.open_tab = `Click to see "%s" in a new tab`
|
||||||
issues.attachment.download = `Click to download "%s"`
|
issues.attachment.download = `Click to download "%s"`
|
||||||
issues.subscribe = Subscribe
|
issues.subscribe = Subscribe
|
||||||
|
@ -1980,7 +1980,7 @@ signing.wont_sign.commitssigned = The merge will not be signed as all the associ
|
||||||
signing.wont_sign.approved = The merge will not be signed as the PR is not approved.
|
signing.wont_sign.approved = The merge will not be signed as the PR is not approved.
|
||||||
signing.wont_sign.not_signed_in = You are not signed in.
|
signing.wont_sign.not_signed_in = You are not signed in.
|
||||||
|
|
||||||
ext_wiki = Access to External Wiki
|
ext_wiki = Access to external Wiki
|
||||||
ext_wiki.desc = Link to an external wiki.
|
ext_wiki.desc = Link to an external wiki.
|
||||||
|
|
||||||
wiki = Wiki
|
wiki = Wiki
|
||||||
|
@ -2345,6 +2345,7 @@ settings.event_pull_request_review_request = Pull request review requested
|
||||||
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
|
settings.event_pull_request_review_request_desc = Pull request review requested or review request removed.
|
||||||
settings.event_pull_request_approvals = Pull request approvals
|
settings.event_pull_request_approvals = Pull request approvals
|
||||||
settings.event_pull_request_merge = Pull request merge
|
settings.event_pull_request_merge = Pull request merge
|
||||||
|
settings.event_pull_request_enforcement = Enforcement
|
||||||
settings.event_package = Package
|
settings.event_package = Package
|
||||||
settings.event_package_desc = Package created or deleted in a repository.
|
settings.event_package_desc = Package created or deleted in a repository.
|
||||||
settings.branch_filter = Branch filter
|
settings.branch_filter = Branch filter
|
||||||
|
@ -2372,9 +2373,9 @@ settings.web_hook_name_dingtalk = DingTalk
|
||||||
settings.web_hook_name_telegram = Telegram
|
settings.web_hook_name_telegram = Telegram
|
||||||
settings.web_hook_name_matrix = Matrix
|
settings.web_hook_name_matrix = Matrix
|
||||||
settings.web_hook_name_msteams = Microsoft Teams
|
settings.web_hook_name_msteams = Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
|
settings.web_hook_name_feishu = Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu = Feishu
|
settings.web_hook_name_feishu_only = Feishu
|
||||||
settings.web_hook_name_larksuite = Lark Suite
|
settings.web_hook_name_larksuite_only = Lark Suite
|
||||||
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork = WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist = Packagist
|
settings.web_hook_name_packagist = Packagist
|
||||||
settings.packagist_username = Packagist username
|
settings.packagist_username = Packagist username
|
||||||
|
@ -2459,6 +2460,8 @@ settings.block_on_official_review_requests = Block merge on official review requ
|
||||||
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
|
settings.block_on_official_review_requests_desc = Merging will not be possible when it has official review requests, even if there are enough approvals.
|
||||||
settings.block_outdated_branch = Block merge if pull request is outdated
|
settings.block_outdated_branch = Block merge if pull request is outdated
|
||||||
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
|
settings.block_outdated_branch_desc = Merging will not be possible when head branch is behind base branch.
|
||||||
|
settings.enforce_on_admins = Enforce this rule for repository admins
|
||||||
|
settings.enforce_on_admins_desc = Repository admins cannot bypass this rule.
|
||||||
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
|
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
|
||||||
settings.merge_style_desc = Merge styles
|
settings.merge_style_desc = Merge styles
|
||||||
settings.default_merge_style_desc = Default merge style
|
settings.default_merge_style_desc = Default merge style
|
||||||
|
@ -3180,7 +3183,7 @@ config.repo_root_path = Repository root path
|
||||||
config.lfs_root_path = LFS root path
|
config.lfs_root_path = LFS root path
|
||||||
config.log_file_root_path = Log path
|
config.log_file_root_path = Log path
|
||||||
config.script_type = Script type
|
config.script_type = Script type
|
||||||
config.reverse_auth_user = Reverse authentication user
|
config.reverse_auth_user = Reverse proxy authentication user
|
||||||
|
|
||||||
config.ssh_config = SSH configuration
|
config.ssh_config = SSH configuration
|
||||||
config.ssh_enabled = Enabled
|
config.ssh_enabled = Enabled
|
||||||
|
@ -3416,6 +3419,15 @@ years = %d years
|
||||||
raw_seconds = seconds
|
raw_seconds = seconds
|
||||||
raw_minutes = minutes
|
raw_minutes = minutes
|
||||||
|
|
||||||
|
[munits.data]
|
||||||
|
b = B
|
||||||
|
kib = KiB
|
||||||
|
mib = MiB
|
||||||
|
gib = GiB
|
||||||
|
tib = TiB
|
||||||
|
pib = PiB
|
||||||
|
eib = EiB
|
||||||
|
|
||||||
[dropzone]
|
[dropzone]
|
||||||
default_message = Drop files or click here to upload.
|
default_message = Drop files or click here to upload.
|
||||||
invalid_input_type = You cannot upload files of this type.
|
invalid_input_type = You cannot upload files of this type.
|
||||||
|
@ -3727,3 +3739,8 @@ normal_file = Normal file
|
||||||
executable_file = Executable file
|
executable_file = Executable file
|
||||||
symbolic_link = Symbolic link
|
symbolic_link = Symbolic link
|
||||||
submodule = Submodule
|
submodule = Submodule
|
||||||
|
|
||||||
|
[markup]
|
||||||
|
filepreview.line = Line %[1]d in %[2]s
|
||||||
|
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
|
||||||
|
filepreview.truncated = Preview has been truncated
|
||||||
|
|
|
@ -1561,7 +1561,7 @@ issues.label.filter_sort.alphabetically=Alfabéticamente
|
||||||
issues.label.filter_sort.reverse_alphabetically=Invertir alfabéticamente
|
issues.label.filter_sort.reverse_alphabetically=Invertir alfabéticamente
|
||||||
issues.label.filter_sort.by_size=Tamaño más pequeño
|
issues.label.filter_sort.by_size=Tamaño más pequeño
|
||||||
issues.label.filter_sort.reverse_by_size=Tamaño más grande
|
issues.label.filter_sort.reverse_by_size=Tamaño más grande
|
||||||
issues.num_participants=%d participantes
|
issues.num_participants_few=%d participantes
|
||||||
issues.attachment.open_tab='Haga clic para ver "%s" en una pestaña nueva'
|
issues.attachment.open_tab='Haga clic para ver "%s" en una pestaña nueva'
|
||||||
issues.attachment.download=`Haga clic para descargar "%s"`
|
issues.attachment.download=`Haga clic para descargar "%s"`
|
||||||
issues.subscribe=Suscribir
|
issues.subscribe=Suscribir
|
||||||
|
@ -2267,9 +2267,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Nombre de usuario Packagist
|
settings.packagist_username=Nombre de usuario Packagist
|
||||||
|
|
|
@ -1189,7 +1189,7 @@ issues.label.filter_sort.alphabetically=الفبایی
|
||||||
issues.label.filter_sort.reverse_alphabetically=برعکس ترتیب الفبا
|
issues.label.filter_sort.reverse_alphabetically=برعکس ترتیب الفبا
|
||||||
issues.label.filter_sort.by_size=کوچکترین اندازه
|
issues.label.filter_sort.by_size=کوچکترین اندازه
|
||||||
issues.label.filter_sort.reverse_by_size=بزرگترین اندازه
|
issues.label.filter_sort.reverse_by_size=بزرگترین اندازه
|
||||||
issues.num_participants=%d مشارکت کننده
|
issues.num_participants_few=%d مشارکت کننده
|
||||||
issues.attachment.open_tab=برای مشاهده "%s" در زبانه جدید، کلیک کنید
|
issues.attachment.open_tab=برای مشاهده "%s" در زبانه جدید، کلیک کنید
|
||||||
issues.attachment.download=`برای دریافت "%s" کلیک کنید`
|
issues.attachment.download=`برای دریافت "%s" کلیک کنید`
|
||||||
issues.subscribe=مشترک شدن
|
issues.subscribe=مشترک شدن
|
||||||
|
|
|
@ -920,7 +920,7 @@ issues.label.filter_sort.alphabetically=Aakkosjärjestyksessä
|
||||||
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
|
issues.label.filter_sort.reverse_alphabetically=Käänteisessä aakkosjärjestyksessä
|
||||||
issues.label.filter_sort.by_size=Pienin koko
|
issues.label.filter_sort.by_size=Pienin koko
|
||||||
issues.label.filter_sort.reverse_by_size=Suurin koko
|
issues.label.filter_sort.reverse_by_size=Suurin koko
|
||||||
issues.num_participants=%d osallistujaa
|
issues.num_participants_few=%d osallistujaa
|
||||||
issues.subscribe=Tilaa
|
issues.subscribe=Tilaa
|
||||||
issues.unsubscribe=Lopeta tilaus
|
issues.unsubscribe=Lopeta tilaus
|
||||||
issues.lock=Lukitse keskustelu
|
issues.lock=Lukitse keskustelu
|
||||||
|
@ -1184,8 +1184,8 @@ settings.web_hook_name_discord=Discord
|
||||||
settings.web_hook_name_dingtalk=DingTalk
|
settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.deploy_keys=Julkaisuavaimet
|
settings.deploy_keys=Julkaisuavaimet
|
||||||
settings.add_deploy_key=Lisää julkaisuavain
|
settings.add_deploy_key=Lisää julkaisuavain
|
||||||
|
|
|
@ -7,7 +7,7 @@ language = Wika
|
||||||
mirrors = Mga Mirror
|
mirrors = Mga Mirror
|
||||||
forks = Mga Fork
|
forks = Mga Fork
|
||||||
activities = Mga Aktibidad
|
activities = Mga Aktibidad
|
||||||
pull_requests = Mga Pull Request
|
pull_requests = Mga pull pequest
|
||||||
issues = Mga Isyu
|
issues = Mga Isyu
|
||||||
milestones = Mga Milestone
|
milestones = Mga Milestone
|
||||||
ok = OK
|
ok = OK
|
||||||
|
@ -18,10 +18,10 @@ save = I-save
|
||||||
add = Magdagdag
|
add = Magdagdag
|
||||||
remove_all = Tanggalin lahat
|
remove_all = Tanggalin lahat
|
||||||
remove_label_str = Tanggalin ang item "%s"
|
remove_label_str = Tanggalin ang item "%s"
|
||||||
edit = I-edit
|
edit = Baguhin
|
||||||
enabled = Naka-enable
|
enabled = Naka-enable
|
||||||
copy = Kopyahin
|
copy = Kopyahin
|
||||||
copy_content = Kopyahin ang content
|
copy_content = Kopyahin ang nilalaman
|
||||||
copy_branch = Kopyahin ang pangalan ng branch
|
copy_branch = Kopyahin ang pangalan ng branch
|
||||||
copy_success = Kinopya!
|
copy_success = Kinopya!
|
||||||
copy_error = Nabigo ang pagkopya
|
copy_error = Nabigo ang pagkopya
|
||||||
|
@ -35,11 +35,11 @@ copy_type_unsupported = Hindi makokopya ang itong uri ng file
|
||||||
error404 = Ang pahina na sinusubukan mong bisitahin ay alinman <strong>hindi umiiral</strong> o <strong>wala kang pahintulot</strong> para itignan.
|
error404 = Ang pahina na sinusubukan mong bisitahin ay alinman <strong>hindi umiiral</strong> o <strong>wala kang pahintulot</strong> para itignan.
|
||||||
version = Bersyon
|
version = Bersyon
|
||||||
powered_by = Pinapatakbo ng %s
|
powered_by = Pinapatakbo ng %s
|
||||||
explore = Mag-explore
|
explore = Tuklasin
|
||||||
help = Tulong
|
help = Tulong
|
||||||
logo = Logo
|
logo = Logo
|
||||||
sign_in = Mag-Sign In
|
sign_in = Mag-Sign In
|
||||||
sign_in_with_provider = Mag-sign in gamit ng %s
|
sign_in_with_provider = Mag-sign in gamit ang %s
|
||||||
sign_in_or = o
|
sign_in_or = o
|
||||||
sign_out = Mag-Sign Out
|
sign_out = Mag-Sign Out
|
||||||
sign_up = Magrehistro
|
sign_up = Magrehistro
|
||||||
|
@ -79,19 +79,19 @@ webauthn_error_empty = Kailangan mong maglapat ng pangalan para sa key na ito.
|
||||||
webauthn_reload = I-reload
|
webauthn_reload = I-reload
|
||||||
repository = Repository
|
repository = Repository
|
||||||
organization = Organisasyon
|
organization = Organisasyon
|
||||||
mirror = Mirror
|
mirror = Salamin
|
||||||
new_repo = Bagong repository
|
new_repo = Bagong repository
|
||||||
new_migrate = Bagong migration
|
new_migrate = Bagong migration
|
||||||
new_mirror = Bagong mirror
|
new_mirror = Bagong salamin
|
||||||
new_fork = Bagong repository fork
|
new_fork = Bagong repository fork
|
||||||
new_org = Bagong organisasyon
|
new_org = Bagong organisasyon
|
||||||
new_project = Bagong proyekto
|
new_project = Bagong proyekto
|
||||||
new_project_column = Bagong column
|
new_project_column = Bagong column
|
||||||
admin_panel = Pangangasiwa ng Site
|
admin_panel = Pangangasiwa ng site
|
||||||
account_settings = Mga Setting ng Account
|
account_settings = Mga setting ng Account
|
||||||
settings = Mga Setting
|
settings = Mga Setting
|
||||||
your_profile = Profile
|
your_profile = Profile
|
||||||
your_starred = Naka-star
|
your_starred = Naka-bitwin
|
||||||
your_settings = Mga Setting
|
your_settings = Mga Setting
|
||||||
all = Lahat
|
all = Lahat
|
||||||
go_back = Bumalik
|
go_back = Bumalik
|
||||||
|
@ -126,10 +126,10 @@ filter.public = Publiko
|
||||||
filter.private = Pribado
|
filter.private = Pribado
|
||||||
notifications = Mga Abiso
|
notifications = Mga Abiso
|
||||||
active_stopwatch = Aktibong Tagasubaybay ng Oras
|
active_stopwatch = Aktibong Tagasubaybay ng Oras
|
||||||
locked = Naka-lock
|
locked = Naka-kandado
|
||||||
preview = I-preview
|
preview = I-preview
|
||||||
confirm_delete_artifact = Sigurado ka bang gusto mong burahin ang artifact na "%s"?
|
confirm_delete_artifact = Sigurado ka bang gusto mong burahin ang artifact na "%s"?
|
||||||
rerun_all = Patakbuhin muli ang lahat ng mga job
|
rerun_all = Patakbuhin muli ang lahat ng mga trabaho
|
||||||
add_all = Idagdag lahat
|
add_all = Idagdag lahat
|
||||||
copy_hash = Kopyahin ang hash
|
copy_hash = Kopyahin ang hash
|
||||||
error = Error
|
error = Error
|
||||||
|
@ -138,6 +138,8 @@ loading = Naglo-load…
|
||||||
confirm_delete_selected = Kumpirmahin na burahin ang lahat ng piniling item?
|
confirm_delete_selected = Kumpirmahin na burahin ang lahat ng piniling item?
|
||||||
home = Panimula
|
home = Panimula
|
||||||
dashboard = Dashboard
|
dashboard = Dashboard
|
||||||
|
more_items = Higit pang mga item
|
||||||
|
invalid_data = Hindi wastong data: %v
|
||||||
|
|
||||||
[home]
|
[home]
|
||||||
search_repos = Maghanap ng Repository…
|
search_repos = Maghanap ng Repository…
|
||||||
|
@ -229,7 +231,7 @@ err_empty_admin_email = Hindi maaring walang laman ang administrator email.
|
||||||
err_admin_name_is_reserved = Hindi angkop ang Administrator Username, naka-reserve ang username
|
err_admin_name_is_reserved = Hindi angkop ang Administrator Username, naka-reserve ang username
|
||||||
err_admin_name_is_invalid = Hindi angkop ang Administrator Username
|
err_admin_name_is_invalid = Hindi angkop ang Administrator Username
|
||||||
general_title = Mga General Setting
|
general_title = Mga General Setting
|
||||||
app_name = Pangalan ng Instansya
|
app_name = Pamagat ng instansya
|
||||||
app_name_helper = Maari mong ilagay ang pangalan ng iyong kompanya dito.
|
app_name_helper = Maari mong ilagay ang pangalan ng iyong kompanya dito.
|
||||||
repo_path_helper = Ang mga remote Git repository ay mase-save sa directory na ito.
|
repo_path_helper = Ang mga remote Git repository ay mase-save sa directory na ito.
|
||||||
repo_path = Root path ng Repository
|
repo_path = Root path ng Repository
|
||||||
|
@ -288,7 +290,7 @@ invalid_db_setting = Hindi angkop ang mga database setting: %v
|
||||||
invalid_db_table = Hindi angkop ang database table na "%s": %v
|
invalid_db_table = Hindi angkop ang database table na "%s": %v
|
||||||
invalid_repo_path = Hindi angkop ang repository root path: %v
|
invalid_repo_path = Hindi angkop ang repository root path: %v
|
||||||
invalid_app_data_path = Hindi angkop ang app data path: %v
|
invalid_app_data_path = Hindi angkop ang app data path: %v
|
||||||
run_user_not_match = Ang "tumakbo bilang" na username ay hindi ang kasulukuyang username: %s -> %s
|
run_user_not_match = Ang "user na tatakbo bilang" na username ay hindi ang kasulukuyang username: %s -> %s
|
||||||
internal_token_failed = Nabigong maka-generate ng internal token: %v
|
internal_token_failed = Nabigong maka-generate ng internal token: %v
|
||||||
secret_key_failed = Nabigong maka-generate ng secret key: %v
|
secret_key_failed = Nabigong maka-generate ng secret key: %v
|
||||||
save_config_failed = Nabigong i-save ang configuration: %v
|
save_config_failed = Nabigong i-save ang configuration: %v
|
||||||
|
@ -352,7 +354,7 @@ app_desc = Isang hindi masakit, at naka self-host na Git service
|
||||||
install = Madaling i-install
|
install = Madaling i-install
|
||||||
platform = Cross-platform
|
platform = Cross-platform
|
||||||
platform_desc = Tumatakbo kahit saan ang Forgejo na ang <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> ay nakaka-compile para sa: Windows, macOS, Linux, ARM, atbp. Piliin ang isa na gusto mo!
|
platform_desc = Tumatakbo kahit saan ang Forgejo na ang <a target="_blank" rel="noopener noreferrer" href="https://go.dev/">Go</a> ay nakaka-compile para sa: Windows, macOS, Linux, ARM, atbp. Piliin ang isa na gusto mo!
|
||||||
lightweight = Hindi Mabigat
|
lightweight = Magaan
|
||||||
lightweight_desc = Mababa ang minimal requirements ng Forgejo at tatakbo sa isang murang Raspberry Pi. Tipirin ang enerhiya ng iyong machine!
|
lightweight_desc = Mababa ang minimal requirements ng Forgejo at tatakbo sa isang murang Raspberry Pi. Tipirin ang enerhiya ng iyong machine!
|
||||||
license = Open Source
|
license = Open Source
|
||||||
install_desc = <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#installation-from-binary">Patakbuhin ang binary</a> para sa iyong platform, i-ship gamit ang <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#container-image">Docker</a>, o kunin ito nang <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download">naka-package</a>.
|
install_desc = <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#installation-from-binary">Patakbuhin ang binary</a> para sa iyong platform, i-ship gamit ang <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download/#container-image">Docker</a>, o kunin ito nang <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org/download">naka-package</a>.
|
||||||
|
@ -921,6 +923,11 @@ oauth2_application_create_description = Ang mga OAuth2 application ay pinapayaga
|
||||||
oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon.
|
oauth2_application_locked = Ang Forgejo ay pini-pre register ang ibang mga OAuth2 application sa startup kapag naka-enable sa config. Para iwasan ang hindi inaasahang gawain, hindi ito maaring i-edit o tanggalin. Mangyaring sumangguni sa dokumentasyon ng OAuth2 para sa karagdagang impormasyon.
|
||||||
remove_account_link_desc = Ang pagtanggal ng naka-link na account ay babawiin ang pag-access nito sa iyong Forgejo account. Magpatuloy?
|
remove_account_link_desc = Ang pagtanggal ng naka-link na account ay babawiin ang pag-access nito sa iyong Forgejo account. Magpatuloy?
|
||||||
visibility.public_tooltip = Makikita ng lahat
|
visibility.public_tooltip = Makikita ng lahat
|
||||||
|
hints = Mga Pahiwatig
|
||||||
|
additional_repo_units_hint_description = Mag-display ng "Magdagdag pa ng mga unit..." na button para sa mga repository na hindi naka-enable ang lahat ng mga available na unit.
|
||||||
|
additional_repo_units_hint = Hikayatin ang pag-enable ng karagdagang mga repository unit
|
||||||
|
update_hints = I-update ang mga pahiwatig
|
||||||
|
update_hints_success = Na-update na ang mga pahiwatig.
|
||||||
|
|
||||||
[repo]
|
[repo]
|
||||||
template_description = Ang mga template repository ay pinapayagan ang mga gumagamit na mag-generate ng mga bagong repository na may magkatulad na istraktura ng direktoryo, mga file, at opsyonal na mga setting.
|
template_description = Ang mga template repository ay pinapayagan ang mga gumagamit na mag-generate ng mga bagong repository na may magkatulad na istraktura ng direktoryo, mga file, at opsyonal na mga setting.
|
||||||
|
@ -950,3 +957,103 @@ fork_from = I-fork mula sa
|
||||||
already_forked = Na-fork mo na ang %s
|
already_forked = Na-fork mo na ang %s
|
||||||
fork_to_different_account = Mag-fork sa ibang account
|
fork_to_different_account = Mag-fork sa ibang account
|
||||||
fork_visibility_helper = Ang visibility ng isang naka-fork na repository ay hindi maaring baguhin.
|
fork_visibility_helper = Ang visibility ng isang naka-fork na repository ay hindi maaring baguhin.
|
||||||
|
open_with_editor = Buksan gamit ang %s
|
||||||
|
download_bundle = I-download ang BUNDLE
|
||||||
|
repo_gitignore_helper_desc = Piliin kung anong mga file na hindi susubaybayin sa listahan ng mga template para sa mga karaniwang wika. Ang mga tipikal na artifact na ginagawa ng mga build tool ng wika ay kasama sa .gitignore ng default.
|
||||||
|
adopt_preexisting = Mag-adopt ng mga umiiral na file
|
||||||
|
repo_gitignore_helper = Pumili ng mga .gitignore template.
|
||||||
|
readme_helper_desc = Ito ang lugar kung saan makakasulat ka ng kumpletong deskripsyon para sa iyong proyekto.
|
||||||
|
trust_model_helper_collaborator_committer = Katulong+Committer: I-trust ang mga signature batay sa mga katulong na tumutugma sa committer
|
||||||
|
mirror_interval = Interval ng mirror (ang mga wastong unit ng oras ay "h", "m", "s"). 0 para i-disable ang periodic sync. (Pinakamababang interval: %s)
|
||||||
|
transfer.reject_desc = Kanselahin ang pag-transfer mula sa "%s"
|
||||||
|
mirror_lfs_endpoint_desc = Ang sync ay susubukang gamitin ang clone url upang <a target="_blank" rel="noopener noreferrer" href="%s">matukoy ang LFS server</a>. Maari ka rin tumukoy ng isang custom na endpoint kapag ang repository LFS data ay nilalagay sa ibang lugar.
|
||||||
|
adopt_search = Ilagay ang username para maghanap ng mga unadopted repository... (iwanang walang laman para hanapin lahat)
|
||||||
|
object_format = Format ng object
|
||||||
|
readme_helper = Pumili ng README file template.
|
||||||
|
default_branch_helper = Ang default branch ay ang base branch para sa mga pull request at mga commit ng code.
|
||||||
|
mirror_interval_invalid = Hindi wasto ang mirror interval.
|
||||||
|
mirror_sync = na-sync
|
||||||
|
mirror_sync_on_commit = I-sync kapag na-push ang mga commit
|
||||||
|
mirror_address = Mag-clone mula sa URL
|
||||||
|
mirror_address_desc = Maglagay ng anumang mga kinakailangang kredensyal sa Awtorisasyon na seksyon.
|
||||||
|
desc.archived = Naka-archive
|
||||||
|
desc.sha256 = SHA256
|
||||||
|
template.items = Mga template item
|
||||||
|
template.git_content = Nilalaman ng Git (Default na branch)
|
||||||
|
reactions_more = at %d pa
|
||||||
|
unit_disabled = Na-disable ng tagapangasiwa ng site ang itong seksyon ng repository.
|
||||||
|
create_repo = Gumawa ng Repository
|
||||||
|
generate_from = I-generate mula sa
|
||||||
|
repo_desc = Deskripsyon
|
||||||
|
fork_branch = Branch na mako-clone sa fork
|
||||||
|
all_branches = Lahat ng mga branch
|
||||||
|
fork_no_valid_owners = Hindi mapo-fork ang repository dahil walang mga wastong may-ari.
|
||||||
|
use_template = Gamitin ang template na ito
|
||||||
|
download_zip = I-download ang ZIP
|
||||||
|
download_tar = I-download ang TAR.GZ
|
||||||
|
issue_labels = Mga label ng isyu
|
||||||
|
generate_repo = I-generate ang repository
|
||||||
|
repo_desc_helper = Maglagay ng maikling deskripsyon (opsyonal)
|
||||||
|
repo_lang = Wika
|
||||||
|
issue_labels_helper = Pumili ng label set ng isyu.
|
||||||
|
license = Lisensya
|
||||||
|
license_helper = Pumili ng file ng lisensya.
|
||||||
|
license_helper_desc = Ang lisensya ay namamahala kung ano ang pwede at hindi pwedeng gawin ng mga ibang tao sa iyong code. Hindi sigurado kung alin ang wasto para sa iyong proyekto? Tignan ang <a target="_blank" rel="noopener noreferrer" href="%s">Pumili ng lisensya.</a>
|
||||||
|
object_format_helper = Object format ng repository. Hindi mababago mamaya. Ang SHA1 ang pinaka-compatible.
|
||||||
|
readme = README
|
||||||
|
auto_init = I-initialize ang repository (Nagdadagdag ng .gitignore, Lisensya, at README)
|
||||||
|
trust_model_helper = Pumili ng trust model para sa signature verification. Ang mga posibleng opsyon ay:
|
||||||
|
trust_model_helper_collaborator = Katulong: I-trust ang mga signature batay sa mga katulong
|
||||||
|
trust_model_helper_committer = Commiter: I-trust ang mga signature na tumutugma sa mga commiter
|
||||||
|
trust_model_helper_default = Default: Gamitin ang default trust model para sa installation na ito
|
||||||
|
default_branch = Default na branch
|
||||||
|
default_branch_label = default
|
||||||
|
mirror_prune = Pungusan
|
||||||
|
mirror_prune_desc = Tanggalin ang mga antikuwado na sangguni ng remote-tracking
|
||||||
|
mirror_address_url_invalid = Ang ibinigay na url ay hindi wasto. Kailangan mong i-escape ang lahat ng mga components ng URL ng tama.
|
||||||
|
mirror_address_protocol_invalid = Ang ibinigay na URL ay hindi wasto. Ang http(s):// o git:// na lokasyon lamang ay magagamit para sa pag-mirror.
|
||||||
|
mirror_lfs = Large File Storage (LFS)
|
||||||
|
mirror_lfs_desc = I-activate ang pag-mirror ng LFS data.
|
||||||
|
mirror_lfs_endpoint = Endpoint ng LFS
|
||||||
|
mirror_last_synced = Huling na-synchronize
|
||||||
|
mirror_password_placeholder = (Hindi nabago)
|
||||||
|
mirror_password_blank_placeholder = (Hindi tinakda)
|
||||||
|
mirror_password_help = Palitan ang username para burahin ang na-store na password.
|
||||||
|
watchers = Mga nanonood
|
||||||
|
stargazers = Mga Stargazer
|
||||||
|
stars_remove_warning = Tatanggalin nito ang lahat ng mga star sa repository na ito.
|
||||||
|
forks = Mga fork
|
||||||
|
language_other = Iba
|
||||||
|
adopt_preexisting_label = Mag-adopt ng mga file
|
||||||
|
adopt_preexisting_content = Gumawa ng repository mula sa %s
|
||||||
|
transfer.accept = Tanggapin ang paglipat
|
||||||
|
transfer.accept_desc = Ilipat sa "%s"
|
||||||
|
transfer.reject = Tanggihan ang paglipat
|
||||||
|
transfer.no_permission_to_accept = Wala kang pahintulot para tanggapin ang palilipat na ito.
|
||||||
|
transfer.no_permission_to_reject = Wala kang pahintulot para tanggihan ang palilipat na ito.
|
||||||
|
desc.private = Pribado
|
||||||
|
desc.public = Publiko
|
||||||
|
desc.template = Template
|
||||||
|
desc.internal = Internal
|
||||||
|
template.git_hooks = Mga hook ng Git
|
||||||
|
|
||||||
|
[search]
|
||||||
|
commit_kind = Maghanap ng mga commit...
|
||||||
|
keyword_search_unavailable = Kasalukuyang hindi available ang paghahanap sa pamamagitan ng keyword. Mangyaring makipag-ugnayan sa tagapangasiwa ng site.
|
||||||
|
search = Maghanap...
|
||||||
|
type_tooltip = Uri ng paghahanap
|
||||||
|
fuzzy = Fuzzy
|
||||||
|
fuzzy_tooltip = Samahan ang mga resulta na tumutugma rin sa search term nang malapit
|
||||||
|
match = Tugma
|
||||||
|
match_tooltip = Samahan lang ang mga resulta na tumutugma sa eksaktong search term
|
||||||
|
repo_kind = Maghanap ng mga repo...
|
||||||
|
user_kind = Maghanap ng mga user...
|
||||||
|
org_kind = Maghanap ng mga org...
|
||||||
|
team_kind = Maghanap ng mga koponan...
|
||||||
|
code_kind = Maghanap ng code...
|
||||||
|
code_search_unavailable = Kasalukuyang hindi available ang paghahanap ng code. Mangyaring makipag-ugnayan sa tagapangasiwa ng site.
|
||||||
|
package_kind = Maghanap ng mga pakete...
|
||||||
|
project_kind = Maghanap ng mga proyekto...
|
||||||
|
branch_kind = Maghanap ng mga branch...
|
||||||
|
runner_kind = Maghanap ng mga runner...
|
||||||
|
no_results = Walang mga tumutugma na resulta na nahanap.
|
|
@ -156,6 +156,8 @@ filter.private = Privé
|
||||||
filter = Filtre
|
filter = Filtre
|
||||||
filter.is_mirror = Répliqué
|
filter.is_mirror = Répliqué
|
||||||
toggle_menu = Menu va-et-vient
|
toggle_menu = Menu va-et-vient
|
||||||
|
more_items = Plus d'éléments
|
||||||
|
invalid_data = Données invalides : %v
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=Barre de navigation
|
navbar=Barre de navigation
|
||||||
|
@ -982,6 +984,11 @@ blocked_since = Bloqué depuis %s
|
||||||
user_unblock_success = Cet utilisateur a été débloqué avec succès.
|
user_unblock_success = Cet utilisateur a été débloqué avec succès.
|
||||||
user_block_success = Cet utilisateur a été bloqué avec succès.
|
user_block_success = Cet utilisateur a été bloqué avec succès.
|
||||||
change_password = Modifier le mot de passe
|
change_password = Modifier le mot de passe
|
||||||
|
hints = Suggestions
|
||||||
|
additional_repo_units_hint_description = Afficher un bouton "Ajouter plus d'unités..." pour les dépôts qui n'ont pas toutes les unités disponibles activées.
|
||||||
|
additional_repo_units_hint = Encourager l'ajout de nouvelles unités pour le dépôt
|
||||||
|
update_hints = Mettre à jour les suggestions
|
||||||
|
update_hints_success = Les suggestions ont été mises à jour.
|
||||||
|
|
||||||
[repo]
|
[repo]
|
||||||
new_repo_helper=Un dépôt contient tous les fichiers d’un projet, ainsi que l’historique de leurs modifications. Vous avez déjà ça ailleurs ? <a href="%s">Migrez-le ici.</a>
|
new_repo_helper=Un dépôt contient tous les fichiers d’un projet, ainsi que l’historique de leurs modifications. Vous avez déjà ça ailleurs ? <a href="%s">Migrez-le ici.</a>
|
||||||
|
@ -1224,7 +1231,7 @@ file_raw=Brut
|
||||||
file_history=Historique
|
file_history=Historique
|
||||||
file_view_source=Voir le code source
|
file_view_source=Voir le code source
|
||||||
file_view_rendered=Voir le rendu
|
file_view_rendered=Voir le rendu
|
||||||
file_view_raw=Voir le Raw
|
file_view_raw=Voir le contenu brut
|
||||||
file_permalink=Lien permanent
|
file_permalink=Lien permanent
|
||||||
file_too_large=Le fichier est trop gros pour être affiché.
|
file_too_large=Le fichier est trop gros pour être affiché.
|
||||||
invisible_runes_header=`Ce fichier contient des caractères Unicode invisibles.`
|
invisible_runes_header=`Ce fichier contient des caractères Unicode invisibles.`
|
||||||
|
@ -1396,7 +1403,7 @@ projects.column.set_default_desc=Les tickets et demandes d’ajout non-catégori
|
||||||
projects.column.unset_default=Défaire par défaut
|
projects.column.unset_default=Défaire par défaut
|
||||||
projects.column.unset_default_desc=Les tickets et demandes d'ajouts non-catégorisés seront placés dans une colonne idoine.
|
projects.column.unset_default_desc=Les tickets et demandes d'ajouts non-catégorisés seront placés dans une colonne idoine.
|
||||||
projects.column.delete=Supprimer la colonne
|
projects.column.delete=Supprimer la colonne
|
||||||
projects.column.deletion_desc=La suppression d'une colonne de projet déplace tous les tickets liés à "Non catégorisé" Continuer ?
|
projects.column.deletion_desc=La suppression d'une colonne de projet déplace tous les tickets liés à la colonne par défaut. Continuer ?
|
||||||
projects.column.color=Couleur
|
projects.column.color=Couleur
|
||||||
projects.open=Ouvrir
|
projects.open=Ouvrir
|
||||||
projects.close=Fermer
|
projects.close=Fermer
|
||||||
|
@ -1599,7 +1606,7 @@ issues.label.filter_sort.alphabetically=Par ordre alphabétique
|
||||||
issues.label.filter_sort.reverse_alphabetically=Par ordre alphabétique inversé
|
issues.label.filter_sort.reverse_alphabetically=Par ordre alphabétique inversé
|
||||||
issues.label.filter_sort.by_size=Plus petite taille
|
issues.label.filter_sort.by_size=Plus petite taille
|
||||||
issues.label.filter_sort.reverse_by_size=Plus grande taille
|
issues.label.filter_sort.reverse_by_size=Plus grande taille
|
||||||
issues.num_participants=%d participants
|
issues.num_participants_few=%d participants
|
||||||
issues.attachment.open_tab=`Cliquez ici pour voir « %s » dans un nouvel onglet.`
|
issues.attachment.open_tab=`Cliquez ici pour voir « %s » dans un nouvel onglet.`
|
||||||
issues.attachment.download=`Cliquez pour télécharger « %s ».`
|
issues.attachment.download=`Cliquez pour télécharger « %s ».`
|
||||||
issues.subscribe=S’abonner
|
issues.subscribe=S’abonner
|
||||||
|
@ -2231,7 +2238,7 @@ settings.githook_edit_desc=Si un Hook est inactif, un exemple de contenu vous se
|
||||||
settings.githook_name=Nom du hook
|
settings.githook_name=Nom du hook
|
||||||
settings.githook_content=Contenu du Hook
|
settings.githook_content=Contenu du Hook
|
||||||
settings.update_githook=Mettre le Hook à jour
|
settings.update_githook=Mettre le Hook à jour
|
||||||
settings.add_webhook_desc=Forgejo enverra à l'URL cible des requêtes <code>POST</code> avec un type de contenu spécifié. Lire la suite dans le <a target="_blank" rel="noopener noreferrer" href="%s">guide des webhooks</a>.
|
settings.add_webhook_desc=Forgejo enverra à l'URL cible des requêtes <code>POST</code> avec le Content-Type spécifié. Lire la suite dans le <a target="_blank" rel="noopener noreferrer" href="%s">guide des webhooks</a>.
|
||||||
settings.payload_url=URL cible
|
settings.payload_url=URL cible
|
||||||
settings.http_method=Méthode HTTP
|
settings.http_method=Méthode HTTP
|
||||||
settings.content_type=Type de contenu POST
|
settings.content_type=Type de contenu POST
|
||||||
|
@ -2317,9 +2324,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Nom d'utilisateur Packagist
|
settings.packagist_username=Nom d'utilisateur Packagist
|
||||||
|
@ -2697,6 +2704,18 @@ settings.confirmation_string = Chaine de confirmation
|
||||||
pulls.agit_explanation = Créé par le workflow AGit. AGit permet aux contributeurs de proposer des modifications en utilisant "git push" sans créer une bifurcation ou une nouvelle branche.
|
pulls.agit_explanation = Créé par le workflow AGit. AGit permet aux contributeurs de proposer des modifications en utilisant "git push" sans créer une bifurcation ou une nouvelle branche.
|
||||||
pulls.merged_title_desc_one = fusionné %[1]d commit depuis <code>%[2]s</code> vers <code>%[3]s</code> %[4]s
|
pulls.merged_title_desc_one = fusionné %[1]d commit depuis <code>%[2]s</code> vers <code>%[3]s</code> %[4]s
|
||||||
pulls.title_desc_one = veut fusionner %[1]d commit depuis <code>%[2]s</code> vers <code id="branch_target">%[3]s</code>
|
pulls.title_desc_one = veut fusionner %[1]d commit depuis <code>%[2]s</code> vers <code id="branch_target">%[3]s</code>
|
||||||
|
stars = Étoiles
|
||||||
|
n_tag_few = %s étiquettes
|
||||||
|
editor.commit_id_not_matching = L'ID de la révision ne correspond pas à celui que vous éditiez. Appliquez les modifications à une nouvelle branche puis procédez à la fusion.
|
||||||
|
commits.search_branch = Cette branche
|
||||||
|
open_with_editor = Ouvrir avec %s
|
||||||
|
pulls.ready_for_review = Prêt à être évalué ?
|
||||||
|
n_commit_one = %s commit
|
||||||
|
n_commit_few = %s commits
|
||||||
|
n_branch_one = %s branch
|
||||||
|
n_branch_few = %s branches
|
||||||
|
n_tag_one = %s étiquettes
|
||||||
|
editor.push_out_of_date = Le push semble obsolète.
|
||||||
|
|
||||||
[graphs]
|
[graphs]
|
||||||
component_loading=Chargement de %s…
|
component_loading=Chargement de %s…
|
||||||
|
@ -3754,6 +3773,7 @@ component_failed_to_load = Une erreur inattendue s'est produite.
|
||||||
contributors.what = contributions
|
contributors.what = contributions
|
||||||
component_loading = Chargement %s...
|
component_loading = Chargement %s...
|
||||||
component_loading_failed = Échec de chargement de %s
|
component_loading_failed = Échec de chargement de %s
|
||||||
|
|
||||||
code_frequency.what = fŕequence de code
|
code_frequency.what = fŕequence de code
|
||||||
recent_commits.what = commits récents
|
recent_commits.what = commits récents
|
||||||
|
|
||||||
|
@ -3762,3 +3782,20 @@ recent_commits.what = commits récents
|
||||||
search = Rechercher...
|
search = Rechercher...
|
||||||
type_tooltip = Type de recherche
|
type_tooltip = Type de recherche
|
||||||
fuzzy = Approximatif
|
fuzzy = Approximatif
|
||||||
|
code_search_by_git_grep = Les résultats de recherche dans le code sont fournis par "git grep". Les résultats pourraient être plus pertinents si l'administrateur du site active les indexeurs de dépôt.
|
||||||
|
runner_kind = Chercher les runners...
|
||||||
|
no_results = Aucun résultat n'a été trouvé.
|
||||||
|
keyword_search_unavailable = La recherche par mot-clé n'est pas disponible actuellement. Veuillez contacter l'administrateur du site.
|
||||||
|
fuzzy_tooltip = Inclure les résultats proches des termes recherchés
|
||||||
|
match = Correspondance
|
||||||
|
match_tooltip = Uniquement inclure les résultats correspondant exactement aux termes recherchés
|
||||||
|
repo_kind = Chercher dans le dépôt...
|
||||||
|
user_kind = Chercher les utilisateurs...
|
||||||
|
org_kind = Chercher les organisations...
|
||||||
|
team_kind = Chercher les équipes...
|
||||||
|
code_kind = Chercher le code...
|
||||||
|
code_search_unavailable = La recherche dans le code n'est pas disponible. Veuillez contacter l'administrateur du site.
|
||||||
|
package_kind = Chercher les paquets...
|
||||||
|
project_kind = Chercher les projets...
|
||||||
|
branch_kind = Chercher les branches...
|
||||||
|
commit_kind = Chercher les commits...
|
|
@ -857,7 +857,7 @@ issues.label.filter_sort.alphabetically=Betűrendben
|
||||||
issues.label.filter_sort.reverse_alphabetically=Fordított betűrendben
|
issues.label.filter_sort.reverse_alphabetically=Fordított betűrendben
|
||||||
issues.label.filter_sort.by_size=Legkisebb méret
|
issues.label.filter_sort.by_size=Legkisebb méret
|
||||||
issues.label.filter_sort.reverse_by_size=Legnagyobb méret
|
issues.label.filter_sort.reverse_by_size=Legnagyobb méret
|
||||||
issues.num_participants=%d Résztvevő
|
issues.num_participants_few=%d Résztvevő
|
||||||
issues.attachment.open_tab=`A(z) "%s" megnyitása új fülön`
|
issues.attachment.open_tab=`A(z) "%s" megnyitása új fülön`
|
||||||
issues.attachment.download=`Kattintson a(z) "%s" letöltéséhez`
|
issues.attachment.download=`Kattintson a(z) "%s" letöltéséhez`
|
||||||
issues.subscribe=Feliratkozás
|
issues.subscribe=Feliratkozás
|
||||||
|
|
|
@ -729,7 +729,7 @@ issues.label_edit=Sunting
|
||||||
issues.label_delete=Hapus
|
issues.label_delete=Hapus
|
||||||
issues.label.filter_sort.alphabetically=Urutan abjad
|
issues.label.filter_sort.alphabetically=Urutan abjad
|
||||||
issues.label.filter_sort.reverse_alphabetically=Membalikkan menurut abjad
|
issues.label.filter_sort.reverse_alphabetically=Membalikkan menurut abjad
|
||||||
issues.num_participants=%d peserta
|
issues.num_participants_few=%d peserta
|
||||||
issues.attachment.open_tab=`Klik untuk melihat "%s" di tab baru`
|
issues.attachment.open_tab=`Klik untuk melihat "%s" di tab baru`
|
||||||
issues.attachment.download=`Klik untuk mengunduh "%s"`
|
issues.attachment.download=`Klik untuk mengunduh "%s"`
|
||||||
issues.subscribe=Berlangganan
|
issues.subscribe=Berlangganan
|
||||||
|
|
|
@ -1065,7 +1065,7 @@ settings.web_hook_name_discord=Discord
|
||||||
settings.web_hook_name_dingtalk=DingTalk
|
settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.title=Heiti
|
settings.title=Heiti
|
||||||
settings.deploy_key_content=Innihald
|
settings.deploy_key_content=Innihald
|
||||||
settings.branches=Greinar
|
settings.branches=Greinar
|
||||||
|
|
|
@ -1478,7 +1478,7 @@ issues.label.filter_sort.alphabetically=In ordine alfabetico
|
||||||
issues.label.filter_sort.reverse_alphabetically=In ordine alfabetico inverso
|
issues.label.filter_sort.reverse_alphabetically=In ordine alfabetico inverso
|
||||||
issues.label.filter_sort.by_size=Dimensione più piccola
|
issues.label.filter_sort.by_size=Dimensione più piccola
|
||||||
issues.label.filter_sort.reverse_by_size=Dimensione più grande
|
issues.label.filter_sort.reverse_by_size=Dimensione più grande
|
||||||
issues.num_participants=%d partecipanti
|
issues.num_participants_few=%d partecipanti
|
||||||
issues.attachment.open_tab=`Clicca per vedere "%s" in una nuova scheda`
|
issues.attachment.open_tab=`Clicca per vedere "%s" in una nuova scheda`
|
||||||
issues.attachment.download=`Clicca qui per scaricare "%s"`
|
issues.attachment.download=`Clicca qui per scaricare "%s"`
|
||||||
issues.subscribe=Iscriviti
|
issues.subscribe=Iscriviti
|
||||||
|
@ -2085,9 +2085,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Nome utente Packagist
|
settings.packagist_username=Nome utente Packagist
|
||||||
|
|
|
@ -55,7 +55,7 @@ repository=リポジトリ
|
||||||
organization=組織
|
organization=組織
|
||||||
mirror=ミラー
|
mirror=ミラー
|
||||||
new_repo=新しいリポジトリ
|
new_repo=新しいリポジトリ
|
||||||
new_migrate=新しい移行
|
new_migrate=新しいマイグレーション
|
||||||
new_mirror=新しいミラー
|
new_mirror=新しいミラー
|
||||||
new_fork=新しいフォーク
|
new_fork=新しいフォーク
|
||||||
new_org=新しい組織
|
new_org=新しい組織
|
||||||
|
@ -155,6 +155,8 @@ filter.public = 公開
|
||||||
filter.private = 非公開
|
filter.private = 非公開
|
||||||
toggle_menu = トグルメニュー
|
toggle_menu = トグルメニュー
|
||||||
filter.not_template = テンプレートではない
|
filter.not_template = テンプレートではない
|
||||||
|
invalid_data = 無効なデータ: %v
|
||||||
|
more_items = さらに表示
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=ナビゲーションバー
|
navbar=ナビゲーションバー
|
||||||
|
@ -1587,7 +1589,7 @@ issues.label.filter_sort.alphabetically=アルファベット順
|
||||||
issues.label.filter_sort.reverse_alphabetically=逆アルファベット順
|
issues.label.filter_sort.reverse_alphabetically=逆アルファベット順
|
||||||
issues.label.filter_sort.by_size=サイズの小さい順
|
issues.label.filter_sort.by_size=サイズの小さい順
|
||||||
issues.label.filter_sort.reverse_by_size=サイズの大きい順
|
issues.label.filter_sort.reverse_by_size=サイズの大きい順
|
||||||
issues.num_participants=%d 人の参加者
|
issues.num_participants_few=%d 人の参加者
|
||||||
issues.attachment.open_tab=`クリックして新しいタブで "%s" を見る`
|
issues.attachment.open_tab=`クリックして新しいタブで "%s" を見る`
|
||||||
issues.attachment.download=`クリックして "%s" をダウンロード`
|
issues.attachment.download=`クリックして "%s" をダウンロード`
|
||||||
issues.subscribe=購読する
|
issues.subscribe=購読する
|
||||||
|
@ -2301,9 +2303,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Packagist ユーザー名
|
settings.packagist_username=Packagist ユーザー名
|
||||||
|
@ -3679,3 +3681,8 @@ executable_file=実行可能ファイル
|
||||||
symbolic_link=シンボリックリンク
|
symbolic_link=シンボリックリンク
|
||||||
submodule=サブモジュール
|
submodule=サブモジュール
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[search]
|
||||||
|
search = 検索...
|
||||||
|
type_tooltip = 検索タイプ
|
|
@ -782,7 +782,7 @@ issues.label_deletion_desc=라벨을 삭제하면 모든 이슈로부터도 삭
|
||||||
issues.label_deletion_success=라벨이 삭제되었습니다.
|
issues.label_deletion_success=라벨이 삭제되었습니다.
|
||||||
issues.label.filter_sort.alphabetically=알파벳순
|
issues.label.filter_sort.alphabetically=알파벳순
|
||||||
issues.label.filter_sort.reverse_alphabetically=이름 역순으로 정렬
|
issues.label.filter_sort.reverse_alphabetically=이름 역순으로 정렬
|
||||||
issues.num_participants=참여자 %d명
|
issues.num_participants_few=참여자 %d명
|
||||||
issues.attachment.open_tab=`클릭하여 "%s" 새탭으로 보기`
|
issues.attachment.open_tab=`클릭하여 "%s" 새탭으로 보기`
|
||||||
issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오 '
|
issues.attachment.download=' "%s"를 다운로드 하려면 클릭 하십시오 '
|
||||||
issues.subscribe=구독하기
|
issues.subscribe=구독하기
|
||||||
|
|
|
@ -1540,7 +1540,7 @@ issues.label.filter_sort.alphabetically=Alfabētiski
|
||||||
issues.label.filter_sort.reverse_alphabetically=Pretēji alfabētiski
|
issues.label.filter_sort.reverse_alphabetically=Pretēji alfabētiski
|
||||||
issues.label.filter_sort.by_size=Mazākais izmērs
|
issues.label.filter_sort.by_size=Mazākais izmērs
|
||||||
issues.label.filter_sort.reverse_by_size=Lielākais izmērs
|
issues.label.filter_sort.reverse_by_size=Lielākais izmērs
|
||||||
issues.num_participants=%d dalībnieki
|
issues.num_participants_few=%d dalībnieki
|
||||||
issues.attachment.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā`
|
issues.attachment.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā`
|
||||||
issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"`
|
issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"`
|
||||||
issues.subscribe=Abonēt
|
issues.subscribe=Abonēt
|
||||||
|
@ -2253,9 +2253,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Packagist lietotājvārds
|
settings.packagist_username=Packagist lietotājvārds
|
||||||
|
|
|
@ -155,6 +155,8 @@ filter.public = Publiek
|
||||||
filter.private = Privé
|
filter.private = Privé
|
||||||
filter = Filter
|
filter = Filter
|
||||||
filter.not_archived = Niet gearchiveerd
|
filter.not_archived = Niet gearchiveerd
|
||||||
|
more_items = Meer items
|
||||||
|
invalid_data = Ongeldige data: %v
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar = Navigatiebalk
|
navbar = Navigatiebalk
|
||||||
|
@ -226,7 +228,7 @@ db_schema=Schema
|
||||||
db_schema_helper=Laat leeg voor de standaard database ("openbaar").
|
db_schema_helper=Laat leeg voor de standaard database ("openbaar").
|
||||||
ssl_mode=SSL
|
ssl_mode=SSL
|
||||||
path=Pad
|
path=Pad
|
||||||
sqlite_helper=Bestandspad voor de SQLite3-database.<br>Vul een volledig pad in als je GItea als een service uitvoert.
|
sqlite_helper=Bestandspad voor de SQLite3-database.<br>Vul een volledig pad in als je Forgejo als een service uitvoert.
|
||||||
reinstall_error=U probeert te installeren in een bestaande Forgejo database
|
reinstall_error=U probeert te installeren in een bestaande Forgejo database
|
||||||
reinstall_confirm_message=Herinstalleren met een bestaande Forgejo-database kan meerdere problemen veroorzaken. In de meeste gevallen kun je het bestaande "app.ini" gebruiken om Forgejo te laten draaien. Als je weet wat je aan het doen bent, bevestig dan het volgende:
|
reinstall_confirm_message=Herinstalleren met een bestaande Forgejo-database kan meerdere problemen veroorzaken. In de meeste gevallen kun je het bestaande "app.ini" gebruiken om Forgejo te laten draaien. Als je weet wat je aan het doen bent, bevestig dan het volgende:
|
||||||
reinstall_confirm_check_1=De gegevens versleuteld door de SECRET_KEY in de app.ini kan verloren gaan: gebruikers kunnen mogelijk niet meer inloggen met 2FA/OTP & spiegels werken mogelijk niet meer. Door dit vakje aan te vinken bevestigt u dat het huidige app.ini bestand de juiste SECRET_KEY bevat.
|
reinstall_confirm_check_1=De gegevens versleuteld door de SECRET_KEY in de app.ini kan verloren gaan: gebruikers kunnen mogelijk niet meer inloggen met 2FA/OTP & spiegels werken mogelijk niet meer. Door dit vakje aan te vinken bevestigt u dat het huidige app.ini bestand de juiste SECRET_KEY bevat.
|
||||||
|
@ -934,7 +936,7 @@ retype_new_password = Nieuw wachtwoord bevestigen
|
||||||
email_desc = Je primaire e-mailadres zal gebruikt worden voor notificaties, wachtwoord herstel en web-gebaseerde Git-operaties, mits het e-mailadres niet verborgen is.
|
email_desc = Je primaire e-mailadres zal gebruikt worden voor notificaties, wachtwoord herstel en web-gebaseerde Git-operaties, mits het e-mailadres niet verborgen is.
|
||||||
can_not_add_email_activations_pending = Er is een activering in gang, probeer het over een paar minuten nogmaals als u een nieuwe e-mail wilt toevoegen.
|
can_not_add_email_activations_pending = Er is een activering in gang, probeer het over een paar minuten nogmaals als u een nieuwe e-mail wilt toevoegen.
|
||||||
select_permissions = Selecteer machtigingen
|
select_permissions = Selecteer machtigingen
|
||||||
permission_no_access = Geen Toegang
|
permission_no_access = Geen toegang
|
||||||
permissions_list = Machtigingen:
|
permissions_list = Machtigingen:
|
||||||
update_oauth2_application_success = U heeft met succes een OAuth2 applicatie bijgewerkt.
|
update_oauth2_application_success = U heeft met succes een OAuth2 applicatie bijgewerkt.
|
||||||
twofa_recovery_tip = Als u uw apparaat verliest, kunt u gebruik maken van de eenmalige herstelcode om weer toegang te krijgen tot uw account.
|
twofa_recovery_tip = Als u uw apparaat verliest, kunt u gebruik maken van de eenmalige herstelcode om weer toegang te krijgen tot uw account.
|
||||||
|
@ -952,7 +954,7 @@ unbind_success = De sociale account is succesvol verwijderd.
|
||||||
permissions_public_only = Alleen publiek
|
permissions_public_only = Alleen publiek
|
||||||
repo_and_org_access = Repository en Organisatie Toegang
|
repo_and_org_access = Repository en Organisatie Toegang
|
||||||
at_least_one_permission = Je moet minstens één machtiging kiezen om een token te kunnen creëren
|
at_least_one_permission = Je moet minstens één machtiging kiezen om een token te kunnen creëren
|
||||||
permission_write = Lees en Schrijf
|
permission_write = Lees en schrijf
|
||||||
oauth2_client_secret_hint = Dit geheim zal niet meer worden getoond nadat u deze pagina heeft verlaten of vernieuwd. Zorg ervoor dat u het heeft opgeslagen.
|
oauth2_client_secret_hint = Dit geheim zal niet meer worden getoond nadat u deze pagina heeft verlaten of vernieuwd. Zorg ervoor dat u het heeft opgeslagen.
|
||||||
revoke_oauth2_grant_success = Toegang succesvol ingetrokken.
|
revoke_oauth2_grant_success = Toegang succesvol ingetrokken.
|
||||||
keep_email_private_popup = Dit zal uw e-mailadres verbergen van uw profielpagina en ook wanneer u een web-gebaseerde Git-operatie uitvoert. Gepushte commits zullen niet aangepast worden. Gebruik %s in commits om deze met uw account te associëren.
|
keep_email_private_popup = Dit zal uw e-mailadres verbergen van uw profielpagina en ook wanneer u een web-gebaseerde Git-operatie uitvoert. Gepushte commits zullen niet aangepast worden. Gebruik %s in commits om deze met uw account te associëren.
|
||||||
|
@ -1237,7 +1239,7 @@ editor.name_your_file=Bestandsnaam…
|
||||||
editor.filename_help=Voeg een map toe door zijn naam te typen, gevolgd door een slash ("/"). Verwijder een map door op backspace te drukken aan het begin van het tekstveld.
|
editor.filename_help=Voeg een map toe door zijn naam te typen, gevolgd door een slash ("/"). Verwijder een map door op backspace te drukken aan het begin van het tekstveld.
|
||||||
editor.or=of
|
editor.or=of
|
||||||
editor.cancel_lower=Annuleer
|
editor.cancel_lower=Annuleer
|
||||||
editor.commit_signed_changes=Commit Ondertekende Wijzigingen
|
editor.commit_signed_changes=Commit ondertekende wijzigingen
|
||||||
editor.commit_changes=Wijzigingen doorvoeren
|
editor.commit_changes=Wijzigingen doorvoeren
|
||||||
editor.add_tmpl="<bestandsnaam>" toevoegen
|
editor.add_tmpl="<bestandsnaam>" toevoegen
|
||||||
editor.patch=Patch toepassen
|
editor.patch=Patch toepassen
|
||||||
|
@ -1312,8 +1314,8 @@ projects.edit=Projecten bewerken
|
||||||
projects.edit_subheader=Projecten organiseren issues en houden voortgang bij.
|
projects.edit_subheader=Projecten organiseren issues en houden voortgang bij.
|
||||||
projects.modify=Project bewerken
|
projects.modify=Project bewerken
|
||||||
projects.type.none=Geen
|
projects.type.none=Geen
|
||||||
projects.type.basic_kanban=Basis Kanban
|
projects.type.basic_kanban=Basis kanban
|
||||||
projects.type.bug_triage=Bug Triage
|
projects.type.bug_triage=Bug triage
|
||||||
projects.template.desc=Project sjabloon
|
projects.template.desc=Project sjabloon
|
||||||
projects.template.desc_helper=Selecteer een projectsjabloon om aan de slag te gaan
|
projects.template.desc_helper=Selecteer een projectsjabloon om aan de slag te gaan
|
||||||
projects.type.uncategorized=Ongecategoriseerd
|
projects.type.uncategorized=Ongecategoriseerd
|
||||||
|
@ -1482,7 +1484,7 @@ issues.label.filter_sort.alphabetically=Alfabetisch
|
||||||
issues.label.filter_sort.reverse_alphabetically=Omgekeerd alfabetisch
|
issues.label.filter_sort.reverse_alphabetically=Omgekeerd alfabetisch
|
||||||
issues.label.filter_sort.by_size=Kleinste grootte
|
issues.label.filter_sort.by_size=Kleinste grootte
|
||||||
issues.label.filter_sort.reverse_by_size=Grootste grootte
|
issues.label.filter_sort.reverse_by_size=Grootste grootte
|
||||||
issues.num_participants=%d deelnemers
|
issues.num_participants_few=%d deelnemers
|
||||||
issues.attachment.open_tab=`Klik om "%s" in een nieuw tabblad te bekijken`
|
issues.attachment.open_tab=`Klik om "%s" in een nieuw tabblad te bekijken`
|
||||||
issues.attachment.download=`Klik om "%s" te downloaden`
|
issues.attachment.download=`Klik om "%s" te downloaden`
|
||||||
issues.subscribe=Abonneren
|
issues.subscribe=Abonneren
|
||||||
|
@ -1749,7 +1751,7 @@ milestones.filter_sort.most_issues=Meeste problemen
|
||||||
milestones.filter_sort.least_issues=Minste problemen
|
milestones.filter_sort.least_issues=Minste problemen
|
||||||
|
|
||||||
|
|
||||||
ext_wiki=Toegang tot Externe Wiki
|
ext_wiki=Toegang tot externe wiki
|
||||||
ext_wiki.desc=Koppelen aan een externe wiki.
|
ext_wiki.desc=Koppelen aan een externe wiki.
|
||||||
|
|
||||||
wiki=Wiki
|
wiki=Wiki
|
||||||
|
@ -2030,9 +2032,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Packagist gebruikersnaam
|
settings.packagist_username=Packagist gebruikersnaam
|
||||||
|
@ -2354,15 +2356,15 @@ commitstatus.failure = Mislukking
|
||||||
commitstatus.success = Succes
|
commitstatus.success = Succes
|
||||||
projects.create_success = Het project "%s" is gecreëerd.
|
projects.create_success = Het project "%s" is gecreëerd.
|
||||||
projects.edit_success = Project "%s" is bijgewerkt.
|
projects.edit_success = Project "%s" is bijgewerkt.
|
||||||
projects.column.edit = Kolom Bewerken
|
projects.column.edit = Kolom bewerken
|
||||||
projects.column.new_submit = Kolom Maken
|
projects.column.new_submit = Kolom maken
|
||||||
projects.column.new = Nieuwe Kolom
|
projects.column.new = Nieuwe kolom
|
||||||
projects.column.set_default = Standaard Instellen
|
projects.column.set_default = Standaard instellen
|
||||||
projects.column.unset_default = Standaardinstelling ongedaan maken
|
projects.column.unset_default = Standaardinstelling ongedaan maken
|
||||||
projects.column.delete = Kolom verwijderen
|
projects.column.delete = Kolom verwijderen
|
||||||
projects.column.assigned_to = Toegewezen aan
|
projects.column.assigned_to = Toegewezen aan
|
||||||
projects.card_type.images_and_text = Afbeeldingen en Tekst
|
projects.card_type.images_and_text = Afbeeldingen en tekst
|
||||||
projects.card_type.text_only = Alleen Tekst
|
projects.card_type.text_only = Alleen tekst
|
||||||
issues.choose.ignore_invalid_templates = Ongeldige sjablonen zijn genegeerd
|
issues.choose.ignore_invalid_templates = Ongeldige sjablonen zijn genegeerd
|
||||||
issues.choose.invalid_templates = %v ongeldige sjablon(en) gevonden
|
issues.choose.invalid_templates = %v ongeldige sjablon(en) gevonden
|
||||||
issues.choose.invalid_config = Deze issue configuratie bevat fouten:
|
issues.choose.invalid_config = Deze issue configuratie bevat fouten:
|
||||||
|
@ -2428,7 +2430,7 @@ editor.invalid_commit_mail = Ongeldige mail voor het aanmaken van een commit.
|
||||||
editor.branch_does_not_exist = Branch "%s" bestaat niet in deze repository.
|
editor.branch_does_not_exist = Branch "%s" bestaat niet in deze repository.
|
||||||
editor.directory_is_a_file = Mapnaam "%s" wordt al gebruikt als bestandsnaam in deze repository.
|
editor.directory_is_a_file = Mapnaam "%s" wordt al gebruikt als bestandsnaam in deze repository.
|
||||||
commits.renamed_from = Hernoemd van %s
|
commits.renamed_from = Hernoemd van %s
|
||||||
projects.card_type.desc = Kaart Voorbeeld
|
projects.card_type.desc = Kaart voorbeeld
|
||||||
pulls.filter_changes_by_commit = Filter op commit
|
pulls.filter_changes_by_commit = Filter op commit
|
||||||
pulls.nothing_to_compare_have_tag = De geselecteerde branch/tag zijn gelijk.
|
pulls.nothing_to_compare_have_tag = De geselecteerde branch/tag zijn gelijk.
|
||||||
pulls.merged_success = Pull request succesvol samengevoegd en gesloten
|
pulls.merged_success = Pull request succesvol samengevoegd en gesloten
|
||||||
|
@ -2514,7 +2516,7 @@ settings.admin_stats_indexer = Code statistieken indexer
|
||||||
settings.new_owner_blocked_doer = De nieuwe eigenaar heeft u geblokkeerd.
|
settings.new_owner_blocked_doer = De nieuwe eigenaar heeft u geblokkeerd.
|
||||||
settings.transfer_notices_2 = - Je behoudt toegang tot de repository als je het overdraagt aan een organisatie waarvan je (mede-)eigenaar bent.
|
settings.transfer_notices_2 = - Je behoudt toegang tot de repository als je het overdraagt aan een organisatie waarvan je (mede-)eigenaar bent.
|
||||||
commits.search.tooltip = U kunt zoektermen voorvoegen met "author:", "committer:", "after:", of "before:", bijvoorbeeld: "revert author:Alice before:2019-01-13".
|
commits.search.tooltip = U kunt zoektermen voorvoegen met "author:", "committer:", "after:", of "before:", bijvoorbeeld: "revert author:Alice before:2019-01-13".
|
||||||
projects.column.deletion_desc = Het verwijderen van een projectkolom verplaatst alle issues naar "Ongecategoriseerd". Wilt u doorgaan?
|
projects.column.deletion_desc = Het verwijderen van een projectkolom verplaatst alle gerelateerde problemen naar de standaard kolom. Doorgaan?
|
||||||
projects.column.set_default_desc = Stel deze kolom in als standaard voor ongecategoriseerde issues and pulls
|
projects.column.set_default_desc = Stel deze kolom in als standaard voor ongecategoriseerde issues and pulls
|
||||||
issues.action_check = Aanvinken/uitvinken
|
issues.action_check = Aanvinken/uitvinken
|
||||||
issues.dependency.issue_batch_close_blocked = Het is niet mogelijk om de issues die u gekozen heeft in bulk te sluiten, omdat issue #%d nog open afhankelijkheden heeft
|
issues.dependency.issue_batch_close_blocked = Het is niet mogelijk om de issues die u gekozen heeft in bulk te sluiten, omdat issue #%d nog open afhankelijkheden heeft
|
||||||
|
@ -2679,13 +2681,23 @@ pulls.agit_explanation = Gemaakt met behulp van de AGit workflow. AGit laat bijd
|
||||||
settings.confirmation_string = Confirmatie string
|
settings.confirmation_string = Confirmatie string
|
||||||
activity.navbar.code_frequency = Code frequentie
|
activity.navbar.code_frequency = Code frequentie
|
||||||
activity.navbar.recent_commits = Recente commits
|
activity.navbar.recent_commits = Recente commits
|
||||||
file_follow = Volg Symlink
|
file_follow = Volg symlink
|
||||||
error.broken_git_hook = it hooks van deze repository lijken kapot te zijn. Volg alsjeblieft <a target="_blank" rel="noreferrer" href="%s">de documentatie</a> om ze te repareren, push daarna wat commits om de status te vernieuwen.
|
error.broken_git_hook = it hooks van deze repository lijken kapot te zijn. Volg alsjeblieft <a target="_blank" rel="noreferrer" href="%s">de documentatie</a> om ze te repareren, push daarna wat commits om de status te vernieuwen.
|
||||||
pulls.title_desc_one = wilt %[1]d commit van <code>%[2]s</code> samenvoegen in <code id="branch_target">%[3]s</code>
|
pulls.title_desc_one = wilt %[1]d commit van <code>%[2]s</code> samenvoegen in <code id="branch_target">%[3]s</code>
|
||||||
open_with_editor = Open met %s
|
open_with_editor = Open met %s
|
||||||
commits.search_branch = Deze branch
|
commits.search_branch = Deze branch
|
||||||
pulls.merged_title_desc_one = heeft %[1]d commit van <code>%[2]s</code> samengevoegd in <code>%[3]s</code> %[4]s
|
pulls.merged_title_desc_one = heeft %[1]d commit van <code>%[2]s</code> samengevoegd in <code>%[3]s</code> %[4]s
|
||||||
pulls.ready_for_review = Klaar voor een beoordeling?
|
pulls.ready_for_review = Klaar voor een beoordeling?
|
||||||
|
editor.push_out_of_date = De push lijkt verouderd.
|
||||||
|
editor.commit_id_not_matching = De commit ID komt niet overeen met degene die je aan het bewerken was. Committeer naar een nieuwe branch en voeg dan samen.
|
||||||
|
settings.rename_branch_failed_protected = Kan branch %s niet hernoemen omdat het een beschermde branch is.
|
||||||
|
stars = Sterren
|
||||||
|
n_commit_few = %s commits
|
||||||
|
n_branch_one = %s branch
|
||||||
|
n_branch_few = %s branches
|
||||||
|
n_tag_one = %s tag
|
||||||
|
n_tag_few = %s tags
|
||||||
|
n_commit_one = %d commit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3072,7 +3084,7 @@ config.repo_root_path=Repository basis pad
|
||||||
config.lfs_root_path=LFS rootpad
|
config.lfs_root_path=LFS rootpad
|
||||||
config.log_file_root_path=Log-pad
|
config.log_file_root_path=Log-pad
|
||||||
config.script_type=Script soort
|
config.script_type=Script soort
|
||||||
config.reverse_auth_user=Omgekeerde verificatie gebruiker
|
config.reverse_auth_user=Reverse proxy verificatie gebruiker
|
||||||
|
|
||||||
config.ssh_config=SSH-configuratie
|
config.ssh_config=SSH-configuratie
|
||||||
config.ssh_enabled=Ingeschakeld
|
config.ssh_enabled=Ingeschakeld
|
||||||
|
@ -3318,7 +3330,7 @@ auths.skip_local_two_fa_helper = Niet ingesteld betekent dat lokale gebruikers m
|
||||||
auths.skip_local_two_fa = Lokale 2FA overslaan
|
auths.skip_local_two_fa = Lokale 2FA overslaan
|
||||||
auths.oauth2_icon_url = Pictogram URL
|
auths.oauth2_icon_url = Pictogram URL
|
||||||
auths.pam_email_domain = PAM e-maildomein (optioneel)
|
auths.pam_email_domain = PAM e-maildomein (optioneel)
|
||||||
auths.tip.gitea = Registreer een nieuwe OAuth2-toepassing. De handleiding is te vinden op https://docs.gitea.com/development/oauth2-provider
|
auths.tip.gitea = Registreer een nieuwe OAuth2-toepassing. De handleiding is te vinden op https://forgejo.org/docs/latest/user/oauth2-provider
|
||||||
auths.tip.discord = Registreer een nieuwe toepassing op https://discordapp.com/developers/applications/me
|
auths.tip.discord = Registreer een nieuwe toepassing op https://discordapp.com/developers/applications/me
|
||||||
auths.tip.bitbucket = Registreer een nieuwe OAuth consumer op https://bitbucket.org/account/user/<uw gebruikersnaam>/oauth-consumers/new en voeg de rechten "Account" - "Read"
|
auths.tip.bitbucket = Registreer een nieuwe OAuth consumer op https://bitbucket.org/account/user/<uw gebruikersnaam>/oauth-consumers/new en voeg de rechten "Account" - "Read"
|
||||||
auths.tips.oauth2.general.tip = Bij het registreren van een nieuwe OAuth2-authenticatie moet de callback/redirect URL zijn:
|
auths.tips.oauth2.general.tip = Bij het registreren van een nieuwe OAuth2-authenticatie moet de callback/redirect URL zijn:
|
||||||
|
@ -3356,6 +3368,7 @@ config_settings = Instellingen
|
||||||
auths.tips.gmail_settings = Gmail instellingen:
|
auths.tips.gmail_settings = Gmail instellingen:
|
||||||
config_summary = Samenvatting
|
config_summary = Samenvatting
|
||||||
config.open_with_editor_app_help = De "Openen met" editors voor het kloonmenu. Als deze leeg blijft, wordt de standaardwaarde gebruikt. Uitvouwen om de standaard te zien.
|
config.open_with_editor_app_help = De "Openen met" editors voor het kloonmenu. Als deze leeg blijft, wordt de standaardwaarde gebruikt. Uitvouwen om de standaard te zien.
|
||||||
|
auths.tip.gitlab_new = Registreer een nieuwe applicatie op https://gitlab.com/-/profile/applications
|
||||||
|
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
|
@ -3704,6 +3717,7 @@ variables.description = Variabelen worden doorgegeven aan bepaalde acties en kun
|
||||||
runners.delete_runner_success = Runner succesvol verwijderd
|
runners.delete_runner_success = Runner succesvol verwijderd
|
||||||
runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s
|
runs.no_matching_online_runner_helper = Geen overeenkomende online runner met label: %s
|
||||||
runs.workflow = Workflow
|
runs.workflow = Workflow
|
||||||
|
runs.no_job_without_needs = De workflow moet ten minste één taak zonder afhankelijkheden bevatten.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -3754,3 +3768,4 @@ type_tooltip = Zoektype
|
||||||
fuzzy_tooltip = Neem resultaten op die ook sterk overeenkomen met de zoekterm
|
fuzzy_tooltip = Neem resultaten op die ook sterk overeenkomen met de zoekterm
|
||||||
code_search_unavailable = Code zoeken is momenteel niet beschikbaar. Neem contact op met de sitebeheerder.
|
code_search_unavailable = Code zoeken is momenteel niet beschikbaar. Neem contact op met de sitebeheerder.
|
||||||
keyword_search_unavailable = Zoeken op trefwoord is momenteel niet beschikbaar. Neem contact op met de beheerder van de site.
|
keyword_search_unavailable = Zoeken op trefwoord is momenteel niet beschikbaar. Neem contact op met de beheerder van de site.
|
||||||
|
code_search_by_git_grep = Huidige code zoekresultaten worden geleverd door "git grep". Er kunnen betere resultaten zijn als de sitebeheerder Repository Indexer inschakelt.
|
|
@ -1179,7 +1179,7 @@ issues.label.filter_sort.alphabetically=Alfabetycznie
|
||||||
issues.label.filter_sort.reverse_alphabetically=Alfabetycznie odwrotnie
|
issues.label.filter_sort.reverse_alphabetically=Alfabetycznie odwrotnie
|
||||||
issues.label.filter_sort.by_size=Najmniejszy rozmiar
|
issues.label.filter_sort.by_size=Najmniejszy rozmiar
|
||||||
issues.label.filter_sort.reverse_by_size=Największy rozmiar
|
issues.label.filter_sort.reverse_by_size=Największy rozmiar
|
||||||
issues.num_participants=Uczestnicy %d
|
issues.num_participants_few=Uczestnicy %d
|
||||||
issues.attachment.open_tab=`Kliknij, aby zobaczyć "%s" w nowej karcie`
|
issues.attachment.open_tab=`Kliknij, aby zobaczyć "%s" w nowej karcie`
|
||||||
issues.attachment.download=`Kliknij, aby pobrać "%s"`
|
issues.attachment.download=`Kliknij, aby pobrać "%s"`
|
||||||
issues.subscribe=Subskrybuj
|
issues.subscribe=Subskrybuj
|
||||||
|
|
|
@ -1552,7 +1552,7 @@ issues.label.filter_sort.alphabetically=Alfabeticamente
|
||||||
issues.label.filter_sort.reverse_alphabetically=Alfabeticamente inverso
|
issues.label.filter_sort.reverse_alphabetically=Alfabeticamente inverso
|
||||||
issues.label.filter_sort.by_size=Menor tamanho
|
issues.label.filter_sort.by_size=Menor tamanho
|
||||||
issues.label.filter_sort.reverse_by_size=Maior tamanho
|
issues.label.filter_sort.reverse_by_size=Maior tamanho
|
||||||
issues.num_participants=%d participante(s)
|
issues.num_participants_few=%d participante(s)
|
||||||
issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
|
issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
|
||||||
issues.attachment.download=`Clique para baixar "%s"`
|
issues.attachment.download=`Clique para baixar "%s"`
|
||||||
issues.subscribe=Inscrever-se
|
issues.subscribe=Inscrever-se
|
||||||
|
@ -2236,9 +2236,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Nome de usuário no Packagist
|
settings.packagist_username=Nome de usuário no Packagist
|
||||||
|
|
|
@ -1548,7 +1548,7 @@ issues.label.filter_sort.alphabetically=por ordem alfabética
|
||||||
issues.label.filter_sort.reverse_alphabetically=por ordem alfabética inversa
|
issues.label.filter_sort.reverse_alphabetically=por ordem alfabética inversa
|
||||||
issues.label.filter_sort.by_size=Menor tamanho
|
issues.label.filter_sort.by_size=Menor tamanho
|
||||||
issues.label.filter_sort.reverse_by_size=Maior tamanho
|
issues.label.filter_sort.reverse_by_size=Maior tamanho
|
||||||
issues.num_participants=%d Participantes
|
issues.num_participants_few=%d Participantes
|
||||||
issues.attachment.open_tab=`Clique para ver "%s" num separador novo`
|
issues.attachment.open_tab=`Clique para ver "%s" num separador novo`
|
||||||
issues.attachment.download=`Clique para descarregar "%s"`
|
issues.attachment.download=`Clique para descarregar "%s"`
|
||||||
issues.subscribe=Subscrever
|
issues.subscribe=Subscrever
|
||||||
|
@ -2269,9 +2269,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Nome de utilizador no Packagist
|
settings.packagist_username=Nome de utilizador no Packagist
|
||||||
|
|
|
@ -155,6 +155,8 @@ filter.public = Публичные
|
||||||
filter.private = Приватные
|
filter.private = Приватные
|
||||||
filter.is_archived = Архивированные
|
filter.is_archived = Архивированные
|
||||||
filter.not_mirror = Не зеркала
|
filter.not_mirror = Не зеркала
|
||||||
|
more_items = Больше элементов
|
||||||
|
invalid_data = Неверные данные: %v
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=Панель навигации
|
navbar=Панель навигации
|
||||||
|
@ -251,16 +253,16 @@ run_user=Запуск от имени пользователя
|
||||||
run_user_helper=Имя пользователя операционной системы, под которым работает Forgejo. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев.
|
run_user_helper=Имя пользователя операционной системы, под которым работает Forgejo. Обратите внимание, что этот пользователь должен иметь доступ к корневому пути репозиториев.
|
||||||
domain=Домен сервера
|
domain=Домен сервера
|
||||||
domain_helper=Домен или адрес хоста для сервера.
|
domain_helper=Домен или адрес хоста для сервера.
|
||||||
ssh_port=Порт SSH сервера
|
ssh_port=Порт SSH-сервера
|
||||||
ssh_port_helper=Номер порта, который использует SSH сервер. Оставьте пустым, чтобы отключить SSH.
|
ssh_port_helper=Номер порта, используемый SSH-сервером. Оставьте пустым для отключения доступа по SSH.
|
||||||
http_port=Forgejo HTTP порт
|
http_port=Порт HTTP-сервера
|
||||||
http_port_helper=Номер порта, который будет прослушиваться Forgejo веб-сервером.
|
http_port_helper=Номер порта, используемый веб-сервером Forgejo.
|
||||||
app_url=Базовый URL Forgejo
|
app_url=Базовый URL Forgejo
|
||||||
app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на некоторые уведомления по эл. почте.
|
app_url_helper=Этот параметр влияет на URL для клонирования по HTTP/HTTPS и на некоторые уведомления по эл. почте.
|
||||||
log_root_path=Путь журналов
|
log_root_path=Путь журналов
|
||||||
log_root_path_helper=Файлы журнала будут записываться в этот каталог.
|
log_root_path_helper=Файлы журнала будут записываться в этот каталог.
|
||||||
|
|
||||||
optional_title=Расширенные настройки
|
optional_title=Дополнительные настройки
|
||||||
email_title=Настройки эл. почты
|
email_title=Настройки эл. почты
|
||||||
smtp_addr=Адрес SMTP
|
smtp_addr=Адрес SMTP
|
||||||
smtp_port=Порт SMTP
|
smtp_port=Порт SMTP
|
||||||
|
@ -991,6 +993,7 @@ owner_helper=Некоторые организации могут не отоб
|
||||||
repo_name=Название репозитория
|
repo_name=Название репозитория
|
||||||
repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов.
|
repo_name_helper=Лучшие названия репозиториев состоят из коротких, легко запоминаемых и уникальных ключевых слов.
|
||||||
repo_size=Размер репозитория
|
repo_size=Размер репозитория
|
||||||
|
size_format = `%[1]s: %[2]s; %[3]s: %[4]s`
|
||||||
template=Шаблон
|
template=Шаблон
|
||||||
template_select=Выбрать шаблон.
|
template_select=Выбрать шаблон.
|
||||||
template_helper=Сделать репозиторий шаблоном
|
template_helper=Сделать репозиторий шаблоном
|
||||||
|
@ -1142,7 +1145,7 @@ migrate.migrating_failed_no_addr=Перенос не удался.
|
||||||
migrate.github.description=Перенесите данные с github.com или сервера GitHub Enterprise.
|
migrate.github.description=Перенесите данные с github.com или сервера GitHub Enterprise.
|
||||||
migrate.git.description=Перенести только репозиторий из любого Git сервиса.
|
migrate.git.description=Перенести только репозиторий из любого Git сервиса.
|
||||||
migrate.gitlab.description=Перенести данные с gitlab.com или других серверов GitLab.
|
migrate.gitlab.description=Перенести данные с gitlab.com или других серверов GitLab.
|
||||||
migrate.gitea.description=Перенести данные с gitea.com или других серверов Gitea/Forgejo.
|
migrate.gitea.description=Перенести данные с gitea.com или других серверов Gitea.
|
||||||
migrate.gogs.description=Перенести данные с notabug.org или других серверов Gogs.
|
migrate.gogs.description=Перенести данные с notabug.org или других серверов Gogs.
|
||||||
migrate.onedev.description=Перенести данные с code.onedev.io или других серверов OneDev.
|
migrate.onedev.description=Перенести данные с code.onedev.io или других серверов OneDev.
|
||||||
migrate.codebase.description=Перенос данных с codebasehq.com.
|
migrate.codebase.description=Перенос данных с codebasehq.com.
|
||||||
|
@ -1232,7 +1235,7 @@ symbolic_link=Символическая ссылка
|
||||||
executable_file=Исполняемый файл
|
executable_file=Исполняемый файл
|
||||||
commit_graph=Граф коммитов
|
commit_graph=Граф коммитов
|
||||||
commit_graph.select=Выбрать ветку
|
commit_graph.select=Выбрать ветку
|
||||||
commit_graph.hide_pr_refs=Скрыть запросы на слияние
|
commit_graph.hide_pr_refs=Скрыть запросы слияний
|
||||||
commit_graph.monochrome=Моно
|
commit_graph.monochrome=Моно
|
||||||
commit_graph.color=Цвет
|
commit_graph.color=Цвет
|
||||||
commit.contained_in=Этот коммит содержится в:
|
commit.contained_in=Этот коммит содержится в:
|
||||||
|
@ -1344,7 +1347,7 @@ commitstatus.failure=Неудача
|
||||||
commitstatus.pending=Ожидание
|
commitstatus.pending=Ожидание
|
||||||
commitstatus.success=Успешно
|
commitstatus.success=Успешно
|
||||||
|
|
||||||
ext_issues=Доступ к внешним задачам
|
ext_issues=Доступ ко внешним задачам
|
||||||
ext_issues.desc=Ссылка на внешнюю систему отслеживания задач.
|
ext_issues.desc=Ссылка на внешнюю систему отслеживания задач.
|
||||||
|
|
||||||
projects=Проекты
|
projects=Проекты
|
||||||
|
@ -1359,7 +1362,7 @@ projects.create_success=Проект «%s» создан.
|
||||||
projects.deletion=Удалить проект
|
projects.deletion=Удалить проект
|
||||||
projects.deletion_desc=Удаление проекта приведёт к его удалению из всех связанных задач. Продолжить?
|
projects.deletion_desc=Удаление проекта приведёт к его удалению из всех связанных задач. Продолжить?
|
||||||
projects.deletion_success=Проект удалён.
|
projects.deletion_success=Проект удалён.
|
||||||
projects.edit=Редактировать проекты
|
projects.edit=Изменить проект
|
||||||
projects.edit_subheader=Создавайте и организуйте задачи и отслеживайте прогресс.
|
projects.edit_subheader=Создавайте и организуйте задачи и отслеживайте прогресс.
|
||||||
projects.modify=Обновить проект
|
projects.modify=Обновить проект
|
||||||
projects.edit_success=Проект «%s» обновлён.
|
projects.edit_success=Проект «%s» обновлён.
|
||||||
|
@ -1372,14 +1375,14 @@ projects.type.uncategorized=Без категории
|
||||||
projects.column.edit=Изменить столбец
|
projects.column.edit=Изменить столбец
|
||||||
projects.column.edit_title=Название
|
projects.column.edit_title=Название
|
||||||
projects.column.new_title=Название
|
projects.column.new_title=Название
|
||||||
projects.column.new_submit=Создать столбец
|
projects.column.new_submit=Добавить столбец
|
||||||
projects.column.new=Новый столбец
|
projects.column.new=Добавить столбец
|
||||||
projects.column.set_default=Установить по умолчанию
|
projects.column.set_default=Установить по умолчанию
|
||||||
projects.column.set_default_desc=Назначить этот столбец по умолчанию для задач и запросов на слияние без категории
|
projects.column.set_default_desc=Назначить этот столбец по умолчанию для задач и запросов на слияние без категории
|
||||||
projects.column.unset_default=Снять установку по умолчанию
|
projects.column.unset_default=Снять установку по умолчанию
|
||||||
projects.column.unset_default_desc=Снять установку этого столбца по умолчанию
|
projects.column.unset_default_desc=Снять установку этого столбца по умолчанию
|
||||||
projects.column.delete=Удалить столбец
|
projects.column.delete=Удалить столбец
|
||||||
projects.column.deletion_desc=При удалении столбца проекта все связанные задачи перемещаются в «Без категории». Продолжить?
|
projects.column.deletion_desc=При удалении столбца все задачи в нём будут перемещены в столбец по умолчанию. Продолжить?
|
||||||
projects.column.color=Цвет
|
projects.column.color=Цвет
|
||||||
projects.open=Открыть
|
projects.open=Открыть
|
||||||
projects.close=Закрыть
|
projects.close=Закрыть
|
||||||
|
@ -1581,7 +1584,8 @@ issues.label.filter_sort.alphabetically=По алфавиту
|
||||||
issues.label.filter_sort.reverse_alphabetically=С конца алфавита
|
issues.label.filter_sort.reverse_alphabetically=С конца алфавита
|
||||||
issues.label.filter_sort.by_size=Меньший размер
|
issues.label.filter_sort.by_size=Меньший размер
|
||||||
issues.label.filter_sort.reverse_by_size=Больший размер
|
issues.label.filter_sort.reverse_by_size=Больший размер
|
||||||
issues.num_participants=%d участвующих
|
issues.num_participants_one=%d участник
|
||||||
|
issues.num_participants_few=%d участников
|
||||||
issues.attachment.open_tab=`Нажмите, чтобы увидеть «%s» в новой вкладке`
|
issues.attachment.open_tab=`Нажмите, чтобы увидеть «%s» в новой вкладке`
|
||||||
issues.attachment.download=`Нажмите, чтобы скачать «%s»`
|
issues.attachment.download=`Нажмите, чтобы скачать «%s»`
|
||||||
issues.subscribe=Подписаться
|
issues.subscribe=Подписаться
|
||||||
|
@ -1901,7 +1905,7 @@ signing.wont_sign.commitssigned=Слияние не будет подписан
|
||||||
signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен.
|
signing.wont_sign.approved=Слияние не будет подписано, так как запрос на слияние не одобрен.
|
||||||
signing.wont_sign.not_signed_in=Вы не вошли в систему.
|
signing.wont_sign.not_signed_in=Вы не вошли в систему.
|
||||||
|
|
||||||
ext_wiki=Доступ к внешней вики
|
ext_wiki=Доступ ко внешней вики
|
||||||
ext_wiki.desc=Ссылка на внешнюю вики.
|
ext_wiki.desc=Ссылка на внешнюю вики.
|
||||||
|
|
||||||
wiki=Вики
|
wiki=Вики
|
||||||
|
@ -2276,9 +2280,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu или Lark Suite
|
settings.web_hook_name_feishu=Feishu или Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Имя пользователя Packagist
|
settings.packagist_username=Имя пользователя Packagist
|
||||||
|
@ -2653,7 +2657,7 @@ signing.wont_sign.nokey = Нет ключей для подписи этого
|
||||||
settings.wiki_globally_editable = Разрешить редактирование Вики всем пользователям
|
settings.wiki_globally_editable = Разрешить редактирование Вики всем пользователям
|
||||||
settings.webhook.test_delivery_desc_disabled = Активируйте этот веб-хук для проверки тестовым событием.
|
settings.webhook.test_delivery_desc_disabled = Активируйте этот веб-хук для проверки тестовым событием.
|
||||||
commits.browse_further = Смотреть далее
|
commits.browse_further = Смотреть далее
|
||||||
vendored = Предоставленный
|
vendored = Сторонний
|
||||||
settings.units.add_more = Доб. больше...
|
settings.units.add_more = Доб. больше...
|
||||||
pulls.fast_forward_only_merge_pull_request = Только fast-forward
|
pulls.fast_forward_only_merge_pull_request = Только fast-forward
|
||||||
settings.units.overview = Обзор
|
settings.units.overview = Обзор
|
||||||
|
@ -2685,6 +2689,16 @@ settings.mirror_settings.docs.doc_link_pull_section = раздел докуме
|
||||||
wiki.original_git_entry_tooltip = Перейти по настоящему пути вместо читабельной ссылки.
|
wiki.original_git_entry_tooltip = Перейти по настоящему пути вместо читабельной ссылки.
|
||||||
open_with_editor = Открыть в %s
|
open_with_editor = Открыть в %s
|
||||||
commits.search_branch = В этой ветке
|
commits.search_branch = В этой ветке
|
||||||
|
stars = Добавившие в избранное
|
||||||
|
n_tag_one = %s тег
|
||||||
|
n_branch_few = %s веток
|
||||||
|
n_commit_few = %s коммитов
|
||||||
|
n_commit_one = %s коммит
|
||||||
|
n_tag_few = %s тегов
|
||||||
|
n_branch_one = %s ветка
|
||||||
|
pulls.ready_for_review = Готово к рецензии?
|
||||||
|
editor.commit_id_not_matching = ID коммита не совпадает с тем, который вы редактировали. Сохраните изменения в новую ветку и выполните слияние.
|
||||||
|
editor.push_out_of_date = Похоже, отправка устарела.
|
||||||
|
|
||||||
[graphs]
|
[graphs]
|
||||||
|
|
||||||
|
@ -2940,10 +2954,10 @@ users.prohibit_login=Запретить вход в учётную запись
|
||||||
users.is_admin=У этой учётной записи есть права администратора
|
users.is_admin=У этой учётной записи есть права администратора
|
||||||
users.is_restricted=Ограничен
|
users.is_restricted=Ограничен
|
||||||
users.allow_git_hook=Может создавать Git-хуки
|
users.allow_git_hook=Может создавать Git-хуки
|
||||||
users.allow_git_hook_tooltip=Git Hooks выполняется как пользователь ОС с Forgejo и будет иметь одинаковый уровень доступа к хосту. В результате пользователи с привилегией Git Hook могут получить доступ и модифицировать все репозитории Forgejo, а также базу данных, используемую Forgejo. Следовательно, они также могут получить привилегии администратора Forgejo.
|
users.allow_git_hook_tooltip=Git hooks выполняются от пользователя ОС, под которым работает Forgejo. Они будут иметь такой же доступ к хосту. Из-за этого пользователи с правами на Git hook будут иметь возможность получать доступ и модифицировать все репозитории в Forgejo, а также базу данных Forgejo. Следовательно, они также могут получить права администратора Forgejo.
|
||||||
users.allow_import_local=Пользователь имеет право импортировать локальные репозитории
|
users.allow_import_local=Может импортировать локальные репозитории
|
||||||
users.allow_create_organization=Эта учётная запись имеет разрешения на создание организаций
|
users.allow_create_organization=Может создавать организации
|
||||||
users.update_profile=Обновить профиль пользователя
|
users.update_profile=Обновить учётную запись
|
||||||
users.delete_account=Удалить эту учётную запись
|
users.delete_account=Удалить эту учётную запись
|
||||||
users.cannot_delete_self=Вы не можете удалить свою учётную запись
|
users.cannot_delete_self=Вы не можете удалить свою учётную запись
|
||||||
users.still_own_repo=Этот пользователь всё ещё является владельцем одного или более репозиториев. Сначала удалите или передайте эти репозитории.
|
users.still_own_repo=Этот пользователь всё ещё является владельцем одного или более репозиториев. Сначала удалите или передайте эти репозитории.
|
||||||
|
@ -2955,16 +2969,16 @@ users.deletion_success=Учётная запись успешно удалена
|
||||||
users.reset_2fa=Сброс 2FA
|
users.reset_2fa=Сброс 2FA
|
||||||
users.list_status_filter.menu_text=Фильтр
|
users.list_status_filter.menu_text=Фильтр
|
||||||
users.list_status_filter.reset=Сбросить
|
users.list_status_filter.reset=Сбросить
|
||||||
users.list_status_filter.is_active=Активный
|
users.list_status_filter.is_active=Активные
|
||||||
users.list_status_filter.not_active=Неактивный
|
users.list_status_filter.not_active=Неактивные
|
||||||
users.list_status_filter.is_admin=Администратор
|
users.list_status_filter.is_admin=Администраторы
|
||||||
users.list_status_filter.not_admin=Не администратор
|
users.list_status_filter.not_admin=Не администраторы
|
||||||
users.list_status_filter.is_restricted=Ограничено
|
users.list_status_filter.is_restricted=Ограниченные
|
||||||
users.list_status_filter.not_restricted=Не ограничено
|
users.list_status_filter.not_restricted=Не ограниченные
|
||||||
users.list_status_filter.is_prohibit_login=Запретить вход
|
users.list_status_filter.is_prohibit_login=Вход запрещён
|
||||||
users.list_status_filter.not_prohibit_login=Разрешить вход
|
users.list_status_filter.not_prohibit_login=Вход разрешён
|
||||||
users.list_status_filter.is_2fa_enabled=2FA включено
|
users.list_status_filter.is_2fa_enabled=2FA включена
|
||||||
users.list_status_filter.not_2fa_enabled=2FA отключено
|
users.list_status_filter.not_2fa_enabled=2FA выключена
|
||||||
users.details=О пользователе
|
users.details=О пользователе
|
||||||
|
|
||||||
emails.email_manage_panel=Управление адресами эл. почты пользователей
|
emails.email_manage_panel=Управление адресами эл. почты пользователей
|
||||||
|
@ -3155,7 +3169,7 @@ config.repo_root_path=Путь до каталога репозиториев
|
||||||
config.lfs_root_path=Корневой путь LFS
|
config.lfs_root_path=Корневой путь LFS
|
||||||
config.log_file_root_path=Путь журналов
|
config.log_file_root_path=Путь журналов
|
||||||
config.script_type=Тип сценария
|
config.script_type=Тип сценария
|
||||||
config.reverse_auth_user=Имя пользователя для авторизации на reverse proxy
|
config.reverse_auth_user=Пользователь для авторизации на обратном прокси
|
||||||
|
|
||||||
config.ssh_config=Конфигурация SSH
|
config.ssh_config=Конфигурация SSH
|
||||||
config.ssh_enabled=SSH включён
|
config.ssh_enabled=SSH включён
|
||||||
|
@ -3329,7 +3343,7 @@ notices.desc=Описание
|
||||||
notices.op=Oп.
|
notices.op=Oп.
|
||||||
notices.delete_success=Уведомления системы были удалены.
|
notices.delete_success=Уведомления системы были удалены.
|
||||||
self_check.no_problem_found = Пока проблем не обнаружено.
|
self_check.no_problem_found = Пока проблем не обнаружено.
|
||||||
auths.tip.gitea = Зарегистрируйте новое приложение OAuth2. Доступна инструкция: https://docs.gitea.com/development/oauth2-provider
|
auths.tip.gitea = Зарегистрируйте новое приложение OAuth2. Доступна инструкция: https://forgejo.org/docs/latest/user/oauth2-provider
|
||||||
auths.tips.oauth2.general.tip = При регистрации нового приложения OAuth2 ссылка обратного перенаправления должна быть:
|
auths.tips.oauth2.general.tip = При регистрации нового приложения OAuth2 ссылка обратного перенаправления должна быть:
|
||||||
self_check.database_fix_mssql = В настоящий момент пользователи MSSQL могут исправить проблемы с сопоставлением только ручным прописыванием "ALTER ... COLLATE ..." в SQL.
|
self_check.database_fix_mssql = В настоящий момент пользователи MSSQL могут исправить проблемы с сопоставлением только ручным прописыванием "ALTER ... COLLATE ..." в SQL.
|
||||||
self_check.database_fix_mysql = Пользователи MySQL и MariaDB могут исправить проблемы с сопоставлением командой "gitea doctor convert". Также можно вручную вписать "ALTER ... COLLATE ..." в SQL.
|
self_check.database_fix_mysql = Пользователи MySQL и MariaDB могут исправить проблемы с сопоставлением командой "gitea doctor convert". Также можно вручную вписать "ALTER ... COLLATE ..." в SQL.
|
||||||
|
@ -3352,6 +3366,7 @@ config_summary = Сводка
|
||||||
config.open_with_editor_app_help = Приложения для "Открыть в" в меню. Оставьте пустым для приложений по умолчанию. Разверните для просмотра.
|
config.open_with_editor_app_help = Приложения для "Открыть в" в меню. Оставьте пустым для приложений по умолчанию. Разверните для просмотра.
|
||||||
config_settings = Настройки
|
config_settings = Настройки
|
||||||
auths.tips.gmail_settings = Настройки Gmail:
|
auths.tips.gmail_settings = Настройки Gmail:
|
||||||
|
auths.tip.gitlab_new = Создайте новое приложение в https://gitlab.com/-/profile/applications
|
||||||
|
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
|
@ -3407,6 +3422,15 @@ years=%d лет
|
||||||
raw_seconds=секунд
|
raw_seconds=секунд
|
||||||
raw_minutes=минут
|
raw_minutes=минут
|
||||||
|
|
||||||
|
[munits.data]
|
||||||
|
b = Б
|
||||||
|
kib = КиБ
|
||||||
|
mib = МиБ
|
||||||
|
gib = ГиБ
|
||||||
|
tib = ТиБ
|
||||||
|
pib = ПиБ
|
||||||
|
eib = ЕиБ
|
||||||
|
|
||||||
[dropzone]
|
[dropzone]
|
||||||
default_message=Перетащите файл или кликните сюда для загрузки.
|
default_message=Перетащите файл или кликните сюда для загрузки.
|
||||||
invalid_input_type=Вы не можете загружать файлы этого типа.
|
invalid_input_type=Вы не можете загружать файлы этого типа.
|
||||||
|
@ -3596,6 +3620,7 @@ rpm.repository = О репозитории
|
||||||
rpm.repository.architectures = Архитектуры
|
rpm.repository.architectures = Архитектуры
|
||||||
rpm.repository.multiple_groups = Этот пакет доступен в нескольких группах.
|
rpm.repository.multiple_groups = Этот пакет доступен в нескольких группах.
|
||||||
owner.settings.chef.keypair.description = Для аутентификации реестра Chef необходима пара ключей. Если до этого вы уже сгенерировали пару ключей, генерация новой приведёт к прекращению действия предыдущей.
|
owner.settings.chef.keypair.description = Для аутентификации реестра Chef необходима пара ключей. Если до этого вы уже сгенерировали пару ключей, генерация новой приведёт к прекращению действия предыдущей.
|
||||||
|
owner.settings.cargo.rebuild.no_index = Невозможно выполнить пересборку. Нет инициализированного индекса.
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
secrets=Секреты
|
secrets=Секреты
|
||||||
|
@ -3748,3 +3773,5 @@ no_results = По запросу ничего не найдено.
|
||||||
keyword_search_unavailable = Поиск по ключевым словам недоступен. Уточните подробности у администратора.
|
keyword_search_unavailable = Поиск по ключевым словам недоступен. Уточните подробности у администратора.
|
||||||
match_tooltip = Включать только результаты, точно соответствующие запросу
|
match_tooltip = Включать только результаты, точно соответствующие запросу
|
||||||
code_search_unavailable = Поиск по коду сейчас недоступен. Уточните подробности у администратора.
|
code_search_unavailable = Поиск по коду сейчас недоступен. Уточните подробности у администратора.
|
||||||
|
runner_kind = Поиск раннеров...
|
||||||
|
code_search_by_git_grep = Эти результаты получены через «git grep». Результатов может быть больше, если администратор сервера включит индексатор кода.
|
||||||
|
|
|
@ -1132,7 +1132,7 @@ issues.label.filter_sort.alphabetically=අකාරාදී
|
||||||
issues.label.filter_sort.reverse_alphabetically=අකාරාදී ප්රතිවිකුණුම්
|
issues.label.filter_sort.reverse_alphabetically=අකාරාදී ප්රතිවිකුණුම්
|
||||||
issues.label.filter_sort.by_size=කුඩාම ප්රමාණය
|
issues.label.filter_sort.by_size=කුඩාම ප්රමාණය
|
||||||
issues.label.filter_sort.reverse_by_size=විශාලම ප්රමාණය
|
issues.label.filter_sort.reverse_by_size=විශාලම ප්රමාණය
|
||||||
issues.num_participants=සහභාගිවන්නන් %d
|
issues.num_participants_few=සහභාගිවන්නන් %d
|
||||||
issues.attachment.open_tab=`නව වගුවක "%s" බැලීමට ක්ලික් කරන්න`
|
issues.attachment.open_tab=`නව වගුවක "%s" බැලීමට ක්ලික් කරන්න`
|
||||||
issues.attachment.download=`"%s" බාගැනීමට ඔබන්න`
|
issues.attachment.download=`"%s" බාගැනීමට ඔබන්න`
|
||||||
issues.subscribe=දායක වන්න
|
issues.subscribe=දායක වන්න
|
||||||
|
|
|
@ -972,7 +972,7 @@ issues.label.filter_sort.alphabetically=Alfabetiskt A-Ö
|
||||||
issues.label.filter_sort.reverse_alphabetically=Alfabetiskt Ö-A
|
issues.label.filter_sort.reverse_alphabetically=Alfabetiskt Ö-A
|
||||||
issues.label.filter_sort.by_size=Minsta storlek
|
issues.label.filter_sort.by_size=Minsta storlek
|
||||||
issues.label.filter_sort.reverse_by_size=Största storlek
|
issues.label.filter_sort.reverse_by_size=Största storlek
|
||||||
issues.num_participants=%d Deltagare
|
issues.num_participants_few=%d Deltagare
|
||||||
issues.attachment.open_tab=`Klicka för att se "%s" i en ny flik`
|
issues.attachment.open_tab=`Klicka för att se "%s" i en ny flik`
|
||||||
issues.attachment.download=`Klicka för att hämta "%s"`
|
issues.attachment.download=`Klicka för att hämta "%s"`
|
||||||
issues.subscribe=Prenumerera
|
issues.subscribe=Prenumerera
|
||||||
|
|
|
@ -1539,7 +1539,7 @@ issues.label.filter_sort.alphabetically=Alfabetik
|
||||||
issues.label.filter_sort.reverse_alphabetically=Ters alfabetik
|
issues.label.filter_sort.reverse_alphabetically=Ters alfabetik
|
||||||
issues.label.filter_sort.by_size=En küçük boyut
|
issues.label.filter_sort.by_size=En küçük boyut
|
||||||
issues.label.filter_sort.reverse_by_size=En büyük boyut
|
issues.label.filter_sort.reverse_by_size=En büyük boyut
|
||||||
issues.num_participants=%d Katılımcı
|
issues.num_participants_few=%d Katılımcı
|
||||||
issues.attachment.open_tab=`Yeni bir sekmede "%s" görmek için tıkla`
|
issues.attachment.open_tab=`Yeni bir sekmede "%s" görmek için tıkla`
|
||||||
issues.attachment.download=`"%s" indirmek için tıkla`
|
issues.attachment.download=`"%s" indirmek için tıkla`
|
||||||
issues.subscribe=Abone Ol
|
issues.subscribe=Abone Ol
|
||||||
|
@ -2252,9 +2252,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Packagist kullanıcı adı
|
settings.packagist_username=Packagist kullanıcı adı
|
||||||
|
|
|
@ -1248,7 +1248,7 @@ issues.label.filter_sort.alphabetically=За алфавітом
|
||||||
issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
|
issues.label.filter_sort.reverse_alphabetically=З кінця алфавіту
|
||||||
issues.label.filter_sort.by_size=Найменший розмір
|
issues.label.filter_sort.by_size=Найменший розмір
|
||||||
issues.label.filter_sort.reverse_by_size=Найбільший розмір
|
issues.label.filter_sort.reverse_by_size=Найбільший розмір
|
||||||
issues.num_participants=%d учасників
|
issues.num_participants_few=%d учасників
|
||||||
issues.attachment.open_tab=`Натисніть щоб побачити "%s" у новій вкладці`
|
issues.attachment.open_tab=`Натисніть щоб побачити "%s" у новій вкладці`
|
||||||
issues.attachment.download=`Натисніть щоб завантажити "%s"`
|
issues.attachment.download=`Натисніть щоб завантажити "%s"`
|
||||||
issues.subscribe=Підписатися
|
issues.subscribe=Підписатися
|
||||||
|
|
|
@ -155,6 +155,8 @@ filter.not_template = 非模板
|
||||||
filter.public = 公开
|
filter.public = 公开
|
||||||
filter.private = 私有
|
filter.private = 私有
|
||||||
toggle_menu = 菜单
|
toggle_menu = 菜单
|
||||||
|
invalid_data = 无效数据: %v
|
||||||
|
more_items = 显示更多
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=导航栏
|
navbar=导航栏
|
||||||
|
@ -1597,7 +1599,7 @@ issues.label.filter_sort.alphabetically=按字母顺序排序
|
||||||
issues.label.filter_sort.reverse_alphabetically=按字母逆序排序
|
issues.label.filter_sort.reverse_alphabetically=按字母逆序排序
|
||||||
issues.label.filter_sort.by_size=最小尺寸
|
issues.label.filter_sort.by_size=最小尺寸
|
||||||
issues.label.filter_sort.reverse_by_size=最大尺寸
|
issues.label.filter_sort.reverse_by_size=最大尺寸
|
||||||
issues.num_participants=%d 名参与者
|
issues.num_participants_few=%d 名参与者
|
||||||
issues.attachment.open_tab=`在新的标签页中查看 '%s'`
|
issues.attachment.open_tab=`在新的标签页中查看 '%s'`
|
||||||
issues.attachment.download=`点击下载 '%s'`
|
issues.attachment.download=`点击下载 '%s'`
|
||||||
issues.subscribe=订阅
|
issues.subscribe=订阅
|
||||||
|
@ -2316,9 +2318,9 @@ settings.web_hook_name_dingtalk=钉钉
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=飞书 / Lark Suite
|
settings.web_hook_name_feishu=飞书 / Lark Suite
|
||||||
settings.web_hook_name_feishu=飞书
|
settings.web_hook_name_feishu_only =飞书
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=企业微信
|
settings.web_hook_name_wechatwork=企业微信
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Packagist 用户名
|
settings.packagist_username=Packagist 用户名
|
||||||
|
@ -2688,6 +2690,20 @@ activity.navbar.code_frequency = 代码频率
|
||||||
activity.navbar.recent_commits = 近期提交
|
activity.navbar.recent_commits = 近期提交
|
||||||
pulls.agit_explanation = 该合并请求是用 AGit 创建的。AGit 是一种可以让贡献者直接通过 “git push” 提出更改代码而不需要派生或建立新分支。
|
pulls.agit_explanation = 该合并请求是用 AGit 创建的。AGit 是一种可以让贡献者直接通过 “git push” 提出更改代码而不需要派生或建立新分支。
|
||||||
error.broken_git_hook = 该仓库的 Git 钩子似乎已经损坏,请按照 <a target="_blank" rel="noreferrer" href="%s">此文档</a>来修复这些问题,然后推送一些提交来刷新状态。
|
error.broken_git_hook = 该仓库的 Git 钩子似乎已经损坏,请按照 <a target="_blank" rel="noreferrer" href="%s">此文档</a>来修复这些问题,然后推送一些提交来刷新状态。
|
||||||
|
pulls.merged_title_desc_one = 已将来自 <code>%[2]s</code> 的 %[1]d 提交合并入 <code>%[3]s</code> %[4]s
|
||||||
|
commits.search_branch = 此分支
|
||||||
|
open_with_editor = 使用 %s 打开
|
||||||
|
pulls.title_desc_one = 想要将来自 <code>%[2]s</code> 的 %[1]d 提交合并到 <code id="branch_target">%[3]s</code>
|
||||||
|
settings.rename_branch_failed_protected = 无法重命名受保护的分支 %s。
|
||||||
|
stars = 点赞
|
||||||
|
settings.confirmation_string = 确认输入
|
||||||
|
n_commit_one = %s 提交
|
||||||
|
n_commit_few = %s 提交
|
||||||
|
n_branch_one = %s 分支
|
||||||
|
n_branch_few = %s 分支
|
||||||
|
n_tag_one = %s 标签
|
||||||
|
n_tag_few = %s 标签
|
||||||
|
editor.commit_id_not_matching = 此提交ID与您当前编辑的不匹配,将提交至新分支后合并。
|
||||||
|
|
||||||
[graphs]
|
[graphs]
|
||||||
component_loading=正在加载 %s...
|
component_loading=正在加载 %s...
|
||||||
|
@ -2958,7 +2974,7 @@ users.max_repo_creation_desc=(设置为 -1 表示使用全局默认值)
|
||||||
users.is_activated=该用户已被激活
|
users.is_activated=该用户已被激活
|
||||||
users.prohibit_login=禁用登录
|
users.prohibit_login=禁用登录
|
||||||
users.is_admin=是管理员
|
users.is_admin=是管理员
|
||||||
users.is_restricted=受限制的
|
users.is_restricted=受限
|
||||||
users.allow_git_hook=允许创建 Git 钩子
|
users.allow_git_hook=允许创建 Git 钩子
|
||||||
users.allow_git_hook_tooltip=Git 钩子将会被以操作系统用户运行,将会拥有同样的主机访问权限。因此,拥有此特殊的Git 钩子权限将能够访问合修改所有的 Forgejo 仓库或者Forgejo的数据库。同时也能获得Forgejo的管理员权限。
|
users.allow_git_hook_tooltip=Git 钩子将会被以操作系统用户运行,将会拥有同样的主机访问权限。因此,拥有此特殊的Git 钩子权限将能够访问合修改所有的 Forgejo 仓库或者Forgejo的数据库。同时也能获得Forgejo的管理员权限。
|
||||||
users.allow_import_local=允许导入本地仓库
|
users.allow_import_local=允许导入本地仓库
|
||||||
|
@ -3370,6 +3386,8 @@ self_check.database_inconsistent_collation_columns=数据库正在使用%s的排
|
||||||
self_check.database_fix_mysql=对于MySQL/MariaDB用户,您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
|
self_check.database_fix_mysql=对于MySQL/MariaDB用户,您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
|
||||||
self_check.database_fix_mssql=对于MSSQL用户,您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。
|
self_check.database_fix_mssql=对于MSSQL用户,您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。
|
||||||
auths.tips.gmail_settings = Gmail 设置:
|
auths.tips.gmail_settings = Gmail 设置:
|
||||||
|
auths.tip.gitlab_new = 在 https://gitlab.com/-/profile/applications 上注册新应用
|
||||||
|
config_settings = 设置
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
create_repo=创建了仓库 <a href="%s">%s</a>
|
create_repo=创建了仓库 <a href="%s">%s</a>
|
||||||
|
@ -3738,3 +3756,25 @@ executable_file=可执行文件
|
||||||
symbolic_link=符号链接
|
symbolic_link=符号链接
|
||||||
submodule=子模块
|
submodule=子模块
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[search]
|
||||||
|
keyword_search_unavailable = 关键词搜索目前不可用,请联系站点管理员。
|
||||||
|
search = 搜索...
|
||||||
|
repo_kind = 搜索仓库...
|
||||||
|
user_kind = 搜索用户...
|
||||||
|
org_kind = 搜索组织...
|
||||||
|
team_kind = 搜索团队...
|
||||||
|
code_kind = 搜索代码...
|
||||||
|
code_search_unavailable = 代码搜索目前不可用,请联系站点管理员。
|
||||||
|
package_kind = 搜索软件包...
|
||||||
|
project_kind = 搜索项目...
|
||||||
|
branch_kind = 搜索分支...
|
||||||
|
commit_kind = 搜索提交...
|
||||||
|
runner_kind = 搜索Runners...
|
||||||
|
no_results = 未找到匹配的结果。
|
||||||
|
type_tooltip = 搜索类型
|
||||||
|
fuzzy = 模糊
|
||||||
|
code_search_by_git_grep = 当前搜索结果由 git grep 提供,如果站点管理员启用了仓库索引可能会有更好的结果。
|
||||||
|
match = 匹配
|
||||||
|
match_tooltip = 仅包含与搜索词完全匹配的结果
|
|
@ -467,7 +467,7 @@ issues.label_edit=編輯
|
||||||
issues.label_delete=刪除
|
issues.label_delete=刪除
|
||||||
issues.label.filter_sort.alphabetically=按字母顺序排序
|
issues.label.filter_sort.alphabetically=按字母顺序排序
|
||||||
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
|
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
|
||||||
issues.num_participants=%d 參與者
|
issues.num_participants_few=%d 參與者
|
||||||
issues.attachment.open_tab=`在新的標籤頁中查看 '%s'`
|
issues.attachment.open_tab=`在新的標籤頁中查看 '%s'`
|
||||||
issues.attachment.download=`點擊下載 '%s'`
|
issues.attachment.download=`點擊下載 '%s'`
|
||||||
issues.subscribe=訂閱
|
issues.subscribe=訂閱
|
||||||
|
|
|
@ -1433,7 +1433,7 @@ issues.label.filter_sort.alphabetically=按字母順序排序
|
||||||
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
|
issues.label.filter_sort.reverse_alphabetically=按字母反向排序
|
||||||
issues.label.filter_sort.by_size=檔案由小到大
|
issues.label.filter_sort.by_size=檔案由小到大
|
||||||
issues.label.filter_sort.reverse_by_size=檔案由大到小
|
issues.label.filter_sort.reverse_by_size=檔案由大到小
|
||||||
issues.num_participants=%d 參與者
|
issues.num_participants_few=%d 參與者
|
||||||
issues.attachment.open_tab=`在新分頁中查看「%s」`
|
issues.attachment.open_tab=`在新分頁中查看「%s」`
|
||||||
issues.attachment.download=`點擊下載「%s」`
|
issues.attachment.download=`點擊下載「%s」`
|
||||||
issues.subscribe=訂閱
|
issues.subscribe=訂閱
|
||||||
|
@ -2071,9 +2071,9 @@ settings.web_hook_name_dingtalk=DingTalk
|
||||||
settings.web_hook_name_telegram=Telegram
|
settings.web_hook_name_telegram=Telegram
|
||||||
settings.web_hook_name_matrix=Matrix
|
settings.web_hook_name_matrix=Matrix
|
||||||
settings.web_hook_name_msteams=Microsoft Teams
|
settings.web_hook_name_msteams=Microsoft Teams
|
||||||
settings.web_hook_name_feishu_or_larksuite=Feishu / Lark Suite
|
settings.web_hook_name_feishu=Feishu / Lark Suite
|
||||||
settings.web_hook_name_feishu=Feishu
|
settings.web_hook_name_feishu_only =Feishu
|
||||||
settings.web_hook_name_larksuite=Lark Suite
|
settings.web_hook_name_larksuite_only =Lark Suite
|
||||||
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
settings.web_hook_name_wechatwork=WeCom (Wechat Work)
|
||||||
settings.web_hook_name_packagist=Packagist
|
settings.web_hook_name_packagist=Packagist
|
||||||
settings.packagist_username=Packagist 帳號
|
settings.packagist_username=Packagist 帳號
|
||||||
|
|
179
package-lock.json
generated
179
package-lock.json
generated
|
@ -70,6 +70,7 @@
|
||||||
"@stylistic/eslint-plugin-js": "1.7.0",
|
"@stylistic/eslint-plugin-js": "1.7.0",
|
||||||
"@stylistic/stylelint-plugin": "2.1.0",
|
"@stylistic/stylelint-plugin": "2.1.0",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
|
"@vue/test-utils": "2.4.5",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-plugin-array-func": "4.0.0",
|
"eslint-plugin-array-func": "4.0.0",
|
||||||
"eslint-plugin-github": "4.10.2",
|
"eslint-plugin-github": "4.10.2",
|
||||||
|
@ -85,7 +86,7 @@
|
||||||
"eslint-plugin-vue": "9.24.0",
|
"eslint-plugin-vue": "9.24.0",
|
||||||
"eslint-plugin-vue-scoped-css": "2.8.0",
|
"eslint-plugin-vue-scoped-css": "2.8.0",
|
||||||
"eslint-plugin-wc": "2.0.4",
|
"eslint-plugin-wc": "2.0.4",
|
||||||
"happy-dom": "14.3.7",
|
"happy-dom": "14.3.10",
|
||||||
"markdownlint-cli": "0.39.0",
|
"markdownlint-cli": "0.39.0",
|
||||||
"postcss-html": "1.6.0",
|
"postcss-html": "1.6.0",
|
||||||
"stylelint": "16.3.0",
|
"stylelint": "16.3.0",
|
||||||
|
@ -1329,6 +1330,12 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@one-ini/wasm": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
|
@ -2680,6 +2687,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
|
||||||
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
|
"integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/test-utils": {
|
||||||
|
"version": "2.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz",
|
||||||
|
"integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"js-beautify": "^1.14.9",
|
||||||
|
"vue-component-type-helpers": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
||||||
|
@ -2862,6 +2879,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
||||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
|
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/abbrev": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/abort-controller": {
|
"node_modules/abort-controller": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
@ -3799,6 +3825,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/config-chain": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ini": "^1.3.4",
|
||||||
|
"proto-list": "~1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/config-chain/node_modules/ini": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/core-js-compat": {
|
"node_modules/core-js-compat": {
|
||||||
"version": "3.36.1",
|
"version": "3.36.1",
|
||||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
|
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
|
||||||
|
@ -4775,6 +4817,48 @@
|
||||||
"marked": "^4.1.0"
|
"marked": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/editorconfig": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@one-ini/wasm": "0.1.1",
|
||||||
|
"commander": "^10.0.0",
|
||||||
|
"minimatch": "9.0.1",
|
||||||
|
"semver": "^7.5.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"editorconfig": "bin/editorconfig"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/editorconfig/node_modules/commander": {
|
||||||
|
"version": "10.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
||||||
|
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/editorconfig/node_modules/minimatch": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.716",
|
"version": "1.4.716",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz",
|
||||||
|
@ -6513,9 +6597,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/happy-dom": {
|
"node_modules/happy-dom": {
|
||||||
"version": "14.3.7",
|
"version": "14.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.10.tgz",
|
||||||
"integrity": "sha512-lUfDRGzjrVJF2pnvh13OL+qEJ9eDpcedVLm77a3aMg8gPGKXfG+xFMNk3cOWetjucU8FveJ4qcSC/EX55nJ4fQ==",
|
"integrity": "sha512-Rh5li9vA9MF9Gkg85CbFABKTa3uoSAByILRNGb92u/vswDd561gBg2p1UW1ZauvDWWwRxPcbACK5zv3BR+gHnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
|
@ -7398,6 +7482,58 @@
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
|
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/js-beautify": {
|
||||||
|
"version": "1.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
|
||||||
|
"integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"config-chain": "^1.1.13",
|
||||||
|
"editorconfig": "^1.0.4",
|
||||||
|
"glob": "^10.3.3",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"nopt": "^7.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"css-beautify": "js/bin/css-beautify.js",
|
||||||
|
"html-beautify": "js/bin/html-beautify.js",
|
||||||
|
"js-beautify": "js/bin/js-beautify.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-beautify/node_modules/glob": {
|
||||||
|
"version": "10.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
|
||||||
|
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"foreground-child": "^3.1.0",
|
||||||
|
"jackspeak": "^2.3.6",
|
||||||
|
"minimatch": "^9.0.1",
|
||||||
|
"minipass": "^7.0.4",
|
||||||
|
"path-scurry": "^1.10.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glob": "dist/esm/bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-cookie": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-levenshtein-esm": {
|
"node_modules/js-levenshtein-esm": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz",
|
||||||
|
@ -8801,6 +8937,21 @@
|
||||||
"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
|
||||||
"integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
|
"integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/nopt": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"abbrev": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nopt": "bin/nopt.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-package-data": {
|
"node_modules/normalize-package-data": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||||
|
@ -9140,11 +9291,11 @@
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.10.1",
|
"version": "1.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
|
||||||
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
|
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^9.1.1 || ^10.0.0",
|
"lru-cache": "^10.2.0",
|
||||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -9727,6 +9878,12 @@
|
||||||
"integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
|
"integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/proto-list": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/proto-props": {
|
"node_modules/proto-props": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz",
|
||||||
|
@ -12089,6 +12246,12 @@
|
||||||
"vue": "^3.0.0-0 || ^2.7.0"
|
"vue": "^3.0.0-0 || ^2.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-component-type-helpers": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-7e12Evdll7JcTIocojgnCgwocX4WzIYStGClBQ+QuWPinZo/vQolv2EMq4a3lg16TKfwWafLimG77bxb56UauA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/vue-eslint-parser": {
|
"node_modules/vue-eslint-parser": {
|
||||||
"version": "9.4.2",
|
"version": "9.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
"@stylistic/eslint-plugin-js": "1.7.0",
|
"@stylistic/eslint-plugin-js": "1.7.0",
|
||||||
"@stylistic/stylelint-plugin": "2.1.0",
|
"@stylistic/stylelint-plugin": "2.1.0",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
|
"@vue/test-utils": "2.4.5",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-plugin-array-func": "4.0.0",
|
"eslint-plugin-array-func": "4.0.0",
|
||||||
"eslint-plugin-github": "4.10.2",
|
"eslint-plugin-github": "4.10.2",
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
"eslint-plugin-vue": "9.24.0",
|
"eslint-plugin-vue": "9.24.0",
|
||||||
"eslint-plugin-vue-scoped-css": "2.8.0",
|
"eslint-plugin-vue-scoped-css": "2.8.0",
|
||||||
"eslint-plugin-wc": "2.0.4",
|
"eslint-plugin-wc": "2.0.4",
|
||||||
"happy-dom": "14.3.7",
|
"happy-dom": "14.3.10",
|
||||||
"markdownlint-cli": "0.39.0",
|
"markdownlint-cli": "0.39.0",
|
||||||
"postcss-html": "1.6.0",
|
"postcss-html": "1.6.0",
|
||||||
"stylelint": "16.3.0",
|
"stylelint": "16.3.0",
|
||||||
|
|
|
@ -621,6 +621,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||||
ProtectedFilePatterns: form.ProtectedFilePatterns,
|
ProtectedFilePatterns: form.ProtectedFilePatterns,
|
||||||
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
|
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
|
||||||
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
|
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
|
||||||
|
ApplyToAdmins: form.ApplyToAdmins,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
||||||
|
@ -808,6 +809,10 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||||
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
|
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if form.ApplyToAdmins != nil {
|
||||||
|
protectBranch.ApplyToAdmins = *form.ApplyToAdmins
|
||||||
|
}
|
||||||
|
|
||||||
var whitelistUsers []int64
|
var whitelistUsers []int64
|
||||||
if form.PushWhitelistUsernames != nil {
|
if form.PushWhitelistUsernames != nil {
|
||||||
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
|
||||||
|
|
|
@ -337,13 +337,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're an admin for the repository we can ignore status checks, reviews and override protected files
|
// It's not allowed t overwrite protected files. Unless if the user is an
|
||||||
if ctx.userPerm.IsAdmin() {
|
// admin and the protected branch rule doesn't apply to admins.
|
||||||
return
|
if changedProtectedfiles && (!ctx.user.IsAdmin || protectBranch.ApplyToAdmins) {
|
||||||
}
|
|
||||||
|
|
||||||
// Now if we're not an admin - we can't overwrite protected files so fail now
|
|
||||||
if changedProtectedfiles {
|
|
||||||
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
|
log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
|
||||||
ctx.JSON(http.StatusForbidden, private.Response{
|
ctx.JSON(http.StatusForbidden, private.Response{
|
||||||
UserMsg: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
|
UserMsg: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
|
||||||
|
@ -352,8 +348,12 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all status checks and reviews are ok
|
// Check all status checks and reviews are ok
|
||||||
if err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil {
|
if pb, err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil {
|
||||||
if models.IsErrDisallowedToMerge(err) {
|
if models.IsErrDisallowedToMerge(err) {
|
||||||
|
// Allow this if the rule doesn't apply to admins and the user is an admin.
|
||||||
|
if ctx.user.IsAdmin && !pb.ApplyToAdmins {
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error())
|
log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error())
|
||||||
ctx.JSON(http.StatusForbidden, private.Response{
|
ctx.JSON(http.StatusForbidden, private.Response{
|
||||||
UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()),
|
UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()),
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
org_service "code.gitea.io/gitea/services/org"
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -210,6 +211,7 @@ func Webhooks(ctx *context.Context) {
|
||||||
ctx.Data["PageIsSettingsHooks"] = true
|
ctx.Data["PageIsSettingsHooks"] = true
|
||||||
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
|
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
|
||||||
ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks"
|
ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks"
|
||||||
|
ctx.Data["WebhookList"] = webhook_service.List()
|
||||||
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
|
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
|
||||||
|
|
||||||
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID})
|
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Org.Organization.ID})
|
||||||
|
|
|
@ -950,7 +950,7 @@ func getGitIdentity(ctx *context.Context, commitMailID int64, tpl base.TplName,
|
||||||
|
|
||||||
if email == nil || !email.IsActivated {
|
if email == nil || !email.IsActivated {
|
||||||
ctx.Data["Err_CommitMailID"] = true
|
ctx.Data["Err_CommitMailID"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tplEditFile, form)
|
ctx.RenderWithErr(ctx.Tr("repo.editor.invalid_commit_mail"), tpl, form)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -18,6 +19,7 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
|
@ -192,6 +194,7 @@ func Releases(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Releases"] = releases
|
ctx.Data["Releases"] = releases
|
||||||
|
addVerifyTagToContext(ctx)
|
||||||
|
|
||||||
numReleases := ctx.Data["NumReleases"].(int64)
|
numReleases := ctx.Data["NumReleases"].(int64)
|
||||||
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
|
pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
|
||||||
|
@ -201,6 +204,44 @@ func Releases(ctx *context.Context) {
|
||||||
ctx.HTML(http.StatusOK, tplReleasesList)
|
ctx.HTML(http.StatusOK, tplReleasesList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyTagSignature(ctx *context.Context, r *repo_model.Release) (*asymkey.ObjectVerification, error) {
|
||||||
|
if err := r.LoadAttributes(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(ctx, r.Repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
tag, err := gitRepo.GetTag(r.TagName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tag.Signature == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
verification := asymkey.ParseTagWithSignature(ctx, gitRepo, tag)
|
||||||
|
return verification, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addVerifyTagToContext(ctx *context.Context) {
|
||||||
|
ctx.Data["VerifyTag"] = func(r *repo_model.Release) *asymkey.ObjectVerification {
|
||||||
|
v, err := verifyTagSignature(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
ctx.Data["HasSignature"] = func(verification *asymkey.ObjectVerification) bool {
|
||||||
|
if verification == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return verification.Reason != "gpg.error.not_signed_commit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TagsList render tags list page
|
// TagsList render tags list page
|
||||||
func TagsList(ctx *context.Context) {
|
func TagsList(ctx *context.Context) {
|
||||||
ctx.Data["PageIsTagList"] = true
|
ctx.Data["PageIsTagList"] = true
|
||||||
|
@ -240,6 +281,7 @@ func TagsList(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Releases"] = releases
|
ctx.Data["Releases"] = releases
|
||||||
|
addVerifyTagToContext(ctx)
|
||||||
|
|
||||||
numTags := ctx.Data["NumTags"].(int64)
|
numTags := ctx.Data["NumTags"].(int64)
|
||||||
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
|
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
|
||||||
|
@ -304,6 +346,7 @@ func SingleRelease(ctx *context.Context) {
|
||||||
if release.IsTag && release.Title == "" {
|
if release.IsTag && release.Title == "" {
|
||||||
release.Title = release.TagName
|
release.Title = release.TagName
|
||||||
}
|
}
|
||||||
|
addVerifyTagToContext(ctx)
|
||||||
|
|
||||||
ctx.Data["PageIsSingleTag"] = release.IsTag
|
ctx.Data["PageIsSingleTag"] = release.IsTag
|
||||||
if release.IsTag {
|
if release.IsTag {
|
||||||
|
|
|
@ -237,6 +237,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||||
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
|
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
|
||||||
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
|
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
|
||||||
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
|
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
|
||||||
|
protectBranch.ApplyToAdmins = f.ApplyToAdmins
|
||||||
|
|
||||||
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
||||||
UserIDs: whitelistUsers,
|
UserIDs: whitelistUsers,
|
||||||
|
|
|
@ -45,6 +45,7 @@ func WebhookList(ctx *context.Context) {
|
||||||
ctx.Data["PageIsSettingsHooks"] = true
|
ctx.Data["PageIsSettingsHooks"] = true
|
||||||
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
|
ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
|
||||||
ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks"
|
ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks"
|
||||||
|
ctx.Data["WebhookList"] = webhook_service.List()
|
||||||
ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://forgejo.org/docs/latest/user/webhooks/")
|
ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://forgejo.org/docs/latest/user/webhooks/")
|
||||||
|
|
||||||
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID})
|
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID})
|
||||||
|
@ -132,13 +133,16 @@ func WebhookNew(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
hookType := ctx.Params(":type")
|
hookType := ctx.Params(":type")
|
||||||
if webhook_service.GetWebhookHandler(hookType) == nil {
|
handler := webhook_service.GetWebhookHandler(hookType)
|
||||||
|
if handler == nil {
|
||||||
ctx.NotFound("GetWebhookHandler", nil)
|
ctx.NotFound("GetWebhookHandler", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["HookType"] = hookType
|
ctx.Data["HookType"] = hookType
|
||||||
|
ctx.Data["WebhookHandler"] = handler
|
||||||
ctx.Data["BaseLink"] = orCtx.LinkNew
|
ctx.Data["BaseLink"] = orCtx.LinkNew
|
||||||
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
||||||
|
ctx.Data["WebhookList"] = webhook_service.List()
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
|
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
|
||||||
}
|
}
|
||||||
|
@ -194,6 +198,7 @@ func WebhookCreate(ctx *context.Context) {
|
||||||
ctx.Data["PageIsSettingsHooksNew"] = true
|
ctx.Data["PageIsSettingsHooksNew"] = true
|
||||||
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
|
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
|
||||||
ctx.Data["HookType"] = hookType
|
ctx.Data["HookType"] = hookType
|
||||||
|
ctx.Data["WebhookHandler"] = handler
|
||||||
|
|
||||||
orCtx, err := getOwnerRepoCtx(ctx)
|
orCtx, err := getOwnerRepoCtx(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -202,6 +207,7 @@ func WebhookCreate(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["BaseLink"] = orCtx.LinkNew
|
ctx.Data["BaseLink"] = orCtx.LinkNew
|
||||||
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
||||||
|
ctx.Data["WebhookList"] = webhook_service.List()
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
// pre-fill the form with the submitted data
|
// pre-fill the form with the submitted data
|
||||||
|
@ -336,6 +342,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
||||||
}
|
}
|
||||||
ctx.Data["BaseLink"] = orCtx.Link
|
ctx.Data["BaseLink"] = orCtx.Link
|
||||||
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
||||||
|
ctx.Data["WebhookList"] = webhook_service.List()
|
||||||
|
|
||||||
var w *webhook.Webhook
|
var w *webhook.Webhook
|
||||||
if orCtx.RepoID > 0 {
|
if orCtx.RepoID > 0 {
|
||||||
|
@ -358,6 +365,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
||||||
|
|
||||||
if handler := webhook_service.GetWebhookHandler(w.Type); handler != nil {
|
if handler := webhook_service.GetWebhookHandler(w.Type); handler != nil {
|
||||||
ctx.Data["HookMetadata"] = handler.Metadata(w)
|
ctx.Data["HookMetadata"] = handler.Metadata(w)
|
||||||
|
ctx.Data["WebhookHandler"] = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["History"], err = w.History(ctx, 1)
|
ctx.Data["History"], err = w.History(ctx, 1)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -23,6 +24,7 @@ func Webhooks(ctx *context.Context) {
|
||||||
ctx.Data["PageIsSettingsHooks"] = true
|
ctx.Data["PageIsSettingsHooks"] = true
|
||||||
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
|
ctx.Data["BaseLink"] = setting.AppSubURL + "/user/settings/hooks"
|
||||||
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
|
ctx.Data["BaseLinkNew"] = setting.AppSubURL + "/user/settings/hooks"
|
||||||
|
ctx.Data["WebhookList"] = webhook_service.List()
|
||||||
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
|
ctx.Data["Description"] = ctx.Tr("settings.hooks.desc")
|
||||||
|
|
||||||
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})
|
ws, err := db.Find[webhook.Webhook](ctx, webhook.ListWebhookOptions{OwnerID: ctx.Doer.ID})
|
||||||
|
|
|
@ -118,4 +118,5 @@ func WebfingerQuery(ctx *context.Context) {
|
||||||
Aliases: aliases,
|
Aliases: aliases,
|
||||||
Links: links,
|
Links: links,
|
||||||
})
|
})
|
||||||
|
ctx.Resp.Header().Set("Content-Type", "application/jrd+json")
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api
|
||||||
RequireSignedCommits: bp.RequireSignedCommits,
|
RequireSignedCommits: bp.RequireSignedCommits,
|
||||||
ProtectedFilePatterns: bp.ProtectedFilePatterns,
|
ProtectedFilePatterns: bp.ProtectedFilePatterns,
|
||||||
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
|
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
|
||||||
|
ApplyToAdmins: bp.ApplyToAdmins,
|
||||||
Created: bp.CreatedUnix.AsTime(),
|
Created: bp.CreatedUnix.AsTime(),
|
||||||
Updated: bp.UpdatedUnix.AsTime(),
|
Updated: bp.UpdatedUnix.AsTime(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,6 +219,7 @@ type ProtectBranchForm struct {
|
||||||
RequireSignedCommits bool
|
RequireSignedCommits bool
|
||||||
ProtectedFilePatterns string
|
ProtectedFilePatterns string
|
||||||
UnprotectedFilePatterns string
|
UnprotectedFilePatterns string
|
||||||
|
ApplyToAdmins bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the fields
|
// Validate validates the fields
|
||||||
|
|
|
@ -5,10 +5,18 @@ package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/perm/access"
|
||||||
|
"code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/user"
|
"code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
gitea_context "code.gitea.io/gitea/services/context"
|
gitea_context "code.gitea.io/gitea/services/context"
|
||||||
|
file_service "code.gitea.io/gitea/services/repository/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProcessorHelper() *markup.ProcessorHelper {
|
func ProcessorHelper() *markup.ProcessorHelper {
|
||||||
|
@ -29,5 +37,51 @@ func ProcessorHelper() *markup.ProcessorHelper {
|
||||||
// when using gitea context (web context), use user's visibility and user's permission to check
|
// when using gitea context (web context), use user's visibility and user's permission to check
|
||||||
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
|
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
|
||||||
},
|
},
|
||||||
|
GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
|
||||||
|
repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *user.User
|
||||||
|
|
||||||
|
giteaCtx, ok := ctx.(*gitea_context.Context)
|
||||||
|
if ok {
|
||||||
|
user = giteaCtx.Doer
|
||||||
|
}
|
||||||
|
|
||||||
|
perms, err := access.GetUserRepoPermission(ctx, repo, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !perms.CanRead(unit.TypeCode) {
|
||||||
|
return nil, fmt.Errorf("cannot access repository code")
|
||||||
|
}
|
||||||
|
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer gitRepo.Close()
|
||||||
|
|
||||||
|
commit, err := gitRepo.GetCommit(commitSha)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if language != nil {
|
||||||
|
*language, err = file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blob, err := commit.GetBlobByPath(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
|
||||||
return ErrIsChecking
|
return ErrIsChecking
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := CheckPullBranchProtections(ctx, pr, false); err != nil {
|
if pb, err := CheckPullBranchProtections(ctx, pr, false); err != nil {
|
||||||
if !models.IsErrDisallowedToMerge(err) {
|
if !models.IsErrDisallowedToMerge(err) {
|
||||||
log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err)
|
log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err)
|
||||||
return err
|
return err
|
||||||
|
@ -117,8 +117,9 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// * if the doer is admin, they could skip the branch protection check
|
// * if the doer is admin, they could skip the branch protection check,
|
||||||
if adminSkipProtectionCheck {
|
// if that's allowed by the protected branch rule.
|
||||||
|
if adminSkipProtectionCheck && !pb.ApplyToAdmins {
|
||||||
if isRepoAdmin, errCheckAdmin := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); errCheckAdmin != nil {
|
if isRepoAdmin, errCheckAdmin := access_model.IsUserRepoAdmin(ctx, pr.BaseRepo, doer); errCheckAdmin != nil {
|
||||||
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
|
log.Error("Unable to check if %-v is a repo admin in %-v: %v", doer, pr.BaseRepo, errCheckAdmin)
|
||||||
return errCheckAdmin
|
return errCheckAdmin
|
||||||
|
|
|
@ -424,63 +424,64 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks)
|
// CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks).
|
||||||
func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) {
|
// Returns the protected branch rule when `ErrDisallowedToMerge` is returned as error.
|
||||||
|
func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (protectedBranchRule *git_model.ProtectedBranch, err error) {
|
||||||
if err = pr.LoadBaseRepo(ctx); err != nil {
|
if err = pr.LoadBaseRepo(ctx); err != nil {
|
||||||
return fmt.Errorf("LoadBaseRepo: %w", err)
|
return nil, fmt.Errorf("LoadBaseRepo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("LoadProtectedBranch: %v", err)
|
return nil, fmt.Errorf("LoadProtectedBranch: %v", err)
|
||||||
}
|
}
|
||||||
if pb == nil {
|
if pb == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isPass, err := IsPullCommitStatusPass(ctx, pr)
|
isPass, err := IsPullCommitStatusPass(ctx, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !isPass {
|
if !isPass {
|
||||||
return models.ErrDisallowedToMerge{
|
return pb, models.ErrDisallowedToMerge{
|
||||||
Reason: "Not all required status checks successful",
|
Reason: "Not all required status checks successful",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
|
if !issues_model.HasEnoughApprovals(ctx, pb, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return pb, models.ErrDisallowedToMerge{
|
||||||
Reason: "Does not have enough approvals",
|
Reason: "Does not have enough approvals",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
|
if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return pb, models.ErrDisallowedToMerge{
|
||||||
Reason: "There are requested changes",
|
Reason: "There are requested changes",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
|
if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return pb, models.ErrDisallowedToMerge{
|
||||||
Reason: "There are official review requests",
|
Reason: "There are official review requests",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
|
if issues_model.MergeBlockedByOutdatedBranch(pb, pr) {
|
||||||
return models.ErrDisallowedToMerge{
|
return pb, models.ErrDisallowedToMerge{
|
||||||
Reason: "The head branch is behind the base branch",
|
Reason: "The head branch is behind the base branch",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipProtectedFilesCheck {
|
if skipProtectedFilesCheck {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
|
if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
|
||||||
return models.ErrDisallowedToMerge{
|
return pb, models.ErrDisallowedToMerge{
|
||||||
Reason: "Changed protected files",
|
Reason: "Changed protected files",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergedManually mark pr as merged manually
|
// MergedManually mark pr as merged manually
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
|
|
||||||
webhook_model "code.gitea.io/gitea/models/webhook"
|
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/svg"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
)
|
)
|
||||||
|
@ -34,6 +36,14 @@ func (dh defaultHandler) Type() webhook_module.HookType {
|
||||||
return webhook_module.GITEA
|
return webhook_module.GITEA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dh defaultHandler) Icon(size int) template.HTML {
|
||||||
|
if dh.forgejo {
|
||||||
|
// forgejo.svg is not in web_src/svg/, so svg.RenderHTML does not work
|
||||||
|
return imgIcon("forgejo.svg", size)
|
||||||
|
}
|
||||||
|
return svg.RenderHTML("gitea-gitea", size, "img")
|
||||||
|
}
|
||||||
|
|
||||||
func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
func (defaultHandler) Metadata(*webhook_model.Webhook) any { return nil }
|
||||||
|
|
||||||
func (defaultHandler) FormFields(bind func(any)) FormFields {
|
func (defaultHandler) FormFields(bind func(any)) FormFields {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue