mirror of
https://github.com/gogs/gogs.git
synced 2026-03-02 10:11:04 +01:00
Compare commits
281 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58436b5ea5 | ||
|
|
d85a1d478e | ||
|
|
676d774d88 | ||
|
|
e7aabf70dc | ||
|
|
6f929dcd9e | ||
|
|
54ca0b2f09 | ||
|
|
2bd64621fc | ||
|
|
05b419b219 | ||
|
|
b163d79a2e | ||
|
|
f255b1e86d | ||
|
|
9372eedf2e | ||
|
|
14a8a46bec | ||
|
|
7679aa1a21 | ||
|
|
9a8aeef478 | ||
|
|
603c7389b8 | ||
|
|
00eb2b221f | ||
|
|
1b5e1bebc2 | ||
|
|
3a81fdf092 | ||
|
|
6f0a41b8b2 | ||
|
|
25ec20d525 | ||
|
|
8e262f3ec4 | ||
|
|
2cee0f84c0 | ||
|
|
94b2816446 | ||
|
|
8411b50f5d | ||
|
|
8a87bee434 | ||
|
|
1dfa693a5c | ||
|
|
d5b92b61d7 | ||
|
|
a374751eb8 | ||
|
|
0af035c37e | ||
|
|
bc82157216 | ||
|
|
8eb4c3121a | ||
|
|
75aab86a8d | ||
|
|
92535c9df0 | ||
|
|
50058b3c6d | ||
|
|
b0226a1d05 | ||
|
|
67ced4aaca | ||
|
|
04806b614e | ||
|
|
cb100c7781 | ||
|
|
5cad124704 | ||
|
|
fad31ca302 | ||
|
|
6b8bef3cf6 | ||
|
|
940898a3ff | ||
|
|
75fe134072 | ||
|
|
16feb5b655 | ||
|
|
659bd29bc5 | ||
|
|
91c9069c4d | ||
|
|
b992deae92 | ||
|
|
c7eaf96b37 | ||
|
|
4323a89c03 | ||
|
|
b90b0c1191 | ||
|
|
f1aa4c0524 | ||
|
|
102b675f96 | ||
|
|
073da3c49d | ||
|
|
0fe6fe663e | ||
|
|
7d72c8333e | ||
|
|
706b0f72e2 | ||
|
|
1f4beb530c | ||
|
|
89bf56a6ac | ||
|
|
31b375782b | ||
|
|
0252629956 | ||
|
|
4f0e31e96d | ||
|
|
f5689ee3a5 | ||
|
|
a020cf803b | ||
|
|
630ebbe6c2 | ||
|
|
b9f5def5dc | ||
|
|
87c3c8172a | ||
|
|
022820103d | ||
|
|
d5fab7f1b9 | ||
|
|
c3ba5590c9 | ||
|
|
379629d28a | ||
|
|
8b92f9cca6 | ||
|
|
78a4e71245 | ||
|
|
63e6e31271 | ||
|
|
2be5837cb0 | ||
|
|
71d8ff247d | ||
|
|
0fbb8c8826 | ||
|
|
e0aab4a7f6 | ||
|
|
db7ac8bc1d | ||
|
|
c6ce6bd4c2 | ||
|
|
b5fdf0947b | ||
|
|
35a65736fa | ||
|
|
04af4b24fd | ||
|
|
a301c7ed26 | ||
|
|
f7c7837fc8 | ||
|
|
02289479ef | ||
|
|
eac91a74d8 | ||
|
|
f17b746a01 | ||
|
|
d3f67d341f | ||
|
|
0f4f81f1fb | ||
|
|
d0c17adfea | ||
|
|
87c05c386f | ||
|
|
8fc8848ce2 | ||
|
|
1f1abb17e2 | ||
|
|
fc7959d3bc | ||
|
|
4dc6285715 | ||
|
|
9825760817 | ||
|
|
9573f9afe9 | ||
|
|
6599869f28 | ||
|
|
932dbccb67 | ||
|
|
a749e6adcf | ||
|
|
b854d3ba40 | ||
|
|
6a6e43f964 | ||
|
|
a42514613f | ||
|
|
9acf02ad7f | ||
|
|
5c6df9f31b | ||
|
|
533c6a8e08 | ||
|
|
bfed40eec4 | ||
|
|
575300cd57 | ||
|
|
9cba6ff84b | ||
|
|
570ddefc32 | ||
|
|
5676fa5b5d | ||
|
|
fc427432ed | ||
|
|
d7f390a3b0 | ||
|
|
2671c86ba7 | ||
|
|
f1c2276c8d | ||
|
|
2020bafee1 | ||
|
|
bc2f546023 | ||
|
|
ef6d12844c | ||
|
|
a443fcf33a | ||
|
|
b5e6af9587 | ||
|
|
01dc8f8a4f | ||
|
|
aff49b1c9e | ||
|
|
b1941f1da1 | ||
|
|
19c3745488 | ||
|
|
ea6c6bc20a | ||
|
|
db00aa7653 | ||
|
|
215920772a | ||
|
|
6fe868a4d5 | ||
|
|
02d3b66265 | ||
|
|
fe8495e4a5 | ||
|
|
280fde9b7c | ||
|
|
79fb24a397 | ||
|
|
4465c58f4b | ||
|
|
5981f1edcd | ||
|
|
ad5e0b833c | ||
|
|
e5310cdbc1 | ||
|
|
e34d0063c3 | ||
|
|
3e7d8db7a2 | ||
|
|
655b69cb1f | ||
|
|
fcb1f4ec07 | ||
|
|
3cad8d9492 | ||
|
|
e63e0b3105 | ||
|
|
d86c785410 | ||
|
|
b05c7b3faa | ||
|
|
5c39d3fa7d | ||
|
|
c60d8bc069 | ||
|
|
87d64acc9f | ||
|
|
c8aa9c6cb1 | ||
|
|
ecd59deb27 | ||
|
|
e0a099ec11 | ||
|
|
5af872955b | ||
|
|
e00268895c | ||
|
|
4a05c4a759 | ||
|
|
a205acf829 | ||
|
|
d5ad4e1141 | ||
|
|
2dde2a8ad3 | ||
|
|
6dfee30bf0 | ||
|
|
21e13cb51e | ||
|
|
986447335d | ||
|
|
84c727ae66 | ||
|
|
ee1256cf74 | ||
|
|
dfc16d0879 | ||
|
|
a8fd615adc | ||
|
|
c98dad1cf3 | ||
|
|
0d5e57e4ae | ||
|
|
1c35380c2c | ||
|
|
bb7ddb45ff | ||
|
|
939d2054d8 | ||
|
|
3544dafb64 | ||
|
|
fefce965f9 | ||
|
|
14a1101139 | ||
|
|
5a2093b053 | ||
|
|
2f23cf98ea | ||
|
|
4f8b209956 | ||
|
|
043ded0896 | ||
|
|
e07675b480 | ||
|
|
4c30caad1c | ||
|
|
216f0477b5 | ||
|
|
befed9c20c | ||
|
|
e787e73e2f | ||
|
|
f8c09dc1ff | ||
|
|
42a38dfca3 | ||
|
|
91220a2501 | ||
|
|
700ac8dea7 | ||
|
|
69b1d65c9b | ||
|
|
7acbcf9ddd | ||
|
|
2340bb1ed2 | ||
|
|
d9c5b3bcee | ||
|
|
86d3c5cbb3 | ||
|
|
acf428863c | ||
|
|
3fb1b6a608 | ||
|
|
562e47f31c | ||
|
|
9d36fc6986 | ||
|
|
923873db85 | ||
|
|
373731f5e8 | ||
|
|
e75fd2f783 | ||
|
|
a517cfdf7b | ||
|
|
2729eb998c | ||
|
|
b003b18788 | ||
|
|
6a1907d994 | ||
|
|
e303d74ab6 | ||
|
|
e4ecbcdf4a | ||
|
|
f5c7f22cc8 | ||
|
|
2bc3e83e1c | ||
|
|
d600530c20 | ||
|
|
2d1bb0cf49 | ||
|
|
5f1183cecf | ||
|
|
b0bf4cc1cb | ||
|
|
98108e379d | ||
|
|
83e747bfda | ||
|
|
e5ed5904c6 | ||
|
|
1fa5b6711b | ||
|
|
8e0a69f86a | ||
|
|
e2d6b0116e | ||
|
|
cd37fccdfb | ||
|
|
6969c20afd | ||
|
|
f4e54aafa5 | ||
|
|
d185f601d3 | ||
|
|
aff773f1b9 | ||
|
|
10de16beb0 | ||
|
|
ed5a61153f | ||
|
|
47df562ced | ||
|
|
c7ac237b57 | ||
|
|
be89802bd8 | ||
|
|
247017d9ff | ||
|
|
362d64df04 | ||
|
|
373ef5d15e | ||
|
|
121a81a2c5 | ||
|
|
0617448282 | ||
|
|
fa728d8dff | ||
|
|
26ac016b9f | ||
|
|
cbd6276200 | ||
|
|
52ec80fa18 | ||
|
|
c8d92fad30 | ||
|
|
c3061c61a7 | ||
|
|
e4d4662074 | ||
|
|
9899ea71e8 | ||
|
|
b954a22ce2 | ||
|
|
964d0262ff | ||
|
|
b75d0378cb | ||
|
|
357c002c03 | ||
|
|
f432f1f41c | ||
|
|
6f6f38e7c3 | ||
|
|
49e120a67c | ||
|
|
af324a6165 | ||
|
|
7b0ae27549 | ||
|
|
31e7b0f588 | ||
|
|
03ea29eb36 | ||
|
|
32c12d553c | ||
|
|
cc83043edc | ||
|
|
3d9b98fae4 | ||
|
|
36405d0faa | ||
|
|
5020576e80 | ||
|
|
56eb252098 | ||
|
|
fbb3486c95 | ||
|
|
7b92dc3d9d | ||
|
|
645d4d0c5b | ||
|
|
232c22208c | ||
|
|
0806725ca5 | ||
|
|
d504ee0417 | ||
|
|
ae1650824c | ||
|
|
c38754d432 | ||
|
|
85f34ba538 | ||
|
|
00767a0522 | ||
|
|
4dbc322859 | ||
|
|
edc99bc8a4 | ||
|
|
80701d45bb | ||
|
|
aa67de910a | ||
|
|
24658fcbdd | ||
|
|
705224353b | ||
|
|
079a2d68db | ||
|
|
119dec51f2 | ||
|
|
46dce2d653 | ||
|
|
d48cde6ec8 | ||
|
|
3b0c2cb480 | ||
|
|
d464d3b0c3 | ||
|
|
b80aef0fa6 | ||
|
|
0f07a5cb84 | ||
|
|
7cb4aa8d82 | ||
|
|
aa9c36514f | ||
|
|
2d1db4bf05 |
@@ -13,8 +13,7 @@ watch_dirs = [
|
||||
watch_exts = [".go"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
#["go-bindata", "-o=modules/bindata/bindata.go", "-ignore=\\.DS_Store|README", "-pkg=bindata", "conf/..."],
|
||||
["go", "install", "-tags", "sqlite"],# redis memcache cert pam
|
||||
["go", "install", "-tags", "sqlite"],# redis memcache cert pam tidb
|
||||
["go", "build", "-tags", "sqlite"],
|
||||
["./gogs", "web"]
|
||||
]
|
||||
@@ -1,6 +1,14 @@
|
||||
.git
|
||||
.git/
|
||||
.git/*
|
||||
conf
|
||||
conf/
|
||||
conf/*
|
||||
packager
|
||||
packager/
|
||||
packager/*
|
||||
scripts
|
||||
scripts/
|
||||
scripts/*
|
||||
*.yml
|
||||
*.md
|
||||
@@ -9,4 +17,4 @@ scripts/*
|
||||
.gitignore
|
||||
.gopmfile
|
||||
config.codekit
|
||||
LICENSE
|
||||
LICENSE
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,3 +35,5 @@ docker/docker/Dockerfile
|
||||
docker/docker/init_gogs.sh
|
||||
gogs.sublime-project
|
||||
gogs.sublime-workspace
|
||||
.tags*
|
||||
release
|
||||
|
||||
58
.gopmfile
58
.gopmfile
@@ -3,37 +3,43 @@ path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/bradfitz/gomemcache = commit:72a68649ba
|
||||
github.com/Unknwon/cae = commit:2e70a1351b
|
||||
github.com/Unknwon/com = commit:47d7d2b81a
|
||||
github.com/Unknwon/i18n = commit:7457d88830
|
||||
github.com/Unknwon/macaron = commit:635c89ac74
|
||||
github.com/Unknwon/paginater = commit:cab2d086fa
|
||||
github.com/codegangsta/cli = commit:142e6cd241
|
||||
github.com/go-sql-driver/mysql = commit:3dd7008ac1
|
||||
github.com/go-xorm/core = commit:515edd92c1
|
||||
github.com/go-xorm/xorm = commit:ce23797899
|
||||
github.com/codegangsta/cli = commit:70e3fa5
|
||||
github.com/go-macaron/binding = commit:864a5ce
|
||||
github.com/go-macaron/cache = commit:5617353
|
||||
github.com/go-macaron/captcha = commit:875ff77
|
||||
github.com/go-macaron/csrf = commit:75c2b04
|
||||
github.com/go-macaron/gzip = commit:4938e9b
|
||||
github.com/go-macaron/i18n = commit:5e728b6
|
||||
github.com/go-macaron/inject = commit:c5ab7bf
|
||||
github.com/go-macaron/session = commit:66031fc
|
||||
github.com/go-macaron/toolbox = commit:ddfcf96
|
||||
github.com/go-sql-driver/mysql = commit:527bcd55aa
|
||||
github.com/go-xorm/core = commit:3e10003353
|
||||
github.com/go-xorm/xorm = commit:8bf4405
|
||||
github.com/gogits/chardet = commit:2404f77725
|
||||
github.com/gogits/go-gogs-client = commit:519eee0af0
|
||||
github.com/lib/pq = commit:b269bd035a
|
||||
github.com/macaron-contrib/binding = commit:de6ed78668
|
||||
github.com/macaron-contrib/cache = commit:a139ea1eee
|
||||
github.com/macaron-contrib/captcha = commit:9a0a0b1468
|
||||
github.com/macaron-contrib/csrf = commit:98ddf5a710
|
||||
github.com/macaron-contrib/i18n = commit:da2b19e90b
|
||||
github.com/macaron-contrib/oauth2 = commit:1adb5ce072
|
||||
github.com/macaron-contrib/session = commit:e48134e803
|
||||
github.com/macaron-contrib/toolbox = commit:acbfe36e16
|
||||
github.com/mattn/go-sqlite3 = commit:897b8800a7
|
||||
github.com/mcuadros/go-version = commit:d52711f8d6
|
||||
github.com/microcosm-cc/bluemonday = commit:2b7763a06c
|
||||
github.com/mssola/user_agent = commit:a163d6a569
|
||||
github.com/gogits/go-gogs-client = commit:1030bf8
|
||||
github.com/issue9/identicon = commit:5a61672
|
||||
github.com/klauspost/compress = commit:0449b1c
|
||||
github.com/klauspost/cpuid = commit:8d9fe96
|
||||
github.com/klauspost/crc32 = commit:f8d2e12
|
||||
github.com/lib/pq = commit:83c4f41
|
||||
github.com/mattn/go-sqlite3 = commit:5651a9d
|
||||
github.com/mcuadros/go-version = commit:d52711f
|
||||
github.com/microcosm-cc/bluemonday = commit:4ac6f27
|
||||
github.com/mssola/user_agent = commit:783ec61
|
||||
github.com/msteinert/pam = commit:6534f23b39
|
||||
github.com/nfnt/resize = commit:dc93e1b98c
|
||||
github.com/russross/blackfriday = commit:8cec3a854e
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:244f5ac324
|
||||
github.com/russross/blackfriday = commit:510be64
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:10ef21a
|
||||
github.com/Unknwon/cae = commit:7f5e046
|
||||
github.com/Unknwon/com = commit:28b053d
|
||||
github.com/Unknwon/i18n = commit:7457d88830
|
||||
github.com/Unknwon/paginater = commit:7748a72
|
||||
golang.org/x/net =
|
||||
golang.org/x/text =
|
||||
gopkg.in/ini.v1 = commit:463307112d
|
||||
gopkg.in/gomail.v2 = commit:df6fc79
|
||||
gopkg.in/ini.v1 = commit:060d7da
|
||||
gopkg.in/macaron.v1 = commit:1c6dd87
|
||||
gopkg.in/redis.v2 = commit:e617904962
|
||||
|
||||
[res]
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y libpam-dev
|
||||
- go get github.com/msteinert/pam
|
||||
|
||||
install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script: go build -v -tags "pam"
|
||||
|
||||
notifications:
|
||||
|
||||
@@ -42,7 +42,7 @@ There is no standard form of making a feature request. Just try to describe the
|
||||
|
||||
### Pull Request
|
||||
|
||||
Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `DEV` BRANCH**.
|
||||
Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `develop` BRANCH**.
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it.
|
||||
|
||||
|
||||
66
Dockerfile
66
Dockerfile
@@ -1,54 +1,22 @@
|
||||
FROM google/debian:wheezy
|
||||
MAINTAINER u@gogs.io
|
||||
FROM alpine:3.2
|
||||
MAINTAINER roemer.jp@gmail.com
|
||||
|
||||
RUN echo "deb http://ftp.debian.org/debian/ wheezy-backports main" >> /etc/apt/sources.list && \
|
||||
apt-get update -qqy && \
|
||||
apt-get install --no-install-recommends -qqy \
|
||||
curl build-essential ca-certificates git \
|
||||
openssh-server rsync libpam-dev && \
|
||||
apt-get autoclean && \
|
||||
apt-get autoremove && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# Install system utils & Gogs runtime dependencies
|
||||
ADD https://github.com/tianon/gosu/releases/download/1.6/gosu-amd64 /usr/sbin/gosu
|
||||
RUN echo "@edge http://dl-4.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories \
|
||||
&& echo "@community http://dl-4.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories \
|
||||
&& apk -U --no-progress upgrade \
|
||||
&& apk -U --no-progress add ca-certificates bash git linux-pam s6@edge curl openssh socat \
|
||||
&& chmod +x /usr/sbin/gosu
|
||||
|
||||
ENV GOROOT /goroot
|
||||
ENV GOPATH /gopath
|
||||
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
|
||||
|
||||
COPY . /gopath/src/github.com/gogits/gogs/
|
||||
WORKDIR /gopath/src/github.com/gogits/gogs/
|
||||
|
||||
# Build binary and clean up useless files
|
||||
RUN mkdir /goroot && \
|
||||
curl https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz | tar xzf - -C /goroot --strip-components=1 && \
|
||||
go get -v -tags "sqlite redis memcache cert pam" && \
|
||||
go build -tags "sqlite redis memcache cert pam" && \
|
||||
mkdir /app/ && \
|
||||
mv /gopath/src/github.com/gogits/gogs/ /app/gogs/ && \
|
||||
rm -r $GOROOT $GOPATH
|
||||
|
||||
WORKDIR /app/gogs/
|
||||
|
||||
RUN useradd --shell /bin/bash --system --comment gogits git
|
||||
|
||||
# SSH login fix, otherwise user is kicked off after login
|
||||
RUN mkdir /var/run/sshd && \
|
||||
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd && \
|
||||
sed 's@UsePrivilegeSeparation yes@UsePrivilegeSeparation no@' -i /etc/ssh/sshd_config && \
|
||||
echo "export VISIBLE=now" >> /etc/profile && \
|
||||
echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config
|
||||
|
||||
# Setup server keys on startup
|
||||
RUN sed 's@^HostKey@\#HostKey@' -i /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_rsa_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_dsa_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_ecdsa_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_ed25519_key" >> /etc/ssh/sshd_config
|
||||
|
||||
# Prepare data
|
||||
ENV GOGS_CUSTOM /data/gogs
|
||||
RUN echo "export GOGS_CUSTOM=/data/gogs" >> /etc/profile
|
||||
|
||||
COPY . /app/gogs/
|
||||
WORKDIR /app/gogs/
|
||||
RUN ./docker/build.sh
|
||||
|
||||
# Configure Docker Container
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT []
|
||||
CMD ["./docker/start.sh"]
|
||||
ENTRYPOINT ["docker/start.sh"]
|
||||
CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
|
||||
30
Makefile
Normal file
30
Makefile
Normal file
@@ -0,0 +1,30 @@
|
||||
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
|
||||
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildGitHash=$(shell git rev-parse HEAD)"
|
||||
|
||||
TAGS = ""
|
||||
|
||||
RELEASE_ROOT = "release"
|
||||
RELEASE_GOGS = "release/gogs"
|
||||
NOW = $(shell date -u '+%Y%m%d%I%M%S')
|
||||
|
||||
.PHONY: build pack release bindata clean
|
||||
|
||||
build:
|
||||
go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
go build -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
|
||||
pack:
|
||||
find . -name ".DS_Store" -print0 | xargs -0 rm
|
||||
rm -rf $(RELEASE_GOGS)
|
||||
mkdir -p $(RELEASE_GOGS)
|
||||
cp -r gogs LICENSE README.md README_ZH.md templates public scripts $(RELEASE_GOGS)
|
||||
rm -rf $(RELEASE_GOGS)/public/config.codekit $(RELEASE_GOGS)/public/less
|
||||
cd $(RELEASE_ROOT) && zip -r gogs.$(NOW).zip "gogs"
|
||||
|
||||
release: build pack
|
||||
|
||||
bindata:
|
||||
go-bindata -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/...
|
||||
|
||||
clean:
|
||||
go clean -i ./...
|
||||
85
README.md
85
README.md
@@ -3,25 +3,25 @@ Gogs - Go Git Service [](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Gogs (Go Git Service) is a painless self-hosted Git service.
|
||||

|
||||
|
||||
##### Current version: 0.6.9 Beta
|
||||
##### Current version: 0.7.0 Beta
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/1.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/2.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/3.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/1.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/2.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/img/screenshots/3.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/4.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/5.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/6.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/4.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/5.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/6.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/7.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/8.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/9.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/7.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/8.png"></td>
|
||||
<td><img src="http://gogs.io/img/screenshots/9.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -29,46 +29,47 @@ Gogs (Go Git Service) is a painless self-hosted Git service.
|
||||
|
||||
- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
|
||||
- The demo site [try.gogs.io](https://try.gogs.io) is running under `develop` branch.
|
||||
- :exclamation::exclamation::exclamation:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes and feature Pull Requests, otherwise it's high possibilities that we are not going to merge it.</span>:exclamation::exclamation::exclamation:
|
||||
- :bangbang:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes and feature Pull Requests, otherwise it's high possibilities that we are not going to merge it.</span>:bangbang:
|
||||
- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
|
||||
- If you're interested in using APIs, we have experimental support with [documentation](https://github.com/gogits/go-gogs-client/wiki).
|
||||
- If your team/company is using Gogs and would like to put your logo on [our website](http://gogs.io), contact us by any means.
|
||||
|
||||
#### Other language version
|
||||
|
||||
- [简体中文](README_ZH.md)
|
||||
[简体中文](README_ZH.md)
|
||||
|
||||
## Purpose
|
||||
|
||||
The goal of this project is to make the easiest, fastest, and most painless way to set up a self-hosted Git service. With Go, this can be done via an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows.
|
||||
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. With Go, this can be done with an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, Windows and ARM.
|
||||
|
||||
## Overview
|
||||
|
||||
- Please see the [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log.
|
||||
- Please see the [Documentation](http://gogs.io/docs/intro) for common usages and change log.
|
||||
- See the [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
|
||||
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs) or go down to the **Installation -> Install from binary** section!
|
||||
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md).
|
||||
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.html).
|
||||
- Want to help with localization? Check out the [guide](http://gogs.io/docs/features/i18n.html)!
|
||||
|
||||
## Features
|
||||
|
||||
- Activity timeline
|
||||
- SSH/HTTP(S) protocol support
|
||||
- SMTP/LDAP/reverse proxy authentication support
|
||||
- Reverse proxy suburl support
|
||||
- Account/Organization(with team)/Repository management
|
||||
- Repository/Organization webhooks(including Slack)
|
||||
- SSH and HTTP/HTTPS protocols
|
||||
- SMTP/LDAP/Reverse proxy authentication
|
||||
- Reverse proxy with sub-path
|
||||
- Account/Organization/Repository management
|
||||
- Repository/Organization webhooks (including Slack)
|
||||
- Repository Git hooks/deploy keys
|
||||
- Add/remove repository collaborators
|
||||
- Gravatar and custom source support
|
||||
- Repository issues and pull requests
|
||||
- Add/Remove repository collaborators
|
||||
- Gravatar and custom source
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL and SQLite3
|
||||
- Social account login (GitHub, Google, QQ, Weibo)
|
||||
- CI integration: [Drone](https://github.com/drone/drone)
|
||||
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Multi-language support ([14 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
## System Requirements
|
||||
|
||||
- A cheap Raspberry Pi is powerful enough for basic functionality.
|
||||
- At least 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
- 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
|
||||
## Browser Support
|
||||
|
||||
@@ -77,13 +78,13 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you install the [prerequisites](http://gogs.io/docs/installation/) first.
|
||||
Make sure you install the [prerequisites](http://gogs.io/docs/installation) first.
|
||||
|
||||
There are 5 ways to install Gogs:
|
||||
|
||||
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.md)
|
||||
- [Install from source](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.html)
|
||||
- [Install from source](http://gogs.io/docs/installation/install_from_source.html)
|
||||
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.html)
|
||||
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/docker)
|
||||
- [Install with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
@@ -99,17 +100,31 @@ There are 5 ways to install Gogs:
|
||||
|
||||
- [Instalando Gogs no Ubuntu](http://blog.linuxpro.com.br/2015/08/14/instalando-gogs-no-ubuntu/) (Português)
|
||||
|
||||
### Deploy to Cloud
|
||||
|
||||
- [OpenShift](https://github.com/tkisme/gogs-openshift)
|
||||
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
|
||||
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
|
||||
- [Portal](https://portaldemo.xyz/cloud/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
|
||||
### Product Support
|
||||
|
||||
- [Synology](https://www.synology.com) (Docker)
|
||||
- [One Space](http://www.onespace.cc) (App Store)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Router and middleware mechanism of [Macaron](https://github.com/Unknwon/macaron).
|
||||
- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
|
||||
- Router and middleware mechanism of [Macaron](https://github.com/go-macaron/macaron).
|
||||
- Modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
|
||||
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
|
||||
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
|
||||
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
|
||||
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
|
||||
|
||||
## Contributors
|
||||
|
||||
- Ex-team members [@lunny](https://github.com/lunny) and [@fuxiaohei](https://github.com/fuxiaohei).
|
||||
- Ex-team members [@lunny](https://github.com/lunny), [@fuxiaohei](https://github.com/fuxiaohei) and [@slene](https://github.com/slene).
|
||||
- See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
- See [TRANSLATORS](conf/locale/TRANSLATORS) for public list of translators.
|
||||
|
||||
|
||||
51
README_ZH.md
51
README_ZH.md
@@ -1,35 +1,36 @@
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs)
|
||||
=====================
|
||||
|
||||
Gogs (Go Git Service) 是一款可轻易搭建的自助 Git 服务。
|
||||
Gogs (Go Git Service) 是一款极易搭建的自助 Git 服务。
|
||||
|
||||
## 开发目的
|
||||
|
||||
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。
|
||||
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X、Windows 以及 ARM 平台。
|
||||
|
||||
## 项目概览
|
||||
|
||||
- 有关项目设计、已知问题和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
|
||||
- 想要先睹为快?通过 [在线体验](https://try.gogs.io/gogs/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.md) 页面获取帮助。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.html) 页面获取帮助。
|
||||
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](http://gogs.io/docs/features/i18n.html)!
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持活动时间线
|
||||
- 支持 SSH/HTTP(S) 协议
|
||||
- 支持 SMTP/LDAP/反向代理的用户认证
|
||||
- 支持 SSH 以及 HTTP/HTTPS 协议
|
||||
- 支持 SMTP、LDAP 和反向代理的用户认证
|
||||
- 支持反向代理子路径
|
||||
- 支持用户、组织和仓库管理系统
|
||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
||||
- 支持仓库 Git 钩子和部署密钥
|
||||
- 支持 添加/删除 仓库协作者
|
||||
- 支持仓库工单(Issue)和合并请求(Pull Request)
|
||||
- 支持添加和删除仓库协作者
|
||||
- 支持 Gravatar 以及自定义源
|
||||
- 支持邮件服务
|
||||
- 支持后台管理面板
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
|
||||
- 支持社交帐号登录(GitHub、Google、QQ、微博)
|
||||
- 支持 CI 集成:[Drone](https://github.com/drone/drone)
|
||||
- 支持 MySQL、PostgreSQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
|
||||
- 支持多语言本地化([14 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
## 系统要求
|
||||
@@ -44,27 +45,45 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
|
||||
## 安装部署
|
||||
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation)。
|
||||
|
||||
然后,您可以通过以下 5 种方式来安装 Gogs:
|
||||
|
||||
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.md)
|
||||
- [源码安装](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [包管理安装](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.html)
|
||||
- [源码安装](http://gogs.io/docs/installation/install_from_source.html)
|
||||
- [包管理安装](http://gogs.io/docs/installation/install_from_packages.html)
|
||||
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/docker)
|
||||
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
### 使用教程
|
||||
|
||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654)
|
||||
|
||||
### 云端部署
|
||||
|
||||
- [OpenShift](https://github.com/tkisme/gogs-openshift)
|
||||
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
|
||||
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
|
||||
- [Portal](https://portaldemo.xyz/cloud/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
|
||||
### 产品支持
|
||||
|
||||
- [Synology](https://www.synology.com)(Docker)
|
||||
- [One Space](http://www.onespace.cc)(应用商店)
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
- 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。
|
||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。
|
||||
- 基于 [Macaron](https://github.com/go-macaron/macaron) 的路由与中间件机制。
|
||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的模块设计。
|
||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
|
||||
- 感谢 [DigitalOcean](https://www.digitalocean.com) 提供主站和体验站点的服务器赞助。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
- 前团队成员 [@lunny](https://github.com/lunny) 和 [@fuxiaohei](https://github.com/fuxiaohei)。
|
||||
- 前团队成员 [@lunny](https://github.com/lunny)、[@fuxiaohei](https://github.com/fuxiaohei) 和 [@slene](https://github.com/slene)。
|
||||
- 您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
- 您可以通过查看 [TRANSLATORS](conf/locale/TRANSLATORS) 文件获取公开的翻译人员列表。
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func runCert(ctx *cli.Context) {
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Acme Co"},
|
||||
CommonName: "Gogs",
|
||||
CommonName: "Gogs",
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
@@ -34,8 +34,8 @@ func runDump(ctx *cli.Context) {
|
||||
if ctx.IsSet("config") {
|
||||
setting.CustomConf = ctx.String("config")
|
||||
}
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
models.SetEngine()
|
||||
|
||||
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
|
||||
|
||||
69
cmd/serve.go
69
cmd/serve.go
@@ -5,6 +5,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -37,7 +38,7 @@ var CmdServ = cli.Command{
|
||||
}
|
||||
|
||||
func setup(logPath string) {
|
||||
setting.NewConfigContext()
|
||||
setting.NewContext()
|
||||
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
|
||||
|
||||
if setting.DisableSSH {
|
||||
@@ -45,9 +46,9 @@ func setup(logPath string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
models.LoadModelsConfig()
|
||||
models.LoadConfigs()
|
||||
|
||||
if setting.UseSQLite3 {
|
||||
if setting.UseSQLite3 || setting.UseTiDB {
|
||||
workDir, _ := setting.WorkDir()
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
@@ -76,6 +77,43 @@ func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
}
|
||||
|
||||
func handleUpdateTask(uuid string, user *models.User, repoUserName, repoName string) {
|
||||
task, err := models.GetUpdateTaskByUUID(uuid)
|
||||
if err != nil {
|
||||
if models.IsErrUpdateTaskNotExist(err) {
|
||||
log.GitLogger.Trace("No update task is presented: %s", uuid)
|
||||
return
|
||||
}
|
||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID,
|
||||
user.Name, repoUserName, repoName, user.Id); err != nil {
|
||||
log.GitLogger.Error(2, "Update: %v", err)
|
||||
}
|
||||
|
||||
if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
|
||||
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
// Ask for running deliver hook and test pull request tasks.
|
||||
reqURL := setting.AppUrl + repoUserName + "/" + repoName + "/tasks/trigger?branch=" +
|
||||
strings.TrimPrefix(task.RefName, "refs/heads/")
|
||||
log.GitLogger.Trace("Trigger task: %s", reqURL)
|
||||
|
||||
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).Response()
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code")
|
||||
}
|
||||
} else {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runServ(c *cli.Context) {
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
@@ -161,7 +199,7 @@ func runServ(c *cli.Context) {
|
||||
fail("Internal error", "UpdateDeployKey: %v", err)
|
||||
}
|
||||
} else {
|
||||
user, err = models.GetUserByKeyId(key.ID)
|
||||
user, err = models.GetUserByKeyID(key.ID)
|
||||
if err != nil {
|
||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
||||
}
|
||||
@@ -200,28 +238,7 @@ func runServ(c *cli.Context) {
|
||||
}
|
||||
|
||||
if requestedMode == models.ACCESS_MODE_WRITE {
|
||||
tasks, err := models.GetUpdateTasksByUuid(uuid)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(2, "GetUpdateTasksByUuid: %v", err)
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId,
|
||||
user.Name, repoUserName, repoName, user.Id)
|
||||
if err != nil {
|
||||
log.GitLogger.Error(2, "Failed to update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = models.DelUpdateTasksByUuid(uuid); err != nil {
|
||||
log.GitLogger.Fatal(2, "DelUpdateTasksByUuid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send deliver hook request.
|
||||
resp, err := httplib.Head(setting.AppUrl + setting.AppSubUrl + repoUserName + "/" + repoName + "/hooks/trigger").Response()
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
handleUpdateTask(uuid, user, repoUserName, repoName)
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
|
||||
@@ -42,16 +42,14 @@ func runUpdate(c *cli.Context) {
|
||||
log.GitLogger.Fatal(2, "refName is empty, shouldn't use")
|
||||
}
|
||||
|
||||
uuid := os.Getenv("uuid")
|
||||
|
||||
task := models.UpdateTask{
|
||||
Uuid: uuid,
|
||||
UUID: os.Getenv("uuid"),
|
||||
RefName: args[0],
|
||||
OldCommitId: args[1],
|
||||
NewCommitId: args[2],
|
||||
OldCommitID: args[1],
|
||||
NewCommitID: args[2],
|
||||
}
|
||||
|
||||
if err := models.AddUpdateTask(&task); err != nil {
|
||||
log.GitLogger.Fatal(2, err.Error())
|
||||
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
85
cmd/web.go
85
cmd/web.go
@@ -15,19 +15,19 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/cache"
|
||||
"github.com/go-macaron/captcha"
|
||||
"github.com/go-macaron/csrf"
|
||||
"github.com/go-macaron/gzip"
|
||||
"github.com/go-macaron/i18n"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/go-macaron/toolbox"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/macaron-contrib/cache"
|
||||
"github.com/macaron-contrib/captcha"
|
||||
"github.com/macaron-contrib/csrf"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
"github.com/macaron-contrib/oauth2"
|
||||
"github.com/macaron-contrib/session"
|
||||
"github.com/macaron-contrib/toolbox"
|
||||
"github.com/mcuadros/go-version"
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
@@ -80,13 +80,13 @@ func checkVersion() {
|
||||
|
||||
// Check dependency version.
|
||||
checkers := []VerChecker{
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.3.0806"},
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.4.1029"},
|
||||
{"github.com/Unknwon/macaron", macaron.Version, "0.5.4"},
|
||||
{"github.com/macaron-contrib/binding", binding.Version, "0.1.0"},
|
||||
{"github.com/macaron-contrib/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.3"},
|
||||
{"github.com/macaron-contrib/i18n", i18n.Version, "0.0.7"},
|
||||
{"github.com/macaron-contrib/session", session.Version, "0.1.6"},
|
||||
{"github.com/go-macaron/binding", binding.Version, "0.1.0"},
|
||||
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/go-macaron/csrf", csrf.Version, "0.0.3"},
|
||||
{"github.com/go-macaron/i18n", i18n.Version, "0.0.7"},
|
||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.3.4"},
|
||||
}
|
||||
for _, c := range checkers {
|
||||
@@ -104,7 +104,7 @@ func newMacaron() *macaron.Macaron {
|
||||
}
|
||||
m.Use(macaron.Recovery())
|
||||
if setting.EnableGzip {
|
||||
m.Use(macaron.Gziper())
|
||||
m.Use(gzip.Gziper())
|
||||
}
|
||||
if setting.Protocol == setting.FCGI {
|
||||
m.SetURLPrefix(setting.AppSubUrl)
|
||||
@@ -167,13 +167,6 @@ func newMacaron() *macaron.Macaron {
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// OAuth 2.
|
||||
if setting.OauthService != nil {
|
||||
for _, info := range setting.OauthService.OauthInfos {
|
||||
m.Use(oauth2.NewOAuth2Provider(info.Options, info.AuthUrl, info.TokenUrl))
|
||||
}
|
||||
}
|
||||
m.Use(middleware.Contexter())
|
||||
return m
|
||||
}
|
||||
@@ -200,7 +193,7 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("/explore", ignSignIn, routers.Explore)
|
||||
m.Combo("/install", routers.InstallInit).Get(routers.Install).
|
||||
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||
m.Get("/:type(issues|pulls)", reqSignIn, user.Issues)
|
||||
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
|
||||
|
||||
// ***** START: API *****
|
||||
// FIXME: custom form error response.
|
||||
@@ -231,10 +224,12 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Get("/search", v1.SearchRepos)
|
||||
})
|
||||
|
||||
m.Group("", func() {
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
|
||||
}, middleware.ApiReqToken())
|
||||
m.Group("/repos", func() {
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
|
||||
m.Combo("/:username/:reponame").Get(v1.GetRepo).
|
||||
Delete(v1.DeleteRepo)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Combo("/hooks").Get(v1.ListRepoHooks).
|
||||
@@ -242,11 +237,11 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), v1.EditRepoHook)
|
||||
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
|
||||
m.Get("/archive/*", v1.GetRepoArchive)
|
||||
}, middleware.ApiRepoAssignment(), middleware.ApiReqToken())
|
||||
})
|
||||
}, middleware.ApiRepoAssignment())
|
||||
}, middleware.ApiReqToken())
|
||||
|
||||
m.Any("/*", func(ctx *middleware.Context) {
|
||||
ctx.HandleAPI(404, "Page not found")
|
||||
ctx.Error(404)
|
||||
})
|
||||
})
|
||||
}, ignSignIn)
|
||||
@@ -256,7 +251,6 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/user", func() {
|
||||
m.Get("/login", user.SignIn)
|
||||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
|
||||
m.Get("/info/:name", user.SocialSignIn)
|
||||
m.Get("/sign_up", user.SignUp)
|
||||
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
|
||||
m.Get("/reset_password", user.ResetPasswd)
|
||||
@@ -267,21 +261,20 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("", user.Settings)
|
||||
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
|
||||
m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar)
|
||||
m.Get("/email", user.SettingsEmails)
|
||||
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||
m.Combo("/email").Get(user.SettingsEmails).
|
||||
Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||
m.Post("/email/delete", user.DeleteEmail)
|
||||
m.Get("/password", user.SettingsPassword)
|
||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
m.Combo("/ssh").Get(user.SettingsSSHKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||
m.Post("/ssh/delete", user.DeleteSSHKey)
|
||||
m.Get("/social", user.SettingsSocial)
|
||||
m.Combo("/applications").Get(user.SettingsApplications).
|
||||
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
|
||||
m.Post("/applications/delete", user.SettingsDeleteApplication)
|
||||
m.Route("/delete", "GET,POST", user.SettingsDelete)
|
||||
}, reqSignIn, func(ctx *middleware.Context) {
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["HasOAuthService"] = setting.OauthService != nil
|
||||
})
|
||||
|
||||
m.Group("/user", func() {
|
||||
@@ -311,7 +304,7 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.Users)
|
||||
m.Get("/new", admin.NewUser)
|
||||
m.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
|
||||
m.Post("/new", bindIgnErr(auth.AdminCrateUserForm{}), admin.NewUserPost)
|
||||
m.Get("/:userid", admin.EditUser)
|
||||
m.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Post("/:userid/delete", admin.DeleteUser)
|
||||
@@ -329,8 +322,8 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("", admin.Authentications)
|
||||
m.Get("/new", admin.NewAuthSource)
|
||||
m.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
m.Get("/:authid", admin.EditAuthSource)
|
||||
m.Post("/:authid", bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Combo("/:authid").Get(admin.EditAuthSource).
|
||||
Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Post("/:authid/delete", admin.DeleteAuthSource)
|
||||
})
|
||||
|
||||
@@ -385,7 +378,7 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/dashboard", user.Dashboard)
|
||||
m.Get("/:type(issues|pulls)", user.Issues)
|
||||
m.Get("/^:type(issues|pulls)$", user.Issues)
|
||||
m.Get("/members", org.Members)
|
||||
m.Get("/members/action/:action", org.MembersAction)
|
||||
|
||||
@@ -406,6 +399,7 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/settings", func() {
|
||||
m.Combo("").Get(org.Settings).
|
||||
Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
|
||||
m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), org.SettingsAvatar)
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", org.Webhooks)
|
||||
@@ -518,8 +512,8 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/releases", middleware.RepoRef(), repo.Releases)
|
||||
m.Get("/:type(issues|pulls)", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/:type(issues|pulls)/:index", repo.ViewIssue)
|
||||
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
m.Get("/branches", repo.Branches)
|
||||
@@ -536,6 +530,9 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("/raw/*", repo.SingleDownload)
|
||||
m.Get("/commits/*", repo.RefCommits)
|
||||
m.Get("/commit/*", repo.Diff)
|
||||
m.Get("/stars", repo.Stars)
|
||||
m.Get("/watchers", repo.Watchers)
|
||||
m.Get("/forks", repo.Forks)
|
||||
}, middleware.RepoRef())
|
||||
|
||||
m.Get("/compare/:before([a-z0-9]{40})...:after([a-z0-9]{40})", repo.CompareDiff)
|
||||
@@ -544,12 +541,12 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/:username", func() {
|
||||
m.Group("/:reponame", func() {
|
||||
m.Get("", repo.Home)
|
||||
m.Get(".git", repo.Home)
|
||||
m.Get("\\.git$", repo.Home)
|
||||
}, ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef())
|
||||
|
||||
m.Group("/:reponame", func() {
|
||||
m.Any("/*", ignSignInAndCsrf, repo.Http)
|
||||
m.Head("/hooks/trigger", repo.TriggerHook)
|
||||
m.Any("/*", ignSignInAndCsrf, repo.HTTP)
|
||||
m.Head("/tasks/trigger", repo.TriggerTask)
|
||||
})
|
||||
})
|
||||
// ***** END: Repository *****
|
||||
|
||||
72
conf/app.ini
72
conf/app.ini
@@ -11,12 +11,30 @@ RUN_MODE = dev
|
||||
[repository]
|
||||
ROOT =
|
||||
SCRIPT_TYPE = bash
|
||||
; Default ANSI charset
|
||||
ANSI_CHARSET =
|
||||
; Force every new repository to be private
|
||||
FORCE_PRIVATE = false
|
||||
; Patch test queue length, make it as large as possible
|
||||
PULL_REQUEST_QUEUE_LENGTH = 10000
|
||||
|
||||
[ui]
|
||||
; Number of repositories that are showed in one explore page
|
||||
EXPLORE_PAGING_NUM = 20
|
||||
; Number of issues that are showed in one page
|
||||
ISSUE_PAGING_NUM = 10
|
||||
; Number of maximum commits showed in one activity feed
|
||||
FEED_MAX_COMMIT_NUM = 5
|
||||
|
||||
[ui.admin]
|
||||
; Number of users that are showed in one page
|
||||
USER_PAGING_NUM = 50
|
||||
; Number of repos that are showed in one page
|
||||
REPO_PAGING_NUM = 50
|
||||
; Number of notices that are showed in one page
|
||||
NOTICE_PAGING_NUM = 50
|
||||
; Number of organization that are showed in one page
|
||||
ORG_PAGING_NUM = 50
|
||||
|
||||
[markdown]
|
||||
; Enable hard line break extension
|
||||
@@ -61,7 +79,7 @@ USER = root
|
||||
PASSWD =
|
||||
; For "postgres" only, either "disable", "require" or "verify-full"
|
||||
SSL_MODE = disable
|
||||
; For "sqlite3" only
|
||||
; For "sqlite3" and "tidb"
|
||||
PATH = data/gogs.db
|
||||
|
||||
[admin]
|
||||
@@ -95,6 +113,18 @@ ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
||||
; Do not check minimum key size with corresponding type
|
||||
DISABLE_MINIMUM_KEY_SIZE_CHECK = false
|
||||
; Enable captcha validation for registration
|
||||
ENABLE_CAPTCHA = true
|
||||
|
||||
; used to filter keys which are too short
|
||||
[service.minimum_key_sizes]
|
||||
ED25519 = 256
|
||||
ECDSA = 256
|
||||
NTRU = 1087
|
||||
MCE = 1702
|
||||
McE = 1702
|
||||
RSA = 1024
|
||||
DSA = 1024
|
||||
|
||||
[webhook]
|
||||
; Hook task queue length
|
||||
@@ -109,7 +139,7 @@ PAGING_NUM = 10
|
||||
[mailer]
|
||||
ENABLED = false
|
||||
; Buffer length of channel, keep it as it is if you don't know what it is.
|
||||
SEND_BUFFER_LEN = 10
|
||||
SEND_BUFFER_LEN = 100
|
||||
; Name displayed in mail title
|
||||
SUBJECT = %(APP_NAME)s
|
||||
; Mail server
|
||||
@@ -133,44 +163,6 @@ FROM =
|
||||
USER =
|
||||
PASSWD =
|
||||
|
||||
[oauth]
|
||||
ENABLED = false
|
||||
|
||||
[oauth.github]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = https://api.github.com/user
|
||||
AUTH_URL = https://github.com/login/oauth/authorize
|
||||
TOKEN_URL = https://github.com/login/oauth/access_token
|
||||
|
||||
; Get client id and secret from
|
||||
; https://console.developers.google.com/project
|
||||
[oauth.google]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
|
||||
AUTH_URL = https://accounts.google.com/o/oauth2/auth
|
||||
TOKEN_URL = https://accounts.google.com/o/oauth2/token
|
||||
|
||||
[oauth.qq]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = get_user_info
|
||||
; QQ 互联
|
||||
AUTH_URL = https://graph.qq.com/oauth2.0/authorize
|
||||
TOKEN_URL = https://graph.qq.com/oauth2.0/token
|
||||
|
||||
[oauth.weibo]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = all
|
||||
AUTH_URL = https://api.weibo.com/oauth2/authorize
|
||||
TOKEN_URL = https://api.weibo.com/oauth2/access_token
|
||||
|
||||
[cache]
|
||||
; Either "memory", "redis", or "memcache", default is "memory"
|
||||
ADAPTER = memory
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Leiningen.gitignore
|
||||
@@ -1 +0,0 @@
|
||||
C++.gitignore
|
||||
@@ -1,20 +1,29 @@
|
||||
# This file lists all PUBLIC individuals having contributed content to the translation.
|
||||
# Order of name is meaningless.
|
||||
# Entries are in alphabetical order.
|
||||
|
||||
Akihiro YAGASAKI <yaggytter@momiage.com>
|
||||
Alexander Steinhöfer <kontakt@lx-s.de>
|
||||
Alexandre Magno <alexandre.mbm@gmail.com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer@gmail.com>
|
||||
Christoph Kisfeld <christoph.kisfeld@gmail.com>
|
||||
Daniel Speichert <daniel@speichert.pl>
|
||||
Gregor Santner <gdev@live.de>
|
||||
Huimin Wang <wanghm2009@hotmail.co.jp>
|
||||
ilko <email>
|
||||
Thomas Fanninger <gogs.thomas@fanninger.at>
|
||||
Łukasz Jan Niemier <lukasz@niemier.pl>
|
||||
Lafriks <lafriks@gmail.com>
|
||||
Luc Stepniewski <luc@stepniewski.fr>
|
||||
Miguel de la Cruz <miguel@mcrx.me>
|
||||
Marc Schiller <marc@schiller.im>
|
||||
Morten Sørensen <klim8d@gmail.com>
|
||||
Natan Albuquerque <natanalbuquerque5@gmail.com>
|
||||
Akihiro YAGASAKI <yaggytter AT momiage DOT com>
|
||||
Alexander Steinhöfer <kontakt AT lx-s DOT de>
|
||||
Alexandre Magno <alexandre DOT mbm AT gmail DOT com>
|
||||
Andrey Nering <andrey AT nering DOT com DOT br>
|
||||
Arthur Aslanyan <arthur DOT e DOT aslanyan AT gmail DOT com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer AT gmail DOT com>
|
||||
Christoph Kisfeld <christoph DOT kisfeld AT gmail DOT com>
|
||||
Daniel Speichert <daniel AT speichert DOT pl>
|
||||
Dmitriy Nogay <me AT catwhocode DOT ga>
|
||||
Gregor Santner <gdev AT live DOT de>
|
||||
Hamid Feizabadi <hamidfzm AT gmail DOT com>
|
||||
Huimin Wang <wanghm2009 AT hotmail DOT co DOT jp>
|
||||
ilko
|
||||
Lafriks <lafriks AT gmail DOT com>
|
||||
Lauri Ojansivu <x AT xet7 DOT org>
|
||||
Luc Stepniewski <luc AT stepniewski DOT fr>
|
||||
Marc Schiller <marc AT schiller DOT im>
|
||||
Miguel de la Cruz <miguel AT mcrx DOT me>
|
||||
Morten Sørensen <klim8d AT gmail DOT com>
|
||||
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
|
||||
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
|
||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||
Tilmann Bach <tilmann AT outlook DOT com>
|
||||
Vladimir Vissoultchev <wqweto AT gmail DOT com>
|
||||
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
|
||||
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
|
||||
@@ -5,7 +5,6 @@ dashboard=Табло
|
||||
explore=Разгледай
|
||||
help=Помощ
|
||||
sign_in=Влизане
|
||||
social_sign_in=Вход чрез социална мрежа: 2. стъпка <small>асоцииране на профил</small>
|
||||
sign_out=Излизане
|
||||
sign_up=Регистрирайте се
|
||||
register=Регистриране
|
||||
@@ -21,7 +20,7 @@ signed_in_as=Вписан като
|
||||
username=Потребител
|
||||
email=Ел. поща
|
||||
password=Парола
|
||||
re_type=Въведете отново
|
||||
re_type=Въведете повторно
|
||||
captcha=Captcha
|
||||
|
||||
repository=Хранилище
|
||||
@@ -54,7 +53,8 @@ code=Код
|
||||
[install]
|
||||
install=Инсталация
|
||||
title=Стъпки за инсталиране при първоначално стартиране
|
||||
requite_db_desc=Gogs изисква MySQL, PostgreSQL или SQLite3.
|
||||
docker_helper=Ако Gogs е стартиран в Docker контейнер, моля прочетете <a target="_blank" href="%s">нашите указания</a> внимателно, преди да правите промени по настройките на тази страница!
|
||||
requite_db_desc=Gogs изисква MySQL, PostgreSQL, SQLite3 или TiDB.
|
||||
db_title=Настройки на базата данни
|
||||
db_type=Тип на база данни
|
||||
host=Сървър
|
||||
@@ -64,8 +64,11 @@ db_name=Име на база данни
|
||||
db_helper=Моля, използвайте INNODB engine с utf8_general_ci кодиране на знаци за MySQL.
|
||||
ssl_mode=Режим SSL
|
||||
path=Път
|
||||
sqlite_helper=Пък към файла на SQLite3 база данни.
|
||||
err_empty_sqlite_path=Пътят за SQLite3 база за данни не може да е празен.
|
||||
sqlite_helper=Файл на SQLite3 или TiDB база данни.
|
||||
err_empty_db_path=Пътят до SQLite3 или TiDB база данни не може да е празен.
|
||||
err_invalid_tidb_name=TiDB не позволява "." и "-" в името на базата данни.
|
||||
no_admin_and_disable_registration=Невъзможно изключване на регистрациите без предварително да е създаден поне един административен профил.
|
||||
err_empty_admin_password=Паролата на администратор не може да е празна.
|
||||
|
||||
general_title=Общи настройки на приложението
|
||||
app_name=Име на приложението
|
||||
@@ -73,11 +76,11 @@ app_name_helper=Постави името на твоята организаци
|
||||
repo_path=Основен път към хранилищата
|
||||
repo_path_helper=Всички отдалечени хранилища на Git ще бъдат съхранени в тази директория.
|
||||
run_user=Потребителски контекст
|
||||
run_user_helper=Този потребител трябва да има достъп до основния път до хранилищата и права да стартира Gogs.
|
||||
run_user_helper=Този потребител трябва да има достъп до основния път към хранилищата и права да стартира Gogs.
|
||||
domain=Домейн
|
||||
domain_helper=Тази настройка влияе на URL адреса за клониране със SSH.
|
||||
domain_helper=Тази настройка влияе на URL адреса за клониране чрез SSH.
|
||||
ssh_port=SSH порт
|
||||
ssh_port_helper=Номер на порт на SSH сървъра. Оставете празно за да забраните SSH.
|
||||
ssh_port_helper=Номер на порт на SSH сървъра. Оставете празно за да изключите достъп през SSH.
|
||||
http_port=HTTP порт
|
||||
http_port_helper=Порт, на който приложението ще слуша.
|
||||
app_url=URL адрес на приложението
|
||||
@@ -87,7 +90,7 @@ optional_title=Опционални настройки
|
||||
email_title=Настройки на пощенска услуга
|
||||
smtp_host=SMTP сървър
|
||||
smtp_from=Подател
|
||||
smtp_from_helper=Адрес на подател на поща по RFC 5322. Може да бъде обикновен адрес на ел. поща или на във формат "Име" <email@example.com>.
|
||||
smtp_from_helper=Адрес на подател на поща по RFC 5322. Може да бъде обикновен адрес на ел. поща или във формат "Име" <email@example.com>.
|
||||
mailer_user=Ел. поща за изпращане
|
||||
mailer_password=Парола за изпращане
|
||||
register_confirm=Включи потвърждението на регистрациите
|
||||
@@ -99,23 +102,25 @@ disable_gravatar=Изключи връзка с Gravatar
|
||||
disable_gravatar_popup=Изключва Gravatar и външни източници, така че всички аватари трябва да са или качени от потребителите или да се ползват аватари по подразбиране.
|
||||
disable_registration=Изключи саморегистрацията
|
||||
disable_registration_popup=Изключи потребителската саморегистрация, само администратор може да създава профили.
|
||||
enable_captcha=Включи Captcha
|
||||
enable_captcha_popup=Изисква валидиране с captcha при саморегистрация на потребители.
|
||||
require_sign_in_view=Включи задължително вписване за преглед на страници
|
||||
require_sign_in_view_popup=Само вписани потребители могат да виждат страниците, посетителите виждат само страниците за регистрация и вход.
|
||||
admin_setting_desc=Няма нужда да създавате администраторски профил в момента, защото потребителят с първо ID в базата автоматично получава администраторски достъп.
|
||||
require_sign_in_view_popup=Само вписани потребители могат да виждат страниците, анонимните посетители виждат само страниците за регистрация и вход.
|
||||
admin_setting_desc=Няма нужда от създаване на администраторски профил в момента, защото потребителят с първо ID в базата автоматично получава администраторски достъп.
|
||||
admin_title=Настройки на профил на администратора
|
||||
admin_name=Потребителско име
|
||||
admin_password=Парола
|
||||
confirm_password=Потвърждение на паролата
|
||||
admin_email=Ел. поща
|
||||
install_gogs=Инсталирай Gogs
|
||||
test_git_failed=Неуспех при тестването на "git" команда: %v
|
||||
test_git_failed=Неуспешно тестването на "git" команда: %v
|
||||
sqlite3_not_available=Вашата версия не поддържа SQLite3, моля, изтеглете официалната двоична версия от %s, а не gobuild версията.
|
||||
invalid_db_setting=Настройките на базата данни са некоректни: %v
|
||||
invalid_repo_path=Основният път към хранилищата е невалиден: %v
|
||||
run_user_not_match=Потребителският контекст на приложението не е на текущия потребител: %s -> %s
|
||||
save_config_failed=Неуспех при запазване на конфигурация: %v
|
||||
save_config_failed=Неуспешно запазване на конфигурация: %v
|
||||
invalid_admin_setting=Настройките на профил на администратора са невалидни: %v
|
||||
install_success=Добре дошли! Радваме се, че избрахте Gogs и Ви пожелаваме приятна работа и сърдечни поздрави!
|
||||
install_success=Добре дошли! Радваме се, че избрахте Gogs, и Ви пожелаваме приятна работа и сърдечни поздрави!
|
||||
|
||||
[home]
|
||||
uname_holder=Потребителско име или ел. поща
|
||||
@@ -127,7 +132,7 @@ my_orgs=Моите организации
|
||||
my_mirrors=Моите огледала
|
||||
view_home=Преглед на %s
|
||||
|
||||
issues.in_your_repos=Във вашите хранилища
|
||||
issues.in_your_repos=Във Вашите хранилища
|
||||
|
||||
[explore]
|
||||
repos=Хранилища
|
||||
@@ -136,24 +141,29 @@ repos=Хранилища
|
||||
create_new_account=Създай нов профил
|
||||
register_hepler_msg=Вече имате профил? Впишете се сега!
|
||||
social_register_hepler_msg=Вече имате профил? Свържете се сега!
|
||||
disable_register_prompt=За съжаление новите регистрации са изключени. Обърнете се към администратора на сайта.
|
||||
disable_register_mail=За съжаление потвърждението на регистрациите е изключено.
|
||||
disable_register_prompt=За съжаление създаването на нови регистрации е изключено. Обърнете се към администратора на сайта.
|
||||
disable_register_mail=За съжаление потвърждението на регистрации е изключено.
|
||||
remember_me=Запомни ме
|
||||
forgot_password=Забравена парола
|
||||
forget_password=Забравена парола?
|
||||
sign_up_now=Нуждаете се от профил? Регистрирайте се сега.
|
||||
confirmation_mail_sent_prompt=Ново писмо за потвърждение е изпратено до <b>%s</b>. Моля проверете пощенската си кутия в рамките на следващите %d часа, за да завършите процеса на регистрация.
|
||||
sign_in_email=Влизане чрез ел. поща
|
||||
active_your_account=Активиране на профил
|
||||
resent_limit_prompt=За съжаление Вие съвсем наскоро изпратихте писмо за активация. Моля изчакайте 3 минути, след което опитайте отново.
|
||||
has_unconfirmed_mail=Здравейте %s, имате непотвърден адрес на ел. поща (<b>%s</b>). Ако не сте получили писмо за потвърждение или имате нужда да се изпрати ново, моля щракнете бутона по-долу.
|
||||
resend_mail=Щракнете тук, за да се изпрати ново писмо за активиране
|
||||
has_unconfirmed_mail=Здравейте %s, имате непотвърден адрес на ел. поща (<b>%s</b>). Ако не сте получили писмо за потвърждение или имате нужда да се изпрати ново писмо, моля щракнете бутона по-долу.
|
||||
resend_mail=Щракнете тук, за да се изпрати ново писмо за потвърждение
|
||||
email_not_associate=Този адрес на ел. поща не е свързан с никой профил.
|
||||
send_reset_mail=Щракнете тук, за да получите (наново) писмо за нулиране на паролата
|
||||
send_reset_mail=Щракнете тук, за да получите (отново) писмо за нулиране на паролата
|
||||
reset_password=Нулиране на паролата
|
||||
invalid_code=За съжаление Вашия код за потвърждение е изтекъл или е невалиден.
|
||||
reset_password_helper=Щракнете тук, за да нулирате паролата си
|
||||
password_too_short=Дължина на паролата не може да бъде по-малко от 6 знака.
|
||||
password_too_short=Размерът на паролата не може да бъде по-малък от 6 знака.
|
||||
|
||||
[mail]
|
||||
activate_account=Моля активирайте Вашия профил
|
||||
activate_email=Провери адрес на ел. поща
|
||||
reset_password=Нулиране на паролата
|
||||
register_success=Успешна регистрация и добре дошли
|
||||
|
||||
[modal]
|
||||
yes=Да
|
||||
@@ -162,15 +172,15 @@ modify=Промени
|
||||
|
||||
[form]
|
||||
UserName=Потребителско име
|
||||
RepoName=Име на хранилище
|
||||
RepoName=Име на хранилището
|
||||
Email=Адрес на ел. поща
|
||||
Password=Парола
|
||||
Retype=Повторно паролата
|
||||
SSHTitle=Име на SSH ключ
|
||||
HttpsUrl=HTTPS URL
|
||||
HttpsUrl=HTTPS URL адрес
|
||||
PayloadUrl=URL адрес на изпращане
|
||||
TeamName=Име на екипа
|
||||
AuthName=Име за удостоверение
|
||||
AuthName=Име на удостоверението
|
||||
AdminEmail=Ел. поща на администратора
|
||||
|
||||
require_error=` не може да бъде празен.`
|
||||
@@ -181,8 +191,9 @@ min_size_error=` трябва да съдържа поне %s знака.`
|
||||
max_size_error=` трябва да съдържа най-много %s знака.`
|
||||
email_error=` не е валиден адрес на ел. поща.`
|
||||
url_error=` не е валиден URL адрес.`
|
||||
unknown_error=Непозната грешка:
|
||||
captcha_incorrect=Captcha не съвпада.
|
||||
include_error=` трябва да съдържа текст '%s'.`
|
||||
unknown_error=Неизвестна грешка:
|
||||
captcha_incorrect=Captcha не е потвърдена.
|
||||
password_not_match=Паролата и потвърждението ѝ не съвпадат.
|
||||
|
||||
username_been_taken=Потребителското име вече се ползва.
|
||||
@@ -190,20 +201,20 @@ repo_name_been_taken=Името на хранилището вече се пол
|
||||
org_name_been_taken=Името на организацията вече се ползва.
|
||||
team_name_been_taken=Името на екипа вече се ползва.
|
||||
email_been_used=Този адрес на ел. поща вече се ползва.
|
||||
illegal_team_name=Името на екип съдържа недопустими знаци.
|
||||
username_password_incorrect=Потребителското име или паролата не е вярна.
|
||||
enterred_invalid_repo_name=Моля уверете се, че името на хранилището е въведено правилно.
|
||||
illegal_team_name=Името на екипа съдържа недопустими знаци.
|
||||
username_password_incorrect=Потребителското име или паролата не са верни.
|
||||
enterred_invalid_repo_name=Моля уверете се, че въведеното име на хранилище е вярно.
|
||||
enterred_invalid_owner_name=Моля уверете се, че въведеното име на притежател е вярно.
|
||||
enterred_invalid_password=Моля уверете се, че въведената парола е вярна.
|
||||
user_not_exist=Даденият потребител не съществува.
|
||||
last_org_owner=Премахване на последния потребител от екип притежатели не е позволено, тъй като винаги трябва да има поне един притежател в дадена организация.
|
||||
|
||||
invalid_ssh_key=За съжаление, ние не сме в състояние да удостоверим вашия SSH ключ: %s
|
||||
unable_verify_ssh_key=Gogs не може да провери вашия SSH ключ, но предполагаме, че е валиден. Моля, проверете го.
|
||||
invalid_ssh_key=За съжаление, ние не сме в състояние да проверим Вашия SSH ключ: %s
|
||||
unable_verify_ssh_key=Gogs не може да провери Вашия SSH ключ, но предполагаме, че е валиден. Моля, проверете го.
|
||||
auth_failed=Неуспешно удостоверяване: %v
|
||||
|
||||
still_own_repo=Вашият профил притежава поне едно хранилище. Първо трябва да ги изтриете или да ги прехвърлите на друг потребител.
|
||||
still_has_org=Вашият профил все още е член на поне една организация. Първо трябва да напуснете или изтриете вашите членства.
|
||||
still_has_org=Вашият профил все още е член на поне една организация. Първо трябва да напуснете или изтриете Вашите членства.
|
||||
org_still_own_repo=Тази организация все още притежава хранилище. Първо трябва да го изтриете или да го прехвърлите на друга организация.
|
||||
|
||||
still_own_user=Това удостоверяване се използва от поне един потребител. Моля премахнете потребителите към него и опитайте отново.
|
||||
@@ -211,8 +222,8 @@ still_own_user=Това удостоверяване се използва от
|
||||
target_branch_not_exist=Целевият клон не съществува.
|
||||
|
||||
[user]
|
||||
change_avatar=Сменете вашия аватар на gravatar.com
|
||||
change_custom_avatar=Промяна на вашия аватар в настройките
|
||||
change_avatar=Сменете Вашия аватар на gravatar.com
|
||||
change_custom_avatar=Сменете Вашия аватар в настройките
|
||||
join_on=Регистриран на
|
||||
repositories=Хранилища
|
||||
activity=Публична дейност
|
||||
@@ -234,20 +245,20 @@ delete=Изтрий профил
|
||||
uid=UID
|
||||
|
||||
public_profile=Публичен профил
|
||||
profile_desc=Вашият адрес на ел. поща е публичен и ще бъде използван за всички свързани с профила ви уведомления и всички уеб базирани операции, направени чрез сайта.
|
||||
profile_desc=Вашият адрес на ел. поща е публичен и ще бъде използван за всички свързани с профила Ви уведомления и всички уеб базирани операции, направени чрез сайта.
|
||||
full_name=Пълно име
|
||||
website=Уебсайт
|
||||
location=Местоположение
|
||||
update_profile=Актуализиране на профила
|
||||
update_profile_success=Вашият профил е актуализиран успешно.
|
||||
location=Локация
|
||||
update_profile=Обнови профила
|
||||
update_profile_success=Вашият профил е запазен успешно.
|
||||
change_username=Потребителското име е променено
|
||||
change_username_desc=Променяте потребителско Ви име. Това ще засегне всички връзки сочещи към профила Ви. Желаете ли да продължите?
|
||||
change_username_prompt=Този промяна ще засегне всички връзки сочещи към профила Ви.
|
||||
continue=Продължи
|
||||
cancel=Отказ
|
||||
|
||||
enable_custom_avatar=Разреши потребителски аватар
|
||||
enable_custom_avatar_helper=Включете тази опция, за да забраните зареждане от Gravatar
|
||||
choose_new_avatar=Изберете нов аватар
|
||||
choose_new_avatar=Избери нов аватар
|
||||
update_avatar=Обнови настройките на аватара
|
||||
uploaded_avatar_not_a_image=Каченият файл не е изображение.
|
||||
no_custom_avatar_available=Невъзможно използване на външен аватар, защото не е активирано.
|
||||
@@ -256,23 +267,27 @@ update_avatar_success=Настройките на аватара са запаз
|
||||
change_password=Промени парола
|
||||
old_password=Текуща парола
|
||||
new_password=Нова парола
|
||||
retype_new_password=Повторно новата парола
|
||||
password_incorrect=Въведената парола не е вярна.
|
||||
change_password_success=Вашата парола е променена успешно. Вече може да влизате, използвайки тази нова парола.
|
||||
|
||||
emails=Адреси на ел. поща
|
||||
manage_emails=Управление на адреси на ел. поща
|
||||
email_desc=Вашият основен адрес на ел. поща ще се използва за известия и други операции.
|
||||
email_desc=Вашият основен адрес на ел. поща ще се използва за изпращане на уведомления и други операции.
|
||||
primary=Основен
|
||||
primary_email=Задаване като основен
|
||||
primary_email=Задай като основен
|
||||
delete_email=Изтрий
|
||||
email_deletion=Изтрий ел. поща
|
||||
email_deletion_desc=При изтриване на тази ел. поща ще се премахне свързаната информация от Вашия профил. Желаете ли да продължите?
|
||||
email_deletion_success=Ел. пощата беше изтрита успешно!
|
||||
add_new_email=Добавяне на нов адрес на ел. поща
|
||||
add_email=Добави ел. поща
|
||||
add_email_confirmation_sent=Ново писмо за потвърждение е изпратено до <b>%s</b>. Моля проверете пощенската си кутия в рамките на следващите %d часа, за да завършите процеса на регистрация.
|
||||
add_email_success=Нов адрес на ел. поща беше добавен успешно.
|
||||
add_email_confirmation_sent=Ново писмо за потвърждение е изпратено до '%s'. Моля проверете пощенската си кутия в рамките на следващите %d часа, за да завършите процеса на регистрация.
|
||||
add_email_success=Ваш нов адрес на ел. поща е добавен успешно.
|
||||
|
||||
manage_ssh_keys=Управление на SSH ключове
|
||||
add_key=Добави ключ
|
||||
ssh_desc=Това е списък на SSH ключове, свързани с вашия акаунт. Тъй като тези ключове позволяват на всеки, който ги използва да получи достъп до хранилищата ви, много е важно да се уверите, че ги разпознавате.
|
||||
ssh_desc=Това е списък на SSH ключове, свързани с Вашия акаунт. Тъй като тези ключове позволяват на всеки, който ги използва да получи достъп до хранилищата Ви, много е важно да се уверите, че ги разпознавате.
|
||||
ssh_helper=<strong>Не знам как?</strong> Проверете на GitHub упътването как да <a href="%s">създадете свои собствени SSH ключове</a> или решаване на <a href="%s">Общи проблеми</a>, които може да възникнат при използване на SSH.
|
||||
add_new_key=Добавяне на SSH ключ
|
||||
ssh_key_been_used=Съдържанието на публичния ключ е използвано.
|
||||
@@ -281,8 +296,8 @@ key_name=Име на ключа
|
||||
key_content=Съдържание
|
||||
add_key_success=Новият SSH ключ '%s' е добавен успешно!
|
||||
delete_key=Изтрий
|
||||
ssh_key_deletion=Изтриване на SSH ключ
|
||||
ssh_key_deletion_desc=Изтривайки този SSH ключ премахвате всякакъв достъп за Вашия профил. Желаете ли да продължите?
|
||||
ssh_key_deletion=Изтрий SSH ключ
|
||||
ssh_key_deletion_desc=При изтриване на този SSH ключ ще се премахнат свързаните права за достъп за Вашия профил. Желаете ли да продължите?
|
||||
ssh_key_deletion_success=SSH ключа беше изтрит успешно!
|
||||
add_on=Добавен на
|
||||
last_used=Последно използван на
|
||||
@@ -298,31 +313,32 @@ unbind_success=Социалния профил е освободен.
|
||||
manage_access_token=Управление на индивидуални токени за достъп
|
||||
generate_new_token=Генериране на нов токен
|
||||
tokens_desc=Генерирани токени, които могат да се използват за достъп до API-то на Gogs.
|
||||
new_token_desc=Всеки токен ще има пълен достъп до вашия профил.
|
||||
token_name=Име на токен
|
||||
new_token_desc=Всеки токен ще има пълен достъп до Вашия профил.
|
||||
token_name=Име на токена
|
||||
generate_token=Генериране на токен
|
||||
generate_token_succees=Успешно е генериран токен за достъп. Уверете се, че сте го копирали, тъй като няма да можете да го видите отново!
|
||||
delete_token=Изтрий
|
||||
access_token_deletion=Изтриване на индивидуален токен за достъп
|
||||
access_token_deletion_desc=Изтриването на този индивидуален токен за достъп ще премахне всички свързани права на приложението. Желаете ли да продължите?
|
||||
access_token_deletion=Изтрий индивидуален токен за достъп
|
||||
access_token_deletion_desc=При изтриване на този индивидуален токен за достъп ще се премахнат всички свързани права на приложението. Желаете ли да продължите?
|
||||
delete_token_success=Индивидуалния токен за достъп е изтрит успешно! Не забравяйте да преконфигурирате приложението също.
|
||||
|
||||
delete_account=Изтриване на вашия профил
|
||||
delete_prompt=Тази операция ще изтрие Вашия профил завинаги и <strong>НЕ МОЖЕ</strong> да се отмени!
|
||||
confirm_delete_account=Потвърждаване на изтриването
|
||||
delete_account=Изтрий собствен профил
|
||||
delete_prompt=Тази операция ще изтрие Вашия профил завинаги и тя <strong>НЕ МОЖЕ</strong> да бъде отменена в последствие!
|
||||
confirm_delete_account=Потвърди изтриването
|
||||
delete_account_title=Изтрий профил
|
||||
delete_account_desc=Този профил ще бъде окончателно изтрит. Желаете ли да продължите?
|
||||
|
||||
[repo]
|
||||
owner=Притежател
|
||||
repo_name=Име на хранилище
|
||||
repo_name=Име на хранилището
|
||||
repo_name_helper=Добро име на хранилище е име, състоящо от кратки, запомнящи се и уникални ключови думи.
|
||||
visibility=Видимост
|
||||
visiblity_helper=Това хранилище е <span class="ui red text">Частно</span>
|
||||
visiblity_helper_forced=Административна настройка задължава всички нови хранилища да бъдат <span class="ui red text">Частни</span>
|
||||
visiblity_fork_helper=(Промяна на тази стойност ще се отрази на всички разклонения)
|
||||
fork_repo=Разклони хранилището
|
||||
fork_from=Разклонение от
|
||||
fork_visiblity_helper=Не можете да промените видимостта на разклонено хранилище.
|
||||
fork_visiblity_helper=Не може да променяте видимостта на разклонено хранилище.
|
||||
repo_desc=Описание
|
||||
repo_lang=Език
|
||||
repo_lang_helper=Изберете .gitignore файлове
|
||||
@@ -335,8 +351,8 @@ create_repo=Създай хранилище
|
||||
default_branch=Клон по подразбиране
|
||||
mirror_interval=Интервал на отразяване (часове)
|
||||
|
||||
form.name_reserved=Името на хранилище '%s' е запазено.
|
||||
form.name_pattern_not_allowed=Име на хранилище от вида '%s' не е позволено.
|
||||
form.name_reserved=Името на хранилището '%s' е запазено.
|
||||
form.name_pattern_not_allowed=Име на хранилището от вида '%s' не е позволено.
|
||||
|
||||
need_auth=Изисква удостоверяване
|
||||
migrate_type=Тип мигриране
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=Това хранилище ще бъде <span class="text
|
||||
migrate_repo=Мигрирай хранилище
|
||||
migrate.clone_address=Адрес за клонирай
|
||||
migrate.clone_address_desc=Това може да е HTTP/HTTPS/GIT адрес или локален път на сървъра.
|
||||
migrate.permission_denied=Недостатъчни права за импорт на локални хранилища.
|
||||
migrate.invalid_local_path=Невалиден път - не съществува или не е директория.
|
||||
|
||||
forked_from=разклонено от
|
||||
fork_from_self=Не можете да разклоните хранилище което си е Ваше!
|
||||
copy_link=Копирай
|
||||
copy_link_success=Копирано!
|
||||
copy_link_error=Натиснете ⌘-C или Ctrl-C за да копирате
|
||||
click_to_copy=Копиране в клипборда
|
||||
copied=Успешно копиране
|
||||
clone_helper=Нуждаете се от помощ при клониране? Посетете <a target="_blank" href="%s">Помощ</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Бърз справочник
|
||||
clone_this_repo=Клонирай хранилището
|
||||
create_new_repo_command=Създай ново хранилище чрез командния ред
|
||||
push_exist_repo=Предай съществуващо хранилище през командния ред
|
||||
repo_is_empty=Това хранилище е празно. Моля проверете по-късно пак!
|
||||
|
||||
|
||||
branch=Клон
|
||||
tree=Дърво
|
||||
@@ -377,7 +398,7 @@ commits=Ревизии
|
||||
releases=Издания
|
||||
file_raw=Суров
|
||||
file_history=История
|
||||
file_view_raw=Суров вид
|
||||
file_view_raw=Виж суров
|
||||
file_permalink=Постоянна връзка
|
||||
|
||||
commits.commits=Ревизии
|
||||
@@ -403,14 +424,14 @@ issues.new.clear_assignee=Изчисти изпълнител
|
||||
issues.new.no_assignee=Няма изпълнител
|
||||
issues.create=Докладвай проблем
|
||||
issues.new_label=Нов етикет
|
||||
issues.new_label_placeholder=Име на етикет...
|
||||
issues.new_label_placeholder=Име на етикета...
|
||||
issues.create_label=Създай етикет
|
||||
issues.open_tab=%d отворени
|
||||
issues.close_tab=%d затворени
|
||||
issues.filter_label=Етикет
|
||||
issues.filter_label_no_select=Не е избран етикет
|
||||
issues.filter_milestone=Етап
|
||||
issues.filter_milestone_no_select=Липсван избран етап
|
||||
issues.filter_milestone_no_select=Липсва избран етап
|
||||
issues.filter_assignee=Изпълнител
|
||||
issues.filter_assginee_no_select=Няма избран изпълнител
|
||||
issues.filter_type=Тип
|
||||
@@ -435,12 +456,12 @@ issues.num_comments=%d коментара
|
||||
issues.commented_at=`коментира <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=Все още няма съдържание.
|
||||
issues.close_issue=Затвори
|
||||
issues.close_comment_issue=Затвори и коментирай
|
||||
issues.reopen_issue=Отвори отново
|
||||
issues.reopen_comment_issue=Отвори отново и коментирай
|
||||
issues.close_comment_issue=Kоментирай и затвори
|
||||
issues.reopen_issue=Отвори повторно
|
||||
issues.reopen_comment_issue=Kоментирай и oтвори отново
|
||||
issues.create_comment=Коментирай
|
||||
issues.closed_at=`затвори <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`отново отвори <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`повторно отвори <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.poster=Участник
|
||||
issues.admin=Администратор
|
||||
@@ -450,15 +471,15 @@ issues.sign_in_require_desc=за да се включите в този разг
|
||||
issues.edit=Редакция
|
||||
issues.cancel=Отказ
|
||||
issues.save=Запис
|
||||
issues.label_title=Име на етикет
|
||||
issues.label_title=Име на етикета
|
||||
issues.label_color=Цвят на етикет
|
||||
issues.label_count=%d етикети
|
||||
issues.label_open_issues=%d отворени проблема
|
||||
issues.label_edit=Редакция
|
||||
issues.label_delete=Изтрий
|
||||
issues.label_modify=Промяна на етикет
|
||||
issues.label_deletion=Премахване на етикет
|
||||
issues.label_deletion_desc=Изтриването на този етикет ще премахне информацията за него във всички свързани проблеми. Желаете ли да продължите?
|
||||
issues.label_deletion=Изтрий етикет
|
||||
issues.label_deletion_desc=При изтриване на този етикет ще се премахне информацията за него във всички свързани проблеми. Желаете ли да продължите?
|
||||
issues.label_deletion_success=Етикетът е изтрит успешно!
|
||||
|
||||
pulls.compare_changes=Сравни промените
|
||||
@@ -475,14 +496,16 @@ pulls.merged_title_desc=обедини %[1]d ревизии от <code>%[2]s</co
|
||||
pulls.tab_conversation=Разговор
|
||||
pulls.tab_commits=Ревизии
|
||||
pulls.tab_files=Променени файлове
|
||||
pulls.reopen_to_merge=Моля отново заявете тази заявка за сливане за да се извърши обединяване.
|
||||
pulls.reopen_to_merge=Моля повторно отворете тази заявка за сливане за да се извърши обединяване.
|
||||
pulls.merged=Обединени
|
||||
pulls.has_merged=Тази заявка за сливане е обединена успешно!
|
||||
pulls.data_broken=Данните от тази заявка за сливане са невалидни поради изтрита информация за някое разклонение.
|
||||
pulls.is_checking=Проверката за конфликт все още е в ход. Моля обновете страницата след малко.
|
||||
pulls.can_auto_merge_desc=Можете да извършвате авто-обединяване на тази заявка за сливане.
|
||||
pulls.cannot_auto_merge_desc=Не можете да извършите авто-обединяване, защото съществуват конфликти между ревизиите.
|
||||
pulls.cannot_auto_merge_helper=Моля, използвайте инструменти на командния ред за да отстраните проблема.
|
||||
pulls.merge_pull_request=Обедини заявка за сливане
|
||||
pulls.open_unmerged_pull_exists=`Невъзможно повторно отваряне, защото вече съществува заявка за сливане (#%d) от същото хранилище със същата информация за обединяване, която чака да бъде извършена`
|
||||
|
||||
milestones.new=Нов етап
|
||||
milestones.open_tab=%d отворени
|
||||
@@ -504,7 +527,7 @@ milestones.edit_subheader=Въведете точни описания на ет
|
||||
milestones.cancel=Отказ
|
||||
milestones.modify=Промяна на етап
|
||||
milestones.edit_success=Промените в етап '%s' са запазени успешно!
|
||||
milestones.deletion=Премахване на етап
|
||||
milestones.deletion=Изтрий етап
|
||||
milestones.deletion_desc=При изтриване на етап ще се премахне информацията за него от всички свързани проблеми. Желаете ли да продължите?
|
||||
milestones.deletion_success=Етапът е изтрит успешно!
|
||||
|
||||
@@ -516,10 +539,10 @@ settings.githooks=Git куки
|
||||
settings.basic_settings=Основни настройки
|
||||
settings.danger_zone=Опасната зона
|
||||
settings.site=Официален сайт
|
||||
settings.update_settings=Промени настройките
|
||||
settings.update_settings=Обнови настройките
|
||||
settings.change_reponame_prompt=Тази промяна ще засегне връзките, които се отнасят до това хранилището.
|
||||
settings.transfer=Прехвърляне на притежание
|
||||
settings.transfer_desc=Прехвърля това хранилище на друг потребител или на организация, в която имате права на администратор.
|
||||
settings.transfer=Прехвърли притежание
|
||||
settings.transfer_desc=Прехвърля това хранилище на друг потребител или към организация, в която имате права на администратор.
|
||||
settings.new_owner_has_same_repo=Новият притежател вече има хранилище със същото име. Изберете друго име.
|
||||
settings.delete=Изтриване на това хранилище
|
||||
settings.delete_desc=След като изтриете хранилището, няма връщане назад. Моля, бъдете сигурни.
|
||||
@@ -531,11 +554,11 @@ settings.delete_notices_2=- Тази операция ще изтрие всич
|
||||
settings.delete_notices_fork_1=- Ако това хранилище е публично, всички негови разклонения ще останат независими след изтриването му.
|
||||
settings.delete_notices_fork_2=- Ако това хранилище е частно, всички негови разклонения ще бъдат премахнати по време на изтриването.
|
||||
settings.delete_notices_fork_3=- Ако желаете да запазите всички разклонения след изтриването му, първо направете хранилището публично.
|
||||
settings.update_settings_success=Настройките на хранилището са актуализирани успешно.
|
||||
settings.update_settings_success=Настройките на хранилището са запазени успешно.
|
||||
settings.transfer_owner=Нов притежател
|
||||
settings.make_transfer=Прехвърляне
|
||||
settings.make_transfer=Прехвърли
|
||||
settings.transfer_succeed=Притежанието на хранилището е прехвърлено успешно.
|
||||
settings.confirm_delete=Потвърждаване на изтриването
|
||||
settings.confirm_delete=Потвърди изтриването
|
||||
settings.add_collaborator=Добави нов сътрудник
|
||||
settings.add_collaborator_success=Добавен е нов сътрудник.
|
||||
settings.remove_collaborator_success=Сътрудникът е премахнат.
|
||||
@@ -543,7 +566,7 @@ settings.user_is_org_member=Потребителят е член на орган
|
||||
settings.add_webhook=Добави уеб-кука
|
||||
settings.hooks_desc=Уеб-куките много приличат на обикновен HTTP POST тригер. Когато нещо се случи в Gogs, ние ще изпратим уведомление до сървъра, който посочите. Научете повече в <a target="_blank" href="%s">Ръководство за уеб-куки</a>.
|
||||
settings.webhook_deletion=Изтрий уеб-кука
|
||||
settings.webhook_deletion_desc=Изтриването на тази уеб-кука ще премахне информацията за нея и цялата хронология на нейното изпращане. Желаете ли да продължите?
|
||||
settings.webhook_deletion_desc=При изтриване на тази уеб-кука ще се премахне информацията за нея и цялата хронология на нейното изпращане. Желаете ли да продължите?
|
||||
settings.webhook_deletion_success=Уеб-куката е изтрита успешно!
|
||||
settings.webhook.request=Заявка
|
||||
settings.webhook.response=Отговор
|
||||
@@ -551,10 +574,10 @@ settings.webhook.headers=Заглавки
|
||||
settings.webhook.payload=Съдържание
|
||||
settings.webhook.body=Тяло
|
||||
settings.githooks_desc=Git куките се изпълняват от Git. Вие може да промените файловете с поддържаните куки в списъка по-долу, за да изпълните външни операции.
|
||||
settings.githook_edit_desc=Ако куката е неактивна, ще бъдат представено примерно съдържание. Ако оставите съдържанието празно, то тази кука ще бъде изключена.
|
||||
settings.githook_name=Име на кука
|
||||
settings.githook_content=Съдържание на кука
|
||||
settings.update_githook=Обнови кука
|
||||
settings.githook_edit_desc=Ако куката е неактивна, ще бъде представено примерно съдържание. Ако оставите съдържанието празно, то тази кука ще бъде изключена.
|
||||
settings.githook_name=Име на куката
|
||||
settings.githook_content=Съдържание на куката
|
||||
settings.update_githook=Обнови куката
|
||||
settings.add_webhook_desc=Gogs ще изпрати <code>POST</code> заявка към указания URL адрес заедно с информация за събитието, което е настъпило. Също можете да укажете в какъв формат желаете да получите данните при задействане на куката (JSON, x-www-form-urlencoded, XML) и др. Допълнително описание можете да намерите в нашето <a target="_blank" href="%s">Ръководство за уеб-куки</a>.
|
||||
settings.payload_url=URL адрес на изпращане
|
||||
settings.content_type=Тип на съдържанието
|
||||
@@ -573,8 +596,8 @@ settings.event_push_desc=Git предаване към хранилището
|
||||
settings.active=Активна
|
||||
settings.active_helper=Подробности относно събитието, което е задействало куката, също ще бъдат изпратени.
|
||||
settings.add_hook_success=Новата уеб-кука е добавена успешно.
|
||||
settings.update_webhook=Промени уеб-куката
|
||||
settings.update_hook_success=Уеб-куката е променена успешно.
|
||||
settings.update_webhook=Обнови уеб-куката
|
||||
settings.update_hook_success=Уеб-куката е запазена успешно.
|
||||
settings.delete_webhook=Изтрий уеб-куката
|
||||
settings.recent_deliveries=Последни изпращания
|
||||
settings.hook_type=Тип на куката
|
||||
@@ -591,7 +614,7 @@ settings.key_been_used=Съдържанието на ключа за внедр
|
||||
settings.key_name_used=Ключ за внедряване с такова име вече съществува.
|
||||
settings.add_key_success=Новият ключ за внедряване '%s' е добавен успешно!
|
||||
settings.deploy_key_deletion=Изтрий ключ за внедряване
|
||||
settings.deploy_key_deletion_desc=Изтриването на този ключ за внедряване ще премахне свързаните права за достъп до това хранилище. Желаете ли да продължите?
|
||||
settings.deploy_key_deletion_desc=При изтриването на този ключ за внедряване ще се премахнат свързаните права за достъп до това хранилище. Желаете ли да продължите?
|
||||
settings.deploy_key_deletion_success=Ключът за внедряване е изтрит успешно!
|
||||
|
||||
diff.browse_source=Преглед на кода
|
||||
@@ -611,7 +634,7 @@ release.stable=Стабилни
|
||||
release.edit=редактиране
|
||||
release.ahead=<strong>%d</strong> ревизии на %s след това издание
|
||||
release.source_code=Изходен код
|
||||
release.tag_name=Име на маркер
|
||||
release.tag_name=Име на маркера
|
||||
release.target=Цел
|
||||
release.tag_helper=Изберете съществуващ маркер или създайте нов маркер по време на публикуване.
|
||||
release.release_title=Заглавие на изданието
|
||||
@@ -629,10 +652,10 @@ release.tag_name_already_exist=Издание с това име на марке
|
||||
|
||||
[org]
|
||||
org_name_holder=Име на организацията
|
||||
org_full_name_holder=Пълно име на организацията
|
||||
org_name_helper=Добрите имена на организация са кратки и запомнящи се.
|
||||
org_email_helper=Ел. пощата на организацията получава всички уведомления и потвърждения.
|
||||
create_org=Създай организация
|
||||
repo_updated=Актуализиране
|
||||
repo_updated=Обновено
|
||||
people=Хора
|
||||
invite_someone=Поканете някого
|
||||
teams=Екипи
|
||||
@@ -646,23 +669,23 @@ team_name_helper=Ще използвате това име при спомена
|
||||
team_desc_helper=Каква е целта на този екип?
|
||||
team_permission_desc=Какво ниво на достъп трябва да има този екип?
|
||||
|
||||
form.name_reserved=Името на организация '%s' е запазено.
|
||||
form.name_pattern_not_allowed=Име на организация от вида '%s' не е разрешено.
|
||||
form.name_reserved=Името на организацията '%s' е запазено.
|
||||
form.name_pattern_not_allowed=Име на организацията от вида '%s' не е разрешено.
|
||||
|
||||
settings=Настройки
|
||||
settings.options=Опции
|
||||
settings.full_name=Пълно име
|
||||
settings.website=Уебсайт
|
||||
settings.location=Местоположение
|
||||
settings.update_settings=Актуализирай настройките
|
||||
settings.change_orgname=Името на екипа е променено
|
||||
settings.change_orgname_desc=Името на организацията ще бъде променено. Това ще засегне всички връзки свързани с организацията. Желаете ли да продължите?
|
||||
settings.location=Локация
|
||||
settings.update_settings=Обнови настройките
|
||||
settings.update_setting_success=Настройките на организацията са запазени успешно.
|
||||
settings.change_orgname_prompt=Този промяна ще засегне всички връзки сочещи към организацията.
|
||||
settings.update_avatar_success=Настройките на аватара на организацията са запазени успешно.
|
||||
settings.delete=Изтрий организацията
|
||||
settings.delete_account=Изтриване на тази организация
|
||||
settings.delete_prompt=Организацията ще бъде изтрита и <strong>НЕ МОЖЕ</strong> да се върне!
|
||||
settings.delete_prompt=Организацията ще бъде изтрита и операцията <strong>НЕ МОЖЕ</strong> да бъде отменена в последствие!
|
||||
settings.confirm_delete_account=Потвърди изтриването
|
||||
settings.delete_org_title=Изтриване на организацията
|
||||
settings.delete_org_title=Изтрий организацията
|
||||
settings.delete_org_desc=Тази организация ще бъде окончателно изтрита. Желаете ли да продължите?
|
||||
settings.hooks_desc=Добави уеб-куки, които ще бъдат използвани от <strong>всички хранилища</strong> в тази организация.
|
||||
|
||||
@@ -690,14 +713,14 @@ teams.no_desc=Този екип няма описание
|
||||
teams.settings=Настройки
|
||||
teams.owners_permission_desc=Притежателите имат пълен достъп до <strong>всички хранилища</strong> и имат <strong>права на администратори</strong> на организацията.
|
||||
teams.members=Членовете на екипа
|
||||
teams.update_settings=Актуализирай настройките
|
||||
teams.update_settings=Обнови настройките
|
||||
teams.delete_team=Изтриване на този екип
|
||||
teams.add_team_member=Добавяне на член в екипа
|
||||
teams.add_team_member=Добави член на екипа
|
||||
teams.delete_team_title=Изтрий екипа
|
||||
teams.delete_team_desc=Тъй като този екип ще бъдат изтрит, членовете на този екип може да загубят достъп до някои хранилища. Желаете ли да продължите?
|
||||
teams.delete_team_desc=Тъй като този екип ще бъдат изтрит, членовете му може да загубят достъп до някои хранилища. Желаете ли да продължите?
|
||||
teams.delete_team_success=Този екип е бил изтрит успешно.
|
||||
teams.read_permission_desc=Този екип предоставя достъп за <strong>четене</strong>: членове могат да разглеждат и клонират хранилищата на екипа.
|
||||
teams.write_permission_desc=Този екип предоставя права за <strong>писане</strong>: членовете могат да четат от и предават към хранилищата на екипа.
|
||||
teams.write_permission_desc=Този екип предоставя достъп за <strong>писане</strong>: членовете могат да четат от и предават към хранилищата на екипа.
|
||||
teams.admin_permission_desc=Този екип предоставя <strong>администраторски</strong> достъп: членовете могат да четат от, да предават към и да добавя нови сътрудници към хранилищата на екипа.
|
||||
teams.repositories=Хранилища на екипа
|
||||
teams.add_team_repository=Добави хранилище на екипа
|
||||
@@ -707,14 +730,15 @@ teams.add_nonexistent_repo=Хранилището, което се опитва
|
||||
[admin]
|
||||
dashboard=Табло
|
||||
users=Потребители
|
||||
organizations=Организация
|
||||
organizations=Организации
|
||||
repositories=Хранилища
|
||||
authentication=Удостоверявания
|
||||
config=Конфигурация
|
||||
notices=Системни известия
|
||||
monitor=Наблюдение
|
||||
prev=Предишен
|
||||
next=Следващ
|
||||
first_page=Първа
|
||||
last_page=Последна
|
||||
total=Общо: %d
|
||||
|
||||
dashboard.statistic=Статистика
|
||||
dashboard.operations=Операции
|
||||
@@ -725,9 +749,9 @@ dashboard.operation_switch=Превключи
|
||||
dashboard.operation_run=Изпълни
|
||||
dashboard.clean_unbind_oauth=Почисти несвързани OAuthes
|
||||
dashboard.clean_unbind_oauth_success=Всички несвързани OAuthes са изтрити успешно.
|
||||
dashboard.delete_inactivate_accounts=Изтриване на всички неактивни профили
|
||||
dashboard.delete_inactivate_accounts=Изтрий всички неактивни профили
|
||||
dashboard.delete_inactivate_accounts_success=Всички неактивни профили са изтрити успешно.
|
||||
dashboard.delete_repo_archives=Изтриване на всички архиви на хранилища
|
||||
dashboard.delete_repo_archives=Изтрий всички архиви на хранилища
|
||||
dashboard.delete_repo_archives_success=Всички архиви на хранилищата са изтрити успешно.
|
||||
dashboard.git_gc_repos=Почисти изтрити данни в хранилищата
|
||||
dashboard.git_gc_repos_success=Всички хранилища са почистени от изтрити данни успешно.
|
||||
@@ -741,9 +765,9 @@ dashboard.current_goroutine=Текущи Goroutines
|
||||
dashboard.current_memory_usage=Текущо използвана памет
|
||||
dashboard.total_memory_allocated=Общо заделена памет
|
||||
dashboard.memory_obtained=Получена памет
|
||||
dashboard.pointer_lookup_times=Време за обхождане на указатели
|
||||
dashboard.memory_allocate_times=Време за заделяне на памет
|
||||
dashboard.memory_free_times=Време за освобождаване на памет
|
||||
dashboard.pointer_lookup_times=Брой обхождания на указатели
|
||||
dashboard.memory_allocate_times=Брой заделяния на памет
|
||||
dashboard.memory_free_times=Брой освобождавания на памет
|
||||
dashboard.current_heap_usage=Текущо използвана осн. памет
|
||||
dashboard.heap_memory_obtained=Получена осн. памет
|
||||
dashboard.heap_memory_idle=Празна осн. памет
|
||||
@@ -759,40 +783,45 @@ dashboard.mcache_structures_obtained=Получени MCache обекти
|
||||
dashboard.profiling_bucket_hash_table_obtained=Получени Profiling Bucket Hash Table
|
||||
dashboard.gc_metadata_obtained=Получени GC метаданни
|
||||
dashboard.other_system_allocation_obtained=Получена друга системна памет
|
||||
dashboard.next_gc_recycle=Слeдващо рециклиране на GC
|
||||
dashboard.next_gc_recycle=Следващо рециклиране на GC
|
||||
dashboard.last_gc_time=Време от последен GC
|
||||
dashboard.total_gc_time=Общо време за GC
|
||||
dashboard.total_gc_pause=Общо пауза за GC
|
||||
dashboard.last_gc_pause=Последна пауза за GC
|
||||
dashboard.gc_times=Брой GC
|
||||
|
||||
users.user_manage_panel=Панел за управление на потребителите
|
||||
users.new_account=Създаване на нов профил
|
||||
users.user_manage_panel=Управление на потребителя
|
||||
users.new_account=Създай нов профил
|
||||
users.name=Име
|
||||
users.activated=Активиран
|
||||
users.admin=Администратор
|
||||
users.repos=Хранилища
|
||||
users.created=Създаване
|
||||
users.send_register_notify=Прати уведомление на потребителя при регистрация
|
||||
users.new_success=Новият профил '%s' е добавен успешно.
|
||||
users.edit=Редакция
|
||||
users.auth_source=Източник за удостоверяване
|
||||
users.local=Локално
|
||||
users.auth_login_name=Потребителско име за удостоверяване
|
||||
users.update_profile_success=Профилът е обновен успешно.
|
||||
users.edit_account=Редактиране на профил
|
||||
users.password_helper=Оставете празна ако не се променя.
|
||||
users.update_profile_success=Профилът е запазен успешно.
|
||||
users.edit_account=Редактирай профил
|
||||
users.is_activated=Този профил е активиран
|
||||
users.is_admin=Този профил има административни права
|
||||
users.allow_git_hook=Този профил има разрешение да създава Git куки
|
||||
users.allow_import_local=Този профил има права за импорт на локални хранилища
|
||||
users.update_profile=Обнови профила
|
||||
users.delete_account=Изтриване на този профил
|
||||
users.still_own_repo=Този профил притежава поне едно хранилище. Първо трябва да го изтриете или да го прехвърлите на друг потребител.
|
||||
users.delete_account=Изтрий този профил
|
||||
users.still_own_repo=Този профил притежава поне едно хранилище. Първо трябва да изтриете хранилището или да го прехвърлите на друг потребител.
|
||||
users.still_has_org=Този профил е член на поне една организация. Първо трябва да напуснете или изтриете тези организации.
|
||||
users.deletion_success=Профилът е изтрит успешно!
|
||||
|
||||
orgs.org_manage_panel=Управление на организациите
|
||||
orgs.org_manage_panel=Управление на организацията
|
||||
orgs.name=Име
|
||||
orgs.teams=Екипи
|
||||
orgs.members=Членове
|
||||
|
||||
repos.repo_manage_panel=Управление на хранилищата
|
||||
repos.repo_manage_panel=Управление на хранилището
|
||||
repos.owner=Притежател
|
||||
repos.name=Име
|
||||
repos.private=Лично
|
||||
@@ -800,41 +829,47 @@ repos.watches=Наблюдавания
|
||||
repos.stars=Харесвания
|
||||
repos.issues=Проблеми
|
||||
|
||||
auths.auth_manage_panel=Управление на удостоверяването
|
||||
auths.auth_manage_panel=Управление на удостоверявания
|
||||
auths.new=Добави нов източник за удостоверяване
|
||||
auths.name=Име
|
||||
auths.type=Тип
|
||||
auths.enabled=Активен
|
||||
auths.updated=Актуализиран
|
||||
auths.updated=Обновен
|
||||
auths.auth_type=Тип на удостоверяване
|
||||
auths.auth_name=Име на удостоверяване
|
||||
auths.domain=Домейн
|
||||
auths.host=Сървър
|
||||
auths.port=Порт
|
||||
auths.bind_dn=Домейн за bind
|
||||
auths.bind_password=Парола за bind
|
||||
auths.bind_dn=Име (DN) за свръзка
|
||||
auths.bind_password=Парола за свръзка
|
||||
auths.bind_password_helper=Внимание: Тази парола се запазва некриптирана. Моля използвайте потребител, който няма административен достъп.
|
||||
auths.user_base=База с потребители
|
||||
auths.user_dn=Име (DN) на потребител
|
||||
auths.attribute_name=Атрибут за име
|
||||
auths.attribute_surname=Атрибут за фамилия
|
||||
auths.attribute_mail=Атрибут за ел. поща
|
||||
auths.filter=Филтър за потребители
|
||||
auths.admin_filter=Филтър за администратори
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Тип на SMTP удостоверяване
|
||||
auths.smtp_auth=SMTP удостоверяване
|
||||
auths.smtphost=SMTP сървър
|
||||
auths.smtpport=SMTP порт
|
||||
auths.allowed_domains=Разрешени домейни
|
||||
auths.allowed_domains_helper=Оставете празно за да не се ограничават домейните. За множество домейни използвайте запетая за разделител.
|
||||
auths.enable_tls=Включи TLS криптиране
|
||||
auths.skip_tls_verify=Пропусни проверка на TLS
|
||||
auths.pam_service_name=Име на PAM услуга
|
||||
auths.enable_auto_register=Включи автоматична регистрация
|
||||
auths.tips=Съвети
|
||||
auths.edit=Редактиране на настройки за удостоверяване
|
||||
auths.edit=Редактирай настройки за удостоверяване
|
||||
auths.activated=Това удостоверяване е активно
|
||||
auths.update_success=Настройките за удостоверяване са обновени успешно.
|
||||
auths.new_success=Новото удостоверяване '%s' е добавено успешно.
|
||||
auths.update_success=Настройките за удостоверяване са запазени успешно.
|
||||
auths.update=Обнови настройки за удостоверяване
|
||||
auths.delete=Изтриване на това удостоверяване
|
||||
auths.delete_auth_title=Изтрий удостоверяването
|
||||
auths.delete_auth_desc=Това удостоверяване ще бъде изтрито. Желаете ли да продължите?
|
||||
auths.deletion_success=Удостоверяването е изтрито успешно!
|
||||
|
||||
config.server_config=Сървърни настройки
|
||||
config.app_name=Име на приложението
|
||||
@@ -858,16 +893,18 @@ config.db_user=Потребител
|
||||
config.db_ssl_mode=SSL режим
|
||||
config.db_ssl_mode_helper=(само за postgres)
|
||||
config.db_path=Път
|
||||
config.db_path_helper=(само за sqlite3)
|
||||
config.db_path_helper=(за "sqlite3" и "tidb")
|
||||
config.service_config=Настройка на услугата
|
||||
config.register_email_confirm=Изисквай потвърждение на адреси на ел. поща
|
||||
config.disable_register=Изключи нови регистриции
|
||||
config.disable_register=Изключи нови регистрации
|
||||
config.show_registration_button=Покажи бутон за регистрация
|
||||
config.require_sign_in_view=Изисквай вписване за преглед
|
||||
config.mail_notify=Уведомяване по ел. поща
|
||||
config.enable_cache_avatar=Включи кеширане на аватари
|
||||
config.mail_notify=Уведомяване по ел. поща
|
||||
config.disable_key_size_check=Изключи проверка минимален размер на ключ
|
||||
config.enable_captcha=Включи Captcha
|
||||
config.active_code_lives=Кодове за активиране
|
||||
config.reset_password_code_lives=Кодове за ресет на парола
|
||||
config.reset_password_code_lives=Кодове за изчистване на парола
|
||||
config.webhook_config=Конфигурация на уеб-куки
|
||||
config.queue_length=Дължина на опашка
|
||||
config.deliver_timeout=Време за отказ при изпращане
|
||||
@@ -887,15 +924,15 @@ config.cache_conn=Кеш на връзката
|
||||
config.session_config=Конфигурация на сесии
|
||||
config.session_provider=Доставчик на сесии
|
||||
config.provider_config=Конфигурация на доставчик
|
||||
config.cookie_name=Име на бисквитка
|
||||
config.cookie_name=Име на бисквитката
|
||||
config.enable_set_cookie=Включи използване на бисквитки
|
||||
config.gc_interval_time=GC през интервал
|
||||
config.session_life_time=Време на живот на сесиите
|
||||
config.session_life_time=Период на валидност на сесиите
|
||||
config.https_only=HTTPS само
|
||||
config.cookie_life_time=Време на живот на бисквитките
|
||||
config.cookie_life_time=Период на валидност на бисквитките
|
||||
config.picture_config=Конфигурация на изображения
|
||||
config.picture_service=Услуги за снимки
|
||||
config.disable_gravatar=Изключване на Gravatar
|
||||
config.disable_gravatar=Изключи Gravatar
|
||||
config.log_config=Конфигурация на журнал
|
||||
config.log_mode=Режим на журнал
|
||||
|
||||
@@ -904,7 +941,7 @@ monitor.name=Име
|
||||
monitor.schedule=График
|
||||
monitor.next=Следващ път
|
||||
monitor.previous=Предишен път
|
||||
monitor.execute_times=Време на изпълнение
|
||||
monitor.execute_times=Брой изпълнения
|
||||
monitor.process=Изпълнявани процеси
|
||||
monitor.desc=Описание
|
||||
monitor.start=Начален час
|
||||
@@ -954,5 +991,5 @@ raw_minutes=минути
|
||||
default_message=Пуснете файлове тук или щракнете за качване.
|
||||
invalid_input_type=Невъзможно качване на файловете от този тип.
|
||||
file_too_big=Размер на файла ({{filesize}} MB) надвишава максималния размер ({{maxFilesize}} MB).
|
||||
remove_file=Премахване на файл
|
||||
remove_file=Премахни файл
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
app_desc=Ein schmerzloser, selbst gehosteter Git-Service, geschrieben in Go
|
||||
app_desc=Ein einfacher, selbst gehosteter Git-Service, geschrieben in Go
|
||||
|
||||
home=Home
|
||||
dashboard=Übersicht
|
||||
explore=Erkunden
|
||||
help=Hilfe
|
||||
sign_in=Anmelden
|
||||
social_sign_in=Anmeldung über soziales Konto: zweiter Schritt <small>Konto verknüpfen</small>
|
||||
sign_out=Abmelden
|
||||
sign_up=Registrieren
|
||||
register=Registrieren
|
||||
@@ -38,7 +37,7 @@ settings=Einstellungen
|
||||
your_profile=Dein Profil
|
||||
your_settings=Deine Einstellungen
|
||||
|
||||
news_feed=News Feed
|
||||
news_feed=Neuigkeiten
|
||||
pull_requests=Pull Requests
|
||||
issues=Issues
|
||||
|
||||
@@ -54,20 +53,24 @@ code=Code
|
||||
[install]
|
||||
install=Installation
|
||||
title=Installation für erstmaligen Start
|
||||
requite_db_desc=Gogs erfordert MySQL, PostgreSQL oder SQLite 3, aber SQLite3 ist in der offiziellen binären Version akiviert.
|
||||
docker_helper=Wenn Gogs innerhalb Docker läuft, lies dir bitte die <a target="_blank" href="%s">Guidelines</a> genau durch, bevor du irgendwas auf dieser Seite änderst!
|
||||
requite_db_desc=Gogs benötigt MySQL, PostgreSQL, SQLite3 oder TiDB.
|
||||
db_title=Datenbankeinstellungen
|
||||
db_type=Datenbanktyp
|
||||
host=Host
|
||||
user=Benutzer
|
||||
password=Passwort
|
||||
db_name=Datenbankname
|
||||
db_helper=Bitte verwenden InnoDB-Engine mit utf8_general_ci Zeichensatz für MySQL.
|
||||
db_helper=Bitte verwenden sie die InnoDB-Engine mit utf8_general_ci Zeichensatz für MySQL.
|
||||
ssl_mode=SSL-Modus
|
||||
path=Pfad
|
||||
sqlite_helper=Der Dateipfad des SQLite3 Datenbank.
|
||||
err_empty_sqlite_path=Pfad zur SQLite3-Datenbank darf nicht leer sein.
|
||||
sqlite_helper=Der Dateipfad zur SQLite3 oder TiDB Datenbank.
|
||||
err_empty_db_path=SQLite3 oder TiDB Datenbankpfad darf nicht leer sein.
|
||||
err_invalid_tidb_name=Der TiDB Datenbankname darf kein "." und kein "-" enthalten.
|
||||
no_admin_and_disable_registration=Du kannst die Registrierung nicht deaktivieren, ohne ein Administratorkonto zu erstellen.
|
||||
err_empty_admin_password=Das Administrator-Passwort darf nicht leer sein.
|
||||
|
||||
general_title=Allgemeine Einstellungen von Gogs
|
||||
general_title=Allgemeine Einstellungen
|
||||
app_name=Anwendungsname
|
||||
app_name_helper=Hier den Organisationsnamen einfügen.
|
||||
repo_path=Repository Root-Verzeichnispfad
|
||||
@@ -79,7 +82,7 @@ domain_helper=Dies hat Auswirkung auf die SSH clone URLs.
|
||||
ssh_port=SSH Port
|
||||
ssh_port_helper=Die Portnummer deines SSH-Servers, lass dieses Feld leer, wenn du SSH deaktivieren möchtest.
|
||||
http_port=HTTP Port
|
||||
http_port_helper=Auf dieser Port Nummer ist die Apllikation erreichbar.
|
||||
http_port_helper=Auf dieser Port Nummer wird Gogs erreichbar sein.
|
||||
app_url=Anwendungs-URL
|
||||
app_url_helper=Dies hat Auswirkung auf die HTTP/HTTPS clone URLs und für die E-Mails.
|
||||
|
||||
@@ -90,8 +93,8 @@ smtp_from=Von
|
||||
smtp_from_helper=Absender-Adresse nach RFC 5322. Entweder nur eine E-Mail Adresse oder im folgenden Format: "Name" <email@example.com>.
|
||||
mailer_user=Sender E-mail
|
||||
mailer_password=Sender Passwort
|
||||
register_confirm=Registrierungsbestätigung aktvieren
|
||||
mail_notify=E-Mail-Benachrichtgung aktivieren
|
||||
register_confirm=Registrierungsbestätigung aktivieren
|
||||
mail_notify=E-Mail-Benachrichtigungen aktivieren
|
||||
server_service_title=Server- und sonstige Diensteinstellungen
|
||||
offline_mode=Offline-Modus aktivieren
|
||||
offline_mode_popup=Deaktiviere das CDN auch im Produktionsmodus, alle Dateien werden von diesem Server ausgeliefert.
|
||||
@@ -99,6 +102,8 @@ disable_gravatar=Gravatar-Dienst deaktivieren
|
||||
disable_gravatar_popup=Gravatar und benutzerdefinierte Quellen deaktivieren, alle Avatare werden standardmäßig vom Nutzer hochgeladen oder sind Standardavatare.
|
||||
disable_registration=Benutzerregistrierung deaktivieren
|
||||
disable_registration_popup=Deaktiviere die Benutzerregistrierung, nur Administratoren können Benutzerkonten anlegen.
|
||||
enable_captcha=Captcha aktivieren
|
||||
enable_captcha_popup=Benötigt Captcha-Überprüfung für Registrierung durch Benutzer.
|
||||
require_sign_in_view=Erfordere Anmeldung, um Inhalte anzusehen
|
||||
require_sign_in_view_popup=Lediglich angemeldete Benutzer können Inhalte betrachten, Gäste sehen nur die Anmelden/Registrieren Seite.
|
||||
admin_setting_desc=Sie müssen jetzt noch keinen Administrator-Account anlegen. Der erste Benutzer ("ID=1") erhält automatisch Administrationsrechte.
|
||||
@@ -106,14 +111,14 @@ admin_title=Konto-Einstellungen für den Administrator
|
||||
admin_name=Benutzername
|
||||
admin_password=Passwort
|
||||
confirm_password=Passwort bestätigen
|
||||
admin_email=E-Mail
|
||||
admin_email=Administrator E-Mail
|
||||
install_gogs=Gogs installieren
|
||||
test_git_failed=Fehler beim Test des 'git' Kommandos: %v
|
||||
sqlite3_not_available=Deine Version unterstützt SQLite3 nicht, bitte lade dir die offizielle binäre Version von %s herunter, NICHT die gobuild-Version.
|
||||
sqlite3_not_available=Ihre Gogs-Version unterstützt kein SQLite3, bitte lade dir die offizielle binäre Version von %s herunter, NICHT die gobuild-Version.
|
||||
invalid_db_setting=Datenbank-Einstellungen sind nicht korrekt: %v
|
||||
invalid_repo_path=Repository Root-Verzeichnis ist ungültig: %v
|
||||
run_user_not_match=Der ausführende Benutzer ist nicht der aktuelle Benutzer: %s -> %s
|
||||
save_config_failed=Versuche die Konfiguration zu speichern ist fehlgeschlagen: %v
|
||||
save_config_failed=Fehler beim Speichern der Konfiguration: %v
|
||||
invalid_admin_setting=Admin-Konto Einstellungen sind ungültig: %v
|
||||
install_success=Herzlich Willkommen! Wir sind froh, dass du dich für Gogs entschieden hast. Wir wünschen viel Vergnügen damit.
|
||||
|
||||
@@ -121,8 +126,8 @@ install_success=Herzlich Willkommen! Wir sind froh, dass du dich für Gogs entsc
|
||||
uname_holder=Benutzername oder E-Mail
|
||||
password_holder=Passwort
|
||||
switch_dashboard_context=Dashboard Kontext wechseln
|
||||
my_repos=Meine Repositorys
|
||||
collaborative_repos=Gemeinschaftliche Repositorys
|
||||
my_repos=Meine Repositories
|
||||
collaborative_repos=Gemeinschaftliche Repositories
|
||||
my_orgs=Meine Organisationen
|
||||
my_mirrors=Meine Spiegel
|
||||
view_home=%s betrachten
|
||||
@@ -130,23 +135,22 @@ view_home=%s betrachten
|
||||
issues.in_your_repos=In deinen Repositories
|
||||
|
||||
[explore]
|
||||
repos=Repositorys
|
||||
repos=Repositories
|
||||
|
||||
[auth]
|
||||
create_new_account=Neues Konto erstellen
|
||||
register_hepler_msg=Du hast schon ein Konto? Jetzt anmelden!
|
||||
social_register_hepler_msg=Du hast schon ein soziales Konto? Jetzt verknüpfen!
|
||||
register_hepler_msg=Du hast bereits ein Konto? Jetzt anmelden!
|
||||
social_register_hepler_msg=Du hast bereits ein soziales Konto? Jetzt verknüpfen!
|
||||
disable_register_prompt=Es tut uns leid, die Registrierung wurde deaktiviert. Bitte wende dich an den Administrator.
|
||||
disable_register_mail=Es tut uns leid, die Bestätigung der Registrierungs-E-Mail wurde deaktiviert.
|
||||
remember_me=angemeldet bleiben
|
||||
remember_me=Angemeldet bleiben
|
||||
forgot_password=Passwort vergessen
|
||||
forget_password=Passwort vergessen?
|
||||
sign_up_now=Du willst ein Konto? Jetzt registrieren!
|
||||
confirmation_mail_sent_prompt=Eine neue Bestätigungs-E-Mail wurde an <b>%s</b> gesendet. Kontrolliere dein Postfach innerhalb der nächsten %d Stunden, um die Registrierung abzuschließen.
|
||||
sign_in_email=Melde dich mit deiner E-Mail-Adresse an
|
||||
active_your_account=Aktiviere dein Konto
|
||||
resent_limit_prompt=Es tut uns leid, du sendest zu häufig Aktivierungs-E-Mails. Bitte warte 3 Minuten.
|
||||
has_unconfirmed_mail=Hallo %s, du hast eine unbestätigte E-Mail-Adresse (<b>%s</b>). Wenn du keine Bestätigungs-E-Mail erhalten hast oder eine neue benötigtst, klicke bitte auf den folgenden Button.
|
||||
has_unconfirmed_mail=Hallo %s, du hast eine unbestätigte E-Mail-Adresse (<b>%s</b>). Wenn du keine Bestätigungs-E-Mail erhalten hast oder eine neue benötigst, klicke bitte auf den folgenden Button.
|
||||
resend_mail=Hier klicken, um deine Aktivierungs-E-Mail erneut zu versenden
|
||||
email_not_associate=Diese E-Mail-Adresse ist mit keinem Konto verknüpft.
|
||||
send_reset_mail=Hier klicken, um die E-Mail zum Passwort-zurücksetzen erneut zu versenden
|
||||
@@ -155,6 +159,12 @@ invalid_code=Es tut uns leid, der Bestätigungscode ist abgelaufen oder ungülti
|
||||
reset_password_helper=Hier klicken, um das Passwort zurückzusetzen
|
||||
password_too_short=Das Passwort muss mindenstens 6 Zeichen lang sein
|
||||
|
||||
[mail]
|
||||
activate_account=Bitte aktiviere dein Konto
|
||||
activate_email=Verifiziere deine E-Mail-Adresse
|
||||
reset_password=Setze dein Passwort zurück
|
||||
register_success=Registrierung erfolgreich, Willkommen
|
||||
|
||||
[modal]
|
||||
yes=Ja
|
||||
no=Nein
|
||||
@@ -174,13 +184,14 @@ AuthName=Authentifizierungsname
|
||||
AdminEmail=Admin E-mail
|
||||
|
||||
require_error=` darf nicht leer sein.`
|
||||
alpha_dash_error=` kann ausschließlich alphanumerische Zeichen und "-_" enthalten.`
|
||||
alpha_dash_dot_error=` kann ausschließlich alphanumerische Zeichen und ".-_" enthalten.`
|
||||
alpha_dash_error=` muss ausschließlich alphanumerische Zeichen und "-_" enthalten.`
|
||||
alpha_dash_dot_error=` muss ausschließlich alphanumerische Zeichen und ".-_" enthalten.`
|
||||
size_error=` muss die Größe %s haben.`
|
||||
min_size_error=` muss mindestens %s Zeichen enthalten.`
|
||||
max_size_error=` darf höchstens %s Zeichen enthalten.`
|
||||
email_error=` ist keine gültige E-Mail-Adresse.`
|
||||
url_error=` ist keine gültige URL.`
|
||||
include_error=`muss den Substring ‚%s‘ enthalten.`
|
||||
unknown_error=Unbekannter Fehler:
|
||||
captcha_incorrect=Captcha stimmt nicht überein.
|
||||
password_not_match=Die Passwörter stimmen nicht überein.
|
||||
@@ -204,9 +215,9 @@ auth_failed=Authentifizierung fehlgeschlagen: %v
|
||||
|
||||
still_own_repo=Dein Konto besitzt noch Repositories. Diese müssen zuerst gelöscht oder übertragen werden.
|
||||
still_has_org=Du bist noch Mitglied einer Organisation, bitte lösche zunächst diese Mitgliedschaft.
|
||||
org_still_own_repo=Diese Organisation besitzt noch Repositorys. Diese müssen zuerst gelöscht oder übertragen werden.
|
||||
org_still_own_repo=Diese Organisation besitzt noch Repositories. Diese müssen zuerst gelöscht oder übertragen werden.
|
||||
|
||||
still_own_user=Diese Authentifizierung wird noch von einigen Benutzern genutzt, diese müssen zuerst gelöscht oder deren Authentifizierung geändert werden.
|
||||
still_own_user=Diese Authentifizierung wird noch von einigen Benutzern genutzt. Entferne diese zuvor und lösche erneut.
|
||||
|
||||
target_branch_not_exist=Ziel-Branch existiert nicht
|
||||
|
||||
@@ -214,9 +225,9 @@ target_branch_not_exist=Ziel-Branch existiert nicht
|
||||
change_avatar=Ändere dein Profilbild auf gravatar.com
|
||||
change_custom_avatar=Ändere deinen Avatar in den Einstellungen
|
||||
join_on=Registriert
|
||||
repositories=Repositorys
|
||||
repositories=Repositories
|
||||
activity=Öffentliche Aktivität
|
||||
followers=Folgen
|
||||
followers=Followers
|
||||
starred=Markiert
|
||||
following=Folgt
|
||||
|
||||
@@ -239,13 +250,13 @@ full_name=Vollständiger Name
|
||||
website=Webseite
|
||||
location=Standort
|
||||
update_profile=Profil aktualisieren
|
||||
update_profile_success=Profil aktualisiert
|
||||
update_profile_success=Ihr Profil wurde erfolgreich aktualisiert.
|
||||
change_username=Benutzername geändert
|
||||
change_username_desc=Benutzername wurde geändert, möchtest du fortfahren? Dies beeinträchtigt sämtliche Links, die dein Konto betreffen.
|
||||
change_username_prompt=Diese Änderung wird sich auf die Linkbezüge zu deinem Account auswirken.
|
||||
continue=Weiter
|
||||
cancel=Abbrechen
|
||||
|
||||
enable_custom_avatar=Aktiviere benuztzerdefinierten Avatar
|
||||
enable_custom_avatar=Aktiviere benutzerdefinierten Avatar
|
||||
enable_custom_avatar_helper=Aktiviere dies, um deinen Avatar nicht von Gravatar zu laden
|
||||
choose_new_avatar=Neuen Avatar auswählen
|
||||
update_avatar=Avatar-Einstellung aktualisieren
|
||||
@@ -256,8 +267,9 @@ update_avatar_success=Deine Avatar-Einstellung wurde aktualisiert.
|
||||
change_password=Passwort ändern
|
||||
old_password=Aktuelles Passwort
|
||||
new_password=Neues Passwort
|
||||
retype_new_password=Neues Passwort erneut eingeben
|
||||
password_incorrect=Aktuelles Passwort ist nicht korrekt.
|
||||
change_password_success=Passwort geändert. Du kannst dich jetzt mit dem neuen Passwort anmelden.
|
||||
change_password_success=Passwort geändert. Du kannst dich jetzt mit deinem neuen Passwort anmelden.
|
||||
|
||||
emails=E-Mail-Adressen
|
||||
manage_emails=E-Mail-Adressen verwalten
|
||||
@@ -265,14 +277,17 @@ email_desc=Deine primäre E-Mail-Adresse wird für Benachrichtigungen und andere
|
||||
primary=Primär
|
||||
primary_email=Als primäre Adresse verwenden
|
||||
delete_email=Löschen
|
||||
email_deletion=E-Mail löschen
|
||||
email_deletion_desc=Das Löschen dieser E-Mail Adresse wird alle Informationen entfernen, die mit dieser E-Mail Adresse verknüpft sind. Willst du fortfahren?
|
||||
email_deletion_success=E-Mail-Adresse wurde erfolgreich gelöscht!
|
||||
add_new_email=Neue E-Mail-Adresse hinzufügen
|
||||
add_email=E-Mail-Adresse hinzufügen
|
||||
add_email_confirmation_sent=Eine neue Bestätigungsmail wurde an <b>%s</b> gesendet, bitte überprüfen Sie Ihren Posteingang innerhalb von %d Stunden um die Bestätigung abzuschließen.
|
||||
add_email_confirmation_sent=Eine neue Bestätigungsmail wurde an '%s' gesendet, bitte überprüfen Sie Ihren Posteingang innerhalb von %d Stunden um die Bestätigung abzuschließen.
|
||||
add_email_success=Deine neue E-Mail-Adresse wurde erfolgreich hinzugefügt.
|
||||
|
||||
manage_ssh_keys=SSH-Schlüssel verwalten
|
||||
add_key=SSH-Schlüssel hinzufügen
|
||||
ssh_desc=Dies ist eine Liste aller SSH-Schlüssel, die mit deinem Konto verknüpft sind. Entferne alle Schlüssel, die du nicht kennst.
|
||||
ssh_desc=Dies ist eine Liste aller SSH-Schlüssel, die mit deinem Konto verknüpft sind. Bitte entferne alle Schlüssel, die dir nicht bekannt sind.
|
||||
ssh_helper=<strong>Du brauchst Hilfe?</strong> Hier ist eine Anleitung zum <a href="%s">Erzeugen von SSH-Schlüsseln</a> oder <a href="%s">Problemlösen einfacher SSH-Probleme</a>.
|
||||
add_new_key=SSH-Schlüssel hinzufügen
|
||||
ssh_key_been_used=Inhalt des öffentlichen Schlüssels wurde verwendet.
|
||||
@@ -285,44 +300,45 @@ ssh_key_deletion=SSH-Schlüssel entfernen
|
||||
ssh_key_deletion_desc=Das Löschen dieses SSH-Schlüssels wird alle zugehörigen Zugriffsberechtigungen auf deinen Account entfernen. Möchtest du fortfahren?
|
||||
ssh_key_deletion_success=SSH-Schlüssel wurde erfolgreich gelöscht!
|
||||
add_on=Hinzugefügt am
|
||||
last_used=Zuletzt verwendet auf
|
||||
last_used=Zuletzt verwendet am
|
||||
no_activity=Keine neuen Aktivitäten
|
||||
key_state_desc=Dieser Schlüssel wurde in den letzten 7 Tagen verwendet
|
||||
token_state_desc=Dieses Token wurde in den letzten 7 Tagen benutzt
|
||||
|
||||
manage_social=Verknüpfte soziale Konten verwalten
|
||||
social_desc=Dies ist eine Liste verknüpfter sozialer Konten. Entferne alle Verknüpfungen, die du nicht kennst.
|
||||
social_desc=Dies ist eine Liste verknüpfter sozialer Konten. Bitte entferne alle Verknüpfungen, die dir nicht bekannt sind.
|
||||
unbind=Verknüpfung entfernen
|
||||
unbind_success=Die Verknüpfung zum sozialen Konto wurde entfernt.
|
||||
|
||||
manage_access_token=Verwaltung persönlicher Zugangs-Tokens
|
||||
generate_new_token=Neues Token erzeugen
|
||||
tokens_desc=Von dir generierte Tokens können benutzt werden, um auf die Gogs APIs zuzugreifen.
|
||||
new_token_desc=Momentan erlaubt jedes Token vollen Zugriff auf dein Konto.
|
||||
generate_new_token=Neuen Token erzeugen
|
||||
tokens_desc=Von dir generierte Token können benutzt werden, um auf die Gogs APIs zuzugreifen.
|
||||
new_token_desc=Momentan erlaubt jeder Token vollen Zugriff auf dein Konto.
|
||||
token_name=Token-Name
|
||||
generate_token=Token erzeugen
|
||||
generate_token_succees=Neues Zugangs-Token wurde erstellt! Bitte kopiere dein persönliches Zugangs-Token jetzt. Du wirst es später nicht mehr anzeigen können!
|
||||
generate_token=Token generieren
|
||||
generate_token_succees=Neuer Zugangs-Token wurde erstellt! Stelle sicher, dass du den Token kopiert hast, da du ihn später nicht mehr ansehen kannst!
|
||||
delete_token=Löschen
|
||||
access_token_deletion=Entfernung von persönlichen Token
|
||||
access_token_deletion_desc=Das Löschen dieses persönlichen Tokens wird alle zugehörigen Zugriffe der Anwendung entfernen. Möchtest du fortfahren?
|
||||
delete_token_success=Persönliches Zugriffs-Token wurde erfolgreich entfernt! Vergiss nicht deine Anwendung zu aktualisieren.
|
||||
delete_token_success=Persönlicher Zugriffs-Token wurde erfolgreich entfernt! Vergiss nicht deine Anwendung zu aktualisieren.
|
||||
|
||||
delete_account=Konto löschen
|
||||
delete_prompt=Diese Aktion wird dein Konto dauerhaft löschen und kann <strong>NICHT</strong> rückgängig gemacht werden!
|
||||
confirm_delete_account=Löschen
|
||||
delete_account_title=Account löschen
|
||||
delete_account_desc=Du bist dabei dieses Konto dauerhaft zu löschen, willst du fortfahren?
|
||||
delete_account_desc=Du bist dabei dieses Konto dauerhaft zu löschen, möchtest du wirklich fortfahren?
|
||||
|
||||
[repo]
|
||||
owner=Besitzer
|
||||
repo_name=Repository-Name
|
||||
repo_name_helper=Gute Repository-Namen sind kurz, einprägsam und <strong>einzigartig</strong>.
|
||||
visibility=Sichtbarkeit
|
||||
visiblity_helper=<span class="ui red text">Privates</span> Repository
|
||||
visiblity_fork_helper=(Eine Änderung dieses Wertes wirk sich auf alle Forks aus)
|
||||
visiblity_helper=Diese Repository ist <span class="ui red text">Privat</span>
|
||||
visiblity_helper_forced=Der Administrator hat festgelegt, dass alle neuen Repositories <span class="ui red text">privat</span> sein müssen
|
||||
visiblity_fork_helper=(Eine Änderung dieses Wertes wirkt sich auf alle Forks aus)
|
||||
fork_repo=Repository abspalten
|
||||
fork_from=Abspaltung von
|
||||
fork_visiblity_helper=Sichtbarkeit von abgespalteten Repositories ist nicht veränderbar
|
||||
fork_from=Forken von
|
||||
fork_visiblity_helper=Die Sichtbarkeit von geforkten Repositories ist nicht veränderbar.
|
||||
repo_desc=Beschreibung
|
||||
repo_lang=Sprache
|
||||
repo_lang_helper=.gitignore Dateien auswählen
|
||||
@@ -338,37 +354,42 @@ mirror_interval=Spiegel-Intervall (in Stunden)
|
||||
form.name_reserved=Repository-Name '%s' ist bereits vergeben.
|
||||
form.name_pattern_not_allowed=Repository-Namesmuster '%s' ist nicht zulässig.
|
||||
|
||||
need_auth=Authorisierung benötigt
|
||||
need_auth=Autorisierung benötigt
|
||||
migrate_type=Migrationstyp
|
||||
migrate_type_helper=Dieses Repositorie wird ein <span class="text blue">Spiegel sein</span>
|
||||
migrate_type_helper=Diese Repository wird ein <span class="text blue">Spiegel</span> sein
|
||||
migrate_repo=Repository migrieren
|
||||
migrate.clone_address=Adresse kopieren
|
||||
migrate.clone_address_desc=Das kann eine HTTP/HTTPS/GIT URL oder ein lokaler Serverpfad sein.
|
||||
migrate.clone_address_desc=Dies kann eine HTTP/HTTPS/GIT URL oder ein lokaler Serverpfad sein.
|
||||
migrate.permission_denied=Ihnen fehlen die Rechte zum Importieren lokaler Repositorys.
|
||||
migrate.invalid_local_path=Lokaler Pfad ist ungültig, er existiert nicht oder ist kein Ordner.
|
||||
|
||||
forked_from=Geforkt von
|
||||
fork_from_self=SIe können kein Repository forken, das ihnen gehört!
|
||||
fork_from_self=Sie können keine Repository forken, welche ihnen gehört!
|
||||
copy_link=Kopieren
|
||||
copy_link_success=Kopiert!
|
||||
copy_link_error=Drücke ⌘-C oder Strg-C zum Kopieren
|
||||
click_to_copy=In Zwischenablage kopieren
|
||||
copied=Kopiert OK
|
||||
clone_helper=Du brauchst Hilfe beim Klonen? Hier gibt es <a target="_blank" href="%s">Hilfe</a>!
|
||||
unwatch=Beobachtung beenden
|
||||
unwatch=Nicht mehr beobachten
|
||||
watch=Beobachten
|
||||
unstar=Markierung aufheben
|
||||
star=Markierung
|
||||
fork=Abspaltung
|
||||
star=Markieren
|
||||
fork=Fork
|
||||
|
||||
no_desc=Keine Beschreibung
|
||||
quick_guide=Kurzanleitung
|
||||
clone_this_repo=Dieses Repository klonen
|
||||
create_new_repo_command=Erstelle ein neues Repository mittels der Kommandozeile
|
||||
push_exist_repo=Übertrage ein existierendes Repository von der Kommandozeile
|
||||
clone_this_repo=Diese Repository klonen
|
||||
create_new_repo_command=Erstelle eine neue Repository mittels Kommandozeile
|
||||
push_exist_repo=Übertrage eine existierende Repository von der Kommandozeile
|
||||
repo_is_empty=Das Repository ist leer, bitte komm später wieder!
|
||||
|
||||
|
||||
branch=Branch
|
||||
tree=Struktur
|
||||
branch_and_tags=Branches & Tags
|
||||
branches=Branches
|
||||
tags=Markierungen
|
||||
tags=Tags
|
||||
issues=Issues
|
||||
pulls=Pull-Anforderung
|
||||
labels=Label
|
||||
@@ -384,12 +405,12 @@ commits.commits=Commits
|
||||
commits.search=Durchsuche Commits
|
||||
commits.find=Finden
|
||||
commits.author=Author
|
||||
commits.message=Mitteilung
|
||||
commits.message=Nachricht
|
||||
commits.date=Datum
|
||||
commits.older=Älter
|
||||
commits.newer=Neuer
|
||||
|
||||
issues.new=Neues Problem
|
||||
issues.new=Neuer Issue
|
||||
issues.new.labels=Labels
|
||||
issues.new.no_label=Kein Label
|
||||
issues.new.clear_labels=Labels entfernen
|
||||
@@ -411,8 +432,8 @@ issues.filter_label=Label
|
||||
issues.filter_label_no_select=Kein Label gewählt
|
||||
issues.filter_milestone=Meilenstein
|
||||
issues.filter_milestone_no_select=Kein ausgewählter Meilenstein
|
||||
issues.filter_assignee=Beauftragter
|
||||
issues.filter_assginee_no_select=Kein ausgwählter Zuständiger
|
||||
issues.filter_assignee=Zuständiger
|
||||
issues.filter_assginee_no_select=Kein ausgewählter Zuständiger
|
||||
issues.filter_type=Typ
|
||||
issues.filter_type.all_issues=Alle Probleme
|
||||
issues.filter_type.assigned_to_you=Dir zugewiesen
|
||||
@@ -435,9 +456,9 @@ issues.num_comments=%d Kommentare
|
||||
issues.commented_at=`kommentiert in <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=Hier gibt es bis jetzt noch keinen Inhalt.
|
||||
issues.close_issue=Schließen
|
||||
issues.close_comment_issue=Schließen und kommentieren
|
||||
issues.close_comment_issue=Kommentieren und schließen
|
||||
issues.reopen_issue=Wiedereröffnen
|
||||
issues.reopen_comment_issue=Wiedereröffnen und kommentieren
|
||||
issues.reopen_comment_issue=Kommentieren und wiedereröffnen
|
||||
issues.create_comment=Kommentieren
|
||||
issues.closed_at=`geschlossen in <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`wiedereröffnet in <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
@@ -453,7 +474,7 @@ issues.save=Speichern
|
||||
issues.label_title=Label Name
|
||||
issues.label_color=Label Farbe
|
||||
issues.label_count=%d Labels
|
||||
issues.label_open_issues=%d offene Probleme
|
||||
issues.label_open_issues=%d offene Issues
|
||||
issues.label_edit=Bearbeiten
|
||||
issues.label_delete=Löschen
|
||||
issues.label_modify=Label Änderung
|
||||
@@ -472,17 +493,19 @@ pulls.has_pull_request=`Es existiert bereits eine Pull-Anforderung zwischen dies
|
||||
pulls.create=Pull Request erstellen
|
||||
pulls.title_desc=möchte %[1]d Commits von <code>%[2]s</code> nach <code>%[3]s</code> zusammenführen
|
||||
pulls.merged_title_desc=%[1]d Commits von <code>%[2]s</code> nach <code>%[3]s</code> %[4]s zusammengeführt
|
||||
pulls.tab_conversation=Unterhaltung
|
||||
pulls.tab_conversation=Konversation
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Dateien geändert
|
||||
pulls.reopen_to_merge=Bitte diese Pull-Anforderung wiedereröffnen, um die Merge-Operation auszuführen.
|
||||
pulls.merged=Zusammengeführt
|
||||
pulls.has_merged=Dieser Pull-Request wurde erfolgreich zusammengeführt!
|
||||
pulls.data_broken=Die Daten dieser Pull-Anforderung sind defekt aufgrund des Löschens von Fork-Informationen.
|
||||
pulls.is_checking=Die Konfliktprüfung ist in Arbeit. Bitte aktualisiere die Seite in wenigen Momenten.
|
||||
pulls.can_auto_merge_desc=Du kannst eine Auto-Merge Operation auf diese Pull-Anforderung durchführen.
|
||||
pulls.cannot_auto_merge_desc=Es kann keine Auto-Merge Operation durchgeführt werden, da es Konflikte zwischen den Commits gibt.
|
||||
pulls.cannot_auto_merge_helper=Bitte benutze ein Kommandozeilentool, um den Konflikt zu lösen.
|
||||
pulls.merge_pull_request=Pull-Request zusammenführen
|
||||
pulls.open_unmerged_pull_exists=`Du kannst die Pull-Anforderung nicht wiedereröffnen, da bereits eine offene Pull-Anforderung (#%d) aus dem selben Repository mit den gleichen Merge-Informationen existiert und auf das Merging wartet.`
|
||||
|
||||
milestones.new=Neuer Meilenstein
|
||||
milestones.open_tab=%d offen
|
||||
@@ -497,7 +520,7 @@ milestones.title=Titel
|
||||
milestones.desc=Beschreibung
|
||||
milestones.due_date=Fälligkeitsdatum (optional)
|
||||
milestones.clear=Bereinigen
|
||||
milestones.invalid_due_date_format=Format des Fälligkeitsdatums ist ungültig. Es muss das Format 'Jahr-mm-dd' haben.
|
||||
milestones.invalid_due_date_format=Format des Fälligkeitsdatums ist ungültig. Es muss das Format 'JJJJ-mm-dd' haben.
|
||||
milestones.create_success=Meilenstein '%s' wurde erfolgreich erstellt!
|
||||
milestones.edit=Meilenstein bearbeiten
|
||||
milestones.edit_subheader=Benutze eine bessere Beschreibung für die Meilensteine, um die Menschen nicht zu verwirren.
|
||||
@@ -505,7 +528,7 @@ milestones.cancel=Abbrechen
|
||||
milestones.modify=Meilenstein ändern
|
||||
milestones.edit_success=Änderungen des Meilensteins '%s' wurden erfolgreich gespeichert!
|
||||
milestones.deletion=Meilenstein löschen
|
||||
milestones.deletion_desc=Löschen dieses Meilensteins wird alle zugehörigen Informationen in allen Issues entfernen. Soll fortgefahren werden?
|
||||
milestones.deletion_desc=Das Löschen dieses Meilensteins wird alle zugehörigen Informationen in allen Issues entfernen. Wirklich fortfahren?
|
||||
milestones.deletion_success=Meilenstein erfolgreich gelöscht!
|
||||
|
||||
settings=Einstellungen
|
||||
@@ -516,34 +539,34 @@ settings.githooks=Git-Hooks
|
||||
settings.basic_settings=Grundeinstellungen
|
||||
settings.danger_zone=Gefahrenzone
|
||||
settings.site=Offizielle Webseite
|
||||
settings.update_settings=Aktualisierungseinstellungen
|
||||
settings.change_reponame_prompt=Diese Änderung wirkt sich darauf aus, wie sich Links auf das Repository beziehen.
|
||||
settings.update_settings=Einstellungen speichern
|
||||
settings.change_reponame_prompt=Diese Änderung wirkt sich darauf aus, wie sich Links auf Repositories beziehen.
|
||||
settings.transfer=Besitz übertragen
|
||||
settings.transfer_desc=Übertrage dieses Repository einem anderen Benutzer oder einer Organisation in der du Admin-Rechte hast.
|
||||
settings.new_owner_has_same_repo=Neuer Eigentümer hat bereits ein Repository mit dem gleichen Namen.
|
||||
settings.transfer_desc=Übertrage diese Repository auf einen anderen Benutzer oder eine Organisation in der du Admin-Rechte hast.
|
||||
settings.new_owner_has_same_repo=Der neue Eigentümer hat bereits ein Repository mit dem gleichen Namen.
|
||||
settings.delete=Repository löschen
|
||||
settings.delete_desc=Wenn dieses Repository gelöscht ist, gibt es keinen Weg zurück. Sei dir sicher!
|
||||
settings.delete_desc=Wenn diese Repository gelöscht ist, gibt es keinen Weg zurück. Bitte sei behutsam.
|
||||
settings.transfer_notices_1=- Du wirst den Zugang verlieren, wenn der neue Besitzer ein individueller Benutzer ist.
|
||||
settings.transfer_notices_2=- Du wirst den Zugang behalten, wenn der neue Besitzer eine Organisation ist und du einer der Besitzer bist.
|
||||
settings.transfer_form_title=Bitte gib die folgenden Informationen ein, um die Operation zu bestätigen:
|
||||
settings.delete_notices_1=- Diese Operation kann <strong>NICHT</strong> rückgängig gemacht werden.
|
||||
settings.delete_notices_2=- Die Operation wird alles, was mit diesem Git-Repository verbunden ist, dauerhaft löschen, inklusive der Daten, Issues, Kommentare und Zugriffsrechte von Mitarbeitern.
|
||||
settings.delete_notices_fork_1=- Wenn dies ein öffentliches Repository ist, werden alle Forks unabhängig nach dem Löschen des Repositorys.
|
||||
settings.delete_notices_fork_2=- Wenn dies ein privates Repository ist, dann werden gleichzeitig alle Forks entfernt.
|
||||
settings.delete_notices_fork_3=Wenn alle Forks nach dem Löschen des Repositorys erhalten bleiben sollen, dann muss zuerst die Sichtbarkeit des Repositorys auf öffentlich gesetzt werden.
|
||||
settings.update_settings_success=Repository-Optionen aktualisiert
|
||||
settings.delete_notices_2=- Die Operation wird alles, was mit dieser Git-Repository verbunden ist, dauerhaft löschen, inklusive der Daten, Issues, Kommentare und Zugriffsrechte von Benutzern.
|
||||
settings.delete_notices_fork_1=- Wenn dies eine öffentliche Repository ist, werden nach der Löschung alle Forks unabhängig.
|
||||
settings.delete_notices_fork_2=- Wenn dies eine private Repository ist, dann werden gleichzeitig alle Forks entfernt.
|
||||
settings.delete_notices_fork_3=Wenn alle Forks erhalten bleiben sollen, dann muss zuerst die Sichtbarkeit der Repository auf öffentlich gesetzt werden.
|
||||
settings.update_settings_success=Repository-Optionen aktualisiert.
|
||||
settings.transfer_owner=Neuer Besitzer
|
||||
settings.make_transfer=übertragen
|
||||
settings.transfer_succeed=Repository-Eigentum wurde erfolgreich übertragen.
|
||||
settings.transfer_succeed=Die Repository wurde erfolgreich übertragen.
|
||||
settings.confirm_delete=Löschen
|
||||
settings.add_collaborator=Mitarbeiter hinzufügen
|
||||
settings.add_collaborator_success=Mitarbeiter hinzugefügt
|
||||
settings.remove_collaborator_success=Mitarbeiter entfernt
|
||||
settings.user_is_org_member=Benutzer ist ein Organisationsmitglied und kann nicht als Mitarbeiter hinzugefügt werden.
|
||||
settings.add_webhook=Webhook hinzufügen
|
||||
settings.hooks_desc=Webhooks erlauben es dir, externe Dienste zu informieren, wenn etwas bestimmtes in deinem Repository passiert. Gogs sendet dann einen POST-Request an alle angegebenen URLs. Erfahre mehr in unserem <a target="_blank" href="%s">Webhooks Guide</a>.
|
||||
settings.hooks_desc=Webhooks erlauben es dir, externe Dienste zu informieren, wenn etwas bestimmtes in deiner Repository passiert. Gogs sendet dann einen POST-Request an alle angegebenen URLs. Erfahre mehr in unserer <a target="_blank" href="%s">Webhooks Guide</a>.
|
||||
settings.webhook_deletion=Webhook entfernen
|
||||
settings.webhook_deletion_desc=Löschen dieses Webhooks wird alle zugehörigen Informationen und den Übertragungsverlauf entfernen. Soll fortgefahren werden?
|
||||
settings.webhook_deletion_desc=Das Löschen dieses Webhooks wird alle zugehörigen Informationen und den Übertragungsverlauf entfernen. Wirklich fortfahren?
|
||||
settings.webhook_deletion_success=Webhook wurde erfolgreich entfernt!
|
||||
settings.webhook.request=Anfrage
|
||||
settings.webhook.response=Rückmeldung
|
||||
@@ -551,7 +574,7 @@ settings.webhook.headers=Kopfzeilen
|
||||
settings.webhook.payload=Nutzdaten
|
||||
settings.webhook.body=Inhalt
|
||||
settings.githooks_desc=Git-Hooks werden von Git selbst bereitgestellt. Du kannst die Dateien der unterstützten Hooks in der Liste unten bearbeiten, um eigene Operationen einzubinden.
|
||||
settings.githook_edit_desc=Wenn ein Hook nicht aktiv ist, wird der Standardinhalt benutzt. Lasse den Inhalt leer, um den Hook zu deaktivieren.
|
||||
settings.githook_edit_desc=Wenn ein Hook inaktiv ist, wird der Standardinhalt benutzt. Lasse den Inhalt leer, um den Hook zu deaktivieren.
|
||||
settings.githook_name=Hook-Name
|
||||
settings.githook_content=Hook-Inhalt
|
||||
settings.update_githook=Aktualisiere Hook
|
||||
@@ -569,30 +592,30 @@ settings.event_choose=Lass mich auswählen, was ich brauche.
|
||||
settings.event_create=Erstellen
|
||||
settings.event_create_desc=Branch/Tag erstellt
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push auf ein Repository
|
||||
settings.event_push_desc=Git push auf eine Repository
|
||||
settings.active=Aktiv
|
||||
settings.active_helper=Ereignisdetails werden ausgeliefert, wenn dieser Webhook ausgelöst wird.
|
||||
settings.add_hook_success=Webhook hinzugefügt
|
||||
settings.update_webhook=Webhook aktualisieren
|
||||
settings.update_hook_success=Webhook aktualisiert
|
||||
settings.update_hook_success=Webhook wurde aktualisiert.
|
||||
settings.delete_webhook=Webhook löschen
|
||||
settings.recent_deliveries=letzte Zustellungen
|
||||
settings.hook_type=Hook Typ
|
||||
settings.add_slack_hook_desc=Füge <a href="%s">Slack</a>-Integration zu deinem Repository hinzu.
|
||||
settings.add_slack_hook_desc=Füge <a href="%s">Slack</a>-Integration zu deiner Repository hinzu.
|
||||
settings.slack_token=Token
|
||||
settings.slack_domain=Domain
|
||||
settings.slack_channel=Kanal
|
||||
settings.deploy_keys=Deploy-Keys
|
||||
settings.add_deploy_key=Deploy-Schlüssel hinzufügen
|
||||
settings.add_deploy_key=Deploy-Key hinzufügen
|
||||
settings.no_deploy_keys=Du hast noch keine Deploy-Schlüssel hinzugefügt.
|
||||
settings.title=Titel
|
||||
settings.deploy_key_content=Inhalt
|
||||
settings.key_been_used=Deploy-Schlüssel wurde verwendet.
|
||||
settings.key_name_used=Deploy-Schlüssel mit diesem Namen existiert bereits.
|
||||
settings.key_name_used=Ein Deploy-Schlüssel mit diesem Namen existiert bereits.
|
||||
settings.add_key_success=Der Deploy-Schlüssel '%s' wurde erfolgreich hinzugefügt!
|
||||
settings.deploy_key_deletion=Deploy-Schlüssel löschen
|
||||
settings.deploy_key_deletion_desc=Nach der Löschung dieses Deploy-Schlüssels werden zugehörige Zugriffe auf das Repository nicht mehr möglich sein. Möchtest du fortfahren?
|
||||
settings.deploy_key_deletion_success=Deploy-Schlüssel wurde erfolgreich gelöscht!
|
||||
settings.deploy_key_deletion=Deploy-Key löschen
|
||||
settings.deploy_key_deletion_desc=Nach der Löschung dieses Deploy-Keys werden zugehörige Zugriffe auf diese Repository nicht mehr möglich sein. Möchtest du wirklich fortfahren?
|
||||
settings.deploy_key_deletion_success=Deploy-Key wurde erfolgreich gelöscht!
|
||||
|
||||
diff.browse_source=Quellcode durchsuchen
|
||||
diff.parent=Ursprung
|
||||
@@ -629,15 +652,15 @@ release.tag_name_already_exist=Ein Release mit diesem Tag existiert bereits.
|
||||
|
||||
[org]
|
||||
org_name_holder=Name der Organisation
|
||||
org_full_name_holder=Vollständiger Name der Organisation
|
||||
org_name_helper=Gute Namen von Organisationen sind kurz und einprägsam.
|
||||
org_email_helper=Die E-Mail-Adresse der Organisation erhält alle Benachrichtigungen und Bestätigungs-E-Mails.
|
||||
create_org=Organisation erstellen
|
||||
repo_updated=Aktualisiert
|
||||
people=Personen
|
||||
invite_someone=Benutzer einladen
|
||||
teams=Teams
|
||||
lower_members=Mitglieder
|
||||
lower_repositories=Repositorys
|
||||
lower_repositories=Repositories
|
||||
create_new_team=Neues Team erstellen
|
||||
org_desc=Beschreibung
|
||||
team_name=Teamname
|
||||
@@ -654,17 +677,17 @@ settings.options=Optionen
|
||||
settings.full_name=Vollständiger Name
|
||||
settings.website=Webseite
|
||||
settings.location=Standort
|
||||
settings.update_settings=Aktualisierungseinstellungen
|
||||
settings.change_orgname=Organisationsname geändert
|
||||
settings.change_orgname_desc=Organisationsname wurde geändert, möchtest du fortfahren? Dies beeinträchtigt sämtliche Links, die diese Organisation betreffen.
|
||||
settings.update_settings=Einstellungen speichern
|
||||
settings.update_setting_success=Organisationseinstellungen aktualisiert
|
||||
settings.change_orgname_prompt=Diese Änderung wird sich auf die Linkbezüge zur Organisation auswirken.
|
||||
settings.update_avatar_success=Avatareinstellung für die Organisation wurde erfolgreich aktualisiert.
|
||||
settings.delete=Organisation löschen
|
||||
settings.delete_account=Diese Organisation löschen
|
||||
settings.delete_prompt=Die Organisation wird dauerhaft gelöscht. Dies kann <strong>NICHT</strong> rückgängig gemacht werden!
|
||||
settings.confirm_delete_account=Löschen
|
||||
settings.delete_org_title=Organisation löschen
|
||||
settings.delete_org_desc=Diese Organisation wird dauerhaft gelöscht, möchtest du fortfahren?
|
||||
settings.hooks_desc=Füge Webhooks hinzu, die für <strong>alle</strong> Repositorys dieser Organisation ausgelöst werden.
|
||||
settings.hooks_desc=Füge Webhooks hinzu, die für <strong>alle</strong> Repositories dieser Organisation ausgelöst werden.
|
||||
|
||||
members.public=Öffentlich
|
||||
members.public_helper=Privat machen
|
||||
@@ -683,7 +706,7 @@ teams.leave=Verlassen
|
||||
teams.read_access=Lesezugriff
|
||||
teams.read_access_helper=Dieses Team wird Repositorys einsehen und klonen können.
|
||||
teams.write_access=Schreibzugriff
|
||||
teams.write_access_helper=Dieses Team wird die Repositorys einsehen und in sie hinein pushen können.
|
||||
teams.write_access_helper=Dieses Team wird die Repositories einsehen und Push Operationen ausführen können.
|
||||
teams.admin_access=Adminzugriff
|
||||
teams.admin_access_helper=Dieses Team wird pull- und push-Rechte für die Repositorys haben und Mitarbeiter einladen können.
|
||||
teams.no_desc=Dieses Team hat keine Beschreibung
|
||||
@@ -696,13 +719,13 @@ teams.add_team_member=Teammitglied hinzufügen
|
||||
teams.delete_team_title=Team löschen
|
||||
teams.delete_team_desc=Dieses Team wird gelöscht, möchtest du fortfahren? Mitglieder dieses Teams verlieren möglicherweise ihren Zugang zu einigen Repositories.
|
||||
teams.delete_team_success=Team gelöscht
|
||||
teams.read_permission_desc=Dieses Team hat <strong>Lesezugriff</strong>: Mitglieder können Team-Repositories einsehen und klonen.
|
||||
teams.write_permission_desc=Dieses Team erlaubt <strong>Schreibzugriff</strong>: Mitglieder können Team-Repositorys einsehen und hinein pushen.
|
||||
teams.read_permission_desc=Dieses Team erlaubt <strong>Lesezugriff</strong>: Mitglieder können Team-Repositories einsehen und klonen.
|
||||
teams.write_permission_desc=Dieses Team erlaubt <strong>Schreibzugriff</strong>: Mitglieder können Team-Repositories einsehen und Push Operationen ausführen.
|
||||
teams.admin_permission_desc=Diese Team erlaubt <strong>Adminzugriff</strong>: Mitglieder dieses Teams können pullen, pushen und dem Team Mitarbeiter hinzufügen.
|
||||
teams.repositories=Team-Repositorys
|
||||
teams.add_team_repository=Team-Repository hinzufügen
|
||||
teams.remove_repo=Entfernen
|
||||
teams.add_nonexistent_repo=Das Repository, das du hinzufügen willst, existiert nicht. Bitte erstelle es zuerst.
|
||||
teams.add_nonexistent_repo=Die Repository, welche du hinzufügen möchtest, existiert nicht. Bitte erstelle diese zuerst.
|
||||
|
||||
[admin]
|
||||
dashboard=Dashboard
|
||||
@@ -713,8 +736,9 @@ authentication=Authentifizierung
|
||||
config=Konfiguration
|
||||
notices=System-Mitteilungen
|
||||
monitor=Monitoring
|
||||
prev=zurück
|
||||
next=vor
|
||||
first_page=Erste
|
||||
last_page=Letzte
|
||||
total=Total: %d
|
||||
|
||||
dashboard.statistic=Statistik
|
||||
dashboard.operations=Operationen
|
||||
@@ -728,11 +752,11 @@ dashboard.clean_unbind_oauth_success=Alle ungebundenen OAuth-Tokens wurden gelö
|
||||
dashboard.delete_inactivate_accounts=inaktive Konten löschen
|
||||
dashboard.delete_inactivate_accounts_success=Alle inaktiven Konten wurden erfolgreich gelöscht.
|
||||
dashboard.delete_repo_archives=Alle Repository-Archive löschen
|
||||
dashboard.delete_repo_archives_success=Alle Repositoriy-Archive wurden gelöscht.
|
||||
dashboard.delete_repo_archives_success=Alle Repository-Archive wurden gelöscht.
|
||||
dashboard.git_gc_repos=Führe Garbage Collection auf Repositories aus
|
||||
dashboard.git_gc_repos_success=Garbage Collection wurde auf allen Repositories erfolgreich ausgeführt.
|
||||
dashboard.resync_all_sshkeys=Überschreibe '.ssh/authorized_keys' Datei (Warnung: Keys, die nicht zu Gogs gehören werden verloren gehen)
|
||||
dashboard.resync_all_sshkeys_success=Alle öffentlichen Keys sind erfolgreich neu geschrieben worden.
|
||||
dashboard.resync_all_sshkeys=Überschreibe '.ssh/authorized_keys' Datei (Warnung: Keys, die nicht zu Gogs gehören gehen verloren)
|
||||
dashboard.resync_all_sshkeys_success=Alle öffentlichen Keys wurden erfolgreich neu geschrieben.
|
||||
dashboard.resync_all_update_hooks=Überschreibe alle Hooks der Repositories (benötigt, wenn sich der Pfad in der Konfiguration ändert)
|
||||
dashboard.resync_all_update_hooks_success=Die Hooks aller Repositories sind erfolgreich neu geschrieben worden.
|
||||
|
||||
@@ -761,8 +785,8 @@ dashboard.gc_metadata_obtained=erhaltene GC-Metadata
|
||||
dashboard.other_system_allocation_obtained=andere erhaltene System-Allokatoren
|
||||
dashboard.next_gc_recycle=nächster GC-Zyklus
|
||||
dashboard.last_gc_time=seit letztem GC-Zyklus
|
||||
dashboard.total_gc_time=gesammte GC-Zeit
|
||||
dashboard.total_gc_pause=gesammte GC-Pause
|
||||
dashboard.total_gc_time=gesamte GC-Zeit
|
||||
dashboard.total_gc_pause=gesamte GC-Pause
|
||||
dashboard.last_gc_pause=letzte GC-Pause
|
||||
dashboard.gc_times=GC-Takt
|
||||
|
||||
@@ -773,19 +797,24 @@ users.activated=Aktiviert
|
||||
users.admin=Admin
|
||||
users.repos=Repositorys
|
||||
users.created=Erzeugt
|
||||
users.send_register_notify=Sende Registrierungsbenachrichtigung an Benutzer
|
||||
users.new_success=Der neue Account '%s' wurde erfolgreich erstellt.
|
||||
users.edit=Bearbeiten
|
||||
users.auth_source=Auth-Quelle
|
||||
users.auth_source=Authentifizierungsquelle
|
||||
users.local=Lokal
|
||||
users.auth_login_name=Auth-Login-Name
|
||||
users.update_profile_success=Kontoprofil aktualisiert
|
||||
users.auth_login_name=Authentifizierung-Loginnname
|
||||
users.password_helper=Leer lassen um es unverändert zu lassen.
|
||||
users.update_profile_success=Kontoprofil wurde erfolgreich aktualisiert.
|
||||
users.edit_account=Konto bearbeiten
|
||||
users.is_activated=Dieses Konto ist aktiviert
|
||||
users.is_admin=Dieses Konto hat Administratorrechte
|
||||
users.allow_git_hook=Dieses Konto ist berechtigt, Git-Hooks zu erstellen
|
||||
users.allow_import_local=Dieses Konto ist berechtigt, lokale Repositorys zu importieren
|
||||
users.update_profile=Kontoprofil aktualisieren
|
||||
users.delete_account=Dieses Konto löschen
|
||||
users.still_own_repo=Dieses Konto besitzt noch Repositories. Diese müssen zuerst gelöscht oder übertragen werden.
|
||||
users.still_has_org=Dieses Konto ist noch Mitglied einer Organisation, bitte entferne diese Mitgliedschaft zuerst.
|
||||
users.deletion_success=Das Konto wurde erfolgreich gelöscht!
|
||||
|
||||
orgs.org_manage_panel=Organisationenverwaltung
|
||||
orgs.name=Name
|
||||
@@ -800,8 +829,8 @@ repos.watches=Beobachtungen
|
||||
repos.stars=Markierungen
|
||||
repos.issues=Issues
|
||||
|
||||
auths.auth_manage_panel=Authentifizierung
|
||||
auths.new=Neue Authentifizierungsquelle hinzufügen
|
||||
auths.auth_manage_panel=Verwaltungspanel für die Authentifizierung
|
||||
auths.new=Neue Quelle hinzufügen
|
||||
auths.name=Name
|
||||
auths.type=Typ
|
||||
auths.enabled=aktiviert
|
||||
@@ -813,16 +842,20 @@ auths.host=Host
|
||||
auths.port=Port
|
||||
auths.bind_dn=DN binden
|
||||
auths.bind_password=Passwort binden
|
||||
auths.bind_password_helper=Warnung: Das Passwort wird im Klartext gespeichert. Benutze keinen Account mit hohen Zugriffsrechten.
|
||||
auths.user_base=Benutzer-Such-Basis
|
||||
auths.user_dn=Benutzer DN
|
||||
auths.attribute_name=Vorname Attribut
|
||||
auths.attribute_surname=Nachname Attribut
|
||||
auths.attribute_mail=E-Mail Attribut
|
||||
auths.filter=Benutzernamen Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=SMTP-Authentifizierungstyp
|
||||
auths.smtp_auth=SMTP Authentifizierungstyp
|
||||
auths.smtphost=SMTP-Host
|
||||
auths.smtpport=SMTP-Port
|
||||
auths.allowed_domains=Erlaubte Domains
|
||||
auths.allowed_domains_helper=Leer lassen für keine Einschränkungen. Mehrere Domains können durch Komma "," getrennt werden.
|
||||
auths.enable_tls=TLS-Verschlüsselung aktivieren
|
||||
auths.skip_tls_verify=TLS-Prüfung überspringen
|
||||
auths.pam_service_name=PAM Dienstname
|
||||
@@ -830,11 +863,13 @@ auths.enable_auto_register=Automatische Registrierung aktivieren
|
||||
auths.tips=Tipps
|
||||
auths.edit=Authentifizierungseinstellungen bearbeiten
|
||||
auths.activated=Diese Authentifizierung ist aktiviert
|
||||
auths.update_success=Authentifizierungseinstellungen aktualisiert
|
||||
auths.new_success=Neue Authentifizierung '%s' wurde erfolgreich hinzugefügt.
|
||||
auths.update_success=Die Authentifizierungseinstellungen wurden erfolgreich aktualisiert.
|
||||
auths.update=Authentifizierungseinstellungen aktualisieren
|
||||
auths.delete=Authentifizierung löschen
|
||||
auths.delete_auth_title=Authentifizierungsquelle löschen
|
||||
auths.delete_auth_desc=Diese Authentifizierungsquelle wird gelöscht, möchtest du fortfahren?
|
||||
auths.delete=Diese Authentifizierung löschen
|
||||
auths.delete_auth_title=Löschen der Authentifizierung
|
||||
auths.delete_auth_desc=Diese Authentifizierung wird gelöscht, möchtest du fortfahren?
|
||||
auths.deletion_success=Authentifizierung wurde erfolgreich entfernt!
|
||||
|
||||
config.server_config=Server-Konfiguration
|
||||
config.app_name=Anwendungsname
|
||||
@@ -858,14 +893,16 @@ config.db_user=Benutzer
|
||||
config.db_ssl_mode=SSL-Modus
|
||||
config.db_ssl_mode_helper=(nur für "postgres")
|
||||
config.db_path=Verzeichnis
|
||||
config.db_path_helper=(nur für "sqlite3")
|
||||
config.db_path_helper=(für "sqlite3" und "tidb")
|
||||
config.service_config=Service-Einstellungen
|
||||
config.register_email_confirm=E-Mail-Bestätigung bei Registrierung
|
||||
config.disable_register=Registrierung deaktivieren
|
||||
config.show_registration_button=Zeige die Schaltfläche Registrieren
|
||||
config.require_sign_in_view=Ansehen erfordert Registrierung
|
||||
config.mail_notify=E-Mail-Benachrichtigung
|
||||
config.enable_cache_avatar=Avatar-Cache aktivieren
|
||||
config.mail_notify=E-Mail-Benachrichtigung
|
||||
config.disable_key_size_check=Prüfung der Mindestschlüssellänge deaktiveren
|
||||
config.enable_captcha=Captcha aktivieren
|
||||
config.active_code_lives=Aktivierungscode Lebensdauer
|
||||
config.reset_password_code_lives=Passwortcode Lebensdauer
|
||||
config.webhook_config=Webhook-Einstellungen
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard = Dashboard
|
||||
explore = Explore
|
||||
help = Help
|
||||
sign_in = Sign In
|
||||
social_sign_in = Social Sign In: 2nd Step <small>associate account</small>
|
||||
sign_out = Sign Out
|
||||
sign_up = Sign Up
|
||||
register = Register
|
||||
@@ -14,7 +13,7 @@ version = Version
|
||||
page = Page
|
||||
template = Template
|
||||
language = Language
|
||||
create_new = Create new...
|
||||
create_new = Create...
|
||||
user_profile_and_more = User profile and more
|
||||
signed_in_as = Signed in as
|
||||
|
||||
@@ -54,7 +53,8 @@ code = Code
|
||||
[install]
|
||||
install = Installation
|
||||
title = Install Steps For First-time Run
|
||||
requite_db_desc = Gogs requires MySQL, PostgreSQL or SQLite3.
|
||||
docker_helper = If you're running Gogs inside Docker, please read <a target="_blank" href="%s">Guidelines</a> carefully before you change anything in this page!
|
||||
requite_db_desc = Gogs requires MySQL, PostgreSQL, SQLite3 or TiDB.
|
||||
db_title = Database Settings
|
||||
db_type = Database Type
|
||||
host = Host
|
||||
@@ -64,8 +64,11 @@ db_name = Database Name
|
||||
db_helper = Please use INNODB engine with utf8_general_ci charset for MySQL.
|
||||
ssl_mode = SSL Mode
|
||||
path = Path
|
||||
sqlite_helper = The file path of SQLite3 database.
|
||||
err_empty_sqlite_path = SQLite3 database path cannot be empty.
|
||||
sqlite_helper = The file path of SQLite3 or TiDB database.
|
||||
err_empty_db_path = SQLite3 or TiDB database path cannot be empty.
|
||||
err_invalid_tidb_name = TiDB database name does not allow characters "." and "-".
|
||||
no_admin_and_disable_registration = You cannot disable registration without creating an admin account.
|
||||
err_empty_admin_password = Admin password cannot be empty.
|
||||
|
||||
general_title = Application General Settings
|
||||
app_name = Application Name
|
||||
@@ -99,6 +102,8 @@ disable_gravatar = Disable Gravatar Service
|
||||
disable_gravatar_popup = Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_registration = Disable Self-registration
|
||||
disable_registration_popup = Disable user self-registration, only admin can create accounts.
|
||||
enable_captcha = Enable Captcha
|
||||
enable_captcha_popup = Require validate captcha for user self-registration.
|
||||
require_sign_in_view = Enable Require Sign In to View Pages
|
||||
require_sign_in_view_popup = Only signed in users can view pages, visitors will only be able to see sign in/up pages.
|
||||
admin_setting_desc = You do not have to create an admin account right now, user whoever ID=1 will gain admin access automatically.
|
||||
@@ -106,7 +111,7 @@ admin_title = Admin Account Settings
|
||||
admin_name = Username
|
||||
admin_password = Password
|
||||
confirm_password = Confirm Password
|
||||
admin_email = E-mail
|
||||
admin_email = Admin E-mail
|
||||
install_gogs = Install Gogs
|
||||
test_git_failed = Fail to test 'git' command: %v
|
||||
sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from %s, NOT the gobuild version.
|
||||
@@ -143,7 +148,6 @@ forgot_password= Forgot Password
|
||||
forget_password = Forgot password?
|
||||
sign_up_now = Need an account? Sign up now.
|
||||
confirmation_mail_sent_prompt = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
|
||||
sign_in_email = Sign in to your e-mail
|
||||
active_your_account = Activate Your Account
|
||||
resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
|
||||
has_unconfirmed_mail = Hi %s, you have an unconfirmed e-mail address (<b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
|
||||
@@ -155,6 +159,12 @@ invalid_code = Sorry, your confirmation code has expired or not valid.
|
||||
reset_password_helper = Click here to reset your password
|
||||
password_too_short = Password length cannot be less then 6.
|
||||
|
||||
[mail]
|
||||
activate_account = Please activate your account
|
||||
activate_email = Verify your e-mail address
|
||||
reset_password = Reset your password
|
||||
register_success = Register success, Welcome
|
||||
|
||||
[modal]
|
||||
yes = Yes
|
||||
no = No
|
||||
@@ -181,6 +191,7 @@ min_size_error = ` must contain at least %s characters.`
|
||||
max_size_error = ` must contain at most %s characters.`
|
||||
email_error = ` is not a valid e-mail address.`
|
||||
url_error = ` is not a valid URL.`
|
||||
include_error = ` must contain substring '%s'.`
|
||||
unknown_error = Unknown error:
|
||||
captcha_incorrect = Captcha didn't match.
|
||||
password_not_match = Password and confirm password are not same.
|
||||
@@ -241,12 +252,12 @@ location = Location
|
||||
update_profile = Update Profile
|
||||
update_profile_success = Your profile has been updated successfully.
|
||||
change_username = Username Changed
|
||||
change_username_desc = You changed your username. This will affect the way how links relate to your account. Do you want to continue?
|
||||
change_username_prompt = This change will affect the way how links relate to your account.
|
||||
continue = Continue
|
||||
cancel = Cancel
|
||||
|
||||
enable_custom_avatar = Enable Custom Avatar
|
||||
enable_custom_avatar_helper = Enable this to disable fetch from Gravatar
|
||||
enable_custom_avatar_helper = Disable fetch from Gravatar
|
||||
choose_new_avatar = Choose new avatar
|
||||
update_avatar = Update Avatar Setting
|
||||
uploaded_avatar_not_a_image = Uploaded file is not a image.
|
||||
@@ -256,6 +267,7 @@ update_avatar_success = Your avatar setting has been updated successfully.
|
||||
change_password = Change Password
|
||||
old_password = Current Password
|
||||
new_password = New Password
|
||||
retype_new_password = Retype New Password
|
||||
password_incorrect = Current password is not correct.
|
||||
change_password_success = Your password was successfully changed. You can now sign using this new password.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc = Your primary e-mail address will be used for notifications and othe
|
||||
primary = Primary
|
||||
primary_email = Set as primary
|
||||
delete_email = Delete
|
||||
email_deletion = E-mail Deletion
|
||||
email_deletion_desc = Delete this e-mail address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success = E-mail has been deleted successfully!
|
||||
add_new_email = Add new e-mail address
|
||||
add_email = Add e-mail
|
||||
add_email_confirmation_sent = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the confirmation process.
|
||||
add_email_confirmation_sent = A new confirmation e-mail has been sent to '%s', please check your inbox within the next %d hours to complete the confirmation process.
|
||||
add_email_success = Your new E-mail address was successfully added.
|
||||
|
||||
manage_ssh_keys = Manage SSH Keys
|
||||
@@ -319,6 +334,7 @@ repo_name = Repository Name
|
||||
repo_name_helper = A good repository name is usually composed of short, memorable and unique keywords.
|
||||
visibility = Visibility
|
||||
visiblity_helper = This repository is <span class="ui red text">Private</span>
|
||||
visiblity_helper_forced = Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper = (Change of this value will affect all forks)
|
||||
fork_repo = Fork Repository
|
||||
fork_from = Fork From
|
||||
@@ -330,7 +346,7 @@ license = License
|
||||
license_helper = Select a license file
|
||||
readme = Readme
|
||||
readme_helper = Select a readme template
|
||||
auto_init = Initialize this repository selected files and template
|
||||
auto_init = Initialize this repository with selected files and template
|
||||
create_repo = Create Repository
|
||||
default_branch = Default Branch
|
||||
mirror_interval = Mirror Interval (hour)
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper = This repository will be a <span class="text blue">mirror</
|
||||
migrate_repo = Migrate Repository
|
||||
migrate.clone_address = Clone Address
|
||||
migrate.clone_address_desc = This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.permission_denied = You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path = Invalid local path, it does not exist or not a directory.
|
||||
|
||||
forked_from = forked from
|
||||
fork_from_self = You cannot fork repository you already owned!
|
||||
copy_link = Copy
|
||||
copy_link_success = Copied!
|
||||
copy_link_error = Press ⌘-C or Ctrl-C to copy
|
||||
click_to_copy = Copy to clipboard
|
||||
copied = Copied OK
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide = Quick Guide
|
||||
clone_this_repo = Clone this repository
|
||||
create_new_repo_command = Create a new repository on the command line
|
||||
push_exist_repo = Push an existing repository from the command line
|
||||
repo_is_empty = This repository is empty, please come back later!
|
||||
|
||||
|
||||
branch = Branch
|
||||
tree = Tree
|
||||
@@ -435,9 +456,9 @@ issues.num_comments = %d comments
|
||||
issues.commented_at = `commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content = There is no content yet.
|
||||
issues.close_issue = Close
|
||||
issues.close_comment_issue = Close and comment
|
||||
issues.close_comment_issue = Comment and close
|
||||
issues.reopen_issue = Reopen
|
||||
issues.reopen_comment_issue = Reopen and comment
|
||||
issues.reopen_comment_issue = Comment and reopen
|
||||
issues.create_comment = Comment
|
||||
issues.closed_at = `closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at = `reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
@@ -479,10 +500,12 @@ pulls.reopen_to_merge = Please reopen this pull request to perform merge operati
|
||||
pulls.merged = Merged
|
||||
pulls.has_merged = This pull request has been merged successfully!
|
||||
pulls.data_broken = Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking = The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc = You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc = You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper = Please use command line tool to solve it.
|
||||
pulls.merge_pull_request = Merge Pull Request
|
||||
pulls.open_unmerged_pull_exists = `You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new = New Milestone
|
||||
milestones.open_tab = %d Open
|
||||
@@ -497,7 +520,7 @@ milestones.title = Title
|
||||
milestones.desc = Description
|
||||
milestones.due_date = Due Date (optional)
|
||||
milestones.clear = Clear
|
||||
milestones.invalid_due_date_format = Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.invalid_due_date_format = Due date format is invalid, must be 'yyyy-mm-dd'.
|
||||
milestones.create_success = Milestone '%s' has been created successfully!
|
||||
milestones.edit = Edit Milestone
|
||||
milestones.edit_subheader = Use better description for milestones so people won't be confused.
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist = Release with this tag name has already existed.
|
||||
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
org_full_name_holder = Organization Full Name
|
||||
org_name_helper = Great organization names are short and memorable.
|
||||
org_email_helper = Organization's E-mail receives all notifications and confirmations.
|
||||
create_org = Create Organization
|
||||
repo_updated = Updated
|
||||
people = People
|
||||
@@ -655,9 +678,9 @@ settings.full_name = Full Name
|
||||
settings.website = Website
|
||||
settings.location = Location
|
||||
settings.update_settings = Update Settings
|
||||
settings.change_orgname = Organization Name Changed
|
||||
settings.change_orgname_desc = Organization name has been changed. This will affect how links relate to the organization. Do you want to continue?
|
||||
settings.update_setting_success = Organization settings were successfully updated.
|
||||
settings.update_setting_success = Organization settings has been updated successfully.
|
||||
settings.change_orgname_prompt = This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success = Organization avatar setting has been updated successfully.
|
||||
settings.delete = Delete Organization
|
||||
settings.delete_account = Delete This Organization
|
||||
settings.delete_prompt = The organization will be permanently removed, and this <strong>CANNOT</strong> be undone!
|
||||
@@ -713,8 +736,9 @@ authentication = Authentications
|
||||
config = Configuration
|
||||
notices = System Notices
|
||||
monitor = Monitoring
|
||||
prev = Prev.
|
||||
next = Next
|
||||
first_page = First
|
||||
last_page = Last
|
||||
total = Total: %d
|
||||
|
||||
dashboard.statistic = Statistic
|
||||
dashboard.operations = Operations
|
||||
@@ -773,19 +797,24 @@ users.activated = Activated
|
||||
users.admin = Admin
|
||||
users.repos = Repos
|
||||
users.created = Created
|
||||
users.send_register_notify = Send Registration Notification To User
|
||||
users.new_success = New account '%s' has been created successfully.
|
||||
users.edit = Edit
|
||||
users.auth_source = Authorization Source
|
||||
users.auth_source = Authentication Source
|
||||
users.local = Local
|
||||
users.auth_login_name = Authorization Login Name
|
||||
users.auth_login_name = Authentication Login Name
|
||||
users.password_helper = Leave it empty to remain unchanged.
|
||||
users.update_profile_success = Account profile has been updated successfully.
|
||||
users.edit_account = Edit Account
|
||||
users.is_activated = This account is activated
|
||||
users.is_admin = This account has administrator permissions
|
||||
users.allow_git_hook = This account has permissions to create Git hooks
|
||||
users.allow_import_local = This account has permissions to import local repositories
|
||||
users.update_profile = Update Account Profile
|
||||
users.delete_account = Delete This Account
|
||||
users.still_own_repo = This account still has ownership over at least one repository, you have to delete or transfer them first.
|
||||
users.still_has_org = This account still has membership in at least one organization, you have to leave or delete the organizations first.
|
||||
users.deletion_success = Account has been deleted successfully!
|
||||
|
||||
orgs.org_manage_panel = Organization Manage Panel
|
||||
orgs.name = Name
|
||||
@@ -800,41 +829,47 @@ repos.watches = Watches
|
||||
repos.stars = Stars
|
||||
repos.issues = Issues
|
||||
|
||||
auths.auth_manage_panel = Authorization Manage Panel
|
||||
auths.new = Add New Authorization Source
|
||||
auths.auth_manage_panel = Authentication Manage Panel
|
||||
auths.new = Add New Source
|
||||
auths.name = Name
|
||||
auths.type = Type
|
||||
auths.enabled = Enabled
|
||||
auths.updated = Updated
|
||||
auths.auth_type = Authorization Type
|
||||
auths.auth_name = Authorization Name
|
||||
auths.auth_type = Authentication Type
|
||||
auths.auth_name = Authentication Name
|
||||
auths.domain = Domain
|
||||
auths.host = Host
|
||||
auths.port = Port
|
||||
auths.bind_dn = Bind DN
|
||||
auths.bind_password = Bind Password
|
||||
auths.bind_password_helper = Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base = User Search Base
|
||||
auths.user_dn = User DN
|
||||
auths.attribute_name = First name attribute
|
||||
auths.attribute_surname = Surname attribute
|
||||
auths.attribute_mail = E-mail attribute
|
||||
auths.filter = User Filter
|
||||
auths.admin_filter = Admin Filter
|
||||
auths.ms_ad_sa = Ms Ad SA
|
||||
auths.smtp_auth = SMTP Authorization Type
|
||||
auths.smtp_auth = SMTP Authentication Type
|
||||
auths.smtphost = SMTP Host
|
||||
auths.smtpport = SMTP Port
|
||||
auths.allowed_domains = Allowed Domains
|
||||
auths.allowed_domains_helper = Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
|
||||
auths.enable_tls = Enable TLS Encryption
|
||||
auths.skip_tls_verify = Skip TLS Verify
|
||||
auths.pam_service_name = PAM Service Name
|
||||
auths.enable_auto_register = Enable Auto Registration
|
||||
auths.tips = Tips
|
||||
auths.edit = Edit Authorization Setting
|
||||
auths.activated = This authentication has activated
|
||||
auths.update_success = Authorization setting has been updated successfully.
|
||||
auths.update = Update Authorization Setting
|
||||
auths.delete = Delete This Authorization
|
||||
auths.delete_auth_title = Authorization Deletion
|
||||
auths.delete_auth_desc = This authorization is going to be deleted, do you want to continue?
|
||||
auths.edit = Edit Authentication Setting
|
||||
auths.activated = This authentication is activate
|
||||
auths.new_success = New authentication '%s' has been added successfully.
|
||||
auths.update_success = Authentication setting has been updated successfully.
|
||||
auths.update = Update Authentication Setting
|
||||
auths.delete = Delete This Authentication
|
||||
auths.delete_auth_title = Authentication Deletion
|
||||
auths.delete_auth_desc = This authentication is going to be deleted, do you want to continue?
|
||||
auths.deletion_success = Authentication has been deleted successfully!
|
||||
|
||||
config.server_config = Server Configuration
|
||||
config.app_name = Application Name
|
||||
@@ -858,14 +893,16 @@ config.db_user = User
|
||||
config.db_ssl_mode = SSL Mode
|
||||
config.db_ssl_mode_helper = (for "postgres" only)
|
||||
config.db_path = Path
|
||||
config.db_path_helper = (for "sqlite3" only)
|
||||
config.db_path_helper = (for "sqlite3" and "tidb")
|
||||
config.service_config = Service Configuration
|
||||
config.register_email_confirm = Require E-mail Confirmation
|
||||
config.disable_register = Disable Registration
|
||||
config.show_registration_button = Show Register Button
|
||||
config.require_sign_in_view = Require Sign In View
|
||||
config.mail_notify = Mail Notification
|
||||
config.enable_cache_avatar = Enable Cache Avatar
|
||||
config.mail_notify = Mail Notification
|
||||
config.disable_key_size_check = Disable Minimum Key Size Check
|
||||
config.enable_captcha = Enable Captcha
|
||||
config.active_code_lives = Active Code Lives
|
||||
config.reset_password_code_lives = Reset Password Code Lives
|
||||
config.webhook_config = Webhook Configuration
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
app_desc=Un servicio de Git auto alojado y sin complicaciones
|
||||
|
||||
home=Incio
|
||||
home=Inicio
|
||||
dashboard=Panel de control
|
||||
explore=Explorar
|
||||
help=Ayuda
|
||||
sign_in=Iniciar sesión
|
||||
social_sign_in=Inicio de sesión social: 2° paso <small>cuenta de asociado</small>
|
||||
sign_out=Cerrar sesión
|
||||
sign_up=Suscripción
|
||||
register=Registro
|
||||
website=Pagina Web
|
||||
website=Página Web
|
||||
version=Versión
|
||||
page=Página
|
||||
template=Plantilla
|
||||
language=Lenguaje
|
||||
create_new=Crear nuevo...
|
||||
language=Idioma
|
||||
create_new=Crear...
|
||||
user_profile_and_more=Perfil de usuario y más
|
||||
signed_in_as=Identificado como
|
||||
|
||||
@@ -26,7 +25,7 @@ captcha=Captcha
|
||||
|
||||
repository=Repositorio
|
||||
organization=Organización
|
||||
mirror=Espejo
|
||||
mirror=Mirror
|
||||
new_repo=Nuevo repositorio
|
||||
new_migrate=Nueva Migración
|
||||
new_fork=Nuevo Fork del Repositorio
|
||||
@@ -38,9 +37,9 @@ settings=Configuraciones
|
||||
your_profile=Tu perfil
|
||||
your_settings=Tu configuración
|
||||
|
||||
news_feed=entrada de noticias
|
||||
pull_requests=Solicitudes de retiro
|
||||
issues=Publicaciones
|
||||
news_feed=Feed de noticias
|
||||
pull_requests=Pull Requests
|
||||
issues=Incidencias
|
||||
|
||||
cancel=Cancelar
|
||||
|
||||
@@ -54,25 +53,29 @@ code=Código
|
||||
[install]
|
||||
install=Instalación
|
||||
title=Pasos de la instalación por primera vez
|
||||
requite_db_desc=Gogs necesita MySQL, PostgreSQL o SQLite3.
|
||||
docker_helper=Si está ejecutando Gogs usando Docker, por favor lea <a target="_blank" href="%s"> estas pautas</a> antes de cambiar nada en esta página!
|
||||
requite_db_desc=Gogs requiere una base de datos MySQL, PostgreSQL, SQLite3 o TiDB.
|
||||
db_title=Configuración de base de datos
|
||||
db_type=Tipo de base de datos
|
||||
host=Anfitrión
|
||||
host=Host
|
||||
user=Usuario
|
||||
password=Contraseña
|
||||
db_name=Nombre de la base de datos
|
||||
db_helper=Por favor utilice el motor INNODB con la configuración de caracteres utf8_general_ci para MySQL.
|
||||
ssl_mode=Modo SSL
|
||||
path=Ruta
|
||||
sqlite_helper=Ruta del archivo de la base de datos de SQLite3.
|
||||
err_empty_sqlite_path=La ruta de la base de datos SQLite3 no puede estar vacía.
|
||||
sqlite_helper=Ruta del archivo con la base de datos SQLite3 o TiDB.
|
||||
err_empty_db_path=La ruta a la base de datos SQLite3 o TiDB no puede estar vacía.
|
||||
err_invalid_tidb_name=El nombre de la base de datos TiDB no puede contener los caracteres "." ni "-".
|
||||
no_admin_and_disable_registration=No puede deshabilitar el registro sin crear una cuenta de administrador.
|
||||
err_empty_admin_password=La contraseña de administrador no puede estar vacía.
|
||||
|
||||
general_title=Configuración General de Gogs
|
||||
app_name=Nombre de la Aplicación
|
||||
app_name_helper=Pon aquí el nombre de tu organización, ¡alto y claro!
|
||||
repo_path=Ruta del repositorio de Raiz (Root)
|
||||
repo_path_helper=Todos los repositorios remotos de Git se guardarán en este directorio.
|
||||
run_user=Abrir el usuario
|
||||
run_user=Ejecutar como Usuario
|
||||
run_user_helper=El usuario necesita tener acceso a la Ruta Raíz del Repositorio y ejecutar Gogs.
|
||||
domain=Dominio
|
||||
domain_helper=Esto afecta a las URLs para clonar por SSH.
|
||||
@@ -96,9 +99,11 @@ server_service_title=Configuración de Servidor y Otros Servicios
|
||||
offline_mode=Activar el modo Sin Conexión
|
||||
offline_mode_popup=Desactivar el CDN incluso en el modo de producción, todos los recursos se servirán localmente.
|
||||
disable_gravatar=Desactivar el Servicio Gravatar
|
||||
disable_gravatar_popup=Desactivar Gravatar y cualquier fuente personalizada, todos los avatares deben ser cargados por los usuarios o el avatar por defecto.
|
||||
disable_gravatar_popup=Desactivar Gravatar y cualquier otra fuente personalizada. Todos los avatares deben ser cargados por los usuarios o en su defecto se mostrará el avatar predeterminado.
|
||||
disable_registration=Desactivar Auto-Registro
|
||||
disable_registration_popup=Desactivar auto-registro del usuario, solo el administrador podrá crear cuentas nuevas.
|
||||
enable_captcha=Activar la Captcha
|
||||
enable_captcha_popup=Requiere validar la captcha para el auto-registro de usuario.
|
||||
require_sign_in_view=Activar el Inicio de Sesión obligatorio para Ver Páginas
|
||||
require_sign_in_view_popup=Solo los usuarios logados pueden ver páginas, los visitantes anónimos solo podrán ver las páginas de login/registro.
|
||||
admin_setting_desc=No es necesario crear una cuenta de administrador ahora mismo, el usuario que tenga ID=1 obtendrá privilegios de administrador automáticamente.
|
||||
@@ -106,7 +111,7 @@ admin_title=Configuración de la Cuenta de Administrador
|
||||
admin_name=Nombre de usuario
|
||||
admin_password=Contraseña
|
||||
confirm_password=Confirmar Contraseña
|
||||
admin_email=Correo electrónico
|
||||
admin_email=Admin E-mail
|
||||
install_gogs=Instalar Gogs
|
||||
test_git_failed=Fallo al probar el comando 'git': %v
|
||||
sqlite3_not_available=Tu versión no soporta SQLite3, por favor descarga el binario oficial desde %s, NO la versión de gobuild.
|
||||
@@ -127,7 +132,7 @@ my_orgs=Mis Organizaciones
|
||||
my_mirrors=Mis Mirrors
|
||||
view_home=Ver %s
|
||||
|
||||
issues.in_your_repos=En sus repositorios
|
||||
issues.in_your_repos=En tus repositorios
|
||||
|
||||
[explore]
|
||||
repos=Repositorios
|
||||
@@ -143,7 +148,6 @@ forgot_password=He olvidado mi contraseña
|
||||
forget_password=¿Has olvidado tu contraseña?
|
||||
sign_up_now=¿Necesitas una cuenta? Regístrate ahora.
|
||||
confirmation_mail_sent_prompt=Un nuevo correo de confirmación se ha enviado a <b>%s</b>. Por favor, comprueba tu bandeja de entrada en las siguientes %d horas para completar el proceso de registro.
|
||||
sign_in_email=Inicia sesión con tu correo electrónico
|
||||
active_your_account=Activa tu cuenta
|
||||
resent_limit_prompt=Lo sentimos, estás solicitando el reenvío del mail de activación con demasiada frecuencia. Por favor, espera 3 minutos.
|
||||
has_unconfirmed_mail=Hola %s, tu correo electrónico (<b>%s</b>) no está confirmado. Si no has recibido un correo de confirmación o necesitas que lo enviemos de nuevo, por favor, haz click en el siguiente botón.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Lo sentimos, su código de confirmación ha expirado o no es valido
|
||||
reset_password_helper=Haga Clic aquí para restablecer su contraseña
|
||||
password_too_short=La longitud de la contraseña no puede ser menor a 6.
|
||||
|
||||
[mail]
|
||||
activate_account=Por favor, active su cuenta
|
||||
activate_email=Verifique su correo electrónico
|
||||
reset_password=Restablezca su contraseña
|
||||
register_success=Registro completado, bienvenido
|
||||
|
||||
[modal]
|
||||
yes=Sí
|
||||
no=No
|
||||
@@ -181,6 +191,7 @@ min_size_error=` debe contener al menos %s caracteres.`
|
||||
max_size_error=` debe contener como máximo %s caracteres.`
|
||||
email_error=` no es una dirección de correo válida.`
|
||||
url_error=` no es una URL válida.`
|
||||
include_error=` must contain substring '%s'.`
|
||||
unknown_error=Error desconocido:
|
||||
captcha_incorrect=El captcha no es válido.
|
||||
password_not_match=La contraseña de confirmación no coincide.
|
||||
@@ -241,7 +252,7 @@ location=Localización
|
||||
update_profile=Actualizar Perfil
|
||||
update_profile_success=Tu perfil se ha actualizado correctamente.
|
||||
change_username=Nombre de usuario modificado
|
||||
change_username_desc=El nombre de usuario ha sido modificado, ¿quieres continuar? Esta acción afectará a todos los enlaces relacionados con tu cuenta.
|
||||
change_username_prompt=Este cambio afectará a los enlaces que hacen referencia a su cuenta.
|
||||
continue=Continuar
|
||||
cancel=Cancelar
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=La configuración de tu avatar se ha actualizado correctam
|
||||
change_password=Cambiar contraseña
|
||||
old_password=Contraseña actual
|
||||
new_password=Nueva contraseña
|
||||
retype_new_password=Confirmar nueva contraseña
|
||||
password_incorrect=Contraseña actual incorrecta.
|
||||
change_password_success=La contraseña se ha modificado correctamente. Ya puedes iniciar sesión con tu nueva contraseña.
|
||||
|
||||
@@ -265,15 +277,18 @@ email_desc=Tu dirección de correo principal se utilizará para las notificacion
|
||||
primary=Principal
|
||||
primary_email=Marcar como principal
|
||||
delete_email=Eliminar
|
||||
email_deletion=Eliminación de Correo Electrónico
|
||||
email_deletion_desc=Al eliminar esta dirección de correo electrónico se eliminará toda la información asociada a esta dirección de correo electrónico. ¿Deseas continuar?
|
||||
email_deletion_success=¡El correo electrónico ha sido eliminado correctamente!
|
||||
add_new_email=Añadir nueva dirección de correo electrónico
|
||||
add_email=Añadir correo electrónico
|
||||
add_email_confirmation_sent=Un nuevo correo de confirmación ha sido enviado a <b>%s</b>, por favor, comprueba tu bandeja de entrada en las próximas %d horas para completar el proceso de confirmación.
|
||||
add_email_confirmation_sent=Un nuevo correo de confirmación ha sido enviado a '%s'. Por favor, comprueba tu bandeja de entrada en las próximas %d horas para completar el proceso.
|
||||
add_email_success=Tu nuevo correo electrónico se ha añadido correctamente.
|
||||
|
||||
manage_ssh_keys=Gestionar Claves SSH
|
||||
add_key=Añadir Clave
|
||||
ssh_desc=Esta es la lista de claves SSH asociadas con tu cuenta. Elimina cualquier clave que no reconozcas.
|
||||
ssh_helper=<strong>¿Necesitas ayuda?</strong>. Consulta nuestra guía para <a href="%s">generar claves SSH</a> o solucionar <a href="%s">problemas comunes de SSH</a>.
|
||||
ssh_helper=<strong>¿Necesitas ayuda?</strong> Consulta la guía de GitHub para <a href="%s">generar claves SSH</a> o solucionar <a href="%s">problemas comunes</a> al usar SSH.
|
||||
add_new_key=Añadir clave SSH
|
||||
ssh_key_been_used=El contenido de la clave pública se ha utilizado.
|
||||
ssh_key_name_used=Ya existe una clave pública con el mismo nombre.
|
||||
@@ -281,14 +296,14 @@ key_name=Nombre de la Clave
|
||||
key_content=Contenido
|
||||
add_key_success=¡Nueva clave SSH '%s' añadida correctamente!
|
||||
delete_key=Eliminar
|
||||
ssh_key_deletion=Borrado de Llave SSH
|
||||
ssh_key_deletion_desc=Al borrar esta llave SSH no podrá volver a acceder con ella a su cuenta. Desea continuar?
|
||||
ssh_key_deletion_success=¡La llave SSH ha sido eliminada con éxito!
|
||||
ssh_key_deletion=Borrado de Clave SSH
|
||||
ssh_key_deletion_desc=Si elimina esta clave SSH no podrá volver a usarla para acceder a su cuenta. ¿Desea continuar?
|
||||
ssh_key_deletion_success=¡La clave SSH ha sido eliminada con éxito!
|
||||
add_on=Añadido en
|
||||
last_used=Utilizado por última vez en
|
||||
no_activity=No hay actividad reciente
|
||||
key_state_desc=Esta clave ha sido usada en los últimos 7 días
|
||||
token_state_desc=Este token ha sido usado en los últimos 7 días
|
||||
token_state_desc=Token usado en los últimos 7 días
|
||||
|
||||
manage_social=Gestionar Redes Sociales asociadas
|
||||
social_desc=Esta es una lista de las Redes Sociales asociadas. Elimina cualquier vínculo que no reconozcas.
|
||||
@@ -297,7 +312,7 @@ unbind_success=La Red Social ha sido desvinculada.
|
||||
|
||||
manage_access_token=Gestionar los Tokens de Acceso personales
|
||||
generate_new_token=Generar nuevo Token
|
||||
tokens_desc=Tokens generados que se pueden usar para acceder al API de Gogs.
|
||||
tokens_desc=Tokens usados para acceder al API de Gogs.
|
||||
new_token_desc=Desde ahora, todos los tokens tendrán acceso completo a tu cuenta.
|
||||
token_name=Nombre del Token
|
||||
generate_token=Generar Token
|
||||
@@ -319,6 +334,7 @@ repo_name=Nombre del Repositorio
|
||||
repo_name_helper=Los grandes nombres de repositorios son cortos, memorables y <strong>únicos</strong>.
|
||||
visibility=Visibilidad
|
||||
visiblity_helper=Este repositorio es <span class="ui red text">Privado</span>
|
||||
visiblity_helper_forced=Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(Este cambio afectará a todos los forks)
|
||||
fork_repo=Hacer Fork del repositorio
|
||||
fork_from=Crear un Fork desde
|
||||
@@ -343,12 +359,15 @@ migrate_type=Tipo de Migración
|
||||
migrate_type_helper=Este repositorio será un <span class="text blue">mirror</span>
|
||||
migrate_repo=Migrar Repositorio
|
||||
migrate.clone_address=Clonar Dirección
|
||||
migrate.clone_address_desc=Esto puede ser una URL HTTP/HTTPS/GIT o una ruta local del servidor.
|
||||
migrate.clone_address_desc=Puede ser una URL HTTP/HTTPS/GIT o una ruta local del servidor.
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=Rutal local inválida, no existe o no es un directorio.
|
||||
|
||||
forked_from=forked de
|
||||
fork_from_self=eres el propietario del repositorio, no puedes hacer fork!
|
||||
fork_from_self=Eres el propietario del repositorio, ¡no puedes hacer fork!
|
||||
copy_link=Copiar
|
||||
copy_link_success=Copiado!
|
||||
copy_link_error=Presione ⌘ + C o Ctrl-C para copiar
|
||||
click_to_copy=Copiar al portapapeles
|
||||
copied=Copiado correctamente
|
||||
clone_helper=¿Necesitas ayuda con el clone? ¡Consulta la <a target="_blank" href="%s">Ayuda</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Guía Rápida
|
||||
clone_this_repo=Clonar este repositorio
|
||||
create_new_repo_command=Crear un nuevo repositorio desde línea de comandos
|
||||
push_exist_repo=Hacer Push de un repositorio existente desde línea de comandos
|
||||
repo_is_empty=This repository is empty, please come back later!
|
||||
|
||||
|
||||
branch=Rama
|
||||
tree=Árbol
|
||||
@@ -410,19 +431,19 @@ issues.close_tab=%d cerradas
|
||||
issues.filter_label=Etiqueta
|
||||
issues.filter_label_no_select=Ninguna etiqueta seleccionada
|
||||
issues.filter_milestone=Milestone
|
||||
issues.filter_milestone_no_select=Milestone no seleccionado
|
||||
issues.filter_assignee=Asignada por
|
||||
issues.filter_milestone_no_select=Ningún Milestone seleccionado
|
||||
issues.filter_assignee=Asignada a
|
||||
issues.filter_assginee_no_select=Sin asignar
|
||||
issues.filter_type=Tipo
|
||||
issues.filter_type.all_issues=Todas las incidencias
|
||||
issues.filter_type.assigned_to_you=Asignada a ti
|
||||
issues.filter_type.created_by_you=Creada por ti
|
||||
issues.filter_type.assigned_to_you=Asignadas a ti
|
||||
issues.filter_type.created_by_you=Creadas por ti
|
||||
issues.filter_type.mentioning_you=Citado en
|
||||
issues.filter_sort=Ordenar
|
||||
issues.filter_sort.latest=Más recientes
|
||||
issues.filter_sort.oldest=Más antiguos
|
||||
issues.filter_sort.oldest=Más antiguas
|
||||
issues.filter_sort.recentupdate=Actualizada recientemente
|
||||
issues.filter_sort.leastupdate=Least recently updated
|
||||
issues.filter_sort.leastupdate=Actualizada menos recientemente
|
||||
issues.filter_sort.mostcomment=Más comentadas
|
||||
issues.filter_sort.leastcomment=Menos comentadas
|
||||
issues.opened_by=abierta %[1]s por <a href="%[2]s">%[3]s</a>
|
||||
@@ -433,20 +454,20 @@ issues.open_title=Abierta
|
||||
issues.closed_title=Cerrada
|
||||
issues.num_comments=%d comentarios
|
||||
issues.commented_at=`comentada <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=No hay contenido por el momento.
|
||||
issues.no_content=Aun no existe contenido.
|
||||
issues.close_issue=Cerrar
|
||||
issues.close_comment_issue=Cerrar y Comentar
|
||||
issues.close_comment_issue=Comentar y cerrar
|
||||
issues.reopen_issue=Reabrir
|
||||
issues.reopen_comment_issue=Reabrir y Comentar
|
||||
issues.reopen_comment_issue=Comentar y reabrir
|
||||
issues.create_comment=Comentar
|
||||
issues.closed_at=`cerrada <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reabierta <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`mencionada esta incidencia en un commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Poster
|
||||
issues.poster=Autor
|
||||
issues.admin=Administrador
|
||||
issues.owner=Propietario
|
||||
issues.sign_up_for_free=Registro gratuito
|
||||
issues.sign_in_require_desc=para unirse a esta conversación. Ya dispone de una cuenta? <a href="%s">Inicie sesión para comentar</a>
|
||||
issues.sign_in_require_desc=para unirse a esta conversación. ¿Ya dispone de una cuenta? <a href="%s">Inicie sesión para comentar</a>
|
||||
issues.edit=Editar
|
||||
issues.cancel=Cancelar
|
||||
issues.save=Guardar
|
||||
@@ -456,7 +477,7 @@ issues.label_count=%d etiquetas
|
||||
issues.label_open_issues=%d incidencias abiertas
|
||||
issues.label_edit=Editar
|
||||
issues.label_delete=Borrar
|
||||
issues.label_modify=Modificación de Etiqueta
|
||||
issues.label_modify=Edición de Etiqueta
|
||||
issues.label_deletion=Borrado de Etiqueta
|
||||
issues.label_deletion_desc=Al borrar la etiqueta su información será eliminada de todas las incidencias relacionadas. Desea continuar?
|
||||
issues.label_deletion_success=Etiqueta borrada con éxito!
|
||||
@@ -466,10 +487,10 @@ pulls.compare_changes_desc=Comparar dos ramas y generar un pull request con las
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=comparar con
|
||||
pulls.filter_branch=Filtrar rama
|
||||
pulls.no_results=No se han encontrado resultados.
|
||||
pulls.nothing_to_compare=No hay nada que comparar porque las dos ramas coinciden.
|
||||
pulls.no_results=Sin resultados.
|
||||
pulls.nothing_to_compare=Nada que comparar. Las dos ramas coinciden.
|
||||
pulls.has_pull_request=`Ya existe un pull request entre estas dos ramas: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Nuevo Pull Request
|
||||
pulls.create=Crear Pull Request
|
||||
pulls.title_desc=desea fusionar %[1]d commits de <code>%[2]s</code> en <code>%[3]s</code>
|
||||
pulls.merged_title_desc=fusionados %[1]d commits de <code>%[2]s</code> en <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversación
|
||||
@@ -479,10 +500,12 @@ pulls.reopen_to_merge=Por favor reabra este pull request para proceder con la op
|
||||
pulls.merged=Fuisionado
|
||||
pulls.has_merged=¡Este pull request se ha completado con éxito!
|
||||
pulls.data_broken=Los datos de este pull request ya no están disponibles porque se ha eliminado la información del fork.
|
||||
pulls.is_checking=The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc=Puede realizar la operación auto-fusionado en este pull request.
|
||||
pulls.cannot_auto_merge_desc=No puede realizar la operación de auto-fusionado porque existen conflictos entre los commits.
|
||||
pulls.cannot_auto_merge_helper=Por favor use la línea de comandos para resolverlo.
|
||||
pulls.merge_pull_request=Fusionar Pull Request
|
||||
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new=Nuevo Milestone
|
||||
milestones.open_tab=%d abiertas
|
||||
@@ -490,14 +513,14 @@ milestones.close_tab=%d cerradas
|
||||
milestones.closed=Cerrada %s
|
||||
milestones.no_due_date=Sin fecha límite
|
||||
milestones.open=Abrir
|
||||
milestones.close=Cerrada
|
||||
milestones.close=Cerrar
|
||||
milestones.new_subheader=Cree milestones para organizar las incidencias.
|
||||
milestones.create=Nuevo Milestone
|
||||
milestones.title=Título
|
||||
milestones.desc=Descripción
|
||||
milestones.due_date=Fecha límite (opcional)
|
||||
milestones.clear=Eliminar
|
||||
milestones.invalid_due_date_format=El formato de la fecha límite no es válido, debe ser 'y-mm-dd'.
|
||||
milestones.invalid_due_date_format=El formato de la fecha límite no es válido, debe ser 'yyyy-mm-dd'.
|
||||
milestones.create_success=¡El milestone '%s' ha sido creado con éxito!
|
||||
milestones.edit=Editar Milestone
|
||||
milestones.edit_subheader=Use una buena descripción en el milestone para no confundir al resto de usuarios.
|
||||
@@ -517,7 +540,7 @@ settings.basic_settings=Configuración Básica
|
||||
settings.danger_zone=Zona de Peligro
|
||||
settings.site=Sitio Oficial
|
||||
settings.update_settings=Actualizar Configuración
|
||||
settings.change_reponame_prompt=This change will affect how links relate to the repository.
|
||||
settings.change_reponame_prompt=Este cambio afectará a los enlaces al repositorio.
|
||||
settings.transfer=Transferir la Propiedad
|
||||
settings.transfer_desc=Transferir este repositorio a otro usuario u organización donde tengas permisos de administración.
|
||||
settings.new_owner_has_same_repo=El nuevo propietario tiene un repositorio con el mismo nombre.
|
||||
@@ -543,7 +566,7 @@ settings.user_is_org_member=El usuario es miembro de la organización, no puede
|
||||
settings.add_webhook=Añadir Webhook
|
||||
settings.hooks_desc=Los Webhooks permiten a servicios externos recibir notificaciones cuando sucedan ciertos eventos en Gogs. Cuando sucedan los eventos especificados, enviaremos una petición POST a cada una de las URLs indicadas. Para obtener más información, consulta nuestra <a target="_blank" href="%s">Guía de Webhooks</a>.
|
||||
settings.webhook_deletion=Eliminar Webhook
|
||||
settings.webhook_deletion_desc=El borrado de este webhook eliminará su información y todo su historial. ¿Desea continuar?
|
||||
settings.webhook_deletion_desc=Al borrar este webhook se eliminará su información y todo su historial. ¿Desea continuar?
|
||||
settings.webhook_deletion_success=¡Webhook eliminado con éxito!
|
||||
settings.webhook.request=Petición
|
||||
settings.webhook.response=Respuesta
|
||||
@@ -567,7 +590,7 @@ settings.event_push_only=Solo el evento <code>push</code>.
|
||||
settings.event_send_everything=Necesito <strong>todo</strong>.
|
||||
settings.event_choose=Déjeme elegir lo que necesito.
|
||||
settings.event_create=Crear
|
||||
settings.event_create_desc=Branch o etiqueta creada
|
||||
settings.event_create_desc=Rama o etiqueta creada
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push a un repositorio
|
||||
settings.active=Activo
|
||||
@@ -583,16 +606,16 @@ settings.slack_token=Token
|
||||
settings.slack_domain=Dominio
|
||||
settings.slack_channel=Canal
|
||||
settings.deploy_keys=Claves de Despliegue
|
||||
settings.add_deploy_key=Add Deploy Key
|
||||
settings.no_deploy_keys=You haven't added any deploy key.
|
||||
settings.add_deploy_key=Añadir Clave de Despliegue
|
||||
settings.no_deploy_keys=No has añadido ninguna clave de despliegue.
|
||||
settings.title=Título
|
||||
settings.deploy_key_content=Contenido
|
||||
settings.key_been_used=Deploy key content has been used.
|
||||
settings.key_name_used=Deploy key with same name has already existed.
|
||||
settings.add_key_success=New deploy key '%s' has been added successfully!
|
||||
settings.deploy_key_deletion=Delete Deploy Key
|
||||
settings.deploy_key_deletion_desc=Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||
settings.deploy_key_deletion_success=Deploy key has been deleted successfully!
|
||||
settings.key_been_used=Se ha usado la clave de despliegue.
|
||||
settings.key_name_used=Ya existe una clave de despliegue con el mismo nombre.
|
||||
settings.add_key_success=¡La nueva clave de desplieque '%s' ha sido creada con éxito!
|
||||
settings.deploy_key_deletion=Eliminar Clave de Despliegue
|
||||
settings.deploy_key_deletion_desc=Al eliminar esta clave de despliegue se perderán el permiso de acceso a este repositorio con dicha clave. ¿Deseas continuar?
|
||||
settings.deploy_key_deletion_success=¡Clave de despliegue eliminada con éxito!
|
||||
|
||||
diff.browse_source=Explorar el Código
|
||||
diff.parent=padre
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Ya existe una Release con esta etiqueta.
|
||||
|
||||
[org]
|
||||
org_name_holder=Nombre de la Organización
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=Los grandes nombres de organizaciones son cortos y memorables.
|
||||
org_email_helper=Los correos electrónicos de las organizaciones reciben todas las notificaciones y confirmaciones.
|
||||
create_org=Crear Organización
|
||||
repo_updated=Actualizado
|
||||
people=Personas
|
||||
@@ -652,12 +675,12 @@ form.name_pattern_not_allowed=El patrón de nombre de la organización '%s' no e
|
||||
settings=Configuración
|
||||
settings.options=Opciones
|
||||
settings.full_name=Nombre Completo
|
||||
settings.website=Sitio Web
|
||||
settings.website=Página Web
|
||||
settings.location=Localización
|
||||
settings.update_settings=Actualizar Configuración
|
||||
settings.change_orgname=Nombre de la Organización Modificado
|
||||
settings.change_orgname_desc=El nombre de la organización ha sido modificado, ¿quieres continuar? Esta acción afectará a todos los enlaces relacionados con esta organización.
|
||||
settings.update_setting_success=La configuración de la Organización se ha actualizado correctamente.
|
||||
settings.change_orgname_prompt=Este cambio afectará a los enlaces que hacen referencia a la organización.
|
||||
settings.update_avatar_success=La configuración de avatar de la organización ha sido actualizada con éxito.
|
||||
settings.delete=Eliminar Organización
|
||||
settings.delete_account=Eliminar esta Organización
|
||||
settings.delete_prompt=Esta operación eliminará esta organización de manera permanente, y ¡<strong>NO PUEDE</strong> deshacerse!
|
||||
@@ -713,8 +736,9 @@ authentication=Autenticaciones
|
||||
config=Configuración
|
||||
notices=Avisos del Sistema
|
||||
monitor=Monitorización
|
||||
prev=Anterior
|
||||
next=Siguiente
|
||||
first_page=Primera
|
||||
last_page=Última
|
||||
total=Total: %d
|
||||
|
||||
dashboard.statistic=Estadísticas
|
||||
dashboard.operations=Operaciones
|
||||
@@ -773,19 +797,24 @@ users.activated=Activado
|
||||
users.admin=Administrador
|
||||
users.repos=Repositorios
|
||||
users.created=Creado
|
||||
users.send_register_notify=Enviar notificación de registro al usuario
|
||||
users.new_success=La cuenta '%s' ha sido creada con éxito.
|
||||
users.edit=Editar
|
||||
users.auth_source=Origen de Autorización
|
||||
users.auth_source=Fuente de Autenticación
|
||||
users.local=Local
|
||||
users.auth_login_name=Nombre de Usuario de Autorización
|
||||
users.auth_login_name=Nombre de Inicio de Sesión de Autenticación
|
||||
users.password_helper=Deje el campo vacío si no desea cambiar la contraseña.
|
||||
users.update_profile_success=El perfil de la cuenta se ha actualizado correctamente.
|
||||
users.edit_account=Editar Cuenta
|
||||
users.is_activated=Esta cuenta está activada
|
||||
users.is_admin=Esta cuenta tiene permisos de administrador
|
||||
users.allow_git_hook=Esta cuenta tiene permisos para crear hooks de Git
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=Actualizar Perfil de la Cuenta
|
||||
users.delete_account=Eliminar esta Cuenta
|
||||
users.still_own_repo=Esta cuenta es propietaria de uno o más repositorios, tienes que borrarlos o transferirlos primero.
|
||||
users.still_has_org=Esta cuenta es miembro de una o más organizaciones, tienes que abandonarlas o eliminarlas primero.
|
||||
users.deletion_success=Su cuenta ha sido eliminada correctamente!
|
||||
|
||||
orgs.org_manage_panel=Panel de Gestión de Organización
|
||||
orgs.name=Nombre
|
||||
@@ -800,41 +829,47 @@ repos.watches=Vigilantes
|
||||
repos.stars=Estrellas
|
||||
repos.issues=Incidencias
|
||||
|
||||
auths.auth_manage_panel=Panel de Gestión de Autorizaciones
|
||||
auths.new=Añadir nuevo origen de autorización
|
||||
auths.auth_manage_panel=Panel de Administración de Autenticación
|
||||
auths.new=Añadir Nuevo Origen
|
||||
auths.name=Nombre
|
||||
auths.type=Tipo
|
||||
auths.enabled=Activo
|
||||
auths.updated=Actualizado
|
||||
auths.auth_type=Tipo de Autorización
|
||||
auths.auth_name=Nombre de Autorización
|
||||
auths.auth_type=Tipo de Autenticación
|
||||
auths.auth_name=Nombre de Autenticación
|
||||
auths.domain=Dominio
|
||||
auths.host=Host
|
||||
auths.port=Puerto
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.user_base=User Search Base
|
||||
auths.bind_password=Contraseña Bind
|
||||
auths.bind_password_helper=Advertencia: La contraseña se almacena como texto plano. No utilice una cuenta con privilegios elevados.
|
||||
auths.user_base=Base de Búsqueda de Usuarios
|
||||
auths.user_dn=DN de Usuario
|
||||
auths.attribute_name=Atributo nombre
|
||||
auths.attribute_surname=Atributo apellido
|
||||
auths.attribute_mail=Atributo correo electrónico
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.filter=Filtro de Usuario
|
||||
auths.admin_filter=Filtro de Aministrador
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Tipo de Autorización SMTP
|
||||
auths.smtp_auth=Tipo de Autenticación SMTP
|
||||
auths.smtphost=SMTP Host
|
||||
auths.smtpport=Puerto SMTP
|
||||
auths.allowed_domains=Dominios Permitidos
|
||||
auths.allowed_domains_helper=Deje el campo vacío si no desea restringir ningún dominio. Para restringir más de uno, separe los dominios con una coma ','.
|
||||
auths.enable_tls=Habilitar Cifrado TLS
|
||||
auths.skip_tls_verify=Omitir la verificación TLS
|
||||
auths.pam_service_name=Nombre del Servicio PAM
|
||||
auths.enable_auto_register=Hablilitar Auto-Registro
|
||||
auths.tips=Consejos
|
||||
auths.edit=Editar la Configuración de Autorización
|
||||
auths.edit=Editar la Configuración de Autenticación
|
||||
auths.activated=Esta autenticación ha sido activada
|
||||
auths.update_success=La Configuración de Autorización ha sido actualizada correctamente.
|
||||
auths.update=Actualizar la Configuración de Autorización
|
||||
auths.delete=Eliminar esta Autorización
|
||||
auths.delete_auth_title=Eliminación de Autorización
|
||||
auths.delete_auth_desc=Se va a eliminar esta autorización, ¿quieres continuar?
|
||||
auths.new_success=¡La autenticación '%s' ha sido añadida con éxito!
|
||||
auths.update_success=La configuración de autenticación ha sido actualizada con éxito.
|
||||
auths.update=Actualizar la Configuración de Autenticación
|
||||
auths.delete=Eliminar Autenticación
|
||||
auths.delete_auth_title=Borrado de Autenticación
|
||||
auths.delete_auth_desc=Esta autenticación será eliminada. ¿Deseas continuar?
|
||||
auths.deletion_success=¡La autenticación ha sido eliminada con éxito!
|
||||
|
||||
config.server_config=Configuración del Servidor
|
||||
config.app_name=Nombre de la Aplicación
|
||||
@@ -843,7 +878,7 @@ config.app_url=URL de la Aplicación
|
||||
config.domain=Dominio
|
||||
config.offline_mode=Modo Sin Conexión
|
||||
config.disable_router_log=Deshabilitar Log del Router
|
||||
config.run_user=Usuario de Ejecución
|
||||
config.run_user=Ejecutada como Usuario
|
||||
config.run_mode=Modo de Ejecución
|
||||
config.repo_root_path=Ruta del Repositorio
|
||||
config.static_file_root_path=Ruta de los Ficheros Estáticos
|
||||
@@ -858,14 +893,16 @@ config.db_user=Usuario
|
||||
config.db_ssl_mode=Modo SSL
|
||||
config.db_ssl_mode_helper=(solo para "postgres")
|
||||
config.db_path=Ruta
|
||||
config.db_path_helper=(solo para "sqlite3")
|
||||
config.db_path_helper=(para "sqlite3" y "tidb")
|
||||
config.service_config=Configuración del Servicio
|
||||
config.register_email_confirm=Solicitar Confirmación por Correo Electrónico
|
||||
config.disable_register=Deshabilitar el Registro
|
||||
config.show_registration_button=Mostrar Botón de Registro
|
||||
config.require_sign_in_view=Solicitar la Vista de Inicio de Sesión
|
||||
config.mail_notify=Notificación por Correo Electrónico
|
||||
config.enable_cache_avatar=Activar la Caché de Avatar
|
||||
config.mail_notify=Notificación por Correo Electrónico
|
||||
config.disable_key_size_check=Deshabilitar la comprobación de Tamaño Mínimo de Clave
|
||||
config.enable_captcha=Activar Captcha
|
||||
config.active_code_lives=Habilitar Vida del Código
|
||||
config.reset_password_code_lives=Restablecer Contraseña de Vida del Código
|
||||
config.webhook_config=Configuración de Webhooks
|
||||
@@ -918,7 +955,7 @@ notices.op=Op.
|
||||
notices.delete_success=La notificación del sistema se ha eliminado correctamente.
|
||||
|
||||
[action]
|
||||
create_repo=Repositorio creado <a href="%s">%s</a>
|
||||
create_repo=repositorio creado <a href="%s">%s</a>
|
||||
rename_repo=repositorio renombrado de <code>%[1]s</code> a <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=hizo push a <a href="%s/src/%s">%[2]s</a> en <a href="%[1]s">%[3]s</a>
|
||||
create_issue=`incidencia abierta <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
@@ -933,20 +970,20 @@ compare_2_commits=Ver la comparación de estos 2 commits
|
||||
ago=hace
|
||||
from_now=desde ahora
|
||||
now=ahora
|
||||
1s=1 segundo %s
|
||||
1m=1 minuto %s
|
||||
1h=1 hora %s
|
||||
1d=1 día %s
|
||||
1w=1 semana %s
|
||||
1mon=1 mes %s
|
||||
1y=1 año %s
|
||||
seconds=%d segundos %s
|
||||
minutes=%d minutos %s
|
||||
hours=%d horas %s
|
||||
days=%d días %s
|
||||
weeks=%d semanas %s
|
||||
months=%d meses %s
|
||||
years=%d años %s
|
||||
1s=%s 1 segundo
|
||||
1m=%s 1 minuto
|
||||
1h=%s 1 hora
|
||||
1d=%s 1 día
|
||||
1w=%s 1 semana
|
||||
1mon=%s 1 mes
|
||||
1y=%s 1 año
|
||||
seconds=%s %d segundos
|
||||
minutes=%s %d minutos
|
||||
hours=%s %d horas
|
||||
days=%s %d días
|
||||
weeks=%s %d semanas
|
||||
months=%s %d meses
|
||||
years=%s %d años
|
||||
raw_seconds=segundos
|
||||
raw_minutes=minutos
|
||||
|
||||
|
||||
@@ -5,16 +5,15 @@ dashboard=Tableau de bord
|
||||
explore=Explorer
|
||||
help=Aide
|
||||
sign_in=Connexion
|
||||
social_sign_in=Connexion avec un réseau social : 2e étape <small>associer un compte</small>
|
||||
sign_out=Déconnexion
|
||||
sign_up=Créer un compte
|
||||
register=S'enregistrer
|
||||
register=Inscription
|
||||
website=Site Web
|
||||
version=Version
|
||||
page=Page
|
||||
template=Modèle
|
||||
language=Langue
|
||||
create_new=Créer nouveau...
|
||||
create_new=Créer...
|
||||
user_profile_and_more=Profil utilisateur et plus
|
||||
signed_in_as=Connecté en tant que
|
||||
|
||||
@@ -24,12 +23,12 @@ password=Mot de passe
|
||||
re_type=Confirmez
|
||||
captcha=Captcha
|
||||
|
||||
repository=Référentiel
|
||||
repository=Dépôt
|
||||
organization=Organisation
|
||||
mirror=Miroir
|
||||
new_repo=Nouveau Référentiel
|
||||
new_repo=Nouveau Dépôt
|
||||
new_migrate=Nouvelle Migration
|
||||
new_fork=Nouveau Référentiel d'Embranchement
|
||||
new_fork=Nouvel embranchement
|
||||
new_org=Nouvelle Organisation
|
||||
manage_org=Gérer les Organisations
|
||||
admin_panel=Administration
|
||||
@@ -39,22 +38,23 @@ your_profile=Votre profil
|
||||
your_settings=Vos paramètres
|
||||
|
||||
news_feed=Flux d'actualités
|
||||
pull_requests=Extraire les Requêtes
|
||||
pull_requests=Pull Requests
|
||||
issues=Problèmes
|
||||
|
||||
cancel=Annuler
|
||||
|
||||
[search]
|
||||
search=Rechercher...
|
||||
repository=Référentiel
|
||||
repository=Dépôt
|
||||
user=Utilisateur
|
||||
issue=Problème
|
||||
code=Code
|
||||
|
||||
[install]
|
||||
install=Installation
|
||||
title=Instructions de Première Installation
|
||||
requite_db_desc=Gogs nécessite MySQL, PostgreSQL ou SQLite3.
|
||||
title=Instructions pour la première exécution
|
||||
docker_helper=Si vous exécuté Gogs grâce à Docker, merci de lire la <a target="_blank" href="%s">procédure</a> attentivement avant de modifier quoi que ce soit dans cette page !
|
||||
requite_db_desc=Gogs requiert MySQL, PostgreSQL, SQLite3 ou TiDB.
|
||||
db_title=Paramètres de la base de données
|
||||
db_type=Type de Base de Données
|
||||
host=Hôte
|
||||
@@ -64,20 +64,23 @@ db_name=Nom de la Base de Données
|
||||
db_helper=Veuillez utiliser le moteur INNODB avec le jeu de caractères utf8_general_ci pour MySQL.
|
||||
ssl_mode=Mode SSL
|
||||
path=Chemin
|
||||
sqlite_helper=Emplacement du fichier de la base de données SQLite3.
|
||||
err_empty_sqlite_path=Le chemin de la base de donnée SQLite3 ne peut être vide.
|
||||
sqlite_helper=Le chemin du fichier de la base de données SQLite3 ou TiDB.
|
||||
err_empty_db_path=Le chemin de la base de données SQLite3 ou TiDB ne peut être vide.
|
||||
err_invalid_tidb_name=Le nom de la base de données TIDB ne peux contenir les caractères "." ou "-".
|
||||
no_admin_and_disable_registration=Vous ne pouvez pas désactiver l'enregistrement sans créer un compte admin.
|
||||
err_empty_admin_password=Le mot de passe Admin ne peut pas être vide.
|
||||
|
||||
general_title=Paramètres Généraux de Gogs
|
||||
app_name=Nom de l'Application
|
||||
app_name_helper=Inscrivez fièrement le nom de votre organisation ici !
|
||||
repo_path=Emplacement Racine du Référentiel
|
||||
repo_path_helper=Tous les Référentiels Git distants seront sauvegardés ici.
|
||||
repo_path=Emplacement Racine du Dépôt
|
||||
repo_path_helper=Tous les Dépôts Git distants seront sauvegardés ici.
|
||||
run_user=Entrer un Utilisateur
|
||||
run_user_helper=L'utilisateur doit avoir accès à la Racine du Référentiel et éxécuter Gogs.
|
||||
domain=Domaine
|
||||
domain_helper=Cela affecte les doublons d'URL SSH.
|
||||
ssh_port=SSH Port
|
||||
ssh_port_helper=Port number which your SSH server is using, leave it empty to disable SSH feature.
|
||||
ssh_port=Port SSH
|
||||
ssh_port_helper=C'est le numéro de port qui qui est utilisé par votre serveur SSH, le laisser vide pour désactiver la fonctionnalité.
|
||||
http_port=Port HTTP
|
||||
http_port_helper=Numéro de port que l'application écoutera.
|
||||
app_url=URL de l'Application
|
||||
@@ -95,10 +98,12 @@ mail_notify=Activer la Notification des Mails reçus
|
||||
server_service_title=Paramètres du serveur et des autres services
|
||||
offline_mode=Activer le Mode hors connexion
|
||||
offline_mode_popup=Désactiver le CDN, même en production. Toutes les ressources seront distribuées en local.
|
||||
disable_gravatar=Disable Gravatar Service
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_gravatar=Désactiver le Service Gravatar
|
||||
disable_gravatar_popup=Désactiver Gravatar et les sources personnalisées, tous les avatars sont téléchargés par les utilisateurs ou par défaut.
|
||||
disable_registration=Désactiver le formulaire d'inscription
|
||||
disable_registration_popup=Désactiver le formulaire d'inscription, seuls les administrateurs peuvent créer des comptes.
|
||||
enable_captcha=Activez le Captcha
|
||||
enable_captcha_popup=Demande la validation Captcha pour l'auto-enregistrement de l'utilisateur.
|
||||
require_sign_in_view=Demander une connexion pour afficher des pages
|
||||
require_sign_in_view_popup=Seules les personnes connectées peuvent voir les pages. Les visiteurs anonymes ne pourront voir que les pages de connexion/enregistrement.
|
||||
admin_setting_desc=Vous n'avez pas besoin de créer un compte admin. L'utilisateur ayant l'ID = 1 aura l'accès admin automatiquement.
|
||||
@@ -106,12 +111,12 @@ admin_title=Paramètres du Compte Administrateur
|
||||
admin_name=Nom d'Utilisateur
|
||||
admin_password=Mot de Passe
|
||||
confirm_password=Confirmez le Mot de Passe
|
||||
admin_email=E-mail
|
||||
admin_email=E-mail de l'administrateur
|
||||
install_gogs=Installer Gogs
|
||||
test_git_failed=Tentative de commande "git" échouée : %v
|
||||
sqlite3_not_available=Votre version publiée ne prend pas en charge SQLite3. Veuillez télécharger la version binaire officielle à cette adresse %s.
|
||||
invalid_db_setting=Paramètres de base de données incorrects : %v
|
||||
invalid_repo_path=Chemin Racine du Référentiel invalide : %v
|
||||
invalid_repo_path=Chemin vers le répertoire racine invalide : %v
|
||||
run_user_not_match=L'utilisateur entré n'est pas l'utilisateur actuel : %s -> %s
|
||||
save_config_failed=Sauvegarde de la configuration échouée : %v
|
||||
invalid_admin_setting=Paramètres du compte administrateur invalides : %v
|
||||
@@ -125,9 +130,9 @@ my_repos=Mes Référentiels
|
||||
collaborative_repos=Référentiels collaboratifs
|
||||
my_orgs=Mes Organisations
|
||||
my_mirrors=Mes Miroirs
|
||||
view_home=View %s
|
||||
view_home=Voir %s
|
||||
|
||||
issues.in_your_repos=In your repositories
|
||||
issues.in_your_repos=Dans vos dépôts
|
||||
|
||||
[explore]
|
||||
repos=Référentiels
|
||||
@@ -143,7 +148,6 @@ forgot_password=Mot de Passe oublié
|
||||
forget_password=Mot de Passe oublié ?
|
||||
sign_up_now=Pas de compte ? Créer maintenant.
|
||||
confirmation_mail_sent_prompt=Un nouveau mail de confirmation à été envoyé à <b>%s</b>. Veuillez vérifier votre boîte de réception dans un délai de %d heures pour compléter votre enregistrement.
|
||||
sign_in_email=Connexion avec l'E-mail
|
||||
active_your_account=Activer votre Compte
|
||||
resent_limit_prompt=Désolé, vos tentatives d'activation sont trop fréquentes. Veuillez réessayer dans 3 minutes.
|
||||
has_unconfirmed_mail=Bonjour %s, votre adresse courriel (<b>%s</b>) n'a pas été confirmée. Si vous n'avez reçu aucun courriel de confirmation ou souhaitez renouveler l'envoi, appuyez sur le bouton ci-dessous.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Désolé, code de confirmation invalide ou expiré.
|
||||
reset_password_helper=Cliquez ici pour réinitialiser votre mot de passe
|
||||
password_too_short=Le mot de passe doit contenir 6 caractères minimum.
|
||||
|
||||
[mail]
|
||||
activate_account=Veuillez activer votre compte
|
||||
activate_email=Veuillez vérifier votre adresse e-mail
|
||||
reset_password=Réinitialiser votre mot de passe
|
||||
register_success=Succès de l'enregistrement, Bienvenue
|
||||
|
||||
[modal]
|
||||
yes=Oui
|
||||
no=Non
|
||||
@@ -162,7 +172,7 @@ modify=Modifier
|
||||
|
||||
[form]
|
||||
UserName=Nom d'Utilisateur
|
||||
RepoName=Nom du Référentiel
|
||||
RepoName=Nom du dépôt
|
||||
Email=Adresse E-mail
|
||||
Password=Mot de Passe
|
||||
Retype=Confirmez le Mot de Passe
|
||||
@@ -181,18 +191,19 @@ min_size_error=` %s caractères minimum `
|
||||
max_size_error=` %s caractères maximum `
|
||||
email_error=` adresse e-mail invalide `
|
||||
url_error=` URL invalide `
|
||||
include_error=`doit contenir la sous-chaîne '%s'.`
|
||||
unknown_error=Erreur inconnue :
|
||||
captcha_incorrect=Le Captcha ne correspond pas.
|
||||
password_not_match=Le mot de passe et la confirmation de mot de passe ne correspondent pas.
|
||||
|
||||
username_been_taken=Nom d'utilisateur déjà pris.
|
||||
repo_name_been_taken=Nom de Référentiel déjà pris.
|
||||
repo_name_been_taken=Nom de dépôt déjà utilisé.
|
||||
org_name_been_taken=Nom d'organisation déjà pris.
|
||||
team_name_been_taken=Nom d'équipe déjà pris.
|
||||
email_been_used=Adresse e-mail déjà utilisée.
|
||||
illegal_team_name=Le nom de l'équipe contient des caractères interdits.
|
||||
username_password_incorrect=Nom d'utilisateur ou mot de passe incorrect.
|
||||
enterred_invalid_repo_name=Veuillez vérifier que le nom saisi du Référentiel soit correct.
|
||||
enterred_invalid_repo_name=Veuillez vérifier que le nom saisi du dépôt soit correct.
|
||||
enterred_invalid_owner_name=Veuillez vérifier que le nom du propriétaire saisi soit correct.
|
||||
enterred_invalid_password=Veuillez vérifier que le mot de passe saisi soit correct.
|
||||
user_not_exist=Cet utilisateur n'existe pas.
|
||||
@@ -204,7 +215,7 @@ auth_failed=Échec d'authentification : %s
|
||||
|
||||
still_own_repo=Votre compte comporte toujours des propriétés du dépôt. Vous devez d'abord les supprimer ou les transférer.
|
||||
still_has_org=Votre compte contient toujours au moins une adhésion à une organisation, vous devez quitter ou supprimer votre adhésion.
|
||||
org_still_own_repo=Cette organisation comporte toujours des propriétés de Référentiel. Vous devez d'abord les supprimer ou les transférer.
|
||||
org_still_own_repo=Cette organisation comporte toujours des propriétés du dépôt. Vous devez d'abord les supprimer ou les transférer.
|
||||
|
||||
still_own_user=Cette authentification a déjà servi à d'autres utilisateurs. Veuillez les déplacer puis supprimez à nouveau.
|
||||
|
||||
@@ -241,7 +252,7 @@ location=Localisation
|
||||
update_profile=Valider les modifications
|
||||
update_profile_success=Profil mis à jour avec succès.
|
||||
change_username=Non d'utilisateur modifié
|
||||
change_username_desc=Nom d'utilisateur modifié. Cela affecte tous les liens relatifs à votre compte. Continuer ?
|
||||
change_username_prompt=Cette modification affectera la manière dont les liens se rapportent à votre compte.
|
||||
continue=Continuer
|
||||
cancel=Annuler
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=Votre avatar a été mis à jour avec succès.
|
||||
change_password=Modifier le Mot de Passe
|
||||
old_password=Mot de Passe actuel
|
||||
new_password=Nouveau Mot de Passe
|
||||
retype_new_password=Retapez le nouveau mot de passe
|
||||
password_incorrect=Mot de passe actuel incorrect.
|
||||
change_password_success=Mot de passe modifié avec succès. Vous pouvez à présent vous connecter avec le nouveau mot de passe.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=Votre adresse e-mail principale sera utilisée pour les notifications
|
||||
primary=Principale
|
||||
primary_email=Définir comme principale
|
||||
delete_email=Supprimer
|
||||
email_deletion=Suppression de l'adresse mél
|
||||
email_deletion_desc=Supprimer cette adresse mél supprimera les informations associées à votre compte. Voulez-vous continuer ?
|
||||
email_deletion_success=L'adresse mél a été supprimée avec succès !
|
||||
add_new_email=Ajouter une nouvelle adresse courriel
|
||||
add_email=Ajouter un courriel
|
||||
add_email_confirmation_sent=Un nouvel e-mail de confirmation a été envoyé à <b>%s</b>, merci de vérifier votre boite de réception dans les %d heures pour compléter le processus de confirmation.
|
||||
add_email_confirmation_sent=Une nouvelle confirmation d'adresse e-mail a été envoyé à '%s', veuillez vérifier votre boîte de réception dans un délai de %d heures pour terminer le processus de confirmation.
|
||||
add_email_success=Votre courriel a été ajouté avec succès.
|
||||
|
||||
manage_ssh_keys=Gérer les clés SSH
|
||||
@@ -281,14 +296,14 @@ key_name=Nom de la Clé
|
||||
key_content=Contenu
|
||||
add_key_success=La nouvelle clé SSH '%s' a été ajoutée avec succès !
|
||||
delete_key=Supprimer
|
||||
ssh_key_deletion=SSH Key Deletion
|
||||
ssh_key_deletion_desc=Delete this SSH key will remove all related accesses for your account. Do you want to continue?
|
||||
ssh_key_deletion_success=SSH key has been deleted successfully!
|
||||
ssh_key_deletion=Suppression de la clé SSH
|
||||
ssh_key_deletion_desc=Supprimer cette clé SSH supprimera tous les accès à votre compte. Voulez-vous continuer ?
|
||||
ssh_key_deletion_success=Clé SSH supprimée avec succès !
|
||||
add_on=Ajouté le
|
||||
last_used=Dernière utilisation le
|
||||
no_activity=Aucune activité récente
|
||||
key_state_desc=Cette clé a été utilisée durant les 7 derniers jours
|
||||
token_state_desc=This token is used in last 7 days
|
||||
token_state_desc=Cette clé a été utilisée durant les 7 derniers jours
|
||||
|
||||
manage_social=Gérer les réseaux sociaux associés
|
||||
social_desc=Ceci est la liste des comptes de réseaux sociaux associés. Supprimez ceux que vous ne reconnaissez pas.
|
||||
@@ -297,15 +312,15 @@ unbind_success=Compte de réseau social dissocié.
|
||||
|
||||
manage_access_token=Gérer les jetons d'accès personnels
|
||||
generate_new_token=Générer le nouveau jeton
|
||||
tokens_desc=Tokens you have generated that can be used to access the Gogs APIs.
|
||||
tokens_desc=Jetons, que vous avez généré, qui peuvent être utilisés pour accéder à l'API Gogs.
|
||||
new_token_desc=Comme pour l'instant, chaque jeton aura un accès complet à votre compte.
|
||||
token_name=Nom du jeton
|
||||
generate_token=Générer le jeton
|
||||
generate_token_succees=Nouveau jeton d'accès a été généré avec succès ! Assurez-vous de copier votre nouveau jeton d'accès personnel maintenant. Vous ne serez pas en mesure de le revoir !
|
||||
delete_token=Supprimer
|
||||
access_token_deletion=Personal Access Token Deletion
|
||||
access_token_deletion_desc=Delete this personal access token will remove all related accesses of application. Do you want to continue?
|
||||
delete_token_success=Personal access token has been removed successfully! Don't forget to update your application as well.
|
||||
access_token_deletion=Suppression du jeton d'accès
|
||||
access_token_deletion_desc=Supprimer ce jeton d'accès supprimera tous les accès de l'application. Voulez-vous continuer ?
|
||||
delete_token_success=Le jeton d'accèsa été supprimée avec succès ! N'oubliez pas de mettre aussi à jour vos applications.
|
||||
|
||||
delete_account=Supprimer le Compte
|
||||
delete_prompt=Votre compte sera supprimé définitivement et cette opération est <strong>IRRÉVERSIBLE</strong> !
|
||||
@@ -319,19 +334,20 @@ repo_name=Nom du Référentiel
|
||||
repo_name_helper=Idéalement, le nom d'un dépot devrait être court, mémorable et <strong>unique</strong>.
|
||||
visibility=Visibilité
|
||||
visiblity_helper=Ce dépôt est <span class="ui red text"> privé</span>
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
fork_repo=Référentiel d'Embranchement
|
||||
visiblity_helper_forced=L'administrateur du site a forcé tous les nouveaux dépôts à être <span class="ui red text">privés</span>
|
||||
visiblity_fork_helper=(Les changement de cette valeur affectera tous les forks)
|
||||
fork_repo=Scinder le dépôt
|
||||
fork_from=Embranchement de
|
||||
fork_visiblity_helper=Un dépôt scindé ne peut pas changer sa visiblité
|
||||
repo_desc=Description
|
||||
repo_lang=Langue
|
||||
repo_lang_helper=Select .gitignore files
|
||||
repo_lang_helper=Sélectionnez les fichiers .gitignore
|
||||
license=Licence
|
||||
license_helper=Sélectionner un fichier de licence
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
create_repo=Créer un Référentiel
|
||||
readme=Fichier Readme
|
||||
readme_helper=Sélectionnez un modèle de readme
|
||||
auto_init=Initialiser ce dépôt avec le modèle et les fichiers sélectionnés
|
||||
create_repo=Créer un dépôt
|
||||
default_branch=Branche par défaut
|
||||
mirror_interval=Intervalle du miroir (heure)
|
||||
|
||||
@@ -341,14 +357,17 @@ form.name_pattern_not_allowed=Motif '%s' interdit pour les noms de dépôt.
|
||||
need_auth=Nécessite une Autorisation
|
||||
migrate_type=Type de Migration
|
||||
migrate_type_helper=Ce dépôt sera un <span class="text blue"> miroir</span>
|
||||
migrate_repo=Migrer le Référentiel
|
||||
migrate_repo=Migrer le dépôt
|
||||
migrate.clone_address=Adresse du clone
|
||||
migrate.clone_address_desc=Cela peut être une URL HTTP/HTTPS/GIT ou un chemin d'accès local.
|
||||
migrate.permission_denied=Vous n'êtes pas autorisé à importer des dépôts locaux.
|
||||
migrate.invalid_local_path=Chemin local non valide, non existant ou n'étant pas un dossier.
|
||||
|
||||
forked_from=dérivé depuis
|
||||
fork_from_self=Vous nous ne pouvez pas dériver un dépôt que vous possédez déja !
|
||||
fork_from_self=Vous nous ne pouvez pas scinder un dépôt que vous possédez déja !
|
||||
copy_link=Copier
|
||||
copy_link_success=Copié!
|
||||
copy_link_error=Appuyez sur ⌘-C ou Ctrl-C pour copier
|
||||
click_to_copy=Copier dans le presse-papiers
|
||||
copied=Copié
|
||||
clone_helper=Besoin d'aide pour le clonage ? Visitez <a target="_blank" href="%s"> l'aider</a> !
|
||||
@@ -360,9 +379,11 @@ fork=Embranchement
|
||||
|
||||
no_desc=Aucune description
|
||||
quick_guide=Introduction rapide
|
||||
clone_this_repo=Dupliquer ce Référentiel
|
||||
create_new_repo_command=Créer un nouveau Référentiel en ligne de commande
|
||||
push_exist_repo=Soumettre un Référentiel existant par ligne de commande
|
||||
clone_this_repo=Cloner ce dépôt
|
||||
create_new_repo_command=Créer un nouveau dépôt en ligne de commande
|
||||
push_exist_repo=Soumettre un dépôt existant par ligne de commande
|
||||
repo_is_empty=Ce référentiel est vide, veuillez revenir plus tard !
|
||||
|
||||
|
||||
branch=Branche
|
||||
tree=Aborescence
|
||||
@@ -399,8 +420,8 @@ issues.new.clear_milestone=Effacer l'étape
|
||||
issues.new.open_milestone=Ouvrir l'étape
|
||||
issues.new.closed_milestone=Étapes fermées
|
||||
issues.new.assignee=Affecté à
|
||||
issues.new.clear_assignee=Clear assignee
|
||||
issues.new.no_assignee=No assignee
|
||||
issues.new.clear_assignee=Supprimer les assignataires
|
||||
issues.new.no_assignee=Pas d'assignataire
|
||||
issues.create=Créer un rapport de problème
|
||||
issues.new_label=Nouvelle étiquette
|
||||
issues.new_label_placeholder=Nom de l'étiquette...
|
||||
@@ -412,7 +433,7 @@ issues.filter_label_no_select=Aucun étiquette sélectionnée
|
||||
issues.filter_milestone=Étape
|
||||
issues.filter_milestone_no_select=Aucun jalon sélectionné
|
||||
issues.filter_assignee=Assigné
|
||||
issues.filter_assginee_no_select=No selected Assignee
|
||||
issues.filter_assginee_no_select=Pas d'assignataire selectionné
|
||||
issues.filter_type=Type
|
||||
issues.filter_type.all_issues=Tous les problèmes
|
||||
issues.filter_type.assigned_to_you=Qui vous sont assignés
|
||||
@@ -425,7 +446,7 @@ issues.filter_sort.recentupdate=Mis à jour récemment
|
||||
issues.filter_sort.leastupdate=Moins récemment mis à jour
|
||||
issues.filter_sort.mostcomment=Plus commentés
|
||||
issues.filter_sort.leastcomment=Moins commenté
|
||||
issues.opened_by=opened %[1]s by <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by=Ouvrir %[1]s by <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=ouvert %[1]s par %[2]s
|
||||
issues.previous=Page Précédente
|
||||
issues.next=Page Suivante
|
||||
@@ -435,20 +456,20 @@ issues.num_comments=%d commentaires
|
||||
issues.commented_at='commenté à <a id="%[1]s" href="#%[1]s"> %[2]s'</a>
|
||||
issues.no_content=Il n'existe pas encore de contenu.
|
||||
issues.close_issue=Fermer
|
||||
issues.close_comment_issue=Fermer et commenter
|
||||
issues.close_comment_issue=Commenter et fermer
|
||||
issues.reopen_issue=Réouvrir
|
||||
issues.reopen_comment_issue=Réouvrir et commenter
|
||||
issues.reopen_comment_issue=Commenter et réouvrir
|
||||
issues.create_comment=Créer un commentaire
|
||||
issues.closed_at="fermé à <a id="%[1]s"href="#%[1]s"> %[2]s"</a>
|
||||
issues.reopened_at='réouvert à <a id="%[1]s" href="#%[1]s"> %[2]s'</a>
|
||||
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`cette question référencé à partir d'un commit <a id="%[1]s" href="#%[1]s"> %[2]s</a>`
|
||||
issues.poster=Publier
|
||||
issues.admin=Admin
|
||||
issues.owner=Propriétaire
|
||||
issues.sign_up_for_free=Inscrivez-vous gratuitement
|
||||
issues.sign_in_require_desc=pour rejoindre cette conversation. Vous avez déjà un compte ? <a href="%s">Connectez-vous commenter</a>
|
||||
issues.edit=Modifier
|
||||
issues.cancel=Cancel
|
||||
issues.cancel=Annuler
|
||||
issues.save=Enregistrer
|
||||
issues.label_title=Nom du Label
|
||||
issues.label_color=Couleur du Label
|
||||
@@ -463,26 +484,28 @@ issues.label_deletion_success=Label supprimé avec succès !
|
||||
|
||||
pulls.compare_changes=Comparer les changements
|
||||
pulls.compare_changes_desc=Comparer deux branches et faire une demande de récupération Pull pour les changements.
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=compare
|
||||
pulls.filter_branch=Filter branch
|
||||
pulls.compare_base=Base
|
||||
pulls.compare_compare=Comparer
|
||||
pulls.filter_branch=Filtre de branche
|
||||
pulls.no_results=Aucun résultat trouvé.
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Create Pull Request
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.nothing_to_compare=Il n'y a rien de comparable parce que les deux branches sont égales.
|
||||
pulls.has_pull_request=`Il y a déjà une demande de tirer entre ces deux cibles : <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Creer une Pull Request
|
||||
pulls.title_desc=veut fusionner %[1]d commits à partir de <code>%[2]s</code> vers <code>%[3]s</code>
|
||||
pulls.merged_title_desc=à fusionné %[1]d commits à partir de <code>%[2]s</code> vers <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversation
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Files changed
|
||||
pulls.reopen_to_merge=Please reopen this pull request to perform merge operation.
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.tab_files=Fichiers modifiés
|
||||
pulls.reopen_to_merge=Veuillez rouvrir cette demande de Pull Request pour effectuer l'opération de fusion.
|
||||
pulls.merged=Fusionné
|
||||
pulls.has_merged=Cette Pull Request a été fusionnée avec succès !
|
||||
pulls.data_broken=Les données de cette Pull Request a été rompues en raison de la suppression d'informations du Fork.
|
||||
pulls.is_checking=La recherche de conflicts est toujours en cours, veuillez rafraichir la page dans quelques instants.
|
||||
pulls.can_auto_merge_desc=Vous pouvez effectuer d'opération de fusion automatique sur cette demande de Pull Request.
|
||||
pulls.cannot_auto_merge_desc=Vous ne pouvez effectuer des opération de fusion automatique car il y a des conflits entre les Commits.
|
||||
pulls.cannot_auto_merge_helper=Veuillez utiliser l'outil en ligne de commande pour le résoudre.
|
||||
pulls.merge_pull_request=Fusionner la Pull Request
|
||||
pulls.open_unmerged_pull_exists=`Vous ne pouvez effectuer une réouverture car il y a déjà une pull-request ouverte (#%d) depuis le même dépôt avec les mêmes informations de fusion et est en attente de fusion.`
|
||||
|
||||
milestones.new=Nouveau Jalon
|
||||
milestones.open_tab=%d Ouvert
|
||||
@@ -497,7 +520,7 @@ milestones.title=Titre
|
||||
milestones.desc=Description
|
||||
milestones.due_date=Date d'échéance (facultatif)
|
||||
milestones.clear=Effacer
|
||||
milestones.invalid_due_date_format=Le format de la date d'échéance est invalide, il doit être comme suit 'année-mm-jj'.
|
||||
milestones.invalid_due_date_format=Le format de la date d'échéance est invalide, il doit être comme suit 'AAAA-mm-jj'.
|
||||
milestones.create_success=Le Jalon '%s' a été crée avec succès !
|
||||
milestones.edit=Éditer le Jalon
|
||||
milestones.edit_subheader=Utilisez une description claire pour les jalons pour ne pas induire les gens en erreur.
|
||||
@@ -517,24 +540,24 @@ settings.basic_settings=Paramètres de base
|
||||
settings.danger_zone=Zone de danger
|
||||
settings.site=Site officiel
|
||||
settings.update_settings=Valider
|
||||
settings.change_reponame_prompt=This change will affect how links relate to the repository.
|
||||
settings.change_reponame_prompt=Ce changement affectera comment les liens sont reliés avec le dépôt.
|
||||
settings.transfer=Transférer les propriétés
|
||||
settings.transfer_desc=Transfèrer ce dépôt à un autre utilisateur ou une organisation dont vous possédez des droits d'administrateur.
|
||||
settings.new_owner_has_same_repo=Le nouveau propriétaire a déjà un dépôt nommé ainsi.
|
||||
settings.delete=Supprimer ce Référentiel
|
||||
settings.delete_desc=Attention, action irréversible. Soyez sûre de vous.
|
||||
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
|
||||
settings.transfer_notices_2=- You will conserve access if new owner is an organization and if you're one of the owners.
|
||||
settings.transfer_form_title=Please enter following information to confirm your operation:
|
||||
settings.delete_notices_1=- This operation <strong>CANNOT</strong> be undone.
|
||||
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
|
||||
settings.delete_notices_fork_1=- If this repository is public, all forks will be became independent after deletion.
|
||||
settings.delete_notices_fork_2=- If this repository is private, all forks will be removed at the same time.
|
||||
settings.delete_notices_fork_3=- If you want to keep all forks after deletion, please change visibility of this repository to public first.
|
||||
settings.transfer_notices_1=-Vous perdrez l'accès si le nouveau propriétaire est un utilisateur individuel.
|
||||
settings.transfer_notices_2=-Vous préserverez l'accès si le nouveau propriétaire est une organisation et si vous y appartenez.
|
||||
settings.transfer_form_title=Veuillez recopier le texte suivant afin de confirmer votre opération :
|
||||
settings.delete_notices_1=- Cette opération <strong>ne peut pas </strong> être annulée.
|
||||
settings.delete_notices_2=-Cette opération supprimera définitivement le dépôt, y compris les données Git, problèmes, commentaires et accès des collaborateurs.
|
||||
settings.delete_notices_fork_1=-Si ce dépôt est public, tous les Forks vont devenir indépendant après la suppression.
|
||||
settings.delete_notices_fork_2=-Si ce dépôt est privé, tous les Forks seront supprimés en même temps.
|
||||
settings.delete_notices_fork_3=-Si vous souhaitez conserver tous les forks après suppression, veuillez tout d'abord modifier la visibilité de ce dépôt en public.
|
||||
settings.update_settings_success=Options mises à jour avec succès.
|
||||
settings.transfer_owner=Nouveau propriétaire
|
||||
settings.make_transfer=Transférer
|
||||
settings.transfer_succeed=Propriétés transférées avec succès.
|
||||
settings.transfer_succeed=Le contrôle du dépôt a été transféré avec succès.
|
||||
settings.confirm_delete=Confirmer la suppression
|
||||
settings.add_collaborator=Ajouter un collaborateur
|
||||
settings.add_collaborator_success=Nouveau collaborateur ajouté.
|
||||
@@ -542,14 +565,14 @@ settings.remove_collaborator_success=Collaborateur supprimé.
|
||||
settings.user_is_org_member=Cet utilisateur ne peut pas être ajouté en tant que collaborateur car il fait partie d'une organisation.
|
||||
settings.add_webhook=Ajouter un Webhook
|
||||
settings.hooks_desc=Webhooks aux services externes être notifié lorsque certains événements se produisent sur Gogs. Lorsque les événements spécifiés se produisent, nous vous enverrons une demande POST à chacun des URL que vous fournissez. Apprenez-en davantage dans notre <a target="_blank" href="%s"> Webhooks Guide</a>.
|
||||
settings.webhook_deletion=Delete Webhook
|
||||
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
|
||||
settings.webhook_deletion_success=Webhook has been deleted successfully!
|
||||
settings.webhook.request=Request
|
||||
settings.webhook.response=Response
|
||||
settings.webhook.headers=Headers
|
||||
settings.webhook_deletion=Supprimer le Webhook
|
||||
settings.webhook_deletion_desc=Supprimer ce webhook va supprimer ses informations et l'historique de livraison. Voulez-vous continuer ?
|
||||
settings.webhook_deletion_success=Le webhook a été supprimée avec succès !
|
||||
settings.webhook.request=Requête
|
||||
settings.webhook.response=Réponse
|
||||
settings.webhook.headers=Entêtes
|
||||
settings.webhook.payload=Payload
|
||||
settings.webhook.body=Body
|
||||
settings.webhook.body=Corps
|
||||
settings.githooks_desc=Les Hooks Git sont alimentés par Git lui même. Les Hooks compatibles sont modifiables dans la liste ci-dessous pour effectuer des opérations personnalisées.
|
||||
settings.githook_edit_desc=Si un Hook est inactif, un exemple de contenu vous sera proposé. Un contenu laissé vide signifie un Hook inactif.
|
||||
settings.githook_name=Nom du Hook
|
||||
@@ -559,17 +582,17 @@ settings.add_webhook_desc=Nous vous enverrons une demande <code>POST</code> à l
|
||||
settings.payload_url=URL des Données Utiles
|
||||
settings.content_type=Type de contenu
|
||||
settings.secret=Confidentiel
|
||||
settings.slack_username=Username
|
||||
settings.slack_icon_url=Icon URL
|
||||
settings.slack_color=Color
|
||||
settings.slack_username=Nom d'utilisateur
|
||||
settings.slack_icon_url=URL de l'icône
|
||||
settings.slack_color=Couleur
|
||||
settings.event_desc=Quel évènement ce Webhook doit-il déclencher ?
|
||||
settings.event_push_only=Uniquement les <code>push</code> (soumissions).
|
||||
settings.event_send_everything=I need <strong>everything</strong>.
|
||||
settings.event_choose=Let me choose what I need.
|
||||
settings.event_create=Create
|
||||
settings.event_create_desc=Branch, or tag created
|
||||
settings.event_send_everything=J'ai besoin de <strong>tout</strong>.
|
||||
settings.event_choose=Permettez-moi de choisir ce dont j'ai besoin.
|
||||
settings.event_create=Créer
|
||||
settings.event_create_desc=Branch, ou Tag créé
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push to a repository
|
||||
settings.event_push_desc=Git push vers un dépôt
|
||||
settings.active=Actif
|
||||
settings.active_helper=Les détails seront délivrés lorsque ce Hook sera déclenché.
|
||||
settings.add_hook_success=Nouveau Webhook ajouté.
|
||||
@@ -578,7 +601,7 @@ settings.update_hook_success=Webhook mis à jour.
|
||||
settings.delete_webhook=Supprimer le Webhook
|
||||
settings.recent_deliveries=Livraisons récentes
|
||||
settings.hook_type=Type de Hook
|
||||
settings.add_slack_hook_desc=Ajouter <a href="%s"> Slack</a> intégration dans votre référentiel.
|
||||
settings.add_slack_hook_desc=Intégrer <a href="%s"> Slack</a> à votre dépôt.
|
||||
settings.slack_token=Jeton
|
||||
settings.slack_domain=Domaine
|
||||
settings.slack_channel=Canal
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Une publication avec ce nom de tag a déjà exist
|
||||
|
||||
[org]
|
||||
org_name_holder=Nom d'organisation
|
||||
org_full_name_holder=Nom complet de l'organisation
|
||||
org_name_helper=Idéalement, un nom d'organisation devrait être court et mémorable.
|
||||
org_email_helper=Le courriel de l'organisation recevra toutes les notifications et confirmations.
|
||||
create_org=Créer une organisation
|
||||
repo_updated=Mis à jour
|
||||
people=Contacts
|
||||
@@ -655,9 +678,9 @@ settings.full_name=Non Complet
|
||||
settings.website=Site Web
|
||||
settings.location=Localisation
|
||||
settings.update_settings=Valider
|
||||
settings.change_orgname=Organisation renommée
|
||||
settings.change_orgname_desc=L'Organisation a été renommée. Cela affecte tous les liens relatifs à cette organisation. Continuer ?
|
||||
settings.update_setting_success=Paramètres d'organisation modifiés avec succès.
|
||||
settings.change_orgname_prompt=Cette modification affectera comment des liens se rapportent à l'organisation.
|
||||
settings.update_avatar_success=Les paramètres de l'avatar de l'organisation a été mis à jour avec succès.
|
||||
settings.delete=Supprimer l'organisation
|
||||
settings.delete_account=Supprimer cette organisation
|
||||
settings.delete_prompt=Cela supprimera cette organisation définitivement. Cette opération est <strong>IRRÉVERSIBLE</strong> !
|
||||
@@ -700,9 +723,9 @@ teams.read_permission_desc=Cette équipe permet l'accès en <strong>lecture</str
|
||||
teams.write_permission_desc=Cette équipe permet l'accès en <strong>écriture</strong> : les membres peuvent participer à ses Référentiels.
|
||||
teams.admin_permission_desc=Cette équipe permet l'accès en <strong>administrateur</strong> : les membres peuvent voir, participer et ajouter des collaborateurs à ses Référentiels.
|
||||
teams.repositories=Référentiels de l'Équipe
|
||||
teams.add_team_repository=Ajouter un Référentiel à l'Équipe
|
||||
teams.add_team_repository=Ajouter un Dépôt à l'Équipe
|
||||
teams.remove_repo=Supprimer
|
||||
teams.add_nonexistent_repo=Référentiel inexistant, veuillez d'abord le créer.
|
||||
teams.add_nonexistent_repo=Dépôt inexistant, veuillez d'abord le créer.
|
||||
|
||||
[admin]
|
||||
dashboard=Tableau de bord
|
||||
@@ -713,8 +736,9 @@ authentication=Authentifications
|
||||
config=Configuration
|
||||
notices=Notes Systèmes
|
||||
monitor=Supervision
|
||||
prev=Préc.
|
||||
next=Suiv.
|
||||
first_page=Première
|
||||
last_page=Dernière
|
||||
total=Total : %d
|
||||
|
||||
dashboard.statistic=Statistiques
|
||||
dashboard.operations=Opérations
|
||||
@@ -773,26 +797,31 @@ users.activated=Activés
|
||||
users.admin=Administrateur
|
||||
users.repos=Dépôts
|
||||
users.created=Créés
|
||||
users.send_register_notify=Envoyer une Notification d'enregistrement à l'utilisateur
|
||||
users.new_success=Nouveau compte '%s' a été créé avec succès.
|
||||
users.edit=Éditer
|
||||
users.auth_source=Source d'Autorisation
|
||||
users.auth_source=Sources d'authentification
|
||||
users.local=Locales
|
||||
users.auth_login_name=Autorisation de connexion
|
||||
users.auth_login_name=Nom d'utilisateur d'authentification
|
||||
users.password_helper=Laissez-le vide pour ne pas changer.
|
||||
users.update_profile_success=Profil mis à jour avec succès.
|
||||
users.edit_account=Modifier le Compte
|
||||
users.is_activated=Ce compte est activé
|
||||
users.is_admin=Ce compte possède un niveau d'accès administrateur
|
||||
users.allow_git_hook=Ce compte dispose des autorisations pour créer des crochets de Git
|
||||
users.allow_import_local=Ce compte dispose des permissions nécessaire à l'import des dépôts locaux
|
||||
users.update_profile=Mettre le profil à jour
|
||||
users.delete_account=Supprimer ce Compte
|
||||
users.still_own_repo=Ce compte possède toujours des dépôts. Vous devez d'abord les supprimer ou les transférer.
|
||||
users.still_has_org=Ce compte a toujours membres de l'organisation, vous avez à gauche ou supprimez tout d'abord.
|
||||
users.deletion_success=Le compte a été supprimé avec succès !
|
||||
|
||||
orgs.org_manage_panel=Gestion des Organisations
|
||||
orgs.name=Nom
|
||||
orgs.teams=Équipes
|
||||
orgs.members=Membres
|
||||
|
||||
repos.repo_manage_panel=Gestion des Référentiels
|
||||
repos.repo_manage_panel=Gestion des Dépôts
|
||||
repos.owner=Propriétaire
|
||||
repos.name=Nom
|
||||
repos.private=Privé
|
||||
@@ -800,41 +829,47 @@ repos.watches=Suivi par
|
||||
repos.stars=Votes
|
||||
repos.issues=Problèmes
|
||||
|
||||
auths.auth_manage_panel=Gestion des Autorisations
|
||||
auths.new=Ajouter une Nouvelle Source d'Autorisation
|
||||
auths.auth_manage_panel=Panel d'administration des authentifications
|
||||
auths.new=Ajouter une nouvelle source d'authentification
|
||||
auths.name=Nom
|
||||
auths.type=Type
|
||||
auths.enabled=Activé
|
||||
auths.updated=Mis à jour
|
||||
auths.auth_type=Type d'Autorisation
|
||||
auths.auth_name=Nom d'Autorisation
|
||||
auths.auth_type=Type d'authentification
|
||||
auths.auth_name=Nom de l'authentification
|
||||
auths.domain=Domaine
|
||||
auths.host=Hôte
|
||||
auths.port=Port
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.user_base=User Search Base
|
||||
auths.bind_password=Bind mot de passe
|
||||
auths.bind_password_helper=AVERTISSEMENT : Ce mot de passe est stocké en texte clair. N'utilisez pas le mot de passe d'un compte doté de privilèges élevé.
|
||||
auths.user_base=Utilisateur Search Base
|
||||
auths.user_dn=Utilisateur DN
|
||||
auths.attribute_name=Attribut du prénom
|
||||
auths.attribute_surname=Attribut du nom de famille
|
||||
auths.attribute_mail=Attribut de l'e-mail
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.filter=Utilisateur Filter
|
||||
auths.admin_filter=Administrateur Filter
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Type d'Autorisation SMTP
|
||||
auths.smtp_auth=Type d'authentification SMTP
|
||||
auths.smtphost=Hôte SMTP
|
||||
auths.smtpport=Port SMTP
|
||||
auths.allowed_domains=Domaines autorisés
|
||||
auths.allowed_domains_helper=Laissez-le vide pour ne pas restreindre de domaines. Plusieurs domaines doivent être séparés par une virgule «, ».
|
||||
auths.enable_tls=Activer le Chiffrement TLS
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.skip_tls_verify=Ne pas vérifier TLS
|
||||
auths.pam_service_name=Nom du Service PAM
|
||||
auths.enable_auto_register=Connexion Automatique
|
||||
auths.tips=Conseils
|
||||
auths.edit=Modifier les Paramètres d'Autorisation
|
||||
auths.edit=Modifier les paramètres d'authentification
|
||||
auths.activated=Authentification activée
|
||||
auths.update_success=Paramètres d'autorisation mis à jour avec succès.
|
||||
auths.update=Mettre les Paramètres d'Autorisation à jour
|
||||
auths.delete=Supprimer cette Autorisation
|
||||
auths.delete_auth_title=Suppression d'Autorisation
|
||||
auths.delete_auth_desc=Cette autorisation sera supprimée. Continuer ?
|
||||
auths.new_success=Nouvelle authentification «%s » a été ajoutée avec succès.
|
||||
auths.update_success=Les paramètre d'authentification a été mis à jour avec succès.
|
||||
auths.update=Mettre à jour les paramètres d'authentifications
|
||||
auths.delete=Supprimer cette authentification
|
||||
auths.delete_auth_title=Suppression de l'authentification
|
||||
auths.delete_auth_desc=Cette autorisation sera supprimée. Continuer?
|
||||
auths.deletion_success=L'authentification a été supprimée avec succès !
|
||||
|
||||
config.server_config=Configuration du Serveur
|
||||
config.app_name=Nom de l'Application
|
||||
@@ -845,7 +880,7 @@ config.offline_mode=Mode hors-ligne
|
||||
config.disable_router_log=Désactiver la Journalisation du Routeur
|
||||
config.run_user=Entrer un Utilisateur
|
||||
config.run_mode=Mode d'Éxécution
|
||||
config.repo_root_path=Emplacement Racine du Référentiel
|
||||
config.repo_root_path=Emplacement des Dépôts
|
||||
config.static_file_root_path=Emplacement Racine du Fichier Statique
|
||||
config.log_file_root_path=Emplacement Racine du Fichier Journal
|
||||
config.script_type=Type de Script
|
||||
@@ -858,14 +893,16 @@ config.db_user=Utilisateur
|
||||
config.db_ssl_mode=Mode SSL
|
||||
config.db_ssl_mode_helper=("postgres" uniquement)
|
||||
config.db_path=Emplacement
|
||||
config.db_path_helper=("sqlite3" uniquement)
|
||||
config.db_path_helper=(pour « sqlite3 » et « TIDB »)
|
||||
config.service_config=Configuration du Service
|
||||
config.register_email_confirm=Nécessite une confirmation par courriel
|
||||
config.disable_register=Désactiver l'Enregistrement
|
||||
config.show_registration_button=Afficher le bouton d'enregistrement
|
||||
config.require_sign_in_view=Connexion Obligatoire pour Visualiser
|
||||
config.mail_notify=Mailer les Notifications
|
||||
config.enable_cache_avatar=Activer le Cache d'Avatar
|
||||
config.mail_notify=Mailer les Notifications
|
||||
config.disable_key_size_check=Désactiver la vérification de la taille de clé minimale
|
||||
config.enable_captcha=Activez le Captcha
|
||||
config.active_code_lives=Limites de Code Actif
|
||||
config.reset_password_code_lives=Réinitialiser le Mot De Passe des Limites de Code
|
||||
config.webhook_config=Configuration Webhook
|
||||
@@ -912,20 +949,20 @@ monitor.execute_time=Heure d'Éxécution
|
||||
|
||||
notices.system_notice_list=Notes Systèmes
|
||||
notices.type=Type
|
||||
notices.type_1=Référentiel
|
||||
notices.type_1=Dépôt
|
||||
notices.desc=Description
|
||||
notices.op=Opération
|
||||
notices.delete_success=Note système supprimée avec succès.
|
||||
|
||||
[action]
|
||||
create_repo=a crée le Référentiel <a href="%s">%s</a>
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
create_repo=a crée le dépôt <a href="%s">%s</a>
|
||||
rename_repo=rebaptisé le dépôt de <code>%[1]s</code> à <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=a soumis à <a href="%s/src/%s">%[2]s</a> chez <a href="%[1]s">%[3]s</a>
|
||||
create_issue=`a ouvert un problème <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`pull request créée le <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue=`a commenté le problème <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=a transféré le Référentiel <code>%s</code> à <a href="%s">%s</a>
|
||||
merge_pull_request=`pull request fusionné le <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=a transféré le dépôt <code>%s</code> à <a href="%s">%s</a>
|
||||
push_tag=a tagé <a href="%s/src/%s">%[2]s</a> à <a href="%[1]s">%[3]s</a>
|
||||
compare_2_commits=Comparer ces 2 commissions
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=Pannello di controllo
|
||||
explore=Esplora
|
||||
help=Aiuto
|
||||
sign_in=Accedi
|
||||
social_sign_in=Login Sociale: Passo 2 <small>associare l'account</small>
|
||||
sign_out=Esci
|
||||
sign_up=Registrati
|
||||
register=Registrati
|
||||
@@ -14,8 +13,8 @@ version=Versione
|
||||
page=Pagina
|
||||
template=Template
|
||||
language=Lingua
|
||||
create_new=Create new...
|
||||
user_profile_and_more=User profile and more
|
||||
create_new=Crea...
|
||||
user_profile_and_more=Profilo utente e altro
|
||||
signed_in_as=Signed in as
|
||||
|
||||
username=Nome utente
|
||||
@@ -35,8 +34,8 @@ manage_org=Gestisci le organizzazioni
|
||||
admin_panel=Pannello di amministrazione
|
||||
account_settings=Impostazioni dell'account
|
||||
settings=Impostazioni
|
||||
your_profile=Your Profile
|
||||
your_settings=Your Settings
|
||||
your_profile=Profilo
|
||||
your_settings=Impostazioni
|
||||
|
||||
news_feed=Notizie
|
||||
pull_requests=Pull Requests
|
||||
@@ -54,7 +53,8 @@ code=Codice
|
||||
[install]
|
||||
install=Installazione
|
||||
title=Passi d'installazione per il primo avvio
|
||||
requite_db_desc=Gogs richiede MySql, PostgresSQL o SQLite.
|
||||
docker_helper=Se stai utilizzando Gogs su Docker, per favore leggi le <a target="_blank" href="%s">Linee guida</a> con attenzione prima di cambiare qualcosa su questa pagina!
|
||||
requite_db_desc=Gogs necessita MySQL, PostgreSQL, SQLite3 o TiDB.
|
||||
db_title=Impostazioni Database
|
||||
db_type=Tipo di database
|
||||
host=Host
|
||||
@@ -64,8 +64,11 @@ db_name=Nome del database
|
||||
db_helper=Utilizza il motore INNODB con codifica utf8_general_ci per MySQL.
|
||||
ssl_mode=Modalità SSL
|
||||
path=Percorso
|
||||
sqlite_helper=Percorso per database SQLite3.
|
||||
err_empty_sqlite_path=Il percorso del database SQLite3 non può essere vuoto.
|
||||
sqlite_helper=The file path of SQLite3 or TiDB database.
|
||||
err_empty_db_path=SQLite3 or TiDB database path cannot be empty.
|
||||
err_invalid_tidb_name=TiDB database name does not allow characters "." and "-".
|
||||
no_admin_and_disable_registration=Non puoi disabilitare la registrazione senza aver creato un amministratore.
|
||||
err_empty_admin_password=La password dell'amministratore non puo' essere vuota.
|
||||
|
||||
general_title=Impostazioni di Base dell'Applicazione
|
||||
app_name=Nome Applicazione
|
||||
@@ -76,7 +79,7 @@ run_user=Esegui con l'utente
|
||||
run_user_helper=L'utente deve avere accesso al percorso root del repository e avviare Gogs.
|
||||
domain=Dominio
|
||||
domain_helper=Questo modifica lo SSH clone URLs.
|
||||
ssh_port=SSH Port
|
||||
ssh_port=Porta SSH
|
||||
ssh_port_helper=Port number which your SSH server is using, leave it empty to disable SSH feature.
|
||||
http_port=Porta HTTP
|
||||
http_port_helper=Porta di ascolto dell'applicazione.
|
||||
@@ -99,6 +102,8 @@ disable_gravatar=Disable Gravatar Service
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_registration=Disabilita Registrazione Manuale
|
||||
disable_registration_popup=Disabilita la registrazione manuale degli utenti, solo gli amministratori possono creare account.
|
||||
enable_captcha=Abilita Captcha
|
||||
enable_captcha_popup=Require validate captcha for user self-registration.
|
||||
require_sign_in_view=Abilita Richiesta di Accesso per Vedere le Pagine
|
||||
require_sign_in_view_popup=Solo gli utenti loggati possono vedere le pagine, i visitatori potranno vedere solo le pagine di accesso e registrazione.
|
||||
admin_setting_desc=Non devi per forza creare un account admin proprio adesso, ma comunque l'utente ID=1 otterrà l'accesso da amministratore automaticamente.
|
||||
@@ -106,7 +111,7 @@ admin_title=Impostazioni Account Amministratore
|
||||
admin_name=Nome utente
|
||||
admin_password=Password
|
||||
confirm_password=Conferma Password
|
||||
admin_email=E-mail
|
||||
admin_email=Admin E-mail
|
||||
install_gogs=Installare Gogs
|
||||
test_git_failed=Fallito il test del comando git: %v
|
||||
sqlite3_not_available=Questa versione non supporta SQLite3, si prega di scaricare la versione binaria ufficiale da %s, NON la versione gobuild.
|
||||
@@ -125,9 +130,9 @@ my_repos=I miei Repository
|
||||
collaborative_repos=Repository Condivisi
|
||||
my_orgs=Le mie Organizzazioni
|
||||
my_mirrors=I miei Mirror
|
||||
view_home=View %s
|
||||
view_home=Vedi %s
|
||||
|
||||
issues.in_your_repos=In your repositories
|
||||
issues.in_your_repos=Nei tuoi repository
|
||||
|
||||
[explore]
|
||||
repos=Repository
|
||||
@@ -143,7 +148,6 @@ forgot_password=Password dimenticata
|
||||
forget_password=Password dimenticata?
|
||||
sign_up_now=Bisogno di un account? Iscriviti ora.
|
||||
confirmation_mail_sent_prompt=Una nuova email di conferma è stata inviata a <b>%s</b>, verifica la tua casella di posta entro le prossime %d ore per completare la registrazione.
|
||||
sign_in_email=Accedi al tuo indirizzo e-mail
|
||||
active_your_account=Attiva il tuo Account
|
||||
resent_limit_prompt=Siamo spiacenti, si stanno inviando e-mail di attivazione troppo spesso. Si prega di attendere 3 minuti.
|
||||
has_unconfirmed_mail=Ciao %s, hai un indirizzo di posta elettronica non confermato (<b>%s</b>). Se non hai ricevuto una e-mail di conferma o vuoi riceverla nuovamente, fare clic sul pulsante qui sotto.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Siamo spiacenti, il codice di conferma è scaduto o non valido.
|
||||
reset_password_helper=Clicca qui per reimpostare la password
|
||||
password_too_short=La lunghezza della password non può essere meno 6 caratteri.
|
||||
|
||||
[mail]
|
||||
activate_account=Per favore attiva il tuo account
|
||||
activate_email=Verifica il tuo indirizzo e-mail
|
||||
reset_password=Reimposta la tua password
|
||||
register_success=Registrazione completata con successo, Benvenuto
|
||||
|
||||
[modal]
|
||||
yes=Sì
|
||||
no=No
|
||||
@@ -181,6 +191,7 @@ min_size_error=` deve contenere almeno %s caratteri.`
|
||||
max_size_error=` deve contenere massimo %s caratteri.`
|
||||
email_error=` non è un indirizzo e-mail valido.`
|
||||
url_error=` non è un URL valido.`
|
||||
include_error=` deve contenere la stringa '%s'.`
|
||||
unknown_error=Errore sconosciuto:
|
||||
captcha_incorrect=Il Captcha non corrisponde.
|
||||
password_not_match=Le due password non corrispondono.
|
||||
@@ -241,7 +252,7 @@ location=Posizione
|
||||
update_profile=Aggiorna Profilo
|
||||
update_profile_success=Il tuo profilo è stato aggiornato con successo.
|
||||
change_username=Username Cambiato
|
||||
change_username_desc=Hai cambiato il tuo username. Questo influenzerà il modo in cui i link si relazionano al tuo account. Vuoi proseguire?
|
||||
change_username_prompt=This change will affect the way how links relate to your account.
|
||||
continue=Continua
|
||||
cancel=Annulla
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=Le tue impostazioni avatar sono state aggiornate con succe
|
||||
change_password=Cambia Password
|
||||
old_password=Password attuale
|
||||
new_password=Nuova Password
|
||||
retype_new_password=Re-inserisci la password
|
||||
password_incorrect=La Password attuale non è corretta.
|
||||
change_password_success=La tua password è stata cambiata con successo. Ora puoi accedere usando la nuova password.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=Il tuo indirizzo e-mail primario sarà usato per le notifiche e altre
|
||||
primary=Primario
|
||||
primary_email=Imposta come primario
|
||||
delete_email=Elimina
|
||||
email_deletion=E-mail Deletion
|
||||
email_deletion_desc=Delete this e-mail address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success=E-mail has been deleted successfully!
|
||||
add_new_email=Aggiungi un nuovo indirizzo E-mail
|
||||
add_email=Aggiungi E-mail
|
||||
add_email_confirmation_sent=Una nuova email di conferma è stata inviata a <b>%s</b>, per favore controlla la tua posta in arrivo nelle prossime %d ore per completare il processo di registrazione.
|
||||
add_email_confirmation_sent=Una nuova email di conferma è stata inviata a '%s', per favore controlla la tua posta in arrivo nelle prossime %d ore per completare il processo di registrazione.
|
||||
add_email_success=Il tuo nuovo indirizzo e-mail è stato aggiunto con successo.
|
||||
|
||||
manage_ssh_keys=Gestisci chiavi SSH
|
||||
@@ -283,12 +298,12 @@ add_key_success=New SSH key '%s' has been added successfully!
|
||||
delete_key=Elimina
|
||||
ssh_key_deletion=SSH Key Deletion
|
||||
ssh_key_deletion_desc=Delete this SSH key will remove all related accesses for your account. Do you want to continue?
|
||||
ssh_key_deletion_success=SSH key has been deleted successfully!
|
||||
ssh_key_deletion_success=La chiave SSH e' stata cancellata con successo!
|
||||
add_on=Aggiunto il
|
||||
last_used=Ultimo accesso il
|
||||
no_activity=Nessuna attività recente
|
||||
key_state_desc=This key is used in last 7 days
|
||||
token_state_desc=This token is used in last 7 days
|
||||
key_state_desc=Hai utilizzato questa chiave negli ultimi 7 giorni
|
||||
token_state_desc=Questo token e' satato utilizzato negli ultimi 7 giorni
|
||||
|
||||
manage_social=Gestisci gli Account Sociali Associati
|
||||
social_desc=Questa è un elenco degli account sociali associati. Rimuovere qualsiasi account che non si riconosce.
|
||||
@@ -319,18 +334,19 @@ repo_name=Nome Repository
|
||||
repo_name_helper=I migliori nomi dei repository sono brevi, facili da memorizzare e <strong>univoci</strong>.
|
||||
visibility=Visibilità
|
||||
visiblity_helper=This repository is <span class="ui red text">Private</span>
|
||||
visiblity_helper_forced=Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
fork_repo=Forka Repository
|
||||
fork_from=Forka da
|
||||
fork_visiblity_helper=Non puoi cambiare la visibilità di un repository forkato.
|
||||
repo_desc=Descrizione
|
||||
repo_lang=Lingua
|
||||
repo_lang_helper=Select .gitignore files
|
||||
repo_lang_helper=Seleziona file .gitignore
|
||||
license=Licenza
|
||||
license_helper=Selezionare un file di licenza
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
readme_helper=Seleziona un template per il readme
|
||||
auto_init=Initialize this repository with selected files and template
|
||||
create_repo=Crea Repository
|
||||
default_branch=Ramo (Branch) predefinito
|
||||
mirror_interval=Intervallo Mirror (in ore)
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=This repository will be a <span class="text blue">mirror</sp
|
||||
migrate_repo=Migra Repository
|
||||
migrate.clone_address=Duplica Indirizzo
|
||||
migrate.clone_address_desc=This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=Percorso locale non valido, non esiste o non è una cartella.
|
||||
|
||||
forked_from=forkato da
|
||||
fork_from_self=Non puoi forkare il tuo stesso repository!
|
||||
copy_link=Copia
|
||||
copy_link_success=Copiato!
|
||||
copy_link_error=Press ⌘-C or Ctrl-C to copy
|
||||
click_to_copy=Copia negli appunti
|
||||
copied=OK copiato
|
||||
clone_helper=Hai bisogno di aiuto per la clonazione? Visita <a target="_blank" href="%s">Aiuto</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Guida rapida
|
||||
clone_this_repo=Clona questo repository
|
||||
create_new_repo_command=Crea nuovo repository da riga di comando
|
||||
push_exist_repo=Push un repo esistente dalla riga di comando
|
||||
repo_is_empty=This repository is empty, please come back later!
|
||||
|
||||
|
||||
branch=Ramo (Branch)
|
||||
tree=Albero (Tree)
|
||||
@@ -390,15 +411,15 @@ commits.older=Più vecchio
|
||||
commits.newer=Più recente
|
||||
|
||||
issues.new=Nuovo Problema
|
||||
issues.new.labels=Labels
|
||||
issues.new.no_label=No Label
|
||||
issues.new.clear_labels=Clear labels
|
||||
issues.new.milestone=Milestone
|
||||
issues.new.labels=Etichette
|
||||
issues.new.no_label=Nessuna etichetta
|
||||
issues.new.clear_labels=Pulisci le etichette
|
||||
issues.new.milestone=Traguardo
|
||||
issues.new.no_milestone=No Milestone
|
||||
issues.new.clear_milestone=Clear milestone
|
||||
issues.new.open_milestone=Open Milestones
|
||||
issues.new.closed_milestone=Closed Milestones
|
||||
issues.new.assignee=Assignee
|
||||
issues.new.assignee=Assegnatario
|
||||
issues.new.clear_assignee=Clear assignee
|
||||
issues.new.no_assignee=No assignee
|
||||
issues.create=Create Issue
|
||||
@@ -418,7 +439,7 @@ issues.filter_type.all_issues=Tutti i problemi
|
||||
issues.filter_type.assigned_to_you=Assegnati a te
|
||||
issues.filter_type.created_by_you=Creati da te
|
||||
issues.filter_type.mentioning_you=Che ti riguardano
|
||||
issues.filter_sort=Sort
|
||||
issues.filter_sort=Ordina
|
||||
issues.filter_sort.latest=Newest
|
||||
issues.filter_sort.oldest=Oldest
|
||||
issues.filter_sort.recentupdate=Recently updated
|
||||
@@ -434,18 +455,18 @@ issues.closed_title=Closed
|
||||
issues.num_comments=%d comments
|
||||
issues.commented_at=`commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=There is no content yet.
|
||||
issues.close_issue=Close
|
||||
issues.close_comment_issue=Close and comment
|
||||
issues.close_issue=Chiudi
|
||||
issues.close_comment_issue=Comment and close
|
||||
issues.reopen_issue=Reopen
|
||||
issues.reopen_comment_issue=Reopen and comment
|
||||
issues.create_comment=Comment
|
||||
issues.reopen_comment_issue=Comment and reopen
|
||||
issues.create_comment=Commento
|
||||
issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Poster
|
||||
issues.admin=Admin
|
||||
issues.owner=Owner
|
||||
issues.sign_up_for_free=Sign up for free
|
||||
issues.admin=Amministratore
|
||||
issues.owner=Proprietario
|
||||
issues.sign_up_for_free=Registrati gratuitamente
|
||||
issues.sign_in_require_desc=to join this conversation. Already have an account? <a href="%s">Sign in to comment</a>
|
||||
issues.edit=Edit
|
||||
issues.cancel=Cancel
|
||||
@@ -469,7 +490,7 @@ pulls.filter_branch=Filter branch
|
||||
pulls.no_results=No results found.
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Create Pull Request
|
||||
pulls.create=Crea Pull Request
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversation
|
||||
@@ -479,10 +500,12 @@ pulls.reopen_to_merge=Please reopen this pull request to perform merge operation
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking=The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.merge_pull_request=Unisci Pull Request
|
||||
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new=New Milestone
|
||||
milestones.open_tab=%d Open
|
||||
@@ -497,7 +520,7 @@ milestones.title=Title
|
||||
milestones.desc=Description
|
||||
milestones.due_date=Due Date (optional)
|
||||
milestones.clear=Clear
|
||||
milestones.invalid_due_date_format=Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.invalid_due_date_format=Due date format is invalid, must be 'yyyy-mm-dd'.
|
||||
milestones.create_success=Milestone '%s' has been created successfully!
|
||||
milestones.edit=Edit Milestone
|
||||
milestones.edit_subheader=Use better description for milestones so people won't be confused.
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Un rilascio con questo tag esiste già.
|
||||
|
||||
[org]
|
||||
org_name_holder=Nome dell'Organizzazione
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=Le migliori organizzazioni hanno nomi brevi e memorabili.
|
||||
org_email_helper=L'Email dell'organizzazione riceve tutte le notifiche e le conferme.
|
||||
create_org=Crea Organizzazione
|
||||
repo_updated=Aggiornato
|
||||
people=Utenti
|
||||
@@ -655,9 +678,9 @@ settings.full_name=Nome Completo
|
||||
settings.website=Sito Web
|
||||
settings.location=Residenza
|
||||
settings.update_settings=Aggiorna Impostazioni
|
||||
settings.change_orgname=Il nome dell'organizzazione è cambiato
|
||||
settings.change_orgname_desc=Il nome dell'organizzazione name è cambiato. Questo influenzerà come collegamenti si riferiscono all'organizzazione. Si desidera continuare?
|
||||
settings.update_setting_success=Impostazioni dell'organizzazione aggiornate con successo.
|
||||
settings.change_orgname_prompt=This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success=Organization avatar setting has been updated successfully.
|
||||
settings.delete=Elimina organizzazione
|
||||
settings.delete_account=Elimina questa organizzazione
|
||||
settings.delete_prompt=L'organizzazione verrà rimossa definitivamente, e questa operazione <strong>NON PUÒ</strong> essere annullata!
|
||||
@@ -713,8 +736,9 @@ authentication=Autenticazioni
|
||||
config=Configurazione
|
||||
notices=Avvisi di sistema
|
||||
monitor=Monitoraggio
|
||||
prev=Prec.
|
||||
next=Succ.
|
||||
first_page=First
|
||||
last_page=Last
|
||||
total=Total: %d
|
||||
|
||||
dashboard.statistic=Statistiche
|
||||
dashboard.operations=Operazioni
|
||||
@@ -773,19 +797,24 @@ users.activated=Attivato
|
||||
users.admin=Amministratore
|
||||
users.repos=Repo
|
||||
users.created=Creato
|
||||
users.send_register_notify=Send Registration Notification To User
|
||||
users.new_success=New account '%s' has been created successfully.
|
||||
users.edit=Modifica
|
||||
users.auth_source=Origine Autorizzazione
|
||||
users.auth_source=Authentication Source
|
||||
users.local=Locale
|
||||
users.auth_login_name=Nome di Accesso dell'Autorizzazione
|
||||
users.auth_login_name=Authentication Login Name
|
||||
users.password_helper=Leave it empty to remain unchanged.
|
||||
users.update_profile_success=Profilo dell'account aggiornato con successo.
|
||||
users.edit_account=Modifica Account
|
||||
users.is_activated=Questo account è attivato
|
||||
users.is_admin=Questo account ha permessi di amministratore
|
||||
users.allow_git_hook=Questo account ha il permesso di creare hooks di Git
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=Aggiornare Profilo Account
|
||||
users.delete_account=Elimina Questo Account
|
||||
users.still_own_repo=Questo account possiede ancora almeno un repository, devi prima cancellarli o trasferirli.
|
||||
users.still_has_org=Questo account appartiene ancora ad almeno un'organizzazione, è necessario prima abbandonarle o eliminale.
|
||||
users.deletion_success=Account has been deleted successfully!
|
||||
|
||||
orgs.org_manage_panel=Pannello Gestione Organizzazioni
|
||||
orgs.name=Nome
|
||||
@@ -800,41 +829,47 @@ repos.watches=Segue
|
||||
repos.stars=Voti
|
||||
repos.issues=Problemi
|
||||
|
||||
auths.auth_manage_panel=Pannello Gestione Autorizzazioni
|
||||
auths.new=Aggiungi Nuova Origine Autorizzazione
|
||||
auths.auth_manage_panel=Authentication Manage Panel
|
||||
auths.new=Add New Source
|
||||
auths.name=Nome
|
||||
auths.type=Tipo
|
||||
auths.enabled=Attivo
|
||||
auths.updated=Aggiornato
|
||||
auths.auth_type=Tipo di Autorizzazione
|
||||
auths.auth_name=Nome Autorizzazione
|
||||
auths.auth_type=Authentication Type
|
||||
auths.auth_name=Authentication Name
|
||||
auths.domain=Dominio
|
||||
auths.host=Host
|
||||
auths.port=Porta
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.bind_password_helper=Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base=User Search Base
|
||||
auths.user_dn=User DN
|
||||
auths.attribute_name=Attributo Nome
|
||||
auths.attribute_surname=Attributo Cognome
|
||||
auths.attribute_mail=Attributo Email
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Tipo di Autorizzazione SMTP
|
||||
auths.smtp_auth=SMTP Authentication Type
|
||||
auths.smtphost=Host SMTP
|
||||
auths.smtpport=Porta SMTP
|
||||
auths.allowed_domains=Allowed Domains
|
||||
auths.allowed_domains_helper=Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
|
||||
auths.enable_tls=Abilitare Crittografia TLS
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.pam_service_name=Nome del Servizio PAM
|
||||
auths.enable_auto_register=Abilitare Registrazione Automatica
|
||||
auths.tips=Consigli
|
||||
auths.edit=Modificare Impostazioni Autorizzazioni
|
||||
auths.edit=Edit Authentication Setting
|
||||
auths.activated=Questa Autenticazione è stata attivata
|
||||
auths.update_success=Impostazione dell'Autorizzazione aggiornate con successo.
|
||||
auths.update=Aggiorna Impostazioni Autorizzazioni
|
||||
auths.delete=Elimina Questa Autorizzazione
|
||||
auths.delete_auth_title=Eliminazione Autorizzazione
|
||||
auths.delete_auth_desc=Questa autorizzazione sarà cancellata, vuoi continuare?
|
||||
auths.new_success=New authentication '%s' has been added successfully.
|
||||
auths.update_success=Authentication setting has been updated successfully.
|
||||
auths.update=Update Authentication Setting
|
||||
auths.delete=Delete This Authentication
|
||||
auths.delete_auth_title=Authentication Deletion
|
||||
auths.delete_auth_desc=This authentication is going to be deleted, do you want to continue?
|
||||
auths.deletion_success=Authentication has been deleted successfully!
|
||||
|
||||
config.server_config=Configurazione Server
|
||||
config.app_name=Nome Applicazione
|
||||
@@ -858,14 +893,16 @@ config.db_user=Utente
|
||||
config.db_ssl_mode=Modalità SSL
|
||||
config.db_ssl_mode_helper=(solo per "postgres")
|
||||
config.db_path=Percorso
|
||||
config.db_path_helper=(solo per "sqlite3")
|
||||
config.db_path_helper=(for "sqlite3" and "tidb")
|
||||
config.service_config=Configurazione Servizio
|
||||
config.register_email_confirm=Richiedono Conferma dell'Email
|
||||
config.disable_register=Disabilita Registrazione
|
||||
config.show_registration_button=Mostra Pulsane Registrazione
|
||||
config.require_sign_in_view=Richiesto Accesso per Vedere
|
||||
config.mail_notify=Email di Notifica
|
||||
config.enable_cache_avatar=Abilitare Cache dell'Avatar
|
||||
config.mail_notify=Email di Notifica
|
||||
config.disable_key_size_check=Disable Minimum Key Size Check
|
||||
config.enable_captcha=Enable Captcha
|
||||
config.active_code_lives=Attiva Vita del Codice
|
||||
config.reset_password_code_lives=Reimpostare Password della Vita del Codice
|
||||
config.webhook_config=Configurazione Webhook
|
||||
@@ -922,7 +959,7 @@ create_repo=ha creato il repository <a href="%s">%s</a>
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=ha pushato nel <a href="%s/src/%s">%[2]s</a> in <a href="%[1]s">%[3]s</a>
|
||||
create_issue=`ha aperto il problema <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`creata pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue=`ha commentato il problema <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=ha trasferito il repository <code>%s</code> a <a href="%s">%s</a>
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=ダッシュボード
|
||||
explore=エスクプローラ
|
||||
help=ヘルプ
|
||||
sign_in=サインイン
|
||||
social_sign_in=SNSでサインイン: ステップ2 <small>アカウント連携</small>
|
||||
sign_out=サインアウト
|
||||
sign_up=サインアップ
|
||||
register=登録
|
||||
@@ -14,7 +13,7 @@ version=バージョン
|
||||
page=ページ
|
||||
template=テンプレート
|
||||
language=言語
|
||||
create_new=新規作成...
|
||||
create_new=作成...
|
||||
user_profile_and_more=ユーザープロファイルなど
|
||||
signed_in_as=サインイン済み
|
||||
|
||||
@@ -54,7 +53,8 @@ code=コード
|
||||
[install]
|
||||
install=インストール
|
||||
title=初回実行のインストール手順
|
||||
requite_db_desc=Gogs には、MySQL や PostgreSQL 、SQLite3 が必要です。
|
||||
docker_helper=DockerでGogsを稼動する場合、このページに変更を加えるまえに、 <a target="_blank" href="%s">Guidelines</a>をよく読んでください!
|
||||
requite_db_desc=Gogs は、MySQL、PostgreSQL、SQLite3 または TiDB が必要です。
|
||||
db_title=データベース設定
|
||||
db_type=データベースの種類
|
||||
host=ホスト
|
||||
@@ -64,8 +64,11 @@ db_name=データベース名
|
||||
db_helper=Mysql INNODB エンジン utf8_general_ci の文字セットを使用してください。
|
||||
ssl_mode=SSL モード
|
||||
path=パス
|
||||
sqlite_helper=SQLite3 データベースのファイル パス
|
||||
err_empty_sqlite_path=SQLite3 データベースのパスは、空にすることはできません。
|
||||
sqlite_helper=SQLite3 または TiDB のデータベースのファイル パス。
|
||||
err_empty_db_path=SQLite3 または TiDB データベースのパスを空にすることはできません。
|
||||
err_invalid_tidb_name=TiDB データベース名は文字"."と"-"を許可しない。
|
||||
no_admin_and_disable_registration=You cannot disable registration without creating an admin account.
|
||||
err_empty_admin_password=管理者パスワードは空白にできません。
|
||||
|
||||
general_title=Gogs の全般設定
|
||||
app_name=アプリケーション名
|
||||
@@ -76,8 +79,8 @@ run_user=実行ユーザ
|
||||
run_user_helper=ユーザーはリポジトリ ルートパスへのアクセス、及びGogs を実行する権限を所有する必要があります。
|
||||
domain=ドメイン
|
||||
domain_helper=これはSSHクローンURLに影響する。
|
||||
ssh_port=SSH Port
|
||||
ssh_port_helper=Port number which your SSH server is using, leave it empty to disable SSH feature.
|
||||
ssh_port=SSH ポート
|
||||
ssh_port_helper=あならのSSHサーバおポート番号、SSH機能を無効するにはここを空白のままにしてください。
|
||||
http_port=HTTP ポート
|
||||
http_port_helper=アプリケーションが待ち受けするポート番号。
|
||||
app_url=アプリケーションの URL
|
||||
@@ -95,10 +98,12 @@ mail_notify=メール通知を有効にする
|
||||
server_service_title=サーバーとその他のサービスの設定
|
||||
offline_mode=オフラインモード有効化
|
||||
offline_mode_popup=プロダクション モードでCDN を無効にし、すべてのリソースファイルをローカルで提供します 。
|
||||
disable_gravatar=Disable Gravatar Service
|
||||
disable_gravatar=Gravatarのサービスを無効にします
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_registration=自己登録を無効にする
|
||||
disable_registration_popup=自己登録を無効にし、管理者のみがアカウント作成できる
|
||||
enable_captcha=Captchaを有効にする
|
||||
enable_captcha_popup=ユーザによる自己登録のため、有効なcaptchaが必要です。
|
||||
require_sign_in_view=サインインしたユーザのみページ閲覧を許可
|
||||
require_sign_in_view_popup=サインインしたユーザのみがページを閲覧できます。ビジターはサインインもしくはサインアップページのみ見られます。
|
||||
admin_setting_desc=今管理者アカウントを作成する必要はありません。ID = 1のユーザ は自動的に管理者の権限を獲得します。
|
||||
@@ -106,7 +111,7 @@ admin_title=管理者アカウントの設定
|
||||
admin_name=ユーザ名
|
||||
admin_password=パスワード
|
||||
confirm_password=パスワード確認
|
||||
admin_email=E-mail
|
||||
admin_email=Admin E-mail
|
||||
install_gogs=Gogs をインストール
|
||||
test_git_failed='Git' コマンドテストに失敗: %v
|
||||
sqlite3_not_available=このリリース バージョンは SQLite3 をサポートしていません。gobuild バージョンではない、公式のバイナリ バージョンを %s からダウンロードしてください。
|
||||
@@ -125,9 +130,9 @@ my_repos=私のリポジトリ
|
||||
collaborative_repos=共同リポジトリ
|
||||
my_orgs=私の組織
|
||||
my_mirrors=私のミラー
|
||||
view_home=View %s
|
||||
view_home=ビュー %s
|
||||
|
||||
issues.in_your_repos=In your repositories
|
||||
issues.in_your_repos=あなたのリポジトリ
|
||||
|
||||
[explore]
|
||||
repos=リポジトリ
|
||||
@@ -143,7 +148,6 @@ forgot_password=パスワードを忘れた
|
||||
forget_password=パスワードを忘れた?
|
||||
sign_up_now=アカウントが必要ですか?今すぐサインアップ
|
||||
confirmation_mail_sent_prompt=新しい確認メールを <b>%s</b> に送りました。登録を完了させるために、%d時間以内にあなたのメールボックスを確認してください。
|
||||
sign_in_email=E-mailでサイイン
|
||||
active_your_account=アカウントをアクティブ
|
||||
resent_limit_prompt=申し訳ありませんが、アクティベーションメールは頻繁に送信しています。3 分お待ちください。
|
||||
has_unconfirmed_mail=こんにちは %s さん、あなたの電子メール アドレス (<b>%s</b>) は未確認です。もし確認メールをまだ確認できていないか、改めて再送信する場合は、下のボタンをクリックしてください。
|
||||
@@ -155,6 +159,12 @@ invalid_code=申し訳ありませんが、確認用コードが期限切れま
|
||||
reset_password_helper=パスワードをリセットするにはここをクリック
|
||||
password_too_short=6文字未満のパスワードは設定できません。
|
||||
|
||||
[mail]
|
||||
activate_account=あなたのアカウントを有効にしてください。
|
||||
activate_email=電子メール アドレスを確認します。
|
||||
reset_password=パスワードをリセットします.
|
||||
register_success=ようこそ、登録成功
|
||||
|
||||
[modal]
|
||||
yes=はい
|
||||
no=いいえ
|
||||
@@ -181,6 +191,7 @@ min_size_error=' 少なくとも %s 文字の必要があります '
|
||||
max_size_error=' %s 文字以下の必要があります '
|
||||
email_error=' は有効な電子メール アドレスではない '
|
||||
url_error=' は有効な URL はありません。 '
|
||||
include_error=` must contain substring '%s'.`
|
||||
unknown_error=不明なエラー:
|
||||
captcha_incorrect=Captcha が一致しませんでした。
|
||||
password_not_match=パスワードと確認用パスワードが一致同していません。
|
||||
@@ -241,7 +252,7 @@ location=ロケーション
|
||||
update_profile=プロファイル更新
|
||||
update_profile_success=あなたのプロフィールが更新されました。
|
||||
change_username=ユーザー名が変更されました
|
||||
change_username_desc=ユーザー名が変更されている、継続したいですか?これはあなたのアカウントに関連するすべてのリンクに影響を与える。
|
||||
change_username_prompt=この変更はリンクをアカウントに関連付ける方法に影響します。
|
||||
continue=続行
|
||||
cancel=キャンセル
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=あなたのアバターの設定が更新されました
|
||||
change_password=パスワードを変更
|
||||
old_password=現在のパスワード
|
||||
new_password=新しいパスワード
|
||||
retype_new_password=新しいパスワードを再入力します。
|
||||
password_incorrect=現在のパスワードが正しくありません。
|
||||
change_password_success=パスワードが正常に変更されました。今すぐ新しいパスワード経由でサインインすることができます。
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=あなたのプライマリメールアドレスは、通知やその
|
||||
primary=プライマリー
|
||||
primary_email=プライマリに設定
|
||||
delete_email=削除
|
||||
email_deletion=電子メールの削除
|
||||
email_deletion_desc=この電子メール アドレスを削除すると、あなたのアカウントの関連情報も削除されます。続行しますか。
|
||||
email_deletion_success=電子メールが正常に削除されました。
|
||||
add_new_email=新しいe-mailアドレスを追加
|
||||
add_email=電子メールを追加します。
|
||||
add_email_confirmation_sent=<b>%s</b> に新しい確認メールを送信しました、次の %d 時間以内に受信トレイを確認し、確認プロセスを完了してください。
|
||||
add_email_confirmation_sent='%s' に新しい確認メールを送信しました、次の %d 時間以内に受信トレイを確認し、確認プロセスを完了してください。
|
||||
add_email_success=新しいe-mail アドレスが追加されました。
|
||||
|
||||
manage_ssh_keys=SSH キーを管理
|
||||
@@ -281,14 +296,14 @@ key_name=キーの名前
|
||||
key_content=コンテンツ
|
||||
add_key_success=新しいSSHキー '%s' が正常に追加されました!
|
||||
delete_key=削除
|
||||
ssh_key_deletion=SSH Key Deletion
|
||||
ssh_key_deletion_desc=Delete this SSH key will remove all related accesses for your account. Do you want to continue?
|
||||
ssh_key_deletion_success=SSH key has been deleted successfully!
|
||||
ssh_key_deletion=SSH キーの削除
|
||||
ssh_key_deletion_desc=このSSHキーを削除すると、あなたのアカウントに関連するすべてのアクセスが削除されます。続行しますか?
|
||||
ssh_key_deletion_success=SSH キーは正常に削除されました!
|
||||
add_on=追加された
|
||||
last_used=最終使用日
|
||||
no_activity=最近の活動なし
|
||||
key_state_desc=この鍵は7日間以内に使われています。
|
||||
token_state_desc=This token is used in last 7 days
|
||||
token_state_desc=この鍵は7日間以内に使われています。
|
||||
|
||||
manage_social=関連付けられているSNSアカウントを管理
|
||||
social_desc=これは関連付けられたソーシャルアカウントのリストです。あなたが認識していない結び付けを削除します。
|
||||
@@ -297,15 +312,15 @@ unbind_success=SNSアカウントがバインドされていない。
|
||||
|
||||
manage_access_token=個人のアクセス トークンを管理
|
||||
generate_new_token=新しいトークンを生成
|
||||
tokens_desc=Tokens you have generated that can be used to access the Gogs APIs.
|
||||
tokens_desc=生成したトークンを利用して Gogs の API にアクセスすることができます。
|
||||
new_token_desc=今のところ、全てのトークンはあなたのアカウントにフルアクセスできます。
|
||||
token_name=トークン名
|
||||
generate_token=トークンを生成
|
||||
generate_token_succees=新しいアクセス トークンは正常に生成されました !今すぐあなたの新しいアクセス トークンをコピーしておいてください。二度と見ることはできませんので確認してください!
|
||||
delete_token=削除
|
||||
access_token_deletion=Personal Access Token Deletion
|
||||
access_token_deletion_desc=Delete this personal access token will remove all related accesses of application. Do you want to continue?
|
||||
delete_token_success=Personal access token has been removed successfully! Don't forget to update your application as well.
|
||||
access_token_deletion=パーソナルアクセストークンの削除
|
||||
access_token_deletion_desc=パーソナルアクセストークンを削除すると、関連するアプリケーションのすべてのアクセスが削除されます。続行しますか?
|
||||
delete_token_success=パーソナルアクセストークンは正常に削除されました!同時にあなたのアプリケーションを更新することを忘れないでください。
|
||||
|
||||
delete_account=アカウントを削除
|
||||
delete_prompt=この操作はあなたのアカウントを完全に削除し、復旧<strong>できない</strong> !
|
||||
@@ -319,18 +334,19 @@ repo_name=リポジトリ名
|
||||
repo_name_helper=偉大なリポジトリ名は短い。思い出に残り、そして<strong>一意</strong>だ。
|
||||
visibility=ビジビリティ
|
||||
visiblity_helper=このリポジトリは<span class="ui red text">プライベート</span>です。
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
visiblity_helper_forced=サイト管理者は、強制的にすべての新しいリポジトリを<span class="ui red text"> プライベート</span> にしています。
|
||||
visiblity_fork_helper=(この値の変更はすべてのフォークに適用されます)
|
||||
fork_repo=フォークのリポジトリ
|
||||
fork_from=フォーク元
|
||||
fork_visiblity_helper=フォークされたリポジトリは可視状態を変更できません
|
||||
repo_desc=説明
|
||||
repo_lang=言語
|
||||
repo_lang_helper=Select .gitignore files
|
||||
repo_lang_helper=.gitignoreファイルを選択
|
||||
license=ライセンス
|
||||
license_helper=ライセンス ファイルを選択
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
readme_helper=Readme ファイルのテンプレートを選択
|
||||
auto_init=選択されたファイルおよびテンプレートでリポジトリを初期化
|
||||
create_repo=リポジトリを作成
|
||||
default_branch=デフォルトのブランチ
|
||||
mirror_interval=ミラー 間隔(時)
|
||||
@@ -340,15 +356,18 @@ form.name_pattern_not_allowed=リポジトリ名のパターン '%s' は許可
|
||||
|
||||
need_auth=認証が必要
|
||||
migrate_type=マイグレーションの種類
|
||||
migrate_type_helper=This repository will be a <span class="text blue">mirror</span>
|
||||
migrate_type_helper=このリポジトリは、<span class="text blue"> ミラー</span> になります
|
||||
migrate_repo=リポジトリを移行
|
||||
migrate.clone_address=クローンアドレス
|
||||
migrate.clone_address_desc=This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.clone_address_desc=これは、HTTP/HTTPS/GIT URL またはローカル サーバー パスを設定できます。
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=ローカルパスが無効です。存在しないかディレクトリではありません。
|
||||
|
||||
forked_from=フォーク元
|
||||
fork_from_self=すでにあなたの所有しているリポジトリはフォークできません
|
||||
copy_link=コピー
|
||||
copy_link_success=コピーされました!
|
||||
copy_link_error=⌘ C または Ctrl-C キーを押してコピー
|
||||
click_to_copy=クリップボードにコピー
|
||||
copied=コピー成功
|
||||
clone_helper=クローニングのヘルプが必要ですか?<a target="_blank"href="%s"> ヘルプ</a> を参照してください!
|
||||
@@ -363,6 +382,8 @@ quick_guide=クイック ガイド
|
||||
clone_this_repo=このリポジトリのクローンを作成
|
||||
create_new_repo_command=コマンドラインで新しいリポジトリを作成します。
|
||||
push_exist_repo=コマンド ・ ラインから既存のリポジトリをプッシュ
|
||||
repo_is_empty=このリポジトリは空です、後で戻って来て下さい!
|
||||
|
||||
|
||||
branch=ブランチ
|
||||
tree=ツリー
|
||||
@@ -370,7 +391,7 @@ branch_and_tags=ブランチ& タグ
|
||||
branches=ブランチ
|
||||
tags=タグ
|
||||
issues=課題
|
||||
pulls=Pull Requests
|
||||
pulls=プルリクエスト
|
||||
labels=ラベル
|
||||
milestones=マイルストーン
|
||||
commits=コミット
|
||||
@@ -412,16 +433,16 @@ issues.filter_label_no_select=選択したラベルがありません。
|
||||
issues.filter_milestone=マイルストーン
|
||||
issues.filter_milestone_no_select=選択されたマイルストーンなし
|
||||
issues.filter_assignee=アサインされた人
|
||||
issues.filter_assginee_no_select=No selected Assignee
|
||||
issues.filter_assginee_no_select=選択可能な担当者がいない
|
||||
issues.filter_type=タイプ
|
||||
issues.filter_type.all_issues=すべての問題
|
||||
issues.filter_type.assigned_to_you=あなたに割り当てられました。
|
||||
issues.filter_type.created_by_you=あなたが作成しました。
|
||||
issues.filter_type.mentioning_you=あなたに伝える
|
||||
issues.filter_sort=Sort
|
||||
issues.filter_sort.latest=Newest
|
||||
issues.filter_sort.oldest=Oldest
|
||||
issues.filter_sort.recentupdate=Recently updated
|
||||
issues.filter_sort=並べ替え
|
||||
issues.filter_sort.latest=最新
|
||||
issues.filter_sort.oldest=最も古い
|
||||
issues.filter_sort.recentupdate=最近更新された
|
||||
issues.filter_sort.leastupdate=Least recently updated
|
||||
issues.filter_sort.mostcomment=Most commented
|
||||
issues.filter_sort.leastcomment=Least commented
|
||||
@@ -429,27 +450,27 @@ issues.opened_by=opened %[1]s by <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=opened %[1]s by %[2]s
|
||||
issues.previous=前ページ
|
||||
issues.next=次ページ
|
||||
issues.open_title=Open
|
||||
issues.closed_title=Closed
|
||||
issues.num_comments=%d comments
|
||||
issues.open_title=オープン
|
||||
issues.closed_title=クローズ
|
||||
issues.num_comments=%d コメント
|
||||
issues.commented_at=`commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=There is no content yet.
|
||||
issues.close_issue=Close
|
||||
issues.close_comment_issue=Close and comment
|
||||
issues.no_content=まだコンテンツがありません
|
||||
issues.close_issue=閉じる
|
||||
issues.close_comment_issue=コメントと閉じる
|
||||
issues.reopen_issue=Reopen
|
||||
issues.reopen_comment_issue=Reopen and comment
|
||||
issues.create_comment=Comment
|
||||
issues.reopen_comment_issue=コメントと再開
|
||||
issues.create_comment=コメント
|
||||
issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Poster
|
||||
issues.admin=Admin
|
||||
issues.owner=Owner
|
||||
issues.sign_up_for_free=Sign up for free
|
||||
issues.poster=ポスター
|
||||
issues.admin=アドミン
|
||||
issues.owner=オーナー
|
||||
issues.sign_up_for_free=無料でサインアップ
|
||||
issues.sign_in_require_desc=to join this conversation. Already have an account? <a href="%s">Sign in to comment</a>
|
||||
issues.edit=Edit
|
||||
issues.cancel=Cancel
|
||||
issues.save=Save
|
||||
issues.edit=編集
|
||||
issues.cancel=キャンセル
|
||||
issues.save=保存
|
||||
issues.label_title=ラベル名
|
||||
issues.label_color=ラベルの色
|
||||
issues.label_count=%d ラベル
|
||||
@@ -463,26 +484,28 @@ issues.label_deletion_success=ラベルは正常に削除されました。
|
||||
|
||||
pulls.compare_changes=変更を比較
|
||||
pulls.compare_changes_desc=2つのブランチを比較し、プルリクエストを作成します。
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=compare
|
||||
pulls.filter_branch=Filter branch
|
||||
pulls.compare_base=ベース
|
||||
pulls.compare_compare=比較
|
||||
pulls.filter_branch=フィルターブランチ
|
||||
pulls.no_results=結果が見つかりませんでした。
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Create Pull Request
|
||||
pulls.create=プルリクエストを作成します。
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversation
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Files changed
|
||||
pulls.tab_conversation=会話
|
||||
pulls.tab_commits=コミット
|
||||
pulls.tab_files=ファイルが変更された
|
||||
pulls.reopen_to_merge=Please reopen this pull request to perform merge operation.
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.merged=マージされた
|
||||
pulls.has_merged=このプルプルリクエストは正常にマージされました!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking=The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.cannot_auto_merge_helper=それを解決するためにコマンド ライン ツールを使用してください。
|
||||
pulls.merge_pull_request=プルリクエストをマージします。
|
||||
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new=新しいマイルストーン
|
||||
milestones.open_tab=%d オープン
|
||||
@@ -491,22 +514,22 @@ milestones.closed=%s を閉じました
|
||||
milestones.no_due_date=期限なし
|
||||
milestones.open=開く
|
||||
milestones.close=閉じる
|
||||
milestones.new_subheader=Create milestones to organize your issues.
|
||||
milestones.create=Create Milestone
|
||||
milestones.title=Title
|
||||
milestones.desc=Description
|
||||
milestones.due_date=Due Date (optional)
|
||||
milestones.clear=Clear
|
||||
milestones.invalid_due_date_format=Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.create_success=Milestone '%s' has been created successfully!
|
||||
milestones.edit=Edit Milestone
|
||||
milestones.edit_subheader=Use better description for milestones so people won't be confused.
|
||||
milestones.cancel=Cancel
|
||||
milestones.modify=Modify Milestone
|
||||
milestones.edit_success=Changes of milestone '%s' has been saved successfully!
|
||||
milestones.deletion=Milestone Deletion
|
||||
milestones.deletion_desc=Delete this milestone will remove its information in all related issues. Do you want to continue?
|
||||
milestones.deletion_success=Milestone has been deleted successfully!
|
||||
milestones.new_subheader=あなたの課題を整理するためマイルス トーンを作成します。
|
||||
milestones.create=マイルス トーンを作成
|
||||
milestones.title=タイトル
|
||||
milestones.desc=説明
|
||||
milestones.due_date=期日 (オプション)
|
||||
milestones.clear=消去
|
||||
milestones.invalid_due_date_format=期限日付のフォーマットが無効、'yyyy-mm-dd' のフォーマットが必要です。
|
||||
milestones.create_success=マイルス トーン '%s' が正常に作成されました!
|
||||
milestones.edit=マイルス トーンを編集
|
||||
milestones.edit_subheader=人々を混乱させないため、マイルス トーンにより良い説明を使用します。
|
||||
milestones.cancel=キャンセル
|
||||
milestones.modify=マイルス トーンを変更します。
|
||||
milestones.edit_success=マイルス トーン '%s' の変更が正常に保存されました。
|
||||
milestones.deletion=マイルス トーンの削除
|
||||
milestones.deletion_desc=このマイルス トーンを削除すると、関連課題に該当情報が削除されます。続行しますか。
|
||||
milestones.deletion_success=マイルス トーンは正常に削除されました。
|
||||
|
||||
settings=設定
|
||||
settings.options=オプション
|
||||
@@ -517,15 +540,15 @@ settings.basic_settings=基本設定
|
||||
settings.danger_zone=危険地帯
|
||||
settings.site=公式サイト
|
||||
settings.update_settings=設定の更新
|
||||
settings.change_reponame_prompt=This change will affect how links relate to the repository.
|
||||
settings.change_reponame_prompt=この変更はリンクがリポジトリに関連付ける方法に影響します。
|
||||
settings.transfer=オーナー移転
|
||||
settings.transfer_desc=リポジトリをあなたが管理者権限を持っている別のユーザーまた組織に移譲します。
|
||||
settings.new_owner_has_same_repo=新しいオーナーは、既に同じ名前のリポジトリを持っています。
|
||||
settings.delete=このリポジトリを削除
|
||||
settings.delete_desc=リポジトリを削除すると元に戻せません。確実に確認してください。
|
||||
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
|
||||
settings.transfer_notices_1=-新しい所有者が個人ユーザーの場合、あなたがアクセスできなくなります。
|
||||
settings.transfer_notices_2=- You will conserve access if new owner is an organization and if you're one of the owners.
|
||||
settings.transfer_form_title=Please enter following information to confirm your operation:
|
||||
settings.transfer_form_title=操作を確認するために、以下の情報を入力してください。
|
||||
settings.delete_notices_1=- This operation <strong>CANNOT</strong> be undone.
|
||||
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
|
||||
settings.delete_notices_fork_1=- If this repository is public, all forks will be became independent after deletion.
|
||||
@@ -542,14 +565,14 @@ settings.remove_collaborator_success=共同編集者が削除されました。
|
||||
settings.user_is_org_member=ユーザーは組織の一員なので、共同編集者として追加することはできません。
|
||||
settings.add_webhook=Webhook を追加
|
||||
settings.hooks_desc=Webhooksは、Gogsで特定のイベントの発生時に指定された外部サービスに通知を許可します。イベントが発生すると、それぞれ指定されたUrlに、POSTリクエストが送られます。詳細はこちらのの <a target="_blank"href="%s"> Webhooks ガイド</a>をご覧ください。
|
||||
settings.webhook_deletion=Delete Webhook
|
||||
settings.webhook_deletion=Webhook を削除
|
||||
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
|
||||
settings.webhook_deletion_success=Webhook has been deleted successfully!
|
||||
settings.webhook.request=Request
|
||||
settings.webhook.response=Response
|
||||
settings.webhook.headers=Headers
|
||||
settings.webhook.payload=Payload
|
||||
settings.webhook.body=Body
|
||||
settings.webhook_deletion_success=Webhook が正常に削除されました。
|
||||
settings.webhook.request=リクエスト
|
||||
settings.webhook.response=レスポンス
|
||||
settings.webhook.headers=ヘッダ
|
||||
settings.webhook.payload=ペイロード
|
||||
settings.webhook.body=ボディ
|
||||
settings.githooks_desc=Git のフックは Git 自体によって提供されています。以下のリストのファイルを編集して、サポートされているフックのカスタム操作を適用することができます。
|
||||
settings.githook_edit_desc=もしフックがアクティブではない場合は、サンプルコンテンツが表示されます。コンテンツを空白にするにはこのフックを無効にします。
|
||||
settings.githook_name=フックの名前
|
||||
@@ -559,17 +582,17 @@ settings.add_webhook_desc=私たちは、指定されたURLに購読されたイ
|
||||
settings.payload_url=ペイロードの URL
|
||||
settings.content_type=コンテンツ タイプ
|
||||
settings.secret=秘密
|
||||
settings.slack_username=Username
|
||||
settings.slack_icon_url=Icon URL
|
||||
settings.slack_color=Color
|
||||
settings.slack_username=ユーザ名
|
||||
settings.slack_icon_url=アイコン URL
|
||||
settings.slack_color=カラー
|
||||
settings.event_desc=どのイベントをこのWEBフックのトリガーにしますか?
|
||||
settings.event_push_only=<code>push</code> イベントのみ
|
||||
settings.event_send_everything=I need <strong>everything</strong>.
|
||||
settings.event_send_everything=<strong>すべて</strong> が必要です。
|
||||
settings.event_choose=Let me choose what I need.
|
||||
settings.event_create=Create
|
||||
settings.event_create_desc=Branch, or tag created
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push to a repository
|
||||
settings.event_create_desc=ブランチ、またはタグを作成
|
||||
settings.event_push=プッシュ
|
||||
settings.event_push_desc=Git リポジトリにプッシュ
|
||||
settings.active=アクティブ
|
||||
settings.active_helper=このフックのトリガーが引かれた時に、イベントの詳細を配信します。
|
||||
settings.add_hook_success=新しい webhook が追加されました。
|
||||
@@ -583,16 +606,16 @@ settings.slack_token=トークン
|
||||
settings.slack_domain=ドメイン
|
||||
settings.slack_channel=チャンネル
|
||||
settings.deploy_keys=デプロイキー
|
||||
settings.add_deploy_key=Add Deploy Key
|
||||
settings.no_deploy_keys=You haven't added any deploy key.
|
||||
settings.title=Title
|
||||
settings.deploy_key_content=Content
|
||||
settings.key_been_used=Deploy key content has been used.
|
||||
settings.key_name_used=Deploy key with same name has already existed.
|
||||
settings.add_key_success=New deploy key '%s' has been added successfully!
|
||||
settings.deploy_key_deletion=Delete Deploy Key
|
||||
settings.deploy_key_deletion_desc=Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||
settings.deploy_key_deletion_success=Deploy key has been deleted successfully!
|
||||
settings.add_deploy_key=デプロイキーを追加
|
||||
settings.no_deploy_keys=でプロキーは1つも追加されていません。
|
||||
settings.title=タイトル
|
||||
settings.deploy_key_content=コンテント
|
||||
settings.key_been_used=デプロイキーが使用されています。
|
||||
settings.key_name_used=同じ名前のデプロイキーが既に存在しています。
|
||||
settings.add_key_success=新しいデプロイキー '%s'が正常に追加されました!
|
||||
settings.deploy_key_deletion=デプロイキーを削除
|
||||
settings.deploy_key_deletion_desc=このデプロイキーを削除すると、このリポジトリに関連するすべてのアクセス権も削除されます。続行しますか。
|
||||
settings.deploy_key_deletion_success=デプロイキーが正常に削除された!
|
||||
|
||||
diff.browse_source=ソースを参照
|
||||
diff.parent=親
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=このタグ名には既にリリースが存在
|
||||
|
||||
[org]
|
||||
org_name_holder=組織名
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=偉大な組織の名は短く覚えやすいです。
|
||||
org_email_helper=組織の電子メールはすべての通知や確認を受け取ります。
|
||||
create_org=組織を作成
|
||||
repo_updated=更新した
|
||||
people=人々
|
||||
@@ -655,9 +678,9 @@ settings.full_name=フルネーム
|
||||
settings.website=WEBサイト
|
||||
settings.location=ロケーション
|
||||
settings.update_settings=設定の更新
|
||||
settings.change_orgname=組織名が変更されました
|
||||
settings.change_orgname_desc=組織名が変更されています、継続しますか?これはすべての関連リンクに影響を与えます。
|
||||
settings.update_setting_success=組織の設定が更新されました。
|
||||
settings.change_orgname_prompt=This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success=Organization avatar setting has been updated successfully.
|
||||
settings.delete=組織を削除
|
||||
settings.delete_account=この組織を削除
|
||||
settings.delete_prompt=操作はこの組織を完全に削除し、復旧<strong>できない</strong>!
|
||||
@@ -713,8 +736,9 @@ authentication=認証
|
||||
config=コンフィギュレーション
|
||||
notices=システム通知
|
||||
monitor=モニタリング
|
||||
prev=前へ
|
||||
next=次へ
|
||||
first_page=First
|
||||
last_page=Last
|
||||
total=合計: %d
|
||||
|
||||
dashboard.statistic=統計
|
||||
dashboard.operations=操作
|
||||
@@ -773,19 +797,24 @@ users.activated=アクティブ化
|
||||
users.admin=アドミン
|
||||
users.repos=リポジトリ
|
||||
users.created=作成されました
|
||||
users.send_register_notify=登録通知をユーザーに送信
|
||||
users.new_success=New account '%s' has been created successfully.
|
||||
users.edit=編集
|
||||
users.auth_source=認証元
|
||||
users.auth_source=認証ソース
|
||||
users.local=ローカル
|
||||
users.auth_login_name=認証ログイン名
|
||||
users.password_helper=それをそのまま空のままにします。
|
||||
users.update_profile_success=アカウントのプロファイルが更新されました。
|
||||
users.edit_account=アカウントの編集
|
||||
users.is_activated=アカウントがアクティブされました
|
||||
users.is_admin=このアカウントには管理者の権限を持つ
|
||||
users.allow_git_hook=このアカウントには Git のフックを作成する権限を持つ
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=アカウント ・ プロファイルを更新
|
||||
users.delete_account=このアカウントを削除
|
||||
users.still_own_repo=アカウント所有のリポジトリがあり、リポジトリの削除または所有者の移譲が必要です。
|
||||
users.still_has_org=アカウントはまだ組織のメンバーであり、組織から退出するか削除する必要があります。
|
||||
users.deletion_success=アカウントが正常に削除されました。
|
||||
|
||||
orgs.org_manage_panel=組織の管理パネル
|
||||
orgs.name=名前
|
||||
@@ -800,41 +829,47 @@ repos.watches=Watches
|
||||
repos.stars=Stars
|
||||
repos.issues=課題
|
||||
|
||||
auths.auth_manage_panel=承認の管理パネル
|
||||
auths.new=新しい認証元を追加
|
||||
auths.auth_manage_panel=認証管理パネル
|
||||
auths.new=新しいソースを追加
|
||||
auths.name=名前
|
||||
auths.type=タイプ
|
||||
auths.enabled=Enabled
|
||||
auths.updated=Updated
|
||||
auths.auth_type=認証の種類
|
||||
auths.auth_type=認証タイプ
|
||||
auths.auth_name=認証名
|
||||
auths.domain=ドメイン
|
||||
auths.host=ホスト
|
||||
auths.port=ポート
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.user_base=User Search Base
|
||||
auths.bind_dn=バインド DN
|
||||
auths.bind_password=バインド パスワード
|
||||
auths.bind_password_helper=Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base=ユーザ検索ベース
|
||||
auths.user_dn=User DN
|
||||
auths.attribute_name=名前属性
|
||||
auths.attribute_surname=名字属性
|
||||
auths.attribute_mail=Eメール属性
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.filter=User フィルター
|
||||
auths.admin_filter=Admin フィルター
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=SMTP 認証の種類
|
||||
auths.smtphost=SMTP ホスト
|
||||
auths.smtpport=SMTP ポート
|
||||
auths.allowed_domains=許可されているドメイン
|
||||
auths.allowed_domains_helper=Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
|
||||
auths.enable_tls=TLS 暗号化を有効にする
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.skip_tls_verify=TLSベリファイを省略
|
||||
auths.pam_service_name=PAMサービス名
|
||||
auths.enable_auto_register=自動登録を有効にする
|
||||
auths.tips=ヒント
|
||||
auths.edit=認証設定を編集
|
||||
auths.activated=認証がアクティブされました
|
||||
auths.new_success=新しい認証 '%s' が正常に追加されました。
|
||||
auths.update_success=認証の設定が正常に更新されました。
|
||||
auths.update=認証設定の更新
|
||||
auths.delete=この権限を削除
|
||||
auths.delete_auth_title=認証の削除
|
||||
auths.update=認証設定を更新
|
||||
auths.delete=この認証を削除
|
||||
auths.delete_auth_title=認証削除
|
||||
auths.delete_auth_desc=認証を削除します、継続しますか?
|
||||
auths.deletion_success=認証が正常に削除されました。
|
||||
|
||||
config.server_config=サーバーの構成
|
||||
config.app_name=アプリケーション名
|
||||
@@ -858,18 +893,20 @@ config.db_user=ユーザ
|
||||
config.db_ssl_mode=SSL モード
|
||||
config.db_ssl_mode_helper=(「postgres」のみ)
|
||||
config.db_path=パス
|
||||
config.db_path_helper=(「sqlite3」のみ)
|
||||
config.db_path_helper=(for "sqlite3" and "tidb")
|
||||
config.service_config=サービスの構成
|
||||
config.register_email_confirm=電子メールの確認を必要
|
||||
config.disable_register=登録を無効にする
|
||||
config.show_registration_button=登録ボタンを表示します。
|
||||
config.require_sign_in_view=サインインを要求
|
||||
config.mail_notify=メール通知
|
||||
config.enable_cache_avatar=アバターのキャッシュを有効にします。
|
||||
config.mail_notify=メール通知
|
||||
config.disable_key_size_check=最小キー サイズ チェックを無効にします
|
||||
config.enable_captcha=Captchaを有効にする
|
||||
config.active_code_lives=コードリンクの有効期限をアクティブ
|
||||
config.reset_password_code_lives=パスワードリンクの有効期限をリセット
|
||||
config.webhook_config=Webhook設定
|
||||
config.queue_length=Queue Length
|
||||
config.queue_length=キューの長さ
|
||||
config.deliver_timeout=送信タイムアウト
|
||||
config.skip_tls_verify=TLSの確認を省略
|
||||
config.mailer_config=メーラーの構成
|
||||
@@ -919,10 +956,10 @@ notices.delete_success=システム通知が正常に削除されました。
|
||||
|
||||
[action]
|
||||
create_repo=リポジトリ <a href="%s"> %s</a>を作成しました
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
rename_repo=<code>%[1]s</code> から <a href="%[2]s">[3]s</a> にリポジトリ名を変更した
|
||||
commit_repo=<a href="%[1]s">%[3]s</a>を<a href="%[1]s/src/%[2]s">%[2]s</a>にプッシュしました
|
||||
create_issue=`問題 <a href="%s/issues/%s">%s#%[2]s</a> を開きました`
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`プルリクエスト <a href="%s/pulls/%s"> %s[2]s</a>を作成`
|
||||
comment_issue=`問題 <a href="%s/issues/%s">%s#%[2]s</a> のコメント`
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=リポジトリ <code>%s</code> を <a href="%s">%s</a> へ転送しました
|
||||
@@ -951,8 +988,8 @@ raw_seconds=秒
|
||||
raw_minutes=分
|
||||
|
||||
[dropzone]
|
||||
default_message=Drop files here or click to upload.
|
||||
invalid_input_type=You can't upload files of this type.
|
||||
file_too_big=File size({{filesize}} MB) exceeds maximum size({{maxFilesize}} MB).
|
||||
remove_file=Remove file
|
||||
default_message=ここにファイルをドロップまたはクリックしてアップロードします。
|
||||
invalid_input_type=このタイプのファイルはアップロードできません.
|
||||
file_too_big=ファイルのサイズ ({{filesize}} MB) では、最大サイズ ({{maxFilesize}} MB) を超えています。
|
||||
remove_file=ファイル削除
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=Infopanelis
|
||||
explore=Izpētīt
|
||||
help=Palīdzība
|
||||
sign_in=Pierakstīties
|
||||
social_sign_in=Sociālā pieteikšanās: 2. solis <small>piesaistīt kontu</small>
|
||||
sign_out=Izrakstīties
|
||||
sign_up=Pieteikties
|
||||
register=Reģistrēties
|
||||
@@ -14,7 +13,7 @@ version=Versija
|
||||
page=Lapa
|
||||
template=Sagatave
|
||||
language=Valoda
|
||||
create_new=Izveidot jaunu...
|
||||
create_new=Izveidot...
|
||||
user_profile_and_more=Lietotāja profilu un vairāk
|
||||
signed_in_as=Pierakstījies kā
|
||||
|
||||
@@ -54,7 +53,8 @@ code=Kods
|
||||
[install]
|
||||
install=Instalācija
|
||||
title=Instalācijas soļi pirmo reizi palaižot
|
||||
requite_db_desc=Gogs ir nepieciešama MySQL, PostgreSQL vai SQLite3 datu bāze.
|
||||
docker_helper=Ja Gogs tiek lietots zem Docker, izlasiet uzmanīgi <a target="_blank" href="%s">vadlīnijas</a>, pirms ko maināt šajā lapā!
|
||||
requite_db_desc=Gogs nepieciešams MySQL, PostgreSQL, SQLite3 vai TiDB.
|
||||
db_title=Datu bāzes iestatījumi
|
||||
db_type=Datu bāzes veids
|
||||
host=Resursdators
|
||||
@@ -64,8 +64,11 @@ db_name=Datu bāzes nosaukums
|
||||
db_helper=Nepieciešams izmantot MySQL INNODB dzini ar rakstzīmju kopu utf8_general_ci.
|
||||
ssl_mode=SSL režīms
|
||||
path=Ceļš
|
||||
sqlite_helper=SQLite 3 datu bāzes faila atrašanās vieta.
|
||||
err_empty_sqlite_path=Nav norādīts SQLite3 datu bāzes ceļš.
|
||||
sqlite_helper=SQLite3 vai TiDB datu bāzes faila atrašanās vieta.
|
||||
err_empty_db_path=Nepieciešams norādīt SQLite3 vai TiDB datu bāzes atrašanās vietu.
|
||||
err_invalid_tidb_name=TiDB datu bāzes nosaukums nevar saturēt simbolus "." un "-".
|
||||
no_admin_and_disable_registration=Reģistrāciju nevar atslēgt, kamēr nav izveidots administratora konts.
|
||||
err_empty_admin_password=Administratora kontam ir obligāti jānorāda parole.
|
||||
|
||||
general_title=Gogs vispārīgie iestatījumi
|
||||
app_name=Lietotnes nosaukums
|
||||
@@ -76,8 +79,8 @@ run_user=Izpildes lietotājs
|
||||
run_user_helper=Lietotājam ir jābūt rakstīšanas tiesībām repozitorija saknes direktorijai un Gogs jābūt palaistam zem šī lietotāja.
|
||||
domain=Domēns
|
||||
domain_helper=Tas ietekmē SSH klonēšanas URL.
|
||||
ssh_port=SSH Port
|
||||
ssh_port_helper=Port number which your SSH server is using, leave it empty to disable SSH feature.
|
||||
ssh_port=SSH ports
|
||||
ssh_port_helper=Porta numurs, kuru izmanto Jūsu SSH serveris, atstājiet tukšu, ja nevēlaties izmantot SSH.
|
||||
http_port=HTTP ports
|
||||
http_port_helper=Porta numurs pēc kura lietojumprogrammai būs iespējams pieslēgties.
|
||||
app_url=Lietotnes URL
|
||||
@@ -95,10 +98,12 @@ mail_notify=Iespējot e-pasta paziņojumus
|
||||
server_service_title=Servera un citu servisu iestatījumi
|
||||
offline_mode=Iespējot bezsaistes režīmu
|
||||
offline_mode_popup=Atspējot CDN arī produkcijas režīmā, visi resursu faili tiks piegādāti no servera.
|
||||
disable_gravatar=Disable Gravatar Service
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_gravatar=Atspējot Gravatar pakalpojumu
|
||||
disable_gravatar_popup=Atspējot Gravatar un citus avotus, visus avatarus augšupielādēts lietotāji vai izmantos noklusēto attēlu.
|
||||
disable_registration=Atspējot lietotāju reģistrāciju
|
||||
disable_registration_popup=Atspējot lietotāju reģistrāciju, tikai administrators varēs izveidot jaunus lietotāju kontus.
|
||||
enable_captcha=Iespējot drošības kodu
|
||||
enable_captcha_popup=Lietotājam reģistrējoties, pieprasīt ievadīt drošības kodu.
|
||||
require_sign_in_view=Iespējot nepieciešamību autorizēties, lai aplūkotu lapas
|
||||
require_sign_in_view_popup=Tika autorizēti lietotāji var aplūkot lapas, neautorizēti lietotāji var piekļūt tikai autorizācijas un reģistrēšanās lapām.
|
||||
admin_setting_desc=Nav nepieciešams izveidot administratora kontu uzreiz, lietotājs ar ID=1 saņems administratora tiesības automātiski.
|
||||
@@ -106,7 +111,7 @@ admin_title=Admin konta iestatījumi
|
||||
admin_name=Lietotājvārds
|
||||
admin_password=Parole
|
||||
confirm_password=Apstipriniet paroli
|
||||
admin_email=E-pasts
|
||||
admin_email=Administratora e-pasts
|
||||
install_gogs=Instalēt Gogs
|
||||
test_git_failed=Kļūda pārbaudot 'git' komandu: %v
|
||||
sqlite3_not_available=Jūsu versija neatbalsta SQLite3, lūdzu lejupielādējiet oficiālo bināro versiju no %s, NEVIS gobuild versiju.
|
||||
@@ -125,9 +130,9 @@ my_repos=Mani repozitoriji
|
||||
collaborative_repos=Sadarbības repozitoriji
|
||||
my_orgs=Manas organizācijas
|
||||
my_mirrors=Mani spoguļi
|
||||
view_home=View %s
|
||||
view_home=Skatīties %s
|
||||
|
||||
issues.in_your_repos=In your repositories
|
||||
issues.in_your_repos=Jūsu repozitorijos
|
||||
|
||||
[explore]
|
||||
repos=Repozitoriji
|
||||
@@ -143,7 +148,6 @@ forgot_password=Aizmirsu paroli
|
||||
forget_password=Aizmirsi paroli?
|
||||
sign_up_now=Nepieciešams konts? Reģistrējies tagad.
|
||||
confirmation_mail_sent_prompt=Jauns apstiprināšanas e-pasts ir nosūtīts uz <b>%s</b>, lūdzu, pārbaudies savu e-pasta kontu tuvāko %d stundu laikā, lai pabeigtu reģistrācijas procesu.
|
||||
sign_in_email=Atvērt savu e-pasta kontu
|
||||
active_your_account=Aktivizēt savu kontu
|
||||
resent_limit_prompt=Atvainojiet, Jūs sūtījāt aktivizācijas e-pastu pārāk bieži. Lūdzu, gaidiet 3 minūtes.
|
||||
has_unconfirmed_mail=Sveiki %s, Jums ir neapstiprināta e-pasta adrese (<b>%s</b>). Ja neesat saņēmis apstiprināšanas e-pastu vai Jums ir nepieciešams nosūtīt jaunu, lūdzu, nospiediet pogu, kas atrodas zemāk.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Atvainojiet, Jūsu apstiprināšanas kodam ir beidzies derīguma te
|
||||
reset_password_helper=Nospiediet šeit, lai atjaunotu paroli
|
||||
password_too_short=Paroles garums nedrīkst būt mazāks par 6.
|
||||
|
||||
[mail]
|
||||
activate_account=Lūdzu, aktivizējiet savu kontu
|
||||
activate_email=Apstipriniet savu e-pasta adresi
|
||||
reset_password=Atiestatīt savu paroli
|
||||
register_success=Reģistrācija notikusi veiksmīgi
|
||||
|
||||
[modal]
|
||||
yes=Jā
|
||||
no=Nē
|
||||
@@ -181,6 +191,7 @@ min_size_error=` jabūt vismaz %s simbolu garumā.`
|
||||
max_size_error=` jabūt ne mazāk kā %s simbolu garumā.`
|
||||
email_error=` nav derīga e-pasta adrese.`
|
||||
url_error=` nav korekts URL.`
|
||||
include_error=` ir jāsatur tekstu '%s'.`
|
||||
unknown_error=Nezināma kļūda:
|
||||
captcha_incorrect=Pārbaudes kods nesakrīt ar attēlā redzamo.
|
||||
password_not_match=Parole un atkārtoti ievadītā parole nav vienādas.
|
||||
@@ -241,7 +252,7 @@ location=Atrašanās vieta
|
||||
update_profile=Mainīt profilu
|
||||
update_profile_success=Jūsu profila dati ir veiksmīgi saglabāti.
|
||||
change_username=Lietotāja vārds mainīts
|
||||
change_username_desc=Lietotājvārds tiks mainīts, vai vēlaties turpināt? Tas ietekmēs visas saites, kas attiecas uz Jūsu kontu.
|
||||
change_username_prompt=Šī izmaiņa ietekmēs saites, kas norāda uz Jūsu kontu.
|
||||
continue=Turpināt
|
||||
cancel=Atcelt
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=Jūsu profila bilde tika veiksmīgi saglabāta.
|
||||
change_password=Mainīt paroli
|
||||
old_password=Pašreizējā parole
|
||||
new_password=Jauna parole
|
||||
retype_new_password=Ievadīt paroli atkāroti
|
||||
password_incorrect=Ievadīta nepareiza pašreizējā parole.
|
||||
change_password_success=Parole tika veiksmīgi nomainīta. Tagad jūs varat pieraksītites, izmantojot jauno paroli.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=Primārā e-pasta adrese tiks izmantota sūtot notifikācijas un cit
|
||||
primary=Primārā
|
||||
primary_email=Iestatīt kā primāro
|
||||
delete_email=Dzēst
|
||||
email_deletion=E-pasta dzēšana
|
||||
email_deletion_desc=Dzēšot šo e-pasta adresi, tiks dzēsta arī visa ar to saistītā informācija no Jūsu konta. Vai vēlaties turpināt?
|
||||
email_deletion_success=E-pasta adrese ir veiksmīgi izdzēsta!
|
||||
add_new_email=Pievienot jaunu e-pasta adresi
|
||||
add_email=Pievienot e-pastu
|
||||
add_email_confirmation_sent=A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the confirmation process.
|
||||
add_email_confirmation_sent=Jauns apstiprinājuma e-pasts tika nosūtīts uz '%s', pārbaudiet savu e-pastu tuvāko %d stundu laikā, lai pabeigtu apstiprināšanas procesu.
|
||||
add_email_success=Jūsu jaunā e-pasta adrese tika veiksmīgi pievienota.
|
||||
|
||||
manage_ssh_keys=Pārvaldīt SSH atslēgas
|
||||
@@ -275,20 +290,20 @@ add_key=Pievienot atslēgu
|
||||
ssh_desc=Šis ir saraksts ar Jūsu kontam piesaistītajām SSH atslēgām. Dzēsiet visas, kuras Jūs neatpazīstat.
|
||||
ssh_helper=<strong>Vajadzīga palīdzība?</strong> Apskatieties pamācību kā <a href="%s">ģenerēt SSH atslēgas</a> vai kā novērst <a href="%s">biežāk sastopamās SSH problēmas</a>.
|
||||
add_new_key=Pievienot SSH atslēgu
|
||||
ssh_key_been_used=Public key content has been used.
|
||||
ssh_key_name_used=Public key with same name has already existed.
|
||||
ssh_key_been_used=Šī publiskā atslēga jau ir izmantota.
|
||||
ssh_key_name_used=Publiskā atslēga ar šādu nosaukumu jau eksistē.
|
||||
key_name=Atslēgas nosaukums
|
||||
key_content=Saturs
|
||||
add_key_success=New SSH key '%s' has been added successfully!
|
||||
add_key_success=Jauna SSH atslēga '%s' tika veiksmīgi pievienota!
|
||||
delete_key=Dzēst
|
||||
ssh_key_deletion=SSH Key Deletion
|
||||
ssh_key_deletion_desc=Delete this SSH key will remove all related accesses for your account. Do you want to continue?
|
||||
ssh_key_deletion_success=SSH key has been deleted successfully!
|
||||
ssh_key_deletion=SSH atslēgas dzēšana
|
||||
ssh_key_deletion_desc=Dzēšot šo SSH atslēgu, tiks dzēsta visa ar to saistītā piekļuve Jūsu kontam. Vai vēlaties turpināt?
|
||||
ssh_key_deletion_success=SSH atslēga tika veiksmīgi izdzēsta!
|
||||
add_on=Pievienota
|
||||
last_used=Pēdējo reizi izmantota
|
||||
no_activity=Nav nesenas aktivitātes
|
||||
key_state_desc=This key is used in last 7 days
|
||||
token_state_desc=This token is used in last 7 days
|
||||
key_state_desc=Šī atslēga tika izmantota pēdējo 7 dienu laikā
|
||||
token_state_desc=Šis talons tika izmantots pēdējo 7 dienu laikā
|
||||
|
||||
manage_social=Pārvaldīt piesaistītos sociālos kontus
|
||||
social_desc=Šeit tiek attēloti visi sociālie konti, kas ir piesaistīti Jūsu kontam. Dzēsiet visus, kurus Jūs neatpazīstat.
|
||||
@@ -297,15 +312,15 @@ unbind_success=Sociālais konts tika atsaistīts.
|
||||
|
||||
manage_access_token=Pārvaldīt personīgos piekļuves talonus
|
||||
generate_new_token=Ģenerēt jaunu talonu
|
||||
tokens_desc=Tokens you have generated that can be used to access the Gogs APIs.
|
||||
tokens_desc=Taloni, kurus esat uzģenerējuši, kas var tikt izmantoti, lai piekļūtu Gogs API.
|
||||
new_token_desc=Pašlaik visiem taloniem ir pilna piekļuve Jūsu kontam.
|
||||
token_name=Talona nosaukums
|
||||
generate_token=Ģenerēt talonu
|
||||
generate_token_succees=Jauns piekļuves talons tika veiksmīgi uzģenerēts! Pārliecinieties, ka esat to nokopējis, jo to Jums vairāk nebūs iespēja izdarīt!
|
||||
delete_token=Dzēst
|
||||
access_token_deletion=Personal Access Token Deletion
|
||||
access_token_deletion_desc=Delete this personal access token will remove all related accesses of application. Do you want to continue?
|
||||
delete_token_success=Personal access token has been removed successfully! Don't forget to update your application as well.
|
||||
access_token_deletion=Personīgā piekļuves talona dzēšana
|
||||
access_token_deletion_desc=Dzēšot personīgo piekļuves talonu, tiks liegta piekļuve aplikācijām, kas to izmanto. Vai vēlaties turpināt?
|
||||
delete_token_success=Personīgās piekļuves talons veiksmīgi izdzēsts! Neaizmirstiet nomainīt uz citu aplikācijās, kas to izmantoja.
|
||||
|
||||
delete_account=Dzēst savu kontu
|
||||
delete_prompt=Šī darbība pilnībā izdzēsīs Jūsu kontu, kā arī tā ir <strong>NEATGRIEZENISKA</strong>!
|
||||
@@ -318,37 +333,41 @@ owner=Īpašnieks
|
||||
repo_name=Repozitorija nosaukums
|
||||
repo_name_helper=Labi repzotoriju nosaukumi ir īsi, tādi kurus viegli atcerēties un <strong>unikāli</strong>.
|
||||
visibility=Redzamība
|
||||
visiblity_helper=This repository is <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
visiblity_helper=Šis repozitorijs ir <span class="ui red text">privāts</span>
|
||||
visiblity_helper_forced=Lapas administrators ir noteicis, ka visiem repozitorijiem ir jābūt <span class="ui red text">privātiem</span>
|
||||
visiblity_fork_helper=(Šīs vērtības maiņa ietekmēs arī visus atdalītos repozitorijus)
|
||||
fork_repo=Atdalīt repozitoriju
|
||||
fork_from=Atdalīt no
|
||||
fork_visiblity_helper=Atdalītam repozitorijam nav iespējams nomainīt tā redzamību
|
||||
repo_desc=Apraksts
|
||||
repo_lang=Valoda
|
||||
repo_lang_helper=Select .gitignore files
|
||||
repo_lang_helper=Izvēlieties .gitignore failus
|
||||
license=Licence
|
||||
license_helper=Izvēlieties licences failu
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
readme=LasiMani
|
||||
readme_helper=Izvēlieties faila LasiMani sagatavi
|
||||
auto_init=Inicializēt šo repozitoriju ar izvēlētajiem failiem un sagatavi
|
||||
create_repo=Izveidot repozitoriju
|
||||
default_branch=Noklusējuma atzars
|
||||
mirror_interval=Spoguļošanas intervāls (stundās)
|
||||
|
||||
form.name_reserved=Repository name '%s' is reserved.
|
||||
form.name_pattern_not_allowed=Repository name pattern '%s' is not allowed.
|
||||
form.name_reserved=Repozitorija nosaukums '%s' ir rezervēts.
|
||||
form.name_pattern_not_allowed=Repozitorija nosaukums '%s' nav atļauts.
|
||||
|
||||
need_auth=Nepieciešama autorizācija
|
||||
migrate_type=Migrācijas veids
|
||||
migrate_type_helper=This repository will be a <span class="text blue">mirror</span>
|
||||
migrate_type_helper=Šis repozitorijs būs <span class="text blue">spogulis</span>
|
||||
migrate_repo=Migrēt repozitoriju
|
||||
migrate.clone_address=Clone Address
|
||||
migrate.clone_address_desc=This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.invalid_local_path=Invalid local path, it does not exist or not a directory.
|
||||
migrate.clone_address=Klonēšanas adrese
|
||||
migrate.clone_address_desc=Tas var būt HTTP/HTTPS/GIT URL vai ceļš uz lokālā servera.
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=Nekorents lokālais ceļš, tas neeksistē vai nav direktorijs.
|
||||
|
||||
forked_from=forked from
|
||||
fork_from_self=You cannot fork repository you already owned!
|
||||
forked_from=atdalīts no
|
||||
fork_from_self=Nav iespējams atdalīt repozitoriju, kuram esat īpašnieks!
|
||||
copy_link=Kopēt
|
||||
copy_link_success=Nokopēts!
|
||||
copy_link_error=Nospiediet ⌘-C vai Ctrl-C, lai nokopētu
|
||||
click_to_copy=Kopēt uz starpliktuvi
|
||||
copied=Kopēšana notikusi veiksmīgi
|
||||
clone_helper=Nepieciešama palīdzība kā veikt klonēšana? Apmeklējiet <a target="_blank" href="%s">Palīdzība</a> lapu!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Īsa pamācība
|
||||
clone_this_repo=Klonēt šo repozitoriju
|
||||
create_new_repo_command=Izveidot jaunu repozitoriju komandrindā
|
||||
push_exist_repo=Nosūtīt izmaiņas no komandrindas eksistējošam repozitorijam
|
||||
repo_is_empty=Šis repozitorijs ir tukšs, apskatiet atkal vēlāk!
|
||||
|
||||
|
||||
branch=Atzars
|
||||
tree=Koks
|
||||
@@ -370,15 +391,15 @@ branch_and_tags=Atzari un tagi
|
||||
branches=Atzari
|
||||
tags=Tagi
|
||||
issues=Problēmas
|
||||
pulls=Pull Requests
|
||||
labels=Labels
|
||||
milestones=Milestones
|
||||
pulls=Izmaiņu pieprasījumi
|
||||
labels=Etiķetes
|
||||
milestones=Atskaites punkti
|
||||
commits=Revīzijas
|
||||
releases=Laidieni
|
||||
file_raw=Neapstrādāts
|
||||
file_history=Vēsture
|
||||
file_view_raw=Rādīt neapstrādātu
|
||||
file_permalink=Permalink
|
||||
file_permalink=Patstāvīgā saite
|
||||
|
||||
commits.commits=Revīzijas
|
||||
commits.search=Meklēt revīzijas
|
||||
@@ -389,124 +410,126 @@ commits.date=Datums
|
||||
commits.older=Vecāki
|
||||
commits.newer=Jaunāki
|
||||
|
||||
issues.new=New Issue
|
||||
issues.new.labels=Labels
|
||||
issues.new.no_label=No Label
|
||||
issues.new.clear_labels=Clear labels
|
||||
issues.new.milestone=Milestone
|
||||
issues.new.no_milestone=No Milestone
|
||||
issues.new.clear_milestone=Clear milestone
|
||||
issues.new.open_milestone=Open Milestones
|
||||
issues.new.closed_milestone=Closed Milestones
|
||||
issues.new.assignee=Assignee
|
||||
issues.new.clear_assignee=Clear assignee
|
||||
issues.new.no_assignee=No assignee
|
||||
issues.create=Create Issue
|
||||
issues.new_label=New Label
|
||||
issues.new_label_placeholder=Label name...
|
||||
issues.create_label=Create Label
|
||||
issues.open_tab=%d Open
|
||||
issues.close_tab=%d Closed
|
||||
issues.filter_label=Label
|
||||
issues.filter_label_no_select=No selected label
|
||||
issues.filter_milestone=Milestone
|
||||
issues.filter_milestone_no_select=No selected milestone
|
||||
issues.filter_assignee=Assignee
|
||||
issues.filter_assginee_no_select=No selected Assignee
|
||||
issues.filter_type=Type
|
||||
issues.filter_type.all_issues=All issues
|
||||
issues.filter_type.assigned_to_you=Assigned to you
|
||||
issues.filter_type.created_by_you=Created by you
|
||||
issues.filter_type.mentioning_you=Mentioning you
|
||||
issues.filter_sort=Sort
|
||||
issues.filter_sort.latest=Newest
|
||||
issues.filter_sort.oldest=Oldest
|
||||
issues.filter_sort.recentupdate=Recently updated
|
||||
issues.filter_sort.leastupdate=Least recently updated
|
||||
issues.filter_sort.mostcomment=Most commented
|
||||
issues.filter_sort.leastcomment=Least commented
|
||||
issues.opened_by=opened %[1]s by <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=opened %[1]s by %[2]s
|
||||
issues.previous=Previous
|
||||
issues.next=Next
|
||||
issues.open_title=Open
|
||||
issues.closed_title=Closed
|
||||
issues.num_comments=%d comments
|
||||
issues.commented_at=`commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=There is no content yet.
|
||||
issues.close_issue=Close
|
||||
issues.close_comment_issue=Close and comment
|
||||
issues.reopen_issue=Reopen
|
||||
issues.reopen_comment_issue=Reopen and comment
|
||||
issues.create_comment=Comment
|
||||
issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Poster
|
||||
issues.admin=Admin
|
||||
issues.owner=Owner
|
||||
issues.sign_up_for_free=Sign up for free
|
||||
issues.sign_in_require_desc=to join this conversation. Already have an account? <a href="%s">Sign in to comment</a>
|
||||
issues.edit=Edit
|
||||
issues.cancel=Cancel
|
||||
issues.save=Save
|
||||
issues.label_title=Label name
|
||||
issues.label_color=Label color
|
||||
issues.label_count=%d labels
|
||||
issues.label_open_issues=%d open issues
|
||||
issues.label_edit=Edit
|
||||
issues.label_delete=Delete
|
||||
issues.label_modify=Label Modification
|
||||
issues.label_deletion=Label Deletion
|
||||
issues.label_deletion_desc=Delete this label will remove its information in all related issues. Do you want to continue?
|
||||
issues.label_deletion_success=Label has been deleted successfully!
|
||||
issues.new=Jauna problēma
|
||||
issues.new.labels=Etiķetes
|
||||
issues.new.no_label=Nav etiķešu
|
||||
issues.new.clear_labels=Noņemt etiķetes
|
||||
issues.new.milestone=Atskaites punkts
|
||||
issues.new.no_milestone=Nav atskaites punktu
|
||||
issues.new.clear_milestone=Notīrīt atskaites punktus
|
||||
issues.new.open_milestone=Atvērtie atskaites punktus
|
||||
issues.new.closed_milestone=Aizvērtie atskaites punkti
|
||||
issues.new.assignee=Atbildīgais
|
||||
issues.new.clear_assignee=Noņemt atbildīgo
|
||||
issues.new.no_assignee=Nav atbildīgā
|
||||
issues.create=Pieteikt problēmu
|
||||
issues.new_label=Jauna etiķete
|
||||
issues.new_label_placeholder=Etiķetes nosaukums...
|
||||
issues.create_label=Izveidot etiķeti
|
||||
issues.open_tab=%d atvērti
|
||||
issues.close_tab=%d aizvērti
|
||||
issues.filter_label=Etiķete
|
||||
issues.filter_label_no_select=Nav atzīmēta etiķete
|
||||
issues.filter_milestone=Atskaites punkts
|
||||
issues.filter_milestone_no_select=Nav atzīmēts atskaites punkts
|
||||
issues.filter_assignee=Atbildīgais
|
||||
issues.filter_assginee_no_select=Nav atzīmēts atbildīgais
|
||||
issues.filter_type=Veids
|
||||
issues.filter_type.all_issues=Visas problēmas
|
||||
issues.filter_type.assigned_to_you=Piešķirtās Jums
|
||||
issues.filter_type.created_by_you=Jūsu izveidotās
|
||||
issues.filter_type.mentioning_you=Esat pieminēts
|
||||
issues.filter_sort=Kārtot
|
||||
issues.filter_sort.latest=Jaunākie
|
||||
issues.filter_sort.oldest=Vecakie
|
||||
issues.filter_sort.recentupdate=Nesen atjaunotās
|
||||
issues.filter_sort.leastupdate=Vissenāk atjaunotās
|
||||
issues.filter_sort.mostcomment=Visvairāk komentētās
|
||||
issues.filter_sort.leastcomment=Vismazāk komentētās
|
||||
issues.opened_by=<a href="%[2]s">%[3]s</a> atvēra %[1]s
|
||||
issues.opened_by_fake=%[2]s atvēra %[1]s
|
||||
issues.previous=Iepriekšējā
|
||||
issues.next=Nākamā
|
||||
issues.open_title=Atvērta
|
||||
issues.closed_title=Slēgta
|
||||
issues.num_comments=%d komentāri
|
||||
issues.commented_at=`komentēja <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=Vēl nav satura.
|
||||
issues.close_issue=Aizvērt
|
||||
issues.close_comment_issue=Komentēt un aizvērt
|
||||
issues.reopen_issue=Atvērt atkārtoti
|
||||
issues.reopen_comment_issue=Komentēt un atvērt atkārtoti
|
||||
issues.create_comment=Komentēt
|
||||
issues.closed_at=`aizvērts <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`atvērts atkārtoti <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`pieminēja šo problēmu revīzijā <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Autors
|
||||
issues.admin=Administrators
|
||||
issues.owner=Īpašnieks
|
||||
issues.sign_up_for_free=Pievienojieties
|
||||
issues.sign_in_require_desc=, lai piedalītos diskusijā. Jau ir konts? <a href="%s">Pierakstieties, lai komentētu</a>
|
||||
issues.edit=Labot
|
||||
issues.cancel=Atcelt
|
||||
issues.save=Saglabāt
|
||||
issues.label_title=Etiķetes nosaukums
|
||||
issues.label_color=Etiķetes krāsa
|
||||
issues.label_count=%d etiķetes
|
||||
issues.label_open_issues=%d atvērtas problēmas
|
||||
issues.label_edit=Labot
|
||||
issues.label_delete=Dzēst
|
||||
issues.label_modify=Etiķetes labošana
|
||||
issues.label_deletion=Etiķetes dzēšana
|
||||
issues.label_deletion_desc=Dzēšot šo etiķeti, tā tiks noņemta no visām saistītajām problēmām. Vai vēlaties turpināt?
|
||||
issues.label_deletion_success=Etiķete tika veiksmīgi izdzēsta!
|
||||
|
||||
pulls.compare_changes=Compare Changes
|
||||
pulls.compare_changes_desc=Compare two branches and make a pull request for changes.
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=compare
|
||||
pulls.filter_branch=Filter branch
|
||||
pulls.no_results=No results found.
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Create Pull Request
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversation
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Files changed
|
||||
pulls.reopen_to_merge=Please reopen this pull request to perform merge operation.
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.compare_changes=Salīdzināt izmaiņas
|
||||
pulls.compare_changes_desc=Salīdzināt divus atzarus un izveidot izmaiņu pieprasījumu.
|
||||
pulls.compare_base=pamata
|
||||
pulls.compare_compare=salīdzināmais
|
||||
pulls.filter_branch=Filtrēt atzarus
|
||||
pulls.no_results=Nekas netika atrasts.
|
||||
pulls.nothing_to_compare=Nav ko salīdzināt, jo bāzes un salīdzināmie atzari ir vienādi.
|
||||
pulls.has_pull_request=`Jau eksistē izmaiņu pieprasījums starp šiem diviem atzariem: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Izveidot izmaiņu pieprasījumu
|
||||
pulls.title_desc=vēlas sapludināt %[1]d revīzijas no <code>%[2]s</code> uz <code>%[3]s</code>
|
||||
pulls.merged_title_desc=sapludināja %[1]d revīzijas no <code>%[2]s</code> uz <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Saruna
|
||||
pulls.tab_commits=Revīzijas
|
||||
pulls.tab_files=Izmainītie faili
|
||||
pulls.reopen_to_merge=Lūdzu, atkārtoti atveriet šo izmaiņu pieprasījumu, lai veiktu sapludināšanu.
|
||||
pulls.merged=Sapludināts
|
||||
pulls.has_merged=Šo izmaiņu pieprasījums tika veiksmīgi sapludināts!
|
||||
pulls.data_broken=Nepieejami izmaiņu pieprasījuma dati, jo dzēsta informācija no atdalītā repozitorija.
|
||||
pulls.is_checking=Notiek konfliktu pārbaude, mirkli uzgaidiet un atjaunojiet lapu.
|
||||
pulls.can_auto_merge_desc=Ir iespējams veikt automātisko sapludināšanas darbību šim izmaiņu pieprasījumam.
|
||||
pulls.cannot_auto_merge_desc=Nav iespējams veikt automātisko sapludināšanas darbību, jo starp revīzijām ir konflikti.
|
||||
pulls.cannot_auto_merge_helper=Lūdzu, izmantojiet komandrindas rīku, lai to atrisinātu.
|
||||
pulls.merge_pull_request=Izmaiņu pieprasījuma sapludināšana
|
||||
pulls.open_unmerged_pull_exists=`Jūs nevarat veikt atkārtotas atvēršanas darbību, jo jau eksistē izmaiņu pieprasījums (#%d) no šī repozitorija ar tādu pašu sapludināšanas informāciju un gaida sapludināšanu.`
|
||||
|
||||
milestones.new=New Milestone
|
||||
milestones.open_tab=%d Open
|
||||
milestones.close_tab=%d Closed
|
||||
milestones.closed=Closed %s
|
||||
milestones.no_due_date=No due date
|
||||
milestones.open=Open
|
||||
milestones.close=Close
|
||||
milestones.new_subheader=Create milestones to organize your issues.
|
||||
milestones.create=Create Milestone
|
||||
milestones.title=Title
|
||||
milestones.desc=Description
|
||||
milestones.due_date=Due Date (optional)
|
||||
milestones.clear=Clear
|
||||
milestones.invalid_due_date_format=Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.create_success=Milestone '%s' has been created successfully!
|
||||
milestones.edit=Edit Milestone
|
||||
milestones.edit_subheader=Use better description for milestones so people won't be confused.
|
||||
milestones.cancel=Cancel
|
||||
milestones.modify=Modify Milestone
|
||||
milestones.edit_success=Changes of milestone '%s' has been saved successfully!
|
||||
milestones.deletion=Milestone Deletion
|
||||
milestones.deletion_desc=Delete this milestone will remove its information in all related issues. Do you want to continue?
|
||||
milestones.deletion_success=Milestone has been deleted successfully!
|
||||
milestones.new=Jauns atskaites punkts
|
||||
milestones.open_tab=%d atvērti
|
||||
milestones.close_tab=%d aizvērti
|
||||
milestones.closed=Aizvērts %s
|
||||
milestones.no_due_date=Bez termiņa
|
||||
milestones.open=Atvērt
|
||||
milestones.close=Aizvērt
|
||||
milestones.new_subheader=Izveidojiet atskaites punktus, lai organizētu problēmas.
|
||||
milestones.create=Izveidot atskaites punktu
|
||||
milestones.title=Virsraksts
|
||||
milestones.desc=Apraksts
|
||||
milestones.due_date=Termiņš (neobligāts)
|
||||
milestones.clear=Notīrīt
|
||||
milestones.invalid_due_date_format=Termiņa datuma formāts ir nekorekts, jābūt formātā 'gggg-mm-dd'.
|
||||
milestones.create_success=Atskaites punkts '%s' tika veiksmīgi izveidots!
|
||||
milestones.edit=Labot atskaites punktu
|
||||
milestones.edit_subheader=Izmantojiet pēc iespējas labāku aprakstu atskaites punktiem, lai citiem tas būtu saprotamāks.
|
||||
milestones.cancel=Atcelt
|
||||
milestones.modify=Mainīt atskaites punktu
|
||||
milestones.edit_success=Izmaiņas atskaites punktā '%s' tika veiksmīgi saglabātas!
|
||||
milestones.deletion=Atskaites punkta dzēšana
|
||||
milestones.deletion_desc=Dzēšot šo atskaites punktu tiks noņemta arī saistītā informācija no problēmu ziņojumiem. Vai vēlaties turpināt?
|
||||
milestones.deletion_success=Atskaites punkts tika veiksmīgi izdzēsts!
|
||||
|
||||
settings=Iestatījumi
|
||||
settings.options=Opcijas
|
||||
@@ -517,20 +540,20 @@ settings.basic_settings=Pamatiestatījumi
|
||||
settings.danger_zone=Bīstamā zona
|
||||
settings.site=Oficiālā mājas lapa
|
||||
settings.update_settings=Mainīt iestatījumus
|
||||
settings.change_reponame_prompt=This change will affect how links relate to the repository.
|
||||
settings.change_reponame_prompt=Šī izmaiņa ietekmēs saites, kas ir saistītas ar šo repozitoriju.
|
||||
settings.transfer=Mainīt īpašnieku
|
||||
settings.transfer_desc=Mainīt šī repozitorija īpašnieku uz citu lietotāju vai organizāciju, kurai Jums ir administratora tiesībs.
|
||||
settings.new_owner_has_same_repo=Jaunajam īpašniekam jau ir repozitorijs ar šādu nosaukumu.
|
||||
settings.delete=Dzēst šo repozitoriju
|
||||
settings.delete_desc=Dzēšot repozitoriju, tā datus vairs nebūs iespējams atgūt. Pirms dzēšanas pārliecinieites vai patiešām vēlaties to darīt.
|
||||
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
|
||||
settings.transfer_notices_2=- You will conserve access if new owner is an organization and if you're one of the owners.
|
||||
settings.transfer_form_title=Please enter following information to confirm your operation:
|
||||
settings.delete_notices_1=- This operation <strong>CANNOT</strong> be undone.
|
||||
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
|
||||
settings.delete_notices_fork_1=- If this repository is public, all forks will be became independent after deletion.
|
||||
settings.delete_notices_fork_2=- If this repository is private, all forks will be removed at the same time.
|
||||
settings.delete_notices_fork_3=- If you want to keep all forks after deletion, please change visibility of this repository to public first.
|
||||
settings.transfer_notices_1=- Jūs pazaudēsiet piekļuvi, ja jaunais īpašnieks ir lietotājs.
|
||||
settings.transfer_notices_2=- Jūs saglabāsiet piekļuvi, ja jaunais īpašnieks ir organizācija un Jūs esat viens no tās īpašniekiem.
|
||||
settings.transfer_form_title=Lūdzu, ievadiet sekojošu informāciju, lai apstiprinātu šo darbību:
|
||||
settings.delete_notices_1=- Šī darbība ir <strong>NEATGRIEZENISKA</strong>.
|
||||
settings.delete_notices_2=- Šī darbība neatgriezeniski izdzēsīs visus šī repozitorija datus, tai skaitā Git datus, problēmu ziņojumus, komentārus un definētās piekļuves tiesības.
|
||||
settings.delete_notices_fork_1=- Ja repozitorijs ir publisks, visi atdalītie repozitoriji kļūs neatkarīgi.
|
||||
settings.delete_notices_fork_2=- Ja repozitorijs ir privāts, tiks dzēsti arī visi atdalītie repozitoriji.
|
||||
settings.delete_notices_fork_3=- Ja vēlaties saglabāt atdalīts repozitorijus pēc dzēšanas, sākumā nomainiet repozitorija redzamību uz publisku.
|
||||
settings.update_settings_success=Repozitorija opcijas ir veiksmīgi saglabātas.
|
||||
settings.transfer_owner=Jaunais īpašnieks
|
||||
settings.make_transfer=Mainīt
|
||||
@@ -542,14 +565,14 @@ settings.remove_collaborator_success=Līdzstrādnieks tika noņemts.
|
||||
settings.user_is_org_member=Lietotājs ir organizācijas biedrs, kas nevar tikt pievienots kā līdzstrādnieks.
|
||||
settings.add_webhook=Pievienot tīmekļa āķi
|
||||
settings.hooks_desc=Tīmekļa āķi ļauj paziņot ārējiem servisiem par noteiktiem notikomiem, kas notiek Git servisā. Kad iestāsies kāds notikums, katram ārējā servisa URL tiks nosūtīts POST pieprasījums. Lai uzzinātu sīkāk skatieties <a target="_blank" href="%s">Tīmekļa āķu rokasgrāmatā</a>.
|
||||
settings.webhook_deletion=Delete Webhook
|
||||
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
|
||||
settings.webhook_deletion_success=Webhook has been deleted successfully!
|
||||
settings.webhook.request=Request
|
||||
settings.webhook.response=Response
|
||||
settings.webhook.headers=Headers
|
||||
settings.webhook.payload=Payload
|
||||
settings.webhook.body=Body
|
||||
settings.webhook_deletion=Dzēst tīmekļa āķi
|
||||
settings.webhook_deletion_desc=Dzēšot tīmekļa āķi tiks dzēsta visa ar to saistītā informācija un izpildes vēsture. Vai vēlaties turpināt?
|
||||
settings.webhook_deletion_success=Tīmekļa āķis tika veiksmīgi izdzēsts!
|
||||
settings.webhook.request=Pieprasījums
|
||||
settings.webhook.response=Atbilde
|
||||
settings.webhook.headers=Galvenes
|
||||
settings.webhook.payload=Derīgā krava
|
||||
settings.webhook.body=Saturs
|
||||
settings.githooks_desc=Git āķus apstrādā pats Git. Jūs varat labot atbalsīto āku failus sarakstā zemāk, lai veiktu pielāgotas darbības.
|
||||
settings.githook_edit_desc=Ja āķis nav aktīvs, tiks attēlots piemērs kā to izmantot. Atstājot āķa saturu tukšu, tas tiks atspējots.
|
||||
settings.githook_name=Āķa nosaukums
|
||||
@@ -559,17 +582,17 @@ settings.add_webhook_desc=Uz norādīto URL tiks nosūtīts <code>POST</code> pi
|
||||
settings.payload_url=Vērtuma URL
|
||||
settings.content_type=Satura tips
|
||||
settings.secret=Noslēpums
|
||||
settings.slack_username=Username
|
||||
settings.slack_icon_url=Icon URL
|
||||
settings.slack_color=Color
|
||||
settings.slack_username=Lietotājvārds
|
||||
settings.slack_icon_url=Ikonas URL
|
||||
settings.slack_color=Krāsa
|
||||
settings.event_desc=Kādu notikumu rezultātā tiktu izsaukts tīmekļā āķis?
|
||||
settings.event_push_only=Tikai izmaiņu nosūtīšanas notikumiem.
|
||||
settings.event_send_everything=I need <strong>everything</strong>.
|
||||
settings.event_choose=Let me choose what I need.
|
||||
settings.event_create=Create
|
||||
settings.event_create_desc=Branch, or tag created
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push to a repository
|
||||
settings.event_send_everything=Vēlos saņemt <strong>visu</strong>.
|
||||
settings.event_choose=Atzīmēt, ko vēlos saņemt.
|
||||
settings.event_create=Izveidot
|
||||
settings.event_create_desc=Atzara vai taga izveidošana
|
||||
settings.event_push=Izmaiņu nosūtīšana
|
||||
settings.event_push_desc=Git izmaiņu nosūtīšana uz repozitoriju
|
||||
settings.active=Aktīvs
|
||||
settings.active_helper=Tiks nosūtīti notikuma dati, kad nostrādās šis āķis.
|
||||
settings.add_hook_success=Jauns tīmekļa āķis tika veiksmīgi pievienots.
|
||||
@@ -583,16 +606,16 @@ settings.slack_token=Talons
|
||||
settings.slack_domain=Domēns
|
||||
settings.slack_channel=Kanāls
|
||||
settings.deploy_keys=Izvietot atslēgas
|
||||
settings.add_deploy_key=Add Deploy Key
|
||||
settings.no_deploy_keys=You haven't added any deploy key.
|
||||
settings.title=Title
|
||||
settings.deploy_key_content=Content
|
||||
settings.key_been_used=Deploy key content has been used.
|
||||
settings.key_name_used=Deploy key with same name has already existed.
|
||||
settings.add_key_success=New deploy key '%s' has been added successfully!
|
||||
settings.deploy_key_deletion=Delete Deploy Key
|
||||
settings.deploy_key_deletion_desc=Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||
settings.deploy_key_deletion_success=Deploy key has been deleted successfully!
|
||||
settings.add_deploy_key=Pievienot izvietošanas atslēgu
|
||||
settings.no_deploy_keys=Nav pievienota neviena izvietošanas atslēga.
|
||||
settings.title=Virsraksts
|
||||
settings.deploy_key_content=Saturs
|
||||
settings.key_been_used=Šāda izvietošanas atslēga jau eksistē.
|
||||
settings.key_name_used=Izvietošanas atslēga ar šādu nosaukumu jau eksistē.
|
||||
settings.add_key_success=Izvietošanas atslēga '%s' tik veiksmīgi pievienota!
|
||||
settings.deploy_key_deletion=Dzēst izvietošanas atslēgu
|
||||
settings.deploy_key_deletion_desc=Dzēšot šo izvietošanas atslēgu tiks noņemta arī ar to saistītā piekļuve šim repozitorijam. Vai vēlaties turpināt?
|
||||
settings.deploy_key_deletion_success=Izvietošanas atslēga tika veiksmīgi izdzēsta!
|
||||
|
||||
diff.browse_source=Pārlūkot izejas kodu
|
||||
diff.parent=vecāks
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Laidiens ar šādu taga nosaukumu jau eksistē.
|
||||
|
||||
[org]
|
||||
org_name_holder=Organizācijas nosaukums
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=Labi organizāciju nosaukumi ir īsi un tādi, kurus viegli atcerēties.
|
||||
org_email_helper=Uz organizācijas e-pastu tiks sūtītas visas notifikācias un apstiprinājumi.
|
||||
create_org=Izveidot organizāciju
|
||||
repo_updated=Atjaunināts
|
||||
people=Personas
|
||||
@@ -646,8 +669,8 @@ team_name_helper=Šo nosaukumu varēs izmantot, lai pieminētu komandu sarunās.
|
||||
team_desc_helper=Komandas apraksts
|
||||
team_permission_desc=Kādām tiesībām šai komandai būtu jābūt?
|
||||
|
||||
form.name_reserved=Organization name '%s' is reserved.
|
||||
form.name_pattern_not_allowed=Organization name pattern '%s' is not allowed.
|
||||
form.name_reserved=Organizācijas nosaukums '%s' ir rezervēts.
|
||||
form.name_pattern_not_allowed=Organizācijas nosaukums '%s' nav atļauts.
|
||||
|
||||
settings=Iestatījumi
|
||||
settings.options=Opcijas
|
||||
@@ -655,9 +678,9 @@ settings.full_name=Pilns vārds, uzvārds
|
||||
settings.website=Mājas lapa
|
||||
settings.location=Atrašanās vieta
|
||||
settings.update_settings=Mainīt iestatījumus
|
||||
settings.change_orgname=Mainīts organizācijas nosaukums
|
||||
settings.change_orgname_desc=Organizācijas nosaukums tiks mainīts, vai vēlaties turpinat? Tas ietekmēs saites, kas attiecas uz šo organizāciju.
|
||||
settings.update_setting_success=Organizācijas iestatījumi tika veiksmīgi saglabāti.
|
||||
settings.change_orgname_prompt=Šī izmaiņa ietekmēs saites, kas ir saistītas ar šo organizāciju.
|
||||
settings.update_avatar_success=Organizācijas avatara iestatījumi tika veiksmīgi saglabāti.
|
||||
settings.delete=Dzēst organizāciju
|
||||
settings.delete_account=Dzēst šo organizāciju
|
||||
settings.delete_prompt=Šī darbība pilnībā dzēsīs šo organizāciju, kā arī tā ir <strong>NEATGRIEZENISKA</strong>!
|
||||
@@ -713,8 +736,9 @@ authentication=Autentifikācijas
|
||||
config=Konfigurācija
|
||||
notices=Sistēmas paziņojumi
|
||||
monitor=Uzraudzība
|
||||
prev=Iepr.
|
||||
next=Tālāk
|
||||
first_page=Pirmā
|
||||
last_page=Pēdējā
|
||||
total=Kopā: %d
|
||||
|
||||
dashboard.statistic=Statistika
|
||||
dashboard.operations=Darbības
|
||||
@@ -773,19 +797,24 @@ users.activated=Aktivizēts
|
||||
users.admin=Administrators
|
||||
users.repos=Repozitoriji
|
||||
users.created=Izveidots
|
||||
users.send_register_notify=Nosūtīt lietotājam reģistrācijas paziņojumu
|
||||
users.new_success=Jauns konts '%s' tika veiksmīgi izveidots.
|
||||
users.edit=Labot
|
||||
users.auth_source=Autorizācijas avots
|
||||
users.auth_source=Autentificēšanas avots
|
||||
users.local=Iebūvētā
|
||||
users.auth_login_name=Autorizāciju, pietiekšanās vārds
|
||||
users.auth_login_name=Autentifikācijas pieteikšanās vārds
|
||||
users.password_helper=Atstājiet tukšu, ja nevēlaties mainīt.
|
||||
users.update_profile_success=Konta profils tika veiksmīgi saglabāts.
|
||||
users.edit_account=Labot kontu
|
||||
users.is_activated=Konts ir aktivizēts
|
||||
users.is_admin=Šim kontam ir administratora piekļuves tiesības
|
||||
users.allow_git_hook=Šim kontam ir tiesības pievienot/labot Git āķus
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=Mainīt konta profilu
|
||||
users.delete_account=Dzēst šo kontu
|
||||
users.still_own_repo=Šis konts ir vismaz viena repozitorija īpašnieks, tos sākumā ir nepieciešams izdzēst vai nomainīt to īpašnieku.
|
||||
users.still_has_org=Šis konts ir vismaz vienas organizācijas biedrs, sākumā nepieciešams pamest vai izdzēst šo organizāciju.
|
||||
users.deletion_success=Konts tika veiksmīgi izdzēsts!
|
||||
|
||||
orgs.org_manage_panel=Organizāciju pārvaldības panelis
|
||||
orgs.name=Nosaukums
|
||||
@@ -800,41 +829,47 @@ repos.watches=Vērošana
|
||||
repos.stars=Atzīmētās zvaigznītes
|
||||
repos.issues=Problēmas
|
||||
|
||||
auths.auth_manage_panel=Autorizāciju pārvaldības panelis
|
||||
auths.new=Pievienot jaunu autorizācijas veidu
|
||||
auths.auth_manage_panel=Autentifikācijas pārvaldības panelis
|
||||
auths.new=Pievienot jaunu avotu
|
||||
auths.name=Nosaukums
|
||||
auths.type=Veids
|
||||
auths.enabled=Iespējota
|
||||
auths.updated=Atjaunināta
|
||||
auths.auth_type=Autorizācijas veids
|
||||
auths.auth_name=Autorizācijas nosaukums
|
||||
auths.auth_type=Autentifikācijas tips
|
||||
auths.auth_name=Autentifikācijas nosaukums
|
||||
auths.domain=Domēns
|
||||
auths.host=Resursdators
|
||||
auths.port=Ports
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.user_base=User Search Base
|
||||
auths.attribute_name=First name attribute
|
||||
auths.attribute_surname=Surname attribute
|
||||
auths.attribute_mail=E-mail attribute
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.bind_dn=Saistīšanas DN
|
||||
auths.bind_password=Saistīšanas parole
|
||||
auths.bind_password_helper=Brīdinājums: Šī parole tiks saglabāta nešifrētā veidā. Neizmantojiet kontu ar augstām privilēģijām.
|
||||
auths.user_base=Lietotāja pamatnosacījumi
|
||||
auths.user_dn=Lietotāja DN
|
||||
auths.attribute_name=Vārda atribūts
|
||||
auths.attribute_surname=Uzvārda atribūts
|
||||
auths.attribute_mail=E-pasta atribūts
|
||||
auths.filter=Lietotāju filts
|
||||
auths.admin_filter=Administratoru filtrs
|
||||
auths.ms_ad_sa=MS Ad SA
|
||||
auths.smtp_auth=SMTP autorizācijas veids
|
||||
auths.smtp_auth=SMTP autentifikācijas tips
|
||||
auths.smtphost=SMTP resursdators
|
||||
auths.smtpport=SMTP ports
|
||||
auths.allowed_domains=Atļautie domēni
|
||||
auths.allowed_domains_helper=Atstājiet tukšu, ja nevēlaties ierobežot domēnu vārdus. Domēna vārdus nepieciešams atdalīt ar komatu ','.
|
||||
auths.enable_tls=Iespējot TLS šifrēšanu
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.pam_service_name=PAM Service Name
|
||||
auths.skip_tls_verify=Izlaist TLS verifikāciju
|
||||
auths.pam_service_name=PAM servisa nosaukums
|
||||
auths.enable_auto_register=Iespējot automātisko reģistrāciju
|
||||
auths.tips=Padomi
|
||||
auths.edit=Labot autorizācijas iestatījumus
|
||||
auths.edit=Labot autentifikācijas iestatījumus
|
||||
auths.activated=Autentifikācija ir aktivizēta
|
||||
auths.update_success=Autorizācijas iestatījumi tika veiksmīgi saglabāti.
|
||||
auths.update=Mainīt autorizācijas iestatījumus
|
||||
auths.delete=Dzēst šo autorizāciju
|
||||
auths.delete_auth_title=Autorizācijas dzēšana
|
||||
auths.delete_auth_desc=Šī autorizācija tiks dzēsta, vai vēlaties turpināt?
|
||||
auths.new_success=Jauna autentifikācija '%s' tika veiksmīgi pievienota.
|
||||
auths.update_success=Autentifikācijas iestatījumi tika veiksmīgi saglabāti.
|
||||
auths.update=Mainīt autentifikācijas iestatījumus
|
||||
auths.delete=Dzēst šo autentifikāciju
|
||||
auths.delete_auth_title=Autentifikācijas dzēšana
|
||||
auths.delete_auth_desc=Šī autentifikācija tiks dzēsta, vai vēlaties turpināt?
|
||||
auths.deletion_success=Autentifikācija tika veiksmīgi izdzēsta!
|
||||
|
||||
config.server_config=Servera konfigurācija
|
||||
config.app_name=Lietotnes nosaukums
|
||||
@@ -858,23 +893,25 @@ config.db_user=Lietotājs
|
||||
config.db_ssl_mode=SSL režīms
|
||||
config.db_ssl_mode_helper=(tikai PostgreSQL datu bāzei)
|
||||
config.db_path=Ceļš
|
||||
config.db_path_helper=(tikai Sqlite3 datu bāzei)
|
||||
config.db_path_helper=(priekš "sqlite3" and "tidb")
|
||||
config.service_config=Pakalpojuma konfigurācija
|
||||
config.register_email_confirm=Pieprasīt e-pasta apstiprināšanu
|
||||
config.disable_register=Atspējot jaunu lietotāju reģistrāciju
|
||||
config.show_registration_button=Rādīt reģistrēšanās pogu
|
||||
config.require_sign_in_view=Nepieciešama autorizācija
|
||||
config.mail_notify=Pasta paziņojumi
|
||||
config.enable_cache_avatar=Glabāt profila attēlus kešatmiņā
|
||||
config.mail_notify=Pasta paziņojumi
|
||||
config.disable_key_size_check=Atspējot atslēgas minimālā garuma pārbaudi
|
||||
config.enable_captcha=Iespējot drošības kodu
|
||||
config.active_code_lives=Aktīvā koda ilgums
|
||||
config.reset_password_code_lives=Paroles atiestatīšanas koda ilgums
|
||||
config.webhook_config=Tīkla āķu konfigurācija
|
||||
config.queue_length=Queue Length
|
||||
config.queue_length=Rindas garums
|
||||
config.deliver_timeout=Piegādes noildze
|
||||
config.skip_tls_verify=Izlaist TLS pārbaudi
|
||||
config.mailer_config=Sūtītāja konfigurācija
|
||||
config.mailer_enabled=Iespējots
|
||||
config.mailer_disable_helo=Disable HELO
|
||||
config.mailer_disable_helo=Atspējot HELO
|
||||
config.mailer_name=Nosaukums
|
||||
config.mailer_host=Resursdators
|
||||
config.mailer_user=Lietotājs
|
||||
@@ -919,12 +956,12 @@ notices.delete_success=Sistēmas paziņojums tika veiksmīgi izdzēsts.
|
||||
|
||||
[action]
|
||||
create_repo=izveidoja repozitoriju <a href="%s">%s</a>
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
rename_repo=pārsauca repozitoriju no <code>%[1]s</code> uz <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=veica izmaiņu nosūtīšanu atzaram <a href="%s/src/%s">%[2]s</a> repozitorijā <a href="%[1]s">%[3]s</a>
|
||||
create_issue=`reģistrēja problēmu <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`izveidoja izmaiņu pieprasījumu <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue=`pievienoja komentāru problēmai <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`sapludināja izmaiņu pieprasījumu <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=mainīja repozitorija <code>%s</code> īpašnieku uz <a href="%s">%s</a>
|
||||
push_tag=pievienoja tagu <a href="%s/src/%s">%[2]s</a> repozitorijam <a href="%[1]s">%[3]s</a>
|
||||
compare_2_commits=Veikt salīdzināšanu starp šīm 2 revīzijām
|
||||
@@ -951,8 +988,8 @@ raw_seconds=sekundes
|
||||
raw_minutes=minūtes
|
||||
|
||||
[dropzone]
|
||||
default_message=Drop files here or click to upload.
|
||||
invalid_input_type=You can't upload files of this type.
|
||||
file_too_big=File size({{filesize}} MB) exceeds maximum size({{maxFilesize}} MB).
|
||||
remove_file=Remove file
|
||||
default_message=Ievelciet failus šeit vai noklikšķiniet, lai augšupielādētu.
|
||||
invalid_input_type=Šādus failus nav iespējams augšupielādēt.
|
||||
file_too_big=Faila izmērs ({{filesize}} MB) pārsniedz maksimālo atļauto izmēru ({{maxFilesize}} MB).
|
||||
remove_file=Noņemt failu
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=Dashboard
|
||||
explore=Verkennen
|
||||
help=Help
|
||||
sign_in=Inloggen
|
||||
social_sign_in=Social netwerk inlog: tweede stap <small>account koppelen</small>
|
||||
sign_out=Afmelden
|
||||
sign_up=Aanmelden
|
||||
register=Registreer
|
||||
@@ -14,7 +13,7 @@ version=Versie
|
||||
page=Pagina
|
||||
template=Sjabloon
|
||||
language=Taal
|
||||
create_new=Maak een nieuwe...
|
||||
create_new=Create...
|
||||
user_profile_and_more=Gebruikersprofiel en meer
|
||||
signed_in_as=Aangemeld als
|
||||
|
||||
@@ -54,7 +53,8 @@ code=Code
|
||||
[install]
|
||||
install=Installatie
|
||||
title=Installatiestappen voor de eerste keer opstarten
|
||||
requite_db_desc=Om Gogs te gebruiken is MySQL, PostgreSQL of SQLite3 vereist (SQLite3 is beschikbaar in de officiële versie).
|
||||
docker_helper=If you're running Gogs inside Docker, please read <a target="_blank" href="%s">Guidelines</a> carefully before you change anything in this page!
|
||||
requite_db_desc=Gogs requires MySQL, PostgreSQL, SQLite3 or TiDB.
|
||||
db_title=Database instellingen
|
||||
db_type=Database-type
|
||||
host=Host
|
||||
@@ -64,8 +64,11 @@ db_name=Database naam
|
||||
db_helper=Gebruik InnoDB engine met utf8_general_ci karakterset voor MySQL.
|
||||
ssl_mode=SSL-modus
|
||||
path=Pad
|
||||
sqlite_helper=Het pad naar de SQLite3 database.
|
||||
err_empty_sqlite_path=SQLite3 database pad mag niet leeg zijn.
|
||||
sqlite_helper=The file path of SQLite3 or TiDB database.
|
||||
err_empty_db_path=SQLite3 or TiDB database path cannot be empty.
|
||||
err_invalid_tidb_name=TiDB database name does not allow characters "." and "-".
|
||||
no_admin_and_disable_registration=You cannot disable registration without creating an admin account.
|
||||
err_empty_admin_password=Admin password cannot be empty.
|
||||
|
||||
general_title=Toepassing algemene instellingen
|
||||
app_name=Applicatienaam
|
||||
@@ -99,6 +102,8 @@ disable_gravatar=Gravatar Service uitschakelen
|
||||
disable_gravatar_popup=Schakel Gravatar en andere bronnen uit, alle avatars worden door gebruikers geüpload of zijn standaard.
|
||||
disable_registration=Schakel zelfregistratie uit
|
||||
disable_registration_popup=Schakel zelfregistratie uit, alleen admins kunnen accounts maken.
|
||||
enable_captcha=Enable Captcha
|
||||
enable_captcha_popup=Require validate captcha for user self-registration.
|
||||
require_sign_in_view=Schakel vereiste aanmelding om pagina's te zien in
|
||||
require_sign_in_view_popup=Alleen ingelogde gebruikers kunnen pagina's bekijken, bezoekers kunnen alleen de login/registratie pagina's zien.
|
||||
admin_setting_desc=U hoeft niet meteen een administratie account te maken, de gebruiker met ID=1 krijgt automatisch administratierechten.
|
||||
@@ -106,7 +111,7 @@ admin_title=Instellingen beheerdersaccount
|
||||
admin_name=Gebruikersnaam
|
||||
admin_password=Wachtwoord
|
||||
confirm_password=Verifieer wachtwoord
|
||||
admin_email=E-mailadres
|
||||
admin_email=Admin E-mail
|
||||
install_gogs=Installeer Gogs
|
||||
test_git_failed=Git test niet gelukt: 'git' commando %v
|
||||
sqlite3_not_available=Uw versie biedt geen ondersteuning voor SQLite3, download de officiële binaire versie van %s, niet de gobuild versie.
|
||||
@@ -143,7 +148,6 @@ forgot_password=Wachtwoord vergeten
|
||||
forget_password=Wachtwoord vergeten?
|
||||
sign_up_now=Een account nodig? Meld u nu aan.
|
||||
confirmation_mail_sent_prompt=Een bevestigingsemail is gestuurd naar <b>%s</b>, Bevestig u aanvraag binnen %d uren om uw registratie te voltooien.
|
||||
sign_in_email=Meld u aan met uw e-mailadres
|
||||
active_your_account=Activeer uw account
|
||||
resent_limit_prompt=Sorry, u heeft te snel na elkaar een aanvraag gedaan voor een activatie mail. Wacht drie minuten voor uw volgende aanvraag.
|
||||
has_unconfirmed_mail=Beste %s, u heeft een onbevestigd e-mailadres (<b>%s</b>). Als u nog geen bevestiging heeft ontvangen, of u een nieuwe aanvraag wilt doen, klik dan op de onderstaande knop.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Sorry, uw bevestigingscode is verlopen of niet meer geldig.
|
||||
reset_password_helper=Klik hier om uw wachtwoord opnieuw in te stellen.
|
||||
password_too_short=De lengte van uw wachtwoord moet minimaal zes karakters zijn.
|
||||
|
||||
[mail]
|
||||
activate_account=Please activate your account
|
||||
activate_email=Verify your e-mail address
|
||||
reset_password=Reset your password
|
||||
register_success=Register success, Welcome
|
||||
|
||||
[modal]
|
||||
yes=Ja
|
||||
no=Nee
|
||||
@@ -181,6 +191,7 @@ min_size_error=moet minimaal %s karakters bevatten.
|
||||
max_size_error=mag maximaal %s karakters bevatten.
|
||||
email_error=is niet een valide e-mail adres.
|
||||
url_error=is niet een valide URL.
|
||||
include_error=` must contain substring '%s'.`
|
||||
unknown_error=Onbekende fout:
|
||||
captcha_incorrect=Captcha komt niet overeen.
|
||||
password_not_match=Wachtwoord en verificatie wachtwoord komen niet overeen.
|
||||
@@ -241,7 +252,7 @@ location=Locatie
|
||||
update_profile=Profile bijwerken
|
||||
update_profile_success=Uw profiel is succesvol bijgewerkt.
|
||||
change_username=Username veranderd
|
||||
change_username_desc=Gebruikersnaam is gewijzigd. Wilt u doorgaan? Dit zal gevolgen hebben voor alle koppelingen die betrekking hebben op uw account.
|
||||
change_username_prompt=This change will affect the way how links relate to your account.
|
||||
continue=Doorgaan
|
||||
cancel=Annuleren
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=Instellingen voor avatar succesvol bijgewerkt.
|
||||
change_password=Verander wachtwoord
|
||||
old_password=Huidige wachtwoord
|
||||
new_password=Nieuw wachtwoord
|
||||
retype_new_password=Retype New Password
|
||||
password_incorrect=Huidig wachtwoord is niet correct.
|
||||
change_password_success=Wachtwoord is succesvol gewijzigd. U kunt nu met uw nieuwe wachtwoord inloggen.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=Uw primaire e-mailadres zal worden gebruikt voor meldingen en andere
|
||||
primary=Primair
|
||||
primary_email=Instellen als primair
|
||||
delete_email=Verwijder
|
||||
email_deletion=E-mail Deletion
|
||||
email_deletion_desc=Delete this e-mail address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success=E-mail has been deleted successfully!
|
||||
add_new_email=Nieuw e-mailadres toevoegen
|
||||
add_email=E-mailadres toevoegen
|
||||
add_email_confirmation_sent=Een nieuwe bevestiging e-mail werd verstuurd naar <b>%s</b>, gelieve uw inbox in de komende %d uren te controleren om het bevestigingsproces te voltooien.
|
||||
add_email_confirmation_sent=Een nieuwe bevestiging e-mail werd verstuurd naar '%s', gelieve uw inbox in de komende %d uren te controleren om het bevestigingsproces te voltooien.
|
||||
add_email_success=Het e-mailadres was toegevoegd.
|
||||
|
||||
manage_ssh_keys=Beheer SSH sleutels
|
||||
@@ -319,6 +334,7 @@ repo_name=Repositorie naam
|
||||
repo_name_helper=Een goede repositorie naam is kort, memorabel en <strong>uniek</strong>.
|
||||
visibility=Zichtbaarheid
|
||||
visiblity_helper=Deze repositorie is <span class="ui red text">privaat</span>
|
||||
visiblity_helper_forced=Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(Verandering van deze waarde zal van invloed zijn op alle forks)
|
||||
fork_repo=Vork Repository
|
||||
fork_from=Afsplitsing van
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=Deze repositorie zal een <span class="text blue">mirror</spa
|
||||
migrate_repo=Migreer repositorie
|
||||
migrate.clone_address=Clone adres
|
||||
migrate.clone_address_desc=Dit kan een HTTP/HTTPS/GIT URL zijn of een lokaal pad.
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=Ongeldig lokaal pad, het pad bestaat niet of het is geen map.
|
||||
|
||||
forked_from=geforked van
|
||||
fork_from_self=U kunt geen repository forken die u al beheert!
|
||||
copy_link=Kopieer
|
||||
copy_link_success=Copied!
|
||||
copy_link_error=Press ⌘-C or Ctrl-C to copy
|
||||
click_to_copy=Kopieer link naar plakbord
|
||||
copied=Gekopieerd
|
||||
clone_helper=De behoeftehulp van klonen? Bezoek <a target="_blank" href="%s"> helpen</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Snelstart gids
|
||||
clone_this_repo=Kloon deze repositorie
|
||||
create_new_repo_command=Maak een nieuwe repositorie aan vanaf de console
|
||||
push_exist_repo=Push een bestaande repositorie vanaf de console
|
||||
repo_is_empty=This repository is empty, please come back later!
|
||||
|
||||
|
||||
branch=Aftakking
|
||||
tree=Boom
|
||||
@@ -479,10 +500,12 @@ pulls.reopen_to_merge=Please reopen this pull request to perform merge operation
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking=The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Samenvoegen van pull verzoek
|
||||
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new=Nieuwe mijlpaal
|
||||
milestones.open_tab=%d geopend
|
||||
@@ -497,7 +520,7 @@ milestones.title=Titel
|
||||
milestones.desc=Beschrijving
|
||||
milestones.due_date=Vervaldatum (optioneel)
|
||||
milestones.clear=Leegmaken
|
||||
milestones.invalid_due_date_format=Formaat vervaldatum is ongeldig, moet zijn "jaar-mm-dd".
|
||||
milestones.invalid_due_date_format=Formaat vervaldatum is ongeldig, moet zijn "jjjj-mm-dd".
|
||||
milestones.create_success=Mijlpaal '%s' is met succes aangemaakt!
|
||||
milestones.edit=Bewerk mijlpaal
|
||||
milestones.edit_subheader=Gebruik een goede beschrijving voor mijlpalen, om verwarring te voorkomen.
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Versie met deze naam bestaat al.
|
||||
|
||||
[org]
|
||||
org_name_holder=Organisatienaam
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=Een goede organisatienaam is kort en memorabel.
|
||||
org_email_helper=Alle notificaties en bevestigingen worden gestuurd naar het e-mailadres van de organisatie.
|
||||
create_org=Nieuwe organisatie aanmaken
|
||||
repo_updated=Geupdate
|
||||
people=Mensen
|
||||
@@ -655,9 +678,9 @@ settings.full_name=Volledige naam
|
||||
settings.website=Website
|
||||
settings.location=Locatie
|
||||
settings.update_settings=Instellingen bijwerken
|
||||
settings.change_orgname=Organisatie naam veranderd
|
||||
settings.change_orgname_desc=De naam van de organisatie is veranderd, wilt u doorgaan? Dit zal gevolgen hebben voor alle koppelingen die betrekking hebben op deze organisatie.
|
||||
settings.update_setting_success=Organisatie instellingen zijn succesvol bijgewerkt.
|
||||
settings.change_orgname_prompt=This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success=Organization avatar setting has been updated successfully.
|
||||
settings.delete=Verwijder organisatie
|
||||
settings.delete_account=Verwijder deze organisatie
|
||||
settings.delete_prompt=Deze actie zal de origanisatie permanent verwijderen. U kunt dit <strong>NIET</strong> terug draaien!
|
||||
@@ -713,8 +736,9 @@ authentication=Autenticaties
|
||||
config=Configuratie
|
||||
notices=Systeem aankondigingen
|
||||
monitor=Bijhouden
|
||||
prev=Vorige
|
||||
next=Volgende
|
||||
first_page=First
|
||||
last_page=Last
|
||||
total=Total: %d
|
||||
|
||||
dashboard.statistic=Statistieken
|
||||
dashboard.operations=Bewerkingen
|
||||
@@ -773,19 +797,24 @@ users.activated=Geactiveerd
|
||||
users.admin=Admin
|
||||
users.repos=Repos
|
||||
users.created=Aangemaakt
|
||||
users.send_register_notify=Send Registration Notification To User
|
||||
users.new_success=New account '%s' has been created successfully.
|
||||
users.edit=Bewerken
|
||||
users.auth_source=Autorisatiebron
|
||||
users.auth_source=Authentication Source
|
||||
users.local=Lokaal
|
||||
users.auth_login_name=Autorisatie inlognaam
|
||||
users.auth_login_name=Authentication Login Name
|
||||
users.password_helper=Leave it empty to remain unchanged.
|
||||
users.update_profile_success=Profiel is succesvol bijgewerkt.
|
||||
users.edit_account=Bewerk account
|
||||
users.is_activated=Dit account is geactiveerd
|
||||
users.is_admin=Dit account heeft beheerdersrechten
|
||||
users.allow_git_hook=Deze account beschikt over machtigingen voor het maken van Git haken
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=Account profiel bijwerken
|
||||
users.delete_account=Dit account verwijderen
|
||||
users.still_own_repo=Dit account is nog steeds eigendom van een repositorie. U moet deze repositorie eerst verwijderen of overdragen.
|
||||
users.still_has_org=Deze account nog steeds lidmaatschap van organisatie, u hebt naar links of hen eerst verwijderen.
|
||||
users.deletion_success=Account has been deleted successfully!
|
||||
|
||||
orgs.org_manage_panel=Organisaties beheren
|
||||
orgs.name=Naam
|
||||
@@ -800,41 +829,47 @@ repos.watches=Volgers
|
||||
repos.stars=Sterren
|
||||
repos.issues=Kwesties
|
||||
|
||||
auths.auth_manage_panel=Autorisatiebeheerpaneel
|
||||
auths.new=Nieuwe autorisatiebron
|
||||
auths.auth_manage_panel=Authentication Manage Panel
|
||||
auths.new=Add New Source
|
||||
auths.name=Naam
|
||||
auths.type=Type
|
||||
auths.enabled=Ingeschakeld
|
||||
auths.updated=Bijgewerkt
|
||||
auths.auth_type=Autorisatietype
|
||||
auths.auth_name=Autorisatienaam
|
||||
auths.auth_type=Authentication Type
|
||||
auths.auth_name=Authentication Name
|
||||
auths.domain=Domein
|
||||
auths.host=Host
|
||||
auths.port=Poort
|
||||
auths.bind_dn=Binden DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.bind_password_helper=Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base=User Search Base
|
||||
auths.user_dn=User DN
|
||||
auths.attribute_name=Voornaam attribuut
|
||||
auths.attribute_surname=Achternaam attribuut
|
||||
auths.attribute_mail=E-mail attribuut
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.ms_ad_sa=MS Ad SA
|
||||
auths.smtp_auth=SMTP authenticatietype
|
||||
auths.smtp_auth=SMTP Authentication Type
|
||||
auths.smtphost=SMTP host
|
||||
auths.smtpport=SMTP poort
|
||||
auths.allowed_domains=Allowed Domains
|
||||
auths.allowed_domains_helper=Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
|
||||
auths.enable_tls=Activeer TLS-encryptie
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.pam_service_name=PAM servicenaam
|
||||
auths.enable_auto_register=Activeer automatische registratie
|
||||
auths.tips=Tips
|
||||
auths.edit=Bewerk autorisatie-instellingen
|
||||
auths.edit=Edit Authentication Setting
|
||||
auths.activated=Deze autorisatiemethode is geactiveerd
|
||||
auths.update_success=Autorisatie-instellingen zijn succesvol bijgewerkt.
|
||||
auths.update=Update autorisatie-instellingen
|
||||
auths.delete=Verwijder deze autorisatie
|
||||
auths.delete_auth_title=Verwijderings-autorisatie
|
||||
auths.delete_auth_desc=Deze autorisatiemethode wordt verwijderd. Weet u zeker dat u wilt doorgaan?
|
||||
auths.new_success=New authentication '%s' has been added successfully.
|
||||
auths.update_success=Authentication setting has been updated successfully.
|
||||
auths.update=Update Authentication Setting
|
||||
auths.delete=Delete This Authentication
|
||||
auths.delete_auth_title=Authentication Deletion
|
||||
auths.delete_auth_desc=This authentication is going to be deleted, do you want to continue?
|
||||
auths.deletion_success=Authentication has been deleted successfully!
|
||||
|
||||
config.server_config=Serverconfiguratie
|
||||
config.app_name=Applicatienaam
|
||||
@@ -858,14 +893,16 @@ config.db_user=Gebruiker
|
||||
config.db_ssl_mode=SSL modus
|
||||
config.db_ssl_mode_helper=(alleen voor "postgres")
|
||||
config.db_path=Pad
|
||||
config.db_path_helper=(alleen voor "sqlite3")
|
||||
config.db_path_helper=(for "sqlite3" and "tidb")
|
||||
config.service_config=Serviceconfiguratie
|
||||
config.register_email_confirm=E-mailbevestiging registreren
|
||||
config.disable_register=Registratie uitgeschakeld
|
||||
config.show_registration_button=Registeren knop weergeven
|
||||
config.require_sign_in_view=Inloggen vereist om te kunnen inzien
|
||||
config.mail_notify=E-mailnotificaties
|
||||
config.enable_cache_avatar=Avatar Cache inschakelen
|
||||
config.mail_notify=E-mailnotificaties
|
||||
config.disable_key_size_check=Disable Minimum Key Size Check
|
||||
config.enable_captcha=Enable Captcha
|
||||
config.active_code_lives=Actieve Code leven
|
||||
config.reset_password_code_lives=Reset wachtwoord Code leven
|
||||
config.webhook_config=Webhook configuratie
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=Pulpit
|
||||
explore=Odkrywaj
|
||||
help=Pomoc
|
||||
sign_in=Zaloguj się
|
||||
social_sign_in=Rejestracja przy pomocy sieci społecznościowych: 2 krok <small>kojarzenie konta</small>
|
||||
sign_out=Wyloguj
|
||||
sign_up=Zarejestruj się
|
||||
register=Zarejestruj się
|
||||
@@ -14,7 +13,7 @@ version=Wersja
|
||||
page=Strona
|
||||
template=Szablon
|
||||
language=Język
|
||||
create_new=Utwórz nowy...
|
||||
create_new=Utwórz...
|
||||
user_profile_and_more=Profil użytkownika i więcej
|
||||
signed_in_as=Zalogowany jako
|
||||
|
||||
@@ -54,7 +53,8 @@ code=Kod
|
||||
[install]
|
||||
install=Instalacja
|
||||
title=Kroki instalacyjne dla pierwszego uruchomienia
|
||||
requite_db_desc=Gogs wymaga MySQL, PostgreSQL lub SQLite3.
|
||||
docker_helper=If you're running Gogs inside Docker, please read <a target="_blank" href="%s">Guidelines</a> carefully before you change anything in this page!
|
||||
requite_db_desc=Gogs requires MySQL, PostgreSQL, SQLite3 or TiDB.
|
||||
db_title=Ustawienia bazy danych
|
||||
db_type=Typ bazy danych
|
||||
host=Host
|
||||
@@ -64,8 +64,11 @@ db_name=Nazwa bazy danych
|
||||
db_helper=Proszę użyć silnika INNODB z kodowaniem utf8_general_ci dla MySQL.
|
||||
ssl_mode=Tryb SSL
|
||||
path=Ścieżka
|
||||
sqlite_helper=Ścieżka do bazy SQLite3.
|
||||
err_empty_sqlite_path=Ścieżka do bazy danych SQLite3 nie może być pusta.
|
||||
sqlite_helper=The file path of SQLite3 or TiDB database.
|
||||
err_empty_db_path=SQLite3 or TiDB database path cannot be empty.
|
||||
err_invalid_tidb_name=TiDB database name does not allow characters "." and "-".
|
||||
no_admin_and_disable_registration=Rejestracji nie można wyłączyć bez tworzenia konta admina.
|
||||
err_empty_admin_password=Hasło admina nie może być pusta.
|
||||
|
||||
general_title=Ustawienia ogólne Gogs
|
||||
app_name=Nazwa aplikacji
|
||||
@@ -99,6 +102,8 @@ disable_gravatar=Wyłącz usługę Gravatar
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_registration=Wyłącz samodzielną rejestrację
|
||||
disable_registration_popup=Wyłącz samodzielną rejestrację użytkownika, tylko administrator będzie mógł tworzyć konta.
|
||||
enable_captcha=Włącz Captcha
|
||||
enable_captcha_popup=Require validate captcha for user self-registration.
|
||||
require_sign_in_view=Włącz wymóg zalogowania do przeglądania stron
|
||||
require_sign_in_view_popup=Tylko zalogowani użytkownicy będą mogli przeglądać strony, goście zobaczą tylko stronę logowania.
|
||||
admin_setting_desc=Nie musisz tworzyć konta administratora teraz, użytkownik z ID = 1 zyska dostęp administratora automatycznie.
|
||||
@@ -106,7 +111,7 @@ admin_title=Ustawienia konta administratora
|
||||
admin_name=Nazwa Użytkownika
|
||||
admin_password=Hasło
|
||||
confirm_password=Potwierdź hasło
|
||||
admin_email=E-mail
|
||||
admin_email=Admin E-mail
|
||||
install_gogs=Zainstaluj Gogs
|
||||
test_git_failed=Nie udało się przetestować polecenia "git": %v
|
||||
sqlite3_not_available=Twoje wydanie nie obsługuje SQLite3, proszę pobrać oficjalne wydanie z %s, a NIE wersję z gobuild.
|
||||
@@ -125,7 +130,7 @@ my_repos=Moje repozytoria
|
||||
collaborative_repos=Wspólne repozytoria
|
||||
my_orgs=Moje organizacje
|
||||
my_mirrors=Moje mirrory
|
||||
view_home=View %s
|
||||
view_home=Zobacz %s
|
||||
|
||||
issues.in_your_repos=W twoich repozytoriach
|
||||
|
||||
@@ -143,7 +148,6 @@ forgot_password=Zapomniałem hasła
|
||||
forget_password=Zapomniałeś hasła?
|
||||
sign_up_now=Potrzebujesz konta? Zarejestruj się teraz.
|
||||
confirmation_mail_sent_prompt=Nowa wiadomość e-mail z potwierdzeniem została wysłana do <b>%s</b>, proszę sprawdzić swoją skrzynkę odbiorczą w ciągu najbliższych godzin %d aby dokończyć proces rejestracji.
|
||||
sign_in_email=Zaloguj się na swój adres e-mail
|
||||
active_your_account=Aktywuj swoje konto
|
||||
resent_limit_prompt=Niestety, zbyt często wysyłasz e-mail aktywacyjny. Proszę odczekać 3 minuty.
|
||||
has_unconfirmed_mail=Witaj, %s, masz niepotwierdzony adres e-mail (<b>%s</b>). Jeśli nie otrzymałeś wiadomości e-mail z potwierdzeniem lub potrzebujesz wysłać nową, kliknij na poniższy przycisk.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Niestety, twój kod potwierdzający wygasł lub jest nieprawidłowy
|
||||
reset_password_helper=Kliknij tutaj, aby zresetować hasło
|
||||
password_too_short=Długość hasła nie może być mniejsza niż 6 znaków.
|
||||
|
||||
[mail]
|
||||
activate_account=Prosimy aktywować swoje konto
|
||||
activate_email=Sprawdź Twój adres e-mail
|
||||
reset_password=Zmień swoje hasło
|
||||
register_success=Zostałeś zarejestrowany, witamy
|
||||
|
||||
[modal]
|
||||
yes=Tak
|
||||
no=Nie
|
||||
@@ -176,11 +186,12 @@ AdminEmail=E-mail administratora
|
||||
require_error=` nie może być puste.`
|
||||
alpha_dash_error=` musi się składać z prawidłowych znaków alfanumerycznych, myślników oraz podkreśleń.`
|
||||
alpha_dash_dot_error=` musi się składać z prawidłowych znaków alfanumerycznych, myślników, podkreśleń oraz kropek.`
|
||||
size_error=` must be size %s.`
|
||||
size_error="musi być wielkości %s."
|
||||
min_size_error=` musi zawierać co najwyżej %s znaków.`
|
||||
max_size_error=` musi zawierać co najwyżej %s znaków.`
|
||||
email_error=` nie jest poprawnym adresem e-mail.`
|
||||
url_error=` nie jest poprawnym adresem URL.`
|
||||
include_error=` must contain substring '%s'.`
|
||||
unknown_error=Nieznany błąd:
|
||||
captcha_incorrect=Kod captcha nie zgadza się.
|
||||
password_not_match=Hasło i potwierdzenie nie zgadzają się.
|
||||
@@ -241,7 +252,7 @@ location=Lolalizacja
|
||||
update_profile=Zaktualizuj profil
|
||||
update_profile_success=Twój profil został pomyślnie zaktualizowany.
|
||||
change_username=Zmieniono nazwę użytkownika
|
||||
change_username_desc=Zmieniono nazwę użytkownika, czy chcesz kontynuować? To wpłynie na wszystkie linki odnoszą się do swojego konta.
|
||||
change_username_prompt=This change will affect the way how links relate to your account.
|
||||
continue=Konynuuj
|
||||
cancel=Anuluj
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=Ustawienia awatarów zostały pomyślnie zaktualizowane.
|
||||
change_password=Zmień hasło
|
||||
old_password=Aktualne hasło
|
||||
new_password=Nowe hasło
|
||||
retype_new_password=Powtórz nowe hasło
|
||||
password_incorrect=Bieżące hasło nie jest prawidłowe.
|
||||
change_password_success=Hasło zostało zmienione pomyślnie. Możesz teraz zalogować się za pomocą nowego hasła.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=Twój podstawowy adres e-mail będzie używany dla powiadomień i inn
|
||||
primary=Podstawowy
|
||||
primary_email=Ustaw jako podstawowy
|
||||
delete_email=Usuń
|
||||
email_deletion=Usunięcie wiadomości e-mail
|
||||
email_deletion_desc=Delete this e-mail address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success=E-mail został usunięty pomyślnie!
|
||||
add_new_email=Dodaj nowy e-mail
|
||||
add_email=Dodaj e-mail
|
||||
add_email_confirmation_sent=Nowa wiadomość e-mail z potwierdzeniem została wysłana do <b>%s</b>, proszę sprawdzić swoją skrzynkę odbiorczą w ciągu %d godzin, aby dokończyć proces potwierdzania.
|
||||
add_email_confirmation_sent=Nowa wiadomość e-mail z potwierdzeniem została wysłana do '%s', proszę sprawdzić swoją skrzynkę odbiorczą w ciągu %d godzin, aby dokończyć proces potwierdzania.
|
||||
add_email_success=Twój nowy e-mail został dodany pomyślnie.
|
||||
|
||||
manage_ssh_keys=Zarządzaj kluczami SSH
|
||||
@@ -276,14 +291,14 @@ ssh_desc=To jest lista kluczy SSH powiązanych z Twoim kontem. Usuń klucze, kt
|
||||
ssh_helper=<strong>Potrzebujesz pomocy?</strong> Sprawdź nasz przewodnik <a href="%s"> generowania kluczy SSH</a> lub rozwiązywanie <a href="%s">typowych problemów z SSH</a>.
|
||||
add_new_key=Dodaj klucz SSH
|
||||
ssh_key_been_used=Public key content has been used.
|
||||
ssh_key_name_used=Public key with same name has already existed.
|
||||
ssh_key_name_used=Klucz publiczny o tej samej nazwie już istnieje.
|
||||
key_name=Nazwa klucza
|
||||
key_content=Treść
|
||||
add_key_success=New SSH key '%s' has been added successfully!
|
||||
add_key_success=Pomyślnie dodano nowy klucz SSH '%s'!
|
||||
delete_key=Usuń
|
||||
ssh_key_deletion=Usunięcie klucza SSH
|
||||
ssh_key_deletion_desc=Usunięcie tego klucza SSH będzie skutkować usunięciem wszystkich powiązanych dostępów do twojego konta. Czy chcesz kontynuować?
|
||||
ssh_key_deletion_success=SSH key has been deleted successfully!
|
||||
ssh_key_deletion_success=Klucz SSH został usunięty pomyślnie!
|
||||
add_on=Dodano
|
||||
last_used=Ostatnio użyto
|
||||
no_activity=Brak aktywności
|
||||
@@ -319,6 +334,7 @@ repo_name=Nazwa repozytorium
|
||||
repo_name_helper=Dobre nazwy repozytorium są krótkie, wpadające w pamięć i <strong>unikalne</strong>.
|
||||
visibility=Widoczność
|
||||
visiblity_helper=To repozytorium jest <span class="ui red text">prywatne</span>
|
||||
visiblity_helper_forced=Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
fork_repo=Sforkowane
|
||||
fork_from=Forkuj z
|
||||
@@ -329,8 +345,8 @@ repo_lang_helper=Wybierz pliki .gitignore
|
||||
license=Licencja
|
||||
license_helper=Wybierz plik licencji
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
readme_helper=Wybierz szablon readme
|
||||
auto_init=Initialize this repository with selected files and template
|
||||
create_repo=Utwórz repozytorium
|
||||
default_branch=Domyślna gałąź
|
||||
mirror_interval=Odświeżanie mirrorów (godziny)
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=This repository will be a <span class="text blue">mirror</sp
|
||||
migrate_repo=Przenieś repozytorium
|
||||
migrate.clone_address=Sklonuj adres
|
||||
migrate.clone_address_desc=This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=Ścieżka jest niepoprawna. Nie istnieje lub nie jest katalogiem.
|
||||
|
||||
forked_from=sklonowany z
|
||||
fork_from_self=You cannot fork repository you already owned!
|
||||
copy_link=Kopiuj
|
||||
copy_link_success=Skopiowane!
|
||||
copy_link_error=Naciśnij klawisze ⌘-C i Ctrl-C, aby skopiować
|
||||
click_to_copy=Kopiuj do schowka
|
||||
copied=Skopiowano
|
||||
clone_helper=Potrzebujesz pomocy z klonowaniem? Odwiedź <a target="_blank" href="%s">Pomoc</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Skrócona instrukcja
|
||||
clone_this_repo=Klonuj repozytorium
|
||||
create_new_repo_command=Utwórz nowe repozytorium z wiersza poleceń
|
||||
push_exist_repo=Wyślij istniejące repozytorium z wiersza poleceń
|
||||
repo_is_empty=To repozytorium jest puste, proszę wrócić później!
|
||||
|
||||
|
||||
branch=Gałąź
|
||||
tree=Drzewo
|
||||
@@ -372,7 +393,7 @@ tags=Tagi
|
||||
issues=Problemy
|
||||
pulls=Pull Requests
|
||||
labels=Etykiety
|
||||
milestones=Milestones
|
||||
milestones=Kamienie milowe
|
||||
commits=Commity
|
||||
releases=Wydania
|
||||
file_raw=Czysty
|
||||
@@ -396,13 +417,13 @@ issues.new.clear_labels=Wyczyść etykiety
|
||||
issues.new.milestone=Kamień milowy
|
||||
issues.new.no_milestone=No Milestone
|
||||
issues.new.clear_milestone=Clear milestone
|
||||
issues.new.open_milestone=Open Milestones
|
||||
issues.new.closed_milestone=Closed Milestones
|
||||
issues.new.open_milestone=Otwórz "kamienie milowe"
|
||||
issues.new.closed_milestone=Zamknięte "kamienie milowe"
|
||||
issues.new.assignee=Assignee
|
||||
issues.new.clear_assignee=Clear assignee
|
||||
issues.new.no_assignee=No assignee
|
||||
issues.create=Create Issue
|
||||
issues.new_label=New Label
|
||||
issues.new_label=Nowa etykieta
|
||||
issues.new_label_placeholder=Label name...
|
||||
issues.create_label=Create Label
|
||||
issues.open_tab=%d Open
|
||||
@@ -434,10 +455,10 @@ issues.closed_title=zamknięty
|
||||
issues.num_comments=%d komentarzy
|
||||
issues.commented_at=`commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=There is no content yet.
|
||||
issues.close_issue=Close
|
||||
issues.close_comment_issue=Close and comment
|
||||
issues.reopen_issue=Reopen
|
||||
issues.reopen_comment_issue=Reopen and comment
|
||||
issues.close_issue=Zamknij
|
||||
issues.close_comment_issue=Skomentuj i zamknij
|
||||
issues.reopen_issue=Otwórz ponownie
|
||||
issues.reopen_comment_issue=Otwórz ponownie i dodaj komentarz
|
||||
issues.create_comment=Komentuj
|
||||
issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
@@ -459,13 +480,13 @@ issues.label_delete=Usuń
|
||||
issues.label_modify=Modyfikacja etykiety
|
||||
issues.label_deletion=Usunięcie etykiety
|
||||
issues.label_deletion_desc=Delete this label will remove its information in all related issues. Do you want to continue?
|
||||
issues.label_deletion_success=Label has been deleted successfully!
|
||||
issues.label_deletion_success=Etykieta została usunięta pomyślnie!
|
||||
|
||||
pulls.compare_changes=Compare Changes
|
||||
pulls.compare_changes_desc=Compare two branches and make a pull request for changes.
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=compare
|
||||
pulls.filter_branch=Filter branch
|
||||
pulls.filter_branch=Filtruj branch
|
||||
pulls.no_results=Nie znaleziono wyników.
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
@@ -473,16 +494,18 @@ pulls.create=Utwórz Pull Request
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversation
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Files changed
|
||||
pulls.tab_commits=Commity
|
||||
pulls.tab_files=Pliki zmodyfikowane
|
||||
pulls.reopen_to_merge=Please reopen this pull request to perform merge operation.
|
||||
pulls.merged=Merged
|
||||
pulls.merged=Scalone
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking=The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.merge_pull_request=Scal Pull Request
|
||||
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new=Nowy kamień milowy
|
||||
milestones.open_tab=%d Open
|
||||
@@ -492,17 +515,17 @@ milestones.no_due_date=Nie ustalono terminu
|
||||
milestones.open=Otwórz
|
||||
milestones.close=Zamknij
|
||||
milestones.new_subheader=Create milestones to organize your issues.
|
||||
milestones.create=Create Milestone
|
||||
milestones.title=Title
|
||||
milestones.desc=Description
|
||||
milestones.due_date=Due Date (optional)
|
||||
milestones.create=Utwórz punkt kontrolny
|
||||
milestones.title=Tytuł
|
||||
milestones.desc=Opis
|
||||
milestones.due_date=Termin realizacji (opcjonalnie)
|
||||
milestones.clear=Wyczyść
|
||||
milestones.invalid_due_date_format=Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.create_success=Milestone '%s' has been created successfully!
|
||||
milestones.edit=Edit Milestone
|
||||
milestones.invalid_due_date_format=Format daty realizacji jest nieprawidłowy, musi być "rrrr-mm-dd".
|
||||
milestones.create_success=Kamień milowy "%s" został utworzony pomyślnie!
|
||||
milestones.edit=Edytuj kamień milowy
|
||||
milestones.edit_subheader=Use better description for milestones so people won't be confused.
|
||||
milestones.cancel=Cancel
|
||||
milestones.modify=Modify Milestone
|
||||
milestones.cancel=Anuluj
|
||||
milestones.modify=Modyfikuj kamień milowy
|
||||
milestones.edit_success=Changes of milestone '%s' has been saved successfully!
|
||||
milestones.deletion=Milestone Deletion
|
||||
milestones.deletion_desc=Delete this milestone will remove its information in all related issues. Do you want to continue?
|
||||
@@ -561,12 +584,12 @@ settings.content_type=Typ zawartości
|
||||
settings.secret=Sekret
|
||||
settings.slack_username=Username
|
||||
settings.slack_icon_url=Icon URL
|
||||
settings.slack_color=Color
|
||||
settings.slack_color=Kolor
|
||||
settings.event_desc=Jakie zdarzenia mają wywoływać ten skrypt internetowy?
|
||||
settings.event_push_only=Tylko zdarzenia <code>push</code>.
|
||||
settings.event_send_everything=I need <strong>everything</strong>.
|
||||
settings.event_choose=Let me choose what I need.
|
||||
settings.event_create=Create
|
||||
settings.event_send_everything=Potrzebuję <strong>wszystkiego</strong>.
|
||||
settings.event_choose=Pozwól mi wybrać, czego potrzebuję.
|
||||
settings.event_create=Utwórz
|
||||
settings.event_create_desc=Branch, or tag created
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push to a repository
|
||||
@@ -585,8 +608,8 @@ settings.slack_channel=Kanał
|
||||
settings.deploy_keys=Klucze wdrożeniowe
|
||||
settings.add_deploy_key=Add Deploy Key
|
||||
settings.no_deploy_keys=You haven't added any deploy key.
|
||||
settings.title=Title
|
||||
settings.deploy_key_content=Content
|
||||
settings.title=Tytuł
|
||||
settings.deploy_key_content=Treść
|
||||
settings.key_been_used=Deploy key content has been used.
|
||||
settings.key_name_used=Deploy key with same name has already existed.
|
||||
settings.add_key_success=New deploy key '%s' has been added successfully!
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Wersja o tej nazwie tagu już istnieje.
|
||||
|
||||
[org]
|
||||
org_name_holder=Nazwa organizacji
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=Świetne nazwy organizacji są krótkie i łatwe do zapamiętania.
|
||||
org_email_helper=Adres e-mail organizacji otrzymuje wszystkie powiadomienia i potwierdzenia.
|
||||
create_org=Utwórz organizację
|
||||
repo_updated=Zaktualizowano
|
||||
people=Ludzie
|
||||
@@ -655,9 +678,9 @@ settings.full_name=Imię i Nazwisko
|
||||
settings.website=Strona
|
||||
settings.location=Lolalizacja
|
||||
settings.update_settings=Aktualizuj ustawienia
|
||||
settings.change_orgname=Zmieniono nazwę organizacji
|
||||
settings.change_orgname_desc=Zmieniono nazwę organizacji. Wpływa to na powiązanie odnośników z organizacją. Czy chcesz kontynuować?
|
||||
settings.update_setting_success=Ustawienia organizacji zostały pomyślnie zaktualizowane.
|
||||
settings.change_orgname_prompt=This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success=Organization avatar setting has been updated successfully.
|
||||
settings.delete=Usuń Organizację
|
||||
settings.delete_account=Usuń tą organizację
|
||||
settings.delete_prompt=Organizacja zostanie trwale usunięta, a to <strong>NIE MOŻE</strong> być cofnięte!
|
||||
@@ -713,8 +736,9 @@ authentication=Uwierzytelnienia
|
||||
config=Konfiguracja
|
||||
notices=Powiadomienia systemowe
|
||||
monitor=Monitorowanie
|
||||
prev=Wstecz
|
||||
next=Następny
|
||||
first_page=Pierwsza
|
||||
last_page=Ostatnia
|
||||
total=Ogółem: %d
|
||||
|
||||
dashboard.statistic=Statystyki
|
||||
dashboard.operations=Operacje
|
||||
@@ -773,19 +797,24 @@ users.activated=Aktywowany
|
||||
users.admin=Admin
|
||||
users.repos=Repozytoria
|
||||
users.created=Utworzony
|
||||
users.send_register_notify=Send Registration Notification To User
|
||||
users.new_success=New account '%s' has been created successfully.
|
||||
users.edit=Edytuj
|
||||
users.auth_source=Źródła autoryzacji
|
||||
users.auth_source=Authentication Source
|
||||
users.local=Lokalne
|
||||
users.auth_login_name=Login Autoryzacyjny
|
||||
users.auth_login_name=Authentication Login Name
|
||||
users.password_helper=Leave it empty to remain unchanged.
|
||||
users.update_profile_success=Profil konta został pomyślnie zaktualizowany.
|
||||
users.edit_account=Edytuj konto
|
||||
users.is_activated=To konto jest aktywne
|
||||
users.is_admin=To konto ma uprawnienia administratora
|
||||
users.allow_git_hook=To konto posiada uprawnienia do tworzenia skryptów Git
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=Zaktualizuj profil konta
|
||||
users.delete_account=Usuń to konto
|
||||
users.still_own_repo=Twoje konto jest dalej właścicielem repozytorium, musisz je usunąć lub przekazać.
|
||||
users.still_has_org=Twoje konto dalej posiada członkostwo w organizacji, musisz ją opuścić bądź usunąć.
|
||||
users.deletion_success=Account has been deleted successfully!
|
||||
|
||||
orgs.org_manage_panel=Panel zarządzania organizacją
|
||||
orgs.name=Nazwa
|
||||
@@ -800,41 +829,47 @@ repos.watches=Obserwujących
|
||||
repos.stars=Polubienia
|
||||
repos.issues=Problemy
|
||||
|
||||
auths.auth_manage_panel=Zarzadzanie Autoryzacja
|
||||
auths.new=Dodaj nowe źródło autoryzacji
|
||||
auths.auth_manage_panel=Authentication Manage Panel
|
||||
auths.new=Dodać nowe Źródło
|
||||
auths.name=Nazwa
|
||||
auths.type=Typ
|
||||
auths.enabled=Włączono
|
||||
auths.updated=Zaktualizowano
|
||||
auths.auth_type=Typ autoryzacji
|
||||
auths.auth_name=Nazwa autoryzacji
|
||||
auths.auth_type=Typ uwierzytelniania
|
||||
auths.auth_name=Nazwa uwierzytelniania
|
||||
auths.domain=Domena
|
||||
auths.host=Host
|
||||
auths.port=Port
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.bind_password_helper=Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base=User Search Base
|
||||
auths.user_dn=User DN
|
||||
auths.attribute_name=Atrybut imienia
|
||||
auths.attribute_surname=Atrybut nazwiska
|
||||
auths.attribute_mail=Atrybut email
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Typ autoryzacji SMTP
|
||||
auths.smtp_auth=SMTP Authentication Type
|
||||
auths.smtphost=Serwer SMTP
|
||||
auths.smtpport=Port SMTP
|
||||
auths.allowed_domains=Dozwolone domeny
|
||||
auths.allowed_domains_helper=Pozostaw puste aby nie ograniczać domen. Wiele domen powinno być oddzielone przecinkami ','.
|
||||
auths.enable_tls=Włącz szyfrowanie TLS
|
||||
auths.skip_tls_verify=Pomiń weryfikację protokołu TLS
|
||||
auths.pam_service_name=Nazwa usługi PAM
|
||||
auths.enable_auto_register=Włącz automatyczną rejestrację
|
||||
auths.tips=Wskazówki
|
||||
auths.edit=Edytuj ustawienia autoryzacji
|
||||
auths.edit=Edytuj ustawienia uwierzytelniania
|
||||
auths.activated=To uwierzytelnienie zostało aktywowane
|
||||
auths.new_success=Pomyślnie dodano nowe uwierzytelnianie '%s'.
|
||||
auths.update_success=Ustawienia uwierzytelnienia zostały zaktualizowane pomyślnie.
|
||||
auths.update=Zaktualizuj ustawienia autoryzacji
|
||||
auths.delete=Usuń tą autoryzację
|
||||
auths.delete_auth_title=Usuwanie autoryzacji
|
||||
auths.update=Aktualizuj ustawienia uwierzytelniania
|
||||
auths.delete=Usuń to uwierzytelnianie
|
||||
auths.delete_auth_title=Usunięcie uwierzytelniania
|
||||
auths.delete_auth_desc=To uwierzytelnienie zostanie usunięte, czy chcesz kontynuować?
|
||||
auths.deletion_success=Uwierzytelnianie zostało usunięte pomyślnie!
|
||||
|
||||
config.server_config=Konfiguracja serwera
|
||||
config.app_name=Nazwa Aplikacji
|
||||
@@ -858,14 +893,16 @@ config.db_user=Użytkownik
|
||||
config.db_ssl_mode=Tryb SSL
|
||||
config.db_ssl_mode_helper=(tylko dla "postgres")
|
||||
config.db_path=Ścieżka
|
||||
config.db_path_helper=(tylko dla "sqlite3")
|
||||
config.db_path_helper=(dla "sqlite3" i "tidb")
|
||||
config.service_config=Konfiguracja usługi
|
||||
config.register_email_confirm=Wymagaj potwierdzenia e-mail
|
||||
config.disable_register=Wyłącz rejestrację
|
||||
config.show_registration_button=Pokazuj przycisk rejestracji
|
||||
config.require_sign_in_view=Wymagaj bycia zalogowanym
|
||||
config.mail_notify=Powiadomienia e-mail
|
||||
config.enable_cache_avatar=Włącz cache awatarów
|
||||
config.mail_notify=Powiadomienia e-mail
|
||||
config.disable_key_size_check=Wyłącz sprawdzanie minimalnego rozmiaru klucza
|
||||
config.enable_captcha=Włącz Captcha
|
||||
config.active_code_lives=Ważność kodów aktywacyjnych
|
||||
config.reset_password_code_lives=Czas życia kodu resetowania hasła
|
||||
config.webhook_config=Konfiguracja skryptów internetowych
|
||||
@@ -919,12 +956,12 @@ notices.delete_success=Powiadomienia systemowe zostały usunięte pomyślnie.
|
||||
|
||||
[action]
|
||||
create_repo=utworzono repozytorium <a href="%s"> %s</a>
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
rename_repo=nazwa repozytorium zmieniona z <code>%[1]s</code> na <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=wypchnął do <a href="%s/src/%s">%[2]s</a> w <a href="%[1]s"> %[3]s</a>
|
||||
create_issue=`zgłosił problem <a href="%s/issues/%s">#%[2]s %[3]s</a>`
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
create_pull_request="stworzył pull request <a href="%s/pulls/%s"> %s #%[2]s"</a>
|
||||
comment_issue=`skomentował problem <a href="%s/issues/%s">#%[2]s %[3]s</a>`
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=scalił pull request <a href="%s/pulls/%s"> %s #%[2]s"</a>
|
||||
transfer_repo=przeniósł repozytorium <code>%s</code> do <a href="%s">%s</a>
|
||||
push_tag=opublikował tag <a href="%s/src/%s">%[2]s</a> w <a href="%[1]s">%[3]s</a>
|
||||
compare_2_commits=Zobacz porównanie tych 2 commitów
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=Painel de controle
|
||||
explore=Explorar
|
||||
help=Ajuda
|
||||
sign_in=Entrar
|
||||
social_sign_in=Entrada Social: 2ª etapa <small>associar uma conta</small>
|
||||
sign_out=Sair
|
||||
sign_up=Cadastrar
|
||||
register=Registrar
|
||||
@@ -54,7 +53,8 @@ code=Código
|
||||
[install]
|
||||
install=Instalação
|
||||
title=Etapas de instalação para Primeira Execução
|
||||
requite_db_desc=Gogs requer MySQL, PostgreSQL ou SQLite3.
|
||||
docker_helper=Se você está rodando o Gogs dentro do Docker, por favor leia os <a target="_blank" href="%s">Guias</a> cuidadosamente antes de mudar qualquer coisa nesta página!
|
||||
requite_db_desc=Gogs requer MySQL, PostgreSQL, SQLite3 ou TiDB.
|
||||
db_title=Configurações de Banco de Dados
|
||||
db_type=Tipo do Banco de Dados
|
||||
host=Host
|
||||
@@ -64,8 +64,11 @@ db_name=Nome do Banco de Dados
|
||||
db_helper=Por favor, use o mecanismo INNODB com o conjunto de caracteres utf8_general_ci para MySQL.
|
||||
ssl_mode=Modo SSL
|
||||
path=Caminho
|
||||
sqlite_helper=O caminho do arquivo do banco de dados do SQLite3.
|
||||
err_empty_sqlite_path=O caminho do arquivo de banco de dados SQLite3 não pode estar em branco.
|
||||
sqlite_helper=O caminho do arquivo do banco de dados SQLite3 ou TiDB.
|
||||
err_empty_db_path=O Caminho do banco de dados SQLite3 ou TiDB não pode ser vazio.
|
||||
err_invalid_tidb_name=Nome do banco de dados TiDB não permite os caracteres "." e "-".
|
||||
no_admin_and_disable_registration=Você não pode desabilitar o registro sem criar uma conta de administrador.
|
||||
err_empty_admin_password=A senha de administrador não pode ser vazia.
|
||||
|
||||
general_title=Configurações Gerais do Gogs
|
||||
app_name=Nome do Aplicativo
|
||||
@@ -96,17 +99,19 @@ server_service_title=Configurações de Servidor e Outros Serviços
|
||||
offline_mode=Ativar Modo Offline
|
||||
offline_mode_popup=Desative o CDN mesmo em modo de produção, todos os recursos serão disponibilizados localmente.
|
||||
disable_gravatar=Desativar Serviço Gravatar
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_gravatar_popup=Desabilitar o Gravatar e fontes personalizadas, todos os avatares são enviados por usuários ou padrão.
|
||||
disable_registration=Desativar auto-registro
|
||||
disable_registration_popup=Desativar o auto-registro de usuário, para que somente o administrador possa criar contas.
|
||||
require_sign_in_view=Requerer autenticação para a visualização de páginas
|
||||
enable_captcha=Habilitar Captcha
|
||||
enable_captcha_popup=Obrigar validação por captcha para auto-registro de usuários.
|
||||
require_sign_in_view=Requerer login para a visualização de páginas
|
||||
require_sign_in_view_popup=Somente usuários autenticados podem ver todas as páginas, visitantes somente podem entrar ou se cadastrar.
|
||||
admin_setting_desc=Você não precisa criar uma conta de administrador agora, no entanto o primeiro usuário (ID=1) automaticamente terá acesso de administrador.
|
||||
admin_title=Configurações da Conta de Administrador
|
||||
admin_name=Nome de Usuário
|
||||
admin_password=Senha
|
||||
confirm_password=Confirmar Senha
|
||||
admin_email=E-mail
|
||||
admin_email=E-mail do Administrador
|
||||
install_gogs=Instalar Gogs
|
||||
test_git_failed=Falha ao testar o comando 'git': %v
|
||||
sqlite3_not_available=Sua versão não suporta SQLite3, por favor faça o download da versão binária oficial em %s, NÃO da versão gobuild.
|
||||
@@ -125,9 +130,9 @@ my_repos=Meus Repositórios
|
||||
collaborative_repos=Repositórios Colaborativos
|
||||
my_orgs=Minhas Organizações
|
||||
my_mirrors=Meus Espelhos
|
||||
view_home=View %s
|
||||
view_home=Ver %s
|
||||
|
||||
issues.in_your_repos=In your repositories
|
||||
issues.in_your_repos=Em seus repositórios
|
||||
|
||||
[explore]
|
||||
repos=Repositórios
|
||||
@@ -137,13 +142,12 @@ create_new_account=Criar Nova Conta
|
||||
register_hepler_msg=Já tem uma conta? Entre agora!
|
||||
social_register_hepler_msg=Já tem uma conta? Junte-se agora!
|
||||
disable_register_prompt=Desculpe, novos registros estão desabilitados. Por favor entre em contato com o administrador do site.
|
||||
disable_register_mail=Desculpe, a confirmação de registro por email foi desabilitada.
|
||||
disable_register_mail=Desculpe, a confirmação de registro por e-mail foi desabilitada.
|
||||
remember_me=Lembrar de Mim
|
||||
forgot_password=Esqueci a Senha
|
||||
forget_password=Esqueceu a senha?
|
||||
sign_up_now=Precisa de uma conta? Cadastre-se agora.
|
||||
confirmation_mail_sent_prompt=Um novo e-mail de confirmação foi enviado para <b>%s</b>, por favor, verifique sua caixa de entrada nas próximas %d horas para completar seu registro.
|
||||
sign_in_email=Entre com seu e-mail
|
||||
active_your_account=Ativar Sua Conta
|
||||
resent_limit_prompt=Desculpe, você está enviando um e-mail de ativação com muita frequência. Por favor, aguarde 3 minutos.
|
||||
has_unconfirmed_mail=Oi %s, você possui um endereço de e-mail não confirmado (<b>%s</b>). Se você não recebeu um e-mail de confirmação ou precisa reenviar um novo, clique no botão abaixo.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Desculpe, seu código de confirmação expirou ou não é válido.
|
||||
reset_password_helper=Clique aqui para redefinir sua senha
|
||||
password_too_short=O comprimento da senha não pode ser menor que 6.
|
||||
|
||||
[mail]
|
||||
activate_account=Por favor, ative sua conta
|
||||
activate_email=Verifique seu endereço de e-mail
|
||||
reset_password=Resetar sua senha
|
||||
register_success=Registrado com sucesso. Bem vindo
|
||||
|
||||
[modal]
|
||||
yes=Sim
|
||||
no=Não
|
||||
@@ -176,26 +186,27 @@ AdminEmail=E-mail do Administrador
|
||||
require_error=` não pode estar vazio.`
|
||||
alpha_dash_error=` devem ser caracteres alfanuméricos ou hífen (-) ou sublinhado (_).`
|
||||
alpha_dash_dot_error=` devem ser caracteres alfanuméricos ou hífen (-) ou sublinhado (_).`
|
||||
size_error=` deve ter %s.`
|
||||
size_error='deve ser o tamanho %s.'
|
||||
min_size_error=` deve conter pelo menos %s caracteres.`
|
||||
max_size_error=` deve conter no máximo %s caracteres.`
|
||||
email_error=` não é um endereço de e-mail válido.`
|
||||
url_error=`não é uma URL válida.`
|
||||
include_error=` deve conter '%s'.`
|
||||
unknown_error=Erro desconhecido:
|
||||
captcha_incorrect=O captcha não correspondeu.
|
||||
password_not_match=Senha e confirmar senha não são as mesmas.
|
||||
password_not_match=Senha e confirmação da senha não são as mesmas.
|
||||
|
||||
username_been_taken=Nome de usuário já foi tomado.
|
||||
repo_name_been_taken=Nome do repositório já foi tomado.
|
||||
org_name_been_taken=Nome da organização já foi tomado.
|
||||
team_name_been_taken=Nome da equipe já foi tomado.
|
||||
email_been_used=Endereço de e-mail já foi usado.
|
||||
illegal_team_name=O nome da equipe contém caracteres ilegais.
|
||||
illegal_team_name=O nome da equipe contém caracteres não permitidos.
|
||||
username_password_incorrect=Usuário ou senha incorretos.
|
||||
enterred_invalid_repo_name=Por favor certifique-se que informou o nome do repositório corretamente.
|
||||
enterred_invalid_owner_name=Por favor, verifique se o nome do proprietário está correto.
|
||||
enterred_invalid_password=Por favor, verifique se a senha que você digitou está correta.
|
||||
user_not_exist=O usuário dado não existe.
|
||||
user_not_exist=O usuário informado não existe.
|
||||
last_org_owner=O usuário a ser removido é o último membro na equipe de proprietários. Deve haver um outro proprietário.
|
||||
|
||||
invalid_ssh_key=Desculpe, não conseguimos verificar a sua chave SSH: %s
|
||||
@@ -208,7 +219,7 @@ org_still_own_repo=Esta organização ainda tem a propriedade do repositório, v
|
||||
|
||||
still_own_user=Esta autenticação ainda é usada por alguns usuários, você deve movê-los e depois apagar novamente.
|
||||
|
||||
target_branch_not_exist=O ramo de destino não existe.
|
||||
target_branch_not_exist=O branch de destino não existe.
|
||||
|
||||
[user]
|
||||
change_avatar=Altere o seu avatar em gravatar.com
|
||||
@@ -217,7 +228,7 @@ join_on=Inscreveu-se em
|
||||
repositories=Repositórios
|
||||
activity=Atividade Pública
|
||||
followers=Seguidores
|
||||
starred=Marcado
|
||||
starred=Favorito
|
||||
following=Seguindo
|
||||
|
||||
form.name_reserved=O nome de usuário '%s' não pode ser usado.
|
||||
@@ -241,7 +252,7 @@ location=Localização
|
||||
update_profile=Atualizar o Perfil
|
||||
update_profile_success=O seu perfil foi atualizado com sucesso.
|
||||
change_username=Nome de Usuário Alterado
|
||||
change_username_desc=O nome de usuário foi alterado, você quer continuar? Isto afetará todos os links relacionados à sua conta.
|
||||
change_username_prompt=Essa alteração afetará os links para a sua conta.
|
||||
continue=Continuar
|
||||
cancel=Cancelar
|
||||
|
||||
@@ -256,23 +267,27 @@ update_avatar_success=Sua configuração de avatar foi atualizada com sucesso.
|
||||
change_password=Mudança de senha
|
||||
old_password=Senha Atual
|
||||
new_password=Nova Senha
|
||||
retype_new_password=Digite novamente a nova senha
|
||||
password_incorrect=A senha atual não está correta.
|
||||
change_password_success=A senha está alterada com sucesso. Você pode agora entrar com a senha nova.
|
||||
|
||||
emails=Endereços de E-mail
|
||||
manage_emails=Gerenciar endereços de e-mail
|
||||
email_desc=Seu endereço de email principal será usado para notificações e outras operações.
|
||||
email_desc=Seu endereço de e-mail principal será usado para notificações e outras operações.
|
||||
primary=Principal
|
||||
primary_email=Definir como principal
|
||||
delete_email=Deletar
|
||||
email_deletion=Exclusão do email
|
||||
email_deletion_desc=Ao Excluir este endereço de e-mail será removido informações relacionadas com a sua conta. Você deseja continuar?
|
||||
email_deletion_success=O E-mail foi excluído com sucesso!
|
||||
add_new_email=Adicionar novo endereço de e-mail
|
||||
add_email=Adicionar e-mail
|
||||
add_email_confirmation_sent=Um novo e-mail de confirmação foi enviado para <b>%s</b>. Por favor, verifique sua Caixa de Entrada dentro das próximas %d horas, para concluir o processo de confirmação.
|
||||
add_email_confirmation_sent=Um novo e-mail de confirmação foi enviado para '%s'. Por favor, verifique sua Caixa de Entrada dentro das próximas %d horas, para concluir o processo de confirmação.
|
||||
add_email_success=Seu novo endereço de E-mail foi adicionado com sucesso.
|
||||
|
||||
manage_ssh_keys=Gerenciar Chaves SSH
|
||||
add_key=Adicionar chave
|
||||
ssh_desc=Esta é uma lista de chaves SSH associadas com a sua conta. Remova quaisquer chaves que você não reconheça.
|
||||
ssh_desc=Esta é uma lista de chaves SSH associadas com a sua conta. Como essas chaves permitem que qualquer um que as usem tenham acesso aos seus repositórios, é altamente importante que você reconheça elas.
|
||||
ssh_helper=<strong>Precisa de ajuda?</strong> Confira nosso guia para <a href="%s">gerar chaves SSH</a> ou solucionar <a href="%s">problemas comuns com SSH</a>.
|
||||
add_new_key=Adicionar Chave SSH
|
||||
ssh_key_been_used=Uma chave pública com esse mesmo conteúdo já está em uso.
|
||||
@@ -281,14 +296,14 @@ key_name=Nome da Chave
|
||||
key_content=Conteúdo
|
||||
add_key_success=A nova chave pública '%s' foi adicionada com sucesso!
|
||||
delete_key=Deletar
|
||||
ssh_key_deletion=SSH Key Deletion
|
||||
ssh_key_deletion_desc=Delete this SSH key will remove all related accesses for your account. Do you want to continue?
|
||||
ssh_key_deletion_success=SSH key has been deleted successfully!
|
||||
ssh_key_deletion=Exclusão da chave de SSH
|
||||
ssh_key_deletion_desc=Ao Excluir esta chave de SSH será removido todos os acessos para sua conta. Você deseja continuar?
|
||||
ssh_key_deletion_success=A chave de SSH foi excluída com sucesso!
|
||||
add_on=Adicionado em
|
||||
last_used=Última vez usado em
|
||||
no_activity=Nenhuma atividade recente
|
||||
key_state_desc=Usada a pelo menos 7 dias
|
||||
token_state_desc=This token is used in last 7 days
|
||||
token_state_desc=Este token é usado em pelo menos 7 dias
|
||||
|
||||
manage_social=Gerenciar Contas Sociais Associadas
|
||||
social_desc=Esta é uma lista de contas sociais. Remova qualquer ligação que você não reconheça.
|
||||
@@ -297,15 +312,15 @@ unbind_success=A conta social foi desvinculada.
|
||||
|
||||
manage_access_token=Gerenciar Tokens de Acesso Pessoal
|
||||
generate_new_token=Gerar novo Token
|
||||
tokens_desc=Tokens you have generated that can be used to access the Gogs APIs.
|
||||
tokens_desc=Tokens gerados por você que podem ser usados para acessar a API do Gogs.
|
||||
new_token_desc=Por enquanto, todo token terá acesso completo à sua conta.
|
||||
token_name=Nome do Token
|
||||
generate_token=Gerar Token
|
||||
generate_token_succees=Novo token de acesso gerado com sucesso! Certifique-se de copiar seu novo token de acesso pessoal agora. Você não poderá vê-lo novamente!
|
||||
delete_token=Excluir
|
||||
access_token_deletion=Personal Access Token Deletion
|
||||
access_token_deletion_desc=Delete this personal access token will remove all related accesses of application. Do you want to continue?
|
||||
delete_token_success=Personal access token has been removed successfully! Don't forget to update your application as well.
|
||||
access_token_deletion=Exclusão do Token de acesso pessoal
|
||||
access_token_deletion_desc=Ao Excluir este token de acesso pessoal será removido todos os acessos do aplicativo. Você deseja continuar?
|
||||
delete_token_success=O Token de acesso pessoal foi removido com sucesso! Não se esqueça de atualizar seus aplicativos também.
|
||||
|
||||
delete_account=Deletar Sua Conta
|
||||
delete_prompt=A operação deletará sua conta permanentemente, e <strong>NÃO PODERÁ</strong> ser desfeita!
|
||||
@@ -316,23 +331,24 @@ delete_account_desc=Esta conta será deletada permanentemente, você quer contin
|
||||
[repo]
|
||||
owner=Dono
|
||||
repo_name=Nome do Repositório
|
||||
repo_name_helper=Nomes de repositórios bons são pequenos, memorizáveis e <strong>únicos</strong>.
|
||||
repo_name_helper=Nomes de repositórios bons são pequenos, memorizáveis e únicos.
|
||||
visibility=Visibilidade
|
||||
visiblity_helper=Este é um repositório <span class="ui red text"> privado</span>
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
visiblity_helper_forced=O adminstrador forçou todos os novos repositórios para serem <span class="ui red text">Privados</span>
|
||||
visiblity_fork_helper=(A alteração desse valor irá afetar todos os forks)
|
||||
fork_repo=Fork o Repositório
|
||||
fork_from=Fork de
|
||||
fork_visiblity_helper=Não é possível alterar a visibilidade de um repositório bifurcado
|
||||
fork_visiblity_helper=Não é possível alterar a visibilidade de um repositório forkado.
|
||||
repo_desc=Descrição
|
||||
repo_lang=Idioma
|
||||
repo_lang=Linguagem
|
||||
repo_lang_helper=Selecione arquivos .gitignore
|
||||
license=Licença
|
||||
license_helper=Selecione um arquivo de licença
|
||||
readme=Leia-me
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
readme_helper=Selecione um modelo de leia-me
|
||||
auto_init=Inicializar este repositório com os arquivos selecionados e modelo
|
||||
create_repo=Criar Repositório
|
||||
default_branch=Ramo padrão
|
||||
default_branch=Branch padrão
|
||||
mirror_interval=Intervalo de Espelho (hora)
|
||||
|
||||
form.name_reserved=O nome de repositório '%s' não pode ser usado.
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=Este repositório será um <span class="text blue"> espelho<
|
||||
migrate_repo=Migrar Repositório
|
||||
migrate.clone_address=Endereço de Clone
|
||||
migrate.clone_address_desc=Isto pode ser uma URL de HTTP/HTTPS/GIT ou um caminho de diretório local.
|
||||
migrate.permission_denied=Você não pode importar repositórios locais.
|
||||
migrate.invalid_local_path=Caminho local inválido, não existe ou não é um diretório.
|
||||
|
||||
forked_from=bifurcação de
|
||||
forked_from=forkado de
|
||||
fork_from_self=Você não pode criar fork de um repositório que já é seu!
|
||||
copy_link=Copiar
|
||||
copy_link_success=Copiado!
|
||||
copy_link_error=Pressione ⌘-C ou Ctrl-C para copiar
|
||||
click_to_copy=Copiar para a área de transferência
|
||||
copied=Copiado com sucesso
|
||||
clone_helper=Precisa de ajuda com a clonagem? Visite a <a target="_blank" href="%s">Ajuda</a>!
|
||||
@@ -363,18 +382,20 @@ quick_guide=Guia Rápido
|
||||
clone_this_repo=Clonar este repositório
|
||||
create_new_repo_command=Criar um novo repositório na linha de comando
|
||||
push_exist_repo=Push um repositório existente na linha de comando
|
||||
repo_is_empty=Este repositório está vazio, por favor volte mais tarde!
|
||||
|
||||
branch=Ramo
|
||||
|
||||
branch=Branch
|
||||
tree=Árvore
|
||||
branch_and_tags=Ramos & Tags
|
||||
branches=Ramos
|
||||
branch_and_tags=Branches & Tags
|
||||
branches=Branches
|
||||
tags=Tags
|
||||
issues=Problemas
|
||||
pulls=Pull Requests
|
||||
labels=Etiquetas
|
||||
milestones=Marcos
|
||||
commits=Commits
|
||||
releases=Lançamentos
|
||||
releases=Versões
|
||||
file_raw=Cru
|
||||
file_history=Histórico
|
||||
file_view_raw=Ver cru
|
||||
@@ -393,7 +414,7 @@ issues.new=Novo problema
|
||||
issues.new.labels=Etiquetas
|
||||
issues.new.no_label=Sem etiqueta
|
||||
issues.new.clear_labels=Limpar
|
||||
issues.new.milestone=Milestone
|
||||
issues.new.milestone=Marco
|
||||
issues.new.no_milestone=Sem marco
|
||||
issues.new.clear_milestone=Limpar
|
||||
issues.new.open_milestone=Marcos abertos
|
||||
@@ -403,7 +424,7 @@ issues.new.clear_assignee=Limpar
|
||||
issues.new.no_assignee=Não atribuída
|
||||
issues.create=Salvar
|
||||
issues.new_label=Nova etiqueta
|
||||
issues.new_label_placeholder=Nome de etiqueta...
|
||||
issues.new_label_placeholder=Nome da etiqueta...
|
||||
issues.create_label=Salvar
|
||||
issues.open_tab=%d aberto
|
||||
issues.close_tab=%d fechados
|
||||
@@ -416,7 +437,7 @@ issues.filter_assginee_no_select=Sem atribuição
|
||||
issues.filter_type=Tipo
|
||||
issues.filter_type.all_issues=Todos os problemas
|
||||
issues.filter_type.assigned_to_you=Atribuídos a você
|
||||
issues.filter_type.created_by_you=Criados por você
|
||||
issues.filter_type.created_by_you=Criado por você
|
||||
issues.filter_type.mentioning_you=Mencionando você
|
||||
issues.filter_sort=Ordenação
|
||||
issues.filter_sort.latest=Mais novos
|
||||
@@ -425,7 +446,7 @@ issues.filter_sort.recentupdate=Mais recentemente atualizados
|
||||
issues.filter_sort.leastupdate=Menos recentemente atualizados
|
||||
issues.filter_sort.mostcomment=Mais comentados
|
||||
issues.filter_sort.leastcomment=Menos comentados
|
||||
issues.opened_by=opened %[1]s by <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by=%[1]s foi aberto por <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=aberto %[1]s por %[2]s
|
||||
issues.previous=Página anterior
|
||||
issues.next=Próxima página
|
||||
@@ -437,12 +458,12 @@ issues.no_content=Nenhum conteúdo textual.
|
||||
issues.close_issue=Fechar
|
||||
issues.close_comment_issue=Comentar e fechar
|
||||
issues.reopen_issue=Reabrir
|
||||
issues.reopen_comment_issue=Reabrir e comentar
|
||||
issues.reopen_comment_issue=Comentar e reabrir
|
||||
issues.create_comment=Comentar
|
||||
issues.closed_at=`fechado em <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reaberto em <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Imagem
|
||||
issues.commit_ref_at=`citou este problema em um commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Autor
|
||||
issues.admin=Administrador
|
||||
issues.owner=Proprietário
|
||||
issues.sign_up_for_free=Cadastre-se gratuitamente
|
||||
@@ -462,27 +483,29 @@ issues.label_deletion_desc=Excluir uma etiqueta a retirará de todos os problema
|
||||
issues.label_deletion_success=A etiqueta foi excluída com sucesso!
|
||||
|
||||
pulls.compare_changes=Comparar mudanças
|
||||
pulls.compare_changes_desc=Comparar dois ramos e criar solicitação de pull com as mudanças.
|
||||
pulls.compare_changes_desc=Comparar os dois branches e criar pull request com as mudanças.
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=comparar
|
||||
pulls.filter_branch=Filter branch
|
||||
pulls.filter_branch=Filtrar branch
|
||||
pulls.no_results=Nada encontrado.
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Create Pull Request
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.nothing_to_compare=Não há nada para comparar porque o branch base e o head estão iguais.
|
||||
pulls.has_pull_request=`Já existem pull requests entre esses dois alvos: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Criar Pull Request
|
||||
pulls.title_desc=quer mesclar %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code>
|
||||
pulls.merged_title_desc=mesclou %[1]d commits de <code>%[2]s</code> em <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversação
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Files changed
|
||||
pulls.reopen_to_merge=Please reopen this pull request to perform merge operation.
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.tab_files=Arquivos alterados
|
||||
pulls.reopen_to_merge=Por favor reabra esse pull request para executar a operação de mescla.
|
||||
pulls.merged=Merge realizado
|
||||
pulls.has_merged=Este pull request foi mesclado com sucesso!
|
||||
pulls.data_broken=Dados deste pull request foram quebrados devido à deleção de informação do fork.
|
||||
pulls.is_checking=A verificação do conflito ainda está em progresso, por favor recarregue a página em instantes.
|
||||
pulls.can_auto_merge_desc=Você pode realizar uma auto-mescla neste pull request.
|
||||
pulls.cannot_auto_merge_desc=Você não pode realizar uma auto-mescla porque há conflitos entre os commits.
|
||||
pulls.cannot_auto_merge_helper=Por favor, utilize linha de comando para solucionar isto.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.open_unmerged_pull_exists=' Você não pode executar a operação de reabrir porque já existe uma solicitação de pull aberta (#%d) do mesmo repositório com as mesmas informações de merge e está esperando pelo merge.'
|
||||
|
||||
milestones.new=Novo marco
|
||||
milestones.open_tab=%d abertos
|
||||
@@ -511,26 +534,26 @@ milestones.deletion_success=Marco excluído com sucesso!
|
||||
settings=Configurações
|
||||
settings.options=Opções
|
||||
settings.collaboration=Colaboração
|
||||
settings.hooks=Hooks da web
|
||||
settings.hooks=Webhooks
|
||||
settings.githooks=Hooks do Git
|
||||
settings.basic_settings=Configurações Básicas
|
||||
settings.danger_zone=Zona de Perigo
|
||||
settings.site=Site Oficial
|
||||
settings.update_settings=Configurações de Atualização
|
||||
settings.change_reponame_prompt=This change will affect how links relate to the repository.
|
||||
settings.change_reponame_prompt=Este mudanças vai afetar os links para este repositório.
|
||||
settings.transfer=Transferir Propriedade
|
||||
settings.transfer_desc=Transferir este repositório para outro usuário ou para uma organização onde você tem direitos de administrador.
|
||||
settings.new_owner_has_same_repo=O novo dono já tem um repositório com o mesmo nome.
|
||||
settings.new_owner_has_same_repo=O novo dono já tem um repositório com o mesmo nome. Por favor, escolha outro nome.
|
||||
settings.delete=Deletar Este Repositório
|
||||
settings.delete_desc=Uma vez que você deleta um repositório, não tem volta. Por favor, tenha certeza.
|
||||
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
|
||||
settings.transfer_notices_2=- You will conserve access if new owner is an organization and if you're one of the owners.
|
||||
settings.transfer_form_title=Please enter following information to confirm your operation:
|
||||
settings.delete_notices_1=- This operation <strong>CANNOT</strong> be undone.
|
||||
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
|
||||
settings.delete_notices_fork_1=- If this repository is public, all forks will be became independent after deletion.
|
||||
settings.delete_notices_fork_2=- If this repository is private, all forks will be removed at the same time.
|
||||
settings.delete_notices_fork_3=- If you want to keep all forks after deletion, please change visibility of this repository to public first.
|
||||
settings.transfer_notices_1=- Você vai perder acesso se o novo dono for um usuário individual.
|
||||
settings.transfer_notices_2=- Você vai continuar tendo acesso se o novo dono é uma organização e você é um dos membros.
|
||||
settings.transfer_form_title=Informe a seguinte informação para confirmar a sua operação:
|
||||
settings.delete_notices_1=-Esta operação <strong>NÃO PODERÁ</strong> ser desfeita.
|
||||
settings.delete_notices_2=- Esta operação irá apagar permanentemente o tudo deste repositório, incluindo os dados do Git, problemas, comentários e acessos dos colaboradores.
|
||||
settings.delete_notices_fork_1=- Se este repositório é público, todos os forks se tornarão independentes após a deleção.
|
||||
settings.delete_notices_fork_2=- Se este repositório é privado, todos os forks serão removidos imediatamente.
|
||||
settings.delete_notices_fork_3=- Se você deseja manter todos os forks, por favor muda a visibilidade do repositório para pública primeiro.
|
||||
settings.update_settings_success=As opções do repositório foram atualizadas com sucesso.
|
||||
settings.transfer_owner=Novo Dono
|
||||
settings.make_transfer=Fazer Transferência
|
||||
@@ -542,10 +565,10 @@ settings.remove_collaborator_success=O colaborador foi removido.
|
||||
settings.user_is_org_member=O usuário é um membro da organização que não pode ser adicionado como um colaborador.
|
||||
settings.add_webhook=Adicionar Webhook
|
||||
settings.hooks_desc=Hooks da web ou Webhooks permitem serviços externos serem notificados quando certos eventos acontecem no Gogs. Quando acontecem os eventos especificados, enviaremos uma solicitação POST para cada uma das URLs que você fornecer. Saiba mais no nosso <a target="_blank" href="%s"> Guia de Webhooks</a>.
|
||||
settings.webhook_deletion=Delete Webhook
|
||||
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
|
||||
settings.webhook_deletion_success=Webhook has been deleted successfully!
|
||||
settings.webhook.request=Request
|
||||
settings.webhook_deletion=Deletar Webhook
|
||||
settings.webhook_deletion_desc=Deletar este Webhook vai remover sua informação e todo o histórico de entrega. Deseja continuar?
|
||||
settings.webhook_deletion_success=Webhook deletado com sucesso!
|
||||
settings.webhook.request=Solicitação
|
||||
settings.webhook.response=Resposta
|
||||
settings.webhook.headers=Cabeçalhos
|
||||
settings.webhook.payload=Payload
|
||||
@@ -559,23 +582,23 @@ settings.add_webhook_desc=Enviaremos uma solicitação <code>POST</code> para o
|
||||
settings.payload_url=URL de carga
|
||||
settings.content_type=Tipo de Conteúdo
|
||||
settings.secret=Secreto
|
||||
settings.slack_username=Username
|
||||
settings.slack_icon_url=Icon URL
|
||||
settings.slack_username=Nome de usuário
|
||||
settings.slack_icon_url=URL do ícone
|
||||
settings.slack_color=Cor
|
||||
settings.event_desc=Quais eventos você gostaria de acionar a esse hook da web?
|
||||
settings.event_desc=Quais eventos você gostaria de acionar a esse webhook?
|
||||
settings.event_push_only=Apenas o evento <code>push</code>.
|
||||
settings.event_send_everything=I need <strong>everything</strong>.
|
||||
settings.event_choose=Let me choose what I need.
|
||||
settings.event_send_everything=Preciso de <strong>tudo</strong>.
|
||||
settings.event_choose=Deixe-me escolher o que eu preciso.
|
||||
settings.event_create=Criar
|
||||
settings.event_create_desc=Branch, or tag created
|
||||
settings.event_create_desc=Branch ou Tag criado
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push to a repository
|
||||
settings.event_push_desc=Git push para o repositório
|
||||
settings.active=Ativar
|
||||
settings.active_helper=Enviaremos detalhes do evento quando este hook for acionado.
|
||||
settings.add_hook_success=Novos hooks de web foram adicionados.
|
||||
settings.update_webhook=Atualizar Hook da Web
|
||||
settings.update_hook_success=Hook da web atualizado.
|
||||
settings.delete_webhook=Excluir Hook da Web
|
||||
settings.update_webhook=Atualizar Webhook
|
||||
settings.update_hook_success=Webhook atualizado.
|
||||
settings.delete_webhook=Excluir Webhook
|
||||
settings.recent_deliveries=Entregas Recentes
|
||||
settings.hook_type=Tipo de Hook
|
||||
settings.add_slack_hook_desc=Adicionar <a href="%s">Slack</a> de integração para o seu repositório.
|
||||
@@ -594,23 +617,23 @@ settings.deploy_key_deletion=Exclusão de chave de deploy
|
||||
settings.deploy_key_deletion_desc=Excluir esta chave de implantação removerá permissões de acesso a este repositório. Quer mesmo continuar?
|
||||
settings.deploy_key_deletion_success=Chave de implantação excluída com sucesso!
|
||||
|
||||
diff.browse_source=Ver Fontes
|
||||
diff.browse_source=Ver Código Fonte
|
||||
diff.parent=pai
|
||||
diff.commit=commit
|
||||
diff.data_not_available=Dados Diff não disponíveis.
|
||||
diff.data_not_available=Dados de Diff não disponíveis.
|
||||
diff.show_diff_stats=Mostrar estatísticas do Diff
|
||||
diff.stats_desc=<strong> %d arquivos alterados</strong> com <strong>%d adições</strong> e <strong>%d exclusões</strong>
|
||||
diff.bin=BIN
|
||||
diff.view_file=Ver Arquivo
|
||||
|
||||
release.releases=Lançamentos
|
||||
release.new_release=Novo Lançamento
|
||||
release.releases=Versões
|
||||
release.new_release=Nova Versão
|
||||
release.draft=Rascunho
|
||||
release.prerelease=Pré-Lançamento
|
||||
release.prerelease=Versão Prévia
|
||||
release.stable=Estável
|
||||
release.edit=editar
|
||||
release.ahead=<strong>%d</strong> commits para %s depois desta versão
|
||||
release.source_code=Código-fonte
|
||||
release.source_code=Código fonte
|
||||
release.tag_name=Nome da tag
|
||||
release.target=Destino
|
||||
release.tag_helper=Escolha uma tag existente, ou crie uma nova tag em publicar.
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Já existiu versão com esse nome de tag.
|
||||
|
||||
[org]
|
||||
org_name_holder=Nome da Organização
|
||||
org_full_name_holder=Nome completo da organização
|
||||
org_name_helper=Nomes de grandes organizações são curtos e memoráveis.
|
||||
org_email_helper=O E-mail da organização receberá todas as notificações e as confirmações.
|
||||
create_org=Criar Organização
|
||||
repo_updated=Atualizado
|
||||
people=Pessoas
|
||||
@@ -655,16 +678,16 @@ settings.full_name=Nome Completo
|
||||
settings.website=Site
|
||||
settings.location=Localização
|
||||
settings.update_settings=Atualizar Configurações
|
||||
settings.change_orgname=Nome da Organização Alterado
|
||||
settings.change_orgname_desc=O nome da organização foi alterado, você quer continuar? Isto afetará todos os links que se relacionam a esta organização.
|
||||
settings.update_setting_success=Configuração da organização atualizada com sucesso.
|
||||
settings.change_orgname_prompt=Esta mudança vai afetar os links para esta organização.
|
||||
settings.update_avatar_success=A configuração de avatar da organização foi atualizado com sucesso.
|
||||
settings.delete=Deletar Organização
|
||||
settings.delete_account=Deletar Esta Organização
|
||||
settings.delete_prompt=A operação deletará esta organização permanentemente, e <strong>NÃO PODERÁ</strong> ser desfeita!
|
||||
settings.confirm_delete_account=Confirmar Deleção
|
||||
settings.delete_org_title=Deleção da Organização
|
||||
settings.delete_org_desc=Esta organização será deletada permanentemente, você quer continuar?
|
||||
settings.hooks_desc=Adicionar Hooks da Web que serão acionados para <strong>todos os repositórios</strong> dessa organização.
|
||||
settings.hooks_desc=Adicionar Webhooks que serão acionados para <strong>todos os repositórios</strong> dessa organização.
|
||||
|
||||
members.public=Público
|
||||
members.public_helper=tornar privado
|
||||
@@ -713,8 +736,9 @@ authentication=Autenticações
|
||||
config=Configuração
|
||||
notices=Sistema de notificações
|
||||
monitor=Monitoramento
|
||||
prev=Anterior
|
||||
next=Próximo
|
||||
first_page=Primeira
|
||||
last_page=Última
|
||||
total=Total: %d
|
||||
|
||||
dashboard.statistic=Estatística
|
||||
dashboard.operations=Operações
|
||||
@@ -771,21 +795,26 @@ users.new_account=Criar Nova Conta
|
||||
users.name=Nome
|
||||
users.activated=Ativado
|
||||
users.admin=Administrador
|
||||
users.repos=Repos
|
||||
users.repos=Repositórios
|
||||
users.created=Criado
|
||||
users.send_register_notify=Enviar notificação de registro para ao usuário
|
||||
users.new_success=Nova conta '%s' foi criada com sucesso.
|
||||
users.edit=Editar
|
||||
users.auth_source=Fonte de Autorização
|
||||
users.auth_source=Fonte da autenticação
|
||||
users.local=Local
|
||||
users.auth_login_name=Nome de Autorização de Login
|
||||
users.auth_login_name=Nome de login da autenticação
|
||||
users.password_helper=Deixe em branco para não mudar.
|
||||
users.update_profile_success=O perfil da conta foi atualizado com sucesso.
|
||||
users.edit_account=Editar Conta
|
||||
users.is_activated=Esta conta está ativada
|
||||
users.is_admin=Esta conta tem permissões de administrador
|
||||
users.allow_git_hook=Esta conta tem permissões para criar ganchos Git
|
||||
users.allow_git_hook=Esta conta tem permissões para criar hooks do Git
|
||||
users.allow_import_local=Esta conta tem permissões para importar repositórios locais
|
||||
users.update_profile=Atualizar Perfil da Conta
|
||||
users.delete_account=Deletar Esta Conta
|
||||
users.still_own_repo=Sua conta ainda é proprietária do repositório, você tem que excluir ou transferi-lo primeiro.
|
||||
users.still_has_org=Sua conta ainda faz parte da organização, você deve sair ou excluí-la primeiro.
|
||||
users.deletion_success=Conta deletada com sucesso!
|
||||
|
||||
orgs.org_manage_panel=Painel de Gerenciamento da Organização
|
||||
orgs.name=Nome
|
||||
@@ -797,44 +826,50 @@ repos.owner=Dono
|
||||
repos.name=Nome
|
||||
repos.private=Privado
|
||||
repos.watches=Observadores
|
||||
repos.stars=Estrelas
|
||||
repos.stars=Favoritos
|
||||
repos.issues=Problemas
|
||||
|
||||
auths.auth_manage_panel=Painel de Gerenciamento da Autorização
|
||||
auths.new=Adicionar Nova Fonte de Autorização
|
||||
auths.auth_manage_panel=Painel de gerenciamento da autenticação
|
||||
auths.new=Adicionar nova fonte
|
||||
auths.name=Nome
|
||||
auths.type=Tipo
|
||||
auths.enabled=Habilitado
|
||||
auths.updated=Atualizado
|
||||
auths.auth_type=Tipo da Autorização
|
||||
auths.auth_name=Nome da Autorização
|
||||
auths.auth_type=Tipo de autenticação
|
||||
auths.auth_name=Nome da autenticação
|
||||
auths.domain=Domínio
|
||||
auths.host=Host
|
||||
auths.port=Porta
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.user_base=User Search Base
|
||||
auths.bind_dn=Vincular DN
|
||||
auths.bind_password=Vincular senha
|
||||
auths.bind_password_helper=Atenção: Esta senha é armazenada em texto plano. Não use uma conta com muitos privilégios.
|
||||
auths.user_base=Base de pesquisa do usuário
|
||||
auths.user_dn=Usuário do DN
|
||||
auths.attribute_name=Atributo primeiro nome
|
||||
auths.attribute_surname=Atributo sobrenome
|
||||
auths.attribute_mail=Atributo e-mail
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.filter=Filtro de usuário
|
||||
auths.admin_filter=Filtro de administrador
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Tipo de Autorização de SMTP
|
||||
auths.smtp_auth=Tipo de autenticação SMTP
|
||||
auths.smtphost=Host SMTP
|
||||
auths.smtpport=Porta SMTP
|
||||
auths.allowed_domains=Domínios autorizados
|
||||
auths.allowed_domains_helper=Deixe em branco para permitir qualquer domínio do host SMTP. Vários domínios devem ser separados por vírgula ','.
|
||||
auths.enable_tls=Habilitar Criptografia TLS
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.skip_tls_verify=Ignorar verificação de TLS
|
||||
auths.pam_service_name=Nome de Serviço PAM
|
||||
auths.enable_auto_register=Habilitar Registro Automático
|
||||
auths.tips=Dicas
|
||||
auths.edit=Editar Configuração da Autorização
|
||||
auths.edit=Editar a configuração de autenticação
|
||||
auths.activated=Esta autenticação foi ativada
|
||||
auths.update_success=A configuração da autorização foi atualizada com sucesso.
|
||||
auths.update=Atualizar Configuração da Autorização
|
||||
auths.delete=Excluir Esta Autorização
|
||||
auths.delete_auth_title=Exclusão da Autorização
|
||||
auths.delete_auth_desc=Esta autorização será excluída, deseja continuar?
|
||||
auths.new_success=Nova autenticação '%s' foi adicionada com sucesso.
|
||||
auths.update_success=A configuração da autenticação foi atualizada com sucesso.
|
||||
auths.update=Atualizar a configuração da autenticação
|
||||
auths.delete=Excluir esta autenticação
|
||||
auths.delete_auth_title=Exclusão da autenticação
|
||||
auths.delete_auth_desc=Esta autenticação esta prestes a ser deletada, deseja continuar?
|
||||
auths.deletion_success=Autenticação deletada com sucesso!
|
||||
|
||||
config.server_config=Configuração do Servidor
|
||||
config.app_name=Nome do Aplicativo
|
||||
@@ -858,14 +893,16 @@ config.db_user=Usuário
|
||||
config.db_ssl_mode=Modo SSL
|
||||
config.db_ssl_mode_helper=(apenas para "postgres")
|
||||
config.db_path=Caminho
|
||||
config.db_path_helper=(apenas para "sqlite3")
|
||||
config.db_path_helper=(para "sqlite3" e "tidb")
|
||||
config.service_config=Configuração do Serviço
|
||||
config.register_email_confirm=Requerer Confirmação de E-mail
|
||||
config.disable_register=Desabilitar Registro
|
||||
config.show_registration_button=Mostrar Botão de Registo
|
||||
config.require_sign_in_view=Requerer Entrar no Gogs para Ver
|
||||
config.mail_notify=Notificação de Correio
|
||||
config.enable_cache_avatar=Habilitar Cache de Avatar
|
||||
config.mail_notify=Notificação de Correio
|
||||
config.disable_key_size_check=Desativar verificação de tamanho mínimo da chave
|
||||
config.enable_captcha=Habilitar o Captcha
|
||||
config.active_code_lives=Ativar Code Lives
|
||||
config.reset_password_code_lives=Redefinir Senha de Code Lives
|
||||
config.webhook_config=Configuração de Hook da Web
|
||||
@@ -919,12 +956,12 @@ notices.delete_success=Aviso do sistema foi deletado com sucesso.
|
||||
|
||||
[action]
|
||||
create_repo=repositório criado <a href="%s"> %s</a>
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
rename_repo=renomeou o o repositório <code>%[1]s</code> para <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=pushed para <a href="%s/src/%s">%[2]s</a> em <a href="%[1]s">%[3]s</a>
|
||||
create_issue='questão aberta <a href="%s/issues/%s">%s#%[2]s</a>'
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`criou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue='comentou sobre a questão <a href="%s/issues/%s">%s#%[2]s</a>'
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`mesclou o pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=repositório transferido de <code>%s</code> para <a href="%s">%s</a>
|
||||
push_tag=Foi feito push na tag <a href="%s/src/%s">%[2]s</a> para <a href="%[1]s">%[3]s</a>
|
||||
compare_2_commits=Ver comparação desses 2 commits
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=Панель мониторинга
|
||||
explore=Обзор
|
||||
help=Помощь
|
||||
sign_in=Войти
|
||||
social_sign_in=Вход через соцсеть: 2-й шаг <small>свяжите учетную запись</small>
|
||||
sign_out=Выход
|
||||
sign_up=Регистрация
|
||||
register=Зарегистрироваться
|
||||
@@ -14,7 +13,7 @@ version=Версия
|
||||
page=Страница
|
||||
template=Шаблон
|
||||
language=Язык
|
||||
create_new=Создать новый...
|
||||
create_new=Создать...
|
||||
user_profile_and_more=Профиль и остальное
|
||||
signed_in_as=Вы вошли как
|
||||
|
||||
@@ -54,7 +53,8 @@ code=Код
|
||||
[install]
|
||||
install=Установка
|
||||
title=Установочные шаги для первого запуска
|
||||
requite_db_desc=Для Gogs требуется MySQL, PostgreSQL или SQLite3.
|
||||
docker_helper=Если вы используете Gogs в Docker-контейнере, пожалуйста прочтите <a target="_blank" href="%s">эти советы</a>, перед тем как что-либо изменить!
|
||||
requite_db_desc=Gogs требует MySQL, PostgreSQL, SQLite3 или TiDB.
|
||||
db_title=Настройки базы данных
|
||||
db_type=Тип базы данных
|
||||
host=Хост
|
||||
@@ -64,8 +64,11 @@ db_name=Имя базы данных
|
||||
db_helper=Для MySQL используйте тип таблиц InnoDB с кодировкой utf8_general_ci.
|
||||
ssl_mode=Режим SSL
|
||||
path=Путь
|
||||
sqlite_helper=Путь к файлу базы данных SQLite3.
|
||||
err_empty_sqlite_path=Путь к базе данных SQLite3 не может быть пустым.
|
||||
sqlite_helper=Путь до базы данных SQLite или TiDB.
|
||||
err_empty_db_path=Путь к базе данных SQLite3 или TiDB не может быть пустым.
|
||||
err_invalid_tidb_name=Название базы данных TiDB не может содержать символы "." и "-".
|
||||
no_admin_and_disable_registration=Вы не можете отключить регистрацию, не создав аккаунт администратора.
|
||||
err_empty_admin_password=Пароль администратора не может быть пустым.
|
||||
|
||||
general_title=Общие параметры Gogs
|
||||
app_name=Имя приложения
|
||||
@@ -96,9 +99,11 @@ server_service_title=Сервер и другие настройки служб
|
||||
offline_mode=Включение офлайн режима
|
||||
offline_mode_popup=Отключить CDN даже в производственном режиме, все файлы ресурсов будут раздаваться локально.
|
||||
disable_gravatar=Отключить службу Gravatar
|
||||
disable_gravatar_popup=Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_gravatar_popup=Отключить Gravatar и пользовательские источники, все аватары по-умолчанию загружаются пользователями.
|
||||
disable_registration=Отключить самостоятельную регистрацию
|
||||
disable_registration_popup=Запретить пользователям самостоятельную регистрацию, только администратор может создавать аккаунты.
|
||||
enable_captcha=Включить капчу
|
||||
enable_captcha_popup=Запрашивать капчу при регистрации пользователя.
|
||||
require_sign_in_view=Разрешить требовать авторизацию для просмотра страниц
|
||||
require_sign_in_view_popup=Только авторизированные пользователи могут просматривать страницы, посетители смогут увидеть только ссылку на авторизацию вверху страницы.
|
||||
admin_setting_desc=Вы не должны создать учетную запись администратора прямо сейчас, пользователь с ID = 1 получит доступ с правами администратора автоматически.
|
||||
@@ -106,7 +111,7 @@ admin_title=Настройки учётной записи администра
|
||||
admin_name=Имя пользователя
|
||||
admin_password=Пароль
|
||||
confirm_password=Подтвердить пароль
|
||||
admin_email=Эл. почта
|
||||
admin_email=Электронная почта администратора
|
||||
install_gogs=Установить Gogs
|
||||
test_git_failed=Не удалось проверить 'git' команду: %v
|
||||
sqlite3_not_available=Ваша версия не поддерживает SQLite3, пожалуйста скачайте официальную бинарную версию от %s, а не версию gobuild.
|
||||
@@ -143,7 +148,6 @@ forgot_password=Забыли пароль
|
||||
forget_password=Забыли пароль?
|
||||
sign_up_now=Нужен аккаунт? Зарегистрируйтесь.
|
||||
confirmation_mail_sent_prompt=Новое письмо для подтверждения было направлено на <b>%s</b>, пожалуйста, проверьте ваш почтовый ящик в течение %d часов для завершения регистрации.
|
||||
sign_in_email=Войдите в свой адрес электронной почты
|
||||
active_your_account=Активируйте свой аккаунт
|
||||
resent_limit_prompt=Вы слишком часто отправляете письмо с активацией. Подождите 3 минуты, пожалуйста.
|
||||
has_unconfirmed_mail=Здравствуйте, %s! У вас есть неподтвержденный адрес электронной почты (<b>%s</b>). Если вам не приходило письмо с подтверждением или нужно выслать новое письмо, нажмите на кнопку ниже.
|
||||
@@ -155,6 +159,12 @@ invalid_code=Извините, ваш код подтверждения исте
|
||||
reset_password_helper=Нажмите здесь, чтобы сбросить свой пароль
|
||||
password_too_short=Длина пароля не менее 6 символов.
|
||||
|
||||
[mail]
|
||||
activate_account=Пожалуйста активируйте свой аккаунт
|
||||
activate_email=Подтвердите адрес своей электронной почты
|
||||
reset_password=Восстановите ваш пароль
|
||||
register_success=Регистрация окончена. Добро пожаловать!
|
||||
|
||||
[modal]
|
||||
yes=Да
|
||||
no=Нет
|
||||
@@ -181,6 +191,7 @@ min_size_error=«должен содержать по крайней мере %s
|
||||
max_size_error=` должен содержать максимум %s символов.`
|
||||
email_error=«не является адресом электронной почты.»
|
||||
url_error=«не является допустимым URL-адресом.»
|
||||
include_error=` должен содержать '%s'`
|
||||
unknown_error=Неизвестная ошибка:
|
||||
captcha_incorrect=CAPTCHA не совпадает.
|
||||
password_not_match=Пароль и подтверждение пароля не совпадают.
|
||||
@@ -241,7 +252,7 @@ location=Местоположение
|
||||
update_profile=Обновить профиль
|
||||
update_profile_success=Ваш профиль был успешно обновлен.
|
||||
change_username=Имя пользователя изменено
|
||||
change_username_desc=Имя пользователя изменено, вы хотите продолжить? Это повлияет на все ссылки, связанные с вашей учетной записью.
|
||||
change_username_prompt=Это изменение может повлечь за собой изменение ссылок относительно вашего аккаунта.
|
||||
continue=Далее
|
||||
cancel=Отмена
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=Настройка вашего аватара обнов
|
||||
change_password=Сменить пароль
|
||||
old_password=Текущий пароль
|
||||
new_password=Новый пароль
|
||||
retype_new_password=Подтверждение нового пароля
|
||||
password_incorrect=Текущий пароль не правильный.
|
||||
change_password_success=Пароль сменен успешно. Теперь вы можете войти с новым паролем.
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=Ваш основной адрес электронной почты
|
||||
primary=Основной
|
||||
primary_email=Установить как основной
|
||||
delete_email=Удалить
|
||||
email_deletion=Удаление адреса электронной почты
|
||||
email_deletion_desc=Удаление этого адреса электронной почты, приведет к удалению связанной с вашим аккаунтом, информации. Вы точно хотите продолжить?
|
||||
email_deletion_success=Адрес электронной почты успешно удален.
|
||||
add_new_email=Добавить новый адрес электронной почты
|
||||
add_email=Добавить электронную почту
|
||||
add_email_confirmation_sent=Новое подтверждение по электронной почте было отправлено<b>%s</b>, пожалуйста, проверьте свой почтовый ящик в течение следующих %d часов, чтобы завершить процесс подтверждения.
|
||||
add_email_confirmation_sent=Новое подтверждение по электронной почте было отправлено '%s', пожалуйста, проверьте свой почтовый ящик в течение следующих %d часов, чтобы завершить процесс подтверждения.
|
||||
add_email_success=Новый адрес электронной почты успешно добавлен.
|
||||
|
||||
manage_ssh_keys=Управление SSH ключами
|
||||
@@ -303,9 +318,9 @@ token_name=Имя маркера
|
||||
generate_token=Генерировать маркер
|
||||
generate_token_succees=Успешно создан новый токен доступа! Пожалуйста сделайте копию вашего нового токена персонального доступа. Вы не сможете увидеть его снова!
|
||||
delete_token=Удалить
|
||||
access_token_deletion=Personal Access Token Deletion
|
||||
access_token_deletion_desc=Delete this personal access token will remove all related accesses of application. Do you want to continue?
|
||||
delete_token_success=Personal access token has been removed successfully! Don't forget to update your application as well.
|
||||
access_token_deletion=Удаление персонального токена доступа
|
||||
access_token_deletion_desc=Удаление этого персонального токена доступа приведет к удалению всех связанных прав доступа к приложению. Вы хотите продолжить?
|
||||
delete_token_success=Персональный токен доступа успешно удален! Не забудьте изменить настройки вашего приложения.
|
||||
|
||||
delete_account=Удалить свой аккаунт
|
||||
delete_prompt=Этим действием вы удалите свою учетную запись навсегда и <strong>НЕ СМОЖЕТЕ</strong> ее вернуть!
|
||||
@@ -318,19 +333,20 @@ owner=Владелец
|
||||
repo_name=Имя репозитория
|
||||
repo_name_helper=Лучшие названия репозиториев коротки, запоминаемы и <strong>уникальны</strong>.
|
||||
visibility=Видимость
|
||||
visiblity_helper=This repository is <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(Change of this value will affect all forks)
|
||||
visiblity_helper=<span class="ui red text">Личный</span> репозиторий
|
||||
visiblity_helper_forced=Все новые репозитории являются <span class="ui red text">Личными</span> по желанию администратора сайта
|
||||
visiblity_fork_helper=(Изменение этого значения затронет все форки)
|
||||
fork_repo=Ответвить репозиторий
|
||||
fork_from=Ответвление от
|
||||
fork_visiblity_helper=Ответвленному репозиторию нельзя поменять уровень видимости
|
||||
repo_desc=Описание
|
||||
repo_lang=Язык
|
||||
repo_lang_helper=Select .gitignore files
|
||||
repo_lang_helper=Выберите файлы .gitignore
|
||||
license=Лицензия
|
||||
license_helper=Выберите файл лицензии
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
readme_helper=Выберите шаблон для файла readme
|
||||
auto_init=Инициализировать этот репозиторий выбранными файлами и шаблоном
|
||||
create_repo=Создать репозиторий
|
||||
default_branch=Ветка по умолчанию
|
||||
mirror_interval=Интервал зеркалирования (час)
|
||||
@@ -340,15 +356,18 @@ form.name_pattern_not_allowed=Шаблон имени репозитория '%s
|
||||
|
||||
need_auth=Требуется авторизация
|
||||
migrate_type=Тип миграции
|
||||
migrate_type_helper=This repository will be a <span class="text blue">mirror</span>
|
||||
migrate_type_helper=Этот репозиторий будет <span class="text blue">зеркалом</span>
|
||||
migrate_repo=Перенос репозитория
|
||||
migrate.clone_address=Скопировать адрес
|
||||
migrate.clone_address_desc=This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.clone_address_desc=Это может быть HTTP/HTTPS/GIT адрес или локальный путь на сервере.
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=Недопустимый локальный путь. Возможно он не существует или является не папкой.
|
||||
|
||||
forked_from=forked from
|
||||
forked_from=форк от
|
||||
fork_from_self=Вы не можете форкнуть репозитарий, так как Вы уже его владелец!
|
||||
copy_link=Скопировать
|
||||
copy_link_success=Скопировано!
|
||||
copy_link_error=Нажмите ⌘-C или Ctrl-C для копирования
|
||||
click_to_copy=Скопировать в буфер обмена
|
||||
copied=Успешно скопировано
|
||||
clone_helper=Нужна помощь в клонировании? Посетите страницу <a target="_blank" href="%s">помощи</a>!
|
||||
@@ -363,6 +382,8 @@ quick_guide=Краткое руководство
|
||||
clone_this_repo=Клонировать репозиторий
|
||||
create_new_repo_command=Создать новый репозиторий из командной строки
|
||||
push_exist_repo=Отправить существующий репозиторий из командной строки
|
||||
repo_is_empty=Этот репозиторий пуст, пожалуйста, возвращайтесь позже!
|
||||
|
||||
|
||||
branch=Ветка
|
||||
tree=Дерево
|
||||
@@ -370,7 +391,7 @@ branch_and_tags=Ветки и метки
|
||||
branches=Ветки
|
||||
tags=Метки
|
||||
issues=Обсуждения
|
||||
pulls=Pull Requests
|
||||
pulls=Пулл реквесты
|
||||
labels=Метки
|
||||
milestones=Этапы
|
||||
commits=Коммиты
|
||||
@@ -390,66 +411,66 @@ commits.older=Раньше
|
||||
commits.newer=Новее
|
||||
|
||||
issues.new=Новая задача
|
||||
issues.new.labels=Labels
|
||||
issues.new.no_label=No Label
|
||||
issues.new.clear_labels=Clear labels
|
||||
issues.new.milestone=Milestone
|
||||
issues.new.no_milestone=No Milestone
|
||||
issues.new.clear_milestone=Clear milestone
|
||||
issues.new.open_milestone=Open Milestones
|
||||
issues.new.closed_milestone=Closed Milestones
|
||||
issues.new.assignee=Assignee
|
||||
issues.new.clear_assignee=Clear assignee
|
||||
issues.new.no_assignee=No assignee
|
||||
issues.create=Create Issue
|
||||
issues.new.labels=Метки
|
||||
issues.new.no_label=Не метка
|
||||
issues.new.clear_labels=Отчистить метки
|
||||
issues.new.milestone=Этап
|
||||
issues.new.no_milestone=Нет этапа
|
||||
issues.new.clear_milestone=Очистить этап
|
||||
issues.new.open_milestone=Открыть этап
|
||||
issues.new.closed_milestone=Завершенные этапы
|
||||
issues.new.assignee=Ответственный
|
||||
issues.new.clear_assignee=Убрать ответственного
|
||||
issues.new.no_assignee=Нет ответственного
|
||||
issues.create=Добавить задачу
|
||||
issues.new_label=Новая метка
|
||||
issues.new_label_placeholder=Имя метки...
|
||||
issues.create_label=Create Label
|
||||
issues.create_label=Добавить метку
|
||||
issues.open_tab=%d Открыть
|
||||
issues.close_tab=%d Закрыть
|
||||
issues.filter_label=Метка
|
||||
issues.filter_label_no_select=Нет выбранной метки
|
||||
issues.filter_milestone=Этап
|
||||
issues.filter_milestone_no_select=No selected milestone
|
||||
issues.filter_milestone_no_select=Этап не выбран
|
||||
issues.filter_assignee=Назначено
|
||||
issues.filter_assginee_no_select=No selected Assignee
|
||||
issues.filter_assginee_no_select=Ответственный не выбран
|
||||
issues.filter_type=Тип
|
||||
issues.filter_type.all_issues=Все задачи
|
||||
issues.filter_type.assigned_to_you=Назначено Вам
|
||||
issues.filter_type.created_by_you=Созданные вами
|
||||
issues.filter_type.mentioning_you=Вы упомянуты
|
||||
issues.filter_sort=Sort
|
||||
issues.filter_sort.latest=Newest
|
||||
issues.filter_sort.oldest=Oldest
|
||||
issues.filter_sort.recentupdate=Recently updated
|
||||
issues.filter_sort.leastupdate=Least recently updated
|
||||
issues.filter_sort.mostcomment=Most commented
|
||||
issues.filter_sort.leastcomment=Least commented
|
||||
issues.opened_by=opened %[1]s by <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=opened %[1]s by %[2]s
|
||||
issues.filter_sort=Сортировать
|
||||
issues.filter_sort.latest=Новейшие
|
||||
issues.filter_sort.oldest=Старейшие
|
||||
issues.filter_sort.recentupdate=Недавно обновленные
|
||||
issues.filter_sort.leastupdate=Давно обновленные
|
||||
issues.filter_sort.mostcomment=Большего комментариев
|
||||
issues.filter_sort.leastcomment=Меньше комментариев
|
||||
issues.opened_by=%[1] открыта <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=%[1]s открыта %[2]s
|
||||
issues.previous=Предыдущая страница
|
||||
issues.next=Следующая страница
|
||||
issues.open_title=Open
|
||||
issues.closed_title=Closed
|
||||
issues.num_comments=%d comments
|
||||
issues.commented_at=`commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=There is no content yet.
|
||||
issues.close_issue=Close
|
||||
issues.close_comment_issue=Close and comment
|
||||
issues.reopen_issue=Reopen
|
||||
issues.reopen_comment_issue=Reopen and comment
|
||||
issues.create_comment=Comment
|
||||
issues.closed_at=`closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.poster=Poster
|
||||
issues.admin=Admin
|
||||
issues.owner=Owner
|
||||
issues.sign_up_for_free=Sign up for free
|
||||
issues.sign_in_require_desc=to join this conversation. Already have an account? <a href="%s">Sign in to comment</a>
|
||||
issues.edit=Edit
|
||||
issues.cancel=Cancel
|
||||
issues.save=Save
|
||||
issues.open_title=Открыта
|
||||
issues.closed_title=Закрыта
|
||||
issues.num_comments=комментариев: %d
|
||||
issues.commented_at=` прокомментировал <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content=Пока нет содержимого.
|
||||
issues.close_issue=Закрыть
|
||||
issues.close_comment_issue=Прокомментировать и закрыть
|
||||
issues.reopen_issue=Открыть снова
|
||||
issues.reopen_comment_issue=Прокомментировать и открыть
|
||||
issues.create_comment=Комментировать
|
||||
issues.closed_at=`закрыл <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at=`открыл снова <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.poster=Автор
|
||||
issues.admin=Администратор
|
||||
issues.owner=Владелец
|
||||
issues.sign_up_for_free=Зарегистрируйтесь бесплатно
|
||||
issues.sign_in_require_desc=чтобы присоединиться к обсуждению. Уже есть аккаунт? <a href="%s">Войдите чтобы прокомментировать</a>
|
||||
issues.edit=Изменить
|
||||
issues.cancel=Отмена
|
||||
issues.save=Сохранить
|
||||
issues.label_title=Имя метки
|
||||
issues.label_color=Цвет метки
|
||||
issues.label_count=%d меток
|
||||
@@ -461,52 +482,54 @@ issues.label_deletion=Удаление метки
|
||||
issues.label_deletion_desc=Удаление ярлыка затронет все связанные задачи. Продолжить?
|
||||
issues.label_deletion_success=Метка была удалена успешно!
|
||||
|
||||
pulls.compare_changes=Compare Changes
|
||||
pulls.compare_changes_desc=Compare two branches and make a pull request for changes.
|
||||
pulls.compare_base=base
|
||||
pulls.compare_compare=compare
|
||||
pulls.filter_branch=Filter branch
|
||||
pulls.no_results=No results found.
|
||||
pulls.nothing_to_compare=There is nothing to compare because base and head branches are even.
|
||||
pulls.has_pull_request=`There is already a pull request between these two targets: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Create Pull Request
|
||||
pulls.title_desc=wants to merge %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code>
|
||||
pulls.merged_title_desc=merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Conversation
|
||||
pulls.tab_commits=Commits
|
||||
pulls.tab_files=Files changed
|
||||
pulls.reopen_to_merge=Please reopen this pull request to perform merge operation.
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.compare_changes=Сравнить изменения
|
||||
pulls.compare_changes_desc=Сравнить две ветки и создать пулл реквест для изменений.
|
||||
pulls.compare_base=родительская ветка
|
||||
pulls.compare_compare=сравнить
|
||||
pulls.filter_branch=Фильтр по ветке
|
||||
pulls.no_results=Результатов не найдено.
|
||||
pulls.nothing_to_compare=Нечего сравнивать, родительская и текущая ветка одинаковые.
|
||||
pulls.has_pull_request=`Уже существует пулл-реквест между двумя целями <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>`
|
||||
pulls.create=Создать пулл-реквест
|
||||
pulls.title_desc=хочет смерджить %[1]d коммит(ов) из <code>%[2]s</code> в <code>%[3]s</code>
|
||||
pulls.merged_title_desc=слито %[1]d коммит(ов) из <code>%[2]s</code> в <code>%[3]s</code> %[4]s
|
||||
pulls.tab_conversation=Обсуждение
|
||||
pulls.tab_commits=Коммиты
|
||||
pulls.tab_files=Измененные файлы
|
||||
pulls.reopen_to_merge=Пожалуйста пересоздайте пулл-реквест для слияния.
|
||||
pulls.merged=Слито
|
||||
pulls.has_merged=Слияние этого пулл-реквеста успешно завершено!
|
||||
pulls.data_broken=Содержимое этого пулл-реквеста было нарушено, вследствии удаления или клонирования информации.
|
||||
pulls.is_checking=Продолжается проверка конфликтов, пожалуйста обновите страницу несколько позже.
|
||||
pulls.can_auto_merge_desc=Вы можете провести операцию автоматического слияния для этого пулл-реквеста.
|
||||
pulls.cannot_auto_merge_desc=Вы не можете произвести операцию автоматического слияния, потому как существуют конфликты между коммитами.
|
||||
pulls.cannot_auto_merge_helper=Используйте командную строку для решения этого.
|
||||
pulls.merge_pull_request=Слить пулл-реквест
|
||||
pulls.open_unmerged_pull_exists=`Вы не можете произвести операцию переоткрытия, потому что уже существует пулл-реквест (#%d) из этого же репозитория, с такими же параметрами слияния, который ожидает слияния.`
|
||||
|
||||
milestones.new=New Milestone
|
||||
milestones.open_tab=%d Open
|
||||
milestones.close_tab=%d Closed
|
||||
milestones.closed=Closed %s
|
||||
milestones.no_due_date=No due date
|
||||
milestones.open=Open
|
||||
milestones.close=Close
|
||||
milestones.new_subheader=Create milestones to organize your issues.
|
||||
milestones.create=Create Milestone
|
||||
milestones.title=Title
|
||||
milestones.desc=Description
|
||||
milestones.due_date=Due Date (optional)
|
||||
milestones.clear=Clear
|
||||
milestones.invalid_due_date_format=Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.create_success=Milestone '%s' has been created successfully!
|
||||
milestones.edit=Edit Milestone
|
||||
milestones.edit_subheader=Use better description for milestones so people won't be confused.
|
||||
milestones.cancel=Cancel
|
||||
milestones.modify=Modify Milestone
|
||||
milestones.edit_success=Changes of milestone '%s' has been saved successfully!
|
||||
milestones.deletion=Milestone Deletion
|
||||
milestones.deletion_desc=Delete this milestone will remove its information in all related issues. Do you want to continue?
|
||||
milestones.deletion_success=Milestone has been deleted successfully!
|
||||
milestones.new=Новая контрольная точка
|
||||
milestones.open_tab=%d открыты
|
||||
milestones.close_tab=%d Закрыт
|
||||
milestones.closed=Закрыт %s
|
||||
milestones.no_due_date=Срок не указан
|
||||
milestones.open=Открыть
|
||||
milestones.close=Закрыть
|
||||
milestones.new_subheader=Создавайте контрольные точки для трекинга ваших вопросов.
|
||||
milestones.create=Создать контрольную точку
|
||||
milestones.title=Заголовок
|
||||
milestones.desc=Описание
|
||||
milestones.due_date=Дата окончания (опционально)
|
||||
milestones.clear=Очистить
|
||||
milestones.invalid_due_date_format=Некорректная дата окончания. Правильный формат - 'гггг-мм-дд'.
|
||||
milestones.create_success=Контрольная точка '%s' успешно создана!
|
||||
milestones.edit=Изменить контрольную точку
|
||||
milestones.edit_subheader=Используйте лучшее описание контрольной точки, во избежание непонимания со стороны других людей.
|
||||
milestones.cancel=Отмена
|
||||
milestones.modify=Изменить контрольную точку
|
||||
milestones.edit_success=Изменения контрольной точки '%s' успешно сохранены!
|
||||
milestones.deletion=Удаление контрольной точки
|
||||
milestones.deletion_desc=Удаление этой контрольной точки приведет с удалению всей информации, во всех вопросах (Issues). Вы действительно хотите продолжить?
|
||||
milestones.deletion_success=Контрольная точка успешно удалена!
|
||||
|
||||
settings=Настройки
|
||||
settings.options=Опции
|
||||
@@ -517,20 +540,20 @@ settings.basic_settings=Основные параметры
|
||||
settings.danger_zone=Опасная зона
|
||||
settings.site=Официальный сайт
|
||||
settings.update_settings=Обновить настройки
|
||||
settings.change_reponame_prompt=This change will affect how links relate to the repository.
|
||||
settings.change_reponame_prompt=Это изменение повлияет на отношения ссылок к этому репозиторию.
|
||||
settings.transfer=Передать права собственности
|
||||
settings.transfer_desc=Передать репозиторий другому пользователю или организации где у вас есть права администратора.
|
||||
settings.new_owner_has_same_repo=У нового владельца уже есть хранилище с таким названием.
|
||||
settings.delete=Удалить этот репозиторий
|
||||
settings.delete_desc=Как только вы удалите репозиторий — пути назад не будет. Удостоверьтесь, что вам это точно нужно.
|
||||
settings.transfer_notices_1=- You will lose access if new owner is a individual user.
|
||||
settings.transfer_notices_2=- You will conserve access if new owner is an organization and if you're one of the owners.
|
||||
settings.transfer_form_title=Please enter following information to confirm your operation:
|
||||
settings.delete_notices_1=- This operation <strong>CANNOT</strong> be undone.
|
||||
settings.delete_notices_2=- This operation will permanently delete the everything of this repository, including Git data, issues, comments and accesses of collaborators.
|
||||
settings.delete_notices_fork_1=- If this repository is public, all forks will be became independent after deletion.
|
||||
settings.delete_notices_fork_2=- If this repository is private, all forks will be removed at the same time.
|
||||
settings.delete_notices_fork_3=- If you want to keep all forks after deletion, please change visibility of this repository to public first.
|
||||
settings.transfer_notices_1=- Вы можете потерять доступ, если новый владелец является отдельным пользователем.
|
||||
settings.transfer_notices_2=- Вы сохраните доступ, если новым владельцем станет организация, владельцем которой вы являетесь.
|
||||
settings.transfer_form_title=Введите сопутствующую информацию для подтверждения операции:
|
||||
settings.delete_notices_1=- Эта операция <strong>НЕ МОЖЕТ</strong> быть отменена.
|
||||
settings.delete_notices_2=- Эта операция перманентно удалит всё из этого репозитория, включая данные Git, связанные с ним вопросы, комментарии и права доступа для сотрудников.
|
||||
settings.delete_notices_fork_1=- Если данный репозиторий является публичным, все склонированные репозитории останутся независимыми, после его удаления.
|
||||
settings.delete_notices_fork_2=- Если данный репозиторий является приватным, все его форки будут удалены вместе с ним.
|
||||
settings.delete_notices_fork_3=- Если вы хотите сохранить все форки после удаления репозитория, то сначала сделайте его публичным.
|
||||
settings.update_settings_success=Настройка репозитория обновлена успешно.
|
||||
settings.transfer_owner=Новый владелец
|
||||
settings.make_transfer=Выполнить передачу
|
||||
@@ -542,16 +565,16 @@ settings.remove_collaborator_success=Соавтор был удален.
|
||||
settings.user_is_org_member=Пользователь является членом организации, члены которой не могут быть добавлены в качестве соавтора.
|
||||
settings.add_webhook=Добавить Webhook
|
||||
settings.hooks_desc=Webhooks позволяют внешним службам получать уведомления при возникновении определенных событий на Gogs. При возникновении указанных событий мы отправим запрос POST на каждый заданный вами URL. Узнать больше можно в нашем <a target="_blank" href="%s">Руководстве по Webhooks</a>.
|
||||
settings.webhook_deletion=Delete Webhook
|
||||
settings.webhook_deletion_desc=Delete this webhook will remove its information and all delivery history. Do you want to continue?
|
||||
settings.webhook_deletion_success=Webhook has been deleted successfully!
|
||||
settings.webhook.request=Request
|
||||
settings.webhook.response=Response
|
||||
settings.webhook.headers=Headers
|
||||
settings.webhook.payload=Payload
|
||||
settings.webhook.body=Body
|
||||
settings.githooks_desc=Git Hooks are powered by Git itself, you can edit files of supported hooks in the list below to perform custom operations.
|
||||
settings.githook_edit_desc=If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
|
||||
settings.webhook_deletion=Удалить веб-хук
|
||||
settings.webhook_deletion_desc=Удаление этого веб-хука приведет к удалению всей, связанной с ним, информации, включая историю. Хотите продолжить?
|
||||
settings.webhook_deletion_success=Веб-хук успешно удален!
|
||||
settings.webhook.request=Запрос
|
||||
settings.webhook.response=Ответ
|
||||
settings.webhook.headers=Заголовки
|
||||
settings.webhook.payload=Содержимое запроса
|
||||
settings.webhook.body=Тело ответа
|
||||
settings.githooks_desc=Git-хуки предоставляются Git самим по себе, вы можете изменять файлы поддерживаемых хуков из списка ниже чтобы выполнять внешние операции.
|
||||
settings.githook_edit_desc=Если хук не активен, будет подставлен пример содержимого. Пустое значение в этом поле приведет к отключению хука.
|
||||
settings.githook_name=Название Hook'a
|
||||
settings.githook_content=Перехватить содержание
|
||||
settings.update_githook=Обновить Hook
|
||||
@@ -559,19 +582,19 @@ settings.add_webhook_desc=Мы отправим запрос <code>POST</code>
|
||||
settings.payload_url=URL обработчика
|
||||
settings.content_type=Тип содержимого
|
||||
settings.secret=Secret
|
||||
settings.slack_username=Username
|
||||
settings.slack_icon_url=Icon URL
|
||||
settings.slack_color=Color
|
||||
settings.slack_username=Имя пользователя
|
||||
settings.slack_icon_url=URL иконки
|
||||
settings.slack_color=Цвет
|
||||
settings.event_desc=На какие события этот webhook должен срабатывать?
|
||||
settings.event_push_only=Просто <code>push</code> событие.
|
||||
settings.event_send_everything=I need <strong>everything</strong>.
|
||||
settings.event_choose=Let me choose what I need.
|
||||
settings.event_create=Create
|
||||
settings.event_create_desc=Branch, or tag created
|
||||
settings.event_send_everything=Мне нужно <strong>все</strong>.
|
||||
settings.event_choose=Позвольте мне выбрать то, что нужно.
|
||||
settings.event_create=Создать
|
||||
settings.event_create_desc=Ветка или тэг созданы
|
||||
settings.event_push=Push
|
||||
settings.event_push_desc=Git push to a repository
|
||||
settings.event_push_desc=Push в репозиторий
|
||||
settings.active=Активен
|
||||
settings.active_helper=Details regarding the event which triggered the hook will be delivered as well.
|
||||
settings.active_helper=Подробности о событии, вызвавшем срабатывание хука, также будут предоставлены.
|
||||
settings.add_hook_success=Был добавлен новый webhook.
|
||||
settings.update_webhook=Обновление Webhook
|
||||
settings.update_hook_success=Webhook обновлен.
|
||||
@@ -583,16 +606,16 @@ settings.slack_token=Token
|
||||
settings.slack_domain=Домен
|
||||
settings.slack_channel=Канал
|
||||
settings.deploy_keys=Ключи развертывания
|
||||
settings.add_deploy_key=Add Deploy Key
|
||||
settings.no_deploy_keys=You haven't added any deploy key.
|
||||
settings.title=Title
|
||||
settings.deploy_key_content=Content
|
||||
settings.key_been_used=Deploy key content has been used.
|
||||
settings.key_name_used=Deploy key with same name has already existed.
|
||||
settings.add_key_success=New deploy key '%s' has been added successfully!
|
||||
settings.deploy_key_deletion=Delete Deploy Key
|
||||
settings.deploy_key_deletion_desc=Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||
settings.deploy_key_deletion_success=Deploy key has been deleted successfully!
|
||||
settings.add_deploy_key=Добавить ключ развертывания
|
||||
settings.no_deploy_keys=Вы не добавляли ключи развертывания.
|
||||
settings.title=Заголовок
|
||||
settings.deploy_key_content=Содержимое
|
||||
settings.key_been_used=Содержимое ключа развертывания уже используется.
|
||||
settings.key_name_used=Ключ развертывания с таким заголовком уже существует.
|
||||
settings.add_key_success=Новый ключ развертывания '%s' успешно добавлен!
|
||||
settings.deploy_key_deletion=Удалить ключ развертывания
|
||||
settings.deploy_key_deletion_desc=Удаление ключа развертывания приведет к удалению всех связанных прав доступа к репозиторию. Вы хотите продолжить?
|
||||
settings.deploy_key_deletion_success=Ключ развертывания успешно удален!
|
||||
|
||||
diff.browse_source=Просмотр исходного кода
|
||||
diff.parent=Родитель
|
||||
@@ -621,7 +644,7 @@ release.preview=Предварительный просмотр
|
||||
release.content_placeholder=Напишите что-нибудь
|
||||
release.loading=Загрузка...
|
||||
release.prerelease_desc=Это предварительный релиз
|
||||
release.prerelease_helper=We’ll point out that this release is not production-ready.
|
||||
release.prerelease_helper=Отдельно отметим, что этот релиз не готов к использованию в продакшене.
|
||||
release.publish=Опубликовать релиз
|
||||
release.save_draft=Сохранить черновик
|
||||
release.edit_release=Редактировать релиз
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=Релиз с этим именем тега уж
|
||||
|
||||
[org]
|
||||
org_name_holder=Название организации
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=Лучшие названия организаций коротки и запоминаемы.
|
||||
org_email_helper=Почта организации получает все уведомления и подтверждения.
|
||||
create_org=Создать Организацию
|
||||
repo_updated=Обновлено
|
||||
people=Люди
|
||||
@@ -643,7 +666,7 @@ org_desc=Описание
|
||||
team_name=Название команды
|
||||
team_desc=Описание
|
||||
team_name_helper=Вы будете использовать это имя для упоминания этой команды в обсуждении.
|
||||
team_desc_helper=What is this team all about?
|
||||
team_desc_helper=Что это за команда?
|
||||
team_permission_desc=Какой уровень разрешений должен быть у этой команды?
|
||||
|
||||
form.name_reserved=Наименование организации '%s' зарезервированно.
|
||||
@@ -655,9 +678,9 @@ settings.full_name=Полное имя
|
||||
settings.website=Сайт
|
||||
settings.location=Местоположение
|
||||
settings.update_settings=Обновить настройки
|
||||
settings.change_orgname=Имя Организации изменено
|
||||
settings.change_orgname_desc=Изменилось название организации, вы хотите продолжить? Это повлияет на все ссылки относящиеся к этой Организации.
|
||||
settings.update_setting_success=Настройки Организации были успешно обновлены.
|
||||
settings.change_orgname_prompt=Это изменение затронет все связанные с организацией, ссылки.
|
||||
settings.update_avatar_success=Аватар организации успешно обновлен.
|
||||
settings.delete=Удалить Организацию
|
||||
settings.delete_account=Удалить Эту Организацию
|
||||
settings.delete_prompt=Это действие безвозвратно удалит эту организацию навсегда.
|
||||
@@ -698,7 +721,7 @@ teams.delete_team_desc=Эта команда будет удалена. Вы х
|
||||
teams.delete_team_success=Данная команда была удалена успешно.
|
||||
teams.read_permission_desc=Эта команда предоставляет доступ на <strong>Чтение</strong>: члены могут просматривать и клонировать репозитории команды.
|
||||
teams.write_permission_desc=Эта команда предоставляет доступ на <strong>Запись</strong>: члены могут получать и выполнять push команды в репозитории.
|
||||
teams.admin_permission_desc=This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories.
|
||||
teams.admin_permission_desc=Эта команда дает <strong>административный</strong> доступ: участники могут читать, пушить и добавлять соавторов к ее репозиториям.
|
||||
teams.repositories=Репозитории группы разработки
|
||||
teams.add_team_repository=Добавить репозиторий группы разработки
|
||||
teams.remove_repo=Удалить
|
||||
@@ -713,8 +736,9 @@ authentication=Авторизация
|
||||
config=Настройки
|
||||
notices=Системные уведомления
|
||||
monitor=Мониторинг
|
||||
prev=Предыдущий.
|
||||
next=Следующий
|
||||
first_page=Первый
|
||||
last_page=Последний
|
||||
total=Всего: %d
|
||||
|
||||
dashboard.statistic=Статистика
|
||||
dashboard.operations=Операции
|
||||
@@ -733,66 +757,71 @@ dashboard.git_gc_repos=Выполнить сборку мусора на реп
|
||||
dashboard.git_gc_repos_success=Сборка мусора на всех репозиториях успешно выполнена.
|
||||
dashboard.resync_all_sshkeys=Переписать файл «.ssh/authorized_keys» (осторожно: не Gogs ключи будут утеряны)
|
||||
dashboard.resync_all_sshkeys_success=Были успешно переписаны все открытые ключи.
|
||||
dashboard.resync_all_update_hooks=Rewrite all update hook of repositories (needed when custom config path is changed)
|
||||
dashboard.resync_all_update_hooks_success=All repositories' update hook have been rewritten successfully.
|
||||
dashboard.resync_all_update_hooks=Перезаписать все апдейт-хуки этого репозитория (необходимо, когда изменен путь до папки конфигураций)
|
||||
dashboard.resync_all_update_hooks_success=Апдейт-хуки всех репозиториев успешно перезаписаны.
|
||||
|
||||
dashboard.server_uptime=Время непрерывной работы сервера
|
||||
dashboard.current_goroutine=Текущий Goroutines
|
||||
dashboard.current_memory_usage=Текущее использование памяти
|
||||
dashboard.total_memory_allocated=Всего памяти выделено
|
||||
dashboard.memory_obtained=Memory Obtained
|
||||
dashboard.pointer_lookup_times=Pointer Lookup Times
|
||||
dashboard.memory_allocate_times=Memory Allocate Times
|
||||
dashboard.memory_free_times=Memory Free Times
|
||||
dashboard.memory_obtained=Памяти использовано
|
||||
dashboard.pointer_lookup_times=Запросов указателя
|
||||
dashboard.memory_allocate_times=Выделений памяти
|
||||
dashboard.memory_free_times=Освобождений памяти
|
||||
dashboard.current_heap_usage=Текущее использование кучи
|
||||
dashboard.heap_memory_obtained=Heap Memory Obtained
|
||||
dashboard.heap_memory_idle=Heap Memory Idle
|
||||
dashboard.heap_memory_obtained=Получено динамической памяти
|
||||
dashboard.heap_memory_idle=Не используется динамической памяти
|
||||
dashboard.heap_memory_in_use=Кучи памяти в работе
|
||||
dashboard.heap_memory_released=Heap Memory Released
|
||||
dashboard.heap_objects=Heap Objects
|
||||
dashboard.bootstrap_stack_usage=Bootstrap Stack Usage
|
||||
dashboard.stack_memory_obtained=Stack Memory Obtained
|
||||
dashboard.mspan_structures_usage=MSpan Structures Usage
|
||||
dashboard.mspan_structures_obtained=MSpan Structures Obtained
|
||||
dashboard.mcache_structures_usage=MCache Structures Usage
|
||||
dashboard.mcache_structures_obtained=MCache Structures Obtained
|
||||
dashboard.profiling_bucket_hash_table_obtained=Profiling Bucket Hash Table Obtained
|
||||
dashboard.gc_metadata_obtained=GC Metadada Obtained
|
||||
dashboard.other_system_allocation_obtained=Other System Allocation Obtained
|
||||
dashboard.next_gc_recycle=Next GC Recycle
|
||||
dashboard.last_gc_time=Since Last GC Time
|
||||
dashboard.total_gc_time=Total GC Pause
|
||||
dashboard.total_gc_pause=Total GC Pause
|
||||
dashboard.last_gc_pause=Last GC Pause
|
||||
dashboard.gc_times=GC Times
|
||||
dashboard.heap_memory_released=Освобождено динамической памяти
|
||||
dashboard.heap_objects=Объектов динамической памяти
|
||||
dashboard.bootstrap_stack_usage=Использование стека загрузчика
|
||||
dashboard.stack_memory_obtained=Память, занятая под стек
|
||||
dashboard.mspan_structures_usage=Использование структур MSpan
|
||||
dashboard.mspan_structures_obtained=Получено структур MSpan
|
||||
dashboard.mcache_structures_usage=Использование структур MCache
|
||||
dashboard.mcache_structures_obtained=Получено структур MCache
|
||||
dashboard.profiling_bucket_hash_table_obtained=Хеш-таблиц получено при профилировании
|
||||
dashboard.gc_metadata_obtained=Получены метаданные сборщика мусора
|
||||
dashboard.other_system_allocation_obtained=Получено других системных выделений памяти
|
||||
dashboard.next_gc_recycle=Следующая очистка сборщика мусора
|
||||
dashboard.last_gc_time=Прошло с последнего сбора мусора
|
||||
dashboard.total_gc_time=Итоговое время GC
|
||||
dashboard.total_gc_pause=Итоговая задержка GC
|
||||
dashboard.last_gc_pause=Последняя пауза сборщика мусора
|
||||
dashboard.gc_times=Количество сборок мусора
|
||||
|
||||
users.user_manage_panel=User Manage Panel
|
||||
users.user_manage_panel=Панель управления пользователями
|
||||
users.new_account=Создать новый аккаунт
|
||||
users.name=Имя
|
||||
users.activated=Активирован
|
||||
users.admin=Администратор
|
||||
users.repos=Репозитории
|
||||
users.created=Создано
|
||||
users.send_register_notify=Отправить пользователю уведомление о регистрации
|
||||
users.new_success=Новая учетная запись '%s' успешно создана.
|
||||
users.edit=Редактировать
|
||||
users.auth_source=Источник авторизации
|
||||
users.auth_source=Источник аутентификации
|
||||
users.local=Локальный
|
||||
users.auth_login_name=Authorization Login Name
|
||||
users.auth_login_name=Логин для авторизации
|
||||
users.password_helper=Оставьте пустым, чтобы оставить без изменений.
|
||||
users.update_profile_success=Профиль учетной записи обновлен успешно.
|
||||
users.edit_account=Изменение учетной записи
|
||||
users.is_activated=Эта учетная запись активирована
|
||||
users.is_admin=У этой учетной записи есть права администратора
|
||||
users.allow_git_hook=Пользователь имеет право создать Git перехватчик
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=Обновить профиль учетной записи
|
||||
users.delete_account=Удалить эту учетную запись
|
||||
users.still_own_repo=На вашем аккаунте все еще остается как минимум один репозиторий, сначала вам нужно удалить или передать его.
|
||||
users.still_has_org=This account still has membership in at least one organization, you have to leave or delete the organizations first.
|
||||
users.still_has_org=Эта учетная запись все еще является членом как минимум одной организации. Для продолжения, покиньте или удалите эту организацию.
|
||||
users.deletion_success=Учетная запись успешно удалена!
|
||||
|
||||
orgs.org_manage_panel=Управление группами
|
||||
orgs.name=Имя
|
||||
orgs.teams=Команды
|
||||
orgs.members=Участники
|
||||
|
||||
repos.repo_manage_panel=Repository Manage Panel
|
||||
repos.repo_manage_panel=Панель управления репозиторием
|
||||
repos.owner=Владелец
|
||||
repos.name=Имя
|
||||
repos.private=Приватный
|
||||
@@ -800,41 +829,47 @@ repos.watches=Следят
|
||||
repos.stars=В избранном
|
||||
repos.issues=Вопросы
|
||||
|
||||
auths.auth_manage_panel=Authorization Manage Panel
|
||||
auths.new=Add New Authorization Source
|
||||
auths.auth_manage_panel=Панель управления аутнентификациями
|
||||
auths.new=Добавить новый источник
|
||||
auths.name=Имя
|
||||
auths.type=Тип
|
||||
auths.enabled=Включено
|
||||
auths.updated=Обновлено
|
||||
auths.auth_type=Тип авторизации
|
||||
auths.auth_name=Название авторизации
|
||||
auths.auth_type=Тип аутентификации
|
||||
auths.auth_name=Имя аутентификации
|
||||
auths.domain=Домен
|
||||
auths.host=Хост
|
||||
auths.port=Порт
|
||||
auths.bind_dn=Bind DN
|
||||
auths.bind_password=Bind Password
|
||||
auths.user_base=User Search Base
|
||||
auths.attribute_name=First name attribute
|
||||
auths.attribute_surname=Surname attribute
|
||||
auths.attribute_mail=E-mail attribute
|
||||
auths.filter=User Filter
|
||||
auths.admin_filter=Admin Filter
|
||||
auths.bind_dn=Привязать DN
|
||||
auths.bind_password=Привязать пароль
|
||||
auths.bind_password_helper=Внимание: Этот пароль сохранен в небезопасном виде. Не используйте высоко-привилегированную учетную запись.
|
||||
auths.user_base=База для поиска пользователя
|
||||
auths.user_dn=DN пользователя
|
||||
auths.attribute_name=Имя аттрибута
|
||||
auths.attribute_surname=Фамилия аттрибута
|
||||
auths.attribute_mail=Электронная почта аттрибута
|
||||
auths.filter=Фильтр пользователя
|
||||
auths.admin_filter=Фильтр администратора
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=Тип авторизации SMTP
|
||||
auths.smtp_auth=Тип аутентификации SMTP
|
||||
auths.smtphost=Узел SMTP
|
||||
auths.smtpport=SMTP-порт
|
||||
auths.allowed_domains=Разрешенные домены
|
||||
auths.allowed_domains_helper=Оставьте пустым чтобы не ограничивать домены. Несколько доменов должны быть разделены запятыми ','.
|
||||
auths.enable_tls=Включение шифрования TLS
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.pam_service_name=PAM Service Name
|
||||
auths.skip_tls_verify=Пропустить проверку TLS
|
||||
auths.pam_service_name=Имя службы PAM
|
||||
auths.enable_auto_register=Включить автоматическую регистрацию
|
||||
auths.tips=Советы
|
||||
auths.edit=Редактировать параметры авторизации
|
||||
auths.edit=Изменить параметры канала аутентификации
|
||||
auths.activated=Эта аутентификация активирована
|
||||
auths.update_success=Настройка авторизации обновлена успешно.
|
||||
auths.update=Обновить параметры авторизации
|
||||
auths.delete=Удалить эту авторизацию
|
||||
auths.delete_auth_title=Удаление авторизации
|
||||
auths.delete_auth_desc=Эта авторизация будет удалена. Вы хотите продолжить?
|
||||
auths.new_success=Новый канал аутентификации '%s' успешно создан.
|
||||
auths.update_success=Настройки канала аутентификации успешно сохранены.
|
||||
auths.update=Обновить параметры аутентификации
|
||||
auths.delete=Удалить этот канал аутентификации
|
||||
auths.delete_auth_title=Удаление канала аутентификации
|
||||
auths.delete_auth_desc=Этот канал аутентификации будет удален. Вы уверены что хотите продолжить?
|
||||
auths.deletion_success=Канал аутентификации успешно удален!
|
||||
|
||||
config.server_config=Конфигурация сервера
|
||||
config.app_name=Имя приложения
|
||||
@@ -845,11 +880,11 @@ config.offline_mode=Автономный режим
|
||||
config.disable_router_log=Отключение журнала маршрутизатора
|
||||
config.run_user=Запуск пользователем
|
||||
config.run_mode=Режим выполнения
|
||||
config.repo_root_path=Repository Root Path
|
||||
config.static_file_root_path=Static File Root Path
|
||||
config.log_file_root_path=Log File Root Path
|
||||
config.repo_root_path=Путь до корня репозитория
|
||||
config.static_file_root_path=Статичный путь до файла
|
||||
config.log_file_root_path=Путь до папки с логами
|
||||
config.script_type=Тип сценария
|
||||
config.reverse_auth_user=Reverse Authentication User
|
||||
config.reverse_auth_user=Заголовок с именем пользователя для авторизации на reverse proxy
|
||||
config.db_config=Конфигурация базы данных
|
||||
config.db_type=Тип
|
||||
config.db_host=Хост
|
||||
@@ -858,18 +893,20 @@ config.db_user=Пользователь
|
||||
config.db_ssl_mode=Режим SSL
|
||||
config.db_ssl_mode_helper=(только для «postgres»)
|
||||
config.db_path=Путь
|
||||
config.db_path_helper=(for "sqlite3" only)
|
||||
config.service_config=Service Configuration
|
||||
config.register_email_confirm=Require E-mail Confirmation
|
||||
config.db_path_helper=(для "SQLite3" и "TiDB")
|
||||
config.service_config=Сервисная конфигурация
|
||||
config.register_email_confirm=Требуется подтверждение по электронной почте
|
||||
config.disable_register=Отключить регистрацию
|
||||
config.show_registration_button=Show Register Button
|
||||
config.show_registration_button=Показать кнопку регистрации
|
||||
config.require_sign_in_view=Для просмотра необходима авторизация
|
||||
config.mail_notify=Почтовые уведомления
|
||||
config.enable_cache_avatar=Кешировать аватар
|
||||
config.active_code_lives=Active Code Lives
|
||||
config.reset_password_code_lives=Reset Password Code Lives
|
||||
config.mail_notify=Почтовые уведомления
|
||||
config.disable_key_size_check=Отключить проверку на минимальный размер ключа
|
||||
config.enable_captcha=Включить капчу
|
||||
config.active_code_lives=Время жизни кода для активации
|
||||
config.reset_password_code_lives=Время жизни кода сброса пароля
|
||||
config.webhook_config=Настройка автоматического обновления репозиции
|
||||
config.queue_length=Queue Length
|
||||
config.queue_length=Длина очереди
|
||||
config.deliver_timeout=Задержка доставки
|
||||
config.skip_tls_verify=Пропустить TLS проверка
|
||||
config.mailer_config=Настройки почты
|
||||
@@ -881,20 +918,20 @@ config.mailer_user=Пользователь
|
||||
config.oauth_config=Конфигурация OAuth
|
||||
config.oauth_enabled=Включено
|
||||
config.cache_config=Настройки кеша
|
||||
config.cache_adapter=Cache Adapter
|
||||
config.cache_interval=Cache Interval
|
||||
config.cache_conn=Cache Connection
|
||||
config.session_config=Session Configuration
|
||||
config.session_provider=Session Provider
|
||||
config.provider_config=Provider Config
|
||||
config.cache_adapter=Адаптер кэша
|
||||
config.cache_interval=Интервал кэширования
|
||||
config.cache_conn=Подключение кэша
|
||||
config.session_config=Конфигурация сессии
|
||||
config.session_provider=Провайдер сессии
|
||||
config.provider_config=Конфигурация провайдера
|
||||
config.cookie_name=Имя файла cookie
|
||||
config.enable_set_cookie=Enable Set Cookie
|
||||
config.gc_interval_time=GC Interval Time
|
||||
config.enable_set_cookie=Включить установку cookies
|
||||
config.gc_interval_time=Интервал работы сборщика мусора
|
||||
config.session_life_time=Время жизни сессии
|
||||
config.https_only=Только HTTPS
|
||||
config.cookie_life_time=Время жизни файла cookie
|
||||
config.picture_config=Настройка изображения
|
||||
config.picture_service=Picture Service
|
||||
config.picture_service=Сервис изображений
|
||||
config.disable_gravatar=Отключить Gravatar
|
||||
config.log_config=Конфигурация журнала
|
||||
config.log_mode=Режим журналирования
|
||||
@@ -904,7 +941,7 @@ monitor.name=Имя
|
||||
monitor.schedule=Расписание
|
||||
monitor.next=В следующий раз
|
||||
monitor.previous=Предыдущий раз
|
||||
monitor.execute_times=Execute Times
|
||||
monitor.execute_times=Количество выполнений
|
||||
monitor.process=Запущенные процессы
|
||||
monitor.desc=Описание
|
||||
monitor.start=Момент начала
|
||||
@@ -919,40 +956,40 @@ notices.delete_success=Системное уведомление успешно
|
||||
|
||||
[action]
|
||||
create_repo=создан репозиторий <a href="%s"> %s</a>
|
||||
rename_repo=renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=pushed to <a href="%s/src/%s">%[2]s</a> at <a href="%[1]s">%[3]s</a>
|
||||
create_issue=`opened issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
create_pull_request=`created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue=`commented on issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=transfered repository <code>%s</code> to <a href="%s">%s</a>
|
||||
push_tag=pushed tag <a href="%s/src/%s">%[2]s</a> to <a href="%[1]s">%[3]s</a>
|
||||
rename_repo=репозиторий переименован из <code>%[1]s</code>на <a href="%[2]s">%[3]s</a>
|
||||
commit_repo=запушил <a href="%s/src/%s">%[2]s</a> в <a href="%[1]s">%[3]s</a>
|
||||
create_issue=`открытый вопрос <a href="%s/issues/%s">%s#%[2]</a>`
|
||||
create_pull_request=`созданный пулл-реквест <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue=`прокомментировал(а) вопрос <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
merge_pull_request=`слил пул реквест <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo=перенес репозиторий <code>%s</code> в <a href="%s">%s</a>
|
||||
push_tag=запушил тэг <a href="%s/src/%s">%[2]s</a> в <a href="%[1]s">%[3]s</a>
|
||||
compare_2_commits=Просмотреть сравнение двух коммитов
|
||||
|
||||
[tool]
|
||||
ago=назад
|
||||
from_now=from now
|
||||
from_now=с этого момента
|
||||
now=сейчас
|
||||
1s=1 second %s
|
||||
1s=1 секунду %s
|
||||
1m=1 минута %s
|
||||
1h=1 час %s
|
||||
1d=1 день %s
|
||||
1w=1 неделя %s
|
||||
1mon=1 month %s
|
||||
1mon=1 месяц %s
|
||||
1y=1 год %s
|
||||
seconds=%d секунд %s
|
||||
minutes=%d минут %s
|
||||
hours=%d часов %s
|
||||
days=%d дней %s
|
||||
weeks=%d weeks %s
|
||||
months=%d months %s
|
||||
years=%d years %s
|
||||
weeks=недель %s: %d
|
||||
months=месяцев %s: %d
|
||||
years=лет %s: %d
|
||||
raw_seconds=секунд
|
||||
raw_minutes=минут
|
||||
|
||||
[dropzone]
|
||||
default_message=Drop files here or click to upload.
|
||||
invalid_input_type=You can't upload files of this type.
|
||||
file_too_big=File size({{filesize}} MB) exceeds maximum size({{maxFilesize}} MB).
|
||||
remove_file=Remove file
|
||||
default_message=Перетащите файл сюда, или кликните для загрузки.
|
||||
invalid_input_type=Вы не можете загружать файлы этого типа.
|
||||
file_too_big=Размер файла ({{filesize}} МБ) больше чем максимальный размер ({{maxFilesize}} МБ).
|
||||
remove_file=Удалить файл
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=控制面板
|
||||
explore=探索
|
||||
help=帮助
|
||||
sign_in=登录
|
||||
social_sign_in=社交帐号登录:第 2 步 <small>关联帐号</small>
|
||||
sign_out=退出
|
||||
sign_up=注册
|
||||
register=注册
|
||||
@@ -14,7 +13,7 @@ version=当前版本
|
||||
page=页面
|
||||
template=模板
|
||||
language=语言选项
|
||||
create_new=创建新的...
|
||||
create_new=创建...
|
||||
user_profile_and_more=用户信息及更多
|
||||
signed_in_as=已登录用户
|
||||
|
||||
@@ -54,7 +53,8 @@ code=代码
|
||||
[install]
|
||||
install=安装页面
|
||||
title=首次运行安装程序
|
||||
requite_db_desc=Gogs 允许后端数据库为 MySQL、PostgreSQL 或 SQLite3。
|
||||
docker_helper=如果您正在使用 Docker 容器运行 Gogs,请务必先仔细阅读 <a target="_blank" href="%s">官方文档</a> 后再对本页面进行填写。
|
||||
requite_db_desc=Gogs 要求安装 MySQL、PostgreSQL、SQLite3 或 TiDB。
|
||||
db_title=数据库设置
|
||||
db_type=数据库类型
|
||||
host=数据库主机
|
||||
@@ -64,8 +64,11 @@ db_name=数据库名称
|
||||
db_helper=如果您使用 MySQL,请使用 INNODB 引擎以及 utf8_general_ci 字符集。
|
||||
ssl_mode=SSL 模式
|
||||
path=数据库文件路径
|
||||
sqlite_helper=SQLite3 数据库的文件路径。
|
||||
err_empty_sqlite_path=SQLite 数据库文件路径不能为空。
|
||||
sqlite_helper=SQLite3 或 TiDB 的数据库路径。
|
||||
err_empty_db_path=SQLite3 或 TiDB 的数据库路径不能为空。
|
||||
err_invalid_tidb_name=TiDB 数据库名称不允许包含字符 "." 或 "-" 。
|
||||
no_admin_and_disable_registration=您不能够在未创建管理员用户的情况下禁止注册。
|
||||
err_empty_admin_password=管理员密码不能为空。
|
||||
|
||||
general_title=应用基本设置
|
||||
app_name=应用名称
|
||||
@@ -99,6 +102,8 @@ disable_gravatar=禁用 Gravatar 服务
|
||||
disable_gravatar_popup=禁用 Gravatar 和自定义源,仅使用由用户上传的或默认的头像。
|
||||
disable_registration=禁止用户自主注册
|
||||
disable_registration_popup=禁止用户自行注册功能,只有管理员可以添加帐号。
|
||||
enable_captcha=启用验证码服务
|
||||
enable_captcha_popup=要求在用户注册时输入预验证码
|
||||
require_sign_in_view=启用登录访问限制
|
||||
require_sign_in_view_popup=只有已登录的用户才能够访问页面,否则将只能看到登录或注册页面。
|
||||
admin_setting_desc=创建管理员帐号并不是必须的,因为 ID=1 的用户将自动获得管理员权限。
|
||||
@@ -143,7 +148,6 @@ forgot_password=忘记密码
|
||||
forget_password=忘记密码?
|
||||
sign_up_now=还没帐户?马上注册。
|
||||
confirmation_mail_sent_prompt=一封新的确认邮件已经被发送至 <b>%s</b>,请检查您的收件箱并在 %d 小时内完成确认注册操作。
|
||||
sign_in_email=登录到您的邮箱
|
||||
active_your_account=激活您的帐户
|
||||
resent_limit_prompt=对不起,您请求发送激活邮件过于频繁,请等待 3 分钟后再试!
|
||||
has_unconfirmed_mail=%s 您好,系统检测到您有一封发送至 <b>%s</b> 但未被确认的邮件。如果您未收到激活邮件,或需要重新发送,请单击下方的按钮。
|
||||
@@ -155,6 +159,12 @@ invalid_code=对不起,您的确认代码已过期或已失效。
|
||||
reset_password_helper=单击此处重置密码
|
||||
password_too_short=密码长度不能少于 6 位!
|
||||
|
||||
[mail]
|
||||
activate_account=请激活您的帐户
|
||||
activate_email=请验证您的邮箱地址
|
||||
reset_password=重置您的密码
|
||||
register_success=注册成功,欢迎使用
|
||||
|
||||
[modal]
|
||||
yes=确认操作
|
||||
no=取消操作
|
||||
@@ -181,6 +191,7 @@ min_size_error=长度最小为 %s 个字符。
|
||||
max_size_error=长度最大为 %s 个字符。
|
||||
email_error=不是一个有效的邮箱地址。
|
||||
url_error=不是一个有效的 URL。
|
||||
include_error=必须包含子字符串 '%s'。
|
||||
unknown_error=未知错误:
|
||||
captcha_incorrect=验证码未匹配。
|
||||
password_not_match=密码与确认密码未匹配。
|
||||
@@ -241,7 +252,7 @@ location=所在地区
|
||||
update_profile=更新信息
|
||||
update_profile_success=您的个人信息更新成功!
|
||||
change_username=用户名将被修改
|
||||
change_username_desc=用户名被修改,您确定要继续操作吗?这将会影响到所有与您帐户有关的链接。
|
||||
change_username_prompt=该操作将会影响到所有与您帐户有关的链接
|
||||
continue=继续操作
|
||||
cancel=取消操作
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=您的头像设置更新成功!
|
||||
change_password=修改密码
|
||||
old_password=当前密码
|
||||
new_password=新的密码
|
||||
retype_new_password=重新输入新的密码
|
||||
password_incorrect=当前密码不正确!
|
||||
change_password_success=密码修改成功!您现在可以使用新的密码登录。
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=您的主要邮箱地址将被用于通知提醒和其它操作。
|
||||
primary=主要
|
||||
primary_email=设为主要
|
||||
delete_email=删除
|
||||
email_deletion=邮箱删除操作
|
||||
email_deletion_desc=删除该邮箱地址将会移除所有相关的信息。是否继续?
|
||||
email_deletion_success=邮箱删除成功!
|
||||
add_new_email=添加新的邮箱地址
|
||||
add_email=添加邮箱
|
||||
add_email_confirmation_sent=一封待确认的电子邮件已发送到 <b>%s</b>,请在 %d 小时内检查您的收件箱,并完成确认过程。
|
||||
add_email_confirmation_sent=一封待确认的电子邮件已发送到 '%s',请在 %d 小时内检查您的收件箱,并完成确认过程。
|
||||
add_email_success=新的邮箱地址添加成功!
|
||||
|
||||
manage_ssh_keys=管理 SSH 密钥
|
||||
@@ -281,7 +296,7 @@ key_name=密钥名称
|
||||
key_content=密钥内容
|
||||
add_key_success=新的 SSH 密钥 '%s' 添加成功!
|
||||
delete_key=删除
|
||||
ssh_key_deletion=删除 SSH 公钥
|
||||
ssh_key_deletion=删除 SSH 公钥操作
|
||||
ssh_key_deletion_desc=删除该 SSH 公钥将删除所有与您帐户相关的访问权限。是否继续?
|
||||
ssh_key_deletion_success=SSH 公钥删除成功!
|
||||
add_on=增加于
|
||||
@@ -303,7 +318,7 @@ token_name=令牌名称
|
||||
generate_token=生成令牌
|
||||
generate_token_succees=新的操作令牌生成成功!您必须立即复制到一个安全的地方,因为该令牌只会显示一次!
|
||||
delete_token=删除令牌
|
||||
access_token_deletion=删除个人操作令牌
|
||||
access_token_deletion=删除个人操作令牌操作
|
||||
access_token_deletion_desc=删除该个人操作令牌将删除所有相关的应用程序的访问权限。是否继续?
|
||||
delete_token_success=个人操作令牌删除成功!请更新与该令牌有关的所有应用。
|
||||
|
||||
@@ -319,6 +334,7 @@ repo_name=仓库名称
|
||||
repo_name_helper=伟大的仓库名称一般都较短、令人深刻并且 <strong>独一无二</strong> 的。
|
||||
visibility=可见性
|
||||
visiblity_helper=该仓库为 <span class="ui red text">私有的</span>
|
||||
visiblity_helper_forced=网站管理员已强制要求所有新建仓库必须为 <span class="ui red text">私有的</span>
|
||||
visiblity_fork_helper=(修改该值将会影响到所有派生仓库)
|
||||
fork_repo=派生仓库
|
||||
fork_from=派生自
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=该仓库将是一个 <span class="text blue">镜像</span>
|
||||
migrate_repo=迁移仓库
|
||||
migrate.clone_address=克隆地址
|
||||
migrate.clone_address_desc=该地址可以是 HTTP/HTTPS/GIT URL 或本地服务器路径。
|
||||
migrate.permission_denied=您没有获得导入本地仓库的权限。
|
||||
migrate.invalid_local_path=无效的本地路径,不存在或不是一个目录!
|
||||
|
||||
forked_from=派生自
|
||||
fork_from_self=无法派生已经拥有的仓库!
|
||||
copy_link=复制链接
|
||||
copy_link_success=复制成功!
|
||||
copy_link_error=请按下 ⌘-C 或 Ctrl-C 复制
|
||||
click_to_copy=复制到剪切板
|
||||
copied=复制成功
|
||||
clone_helper=不知道如何操作?访问 <a target="_blank" href="%s">此处</a> 查看帮助!
|
||||
@@ -363,6 +382,8 @@ quick_guide=快速帮助
|
||||
clone_this_repo=克隆当前仓库
|
||||
create_new_repo_command=从命令行创建一个新的仓库
|
||||
push_exist_repo=从命令行推送已经创建的仓库
|
||||
repo_is_empty=该仓库不包含任何内容,请稍后再进行访问!
|
||||
|
||||
|
||||
branch=分支
|
||||
tree=目录树
|
||||
@@ -435,9 +456,9 @@ issues.num_comments=%d 条评论
|
||||
issues.commented_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 评论`
|
||||
issues.no_content=这个人很懒,什么都没留下。
|
||||
issues.close_issue=关闭
|
||||
issues.close_comment_issue=关闭并评论
|
||||
issues.close_comment_issue=评论并关闭
|
||||
issues.reopen_issue=重新开启
|
||||
issues.reopen_comment_issue=重新开启并评论
|
||||
issues.reopen_comment_issue=评论并重新开启
|
||||
issues.create_comment=评论
|
||||
issues.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闭`
|
||||
issues.reopened_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 重新开启`
|
||||
@@ -457,7 +478,7 @@ issues.label_open_issues=%d 个开启的工单
|
||||
issues.label_edit=编辑
|
||||
issues.label_delete=删除
|
||||
issues.label_modify=修改标签
|
||||
issues.label_deletion=删除标签
|
||||
issues.label_deletion=删除标签操作
|
||||
issues.label_deletion_desc=删除该标签将会移除所有工单中相关的信息。是否继续?
|
||||
issues.label_deletion_success=标签删除成功!
|
||||
|
||||
@@ -479,10 +500,12 @@ pulls.reopen_to_merge=请重新开启合并请求来完成合并操作。
|
||||
pulls.merged=已合并
|
||||
pulls.has_merged=该合并请求已经成功合并!
|
||||
pulls.data_broken=该合并请求的数据由于派生仓库的相关信息被删除而被破坏。
|
||||
pulls.is_checking=该合并请求正在进行冲突检查,请稍后再刷新页面。
|
||||
pulls.can_auto_merge_desc=您可以实现该合并请求的自动合并操作。
|
||||
pulls.cannot_auto_merge_desc=因为代码提交存在冲突,您无法对该合并请求执行自动合并操作。
|
||||
pulls.cannot_auto_merge_helper=请使用命令行工具来解决冲突。
|
||||
pulls.merge_pull_request=合并请求
|
||||
pulls.open_unmerged_pull_exists=`由于已经存在来自相同仓库和合并信息的未合并请求(#%d),您无法执行重新开启操作。`
|
||||
|
||||
milestones.new=新的里程碑
|
||||
milestones.open_tab=%d 开启中
|
||||
@@ -497,14 +520,14 @@ milestones.title=标题
|
||||
milestones.desc=描述
|
||||
milestones.due_date=截止日期(可选)
|
||||
milestones.clear=清除
|
||||
milestones.invalid_due_date_format=截止日期的格式错误,必须是 'year-mm-dd' 的形式。
|
||||
milestones.invalid_due_date_format=截止日期的格式错误,必须是 'yyyy-mm-dd' 的形式。
|
||||
milestones.create_success=里程碑 '%s' 创建成功!
|
||||
milestones.edit=编辑里程碑
|
||||
milestones.edit_subheader=使用更加清晰的描述来帮助人们更好地理解里程碑的作用。
|
||||
milestones.cancel=取消
|
||||
milestones.modify=修改里程碑
|
||||
milestones.edit_success=里程碑 '%s' 的修改内容已经生效!
|
||||
milestones.deletion=删除里程碑
|
||||
milestones.deletion=删除里程碑操作
|
||||
milestones.deletion_desc=删除该里程碑将会移除所有工单中相关的信息。是否继续?
|
||||
milestones.deletion_success=里程碑删除成功!
|
||||
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=已经存在使用相同标签进行发布的版
|
||||
|
||||
[org]
|
||||
org_name_holder=组织名称
|
||||
org_full_name_holder=组织全名
|
||||
org_name_helper=伟大的组织都有一个简短而寓意深刻的名字。
|
||||
org_email_helper=组织的邮箱用于接收所有通知和确认邮件。
|
||||
create_org=创建组织
|
||||
repo_updated=最后更新于
|
||||
people=组织成员
|
||||
@@ -655,9 +678,9 @@ settings.full_name=组织全名
|
||||
settings.website=官方网站
|
||||
settings.location=所在地区
|
||||
settings.update_settings=更新组织设置
|
||||
settings.change_orgname=组织名称将被修改
|
||||
settings.change_orgname_desc=组织名称被修改,您确定要继续操作吗?这将会影响到所有与该组织有关的链接。
|
||||
settings.update_setting_success=组织设置更新成功!
|
||||
settings.change_orgname_prompt=该操作将会影响到所有与该组织有关的链接
|
||||
settings.update_avatar_success=组织头像更新成功!
|
||||
settings.delete=删除组织
|
||||
settings.delete_account=删除当前组织
|
||||
settings.delete_prompt=删除操作会永久清除该组织的信息,并且 <strong>不可恢复</strong>!
|
||||
@@ -713,8 +736,9 @@ authentication=授权认证管理
|
||||
config=应用配置管理
|
||||
notices=系统提示管理
|
||||
monitor=应用监控面板
|
||||
prev=上一页
|
||||
next=下一页
|
||||
first_page=首页
|
||||
last_page=末页
|
||||
total=总计:%d
|
||||
|
||||
dashboard.statistic=应用统计数据
|
||||
dashboard.operations=管理员操作
|
||||
@@ -773,19 +797,24 @@ users.activated=已激活
|
||||
users.admin=管理员
|
||||
users.repos=仓库数
|
||||
users.created=创建时间
|
||||
users.send_register_notify=向用户发送注册通知邮件
|
||||
users.new_success=新的用户 '%s' 创建成功!
|
||||
users.edit=编辑
|
||||
users.auth_source=认证源
|
||||
users.local=本地
|
||||
users.auth_login_name=认证登录名
|
||||
users.auth_login_name=认证登录名称
|
||||
users.password_helper=将值留空使其保持不变。
|
||||
users.update_profile_success=该用户信息更新成功!
|
||||
users.edit_account=编辑用户信息
|
||||
users.is_activated=该用户已被激活
|
||||
users.is_admin=该用户具有管理员权限
|
||||
users.allow_git_hook=该帐户具有创建 Git 钩子的权限
|
||||
users.allow_git_hook=该用户具有创建 Git 钩子的权限
|
||||
users.allow_import_local=该用户具有导入本地仓库的权限
|
||||
users.update_profile=更新用户信息
|
||||
users.delete_account=删除该用户
|
||||
users.still_own_repo=该帐户仍然是某些仓库的拥有者,您必须先转移或删除它们才能执行删除帐户操作!
|
||||
users.still_has_org=该帐户仍旧是某些组织的成员,您必须先使其离开或删除组织。
|
||||
users.deletion_success=用户删除成功!
|
||||
|
||||
orgs.org_manage_panel=组织管理面板
|
||||
orgs.name=组织名称
|
||||
@@ -800,41 +829,47 @@ repos.watches=关注数
|
||||
repos.stars=点赞数
|
||||
repos.issues=工单数
|
||||
|
||||
auths.auth_manage_panel=授权认证管理面板
|
||||
auths.new=添加新的认证源
|
||||
auths.auth_manage_panel=认证管理面板
|
||||
auths.new=添加新的源
|
||||
auths.name=认证名称
|
||||
auths.type=认证类型
|
||||
auths.enabled=已启用
|
||||
auths.updated=最后更新时间
|
||||
auths.auth_type=授权类型
|
||||
auths.auth_name=授权名称
|
||||
auths.auth_type=认证类型
|
||||
auths.auth_name=认证名称
|
||||
auths.domain=域名
|
||||
auths.host=主机地址
|
||||
auths.port=主机端口
|
||||
auths.bind_dn=绑定 DN
|
||||
auths.bind_password=绑定密码
|
||||
auths.bind_password_helper=警告:该密码将会以明文的形式保存在数据库中。请不要使用拥有高权限的帐户!
|
||||
auths.user_base=用户搜索基准
|
||||
auths.user_dn=User DN
|
||||
auths.attribute_name=名字属性
|
||||
auths.attribute_surname=姓氏属性
|
||||
auths.attribute_mail=邮箱属性
|
||||
auths.filter=用户过滤规则
|
||||
auths.admin_filter=管理员过滤规则
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=SMTP 授权类型
|
||||
auths.smtp_auth=SMTP 认证类型
|
||||
auths.smtphost=SMTP 主机地址
|
||||
auths.smtpport=SMTP 主机端口
|
||||
auths.allowed_domains=域名白名单
|
||||
auths.allowed_domains_helper=将值留空表示不对域名做任何限制。多个域名之间需要使用逗号 ',' 分隔。
|
||||
auths.enable_tls=启用 TLS 加密
|
||||
auths.skip_tls_verify=忽略 TLS 验证
|
||||
auths.pam_service_name=PAM 服务名称
|
||||
auths.enable_auto_register=允许授权用户自动注册
|
||||
auths.tips=帮助提示
|
||||
auths.edit=修改授权认证设置
|
||||
auths.edit=编辑认证设置
|
||||
auths.activated=该授权认证已经启用
|
||||
auths.update_success=授权认证设置更新成功!
|
||||
auths.update=更新授权认证信息
|
||||
auths.delete=删除该授权认证
|
||||
auths.delete_auth_title=授权认证删除操作
|
||||
auths.delete_auth_desc=该授权认证将被删除,您确定要继续吗?
|
||||
auths.new_success=新的授权源 "%s" 添加成功!
|
||||
auths.update_success=认证设置更新成功!
|
||||
auths.update=更新认证设置
|
||||
auths.delete=删除该认证
|
||||
auths.delete_auth_title=删除认证操作
|
||||
auths.delete_auth_desc=该认证将被删除。是否继续?
|
||||
auths.deletion_success=授权源删除成功!
|
||||
|
||||
config.server_config=服务器配置
|
||||
config.app_name=应用名称
|
||||
@@ -858,14 +893,16 @@ config.db_user=连接用户
|
||||
config.db_ssl_mode=SSL 模式
|
||||
config.db_ssl_mode_helper=(仅限 "postgres" 使用)
|
||||
config.db_path=数据库路径
|
||||
config.db_path_helper=(仅限 "sqlite3" 使用)
|
||||
config.db_path_helper=(用于 "sqlite3" 和 "tidb")
|
||||
config.service_config=服务配置
|
||||
config.register_email_confirm=注册邮件确认
|
||||
config.disable_register=关闭注册功能
|
||||
config.show_registration_button=显示注册按钮
|
||||
config.require_sign_in_view=强制登录浏览
|
||||
config.mail_notify=邮件通知提醒
|
||||
config.enable_cache_avatar=开启缓存头像
|
||||
config.mail_notify=邮件通知提醒
|
||||
config.disable_key_size_check=禁用密钥最小长度检查
|
||||
config.enable_captcha=启用验证码服务
|
||||
config.active_code_lives=激活用户链接有效期
|
||||
config.reset_password_code_lives=重置密码链接有效期
|
||||
config.webhook_config=Web 钩子配置
|
||||
|
||||
@@ -5,7 +5,6 @@ dashboard=控制面版
|
||||
explore=探索
|
||||
help=幫助
|
||||
sign_in=登錄
|
||||
social_sign_in=社交帳號登錄:第 2 步 <small>關聯帳號</small>
|
||||
sign_out=退出
|
||||
sign_up=註冊
|
||||
register=註冊
|
||||
@@ -14,7 +13,7 @@ version=當前版本
|
||||
page=頁面
|
||||
template=模版
|
||||
language=語言選項
|
||||
create_new=創建新的...
|
||||
create_new=Create...
|
||||
user_profile_and_more=用戶信息及更多
|
||||
signed_in_as=已登錄用戶
|
||||
|
||||
@@ -54,7 +53,8 @@ code=程式碼
|
||||
[install]
|
||||
install=安裝頁面
|
||||
title=首次執行安裝程序
|
||||
requite_db_desc=Gogs 允許後端數據庫為 MySQL、PostgreSQL 或 SQLite3,但是 SQLite3 一般只有官方二進制發行版才支持。
|
||||
docker_helper=If you're running Gogs inside Docker, please read <a target="_blank" href="%s">Guidelines</a> carefully before you change anything in this page!
|
||||
requite_db_desc=Gogs requires MySQL, PostgreSQL, SQLite3 or TiDB.
|
||||
db_title=數據庫設置
|
||||
db_type=數據庫類型
|
||||
host=數據庫主機
|
||||
@@ -64,8 +64,11 @@ db_name=數據庫名稱
|
||||
db_helper=如果您使用 MySQL,請使用 INNODB 引擎以及 utf8_general_ci 字符集。
|
||||
ssl_mode=SSL 模式
|
||||
path=數據庫文件路徑
|
||||
sqlite_helper=SQLite3 數據庫的文件路徑。
|
||||
err_empty_sqlite_path=SQLite 數據庫文件路徑不能為空。
|
||||
sqlite_helper=The file path of SQLite3 or TiDB database.
|
||||
err_empty_db_path=SQLite3 or TiDB database path cannot be empty.
|
||||
err_invalid_tidb_name=TiDB database name does not allow characters "." and "-".
|
||||
no_admin_and_disable_registration=You cannot disable registration without creating an admin account.
|
||||
err_empty_admin_password=Admin password cannot be empty.
|
||||
|
||||
general_title=應用基本設置
|
||||
app_name=應用名稱
|
||||
@@ -99,6 +102,8 @@ disable_gravatar=禁用 Gravatar 服務
|
||||
disable_gravatar_popup=禁用 Gravatar 和自定義源,僅使用由用戶上傳或默認的頭像。
|
||||
disable_registration=禁止用戶自主註冊
|
||||
disable_registration_popup=禁止用戶自主註冊功能,只有管理員可以添加帳號。
|
||||
enable_captcha=Enable Captcha
|
||||
enable_captcha_popup=Require validate captcha for user self-registration.
|
||||
require_sign_in_view=啓用登錄訪問限制
|
||||
require_sign_in_view_popup=只有已登錄的用戶才能夠訪問頁面,否則將只能看到登錄或註冊頁面。
|
||||
admin_setting_desc=創建管理員帳號並不是必須的,因為 ID=1 的用戶將自動獲得管理員權限。
|
||||
@@ -106,7 +111,7 @@ admin_title=管理員帳號設置
|
||||
admin_name=管理員用戶名
|
||||
admin_password=管理員密碼
|
||||
confirm_password=確認密碼
|
||||
admin_email=管理員郵箱
|
||||
admin_email=Admin E-mail
|
||||
install_gogs=立即安裝
|
||||
test_git_failed=無法識別 'git' 命令:%v
|
||||
sqlite3_not_available=您所使用的發行版本不支持 SQLite3,請從 %s 下載官方構建版,而不是 gobuild 版本。
|
||||
@@ -143,7 +148,6 @@ forgot_password=忘記密碼
|
||||
forget_password=忘記密碼?
|
||||
sign_up_now=還沒帳戶?馬上註冊。
|
||||
confirmation_mail_sent_prompt=一封新的確認郵件已經被發送至 <b>%s</b>,請檢查您的收件箱並在 %d 小時內完成確認註冊操作。
|
||||
sign_in_email=登錄到您的郵箱
|
||||
active_your_account=激活您的帳戶
|
||||
resent_limit_prompt=對不起,您請求發送激活郵件過於頻繁,請等待 3 分鐘後再試!
|
||||
has_unconfirmed_mail=%s 您好,您有一封發送至( <b>%s</b>) 但未被確認的郵件。如果您未收到激活郵件,或需要重新發送,請單擊下方的按鈕。
|
||||
@@ -155,6 +159,12 @@ invalid_code=對不起,您的確認代碼已過期或已失效。
|
||||
reset_password_helper=單擊此處重置密碼
|
||||
password_too_short=密碼長度不能少於 6 位!
|
||||
|
||||
[mail]
|
||||
activate_account=Please activate your account
|
||||
activate_email=Verify your e-mail address
|
||||
reset_password=Reset your password
|
||||
register_success=Register success, Welcome
|
||||
|
||||
[modal]
|
||||
yes=確認操作
|
||||
no=取消操作
|
||||
@@ -181,6 +191,7 @@ min_size_error=長度最小為 %s 個字符。
|
||||
max_size_error=長度最大為 %s 個字符。
|
||||
email_error=不是一個有效的郵箱地址。
|
||||
url_error=不是一個有效的 URL。
|
||||
include_error=` must contain substring '%s'.`
|
||||
unknown_error=未知錯誤:
|
||||
captcha_incorrect=驗證碼未匹配。
|
||||
password_not_match=密碼與確認密碼未匹配。
|
||||
@@ -241,7 +252,7 @@ location=所在地區
|
||||
update_profile=更新信息
|
||||
update_profile_success=您的個人信息更新成功!
|
||||
change_username=用戶名將被修改
|
||||
change_username_desc=用戶名被修改,您確定要繼續操作嗎?這將會影響到所有與您帳戶有關的連結。
|
||||
change_username_prompt=This change will affect the way how links relate to your account.
|
||||
continue=繼續操作
|
||||
cancel=取消操作
|
||||
|
||||
@@ -256,6 +267,7 @@ update_avatar_success=您的頭像設置更新成功!
|
||||
change_password=修改密碼
|
||||
old_password=當前密碼
|
||||
new_password=新的密碼
|
||||
retype_new_password=Retype New Password
|
||||
password_incorrect=當前密碼不正確!
|
||||
change_password_success=密碼修改成功!您現在可以使用新的密碼登錄。
|
||||
|
||||
@@ -265,9 +277,12 @@ email_desc=您的主要邮箱地址将被用于通知提醒和其它操作。
|
||||
primary=主要
|
||||
primary_email=设为主要
|
||||
delete_email=刪除
|
||||
email_deletion=E-mail Deletion
|
||||
email_deletion_desc=Delete this e-mail address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success=E-mail has been deleted successfully!
|
||||
add_new_email=添加新的電子郵件地址
|
||||
add_email=添加電子郵件
|
||||
add_email_confirmation_sent=一封待確認的電子郵件已發送到<b>%s</b>,請在%d 小時內檢查您的收件箱,並完成確認過程。
|
||||
add_email_confirmation_sent=一封待確認的電子郵件已發送到 '%s',請在%d 小時內檢查您的收件箱,並完成確認過程。
|
||||
add_email_success=新的邮箱地址添加成功。
|
||||
|
||||
manage_ssh_keys=管理 SSH 密鑰
|
||||
@@ -319,6 +334,7 @@ repo_name=倉庫名稱
|
||||
repo_name_helper=偉大的倉庫名稱一般都較短、令人深刻並且 <strong>獨一無二</strong> 的。
|
||||
visibility=可見度
|
||||
visiblity_helper=該倉庫為 <span class="ui red text">私有的</span>
|
||||
visiblity_helper_forced=Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper=(修改該值將會影響到所有派生倉庫)
|
||||
fork_repo=派生倉庫
|
||||
fork_from=派生自
|
||||
@@ -330,7 +346,7 @@ license=授權許可
|
||||
license_helper=請選擇授權許可文件
|
||||
readme=Readme
|
||||
readme_helper=Select a readme template
|
||||
auto_init=Initialize this repository selected files and template
|
||||
auto_init=Initialize this repository with selected files and template
|
||||
create_repo=創建倉庫
|
||||
default_branch=默認分支
|
||||
mirror_interval=鏡像同步周期(小時)
|
||||
@@ -344,11 +360,14 @@ migrate_type_helper=該倉庫將是一個 <span class="text blue">鏡像</span>
|
||||
migrate_repo=遷移倉庫
|
||||
migrate.clone_address=複製地址
|
||||
migrate.clone_address_desc=該地址可以是 HTTP/HTTPS/GIT URL 或本地服務器路徑。
|
||||
migrate.permission_denied=You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path=無效的本地路徑,該路徑不存在或不是一個目錄!
|
||||
|
||||
forked_from=派生自
|
||||
fork_from_self=無法派生已經擁有的倉庫!
|
||||
copy_link=複製連結
|
||||
copy_link_success=Copied!
|
||||
copy_link_error=Press ⌘-C or Ctrl-C to copy
|
||||
click_to_copy=複製到剪切簿
|
||||
copied=複製成功
|
||||
clone_helper=不知道如何操作?訪問 <a target="_blank"href="%s"> 帮助説明</a> !
|
||||
@@ -363,6 +382,8 @@ quick_guide=快速幫助
|
||||
clone_this_repo=複製當前倉庫
|
||||
create_new_repo_command=從命令行創建一個新的倉庫
|
||||
push_exist_repo=從命令行推送已經創建的倉庫
|
||||
repo_is_empty=This repository is empty, please come back later!
|
||||
|
||||
|
||||
branch=分支
|
||||
tree=目錄樹
|
||||
@@ -479,10 +500,12 @@ pulls.reopen_to_merge=Please reopen this pull request to perform merge operation
|
||||
pulls.merged=Merged
|
||||
pulls.has_merged=This pull request has been merged successfully!
|
||||
pulls.data_broken=Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking=The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc=You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc=You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper=Please use command line tool to solve it.
|
||||
pulls.merge_pull_request=Merge Pull Request
|
||||
pulls.open_unmerged_pull_exists=`You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new=新的里程碑
|
||||
milestones.open_tab=%d 開啟中
|
||||
@@ -497,7 +520,7 @@ milestones.title=標題
|
||||
milestones.desc=描述
|
||||
milestones.due_date=截止日期(可選)
|
||||
milestones.clear=清除
|
||||
milestones.invalid_due_date_format=截止日期的格式錯誤,必須是 'year-mm-dd' 的形式。
|
||||
milestones.invalid_due_date_format=截止日期的格式錯誤,必須是 'yyyy-mm-dd' 的形式。
|
||||
milestones.create_success=里程碑 '%s' 創建成功!
|
||||
milestones.edit=編輯里程碑
|
||||
milestones.edit_subheader=使用更加清晰的描述來幫助人們更好地理解里程碑的作用。
|
||||
@@ -629,8 +652,8 @@ release.tag_name_already_exist=已經存在使用相同標籤的發佈版本。
|
||||
|
||||
[org]
|
||||
org_name_holder=組織名稱
|
||||
org_full_name_holder=Organization Full Name
|
||||
org_name_helper=偉大的組織都有一個簡短而寓意深刻的名字。
|
||||
org_email_helper=組織的郵箱用於接收所有通知和確認郵件。
|
||||
create_org=創建組織
|
||||
repo_updated=最後更新於
|
||||
people=組織成員
|
||||
@@ -655,9 +678,9 @@ settings.full_name=組織全名
|
||||
settings.website=官方網站
|
||||
settings.location=所在地區
|
||||
settings.update_settings=更新組織設置
|
||||
settings.change_orgname=組織名稱將被修改
|
||||
settings.change_orgname_desc=組織名稱被修改,您確定要繼續操作嗎?這將會影響到所有與該組織有關的連結。
|
||||
settings.update_setting_success=組織設置更新成功!
|
||||
settings.change_orgname_prompt=This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success=Organization avatar setting has been updated successfully.
|
||||
settings.delete=刪除組織
|
||||
settings.delete_account=刪除當前組織
|
||||
settings.delete_prompt=刪除操作會永久清除該組織的信息,並且 <strong>不可恢復</strong>!
|
||||
@@ -713,8 +736,9 @@ authentication=授權認證管理
|
||||
config=應用配置管理
|
||||
notices=系統提示管理
|
||||
monitor=應用監控面版
|
||||
prev=上一頁
|
||||
next=下一頁
|
||||
first_page=First
|
||||
last_page=Last
|
||||
total=Total: %d
|
||||
|
||||
dashboard.statistic=應用統計數據
|
||||
dashboard.operations=管理員操作
|
||||
@@ -773,19 +797,24 @@ users.activated=已激活
|
||||
users.admin=管理員
|
||||
users.repos=倉庫數
|
||||
users.created=創建時間
|
||||
users.send_register_notify=Send Registration Notification To User
|
||||
users.new_success=New account '%s' has been created successfully.
|
||||
users.edit=編輯
|
||||
users.auth_source=認證源
|
||||
users.auth_source=Authentication Source
|
||||
users.local=本地
|
||||
users.auth_login_name=認證登錄名
|
||||
users.auth_login_name=Authentication Login Name
|
||||
users.password_helper=Leave it empty to remain unchanged.
|
||||
users.update_profile_success=該用戶信息更新成功!
|
||||
users.edit_account=編輯用戶信息
|
||||
users.is_activated=該用戶已被激活
|
||||
users.is_admin=該用戶具有管理員權限
|
||||
users.allow_git_hook=該帳戶具有創建 Git 鉤子的權限
|
||||
users.allow_import_local=This account has permissions to import local repositories
|
||||
users.update_profile=更新用戶信息
|
||||
users.delete_account=刪除該用戶
|
||||
users.still_own_repo=該帳戶仍然是某些倉庫的擁有者,您必須先轉移或刪除它們才能執行刪除帳戶操作!
|
||||
users.still_has_org=該帳戶仍舊是某些組織的成員,您必須先使其離開或刪除組織。
|
||||
users.deletion_success=Account has been deleted successfully!
|
||||
|
||||
orgs.org_manage_panel=組織管理面版
|
||||
orgs.name=組織名稱
|
||||
@@ -800,41 +829,47 @@ repos.watches=關註數
|
||||
repos.stars=讚好數
|
||||
repos.issues=問題數
|
||||
|
||||
auths.auth_manage_panel=授權認證管理面版
|
||||
auths.new=添加新的認證源
|
||||
auths.auth_manage_panel=Authentication Manage Panel
|
||||
auths.new=Add New Source
|
||||
auths.name=認證名稱
|
||||
auths.type=認證類型
|
||||
auths.enabled=已啟用
|
||||
auths.updated=最後更新時間
|
||||
auths.auth_type=授權類型
|
||||
auths.auth_name=授權名稱
|
||||
auths.auth_type=Authentication Type
|
||||
auths.auth_name=Authentication Name
|
||||
auths.domain=域名
|
||||
auths.host=主機地址
|
||||
auths.port=主機端口
|
||||
auths.bind_dn=綁定DN
|
||||
auths.bind_password=綁定密碼
|
||||
auths.bind_password_helper=Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base=User Search Base
|
||||
auths.user_dn=User DN
|
||||
auths.attribute_name=名子屬性
|
||||
auths.attribute_surname=姓氏屬性
|
||||
auths.attribute_mail=電子郵箱屬性
|
||||
auths.filter=使用者篩選器
|
||||
auths.admin_filter=管理者篩選器
|
||||
auths.ms_ad_sa=Ms Ad SA
|
||||
auths.smtp_auth=SMTP 授權類型
|
||||
auths.smtp_auth=SMTP Authentication Type
|
||||
auths.smtphost=SMTP 主機地址
|
||||
auths.smtpport=SMTP 主機端口
|
||||
auths.allowed_domains=Allowed Domains
|
||||
auths.allowed_domains_helper=Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
|
||||
auths.enable_tls=啟用 TLS 加密
|
||||
auths.skip_tls_verify=Skip TLS Verify
|
||||
auths.pam_service_name=PAM 服務名稱
|
||||
auths.enable_auto_register=允許授權用戶自動註冊
|
||||
auths.tips=幫助提示
|
||||
auths.edit=修改授權認證設置
|
||||
auths.edit=Edit Authentication Setting
|
||||
auths.activated=該授權認證已經啟用
|
||||
auths.update_success=授權認證設置更新成功!
|
||||
auths.update=更新授權認證信息
|
||||
auths.delete=刪除該授權認證
|
||||
auths.delete_auth_title=授權認證刪除操作
|
||||
auths.delete_auth_desc=該授權認證將被刪除,您確定要繼續嗎?
|
||||
auths.new_success=New authentication '%s' has been added successfully.
|
||||
auths.update_success=Authentication setting has been updated successfully.
|
||||
auths.update=Update Authentication Setting
|
||||
auths.delete=Delete This Authentication
|
||||
auths.delete_auth_title=Authentication Deletion
|
||||
auths.delete_auth_desc=This authentication is going to be deleted, do you want to continue?
|
||||
auths.deletion_success=Authentication has been deleted successfully!
|
||||
|
||||
config.server_config=服務器配置
|
||||
config.app_name=應用名稱
|
||||
@@ -858,14 +893,16 @@ config.db_user=數據庫用戶
|
||||
config.db_ssl_mode=SSL 模式
|
||||
config.db_ssl_mode_helper=(僅限 "postgres" 使用)
|
||||
config.db_path=數據庫路徑
|
||||
config.db_path_helper=(僅限 "sqlite3" 使用)
|
||||
config.db_path_helper=(for "sqlite3" and "tidb")
|
||||
config.service_config=服務配置
|
||||
config.register_email_confirm=註冊電子郵件確認
|
||||
config.disable_register=關閉註冊功能
|
||||
config.show_registration_button=顯示註冊按鈕
|
||||
config.require_sign_in_view=強制登錄瀏覽
|
||||
config.mail_notify=郵件通知提醒
|
||||
config.enable_cache_avatar=開啟緩存頭像
|
||||
config.mail_notify=郵件通知提醒
|
||||
config.disable_key_size_check=Disable Minimum Key Size Check
|
||||
config.enable_captcha=Enable Captcha
|
||||
config.active_code_lives=激活用戶連結有效期
|
||||
config.reset_password_code_lives=重置密碼連結有效期
|
||||
config.webhook_config=Web 鉤子配置
|
||||
|
||||
@@ -162,6 +162,17 @@
|
||||
"outputPathIsSetByUser": 0,
|
||||
"processed": 1
|
||||
},
|
||||
"\/public\/img\/gogs-large-resize.png": {
|
||||
"fileType": 32768,
|
||||
"ignore": 0,
|
||||
"ignoreWasSetByUser": 0,
|
||||
"initialSize": 54978,
|
||||
"inputAbbreviatedPath": "\/public\/img\/gogs-large-resize.png",
|
||||
"outputAbbreviatedPath": "\/public\/img\/gogs-large-resize.png",
|
||||
"outputPathIsOutsideProject": 0,
|
||||
"outputPathIsSetByUser": 0,
|
||||
"processed": 0
|
||||
},
|
||||
"\/public\/img\/gogs-lg.png": {
|
||||
"fileType": 32768,
|
||||
"ignore": 0,
|
||||
@@ -244,6 +255,46 @@
|
||||
"strictMath": 0,
|
||||
"strictUnits": 0
|
||||
},
|
||||
"\/public\/less\/_emojify.less": {
|
||||
"allowInsecureImports": 0,
|
||||
"createSourceMap": 0,
|
||||
"disableJavascript": 0,
|
||||
"fileType": 1,
|
||||
"ieCompatibility": 1,
|
||||
"ignore": 1,
|
||||
"ignoreWasSetByUser": 0,
|
||||
"inputAbbreviatedPath": "\/public\/less\/_emojify.less",
|
||||
"outputAbbreviatedPath": "\/public\/css\/_emojify.css",
|
||||
"outputPathIsOutsideProject": 0,
|
||||
"outputPathIsSetByUser": 0,
|
||||
"outputStyle": 0,
|
||||
"relativeURLS": 0,
|
||||
"shouldRunAutoprefixer": 0,
|
||||
"shouldRunBless": 0,
|
||||
"strictImports": 0,
|
||||
"strictMath": 0,
|
||||
"strictUnits": 0
|
||||
},
|
||||
"\/public\/less\/_explore.less": {
|
||||
"allowInsecureImports": 0,
|
||||
"createSourceMap": 0,
|
||||
"disableJavascript": 0,
|
||||
"fileType": 1,
|
||||
"ieCompatibility": 1,
|
||||
"ignore": 1,
|
||||
"ignoreWasSetByUser": 0,
|
||||
"inputAbbreviatedPath": "\/public\/less\/_explore.less",
|
||||
"outputAbbreviatedPath": "\/public\/css\/_explore.css",
|
||||
"outputPathIsOutsideProject": 0,
|
||||
"outputPathIsSetByUser": 0,
|
||||
"outputStyle": 0,
|
||||
"relativeURLS": 0,
|
||||
"shouldRunAutoprefixer": 0,
|
||||
"shouldRunBless": 0,
|
||||
"strictImports": 0,
|
||||
"strictMath": 0,
|
||||
"strictUnits": 0
|
||||
},
|
||||
"\/public\/less\/_form.less": {
|
||||
"allowInsecureImports": 0,
|
||||
"createSourceMap": 0,
|
||||
@@ -1697,7 +1748,7 @@
|
||||
"sassUseLibsass": 0,
|
||||
"shouldRunAutoprefixer": 0,
|
||||
"shouldRunBless": 0,
|
||||
"skippedItemsString": "_cache, logs, \/public\/css, _logs, cache, \/public\/js\/lib, .git, \/public\/js, log, .svn, .hg",
|
||||
"skippedItemsString": "\/public\/js, _logs, \/public\/js\/lib, .hg, _cache, log, logs, cache, .svn, .git, \/public\/img\/emoji, \/public\/css",
|
||||
"slimAutoOutputPathEnabled": 1,
|
||||
"slimAutoOutputPathFilenamePattern": "*.html",
|
||||
"slimAutoOutputPathRelativePath": "",
|
||||
|
||||
@@ -17,12 +17,12 @@ $ mkdir -p /var/gogs
|
||||
$ docker run --name=gogs -p 10022:22 -p 10080:3000 -v /var/gogs:/data gogs/gogs
|
||||
|
||||
# Use `docker start` if you have stopped it.
|
||||
$ docker start gogs
|
||||
$ docker start gogs
|
||||
```
|
||||
|
||||
Files will be store in local path `/var/gogs` in my case.
|
||||
|
||||
Directory `/var/gogs` keeps Git repoistories and Gogs data:
|
||||
Directory `/var/gogs` keeps Git repositories and Gogs data:
|
||||
|
||||
/var/gogs
|
||||
|-- git
|
||||
@@ -35,29 +35,41 @@ Directory `/var/gogs` keeps Git repoistories and Gogs data:
|
||||
|-- log
|
||||
|-- templates
|
||||
|
||||
### Volume with data container
|
||||
|
||||
If you're more comfortable with mounting data to a data container, the commands you execute at the first time will look like as follows:
|
||||
|
||||
```
|
||||
# Create data container
|
||||
docker run --name=gogs-data --entrypoint /bin/true gogs/gogs
|
||||
# Use `docker run` for the first time.
|
||||
docker run --name=gogs --volumes-from gogs-data -p 10022:22 -p 10080:3000 gogs/gogs
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
Most of settings are obvious and easy to understand, but there are some settings can be confusing by running Gogs inside Docker:
|
||||
|
||||
- **Repository Root Path**: keep it as default value `/home/git/gogs-repositories` because `start.sh` already made a symbolic link for you.
|
||||
- **Run User**: keep it as default value `git` because `start.sh` already setup a user with name `git`.
|
||||
- **Domain**: fill in with Docker container IP(e.g. `192.168.99.100`).
|
||||
- **Domain**: fill in with Docker container IP(e.g. `192.168.99.100`). But if you want to access your Gogs instance from a different physical machine, please fill in with the hostname or IP address of the Docker host machine.
|
||||
- **SSH Port**: Use the exposed port from Docker container. For example, your SSH server listens on `22` inside Docker, but you expose it by `10022:22`, then use `10022` for this value.
|
||||
- **HTTP Port**: Use port you want Gogs to listen on inside Docker container. For example, your Gogs listens on `3000` inside Docker, and you expose it by `10080:3000`, but you still use `3000` for this value.
|
||||
- **Application URL**: Use combination of **Domain** and **exposed HTTP Port** values(e.g. `http://192.168.99.100:10080/`).
|
||||
- **Application URL**: Use combination of **Domain** and **exposed HTTP Port** values(e.g. `http://192.168.99.100:10080/`).
|
||||
|
||||
Full documentation of settings can be found [here](http://gogs.io/docs/advanced/configuration_cheat_sheet.html).
|
||||
|
||||
## Troubleshooting
|
||||
## Upgrade
|
||||
|
||||
If you see the following error:
|
||||
:exclamation::exclamation::exclamation:<span style="color: red">**Make sure you have volumed data to somewhere outside Docker container**</span>:exclamation::exclamation::exclamation:
|
||||
|
||||
```
|
||||
checkVersion()] [E] Binary and template file version does not match
|
||||
```
|
||||
Steps to upgrade Gogs with Docker:
|
||||
|
||||
Run `rm -fr /var/gogs/gogs/templates/` should fix this it. Just remember to backup templates file if you have made modifications youself.
|
||||
- `docker pull gogs/gogs`
|
||||
- `docker stop gogs`
|
||||
- `docker rm gogs`
|
||||
- Finally, create container as the first time and don't forget to do same volume and port mapping.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- [Use ctrl+c when clone through SSH makes Docker exit unexpectedly](https://github.com/gogits/gogs/issues/1499)
|
||||
- `.dockerignore` seems to be ignored during Docker Hub Automated build
|
||||
|
||||
25
docker/build.sh
Executable file
25
docker/build.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Set temp environment vars
|
||||
export GOPATH=/tmp/go
|
||||
export PATH=${PATH}:${GOPATH}/bin
|
||||
|
||||
# Install build deps
|
||||
apk -U --no-progress add linux-pam-dev go@community gcc musl-dev
|
||||
|
||||
# Init go environment to build Gogs
|
||||
mkdir -p ${GOPATH}/src/github.com/gogits/
|
||||
ln -s /app/gogs/ ${GOPATH}/src/github.com/gogits/gogs
|
||||
cd ${GOPATH}/src/github.com/gogits/gogs
|
||||
go get -v -tags "sqlite redis memcache cert pam"
|
||||
go build -tags "sqlite redis memcache cert pam"
|
||||
|
||||
# Cleanup GOPATH
|
||||
rm -r $GOPATH
|
||||
|
||||
# Remove build deps
|
||||
apk --no-progress del linux-pam-dev go gcc musl-dev
|
||||
|
||||
# Create git user for Gogs
|
||||
adduser -H -D -g 'Gogs Git User' git -h /data/git -s /bin/bash && passwd -u git
|
||||
echo "export GOGS_CUSTOM=${GOGS_CUSTOM}" >> /etc/profile
|
||||
5
docker/s6/.s6-svscan/finish
Executable file
5
docker/s6/.s6-svscan/finish
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Cleanup SOCAT services and s6 event folder
|
||||
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
|
||||
rm -rf /app/gogs/docker/s6/SOCAT_*
|
||||
8
docker/s6/gogs/run
Executable file
8
docker/s6/gogs/run
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
export USER=git
|
||||
exec gosu $USER /app/gogs/gogs web
|
||||
23
docker/s6/gogs/setup
Executable file
23
docker/s6/gogs/setup
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! test -d ~git/.ssh; then
|
||||
mkdir -p ~git/.ssh
|
||||
chmod 700 ~git/.ssh
|
||||
fi
|
||||
|
||||
if ! test -f ~git/.ssh/environment; then
|
||||
echo "GOGS_CUSTOM=${GOGS_CUSTOM}" > ~git/.ssh/environment
|
||||
chmod 600 ~git/.ssh/environment
|
||||
fi
|
||||
|
||||
cd /app/gogs
|
||||
|
||||
# Link volumed data with app data
|
||||
ln -sf /data/gogs/log ./log
|
||||
ln -sf /data/gogs/data ./data
|
||||
|
||||
# Backward Compatibility with Gogs Container v0.6.15
|
||||
ln -sf /data/git /home/git
|
||||
|
||||
chown -R git:git /data /app/gogs ~git/
|
||||
chmod 0755 /data /data/gogs ~git/
|
||||
7
docker/s6/openssh/run
Executable file
7
docker/s6/openssh/run
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
exec gosu root /usr/sbin/sshd -D -f /app/gogs/docker/sshd_config
|
||||
27
docker/s6/openssh/setup
Executable file
27
docker/s6/openssh/setup
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check if host keys are present, else create them
|
||||
if ! test -f /data/ssh/ssh_host_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_key -N '' -t rsa1
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_rsa_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_rsa_key -N '' -t rsa
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_dsa_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_dsa_key -N '' -t dsa
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_ecdsa_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_ed25519_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ed25519_key -N '' -t ed25519
|
||||
fi
|
||||
|
||||
# Set correct right to ssh keys
|
||||
chown -R root:root /data/ssh/*
|
||||
chmod 0700 /data/ssh
|
||||
chmod 0600 /data/ssh/*
|
||||
7
docker/s6/syslogd/run
Executable file
7
docker/s6/syslogd/run
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
exec gosu root /sbin/syslogd -nS -O-
|
||||
17
docker/sshd_config
Normal file
17
docker/sshd_config
Normal file
@@ -0,0 +1,17 @@
|
||||
Port 22
|
||||
AddressFamily any
|
||||
ListenAddress 0.0.0.0
|
||||
ListenAddress ::
|
||||
Protocol 2
|
||||
LogLevel INFO
|
||||
HostKey /data/ssh/ssh_host_key
|
||||
HostKey /data/ssh/ssh_host_rsa_key
|
||||
HostKey /data/ssh/ssh_host_dsa_key
|
||||
HostKey /data/ssh/ssh_host_ecdsa_key
|
||||
HostKey /data/ssh/ssh_host_ed25519_key
|
||||
PermitRootLogin no
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication no
|
||||
UsePrivilegeSeparation no
|
||||
PermitUserEnvironment yes
|
||||
AllowUsers git
|
||||
@@ -1,47 +1,56 @@
|
||||
#!/bin/bash -
|
||||
#
|
||||
#!/bin/sh
|
||||
|
||||
if ! test -d /data/gogs
|
||||
then
|
||||
mkdir -p /var/run/sshd
|
||||
mkdir -p /data/gogs/data /data/gogs/conf /data/gogs/log /data/git
|
||||
create_socat_links() {
|
||||
# Bind linked docker container to localhost socket using socat
|
||||
USED_PORT="3000:22"
|
||||
while read NAME ADDR PORT; do
|
||||
if test -z "$NAME$ADDR$PORT"; then
|
||||
continue
|
||||
elif echo $USED_PORT | grep -E "(^|:)$PORT($|:)" > /dev/null; then
|
||||
echo "init:socat | Can't bind linked container ${NAME} to localhost, port ${PORT} already in use" 1>&2
|
||||
else
|
||||
SERV_FOLDER=/app/gogs/docker/s6/SOCAT_${NAME}_${PORT}
|
||||
mkdir -p ${SERV_FOLDER}
|
||||
CMD="socat -ls TCP4-LISTEN:${PORT},fork,reuseaddr TCP4:${ADDR}:${PORT}"
|
||||
echo -e "#!/bin/sh\nexec $CMD" > ${SERV_FOLDER}/run
|
||||
chmod +x ${SERV_FOLDER}/run
|
||||
USED_PORT="${USED_PORT}:${PORT}"
|
||||
echo "init:socat | Linked container ${NAME} will be binded to localhost on port ${PORT}" 1>&2
|
||||
fi
|
||||
done << EOT
|
||||
$(env | sed -En 's|(.*)_PORT_([0-9]+)_TCP=tcp://(.*):([0-9]+)|\1 \3 \4|p')
|
||||
EOT
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
# Cleanup SOCAT services and s6 event folder
|
||||
# On start and on shutdown in case container has been killed
|
||||
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
|
||||
rm -rf /app/gogs/docker/s6/SOCAT_*
|
||||
}
|
||||
|
||||
create_volume_subfolder() {
|
||||
# Create VOLUME subfolder
|
||||
for f in /data/gogs/data /data/gogs/conf /data/gogs/log /data/git /data/ssh; do
|
||||
if ! test -d $f; then
|
||||
mkdir -p $f
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cleanup
|
||||
create_volume_subfolder
|
||||
|
||||
LINK=$(echo "$SOCAT_LINK" | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$LINK" = "false" -o "$LINK" = "0" ]; then
|
||||
echo "init:socat | Will not try to create socat links as requested" 1>&2
|
||||
else
|
||||
create_socat_links
|
||||
fi
|
||||
|
||||
if ! test -d /data/ssh
|
||||
then
|
||||
mkdir /data/ssh
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_key -N '' -t rsa1
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_rsa_key -N '' -t rsa
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_dsa_key -N '' -t dsa
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ed25519_key -N '' -t ed25519
|
||||
chown -R root:root /data/ssh/*
|
||||
chmod 600 /data/ssh/*
|
||||
# Exec CMD or S6 by default if nothing present
|
||||
if [ $# -gt 0 ];then
|
||||
exec "$@"
|
||||
else
|
||||
exec /usr/bin/s6-svscan /app/gogs/docker/s6/
|
||||
fi
|
||||
|
||||
service ssh start
|
||||
|
||||
# sync templates
|
||||
test -d /data/gogs/templates || cp -ar ./templates /data/gogs/
|
||||
rsync -rtv /data/gogs/templates/ ./templates/
|
||||
|
||||
ln -sf /data/gogs/log ./log
|
||||
ln -sf /data/gogs/data ./data
|
||||
ln -sf /data/git /home/git
|
||||
|
||||
|
||||
if ! test -d ~git/.ssh
|
||||
then
|
||||
mkdir ~git/.ssh
|
||||
chmod 700 ~git/.ssh
|
||||
fi
|
||||
|
||||
if ! test -f ~git/.ssh/environment
|
||||
then
|
||||
echo "GOGS_CUSTOM=/data/gogs" > ~git/.ssh/environment
|
||||
chown git:git ~git/.ssh/environment
|
||||
chown 600 ~git/.ssh/environment
|
||||
fi
|
||||
|
||||
chown -R git:git /data .
|
||||
exec su git -c "./gogs web"
|
||||
|
||||
6
gogs.go
6
gogs.go
@@ -1,10 +1,10 @@
|
||||
// +build go1.2
|
||||
// +build go1.3
|
||||
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
|
||||
// Gogs (Go Git Service) is a painless self-hosted Git Service.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.6.9.0903 Beta"
|
||||
const APP_VER = "0.7.0.1107 Beta"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
@@ -189,7 +189,10 @@ func issueIndexTrimRight(c rune) bool {
|
||||
|
||||
// updateIssuesCommit checks if issues are manipulated by commit message.
|
||||
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
|
||||
for _, c := range commits {
|
||||
// Commits are appended in the reverse order.
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
c := commits[i]
|
||||
|
||||
refMarked := make(map[int64]bool)
|
||||
for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) {
|
||||
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
|
||||
@@ -210,6 +213,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -220,7 +226,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
|
||||
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
|
||||
if _, err = CreateComment(u, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, message, nil); err != nil {
|
||||
if err = CreateRefComment(u, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -246,6 +252,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -283,6 +292,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -325,6 +337,12 @@ func CommitRepoAction(
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
|
||||
isNewBranch := false
|
||||
opType := COMMIT_REPO
|
||||
// Check it's tag push or branch.
|
||||
@@ -339,17 +357,15 @@ func CommitRepoAction(
|
||||
isNewBranch = true
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
|
||||
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
|
||||
log.Debug("updateIssuesCommit: %v", err)
|
||||
log.Error(4, "updateIssuesCommit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(commit.Commits) > setting.FeedMaxCommitNum {
|
||||
commit.Commits = commit.Commits[:setting.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(commit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Marshal: %v", err)
|
||||
|
||||
@@ -50,11 +50,10 @@ func CountNotices() int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
// GetNotices returns given number of notices with offset.
|
||||
func GetNotices(num, offset int) ([]*Notice, error) {
|
||||
notices := make([]*Notice, 0, num)
|
||||
err := x.Limit(num, offset).Desc("id").Find(¬ices)
|
||||
return notices, err
|
||||
// Notices returns number of notices in given page.
|
||||
func Notices(page, pageSize int) ([]*Notice, error) {
|
||||
notices := make([]*Notice, 0, pageSize)
|
||||
return notices, x.Limit(pageSize, (page-1)*pageSize).Desc("id").Find(¬ices)
|
||||
}
|
||||
|
||||
// DeleteNotice deletes a system notice by given ID.
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
var c = cron.New()
|
||||
|
||||
func NewCronContext() {
|
||||
func NewContext() {
|
||||
var (
|
||||
entry *cron.Entry
|
||||
err error
|
||||
|
||||
@@ -18,7 +18,7 @@ func IsErrNameReserved(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNameReserved) Error() string {
|
||||
return fmt.Sprintf("name is reserved: [name: %s]", err.Name)
|
||||
return fmt.Sprintf("name is reserved [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrNamePatternNotAllowed struct {
|
||||
@@ -31,7 +31,7 @@ func IsErrNamePatternNotAllowed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNamePatternNotAllowed) Error() string {
|
||||
return fmt.Sprintf("name pattern is not allowed: [pattern: %s]", err.Pattern)
|
||||
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
|
||||
}
|
||||
|
||||
// ____ ___
|
||||
@@ -51,7 +51,7 @@ func IsErrUserAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("user already exists: [name: %s]", err.Name)
|
||||
return fmt.Sprintf("user already exists [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrUserNotExist struct {
|
||||
@@ -65,7 +65,7 @@ func IsErrUserNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserNotExist) Error() string {
|
||||
return fmt.Sprintf("user does not exist: [uid: %d, name: %s]", err.UID, err.Name)
|
||||
return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
|
||||
}
|
||||
|
||||
type ErrEmailAlreadyUsed struct {
|
||||
@@ -78,7 +78,7 @@ func IsErrEmailAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail has been used: [email: %s]", err.Email)
|
||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
type ErrUserOwnRepos struct {
|
||||
@@ -91,7 +91,7 @@ func IsErrUserOwnRepos(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserOwnRepos) Error() string {
|
||||
return fmt.Sprintf("user still has ownership of repositories: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
type ErrUserHasOrgs struct {
|
||||
@@ -104,7 +104,7 @@ func IsErrUserHasOrgs(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserHasOrgs) Error() string {
|
||||
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// __________ ___. .__ .__ ____ __.
|
||||
@@ -124,7 +124,7 @@ func IsErrKeyNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
|
||||
return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
type ErrKeyAlreadyExist struct {
|
||||
@@ -138,7 +138,7 @@ func IsErrKeyAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
}
|
||||
|
||||
type ErrKeyNameAlreadyUsed struct {
|
||||
@@ -152,7 +152,7 @@ func IsErrKeyNameAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
}
|
||||
|
||||
type ErrDeployKeyAlreadyExist struct {
|
||||
@@ -166,7 +166,7 @@ func IsErrDeployKeyAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNameAlreadyUsed struct {
|
||||
@@ -180,7 +180,7 @@ func IsErrDeployKeyNameAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
}
|
||||
|
||||
// _____ ___________ __
|
||||
@@ -200,7 +200,7 @@ func IsErrAccessTokenNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenNotExist) Error() string {
|
||||
return fmt.Sprintf("access token does not exist: [sha: %s]", err.SHA)
|
||||
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
|
||||
}
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -220,7 +220,7 @@ func IsErrLastOrgOwner(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrLastOrgOwner) Error() string {
|
||||
return fmt.Sprintf("user is the last member of owner team: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// __________ .__ __
|
||||
@@ -259,6 +259,35 @@ func (err ErrRepoAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
|
||||
}
|
||||
|
||||
type ErrInvalidCloneAddr struct {
|
||||
IsURLError bool
|
||||
IsInvalidPath bool
|
||||
IsPermissionDenied bool
|
||||
}
|
||||
|
||||
func IsErrInvalidCloneAddr(err error) bool {
|
||||
_, ok := err.(ErrInvalidCloneAddr)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidCloneAddr) Error() string {
|
||||
return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]",
|
||||
err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied)
|
||||
}
|
||||
|
||||
type ErrUpdateTaskNotExist struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
func IsErrUpdateTaskNotExist(err error) bool {
|
||||
_, ok := err.(ErrUpdateTaskNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUpdateTaskNotExist) Error() string {
|
||||
return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ __
|
||||
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
|
||||
@@ -310,7 +339,7 @@ func (err ErrIssueNotExist) Error() string {
|
||||
|
||||
type ErrPullRequestNotExist struct {
|
||||
ID int64
|
||||
PullID int64
|
||||
IssueID int64
|
||||
HeadRepoID int64
|
||||
BaseRepoID int64
|
||||
HeadBarcnh string
|
||||
@@ -323,8 +352,8 @@ func IsErrPullRequestNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrPullRequestNotExist) Error() string {
|
||||
return fmt.Sprintf("pull request does not exist [id: %d, pull_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
|
||||
err.ID, err.PullID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
|
||||
return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
|
||||
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
|
||||
}
|
||||
|
||||
// _________ __
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
DIFF_FILE_ADD = iota + 1
|
||||
DIFF_FILE_CHANGE
|
||||
DIFF_FILE_DEL
|
||||
DIFF_FILE_RENAME
|
||||
)
|
||||
|
||||
type DiffLine struct {
|
||||
@@ -57,12 +58,14 @@ type DiffSection struct {
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
OldName string
|
||||
Index int
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
IsBin bool
|
||||
IsRenamed bool
|
||||
Sections []*DiffSection
|
||||
}
|
||||
|
||||
@@ -86,7 +89,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
}
|
||||
|
||||
leftLine, rightLine int
|
||||
isTooLong bool
|
||||
// FIXME: Should use cache in the future.
|
||||
buf bytes.Buffer
|
||||
)
|
||||
@@ -95,7 +97,7 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
var i int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// fmt.Println(i, line)
|
||||
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
|
||||
continue
|
||||
}
|
||||
@@ -107,9 +109,10 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
i = i + 1
|
||||
|
||||
// Diff data too large, we only show the first about maxlines lines
|
||||
if i == maxlines {
|
||||
isTooLong = true
|
||||
if i >= maxlines {
|
||||
log.Warn("Diff data too large")
|
||||
diff.Files = nil
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -120,10 +123,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
case line[0] == '@':
|
||||
if isTooLong {
|
||||
break
|
||||
}
|
||||
|
||||
curSection = &DiffSection{}
|
||||
curFile.Sections = append(curFile.Sections, curSection)
|
||||
ss := strings.Split(line, "@@")
|
||||
@@ -162,21 +161,27 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
|
||||
// Get new file.
|
||||
if strings.HasPrefix(line, DIFF_HEAD) {
|
||||
if isTooLong {
|
||||
break
|
||||
middle := -1
|
||||
|
||||
// Note: In case file name is surrounded by double quotes(it happens only in git-shell).
|
||||
hasQuote := strings.Index(line, `\"`) > -1
|
||||
if hasQuote {
|
||||
line = strings.Replace(line, `\"`, `"`, -1)
|
||||
middle = strings.Index(line, ` "b/`)
|
||||
} else {
|
||||
middle = strings.Index(line, " b/")
|
||||
}
|
||||
|
||||
beg := len(DIFF_HEAD)
|
||||
a := line[beg : (len(line)-beg)/2+beg]
|
||||
|
||||
// In case file name is surrounded by double quotes(it happens only in git-shell).
|
||||
if a[0] == '"' {
|
||||
a := line[beg+2 : middle]
|
||||
b := line[middle+3:]
|
||||
if hasQuote {
|
||||
a = a[1 : len(a)-1]
|
||||
a = strings.Replace(a, `\"`, `"`, -1)
|
||||
b = b[1 : len(b)-1]
|
||||
}
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: a[strings.Index(a, "/")+1:],
|
||||
Name: a,
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
@@ -188,16 +193,17 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
switch {
|
||||
case strings.HasPrefix(scanner.Text(), "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
curFile.IsDeleted = false
|
||||
curFile.IsCreated = true
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = false
|
||||
case strings.HasPrefix(scanner.Text(), "similarity index 100%"):
|
||||
curFile.Type = DIFF_FILE_RENAME
|
||||
curFile.IsRenamed = true
|
||||
curFile.OldName = curFile.Name
|
||||
curFile.Name = b
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
break
|
||||
@@ -252,10 +258,10 @@ func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxline
|
||||
cmd = exec.Command("git", "show", afterCommitId)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitId)
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-M", beforeCommitId, afterCommitId)
|
||||
}
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
|
||||
312
models/issue.go
312
models/issue.go
@@ -9,7 +9,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path"
|
||||
@@ -20,9 +19,7 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
gouuid "github.com/gogits/gogs/modules/uuid"
|
||||
)
|
||||
@@ -95,15 +92,6 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
|
||||
if err != nil {
|
||||
log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
|
||||
}
|
||||
case "is_pull":
|
||||
if !i.IsPull {
|
||||
return
|
||||
}
|
||||
|
||||
i.PullRequest, err = GetPullRequestByPullID(i.ID)
|
||||
if err != nil {
|
||||
log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err)
|
||||
}
|
||||
case "created":
|
||||
i.Created = regulateTimeZone(i.Created)
|
||||
}
|
||||
@@ -236,7 +224,7 @@ func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err er
|
||||
}
|
||||
i.IsClosed = isClosed
|
||||
|
||||
if err = updateIssue(e, i); err != nil {
|
||||
if err = updateIssueCols(e, i, "is_closed"); err != nil {
|
||||
return err
|
||||
} else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
|
||||
return err
|
||||
@@ -285,6 +273,15 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func (i *Issue) GetPullRequest() (err error) {
|
||||
if i.PullRequest != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i.PullRequest, err = GetPullRequestByIssueID(i.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// It's caller's responsibility to create action.
|
||||
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
|
||||
if _, err = e.Insert(issue); err != nil {
|
||||
@@ -546,7 +543,7 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
|
||||
// IssueUser represents an issue-user relation.
|
||||
type IssueUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"uid INDEX"` // User ID.
|
||||
UID int64 `xorm:"INDEX"` // User ID.
|
||||
IssueID int64
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
MilestoneID int64
|
||||
@@ -758,7 +755,7 @@ func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPul
|
||||
|
||||
queryStr := "SELECT COUNT(*) FROM `issue` "
|
||||
baseCond := " WHERE issue.is_closed=?"
|
||||
if repoID > 0 {
|
||||
if repoID > 0 || len(repoIDs) == 0 {
|
||||
baseCond += " AND issue.repo_id=" + com.ToStr(repoID)
|
||||
} else {
|
||||
baseCond += " AND issue.repo_id IN (" + strings.Join(base.Int64sToStrings(repoIDs), ",") + ")"
|
||||
@@ -821,11 +818,17 @@ func updateIssue(e Engine, issue *Issue) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssue updates information of issue.
|
||||
// UpdateIssue updates all fields of given issue.
|
||||
func UpdateIssue(issue *Issue) error {
|
||||
return updateIssue(x, issue)
|
||||
}
|
||||
|
||||
// updateIssueCols updates specific fields of given issue.
|
||||
func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
|
||||
_, err := e.Id(issue.ID).Cols(cols...).Update(issue)
|
||||
return err
|
||||
}
|
||||
|
||||
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
|
||||
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
|
||||
return err
|
||||
@@ -894,233 +897,6 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// __________ .__ .__ __________ __
|
||||
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
|
||||
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
|
||||
// | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
|
||||
// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
|
||||
// \/ \/ |__| \/ \/
|
||||
|
||||
type PullRequestType int
|
||||
|
||||
const (
|
||||
PULL_REQUEST_GOGS = iota
|
||||
PLLL_ERQUEST_GIT
|
||||
)
|
||||
|
||||
// PullRequest represents relation between pull request and repositories.
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PullID int64 `xorm:"INDEX"`
|
||||
Pull *Issue `xorm:"-"`
|
||||
PullIndex int64
|
||||
HeadRepoID int64
|
||||
HeadRepo *Repository `xorm:"-"`
|
||||
BaseRepoID int64
|
||||
HeadUserName string
|
||||
HeadBarcnh string
|
||||
BaseBranch string
|
||||
MergeBase string `xorm:"VARCHAR(40)"`
|
||||
MergedCommitID string `xorm:"VARCHAR(40)"`
|
||||
Type PullRequestType
|
||||
CanAutoMerge bool
|
||||
HasMerged bool
|
||||
Merged time.Time
|
||||
MergerID int64
|
||||
Merger *User `xorm:"-"`
|
||||
}
|
||||
|
||||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
|
||||
var err error
|
||||
switch colName {
|
||||
case "head_repo_id":
|
||||
pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
|
||||
if err != nil {
|
||||
log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
|
||||
}
|
||||
case "merger_id":
|
||||
if !pr.HasMerged {
|
||||
return
|
||||
}
|
||||
|
||||
pr.Merger, err = GetUserByID(pr.MergerID)
|
||||
if err != nil {
|
||||
if IsErrUserNotExist(err) {
|
||||
pr.MergerID = -1
|
||||
pr.Merger = NewFakeUser()
|
||||
} else {
|
||||
log.Error(3, "GetUserByID[%d]: %v", pr.ID, err)
|
||||
}
|
||||
}
|
||||
case "merged":
|
||||
if !pr.HasMerged {
|
||||
return
|
||||
}
|
||||
|
||||
pr.Merged = regulateTimeZone(pr.Merged)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges pull request to base repository.
|
||||
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pr.Pull.changeStatus(sess, doer, true); err != nil {
|
||||
return fmt.Errorf("Pull.changeStatus: %v", err)
|
||||
}
|
||||
|
||||
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBarcnh)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
|
||||
}
|
||||
|
||||
if err = mergePullRequestAction(sess, doer, pr.Pull.Repo, pr.Pull); err != nil {
|
||||
return fmt.Errorf("mergePullRequestAction: %v", err)
|
||||
}
|
||||
|
||||
pr.HasMerged = true
|
||||
pr.Merged = time.Now()
|
||||
pr.MergerID = doer.Id
|
||||
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
|
||||
return fmt.Errorf("update pull request: %v", err)
|
||||
}
|
||||
|
||||
// Clone base repo.
|
||||
tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
|
||||
os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
|
||||
defer os.RemoveAll(path.Dir(tmpBasePath))
|
||||
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecTimeout(5*time.Minute,
|
||||
fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
|
||||
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
|
||||
return fmt.Errorf("git clone: %s", stderr)
|
||||
}
|
||||
|
||||
// Check out base branch.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
|
||||
"git", "checkout", pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
// Pull commits.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath),
|
||||
"git", "pull", headRepoPath, pr.HeadBarcnh); err != nil {
|
||||
return fmt.Errorf("git pull: %s", stderr)
|
||||
}
|
||||
|
||||
// Push back to upstream.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
|
||||
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
|
||||
return fmt.Errorf("newIssue: %v", err)
|
||||
}
|
||||
|
||||
// Notify watchers.
|
||||
act := &Action{
|
||||
ActUserID: pull.Poster.Id,
|
||||
ActUserName: pull.Poster.Name,
|
||||
ActEmail: pull.Poster.Email,
|
||||
OpType: CREATE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
if err = notifyWatchers(sess, act); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Test apply patch.
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("RepoPath: %v", err)
|
||||
}
|
||||
patchPath := path.Join(repoPath, "pulls", com.ToStr(pr.ID)+".patch")
|
||||
|
||||
os.MkdirAll(path.Dir(patchPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
|
||||
return fmt.Errorf("save patch: %v", err)
|
||||
}
|
||||
defer os.Remove(patchPath)
|
||||
|
||||
stdout, stderr, err := process.ExecDir(-1, repoPath,
|
||||
fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
|
||||
"git", "apply", "--check", "-v", patchPath)
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal:") {
|
||||
return fmt.Errorf("git apply --check: %v - %s", err, stderr)
|
||||
}
|
||||
}
|
||||
pr.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:")
|
||||
|
||||
pr.PullID = pull.ID
|
||||
pr.PullIndex = pull.Index
|
||||
if _, err = sess.Insert(pr); err != nil {
|
||||
return fmt.Errorf("insert pull repo: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequest returnss a pull request hasn't been merged by given info.
|
||||
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
|
||||
pr := &PullRequest{
|
||||
HeadRepoID: headRepoID,
|
||||
BaseRepoID: baseRepoID,
|
||||
HeadBarcnh: headBranch,
|
||||
BaseBranch: baseBranch,
|
||||
}
|
||||
|
||||
has, err := x.Where("has_merged=?", false).Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
|
||||
}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// GetPullRequestByPullID returns pull repo by given pull ID.
|
||||
func GetPullRequestByPullID(pullID int64) (*PullRequest, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := x.Where("pull_id=?", pullID).Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
// | | _____ \_ |__ ____ | |
|
||||
// | | \__ \ | __ \_/ __ \| |
|
||||
@@ -1619,7 +1395,7 @@ func DeleteMilestoneByID(mid int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(m.ID).Delete(m); err != nil {
|
||||
if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1685,6 +1461,9 @@ type Comment struct {
|
||||
RenderedContent string `xorm:"-"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
|
||||
// Reference issue in commit message
|
||||
CommitSHA string `xorm:"VARCHAR(40)"`
|
||||
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
|
||||
// For view issue page.
|
||||
@@ -1733,14 +1512,15 @@ func (c *Comment) EventTag() string {
|
||||
return "event-" + com.ToStr(c.ID)
|
||||
}
|
||||
|
||||
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, uuids []string) (_ *Comment, err error) {
|
||||
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, uuids []string) (_ *Comment, err error) {
|
||||
comment := &Comment{
|
||||
PosterID: u.Id,
|
||||
Type: cmtType,
|
||||
IssueID: issue.ID,
|
||||
CommitID: commitID,
|
||||
Line: line,
|
||||
Content: content,
|
||||
PosterID: u.Id,
|
||||
Type: cmtType,
|
||||
IssueID: issue.ID,
|
||||
CommitID: commitID,
|
||||
Line: line,
|
||||
Content: content,
|
||||
CommitSHA: commitSHA,
|
||||
}
|
||||
if _, err = e.Insert(comment); err != nil {
|
||||
return nil, err
|
||||
@@ -1819,18 +1599,18 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I
|
||||
if !issue.IsClosed {
|
||||
cmtType = COMMENT_TYPE_REOPEN
|
||||
}
|
||||
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", nil)
|
||||
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", "", nil)
|
||||
}
|
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, attachments []string) (comment *Comment, err error) {
|
||||
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, attachments []string) (comment *Comment, err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, attachments)
|
||||
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, commitSHA, attachments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1840,7 +1620,29 @@ func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line in
|
||||
|
||||
// CreateIssueComment creates a plain issue comment.
|
||||
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
|
||||
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
|
||||
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, "", attachments)
|
||||
}
|
||||
|
||||
// CreateRefComment creates a commit reference comment to issue.
|
||||
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
|
||||
if len(commitSHA) == 0 {
|
||||
return fmt.Errorf("cannot create reference with empty commit SHA")
|
||||
}
|
||||
|
||||
// Check if same reference from same commit has already existed.
|
||||
has, err := x.Get(&Comment{
|
||||
Type: COMMENT_TYPE_COMMIT_REF,
|
||||
IssueID: issue.ID,
|
||||
CommitSHA: commitSHA,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("check reference comment: %v", err)
|
||||
} else if has {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, content, commitSHA, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCommentByID returns the comment by given ID.
|
||||
|
||||
310
models/login.go
310
models/login.go
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
@@ -23,12 +24,14 @@ import (
|
||||
|
||||
type LoginType int
|
||||
|
||||
// Note: new type must be added at the end of list to maintain compatibility.
|
||||
const (
|
||||
NOTYPE LoginType = iota
|
||||
PLAIN
|
||||
LDAP
|
||||
SMTP
|
||||
PAM
|
||||
DLDAP
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,10 +40,11 @@ var (
|
||||
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
|
||||
)
|
||||
|
||||
var LoginTypes = map[LoginType]string{
|
||||
LDAP: "LDAP",
|
||||
SMTP: "SMTP",
|
||||
PAM: "PAM",
|
||||
var LoginNames = map[LoginType]string{
|
||||
LDAP: "LDAP (via BindDN)",
|
||||
DLDAP: "LDAP (simple auth)",
|
||||
SMTP: "SMTP",
|
||||
PAM: "PAM",
|
||||
}
|
||||
|
||||
// Ensure structs implemented interface.
|
||||
@@ -51,23 +55,24 @@ var (
|
||||
)
|
||||
|
||||
type LDAPConfig struct {
|
||||
ldap.Ldapsource
|
||||
*ldap.Source
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg.Ldapsource)
|
||||
return json.Unmarshal(bs, &cfg)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg.Ldapsource)
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
AllowedDomains string `xorm:"TEXT"`
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
|
||||
@@ -91,32 +96,71 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
|
||||
}
|
||||
|
||||
type LoginSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type LoginType
|
||||
Name string `xorm:"UNIQUE"`
|
||||
IsActived bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Cfg core.Conversion `xorm:"TEXT"`
|
||||
AllowAutoRegister bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type LoginType
|
||||
Name string `xorm:"UNIQUE"`
|
||||
IsActived bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Cfg core.Conversion `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
|
||||
switch colName {
|
||||
case "type":
|
||||
switch LoginType((*val).(int64)) {
|
||||
case LDAP:
|
||||
case LDAP, DLDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
case PAM:
|
||||
source.Cfg = new(PAMConfig)
|
||||
default:
|
||||
panic("unrecognized login source type: " + com.ToStr(*val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (source *LoginSource) TypeString() string {
|
||||
return LoginTypes[source.Type]
|
||||
func (source *LoginSource) TypeName() string {
|
||||
return LoginNames[source.Type]
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsLDAP() bool {
|
||||
return source.Type == LDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsDLDAP() bool {
|
||||
return source.Type == DLDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsSMTP() bool {
|
||||
return source.Type == SMTP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsPAM() bool {
|
||||
return source.Type == PAM
|
||||
}
|
||||
|
||||
func (source *LoginSource) UseTLS() bool {
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
return source.LDAP().UseSSL
|
||||
case SMTP:
|
||||
return source.SMTP().TLS
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (source *LoginSource) SkipVerify() bool {
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
return source.LDAP().SkipVerify
|
||||
case SMTP:
|
||||
return source.SMTP().SkipVerify
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (source *LoginSource) LDAP() *LDAPConfig {
|
||||
@@ -131,12 +175,18 @@ func (source *LoginSource) PAM() *PAMConfig {
|
||||
return source.Cfg.(*PAMConfig)
|
||||
}
|
||||
|
||||
// CountLoginSources returns number of login sources.
|
||||
func CountLoginSources() int64 {
|
||||
count, _ := x.Count(new(LoginSource))
|
||||
return count
|
||||
}
|
||||
|
||||
func CreateSource(source *LoginSource) error {
|
||||
_, err := x.Insert(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetAuths() ([]*LoginSource, error) {
|
||||
func LoginSources() ([]*LoginSource, error) {
|
||||
auths := make([]*LoginSource, 0, 5)
|
||||
return auths, x.Find(&auths)
|
||||
}
|
||||
@@ -157,107 +207,32 @@ func UpdateSource(source *LoginSource) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func DelLoginSource(source *LoginSource) error {
|
||||
cnt, err := x.Count(&User{LoginSource: source.ID})
|
||||
func DeleteSource(source *LoginSource) error {
|
||||
count, err := x.Count(&User{LoginSource: source.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
} else if count > 0 {
|
||||
return ErrAuthenticationUserUsed
|
||||
}
|
||||
_, err = x.Id(source.ID).Delete(&LoginSource{})
|
||||
_, err = x.Id(source.ID).Delete(new(LoginSource))
|
||||
return err
|
||||
}
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
u := new(User)
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
|
||||
has, err := x.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.LoginType == NOTYPE && has {
|
||||
u.LoginType = PLAIN
|
||||
}
|
||||
|
||||
// For plain login, user must exist to reach this line.
|
||||
// Now verify password.
|
||||
if u.LoginType == PLAIN {
|
||||
if !u.ValidatePassword(passwd) {
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
if !has {
|
||||
var sources []LoginSource
|
||||
if err = x.UseBool().Find(&sources,
|
||||
&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if source.Type == LDAP {
|
||||
u, err := LoginUserLdapSource(nil, uname, passwd,
|
||||
source.ID, source.Cfg.(*LDAPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
|
||||
} else if source.Type == SMTP {
|
||||
u, err := LoginUserSMTPSource(nil, uname, passwd,
|
||||
source.ID, source.Cfg.(*SMTPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
|
||||
} else if source.Type == PAM {
|
||||
u, err := LoginUserPAMSource(nil, uname, passwd,
|
||||
source.ID, source.Cfg.(*PAMConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
log.Warn("Fail to login(%s) by PAM(%s): %v", uname, source.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
|
||||
var source LoginSource
|
||||
hasSource, err := x.Id(u.LoginSource).Get(&source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasSource {
|
||||
return nil, ErrLoginSourceNotExist
|
||||
} else if !source.IsActived {
|
||||
return nil, ErrLoginSourceNotActived
|
||||
}
|
||||
|
||||
switch u.LoginType {
|
||||
case LDAP:
|
||||
return LoginUserLdapSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*LDAPConfig), false)
|
||||
case SMTP:
|
||||
return LoginUserSMTPSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*SMTPConfig), false)
|
||||
case PAM:
|
||||
return LoginUserPAMSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*PAMConfig), false)
|
||||
}
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
// .____ ________ _____ __________
|
||||
// | | \______ \ / _ \\______ \
|
||||
// | | | | \ / /_\ \| ___/
|
||||
// | |___ | ` \/ | \ |
|
||||
// |_______ \/_______ /\____|__ /____|
|
||||
// \/ \/ \/
|
||||
|
||||
// Query if name/passwd can login against the LDAP directory pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
// FIXME: https://github.com/gogits/gogs/issues/672
|
||||
func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
|
||||
fn, sn, mail, admin, logged := cfg.Ldapsource.SearchEntry(name, passwd)
|
||||
func LoginUserLDAPSource(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
cfg := source.Cfg.(*LDAPConfig)
|
||||
directBind := (source.Type == DLDAP)
|
||||
fn, sn, mail, admin, logged := cfg.SearchEntry(name, passwd, directBind)
|
||||
if !logged {
|
||||
// User not in LDAP, do nothing
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
@@ -275,11 +250,10 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: name,
|
||||
FullName: fn + " " + sn,
|
||||
LoginType: LDAP,
|
||||
LoginSource: sourceId,
|
||||
FullName: strings.TrimSpace(fn + " " + sn),
|
||||
LoginType: source.Type,
|
||||
LoginSource: source.ID,
|
||||
LoginName: name,
|
||||
Passwd: passwd,
|
||||
Email: mail,
|
||||
IsAdmin: admin,
|
||||
IsActive: true,
|
||||
@@ -287,6 +261,13 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
|
||||
return u, CreateUser(u)
|
||||
}
|
||||
|
||||
// _________ __________________________
|
||||
// / _____/ / \__ ___/\______ \
|
||||
// \_____ \ / \ / \| | | ___/
|
||||
// / \/ Y \ | | |
|
||||
// /_______ /\____|__ /____| |____|
|
||||
// \/ \/
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
@@ -316,9 +297,7 @@ const (
|
||||
SMTP_LOGIN = "LOGIN"
|
||||
)
|
||||
|
||||
var (
|
||||
SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
|
||||
)
|
||||
var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
|
||||
|
||||
func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
||||
c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
|
||||
@@ -357,6 +336,16 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
|
||||
// Verify allowed domains.
|
||||
if len(cfg.AllowedDomains) > 0 {
|
||||
idx := strings.Index(name, "@")
|
||||
if idx == -1 {
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), name[idx+1:]) {
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
}
|
||||
}
|
||||
|
||||
var auth smtp.Auth
|
||||
if cfg.Auth == SMTP_PLAIN {
|
||||
auth = smtp.PlainAuth("", name, passwd, cfg.Host)
|
||||
@@ -368,7 +357,7 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
|
||||
|
||||
if err := SMTPAuth(auth, cfg); err != nil {
|
||||
if strings.Contains(err.Error(), "Username and Password not accepted") {
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -397,13 +386,20 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
|
||||
return u, err
|
||||
}
|
||||
|
||||
// __________ _____ _____
|
||||
// \______ \/ _ \ / \
|
||||
// | ___/ /_\ \ / \ / \
|
||||
// | | / | \/ Y \
|
||||
// |____| \____|__ /\____|__ /
|
||||
// \/ \/
|
||||
|
||||
// Query if name/passwd can login against PAM
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
|
||||
if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
|
||||
if strings.Contains(err.Error(), "Authentication failure") {
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -426,3 +422,73 @@ func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMCo
|
||||
err := CreateUser(u)
|
||||
return u, err
|
||||
}
|
||||
|
||||
func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
if !source.IsActived {
|
||||
return nil, ErrLoginSourceNotActived
|
||||
}
|
||||
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
return LoginUserLDAPSource(u, name, passwd, source, autoRegister)
|
||||
case SMTP:
|
||||
return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
|
||||
case PAM:
|
||||
return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister)
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
var u *User
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
|
||||
userExists, err := x.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userExists {
|
||||
switch u.LoginType {
|
||||
case NOTYPE, PLAIN:
|
||||
if u.ValidatePassword(passwd) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
|
||||
default:
|
||||
var source LoginSource
|
||||
hasSource, err := x.Id(u.LoginSource).Get(&source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasSource {
|
||||
return nil, ErrLoginSourceNotExist
|
||||
}
|
||||
|
||||
return ExternalUserLogin(u, u.LoginName, passwd, &source, false)
|
||||
}
|
||||
}
|
||||
|
||||
var sources []LoginSource
|
||||
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
u, err := ExternalUserLogin(nil, uname, passwd, &source, true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -65,6 +66,8 @@ var migrations = []Migration{
|
||||
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
|
||||
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
|
||||
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
|
||||
NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
|
||||
NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
@@ -606,3 +609,96 @@ func attachmentRefactor(x *xorm.Engine) error {
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func renamePullRequestFields(x *xorm.Engine) (err error) {
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PullID int64 `xorm:"INDEX"`
|
||||
PullIndex int64
|
||||
HeadBarcnh string
|
||||
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Index int64
|
||||
HeadBranch string
|
||||
}
|
||||
|
||||
if err = x.Sync(new(PullRequest)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
results, err := x.Query("SELECT `id`,`pull_id`,`pull_index`,`head_barcnh` FROM `pull_request`")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select pull requests: %v", err)
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pull *PullRequest
|
||||
for _, pr := range results {
|
||||
pull = &PullRequest{
|
||||
ID: com.StrTo(pr["id"]).MustInt64(),
|
||||
IssueID: com.StrTo(pr["pull_id"]).MustInt64(),
|
||||
Index: com.StrTo(pr["pull_index"]).MustInt64(),
|
||||
HeadBranch: string(pr["head_barcnh"]),
|
||||
}
|
||||
if _, err = sess.Id(pull.ID).Update(pull); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
|
||||
type (
|
||||
User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
LowerName string
|
||||
}
|
||||
Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64
|
||||
LowerName string
|
||||
}
|
||||
)
|
||||
|
||||
repos := make([]*Repository, 0, 25)
|
||||
if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
|
||||
return fmt.Errorf("select all non-mirror repositories: %v", err)
|
||||
}
|
||||
var user *User
|
||||
for _, repo := range repos {
|
||||
user = &User{ID: repo.OwnerID}
|
||||
has, err := x.Get(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
|
||||
} else if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
|
||||
|
||||
// In case repository file is somehow missing.
|
||||
if !com.IsFile(configPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open config file: %v", err)
|
||||
}
|
||||
cfg.DeleteSection("remote \"origin\"")
|
||||
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
|
||||
return fmt.Errorf("save config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/gogits/gogs/models/migrations"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -46,20 +47,22 @@ func sessionRelease(sess *xorm.Session) {
|
||||
// Note: get back time.Time from database Go sees it at UTC where they are really Local.
|
||||
// So this function makes correct timezone offset.
|
||||
func regulateTimeZone(t time.Time) time.Time {
|
||||
if setting.UseSQLite3 {
|
||||
if !setting.UseMySQL {
|
||||
return t
|
||||
}
|
||||
|
||||
zone := t.Local().Format("-0700")
|
||||
if len(zone) != 5 {
|
||||
log.Error(4, "Unprocessable timezone: %s - %s", t.Local(), zone)
|
||||
return t
|
||||
}
|
||||
offset := com.StrTo(zone[2:3]).MustInt()
|
||||
hour := com.StrTo(zone[2:3]).MustInt()
|
||||
minutes := com.StrTo(zone[3:5]).MustInt()
|
||||
|
||||
if zone[0] == '-' {
|
||||
return t.Add(time.Duration(offset) * time.Hour)
|
||||
return t.Add(time.Duration(hour) * time.Hour).Add(time.Duration(minutes) * time.Minute)
|
||||
}
|
||||
return t.Add(-1 * time.Duration(offset) * time.Hour)
|
||||
return t.Add(-1 * time.Duration(hour) * time.Hour).Add(-1 * time.Duration(minutes) * time.Minute)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -72,11 +75,12 @@ var (
|
||||
}
|
||||
|
||||
EnableSQLite3 bool
|
||||
EnableTidb bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
tables = append(tables,
|
||||
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
|
||||
new(User), new(PublicKey), new(AccessToken),
|
||||
new(Repository), new(DeployKey), new(Collaboration), new(Access),
|
||||
new(Watch), new(Star), new(Follow), new(Action),
|
||||
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
||||
@@ -92,7 +96,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func LoadModelsConfig() {
|
||||
func LoadConfigs() {
|
||||
sec := setting.Cfg.Section("database")
|
||||
DbCfg.Type = sec.Key("DB_TYPE").String()
|
||||
switch DbCfg.Type {
|
||||
@@ -102,6 +106,8 @@ func LoadModelsConfig() {
|
||||
setting.UseMySQL = true
|
||||
case "postgres":
|
||||
setting.UsePostgreSQL = true
|
||||
case "tidb":
|
||||
setting.UseTiDB = true
|
||||
}
|
||||
DbCfg.Host = sec.Key("HOST").String()
|
||||
DbCfg.Name = sec.Key("NAME").String()
|
||||
@@ -143,6 +149,14 @@ func getEngine() (*xorm.Engine, error) {
|
||||
return nil, fmt.Errorf("Fail to create directories: %v", err)
|
||||
}
|
||||
cnnstr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc"
|
||||
case "tidb":
|
||||
if !EnableTidb {
|
||||
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("Fail to create directories: %v", err)
|
||||
}
|
||||
cnnstr = "goleveldb://" + DbCfg.Path
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
@@ -224,11 +238,11 @@ func GetStatistic() (stats Statistic) {
|
||||
stats.Counter.Access, _ = x.Count(new(Access))
|
||||
stats.Counter.Issue, _ = x.Count(new(Issue))
|
||||
stats.Counter.Comment, _ = x.Count(new(Comment))
|
||||
stats.Counter.Oauth, _ = x.Count(new(Oauth2))
|
||||
stats.Counter.Oauth = 0
|
||||
stats.Counter.Follow, _ = x.Count(new(Follow))
|
||||
stats.Counter.Mirror, _ = x.Count(new(Mirror))
|
||||
stats.Counter.Release, _ = x.Count(new(Release))
|
||||
stats.Counter.LoginSource, _ = x.Count(new(LoginSource))
|
||||
stats.Counter.LoginSource = CountLoginSources()
|
||||
stats.Counter.Webhook, _ = x.Count(new(Webhook))
|
||||
stats.Counter.Milestone, _ = x.Count(new(Milestone))
|
||||
stats.Counter.Label, _ = x.Count(new(Label))
|
||||
|
||||
18
models/models_tidb.go
Normal file
18
models/models_tidb.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// +build tidb go1.4.2
|
||||
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
_ "github.com/go-xorm/tidb"
|
||||
"github.com/ngaut/log"
|
||||
_ "github.com/pingcap/tidb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableTidb = true
|
||||
log.SetLevelByString("error")
|
||||
}
|
||||
106
models/oauth2.go
106
models/oauth2.go
@@ -1,106 +0,0 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OauthType int
|
||||
|
||||
const (
|
||||
GITHUB OauthType = iota + 1
|
||||
GOOGLE
|
||||
TWITTER
|
||||
QQ
|
||||
WEIBO
|
||||
BITBUCKET
|
||||
FACEBOOK
|
||||
)
|
||||
|
||||
var (
|
||||
ErrOauth2RecordNotExist = errors.New("OAuth2 record does not exist")
|
||||
ErrOauth2NotAssociated = errors.New("OAuth2 is not associated with user")
|
||||
)
|
||||
|
||||
type Oauth2 struct {
|
||||
Id int64
|
||||
Uid int64 `xorm:"unique(s)"` // userId
|
||||
User *User `xorm:"-"`
|
||||
Type int `xorm:"unique(s) unique(oauth)"` // twitter,github,google...
|
||||
Identity string `xorm:"unique(s) unique(oauth)"` // id..
|
||||
Token string `xorm:"TEXT not null"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time
|
||||
HasRecentActivity bool `xorm:"-"`
|
||||
}
|
||||
|
||||
func BindUserOauth2(userId, oauthId int64) error {
|
||||
_, err := x.Id(oauthId).Update(&Oauth2{Uid: userId})
|
||||
return err
|
||||
}
|
||||
|
||||
func AddOauth2(oa *Oauth2) error {
|
||||
_, err := x.Insert(oa)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetOauth2(identity string) (oa *Oauth2, err error) {
|
||||
oa = &Oauth2{Identity: identity}
|
||||
isExist, err := x.Get(oa)
|
||||
if err != nil {
|
||||
return
|
||||
} else if !isExist {
|
||||
return nil, ErrOauth2RecordNotExist
|
||||
} else if oa.Uid == -1 {
|
||||
return oa, ErrOauth2NotAssociated
|
||||
}
|
||||
oa.User, err = GetUserByID(oa.Uid)
|
||||
return oa, err
|
||||
}
|
||||
|
||||
func GetOauth2ById(id int64) (oa *Oauth2, err error) {
|
||||
oa = new(Oauth2)
|
||||
has, err := x.Id(id).Get(oa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrOauth2RecordNotExist
|
||||
}
|
||||
return oa, nil
|
||||
}
|
||||
|
||||
// UpdateOauth2 updates given OAuth2.
|
||||
func UpdateOauth2(oa *Oauth2) error {
|
||||
_, err := x.Id(oa.Id).AllCols().Update(oa)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOauthByUserId returns list of oauthes that are related to given user.
|
||||
func GetOauthByUserId(uid int64) ([]*Oauth2, error) {
|
||||
socials := make([]*Oauth2, 0, 5)
|
||||
err := x.Find(&socials, Oauth2{Uid: uid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, social := range socials {
|
||||
social.HasRecentActivity = social.Updated.Add(7 * 24 * time.Hour).After(time.Now())
|
||||
}
|
||||
return socials, err
|
||||
}
|
||||
|
||||
// DeleteOauth2ById deletes a oauth2 by ID.
|
||||
func DeleteOauth2ById(id int64) error {
|
||||
_, err := x.Delete(&Oauth2{Id: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUnbindOauth deletes all unbind OAuthes.
|
||||
func CleanUnbindOauth() error {
|
||||
_, err := x.Delete(&Oauth2{Uid: -1})
|
||||
return err
|
||||
}
|
||||
102
models/org.go
102
models/org.go
@@ -10,7 +10,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -93,17 +93,6 @@ func (org *User) RemoveOrgRepo(repoID int64) error {
|
||||
return org.removeOrgRepo(x, repoID)
|
||||
}
|
||||
|
||||
// IsOrgEmailUsed returns true if the e-mail has been used in organization account.
|
||||
func IsOrgEmailUsed(email string) (bool, error) {
|
||||
if len(email) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return x.Get(&User{
|
||||
Email: email,
|
||||
Type: ORGANIZATION,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateOrganization creates record of a new organization.
|
||||
func CreateOrganization(org, owner *User) (err error) {
|
||||
if err = IsUsableName(org.Name); err != nil {
|
||||
@@ -117,18 +106,9 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
return ErrUserAlreadyExist{org.Name}
|
||||
}
|
||||
|
||||
isExist, err = IsOrgEmailUsed(org.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isExist {
|
||||
return ErrEmailAlreadyUsed{org.Email}
|
||||
}
|
||||
|
||||
org.LowerName = strings.ToLower(org.Name)
|
||||
org.FullName = org.Name
|
||||
org.Avatar = base.EncodeMd5(org.Email)
|
||||
org.AvatarEmail = org.Email
|
||||
// No password for organization.
|
||||
org.UseCustomAvatar = true
|
||||
org.NumTeams = 1
|
||||
org.NumMembers = 1
|
||||
|
||||
@@ -141,6 +121,17 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
if _, err = sess.Insert(org); err != nil {
|
||||
return fmt.Errorf("insert organization: %v", err)
|
||||
}
|
||||
org.GenerateRandomAvatar()
|
||||
|
||||
// Add initial creator to organization and owner team.
|
||||
if _, err = sess.Insert(&OrgUser{
|
||||
Uid: owner.Id,
|
||||
OrgID: org.Id,
|
||||
IsOwner: true,
|
||||
NumTeams: 1,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("insert org-user relation: %v", err)
|
||||
}
|
||||
|
||||
// Create default owner team.
|
||||
t := &Team{
|
||||
@@ -154,23 +145,11 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
return fmt.Errorf("insert owner team: %v", err)
|
||||
}
|
||||
|
||||
// Add initial creator to organization and owner team.
|
||||
ou := &OrgUser{
|
||||
Uid: owner.Id,
|
||||
OrgID: org.Id,
|
||||
IsOwner: true,
|
||||
NumTeams: 1,
|
||||
}
|
||||
if _, err = sess.Insert(ou); err != nil {
|
||||
return fmt.Errorf("insert org-user relation: %v", err)
|
||||
}
|
||||
|
||||
tu := &TeamUser{
|
||||
if _, err = sess.Insert(&TeamUser{
|
||||
Uid: owner.Id,
|
||||
OrgID: org.Id,
|
||||
TeamID: t.ID,
|
||||
}
|
||||
if _, err = sess.Insert(tu); err != nil {
|
||||
}); err != nil {
|
||||
return fmt.Errorf("insert team-user relation: %v", err)
|
||||
}
|
||||
|
||||
@@ -205,14 +184,12 @@ func CountOrganizations() int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
// GetOrganizations returns given number of organizations with offset.
|
||||
func GetOrganizations(num, offset int) ([]*User, error) {
|
||||
orgs := make([]*User, 0, num)
|
||||
err := x.Limit(num, offset).Where("type=1").Asc("id").Find(&orgs)
|
||||
return orgs, err
|
||||
// Organizations returns number of organizations in given page.
|
||||
func Organizations(page, pageSize int) ([]*User, error) {
|
||||
orgs := make([]*User, 0, pageSize)
|
||||
return orgs, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs)
|
||||
}
|
||||
|
||||
// TODO: need some kind of mechanism to record failure.
|
||||
// DeleteOrganization completely and permanently deletes everything of organization.
|
||||
func DeleteOrganization(org *User) (err error) {
|
||||
if err := DeleteUser(org); err != nil {
|
||||
@@ -220,23 +197,23 @@ func DeleteOrganization(org *User) (err error) {
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Team{OrgID: org.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
if err = deleteBeans(sess,
|
||||
&Team{OrgID: org.Id},
|
||||
&OrgUser{OrgID: org.Id},
|
||||
&TeamUser{OrgID: org.Id},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
if _, err = sess.Delete(&OrgUser{OrgID: org.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&TeamUser{OrgID: org.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
|
||||
if err = deleteUser(sess, org); err != nil {
|
||||
return fmt.Errorf("deleteUser: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
@@ -275,6 +252,25 @@ func IsPublicMembership(orgId, uid int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
|
||||
orgs := make([]*User, 0, 10)
|
||||
return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true).
|
||||
Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs)
|
||||
}
|
||||
|
||||
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
|
||||
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
|
||||
sess := x.NewSession()
|
||||
return getOwnedOrgsByUserID(sess, userID)
|
||||
}
|
||||
|
||||
// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by
|
||||
// given user ID and descring order by given condition.
|
||||
func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
|
||||
sess := x.NewSession()
|
||||
return getOwnedOrgsByUserID(sess.Desc(desc), userID)
|
||||
}
|
||||
|
||||
// GetOrgUsersByUserId returns all organization-user relations by user ID.
|
||||
func GetOrgUsersByUserId(uid int64) ([]*OrgUser, error) {
|
||||
ous := make([]*OrgUser, 0, 10)
|
||||
|
||||
@@ -117,16 +117,6 @@ func (key *PublicKey) GetAuthorizedString() string {
|
||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
|
||||
}
|
||||
|
||||
var minimumKeySizes = map[string]int{
|
||||
"(ED25519)": 256,
|
||||
"(ECDSA)": 256,
|
||||
"(NTRU)": 1087,
|
||||
"(MCE)": 1702,
|
||||
"(McE)": 1702,
|
||||
"(RSA)": 1024,
|
||||
"(DSA)": 1024,
|
||||
}
|
||||
|
||||
func extractTypeFromBase64Key(key string) (string, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil || len(b) < 4 {
|
||||
@@ -153,7 +143,7 @@ func parseKeyString(content string) (string, error) {
|
||||
|
||||
if len(lines) == 1 {
|
||||
// Parse openssh format
|
||||
parts := strings.Fields(lines[0])
|
||||
parts := strings.SplitN(lines[0], " ", 3)
|
||||
switch len(parts) {
|
||||
case 0:
|
||||
return "", errors.New("Empty key")
|
||||
@@ -228,9 +218,9 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
tmpFile.Close()
|
||||
|
||||
// Check if ssh-keygen recognizes its contents.
|
||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
|
||||
if err != nil {
|
||||
return "", errors.New("ssh-keygen -l -f: " + stderr)
|
||||
return "", errors.New("ssh-keygen -lf: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
|
||||
}
|
||||
@@ -251,9 +241,10 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
if keySize == 0 {
|
||||
return "", errors.New("cannot get key size of the given key")
|
||||
}
|
||||
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
|
||||
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||
return "", errors.New("sorry, unrecognized public key type")
|
||||
|
||||
keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
|
||||
if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||
return "", fmt.Errorf("unrecognized public key type: %s", keyType)
|
||||
} else if keySize < minimumKeySize {
|
||||
return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
||||
}
|
||||
@@ -321,9 +312,9 @@ func addKey(e Engine, key *PublicKey) (err error) {
|
||||
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
|
||||
if err != nil {
|
||||
return errors.New("ssh-keygen -l -f: " + stderr)
|
||||
return errors.New("ssh-keygen -lf: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return errors.New("not enough output for calculating fingerprint: " + stdout)
|
||||
}
|
||||
|
||||
548
models/pull.go
Normal file
548
models/pull.go
Normal file
@@ -0,0 +1,548 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type PullRequestType int
|
||||
|
||||
const (
|
||||
PULL_REQUEST_GOGS PullRequestType = iota
|
||||
PLLL_ERQUEST_GIT
|
||||
)
|
||||
|
||||
type PullRequestStatus int
|
||||
|
||||
const (
|
||||
PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
|
||||
PULL_REQUEST_STATUS_CHECKING
|
||||
PULL_REQUEST_STATUS_MERGEABLE
|
||||
)
|
||||
|
||||
// PullRequest represents relation between pull request and repositories.
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type PullRequestType
|
||||
Status PullRequestStatus
|
||||
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Issue *Issue `xorm:"-"`
|
||||
Index int64
|
||||
|
||||
HeadRepoID int64
|
||||
HeadRepo *Repository `xorm:"-"`
|
||||
BaseRepoID int64
|
||||
BaseRepo *Repository `xorm:"-"`
|
||||
HeadUserName string
|
||||
HeadBranch string
|
||||
BaseBranch string
|
||||
MergeBase string `xorm:"VARCHAR(40)"`
|
||||
|
||||
HasMerged bool
|
||||
MergedCommitID string `xorm:"VARCHAR(40)"`
|
||||
Merged time.Time
|
||||
MergerID int64
|
||||
Merger *User `xorm:"-"`
|
||||
}
|
||||
|
||||
// Note: don't try to get Pull because will end up recursive querying.
|
||||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "merged":
|
||||
if !pr.HasMerged {
|
||||
return
|
||||
}
|
||||
|
||||
pr.Merged = regulateTimeZone(pr.Merged)
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
|
||||
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
|
||||
if err != nil && !IsErrRepoNotExist(err) {
|
||||
return fmt.Errorf("getRepositoryByID(head): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetHeadRepo() (err error) {
|
||||
return pr.getHeadRepo(x)
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetBaseRepo() (err error) {
|
||||
if pr.BaseRepo != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID(base): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetMerger() (err error) {
|
||||
if !pr.HasMerged || pr.Merger != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pr.Merger, err = GetUserByID(pr.MergerID)
|
||||
if IsErrUserNotExist(err) {
|
||||
pr.MergerID = -1
|
||||
pr.Merger = NewFakeUser()
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("GetUserByID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsChecking returns true if this pull request is still checking conflict.
|
||||
func (pr *PullRequest) IsChecking() bool {
|
||||
return pr.Status == PULL_REQUEST_STATUS_CHECKING
|
||||
}
|
||||
|
||||
// CanAutoMerge returns true if this pull request can be merged automatically.
|
||||
func (pr *PullRequest) CanAutoMerge() bool {
|
||||
return pr.Status == PULL_REQUEST_STATUS_MERGEABLE
|
||||
}
|
||||
|
||||
// Merge merges pull request to base repository.
|
||||
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pr.Issue.changeStatus(sess, doer, true); err != nil {
|
||||
return fmt.Errorf("Issue.changeStatus: %v", err)
|
||||
}
|
||||
|
||||
if err = pr.getHeadRepo(sess); err != nil {
|
||||
return fmt.Errorf("getHeadRepo: %v", err)
|
||||
}
|
||||
|
||||
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
|
||||
}
|
||||
|
||||
if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
|
||||
return fmt.Errorf("mergePullRequestAction: %v", err)
|
||||
}
|
||||
|
||||
pr.HasMerged = true
|
||||
pr.Merged = time.Now()
|
||||
pr.MergerID = doer.Id
|
||||
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
|
||||
return fmt.Errorf("update pull request: %v", err)
|
||||
}
|
||||
|
||||
// Clone base repo.
|
||||
tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
|
||||
os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
|
||||
defer os.RemoveAll(path.Dir(tmpBasePath))
|
||||
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecTimeout(5*time.Minute,
|
||||
fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
|
||||
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
|
||||
return fmt.Errorf("git clone: %s", stderr)
|
||||
}
|
||||
|
||||
// Check out base branch.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
|
||||
"git", "checkout", pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
// Add head repo remote.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git remote add): %s", tmpBasePath),
|
||||
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
|
||||
return fmt.Errorf("git remote add[%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
// Merge commits.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git fetch): %s", tmpBasePath),
|
||||
"git", "fetch", "head_repo"); err != nil {
|
||||
return fmt.Errorf("git fetch[%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git merge): %s", tmpBasePath),
|
||||
"git", "merge", "--no-ff", "-m",
|
||||
fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch),
|
||||
"head_repo/"+pr.HeadBranch); err != nil {
|
||||
return fmt.Errorf("git merge[%s]: %s", tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
// Push back to upstream.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
|
||||
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// patchConflicts is a list of conflit description from Git.
|
||||
var patchConflicts = []string{
|
||||
"patch does not apply",
|
||||
"already exists in working directory",
|
||||
"unrecognized input",
|
||||
}
|
||||
|
||||
// testPatch checks if patch can be merged to base repository without conflit.
|
||||
func (pr *PullRequest) testPatch() (err error) {
|
||||
if pr.BaseRepo == nil {
|
||||
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
patchPath, err := pr.BaseRepo.PatchPath(pr.Index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("BaseRepo.PatchPath: %v", err)
|
||||
}
|
||||
|
||||
// Fast fail if patch does not exist, this assumes data is cruppted.
|
||||
if !com.IsFile(patchPath) {
|
||||
log.Trace("PullRequest[%d].testPatch: ignored cruppted data", pr.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Trace("PullRequest[%d].testPatch(patchPath): %s", pr.ID, patchPath)
|
||||
|
||||
if err := pr.BaseRepo.UpdateLocalCopy(); err != nil {
|
||||
return fmt.Errorf("UpdateLocalCopy: %v", err)
|
||||
}
|
||||
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
_, stderr, err := process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
|
||||
fmt.Sprintf("testPatch(git apply --check): %d", pr.BaseRepo.ID),
|
||||
"git", "apply", "--check", patchPath)
|
||||
if err != nil {
|
||||
for i := range patchConflicts {
|
||||
if strings.Contains(stderr, patchConflicts[i]) {
|
||||
log.Trace("PullRequest[%d].testPatch(apply): has conflit", pr.ID)
|
||||
pr.Status = PULL_REQUEST_STATUS_CONFLICT
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("git apply --check: %v - %s", err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
|
||||
return fmt.Errorf("newIssue: %v", err)
|
||||
}
|
||||
|
||||
// Notify watchers.
|
||||
act := &Action{
|
||||
ActUserID: pull.Poster.Id,
|
||||
ActUserName: pull.Poster.Name,
|
||||
ActEmail: pull.Poster.Email,
|
||||
OpType: CREATE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
if err = notifyWatchers(sess, act); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.Index = pull.Index
|
||||
if err = repo.SavePatch(pr.Index, patch); err != nil {
|
||||
return fmt.Errorf("SavePatch: %v", err)
|
||||
}
|
||||
|
||||
pr.BaseRepo = repo
|
||||
if err = pr.testPatch(); err != nil {
|
||||
return fmt.Errorf("testPatch: %v", err)
|
||||
}
|
||||
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
|
||||
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
|
||||
}
|
||||
|
||||
pr.IssueID = pull.ID
|
||||
if _, err = sess.Insert(pr); err != nil {
|
||||
return fmt.Errorf("insert pull repo: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequest returnss a pull request that is open and has not been merged
|
||||
// by given head/base and repo/branch.
|
||||
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
headRepoID, headBranch, baseRepoID, baseBranch, false, false).
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
|
||||
}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequestsByHeadInfo returnss all pull requests that are open and has not been merged
|
||||
// by given head information (repo and branch).
|
||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
return prs, x.Where("head_repo_id=? AND head_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
repoID, branch, false, false).
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequestsByBaseInfo returnss all pull requests that are open and has not been merged
|
||||
// by given base information (repo and branch).
|
||||
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
repoID, branch, false, false).
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
|
||||
}
|
||||
|
||||
// GetPullRequestByID returns a pull request by given ID.
|
||||
func GetPullRequestByID(id int64) (*PullRequest, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := x.Id(id).Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// GetPullRequestByIssueID returns pull request by given issue ID.
|
||||
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
|
||||
pr := &PullRequest{
|
||||
IssueID: issueID,
|
||||
}
|
||||
has, err := x.Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// Update updates all fields of pull request.
|
||||
func (pr *PullRequest) Update() error {
|
||||
_, err := x.Id(pr.ID).AllCols().Update(pr)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update updates specific fields of pull request.
|
||||
func (pr *PullRequest) UpdateCols(cols ...string) error {
|
||||
_, err := x.Id(pr.ID).Cols(cols...).Update(pr)
|
||||
return err
|
||||
}
|
||||
|
||||
var PullRequestQueue = NewUniqueQueue(setting.Repository.PullRequestQueueLength)
|
||||
|
||||
// UpdatePatch generates and saves a new patch.
|
||||
func (pr *PullRequest) UpdatePatch() (err error) {
|
||||
if err = pr.GetHeadRepo(); err != nil {
|
||||
return fmt.Errorf("GetHeadRepo: %v", err)
|
||||
} else if pr.HeadRepo == nil {
|
||||
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = pr.GetBaseRepo(); err != nil {
|
||||
return fmt.Errorf("GetBaseRepo: %v", err)
|
||||
} else if err = pr.BaseRepo.GetOwner(); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
headRepoPath, err := pr.HeadRepo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("HeadRepo.RepoPath: %v", err)
|
||||
}
|
||||
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.Owner.Name, pr.BaseRepo.Name)); err != nil {
|
||||
return fmt.Errorf("AddRemote: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
headGitRepo.RemoveRemote(tmpRemote)
|
||||
}()
|
||||
remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
|
||||
pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetMergeBase: %v", err)
|
||||
} else if err = pr.Update(); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
|
||||
patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetPatch: %v", err)
|
||||
}
|
||||
|
||||
if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil {
|
||||
return fmt.Errorf("BaseRepo.SavePatch: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddToTaskQueue adds itself to pull request test task queue.
|
||||
func (pr *PullRequest) AddToTaskQueue() {
|
||||
go PullRequestQueue.AddFunc(pr.ID, func() {
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
if err := pr.UpdateCols("status"); err != nil {
|
||||
log.Error(5, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func addHeadRepoTasks(prs []*PullRequest) {
|
||||
for _, pr := range prs {
|
||||
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
|
||||
if err := pr.UpdatePatch(); err != nil {
|
||||
log.Error(4, "UpdatePatch: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
pr.AddToTaskQueue()
|
||||
}
|
||||
}
|
||||
|
||||
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
||||
// and generate new patch for testing as needed.
|
||||
func AddTestPullRequestTask(repoID int64, branch string) {
|
||||
log.Trace("AddTestPullRequestTask[head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
|
||||
if err != nil {
|
||||
log.Error(4, "Find pull requests[head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
addHeadRepoTasks(prs)
|
||||
|
||||
log.Trace("AddTestPullRequestTask[base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
|
||||
if err != nil {
|
||||
log.Error(4, "Find pull requests[base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
for _, pr := range prs {
|
||||
pr.AddToTaskQueue()
|
||||
}
|
||||
}
|
||||
|
||||
// checkAndUpdateStatus checks if pull request is possible to levaing checking status,
|
||||
// and set to be either conflict or mergeable.
|
||||
func (pr *PullRequest) checkAndUpdateStatus() {
|
||||
// Status is not changed to conflict means mergeable.
|
||||
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
|
||||
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
|
||||
}
|
||||
|
||||
// Make sure there is no waiting test to process before levaing the checking status.
|
||||
if !PullRequestQueue.Exist(pr.ID) {
|
||||
if err := pr.UpdateCols("status"); err != nil {
|
||||
log.Error(4, "Update[%d]: %v", pr.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPullRequests checks and tests untested patches of pull requests.
|
||||
// TODO: test more pull requests at same time.
|
||||
func TestPullRequests() {
|
||||
prs := make([]*PullRequest, 0, 10)
|
||||
x.Iterate(PullRequest{
|
||||
Status: PULL_REQUEST_STATUS_CHECKING,
|
||||
},
|
||||
func(idx int, bean interface{}) error {
|
||||
pr := bean.(*PullRequest)
|
||||
|
||||
if err := pr.GetBaseRepo(); err != nil {
|
||||
log.Error(3, "GetBaseRepo: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pr.testPatch(); err != nil {
|
||||
log.Error(3, "testPatch: %v", err)
|
||||
return nil
|
||||
}
|
||||
prs = append(prs, pr)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Update pull request status.
|
||||
for _, pr := range prs {
|
||||
pr.checkAndUpdateStatus()
|
||||
}
|
||||
|
||||
// Start listening on new test requests.
|
||||
for prID := range PullRequestQueue.Queue() {
|
||||
log.Trace("TestPullRequests[%v]: processing test task", prID)
|
||||
PullRequestQueue.Remove(prID)
|
||||
|
||||
pr, err := GetPullRequestByID(com.StrTo(prID).MustInt64())
|
||||
if err != nil {
|
||||
log.Error(4, "GetPullRequestByID[%d]: %v", prID, err)
|
||||
continue
|
||||
} else if err = pr.testPatch(); err != nil {
|
||||
log.Error(4, "testPatch[%d]: %v", pr.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
pr.checkAndUpdateStatus()
|
||||
}
|
||||
}
|
||||
|
||||
func InitTestPullRequests() {
|
||||
go TestPullRequests()
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.Id.String()); err != nil {
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
236
models/repo.go
236
models/repo.go
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bindata"
|
||||
@@ -46,6 +47,9 @@ var (
|
||||
|
||||
var (
|
||||
Gitignores, Licenses, Readmes []string
|
||||
|
||||
// Maximum items per page in forks, watchers and stars of a repo
|
||||
ItemsPerPage = 54
|
||||
)
|
||||
|
||||
func LoadRepoConfig() {
|
||||
@@ -102,6 +106,7 @@ func NewRepoContext() {
|
||||
if ver.LessThan(reqVer) {
|
||||
log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1")
|
||||
}
|
||||
log.Info("Git Version: %s", ver.String())
|
||||
|
||||
// Git requires setting user.name and user.email in order to commit changes.
|
||||
for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} {
|
||||
@@ -179,9 +184,11 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
|
||||
func (repo *Repository) getOwner(e Engine) (err error) {
|
||||
if repo.Owner == nil {
|
||||
repo.Owner, err = getUserByID(e, repo.OwnerID)
|
||||
if repo.Owner != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo.Owner, err = getUserByID(e, repo.OwnerID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -272,6 +279,11 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
|
||||
return repo.OwnerID == userID
|
||||
}
|
||||
|
||||
// CanBeForked returns true if repository meets the requirements of being forked.
|
||||
func (repo *Repository) CanBeForked() bool {
|
||||
return !repo.IsBare && !repo.IsMirror
|
||||
}
|
||||
|
||||
func (repo *Repository) NextIssueIndex() int64 {
|
||||
return int64(repo.NumIssues+repo.NumPulls) + 1
|
||||
}
|
||||
@@ -288,6 +300,59 @@ func (repo *Repository) DescriptionHtml() template.HTML {
|
||||
return template.HTML(DescPattern.ReplaceAllStringFunc(base.Sanitizer.Sanitize(repo.Description), sanitize))
|
||||
}
|
||||
|
||||
func (repo *Repository) LocalCopyPath() string {
|
||||
return path.Join(setting.AppDataPath, "tmp/local", com.ToStr(repo.ID))
|
||||
}
|
||||
|
||||
// UpdateLocalCopy makes sure the local copy of repository is up-to-date.
|
||||
func (repo *Repository) UpdateLocalCopy() error {
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localPath := repo.LocalCopyPath()
|
||||
if !com.IsExist(localPath) {
|
||||
_, stderr, err := process.Exec(
|
||||
fmt.Sprintf("UpdateLocalCopy(git clone): %s", repoPath), "git", "clone", repoPath, localPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git clone: %v - %s", err, stderr)
|
||||
}
|
||||
} else {
|
||||
_, stderr, err := process.ExecDir(-1, localPath,
|
||||
fmt.Sprintf("UpdateLocalCopy(git pull): %s", repoPath), "git", "pull")
|
||||
if err != nil {
|
||||
return fmt.Errorf("git pull: %v - %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchPath returns corresponding patch file path of repository by given issue ID.
|
||||
func (repo *Repository) PatchPath(index int64) (string, error) {
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil
|
||||
}
|
||||
|
||||
// SavePatch saves patch data to corresponding location by given issue ID.
|
||||
func (repo *Repository) SavePatch(index int64, patch []byte) error {
|
||||
patchPath, err := repo.PatchPath(index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PatchPath: %v", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(path.Dir(patchPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
|
||||
return fmt.Errorf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
|
||||
has, err := e.Get(&Repository{
|
||||
OwnerID: u.Id,
|
||||
@@ -317,9 +382,9 @@ func (repo *Repository) CloneLink() (cl CloneLink, err error) {
|
||||
if setting.SSHPort != 22 {
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
|
||||
} else {
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.Name, repo.Name)
|
||||
}
|
||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.Name, repo.Name)
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
@@ -416,13 +481,21 @@ func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
type MigrateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
// MigrateRepository migrates a existing repository from other project hosting.
|
||||
func MigrateRepository(u *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
|
||||
func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
repo, err := CreateRepository(u, CreateRepoOptions{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
IsPrivate: private,
|
||||
IsMirror: mirror,
|
||||
Name: opts.Name,
|
||||
Description: opts.Description,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
IsMirror: opts.IsMirror,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -432,7 +505,7 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
|
||||
repoPath := RepoPath(u.Name, name)
|
||||
repoPath := RepoPath(u.Name, opts.Name)
|
||||
|
||||
if u.IsOrganization() {
|
||||
t, err := u.GetOwnerTeam()
|
||||
@@ -445,8 +518,8 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
}
|
||||
|
||||
repo.IsBare = false
|
||||
if mirror {
|
||||
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, url); err != nil {
|
||||
if opts.IsMirror {
|
||||
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
repo.IsMirror = true
|
||||
@@ -458,13 +531,34 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
// FIXME: this command could for both migrate and mirror
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("MigrateRepository: %s", repoPath),
|
||||
"git", "clone", "--mirror", "--bare", "--quiet", url, repoPath)
|
||||
"git", "clone", "--mirror", "--bare", "--quiet", opts.RemoteAddr, repoPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("git clone --mirror --bare --quiet: %v", stderr)
|
||||
} else if err = createUpdateHook(repoPath); err != nil {
|
||||
return repo, fmt.Errorf("create update hook: %v", err)
|
||||
}
|
||||
|
||||
// Clean up mirror info which prevents "push --all".
|
||||
configPath := filepath.Join(repoPath, "/config")
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("open config file: %v", err)
|
||||
}
|
||||
cfg.DeleteSection("remote \"origin\"")
|
||||
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
|
||||
return repo, fmt.Errorf("save config file: %v", err)
|
||||
}
|
||||
|
||||
// Check if repository is empty.
|
||||
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
|
||||
repo.IsBare = true
|
||||
} else {
|
||||
return repo, fmt.Errorf("check bare: %v - %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if repository has master branch, if so set it to default branch.
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
@@ -615,7 +709,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
|
||||
fmt.Println(tmpDir)
|
||||
|
||||
// Initialize repository according to user's choice.
|
||||
if opts.AutoInit {
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
@@ -761,21 +855,16 @@ func CountPublicRepositories() int64 {
|
||||
return countRepositories(false)
|
||||
}
|
||||
|
||||
// GetRepositoriesWithUsers returns given number of repository objects with offset.
|
||||
// It also auto-gets corresponding users.
|
||||
func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, num)
|
||||
if err := x.Limit(num, offset).Asc("id").Find(&repos); err != nil {
|
||||
// RepositoriesWithUsers returns number of repos in given page.
|
||||
func RepositoriesWithUsers(page, pageSize int) (_ []*Repository, err error) {
|
||||
repos := make([]*Repository, 0, pageSize)
|
||||
if err = x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
repo.Owner = &User{Id: repo.OwnerID}
|
||||
has, err := x.Get(repo.Owner)
|
||||
if err != nil {
|
||||
for i := range repos {
|
||||
if err = repos[i].GetOwner(); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist{repo.OwnerID, ""}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,9 +910,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
}
|
||||
|
||||
// Remove redundant collaborators.
|
||||
collaborators, err := repo.GetCollaborators()
|
||||
collaborators, err := repo.getCollaborators(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCollaborators: %v", err)
|
||||
return fmt.Errorf("getCollaborators: %v", err)
|
||||
}
|
||||
|
||||
// Dummy object.
|
||||
@@ -859,9 +948,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
}
|
||||
|
||||
if newOwner.IsOrganization() {
|
||||
t, err := newOwner.GetOwnerTeam()
|
||||
t, err := newOwner.getOwnerTeam(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetOwnerTeam: %v", err)
|
||||
return fmt.Errorf("getOwnerTeam: %v", err)
|
||||
} else if err = t.addRepository(sess, repo); err != nil {
|
||||
return fmt.Errorf("add to owner team: %v", err)
|
||||
}
|
||||
@@ -940,13 +1029,11 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
|
||||
if err = repo.getOwner(e); err != nil {
|
||||
return fmt.Errorf("getOwner: %v", err)
|
||||
}
|
||||
if !repo.Owner.IsOrganization() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Organization repository need to recalculate access table when visivility is changed.
|
||||
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
|
||||
return fmt.Errorf("recalculateTeamAccesses: %v", err)
|
||||
if repo.Owner.IsOrganization() {
|
||||
// Organization repository need to recalculate access table when visivility is changed.
|
||||
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
|
||||
return fmt.Errorf("recalculateTeamAccesses: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
forkRepos, err := getRepositoriesByForkID(e, repo.ID)
|
||||
@@ -1084,7 +1171,7 @@ func DeleteRepository(uid, repoID int64) error {
|
||||
desc := fmt.Sprintf("delete repository files[%s]: %v", repoPath, err)
|
||||
log.Warn(desc)
|
||||
if err = CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error(4, "add notice: %v", err)
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,9 +1272,13 @@ func GetRecentUpdatedRepositories(page int) (repos []*Repository, err error) {
|
||||
Where("is_private=?", false).Limit(setting.ExplorePagingNum).Desc("updated").Find(&repos)
|
||||
}
|
||||
|
||||
func getRepositoryCount(e Engine, u *User) (int64, error) {
|
||||
return x.Count(&Repository{OwnerID: u.Id})
|
||||
}
|
||||
|
||||
// GetRepositoryCount returns the total number of repositories of user.
|
||||
func GetRepositoryCount(u *User) (int64, error) {
|
||||
return x.Count(&Repository{OwnerID: u.Id})
|
||||
return getRepositoryCount(x, u)
|
||||
}
|
||||
|
||||
type SearchOption struct {
|
||||
@@ -1212,7 +1303,7 @@ func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) {
|
||||
sess.Where("owner_id=?", opt.Uid)
|
||||
}
|
||||
if !opt.Private {
|
||||
sess.And("is_private=false")
|
||||
sess.And("is_private=?", false)
|
||||
}
|
||||
sess.And("lower_name like ?", "%"+opt.Keyword+"%").Find(&repos)
|
||||
return repos, err
|
||||
@@ -1223,10 +1314,14 @@ func DeleteRepositoryArchives() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return err
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepositoryArchives[%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return os.RemoveAll(filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "archives"))
|
||||
return os.RemoveAll(filepath.Join(repoPath, "archives"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1235,10 +1330,14 @@ func RewriteRepositoryUpdateHook() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return err
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("RewriteRepositoryUpdateHook[%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return createUpdateHook(RepoPath(repo.Owner.Name, repo.Name))
|
||||
return createUpdateHook(repoPath)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1405,6 +1504,12 @@ func CheckRepoStats() {
|
||||
"UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?",
|
||||
"user count 'num_repos'",
|
||||
},
|
||||
// Issue.NumComments
|
||||
{
|
||||
"SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)",
|
||||
"UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?",
|
||||
"issue count 'num_comments'",
|
||||
},
|
||||
}
|
||||
for i := range checkers {
|
||||
repoStatsCheck(checkers[i])
|
||||
@@ -1555,15 +1660,19 @@ type Watch struct {
|
||||
RepoID int64 `xorm:"UNIQUE(watch)"`
|
||||
}
|
||||
|
||||
func isWatching(e Engine, uid, repoId int64) bool {
|
||||
has, _ := e.Get(&Watch{0, uid, repoId})
|
||||
return has
|
||||
}
|
||||
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(uid, repoId int64) bool {
|
||||
has, _ := x.Get(&Watch{0, uid, repoId})
|
||||
return has
|
||||
return isWatching(x, uid, repoId)
|
||||
}
|
||||
|
||||
func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
|
||||
if watch {
|
||||
if IsWatching(uid, repoId) {
|
||||
if isWatching(e, uid, repoId) {
|
||||
return nil
|
||||
}
|
||||
if _, err = e.Insert(&Watch{RepoID: repoId, UserID: uid}); err != nil {
|
||||
@@ -1571,7 +1680,7 @@ func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
|
||||
}
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId)
|
||||
} else {
|
||||
if !IsWatching(uid, repoId) {
|
||||
if !isWatching(e, uid, repoId) {
|
||||
return nil
|
||||
}
|
||||
if _, err = e.Delete(&Watch{0, uid, repoId}); err != nil {
|
||||
@@ -1598,6 +1707,16 @@ func GetWatchers(rid int64) ([]*Watch, error) {
|
||||
return getWatchers(x, rid)
|
||||
}
|
||||
|
||||
// Repository.GetWatchers returns all users watching given repository.
|
||||
func (repo *Repository) GetWatchers(offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, 10)
|
||||
offset = (offset - 1) * ItemsPerPage
|
||||
|
||||
err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
|
||||
|
||||
return users, err
|
||||
}
|
||||
|
||||
func notifyWatchers(e Engine, act *Action) error {
|
||||
// Add feeds for user self and all watchers.
|
||||
watches, err := getWatchers(e, act.RepoID)
|
||||
@@ -1639,7 +1758,7 @@ func NotifyWatchers(act *Action) error {
|
||||
|
||||
type Star struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"uid UNIQUE(s)"`
|
||||
UID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
@@ -1675,6 +1794,15 @@ func IsStaring(uid, repoId int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func (repo *Repository) GetStars(offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, 10)
|
||||
offset = (offset - 1) * ItemsPerPage
|
||||
|
||||
err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
|
||||
|
||||
return users, err
|
||||
}
|
||||
|
||||
// ___________ __
|
||||
// \_ _____/__________| | __
|
||||
// | __)/ _ \_ __ \ |/ /
|
||||
@@ -1742,3 +1870,11 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
|
||||
|
||||
return repo, sess.Commit()
|
||||
}
|
||||
|
||||
func (repo *Repository) GetForks() ([]*Repository, error) {
|
||||
forks := make([]*Repository, 0, 10)
|
||||
|
||||
err := x.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
|
||||
return forks, err
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// AccessToken represents a personal access token.
|
||||
type AccessToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"uid INDEX"`
|
||||
UID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
|
||||
@@ -16,43 +16,41 @@ import (
|
||||
)
|
||||
|
||||
type UpdateTask struct {
|
||||
Id int64
|
||||
Uuid string `xorm:"index"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"index"`
|
||||
RefName string
|
||||
OldCommitId string
|
||||
NewCommitId string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
}
|
||||
|
||||
const (
|
||||
MAX_COMMITS int = 5
|
||||
)
|
||||
|
||||
func AddUpdateTask(task *UpdateTask) error {
|
||||
_, err := x.Insert(task)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUpdateTasksByUuid(uuid string) ([]*UpdateTask, error) {
|
||||
// GetUpdateTaskByUUID returns update task by given UUID.
|
||||
func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
|
||||
task := &UpdateTask{
|
||||
Uuid: uuid,
|
||||
UUID: uuid,
|
||||
}
|
||||
tasks := make([]*UpdateTask, 0)
|
||||
err := x.Find(&tasks, task)
|
||||
has, err := x.Get(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUpdateTaskNotExist{uuid}
|
||||
}
|
||||
return tasks, nil
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func DelUpdateTasksByUuid(uuid string) error {
|
||||
_, err := x.Delete(&UpdateTask{Uuid: uuid})
|
||||
func DeleteUpdateTaskByUUID(uuid string) error {
|
||||
_, err := x.Delete(&UpdateTask{UUID: uuid})
|
||||
return err
|
||||
}
|
||||
|
||||
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitId, "0000000")
|
||||
func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName string, userID int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitID, "0000000")
|
||||
if isNew &&
|
||||
strings.HasPrefix(newCommitId, "0000000") {
|
||||
strings.HasPrefix(newCommitID, "0000000") {
|
||||
return fmt.Errorf("old rev and new rev both 000000")
|
||||
}
|
||||
|
||||
@@ -62,23 +60,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
gitUpdate.Dir = f
|
||||
gitUpdate.Run()
|
||||
|
||||
isDel := strings.HasPrefix(newCommitId, "0000000")
|
||||
isDel := strings.HasPrefix(newCommitID, "0000000")
|
||||
if isDel {
|
||||
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
|
||||
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
repo, err := git.OpenRepository(f)
|
||||
gitRepo, err := git.OpenRepository(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.Open repoId: %v", err)
|
||||
}
|
||||
|
||||
ru, err := GetUserByName(repoUserName)
|
||||
user, err := GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.GetUserByName: %v", err)
|
||||
}
|
||||
|
||||
repos, err := GetRepositoryByName(ru.Id, repoName)
|
||||
repo, err := GetRepositoryByName(user.Id, repoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
|
||||
}
|
||||
@@ -86,7 +84,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
// Push tags.
|
||||
if strings.HasPrefix(refName, "refs/tags/") {
|
||||
tagName := git.RefEndName(refName)
|
||||
tag, err := repo.GetTag(tagName)
|
||||
tag, err := gitRepo.GetTag(tagName)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(4, "runUpdate.GetTag: %v", err)
|
||||
}
|
||||
@@ -104,14 +102,14 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
|
||||
commit := &base.PushCommits{}
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.ID, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, commit, oldCommitID, newCommitID); err != nil {
|
||||
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
newCommit, err := repo.GetCommit(newCommitId)
|
||||
newCommit, err := gitRepo.GetCommit(newCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
|
||||
}
|
||||
@@ -121,12 +119,12 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
if isNew {
|
||||
l, err = newCommit.CommitsBefore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Find CommitsBefore erro: %v", err)
|
||||
return fmt.Errorf("CommitsBefore: %v", err)
|
||||
}
|
||||
} else {
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitId)
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Find CommitsBeforeUntil erro: %v", err)
|
||||
return fmt.Errorf("CommitsBeforeUntil: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,22 +137,19 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
var actEmail string
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
commit := e.Value.(*git.Commit)
|
||||
|
||||
if actEmail == "" {
|
||||
actEmail = commit.Committer.Email
|
||||
}
|
||||
commits = append(commits,
|
||||
&base.PushCommit{commit.Id.String(),
|
||||
&base.PushCommit{commit.ID.String(),
|
||||
commit.Message(),
|
||||
commit.Author.Email,
|
||||
commit.Author.Name})
|
||||
if len(commits) >= MAX_COMMITS {
|
||||
break
|
||||
}
|
||||
commit.Author.Name,
|
||||
})
|
||||
}
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitID, newCommitID); err != nil {
|
||||
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
248
models/user.go
248
models/user.go
@@ -55,12 +55,13 @@ type User struct {
|
||||
Name string `xorm:"UNIQUE NOT NULL"`
|
||||
FullName string
|
||||
// Email is the primary email address (to be used for communication).
|
||||
Email string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Email string `xorm:"NOT NULL"`
|
||||
Passwd string `xorm:"NOT NULL"`
|
||||
LoginType LoginType
|
||||
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
LoginName string
|
||||
Type UserType `xorm:"UNIQUE(s)"`
|
||||
Type UserType
|
||||
OwnedOrgs []*User `xorm:"-"`
|
||||
Orgs []*User `xorm:"-"`
|
||||
Repos []*Repository `xorm:"-"`
|
||||
Location string
|
||||
@@ -70,13 +71,14 @@ type User struct {
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
|
||||
// Remember visibility choice for convenience.
|
||||
// Remember visibility choice for convenience, true for private
|
||||
LastRepoVisibility bool
|
||||
|
||||
// Permissions.
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
AllowImportLocal bool // Allow migrate repository by local path
|
||||
|
||||
// Avatar.
|
||||
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
|
||||
@@ -106,11 +108,21 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// CanEditGitHook returns true if user can edit Git hooks.
|
||||
func (u *User) CanEditGitHook() bool {
|
||||
return u.IsAdmin || u.AllowGitHook
|
||||
}
|
||||
|
||||
// CanImportLocal returns true if user can migrate repository by local path.
|
||||
func (u *User) CanImportLocal() bool {
|
||||
return u.IsAdmin || u.AllowImportLocal
|
||||
}
|
||||
|
||||
// EmailAdresses is the list of all email addresses of a user. Can contain the
|
||||
// primary email address, but is not obligatory
|
||||
type EmailAddress struct {
|
||||
Id int64
|
||||
Uid int64 `xorm:"INDEX NOT NULL"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX NOT NULL"`
|
||||
Email string `xorm:"UNIQUE NOT NULL"`
|
||||
IsActivated bool
|
||||
IsPrimary bool `xorm:"-"`
|
||||
@@ -132,42 +144,72 @@ func (u *User) HomeLink() string {
|
||||
return setting.AppSubUrl + "/" + u.Name
|
||||
}
|
||||
|
||||
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
|
||||
func (u *User) GenerateEmailActivateCode(email string) string {
|
||||
code := base.CreateTimeLimitCode(
|
||||
com.ToStr(u.Id)+email+u.LowerName+u.Passwd+u.Rands,
|
||||
setting.Service.ActiveCodeLives, nil)
|
||||
|
||||
// Add tail hex username
|
||||
code += hex.EncodeToString([]byte(u.LowerName))
|
||||
return code
|
||||
}
|
||||
|
||||
// GenerateActivateCode generates an activate code based on user information.
|
||||
func (u *User) GenerateActivateCode() string {
|
||||
return u.GenerateEmailActivateCode(u.Email)
|
||||
}
|
||||
|
||||
// CustomAvatarPath returns user custom avatar file path.
|
||||
func (u *User) CustomAvatarPath() string {
|
||||
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
|
||||
}
|
||||
|
||||
// GenerateRandomAvatar generates a random avatar for user.
|
||||
func (u *User) GenerateRandomAvatar() error {
|
||||
seed := u.Email
|
||||
if len(seed) == 0 {
|
||||
seed = u.Name
|
||||
}
|
||||
|
||||
img, err := avatar.RandomImage([]byte(seed))
|
||||
if err != nil {
|
||||
return fmt.Errorf("RandomImage: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(path.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("MkdirAll: %v", err)
|
||||
}
|
||||
fw, err := os.Create(u.CustomAvatarPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, img, nil); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
|
||||
log.Info("New random avatar created: %d", u.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) RelAvatarLink() string {
|
||||
defaultImgUrl := "/img/avatar_default.jpg"
|
||||
if u.Id == -1 {
|
||||
return defaultImgUrl
|
||||
}
|
||||
|
||||
imgPath := path.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
if !com.IsExist(imgPath) {
|
||||
if !com.IsExist(u.CustomAvatarPath()) {
|
||||
return defaultImgUrl
|
||||
}
|
||||
return "/avatars/" + com.ToStr(u.Id)
|
||||
case setting.DisableGravatar, setting.OfflineMode:
|
||||
if !com.IsExist(imgPath) {
|
||||
img, err := avatar.RandomImage([]byte(u.Email))
|
||||
if err != nil {
|
||||
log.Error(3, "RandomImage: %v", err)
|
||||
return defaultImgUrl
|
||||
if !com.IsExist(u.CustomAvatarPath()) {
|
||||
if err := u.GenerateRandomAvatar(); err != nil {
|
||||
log.Error(3, "GenerateRandomAvatar: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(path.Dir(imgPath), os.ModePerm); err != nil {
|
||||
log.Error(3, "MkdirAll: %v", err)
|
||||
return defaultImgUrl
|
||||
}
|
||||
fw, err := os.Create(imgPath)
|
||||
if err != nil {
|
||||
log.Error(3, "Create: %v", err)
|
||||
return defaultImgUrl
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, img, nil); err != nil {
|
||||
log.Error(3, "Encode: %v", err)
|
||||
return defaultImgUrl
|
||||
}
|
||||
log.Info("New random avatar created: %d", u.Id)
|
||||
}
|
||||
|
||||
return "/avatars/" + com.ToStr(u.Id)
|
||||
@@ -208,11 +250,6 @@ func (u *User) ValidatePassword(passwd string) bool {
|
||||
return u.Passwd == newUser.Passwd
|
||||
}
|
||||
|
||||
// CustomAvatarPath returns user custom avatar file path.
|
||||
func (u *User) CustomAvatarPath() string {
|
||||
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
|
||||
}
|
||||
|
||||
// UploadAvatar saves custom avatar for user.
|
||||
// FIXME: split uploads to different subdirs in case we have massive users.
|
||||
func (u *User) UploadAvatar(data []byte) error {
|
||||
@@ -222,28 +259,27 @@ func (u *User) UploadAvatar(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := resize.Resize(234, 234, img, resize.NearestNeighbor)
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
|
||||
fw, err := os.Create(u.CustomAvatarPath())
|
||||
if err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, m, nil); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -284,9 +320,13 @@ func (u *User) IsPublicMember(orgId int64) bool {
|
||||
return IsPublicMembership(orgId, u.Id)
|
||||
}
|
||||
|
||||
func (u *User) getOrganizationCount(e Engine) (int64, error) {
|
||||
return e.Where("uid=?", u.Id).Count(new(OrgUser))
|
||||
}
|
||||
|
||||
// GetOrganizationCount returns count of membership of organization of user.
|
||||
func (u *User) GetOrganizationCount() (int64, error) {
|
||||
return x.Where("uid=?", u.Id).Count(new(OrgUser))
|
||||
return u.getOrganizationCount(x)
|
||||
}
|
||||
|
||||
// GetRepositories returns all repositories that user owns, including private repositories.
|
||||
@@ -295,6 +335,12 @@ func (u *User) GetRepositories() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOwnedOrganizations returns all organizations that user owns.
|
||||
func (u *User) GetOwnedOrganizations() (err error) {
|
||||
u.OwnedOrgs, err = GetOwnedOrgsByUserID(u.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOrganizations returns all organizations that user belongs to.
|
||||
func (u *User) GetOrganizations() error {
|
||||
ous, err := GetOrgUsersByUserId(u.Id)
|
||||
@@ -413,11 +459,10 @@ func CountUsers() int64 {
|
||||
return countUsers(x)
|
||||
}
|
||||
|
||||
// GetUsers returns given number of user objects with offset.
|
||||
func GetUsers(num, offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, num)
|
||||
err := x.Limit(num, offset).Where("type=0").Asc("id").Find(&users)
|
||||
return users, err
|
||||
// Users returns number of users in given page.
|
||||
func Users(page, pageSize int) ([]*User, error) {
|
||||
users := make([]*User, 0, pageSize)
|
||||
return users, x.Limit(pageSize, (page-1)*pageSize).Where("type=0").Asc("id").Find(&users)
|
||||
}
|
||||
|
||||
// get user by erify code
|
||||
@@ -490,12 +535,20 @@ func ChangeUserName(u *User, newUserName string) (err error) {
|
||||
}
|
||||
|
||||
func updateUser(e Engine, u *User) error {
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return ErrEmailAlreadyUsed{u.Email}
|
||||
// Organization does not need e-mail.
|
||||
if !u.IsOrganization() {
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return ErrEmailAlreadyUsed{u.Email}
|
||||
}
|
||||
|
||||
if len(u.AvatarEmail) == 0 {
|
||||
u.AvatarEmail = u.Email
|
||||
}
|
||||
u.Avatar = avatar.HashEmail(u.AvatarEmail)
|
||||
}
|
||||
|
||||
u.LowerName = strings.ToLower(u.Name)
|
||||
@@ -510,13 +563,8 @@ func updateUser(e Engine, u *User) error {
|
||||
u.Description = u.Description[:255]
|
||||
}
|
||||
|
||||
if u.AvatarEmail == "" {
|
||||
u.AvatarEmail = u.Email
|
||||
}
|
||||
u.Avatar = avatar.HashEmail(u.AvatarEmail)
|
||||
|
||||
u.FullName = base.Sanitizer.Sanitize(u.FullName)
|
||||
_, err = e.Id(u.Id).AllCols().Update(u)
|
||||
_, err := e.Id(u.Id).AllCols().Update(u)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -525,8 +573,8 @@ func UpdateUser(u *User) error {
|
||||
return updateUser(x, u)
|
||||
}
|
||||
|
||||
// DeleteBeans deletes all given beans, beans should contain delete conditions.
|
||||
func DeleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
// deleteBeans deletes all given beans, beans should contain delete conditions.
|
||||
func deleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
for i := range beans {
|
||||
if _, err = e.Delete(beans[i]); err != nil {
|
||||
return err
|
||||
@@ -536,14 +584,12 @@ func DeleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
}
|
||||
|
||||
// FIXME: need some kind of mechanism to record failure. HINT: system notice
|
||||
// DeleteUser completely and permanently deletes everything of a user,
|
||||
// but issues/comments/pulls will be kept and shown as someone has been deleted.
|
||||
func DeleteUser(u *User) error {
|
||||
func deleteUser(e *xorm.Session, u *User) error {
|
||||
// Note: A user owns any repository or belongs to any organization
|
||||
// cannot perform delete operation.
|
||||
|
||||
// Check ownership of repository.
|
||||
count, err := GetRepositoryCount(u)
|
||||
count, err := getRepositoryCount(e, u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryCount: %v", err)
|
||||
} else if count > 0 {
|
||||
@@ -551,26 +597,20 @@ func DeleteUser(u *User) error {
|
||||
}
|
||||
|
||||
// Check membership of organization.
|
||||
count, err = u.GetOrganizationCount()
|
||||
count, err = u.getOrganizationCount(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetOrganizationCount: %v", err)
|
||||
} else if count > 0 {
|
||||
return ErrUserHasOrgs{UID: u.Id}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ***** START: Watch *****
|
||||
watches := make([]*Watch, 0, 10)
|
||||
if err = x.Find(&watches, &Watch{UserID: u.Id}); err != nil {
|
||||
if err = e.Find(&watches, &Watch{UserID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all watches: %v", err)
|
||||
}
|
||||
for i := range watches {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
|
||||
if _, err = e.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
|
||||
return fmt.Errorf("decrease repository watch number[%d]: %v", watches[i].RepoID, err)
|
||||
}
|
||||
}
|
||||
@@ -578,11 +618,11 @@ func DeleteUser(u *User) error {
|
||||
|
||||
// ***** START: Star *****
|
||||
stars := make([]*Star, 0, 10)
|
||||
if err = x.Find(&stars, &Star{UID: u.Id}); err != nil {
|
||||
if err = e.Find(&stars, &Star{UID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all stars: %v", err)
|
||||
}
|
||||
for i := range stars {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
|
||||
if _, err = e.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
|
||||
return fmt.Errorf("decrease repository star number[%d]: %v", stars[i].RepoID, err)
|
||||
}
|
||||
}
|
||||
@@ -590,18 +630,17 @@ func DeleteUser(u *User) error {
|
||||
|
||||
// ***** START: Follow *****
|
||||
followers := make([]*Follow, 0, 10)
|
||||
if err = x.Find(&followers, &Follow{UserID: u.Id}); err != nil {
|
||||
if err = e.Find(&followers, &Follow{UserID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all followers: %v", err)
|
||||
}
|
||||
for i := range followers {
|
||||
if _, err = sess.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
|
||||
if _, err = e.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
|
||||
return fmt.Errorf("decrease user follower number[%d]: %v", followers[i].UserID, err)
|
||||
}
|
||||
}
|
||||
// ***** END: Follow *****
|
||||
|
||||
if err = DeleteBeans(sess,
|
||||
&Oauth2{Uid: u.Id},
|
||||
if err = deleteBeans(e,
|
||||
&AccessToken{UID: u.Id},
|
||||
&Collaboration{UserID: u.Id},
|
||||
&Access{UserID: u.Id},
|
||||
@@ -610,36 +649,32 @@ func DeleteUser(u *User) error {
|
||||
&Follow{FollowID: u.Id},
|
||||
&Action{UserID: u.Id},
|
||||
&IssueUser{UID: u.Id},
|
||||
&EmailAddress{Uid: u.Id},
|
||||
&EmailAddress{UID: u.Id},
|
||||
); err != nil {
|
||||
return fmt.Errorf("DeleteBeans: %v", err)
|
||||
return fmt.Errorf("deleteUser: %v", err)
|
||||
}
|
||||
|
||||
// ***** START: PublicKey *****
|
||||
keys := make([]*PublicKey, 0, 10)
|
||||
if err = sess.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
|
||||
if err = e.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all public keys: %v", err)
|
||||
}
|
||||
for _, key := range keys {
|
||||
if err = deletePublicKey(sess, key.ID); err != nil {
|
||||
if err = deletePublicKey(e, key.ID); err != nil {
|
||||
return fmt.Errorf("deletePublicKey: %v", err)
|
||||
}
|
||||
}
|
||||
// ***** END: PublicKey *****
|
||||
|
||||
// Clear assignee.
|
||||
if _, err = sess.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
|
||||
if _, err = e.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
|
||||
return fmt.Errorf("clear assignee: %v", err)
|
||||
}
|
||||
|
||||
if _, err = sess.Id(u.Id).Delete(new(User)); err != nil {
|
||||
if _, err = e.Id(u.Id).Delete(new(User)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: system notice
|
||||
// Note: There are something just cannot be roll back,
|
||||
// so just keep error logs of those operations.
|
||||
@@ -651,6 +686,23 @@ func DeleteUser(u *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser completely and permanently deletes everything of a user,
|
||||
// but issues/comments/pulls will be kept and shown as someone has been deleted.
|
||||
func DeleteUser(u *User) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = deleteUser(sess, u); err != nil {
|
||||
// Note: don't wrapper error here.
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// DeleteInactivateUsers deletes all inactivate users and email addresses.
|
||||
func DeleteInactivateUsers() (err error) {
|
||||
users := make([]*User, 0, 10)
|
||||
@@ -676,9 +728,9 @@ func UserPath(userName string) string {
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
func GetUserByKeyID(keyID int64) (*User, error) {
|
||||
user := new(User)
|
||||
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user)
|
||||
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyID).Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
@@ -805,11 +857,11 @@ func AddEmailAddress(email *EmailAddress) error {
|
||||
|
||||
func (email *EmailAddress) Activate() error {
|
||||
email.IsActivated = true
|
||||
if _, err := x.Id(email.Id).AllCols().Update(email); err != nil {
|
||||
if _, err := x.Id(email.ID).AllCols().Update(email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user, err := GetUserByID(email.Uid); err != nil {
|
||||
if user, err := GetUserByID(email.UID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
user.Rands = GetUserSalt()
|
||||
@@ -825,7 +877,7 @@ func DeleteEmailAddress(email *EmailAddress) error {
|
||||
return ErrEmailNotExist
|
||||
}
|
||||
|
||||
if _, err = x.Id(email.Id).Delete(email); err != nil {
|
||||
if _, err = x.Id(email.ID).Delete(email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -845,12 +897,12 @@ func MakeEmailPrimary(email *EmailAddress) error {
|
||||
return ErrEmailNotActivated
|
||||
}
|
||||
|
||||
user := &User{Id: email.Uid}
|
||||
user := &User{Id: email.UID}
|
||||
has, err = x.Get(user)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrUserNotExist{email.Uid, ""}
|
||||
return ErrUserNotExist{email.UID, ""}
|
||||
}
|
||||
|
||||
// Make sure the former primary email doesn't disappear
|
||||
@@ -859,7 +911,7 @@ func MakeEmailPrimary(email *EmailAddress) error {
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
former_primary_email.Uid = user.Id
|
||||
former_primary_email.UID = user.Id
|
||||
former_primary_email.IsActivated = user.IsActive
|
||||
x.Insert(former_primary_email)
|
||||
}
|
||||
@@ -936,7 +988,7 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return GetUserByID(emailAddress.Uid)
|
||||
return GetUserByID(emailAddress.UID)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{0, "email"}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
@@ -435,39 +436,65 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
|
||||
return nil
|
||||
}
|
||||
|
||||
type hookQueue struct {
|
||||
// Make sure one repository only occur once in the queue.
|
||||
lock sync.Mutex
|
||||
repoIDs map[int64]bool
|
||||
// UniqueQueue represents a queue that guarantees only one instance of same ID is in the line.
|
||||
type UniqueQueue struct {
|
||||
lock sync.Mutex
|
||||
ids map[string]bool
|
||||
|
||||
queue chan int64
|
||||
queue chan string
|
||||
}
|
||||
|
||||
func (q *hookQueue) removeRepoID(id int64) {
|
||||
func (q *UniqueQueue) Queue() <-chan string {
|
||||
return q.queue
|
||||
}
|
||||
|
||||
func NewUniqueQueue(queueLength int) *UniqueQueue {
|
||||
if queueLength <= 0 {
|
||||
queueLength = 100
|
||||
}
|
||||
|
||||
return &UniqueQueue{
|
||||
ids: make(map[string]bool),
|
||||
queue: make(chan string, queueLength),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *UniqueQueue) Remove(id interface{}) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
delete(q.repoIDs, id)
|
||||
delete(q.ids, com.ToStr(id))
|
||||
}
|
||||
|
||||
func (q *hookQueue) addRepoID(id int64) {
|
||||
q.lock.Lock()
|
||||
if q.repoIDs[id] {
|
||||
q.lock.Unlock()
|
||||
func (q *UniqueQueue) AddFunc(id interface{}, fn func()) {
|
||||
newid := com.ToStr(id)
|
||||
|
||||
if q.Exist(id) {
|
||||
return
|
||||
}
|
||||
q.repoIDs[id] = true
|
||||
|
||||
q.lock.Lock()
|
||||
q.ids[newid] = true
|
||||
if fn != nil {
|
||||
fn()
|
||||
}
|
||||
q.lock.Unlock()
|
||||
q.queue <- id
|
||||
q.queue <- newid
|
||||
}
|
||||
|
||||
// AddRepoID adds repository ID to hook delivery queue.
|
||||
func (q *hookQueue) AddRepoID(id int64) {
|
||||
go q.addRepoID(id)
|
||||
func (q *UniqueQueue) Add(id interface{}) {
|
||||
q.AddFunc(id, nil)
|
||||
}
|
||||
|
||||
var HookQueue *hookQueue
|
||||
func (q *UniqueQueue) Exist(id interface{}) bool {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
func deliverHook(t *HookTask) {
|
||||
return q.ids[com.ToStr(id)]
|
||||
}
|
||||
|
||||
var HookQueue = NewUniqueQueue(setting.Webhook.QueueLength)
|
||||
|
||||
func (t *HookTask) deliver() {
|
||||
t.IsDelivered = true
|
||||
|
||||
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
|
||||
@@ -549,12 +576,13 @@ func deliverHook(t *HookTask) {
|
||||
}
|
||||
|
||||
// DeliverHooks checks and delivers undelivered hooks.
|
||||
// TODO: shoot more hooks at same time.
|
||||
func DeliverHooks() {
|
||||
tasks := make([]*HookTask, 0, 10)
|
||||
x.Where("is_delivered=?", false).Iterate(new(HookTask),
|
||||
func(idx int, bean interface{}) error {
|
||||
t := bean.(*HookTask)
|
||||
deliverHook(t)
|
||||
t.deliver()
|
||||
tasks = append(tasks, t)
|
||||
return nil
|
||||
})
|
||||
@@ -566,15 +594,10 @@ func DeliverHooks() {
|
||||
}
|
||||
}
|
||||
|
||||
HookQueue = &hookQueue{
|
||||
lock: sync.Mutex{},
|
||||
repoIDs: make(map[int64]bool),
|
||||
queue: make(chan int64, setting.Webhook.QueueLength),
|
||||
}
|
||||
|
||||
// Start listening on new hook requests.
|
||||
for repoID := range HookQueue.queue {
|
||||
HookQueue.removeRepoID(repoID)
|
||||
for repoID := range HookQueue.Queue() {
|
||||
log.Trace("DeliverHooks[%v]: processing delivery hooks", repoID)
|
||||
HookQueue.Remove(repoID)
|
||||
|
||||
tasks = make([]*HookTask, 0, 5)
|
||||
if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
|
||||
@@ -582,9 +605,10 @@ func DeliverHooks() {
|
||||
continue
|
||||
}
|
||||
for _, t := range tasks {
|
||||
deliverHook(t)
|
||||
t.deliver()
|
||||
if err := UpdateHookTask(t); err != nil {
|
||||
log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
|
||||
log.Error(4, "UpdateHookTask[%d]: %v", t.ID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,36 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
)
|
||||
|
||||
type AdminCrateUserForm struct {
|
||||
LoginType string `binding:"Required"`
|
||||
LoginName string
|
||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
SendNotify bool
|
||||
}
|
||||
|
||||
func (f *AdminCrateUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type AdminEditUserForm struct {
|
||||
FullName string `form:"fullname" binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"OmitEmpty;MinSize(6);MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Avatar string `binding:"Required;Email;MaxSize(50)"`
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
LoginType int
|
||||
LoginType string `binding:"Required"`
|
||||
LoginName string
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
AllowImportLocal bool
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -7,8 +7,8 @@ package apiv1
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
)
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/macaron-contrib/session"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/session"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
@@ -181,7 +181,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func getSize(field reflect.StructField, prefix string) string {
|
||||
func getRuleBody(field reflect.StructField, prefix string) string {
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if strings.HasPrefix(rule, prefix) {
|
||||
return rule[len(prefix) : len(rule)-1]
|
||||
@@ -191,15 +191,19 @@ func getSize(field reflect.StructField, prefix string) string {
|
||||
}
|
||||
|
||||
func GetSize(field reflect.StructField) string {
|
||||
return getSize(field, "Size(")
|
||||
return getRuleBody(field, "Size(")
|
||||
}
|
||||
|
||||
func GetMinSize(field reflect.StructField) string {
|
||||
return getSize(field, "MinSize(")
|
||||
return getRuleBody(field, "MinSize(")
|
||||
}
|
||||
|
||||
func GetMaxSize(field reflect.StructField) string {
|
||||
return getSize(field, "MaxSize(")
|
||||
return getRuleBody(field, "MaxSize(")
|
||||
}
|
||||
|
||||
func GetInclude(field reflect.StructField) string {
|
||||
return getRuleBody(field, "Include(")
|
||||
}
|
||||
|
||||
// FIXME: struct contains a struct
|
||||
@@ -260,6 +264,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro
|
||||
data["ErrorMsg"] = trName + l.Tr("form.email_error")
|
||||
case binding.ERR_URL:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.url_error")
|
||||
case binding.ERR_INCLUDE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
|
||||
default:
|
||||
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
|
||||
}
|
||||
|
||||
@@ -5,33 +5,33 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type AuthenticationForm struct {
|
||||
ID int64 `form:"id"`
|
||||
Type int
|
||||
Name string `binding:"Required;MaxSize(50)"`
|
||||
Host string
|
||||
Port int
|
||||
UseSSL bool `form:"use_ssl"`
|
||||
BindDN string `form:"bind_dn"`
|
||||
BindPassword string
|
||||
UserBase string
|
||||
AttributeName string
|
||||
AttributeSurname string
|
||||
AttributeMail string
|
||||
Filter string
|
||||
AdminFilter string
|
||||
IsActived bool
|
||||
SMTPAuth string `form:"smtp_auth"`
|
||||
SMTPHost string `form:"smtp_host"`
|
||||
SMTPPort int `form:"smtp_port"`
|
||||
TLS bool `form:"tls"`
|
||||
SkipVerify bool
|
||||
AllowAutoRegister bool `form:"allowautoregister"`
|
||||
PAMServiceName string
|
||||
ID int64
|
||||
Type int `binding:"Range(2,5)"`
|
||||
Name string `binding:"Required;MaxSize(30)"`
|
||||
Host string
|
||||
Port int
|
||||
BindDN string
|
||||
BindPassword string
|
||||
UserBase string
|
||||
UserDN string `form:"user_dn"`
|
||||
AttributeName string
|
||||
AttributeSurname string
|
||||
AttributeMail string
|
||||
Filter string
|
||||
AdminFilter string
|
||||
IsActive bool
|
||||
SMTPAuth string
|
||||
SMTPHost string
|
||||
SMTPPort int
|
||||
AllowedDomains string
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
PAMServiceName string `form:"pam_service_name"`
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -4,61 +4,98 @@ Gogs LDAP Authentication Module
|
||||
## About
|
||||
|
||||
This authentication module attempts to authorize and authenticate a user
|
||||
against an LDAP server. Like most LDAP authentication systems, this module does
|
||||
this in two steps. First, it queries the LDAP server using a Bind DN and
|
||||
searches for the user that is attempting to sign in. If the user is found, the
|
||||
module attempts to bind to the server using the user's supplied credentials. If
|
||||
this succeeds, the user has been authenticated, and his account information is
|
||||
retrieved and passed to the Gogs login infrastructure.
|
||||
against an LDAP server. It provides two methods of authentication: LDAP via
|
||||
BindDN, and LDAP simple authentication.
|
||||
|
||||
LDAP via BindDN functions like most LDAP authentication systems. First, it
|
||||
queries the LDAP server using a Bind DN and searches for the user that is
|
||||
attempting to sign in. If the user is found, the module attempts to bind to the
|
||||
server using the user's supplied credentials. If this succeeds, the user has
|
||||
been authenticated, and his account information is retrieved and passed to the
|
||||
Gogs login infrastructure.
|
||||
|
||||
LDAP simple authentication does not utilize a Bind DN. Instead, it binds
|
||||
directly with the LDAP server using the user's supplied credentials. If the bind
|
||||
succeeds and no filter rules out the user, the user is authenticated.
|
||||
|
||||
LDAP via BindDN is recommended for most users. By using a Bind DN, the server
|
||||
can perform authorization by restricting which entries the Bind DN account can
|
||||
read. Further, using a Bind DN with reduced permissions can reduce security risk
|
||||
in the face of application bugs.
|
||||
|
||||
## Usage
|
||||
|
||||
To use this module, add an LDAP authentication source via the Authentications
|
||||
section in the admin panel. The fields should be set as follows:
|
||||
section in the admin panel. Both the LDAP via BindDN and the simple auth LDAP
|
||||
share the following fields:
|
||||
|
||||
* Authorization Name **(required)**
|
||||
* A name to assign to the new method of authorization.
|
||||
* A name to assign to the new method of authorization.
|
||||
|
||||
* Host **(required)**
|
||||
* The address where the LDAP server can be reached.
|
||||
* Example: mydomain.com
|
||||
* The address where the LDAP server can be reached.
|
||||
* Example: mydomain.com
|
||||
|
||||
* Port **(required)**
|
||||
* The port to use when connecting to the server.
|
||||
* Example: 636
|
||||
* The port to use when connecting to the server.
|
||||
* Example: 636
|
||||
|
||||
* Enable TLS Encryption (optional)
|
||||
* Whether to use TLS when connecting to the LDAP server.
|
||||
* Whether to use TLS when connecting to the LDAP server.
|
||||
|
||||
* Bind DN (optional)
|
||||
* The DN to bind to the LDAP server with when searching for the user.
|
||||
This may be left blank to perform an anonymous search.
|
||||
* Example: cn=Search,dc=mydomain,dc=com
|
||||
|
||||
* Bind Password (optional)
|
||||
* The password for the Bind DN specified above, if any.
|
||||
|
||||
* User Search Base **(required)**
|
||||
* The LDAP base at which user accounts will be searched for.
|
||||
* Example: ou=Users,dc=mydomain,dc=com
|
||||
|
||||
* User Filter **(required)**
|
||||
* An LDAP filter declaring how to find the user record that is attempting
|
||||
to authenticate. The '%s' matching parameter will be substituted with
|
||||
the user's username.
|
||||
* Example: (&(objectClass=posixAccount)(uid=%s))
|
||||
* Admin Filter (optional)
|
||||
* An LDAP filter specifying if a user should be given administrator
|
||||
privileges. If a user accounts passes the filter, the user will be
|
||||
privileged as an administrator.
|
||||
* Example: (objectClass=adminAccount)
|
||||
|
||||
* First name attribute (optional)
|
||||
* The attribute of the user's LDAP record containing the user's first
|
||||
name. This will be used to populate their account information.
|
||||
* Example: givenName
|
||||
* The attribute of the user's LDAP record containing the user's first name.
|
||||
This will be used to populate their account information.
|
||||
* Example: givenName
|
||||
|
||||
* Surname name attribute (optional)
|
||||
* The attribute of the user's LDAP record containing the user's surname
|
||||
This will be used to populate their account information.
|
||||
* Example: sn
|
||||
* Surname attribute (optional)
|
||||
* The attribute of the user's LDAP record containing the user's surname This
|
||||
will be used to populate their account information.
|
||||
* Example: sn
|
||||
|
||||
* E-mail attribute **(required)**
|
||||
* The attribute of the user's LDAP record containing the user's email
|
||||
address. This will be used to populate their account information.
|
||||
* Example: mail
|
||||
* The attribute of the user's LDAP record containing the user's email
|
||||
address. This will be used to populate their account information.
|
||||
* Example: mail
|
||||
|
||||
**LDAP via BindDN** adds the following fields:
|
||||
|
||||
* Bind DN (optional)
|
||||
* The DN to bind to the LDAP server with when searching for the user. This
|
||||
may be left blank to perform an anonymous search.
|
||||
* Example: cn=Search,dc=mydomain,dc=com
|
||||
|
||||
* Bind Password (optional)
|
||||
* The password for the Bind DN specified above, if any. _Note: The password
|
||||
is stored in plaintext at the server. As such, ensure that your Bind DN
|
||||
has as few privileges as possible._
|
||||
|
||||
* User Search Base **(required)**
|
||||
* The LDAP base at which user accounts will be searched for.
|
||||
* Example: ou=Users,dc=mydomain,dc=com
|
||||
|
||||
* User Filter **(required)**
|
||||
* An LDAP filter declaring how to find the user record that is attempting to
|
||||
authenticate. The '%s' matching parameter will be substituted with the
|
||||
user's username.
|
||||
* Example: (&(objectClass=posixAccount)(uid=%s))
|
||||
|
||||
**LDAP using simple auth** adds the following fields:
|
||||
|
||||
* User DN **(required)**
|
||||
* A template to use as the user's DN. The `%s` matching parameter will be
|
||||
substituted with the user's username.
|
||||
* Example: cn=%s,ou=Users,dc=mydomain,dc=com
|
||||
* Example: uid=%s,ou=Users,dc=mydomain,dc=com
|
||||
|
||||
* User Filter **(required)**
|
||||
* An LDAP filter declaring when a user should be allowed to log in. The `%s`
|
||||
matching parameter will be substituted with the user's username.
|
||||
* Example: (&(objectClass=posixAccount)(cn=%s))
|
||||
* Example: (&(objectClass=posixAccount)(uid=%s))
|
||||
|
||||
@@ -7,21 +7,25 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Basic LDAP authentication service
|
||||
type Ldapsource struct {
|
||||
type Source struct {
|
||||
Name string // canonical name (ie. corporate.ad)
|
||||
Host string // LDAP host
|
||||
Port int // port number
|
||||
UseSSL bool // Use SSL
|
||||
SkipVerify bool
|
||||
BindDN string // DN to bind with
|
||||
BindPassword string // Bind DN password
|
||||
UserBase string // Base search path for users
|
||||
UserDN string // Template for the DN of the user for simple auth
|
||||
AttributeName string // First name attribute
|
||||
AttributeSurname string // Surname attribute
|
||||
AttributeMail string // E-mail attribute
|
||||
@@ -30,7 +34,29 @@ type Ldapsource struct {
|
||||
Enabled bool // if this source is disabled
|
||||
}
|
||||
|
||||
func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
func (ls *Source) sanitizedUserQuery(username string) (string, bool) {
|
||||
// See http://tools.ietf.org/search/rfc4515
|
||||
badCharacters := "\x00()*\\"
|
||||
if strings.ContainsAny(username, badCharacters) {
|
||||
log.Debug("'%s' contains invalid query characters. Aborting.", username)
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ls.Filter, username), true
|
||||
}
|
||||
|
||||
func (ls *Source) sanitizedUserDN(username string) (string, bool) {
|
||||
// See http://tools.ietf.org/search/rfc4514: "special characters"
|
||||
badCharacters := "\x00()*\\,='\"#+;<> "
|
||||
if strings.ContainsAny(username, badCharacters) {
|
||||
log.Debug("'%s' contains invalid DN characters. Aborting.", username)
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ls.UserDN, username), true
|
||||
}
|
||||
|
||||
func (ls *Source) FindUserDN(name string) (string, bool) {
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
|
||||
@@ -52,7 +78,11 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
}
|
||||
|
||||
// A search for the user.
|
||||
userFilter := fmt.Sprintf(ls.Filter, name)
|
||||
userFilter, ok := ls.sanitizedUserQuery(name)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
log.Trace("Searching using filter %s", userFilter)
|
||||
search := ldap.NewSearchRequest(
|
||||
ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0,
|
||||
@@ -78,19 +108,32 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
}
|
||||
|
||||
// searchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
|
||||
func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, bool, bool) {
|
||||
userDN, found := ls.FindUserDN(name)
|
||||
if !found {
|
||||
return "", "", "", false, false
|
||||
func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, bool, bool) {
|
||||
var userDN string
|
||||
if directBind {
|
||||
log.Trace("LDAP will bind directly via UserDN template: %s", ls.UserDN)
|
||||
|
||||
var ok bool
|
||||
userDN, ok = ls.sanitizedUserDN(name)
|
||||
if !ok {
|
||||
return "", "", "", false, false
|
||||
}
|
||||
} else {
|
||||
log.Trace("LDAP will use BindDN.")
|
||||
|
||||
var found bool
|
||||
userDN, found = ls.FindUserDN(name)
|
||||
if !found {
|
||||
return "", "", "", false, false
|
||||
}
|
||||
}
|
||||
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
|
||||
log.Error(4, "LDAP Connect error (%s): %v", ls.Host, err)
|
||||
ls.Enabled = false
|
||||
return "", "", "", false, false
|
||||
}
|
||||
|
||||
defer l.Close()
|
||||
|
||||
log.Trace("Binding with userDN: %s", userDN)
|
||||
@@ -101,7 +144,11 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
|
||||
}
|
||||
|
||||
log.Trace("Bound successfully with userDN: %s", userDN)
|
||||
userFilter := fmt.Sprintf(ls.Filter, name)
|
||||
userFilter, ok := ls.sanitizedUserQuery(name)
|
||||
if !ok {
|
||||
return "", "", "", false, false
|
||||
}
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter,
|
||||
[]string{ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
|
||||
@@ -112,7 +159,12 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
|
||||
return "", "", "", false, false
|
||||
} else if len(sr.Entries) < 1 {
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
|
||||
if directBind {
|
||||
log.Error(4, "User filter inhibited user login.")
|
||||
} else {
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
|
||||
}
|
||||
|
||||
return "", "", "", false, false
|
||||
}
|
||||
|
||||
@@ -140,10 +192,12 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
|
||||
return name_attr, sn_attr, mail_attr, admin_attr, true
|
||||
}
|
||||
|
||||
func ldapDial(ls Ldapsource) (*ldap.Conn, error) {
|
||||
func ldapDial(ls *Source) (*ldap.Conn, error) {
|
||||
if ls.UseSSL {
|
||||
log.Debug("Using TLS for LDAP")
|
||||
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
|
||||
log.Debug("Using TLS for LDAP without verifying: %v", ls.SkipVerify)
|
||||
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), &tls.Config{
|
||||
InsecureSkipVerify: ls.SkipVerify,
|
||||
})
|
||||
} else {
|
||||
return ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -17,8 +17,7 @@ import (
|
||||
// \/ /_____/ \/ \/ \/ \/ \/
|
||||
|
||||
type CreateOrgForm struct {
|
||||
OrgName string `form:"org_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
OrgName string `binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
}
|
||||
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -26,13 +25,11 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
|
||||
}
|
||||
|
||||
type UpdateOrgSettingForm struct {
|
||||
OrgUserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
OrgFullName string `form:"fullname" binding:"MaxSize(100)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Description string `form:"desc" binding:"MaxSize(255)"`
|
||||
Website string `form:"website" binding:"Url;MaxSize(100)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Name string `binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
)
|
||||
|
||||
// _______________________________________ _________.______________________ _______________.___.
|
||||
@@ -37,8 +43,8 @@ type MigrateRepoForm struct {
|
||||
AuthPassword string `json:"auth_password"`
|
||||
Uid int64 `json:"uid" binding:"Required"`
|
||||
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Private bool `json:"mirror"`
|
||||
Mirror bool `json:"private"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Private bool `json:"private"`
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
}
|
||||
|
||||
@@ -46,6 +52,34 @@ func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ParseRemoteAddr checks if given remote address is valid,
|
||||
// and returns composed URL with needed username and passowrd.
|
||||
// It also checks if given user has permission when remote address
|
||||
// is actually a local path.
|
||||
func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
|
||||
remoteAddr := f.CloneAddr
|
||||
|
||||
// Remote address can be HTTP/HTTPS/Git URL or local path.
|
||||
if strings.HasPrefix(remoteAddr, "http://") ||
|
||||
strings.HasPrefix(remoteAddr, "https://") ||
|
||||
strings.HasPrefix(remoteAddr, "git://") {
|
||||
u, err := url.Parse(remoteAddr)
|
||||
if err != nil {
|
||||
return "", models.ErrInvalidCloneAddr{IsURLError: true}
|
||||
}
|
||||
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
|
||||
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
} else if !user.CanImportLocal() {
|
||||
return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
|
||||
} else if !com.IsDir(remoteAddr) {
|
||||
return "", models.ErrInvalidCloneAddr{IsInvalidPath: true}
|
||||
}
|
||||
|
||||
return remoteAddr, nil
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
|
||||
@@ -7,8 +7,8 @@ package auth
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type InstallForm struct {
|
||||
@@ -30,7 +30,7 @@ type InstallForm struct {
|
||||
|
||||
SMTPHost string
|
||||
SMTPFrom string
|
||||
SMTPEmail string `binding:"OmitEmpty;Email;MaxSize(50)" locale:"install.mailer_user"`
|
||||
SMTPEmail string `binding:"OmitEmpty;Email;MaxSize(254)" locale:"install.mailer_user"`
|
||||
SMTPPasswd string
|
||||
RegisterConfirm bool
|
||||
MailNotify bool
|
||||
@@ -38,12 +38,13 @@ type InstallForm struct {
|
||||
OfflineMode bool
|
||||
DisableGravatar bool
|
||||
DisableRegistration bool
|
||||
EnableCaptcha bool
|
||||
RequireSignInView bool
|
||||
|
||||
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
|
||||
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
|
||||
AdminConfirmPasswd string
|
||||
AdminEmail string `binding:"OmitEmpty;Email;MaxSize(50)" locale:"install.admin_email"`
|
||||
AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
|
||||
}
|
||||
|
||||
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -58,12 +59,10 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
|
||||
// \/ \/
|
||||
|
||||
type RegisterForm struct {
|
||||
UserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `form:"password" binding:"Required;MaxSize(255)"`
|
||||
Retype string `form:"retype"`
|
||||
LoginType string `form:"logintype"`
|
||||
LoginName string `form:"loginname"`
|
||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
Retype string
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -71,9 +70,9 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
|
||||
}
|
||||
|
||||
type SignInForm struct {
|
||||
UserName string `form:"uname" binding:"Required;MaxSize(254)"`
|
||||
Password string `form:"password" binding:"Required;MaxSize(255)"`
|
||||
Remember bool `form:"remember"`
|
||||
UserName string `binding:"Required;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
Remember bool
|
||||
}
|
||||
|
||||
func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -88,12 +87,12 @@ func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
UserName string `form:"uname" binding:"Required;MaxSize(35)"`
|
||||
FullName string `form:"fullname" binding:"MaxSize(100)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(254)"`
|
||||
Website string `form:"website" binding:"Url;MaxSize(100)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(254)"`
|
||||
Name string `binding:"Required;MaxSize(35)"`
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Gravatar string `binding:"Required;Email;MaxSize(254)"`
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -101,8 +100,8 @@ func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
||||
}
|
||||
|
||||
type UploadAvatarForm struct {
|
||||
Enable bool `form:"enable"`
|
||||
Avatar *multipart.FileHeader `form:"avatar"`
|
||||
Enable bool
|
||||
Avatar *multipart.FileHeader
|
||||
}
|
||||
|
||||
func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -110,7 +109,7 @@ func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) b
|
||||
}
|
||||
|
||||
type AddEmailForm struct {
|
||||
Email string `binding:"Required;Email;MaxSize(50)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
}
|
||||
|
||||
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -32,9 +32,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/issue9/identicon"
|
||||
"github.com/nfnt/resize"
|
||||
|
||||
"github.com/gogits/gogs/modules/identicon"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -8,11 +8,6 @@ const DOC_URL = "https://github.com/gogits/go-gogs-client/wiki"
|
||||
|
||||
type (
|
||||
TplName string
|
||||
|
||||
ApiJsonErr struct {
|
||||
Message string `json:"message"`
|
||||
DocUrl string `json:"url"`
|
||||
}
|
||||
)
|
||||
|
||||
var GoGetMetas = make(map[string]bool)
|
||||
|
||||
@@ -100,11 +100,20 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
|
||||
}
|
||||
|
||||
func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
prefix := strings.Replace(options.urlPrefix, "/src/", "/raw/", 1)
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
link = []byte(path.Join(strings.Replace(options.urlPrefix, "/src/", "/raw/", 1), string(link)))
|
||||
if link[0] != '/' {
|
||||
prefix += "/"
|
||||
}
|
||||
link = []byte(prefix + string(link))
|
||||
}
|
||||
fmt.Println(2, string(link))
|
||||
|
||||
out.WriteString(`<a href="`)
|
||||
out.Write(link)
|
||||
out.WriteString(`">`)
|
||||
options.Renderer.Image(out, link, title, alt)
|
||||
out.WriteString("</a>")
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -59,8 +59,8 @@ func ShortSha(sha1 string) string {
|
||||
func DetectEncoding(content []byte) (string, error) {
|
||||
detector := chardet.NewTextDetector()
|
||||
result, err := detector.DetectBest(content)
|
||||
if result.Charset != "UTF-8" && len(setting.AnsiCharset) > 0 {
|
||||
return setting.AnsiCharset, err
|
||||
if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
|
||||
return setting.Repository.AnsiCharset, err
|
||||
}
|
||||
return result.Charset, err
|
||||
}
|
||||
@@ -76,9 +76,8 @@ func ToUtf8WithErr(content []byte) (error, string) {
|
||||
}
|
||||
|
||||
encoding, _ := charset.Lookup(charsetLabel)
|
||||
|
||||
if encoding == nil {
|
||||
return fmt.Errorf("unknow char decoder %s", charsetLabel), string(content)
|
||||
return fmt.Errorf("unknown char decoder %s", charsetLabel), string(content)
|
||||
}
|
||||
|
||||
result, n, err := transform.String(encoding.NewDecoder(), string(content))
|
||||
@@ -97,13 +96,42 @@ func ToUtf8(content string) string {
|
||||
return res
|
||||
}
|
||||
|
||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||
func RenderCommitMessage(msg, urlPrefix string) template.HTML {
|
||||
return template.HTML(string(RenderIssueIndexPattern([]byte(template.HTMLEscapeString(msg)), urlPrefix)))
|
||||
// Replaces all prefixes 'old' in 's' with 'new'.
|
||||
func ReplaceLeft(s, old, new string) string {
|
||||
old_len, new_len, i, n := len(old), len(new), 0, 0
|
||||
for ; i < len(s) && strings.HasPrefix(s[i:], old); n += 1 {
|
||||
i += old_len
|
||||
}
|
||||
|
||||
// simple optimization
|
||||
if n == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
// allocating space for the new string
|
||||
newLen := n*new_len + len(s[i:])
|
||||
replacement := make([]byte, newLen, newLen)
|
||||
|
||||
j := 0
|
||||
for ; j < n*new_len; j += new_len {
|
||||
copy(replacement[j:j+new_len], new)
|
||||
}
|
||||
|
||||
copy(replacement[j:], s[i:])
|
||||
return string(replacement)
|
||||
}
|
||||
|
||||
var mailDomains = map[string]string{
|
||||
"gmail.com": "gmail.com",
|
||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||
func RenderCommitMessage(msg, urlPrefix string) template.HTML {
|
||||
cleanMsg := template.HTMLEscapeString(msg)
|
||||
fullMessage := string(RenderIssueIndexPattern([]byte(cleanMsg), urlPrefix))
|
||||
msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
|
||||
for i := range msgLines {
|
||||
msgLines[i] = ReplaceLeft(msgLines[i], " ", " ")
|
||||
}
|
||||
|
||||
fullMessage = strings.Join(msgLines, "<br>")
|
||||
return template.HTML(fullMessage)
|
||||
}
|
||||
|
||||
var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
@@ -151,12 +179,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
return "try.gogs.io"
|
||||
}
|
||||
|
||||
suffix := strings.SplitN(mail, "@", 2)[1]
|
||||
domain, ok := mailDomains[suffix]
|
||||
if !ok {
|
||||
return "mail." + suffix
|
||||
}
|
||||
return domain
|
||||
return strings.SplitN(mail, "@", 2)[1]
|
||||
},
|
||||
"SubStr": func(str string, start, length int) string {
|
||||
if len(str) == 0 {
|
||||
@@ -240,7 +263,7 @@ func ActionContent2Commits(act Actioner) *PushCommits {
|
||||
|
||||
func DiffTypeToStr(diffType int) string {
|
||||
diffTypes := map[int]string{
|
||||
1: "add", 2: "modify", 3: "del",
|
||||
1: "add", 2: "modify", 3: "del", 4: "rename",
|
||||
}
|
||||
return diffTypes[diffType]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"hash"
|
||||
"html/template"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var Sanitizer = bluemonday.UGCPolicy()
|
||||
var Sanitizer = bluemonday.UGCPolicy().AllowAttrs("class").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).OnElements("code")
|
||||
|
||||
// Encode string to md5 hex value.
|
||||
func EncodeMd5(str string) string {
|
||||
|
||||
File diff suppressed because one or more lines are too long
615
modules/crypto/ssh/agent/client.go
Executable file
615
modules/crypto/ssh/agent/client.go
Executable file
@@ -0,0 +1,615 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package agent implements a client to an ssh-agent daemon.
|
||||
|
||||
References:
|
||||
[PROTOCOL.agent]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent?rev=HEAD
|
||||
*/
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// Agent represents the capabilities of an ssh-agent.
|
||||
type Agent interface {
|
||||
// List returns the identities known to the agent.
|
||||
List() ([]*Key, error)
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
// in [PROTOCOL.agent] section 2.6.2.
|
||||
Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error)
|
||||
|
||||
// Add adds a private key to the agent.
|
||||
Add(key AddedKey) error
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
Remove(key ssh.PublicKey) error
|
||||
|
||||
// RemoveAll removes all identities.
|
||||
RemoveAll() error
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||
Lock(passphrase []byte) error
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
Unlock(passphrase []byte) error
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
Signers() ([]ssh.Signer, error)
|
||||
}
|
||||
|
||||
// AddedKey describes an SSH key to be added to an Agent.
|
||||
type AddedKey struct {
|
||||
// PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey or
|
||||
// *ecdsa.PrivateKey, which will be inserted into the agent.
|
||||
PrivateKey interface{}
|
||||
// Certificate, if not nil, is communicated to the agent and will be
|
||||
// stored with the key.
|
||||
Certificate *ssh.Certificate
|
||||
// Comment is an optional, free-form string.
|
||||
Comment string
|
||||
// LifetimeSecs, if not zero, is the number of seconds that the
|
||||
// agent will store the key for.
|
||||
LifetimeSecs uint32
|
||||
// ConfirmBeforeUse, if true, requests that the agent confirm with the
|
||||
// user before each use of this key.
|
||||
ConfirmBeforeUse bool
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 3.
|
||||
const (
|
||||
agentRequestV1Identities = 1
|
||||
|
||||
// 3.2 Requests from client to agent for protocol 2 key operations
|
||||
agentAddIdentity = 17
|
||||
agentRemoveIdentity = 18
|
||||
agentRemoveAllIdentities = 19
|
||||
agentAddIdConstrained = 25
|
||||
|
||||
// 3.3 Key-type independent requests from client to agent
|
||||
agentAddSmartcardKey = 20
|
||||
agentRemoveSmartcardKey = 21
|
||||
agentLock = 22
|
||||
agentUnlock = 23
|
||||
agentAddSmartcardKeyConstrained = 26
|
||||
|
||||
// 3.7 Key constraint identifiers
|
||||
agentConstrainLifetime = 1
|
||||
agentConstrainConfirm = 2
|
||||
)
|
||||
|
||||
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
||||
// is a sanity check, not a limit in the spec.
|
||||
const maxAgentResponseBytes = 16 << 20
|
||||
|
||||
// Agent messages:
|
||||
// These structures mirror the wire format of the corresponding ssh agent
|
||||
// messages found in [PROTOCOL.agent].
|
||||
|
||||
// 3.4 Generic replies from agent to client
|
||||
const agentFailure = 5
|
||||
|
||||
type failureAgentMsg struct{}
|
||||
|
||||
const agentSuccess = 6
|
||||
|
||||
type successAgentMsg struct{}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.2.
|
||||
const agentRequestIdentities = 11
|
||||
|
||||
type requestIdentitiesAgentMsg struct{}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.5.2.
|
||||
const agentIdentitiesAnswer = 12
|
||||
|
||||
type identitiesAnswerAgentMsg struct {
|
||||
NumKeys uint32 `sshtype:"12"`
|
||||
Keys []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.6.2.
|
||||
const agentSignRequest = 13
|
||||
|
||||
type signRequestAgentMsg struct {
|
||||
KeyBlob []byte `sshtype:"13"`
|
||||
Data []byte
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// See [PROTOCOL.agent], section 2.6.2.
|
||||
|
||||
// 3.6 Replies from agent to client for protocol 2 key operations
|
||||
const agentSignResponse = 14
|
||||
|
||||
type signResponseAgentMsg struct {
|
||||
SigBlob []byte `sshtype:"14"`
|
||||
}
|
||||
|
||||
type publicKey struct {
|
||||
Format string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Key represents a protocol 2 public key as defined in
|
||||
// [PROTOCOL.agent], section 2.5.2.
|
||||
type Key struct {
|
||||
Format string
|
||||
Blob []byte
|
||||
Comment string
|
||||
}
|
||||
|
||||
func clientErr(err error) error {
|
||||
return fmt.Errorf("agent: client error: %v", err)
|
||||
}
|
||||
|
||||
// String returns the storage form of an agent key with the format, base64
|
||||
// encoded serialized key, and the comment if it is not empty.
|
||||
func (k *Key) String() string {
|
||||
s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob)
|
||||
|
||||
if k.Comment != "" {
|
||||
s += " " + k.Comment
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Type returns the public key type.
|
||||
func (k *Key) Type() string {
|
||||
return k.Format
|
||||
}
|
||||
|
||||
// Marshal returns key blob to satisfy the ssh.PublicKey interface.
|
||||
func (k *Key) Marshal() []byte {
|
||||
return k.Blob
|
||||
}
|
||||
|
||||
// Verify satisfies the ssh.PublicKey interface, but is not
|
||||
// implemented for agent keys.
|
||||
func (k *Key) Verify(data []byte, sig *ssh.Signature) error {
|
||||
return errors.New("agent: agent key does not know how to verify")
|
||||
}
|
||||
|
||||
type wireKey struct {
|
||||
Format string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
func parseKey(in []byte) (out *Key, rest []byte, err error) {
|
||||
var record struct {
|
||||
Blob []byte
|
||||
Comment string
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
if err := ssh.Unmarshal(in, &record); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(record.Blob, &wk); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Key{
|
||||
Format: wk.Format,
|
||||
Blob: record.Blob,
|
||||
Comment: record.Comment,
|
||||
}, record.Rest, nil
|
||||
}
|
||||
|
||||
// client is a client for an ssh-agent process.
|
||||
type client struct {
|
||||
// conn is typically a *net.UnixConn
|
||||
conn io.ReadWriter
|
||||
// mu is used to prevent concurrent access to the agent
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewClient returns an Agent that talks to an ssh-agent process over
|
||||
// the given connection.
|
||||
func NewClient(rw io.ReadWriter) Agent {
|
||||
return &client{conn: rw}
|
||||
}
|
||||
|
||||
// call sends an RPC to the agent. On success, the reply is
|
||||
// unmarshaled into reply and replyType is set to the first byte of
|
||||
// the reply, which contains the type of the message.
|
||||
func (c *client) call(req []byte) (reply interface{}, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
msg := make([]byte, 4+len(req))
|
||||
binary.BigEndian.PutUint32(msg, uint32(len(req)))
|
||||
copy(msg[4:], req)
|
||||
if _, err = c.conn.Write(msg); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
|
||||
var respSizeBuf [4]byte
|
||||
if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
respSize := binary.BigEndian.Uint32(respSizeBuf[:])
|
||||
if respSize > maxAgentResponseBytes {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, respSize)
|
||||
if _, err = io.ReadFull(c.conn, buf); err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
reply, err = unmarshal(buf)
|
||||
if err != nil {
|
||||
return nil, clientErr(err)
|
||||
}
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *client) simpleCall(req []byte) error {
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
func (c *client) RemoveAll() error {
|
||||
return c.simpleCall([]byte{agentRemoveAllIdentities})
|
||||
}
|
||||
|
||||
func (c *client) Remove(key ssh.PublicKey) error {
|
||||
req := ssh.Marshal(&agentRemoveIdentityMsg{
|
||||
KeyBlob: key.Marshal(),
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
func (c *client) Lock(passphrase []byte) error {
|
||||
req := ssh.Marshal(&agentLockMsg{
|
||||
Passphrase: passphrase,
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
func (c *client) Unlock(passphrase []byte) error {
|
||||
req := ssh.Marshal(&agentUnlockMsg{
|
||||
Passphrase: passphrase,
|
||||
})
|
||||
return c.simpleCall(req)
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (c *client) List() ([]*Key, error) {
|
||||
// see [PROTOCOL.agent] section 2.5.2.
|
||||
req := []byte{agentRequestIdentities}
|
||||
|
||||
msg, err := c.call(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *identitiesAnswerAgentMsg:
|
||||
if msg.NumKeys > maxAgentResponseBytes/8 {
|
||||
return nil, errors.New("agent: too many keys in agent reply")
|
||||
}
|
||||
keys := make([]*Key, msg.NumKeys)
|
||||
data := msg.Keys
|
||||
for i := uint32(0); i < msg.NumKeys; i++ {
|
||||
var key *Key
|
||||
var err error
|
||||
if key, data, err = parseKey(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[i] = key
|
||||
}
|
||||
return keys, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to list keys")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Sign has the agent sign the data using a protocol 2 key as defined
|
||||
// in [PROTOCOL.agent] section 2.6.2.
|
||||
func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
req := ssh.Marshal(signRequestAgentMsg{
|
||||
KeyBlob: key.Marshal(),
|
||||
Data: data,
|
||||
})
|
||||
|
||||
msg, err := c.call(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *signResponseAgentMsg:
|
||||
var sig ssh.Signature
|
||||
if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sig, nil
|
||||
case *failureAgentMsg:
|
||||
return nil, errors.New("agent: failed to sign challenge")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// unmarshal parses an agent message in packet, returning the parsed
|
||||
// form and the message type of packet.
|
||||
func unmarshal(packet []byte) (interface{}, error) {
|
||||
if len(packet) < 1 {
|
||||
return nil, errors.New("agent: empty packet")
|
||||
}
|
||||
var msg interface{}
|
||||
switch packet[0] {
|
||||
case agentFailure:
|
||||
return new(failureAgentMsg), nil
|
||||
case agentSuccess:
|
||||
return new(successAgentMsg), nil
|
||||
case agentIdentitiesAnswer:
|
||||
msg = new(identitiesAnswerAgentMsg)
|
||||
case agentSignResponse:
|
||||
msg = new(signResponseAgentMsg)
|
||||
default:
|
||||
return nil, fmt.Errorf("agent: unknown type tag %d", packet[0])
|
||||
}
|
||||
if err := ssh.Unmarshal(packet, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
type rsaKeyMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
N *big.Int
|
||||
E *big.Int
|
||||
D *big.Int
|
||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type dsaKeyMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
G *big.Int
|
||||
Y *big.Int
|
||||
X *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ecdsaKeyMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
Curve string
|
||||
KeyBytes []byte
|
||||
D *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Insert adds a private key to the agent.
|
||||
func (c *client) insertKey(s interface{}, comment string, constraints []byte) error {
|
||||
var req []byte
|
||||
switch k := s.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if len(k.Primes) != 2 {
|
||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||
}
|
||||
k.Precompute()
|
||||
req = ssh.Marshal(rsaKeyMsg{
|
||||
Type: ssh.KeyAlgoRSA,
|
||||
N: k.N,
|
||||
E: big.NewInt(int64(k.E)),
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaKeyMsg{
|
||||
Type: ssh.KeyAlgoDSA,
|
||||
P: k.P,
|
||||
Q: k.Q,
|
||||
G: k.G,
|
||||
Y: k.Y,
|
||||
X: k.X,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *ecdsa.PrivateKey:
|
||||
nistID := fmt.Sprintf("nistp%d", k.Params().BitSize)
|
||||
req = ssh.Marshal(ecdsaKeyMsg{
|
||||
Type: "ecdsa-sha2-" + nistID,
|
||||
Curve: nistID,
|
||||
KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y),
|
||||
D: k.D,
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||
}
|
||||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
type rsaCertMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
CertBytes []byte
|
||||
D *big.Int
|
||||
Iqmp *big.Int // IQMP = Inverse Q Mod P
|
||||
P *big.Int
|
||||
Q *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type dsaCertMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
CertBytes []byte
|
||||
X *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
type ecdsaCertMsg struct {
|
||||
Type string `sshtype:"17"`
|
||||
CertBytes []byte
|
||||
D *big.Int
|
||||
Comments string
|
||||
Constraints []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// Insert adds a private key to the agent. If a certificate is given,
|
||||
// that certificate is added instead as public key.
|
||||
func (c *client) Add(key AddedKey) error {
|
||||
var constraints []byte
|
||||
|
||||
if secs := key.LifetimeSecs; secs != 0 {
|
||||
constraints = append(constraints, agentConstrainLifetime)
|
||||
|
||||
var secsBytes [4]byte
|
||||
binary.BigEndian.PutUint32(secsBytes[:], secs)
|
||||
constraints = append(constraints, secsBytes[:]...)
|
||||
}
|
||||
|
||||
if key.ConfirmBeforeUse {
|
||||
constraints = append(constraints, agentConstrainConfirm)
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert == nil {
|
||||
return c.insertKey(key.PrivateKey, key.Comment, constraints)
|
||||
} else {
|
||||
return c.insertCert(key.PrivateKey, cert, key.Comment, constraints)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error {
|
||||
var req []byte
|
||||
switch k := s.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
if len(k.Primes) != 2 {
|
||||
return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes))
|
||||
}
|
||||
k.Precompute()
|
||||
req = ssh.Marshal(rsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
D: k.D,
|
||||
Iqmp: k.Precomputed.Qinv,
|
||||
P: k.Primes[0],
|
||||
Q: k.Primes[1],
|
||||
Comments: comment,
|
||||
Constraints: constraints,
|
||||
})
|
||||
case *dsa.PrivateKey:
|
||||
req = ssh.Marshal(dsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
X: k.X,
|
||||
Comments: comment,
|
||||
})
|
||||
case *ecdsa.PrivateKey:
|
||||
req = ssh.Marshal(ecdsaCertMsg{
|
||||
Type: cert.Type(),
|
||||
CertBytes: cert.Marshal(),
|
||||
D: k.D,
|
||||
Comments: comment,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("agent: unsupported key type %T", s)
|
||||
}
|
||||
|
||||
// if constraints are present then the message type needs to be changed.
|
||||
if len(constraints) != 0 {
|
||||
req[0] = agentAddIdConstrained
|
||||
}
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||
return errors.New("agent: signer and cert have different public key")
|
||||
}
|
||||
|
||||
resp, err := c.call(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := resp.(*successAgentMsg); ok {
|
||||
return nil
|
||||
}
|
||||
return errors.New("agent: failure")
|
||||
}
|
||||
|
||||
// Signers provides a callback for client authentication.
|
||||
func (c *client) Signers() ([]ssh.Signer, error) {
|
||||
keys, err := c.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []ssh.Signer
|
||||
for _, k := range keys {
|
||||
result = append(result, &agentKeyringSigner{c, k})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type agentKeyringSigner struct {
|
||||
agent *client
|
||||
pub ssh.PublicKey
|
||||
}
|
||||
|
||||
func (s *agentKeyringSigner) PublicKey() ssh.PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
|
||||
// The agent has its own entropy source, so the rand argument is ignored.
|
||||
return s.agent.Sign(s.pub, data)
|
||||
}
|
||||
287
modules/crypto/ssh/agent/client_test.go
Executable file
287
modules/crypto/ssh/agent/client_test.go
Executable file
@@ -0,0 +1,287 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// startAgent executes ssh-agent, and returns a Agent interface to it.
|
||||
func startAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
|
||||
if testing.Short() {
|
||||
// ssh-agent is not always available, and the key
|
||||
// types supported vary by platform.
|
||||
t.Skip("skipping test due to -short")
|
||||
}
|
||||
|
||||
bin, err := exec.LookPath("ssh-agent")
|
||||
if err != nil {
|
||||
t.Skip("could not find ssh-agent")
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, "-s")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("cmd.Output: %v", err)
|
||||
}
|
||||
|
||||
/* Output looks like:
|
||||
|
||||
SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
|
||||
SSH_AGENT_PID=15542; export SSH_AGENT_PID;
|
||||
echo Agent pid 15542;
|
||||
*/
|
||||
fields := bytes.Split(out, []byte(";"))
|
||||
line := bytes.SplitN(fields[0], []byte("="), 2)
|
||||
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||
if string(line[0]) != "SSH_AUTH_SOCK" {
|
||||
t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
|
||||
}
|
||||
socket = string(line[1])
|
||||
|
||||
line = bytes.SplitN(fields[2], []byte("="), 2)
|
||||
line[0] = bytes.TrimLeft(line[0], "\n")
|
||||
if string(line[0]) != "SSH_AGENT_PID" {
|
||||
t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
|
||||
}
|
||||
pidStr := line[1]
|
||||
pid, err := strconv.Atoi(string(pidStr))
|
||||
if err != nil {
|
||||
t.Fatalf("Atoi(%q): %v", pidStr, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", string(socket))
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial: %v", err)
|
||||
}
|
||||
|
||||
ac := NewClient(conn)
|
||||
return ac, socket, func() {
|
||||
proc, _ := os.FindProcess(pid)
|
||||
if proc != nil {
|
||||
proc.Kill()
|
||||
}
|
||||
conn.Close()
|
||||
os.RemoveAll(filepath.Dir(socket))
|
||||
}
|
||||
}
|
||||
|
||||
func testAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
testAgentInterface(t, agent, key, cert, lifetimeSecs)
|
||||
}
|
||||
|
||||
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSignerFromKey(%T): %v", key, err)
|
||||
}
|
||||
// The agent should start up empty.
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Fatalf("RequestIdentities: %v", err)
|
||||
} else if len(keys) > 0 {
|
||||
t.Fatalf("got %d keys, want 0: %v", len(keys), keys)
|
||||
}
|
||||
|
||||
// Attempt to insert the key, with certificate if specified.
|
||||
var pubKey ssh.PublicKey
|
||||
if cert != nil {
|
||||
err = agent.Add(AddedKey{
|
||||
PrivateKey: key,
|
||||
Certificate: cert,
|
||||
Comment: "comment",
|
||||
LifetimeSecs: lifetimeSecs,
|
||||
})
|
||||
pubKey = cert
|
||||
} else {
|
||||
err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
|
||||
pubKey = signer.PublicKey()
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("insert(%T): %v", key, err)
|
||||
}
|
||||
|
||||
// Did the key get inserted successfully?
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
} else if len(keys) != 1 {
|
||||
t.Fatalf("got %v, want 1 key", keys)
|
||||
} else if keys[0].Comment != "comment" {
|
||||
t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment")
|
||||
} else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) {
|
||||
t.Fatalf("key mismatch")
|
||||
}
|
||||
|
||||
// Can the agent make a valid signature?
|
||||
data := []byte("hello")
|
||||
sig, err := agent.Sign(pubKey, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(%s): %v", pubKey.Type(), err)
|
||||
}
|
||||
|
||||
if err := pubKey.Verify(data, sig); err != nil {
|
||||
t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent(t *testing.T) {
|
||||
for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
|
||||
testAgent(t, testPrivateKeys[keyType], nil, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
cert := &ssh.Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: ssh.CertTimeInfinity,
|
||||
CertType: ssh.UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
testAgent(t, testPrivateKeys["rsa"], cert, 0)
|
||||
}
|
||||
|
||||
func TestConstraints(t *testing.T) {
|
||||
testAgent(t, testPrivateKeys["rsa"], nil, 3600 /* lifetime in seconds */)
|
||||
}
|
||||
|
||||
// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
|
||||
// therefore is buffered (net.Pipe deadlocks if both sides start with
|
||||
// a write.)
|
||||
func netPipe() (net.Conn, net.Conn, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer listener.Close()
|
||||
c1, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c2, err := listener.Accept()
|
||||
if err != nil {
|
||||
c1.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c1, c2, nil
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
|
||||
serverConf := ssh.ServerConfig{}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("pubkey rejected")
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{}
|
||||
conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
|
||||
conn, _, _, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestLockClient(t *testing.T) {
|
||||
agent, _, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
testLockAgent(agent, t)
|
||||
}
|
||||
|
||||
func testLockAgent(agent Agent, t *testing.T) {
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["dsa"], Comment: "comment dsa"}); err != nil {
|
||||
t.Errorf("Add: %v", err)
|
||||
}
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 2 {
|
||||
t.Errorf("Want 2 keys, got %v", keys)
|
||||
}
|
||||
|
||||
passphrase := []byte("secret")
|
||||
if err := agent.Lock(passphrase); err != nil {
|
||||
t.Errorf("Lock: %v", err)
|
||||
}
|
||||
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 0 {
|
||||
t.Errorf("Want 0 keys, got %v", keys)
|
||||
}
|
||||
|
||||
signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"])
|
||||
if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil {
|
||||
t.Fatalf("Sign did not fail")
|
||||
}
|
||||
|
||||
if err := agent.Remove(signer.PublicKey()); err == nil {
|
||||
t.Fatalf("Remove did not fail")
|
||||
}
|
||||
|
||||
if err := agent.RemoveAll(); err == nil {
|
||||
t.Fatalf("RemoveAll did not fail")
|
||||
}
|
||||
|
||||
if err := agent.Unlock(nil); err == nil {
|
||||
t.Errorf("Unlock with wrong passphrase succeeded")
|
||||
}
|
||||
if err := agent.Unlock(passphrase); err != nil {
|
||||
t.Errorf("Unlock: %v", err)
|
||||
}
|
||||
|
||||
if err := agent.Remove(signer.PublicKey()); err != nil {
|
||||
t.Fatalf("Remove: %v", err)
|
||||
}
|
||||
|
||||
if keys, err := agent.List(); err != nil {
|
||||
t.Errorf("List: %v", err)
|
||||
} else if len(keys) != 1 {
|
||||
t.Errorf("Want 1 keys, got %v", keys)
|
||||
}
|
||||
}
|
||||
103
modules/crypto/ssh/agent/forward.go
Executable file
103
modules/crypto/ssh/agent/forward.go
Executable file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// RequestAgentForwarding sets up agent forwarding for the session.
|
||||
// ForwardToAgent or ForwardToRemote should be called to route
|
||||
// the authentication requests.
|
||||
func RequestAgentForwarding(session *ssh.Session) error {
|
||||
ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("forwarding request denied")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForwardToAgent routes authentication requests to the given keyring.
|
||||
func ForwardToAgent(client *ssh.Client, keyring Agent) error {
|
||||
channels := client.HandleChannelOpen(channelType)
|
||||
if channels == nil {
|
||||
return errors.New("agent: already have handler for " + channelType)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for ch := range channels {
|
||||
channel, reqs, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go func() {
|
||||
ServeAgent(keyring, channel)
|
||||
channel.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
const channelType = "auth-agent@openssh.com"
|
||||
|
||||
// ForwardToRemote routes authentication requests to the ssh-agent
|
||||
// process serving on the given unix socket.
|
||||
func ForwardToRemote(client *ssh.Client, addr string) error {
|
||||
channels := client.HandleChannelOpen(channelType)
|
||||
if channels == nil {
|
||||
return errors.New("agent: already have handler for " + channelType)
|
||||
}
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
go func() {
|
||||
for ch := range channels {
|
||||
channel, reqs, err := ch.Accept()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
go forwardUnixSocket(channel, addr)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardUnixSocket(channel ssh.Channel, addr string) {
|
||||
conn, err := net.Dial("unix", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
io.Copy(conn, channel)
|
||||
conn.(*net.UnixConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(channel, conn)
|
||||
channel.CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
conn.Close()
|
||||
channel.Close()
|
||||
}
|
||||
184
modules/crypto/ssh/agent/keyring.go
Executable file
184
modules/crypto/ssh/agent/keyring.go
Executable file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
type privKey struct {
|
||||
signer ssh.Signer
|
||||
comment string
|
||||
}
|
||||
|
||||
type keyring struct {
|
||||
mu sync.Mutex
|
||||
keys []privKey
|
||||
|
||||
locked bool
|
||||
passphrase []byte
|
||||
}
|
||||
|
||||
var errLocked = errors.New("agent: locked")
|
||||
|
||||
// NewKeyring returns an Agent that holds keys in memory. It is safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
func NewKeyring() Agent {
|
||||
return &keyring{}
|
||||
}
|
||||
|
||||
// RemoveAll removes all identities.
|
||||
func (r *keyring) RemoveAll() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
r.keys = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes all identities with the given public key.
|
||||
func (r *keyring) Remove(key ssh.PublicKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
want := key.Marshal()
|
||||
found := false
|
||||
for i := 0; i < len(r.keys); {
|
||||
if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) {
|
||||
found = true
|
||||
r.keys[i] = r.keys[len(r.keys)-1]
|
||||
r.keys = r.keys[len(r.keys)-1:]
|
||||
continue
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("agent: key not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock locks the agent. Sign and Remove will fail, and List will empty an empty list.
|
||||
func (r *keyring) Lock(passphrase []byte) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
|
||||
r.locked = true
|
||||
r.passphrase = passphrase
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock undoes the effect of Lock
|
||||
func (r *keyring) Unlock(passphrase []byte) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if !r.locked {
|
||||
return errors.New("agent: not locked")
|
||||
}
|
||||
if len(passphrase) != len(r.passphrase) || 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
|
||||
return fmt.Errorf("agent: incorrect passphrase")
|
||||
}
|
||||
|
||||
r.locked = false
|
||||
r.passphrase = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns the identities known to the agent.
|
||||
func (r *keyring) List() ([]*Key, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
// section 2.7: locked agents return empty.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ids []*Key
|
||||
for _, k := range r.keys {
|
||||
pub := k.signer.PublicKey()
|
||||
ids = append(ids, &Key{
|
||||
Format: pub.Type(),
|
||||
Blob: pub.Marshal(),
|
||||
Comment: k.comment})
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Insert adds a private key to the keyring. If a certificate
|
||||
// is given, that certificate is added as public key. Note that
|
||||
// any constraints given are ignored.
|
||||
func (r *keyring) Add(key AddedKey) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return errLocked
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cert := key.Certificate; cert != nil {
|
||||
signer, err = ssh.NewCertSigner(cert, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.keys = append(r.keys, privKey{signer, key.Comment})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign returns a signature for the data.
|
||||
func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return nil, errLocked
|
||||
}
|
||||
|
||||
wanted := key.Marshal()
|
||||
for _, k := range r.keys {
|
||||
if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) {
|
||||
return k.signer.Sign(rand.Reader, data)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
// Signers returns signers for all the known keys.
|
||||
func (r *keyring) Signers() ([]ssh.Signer, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.locked {
|
||||
return nil, errLocked
|
||||
}
|
||||
|
||||
s := make([]ssh.Signer, 0, len(r.keys))
|
||||
for _, k := range r.keys {
|
||||
s = append(s, k.signer)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
209
modules/crypto/ssh/agent/server.go
Executable file
209
modules/crypto/ssh/agent/server.go
Executable file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
// Server wraps an Agent and uses it to implement the agent side of
|
||||
// the SSH-agent, wire protocol.
|
||||
type server struct {
|
||||
agent Agent
|
||||
}
|
||||
|
||||
func (s *server) processRequestBytes(reqData []byte) []byte {
|
||||
rep, err := s.processRequest(reqData)
|
||||
if err != nil {
|
||||
if err != errLocked {
|
||||
// TODO(hanwen): provide better logging interface?
|
||||
log.Printf("agent %d: %v", reqData[0], err)
|
||||
}
|
||||
return []byte{agentFailure}
|
||||
}
|
||||
|
||||
if err == nil && rep == nil {
|
||||
return []byte{agentSuccess}
|
||||
}
|
||||
|
||||
return ssh.Marshal(rep)
|
||||
}
|
||||
|
||||
func marshalKey(k *Key) []byte {
|
||||
var record struct {
|
||||
Blob []byte
|
||||
Comment string
|
||||
}
|
||||
record.Blob = k.Marshal()
|
||||
record.Comment = k.Comment
|
||||
|
||||
return ssh.Marshal(&record)
|
||||
}
|
||||
|
||||
type agentV1IdentityMsg struct {
|
||||
Numkeys uint32 `sshtype:"2"`
|
||||
}
|
||||
|
||||
type agentRemoveIdentityMsg struct {
|
||||
KeyBlob []byte `sshtype:"18"`
|
||||
}
|
||||
|
||||
type agentLockMsg struct {
|
||||
Passphrase []byte `sshtype:"22"`
|
||||
}
|
||||
|
||||
type agentUnlockMsg struct {
|
||||
Passphrase []byte `sshtype:"23"`
|
||||
}
|
||||
|
||||
func (s *server) processRequest(data []byte) (interface{}, error) {
|
||||
switch data[0] {
|
||||
case agentRequestV1Identities:
|
||||
return &agentV1IdentityMsg{0}, nil
|
||||
case agentRemoveIdentity:
|
||||
var req agentRemoveIdentityMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
||||
|
||||
case agentRemoveAllIdentities:
|
||||
return nil, s.agent.RemoveAll()
|
||||
|
||||
case agentLock:
|
||||
var req agentLockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.agent.Lock(req.Passphrase)
|
||||
|
||||
case agentUnlock:
|
||||
var req agentLockMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.agent.Unlock(req.Passphrase)
|
||||
|
||||
case agentSignRequest:
|
||||
var req signRequestAgentMsg
|
||||
if err := ssh.Unmarshal(data, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wk wireKey
|
||||
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := &Key{
|
||||
Format: wk.Format,
|
||||
Blob: req.KeyBlob,
|
||||
}
|
||||
|
||||
sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
||||
case agentRequestIdentities:
|
||||
keys, err := s.agent.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rep := identitiesAnswerAgentMsg{
|
||||
NumKeys: uint32(len(keys)),
|
||||
}
|
||||
for _, k := range keys {
|
||||
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
||||
}
|
||||
return rep, nil
|
||||
case agentAddIdentity:
|
||||
return nil, s.insertIdentity(data)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
||||
}
|
||||
|
||||
func (s *server) insertIdentity(req []byte) error {
|
||||
var record struct {
|
||||
Type string `sshtype:"17"`
|
||||
Rest []byte `ssh:"rest"`
|
||||
}
|
||||
if err := ssh.Unmarshal(req, &record); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch record.Type {
|
||||
case ssh.KeyAlgoRSA:
|
||||
var k rsaKeyMsg
|
||||
if err := ssh.Unmarshal(req, &k); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
priv := rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
E: int(k.E.Int64()),
|
||||
N: k.N,
|
||||
},
|
||||
D: k.D,
|
||||
Primes: []*big.Int{k.P, k.Q},
|
||||
}
|
||||
priv.Precompute()
|
||||
|
||||
return s.agent.Add(AddedKey{PrivateKey: &priv, Comment: k.Comments})
|
||||
}
|
||||
return fmt.Errorf("not implemented: %s", record.Type)
|
||||
}
|
||||
|
||||
// ServeAgent serves the agent protocol on the given connection. It
|
||||
// returns when an I/O error occurs.
|
||||
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
||||
s := &server{agent}
|
||||
|
||||
var length [4]byte
|
||||
for {
|
||||
if _, err := io.ReadFull(c, length[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
l := binary.BigEndian.Uint32(length[:])
|
||||
if l > maxAgentResponseBytes {
|
||||
// We also cap requests.
|
||||
return fmt.Errorf("agent: request too large: %d", l)
|
||||
}
|
||||
|
||||
req := make([]byte, l)
|
||||
if _, err := io.ReadFull(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repData := s.processRequestBytes(req)
|
||||
if len(repData) > maxAgentResponseBytes {
|
||||
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
||||
if _, err := c.Write(length[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := c.Write(repData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
77
modules/crypto/ssh/agent/server_test.go
Executable file
77
modules/crypto/ssh/agent/server_test.go
Executable file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
client := NewClient(c1)
|
||||
|
||||
go ServeAgent(NewKeyring(), c2)
|
||||
|
||||
testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
|
||||
}
|
||||
|
||||
func TestLockServer(t *testing.T) {
|
||||
testLockAgent(NewKeyring(), t)
|
||||
}
|
||||
|
||||
func TestSetupForwardAgent(t *testing.T) {
|
||||
a, b, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
_, socket, cleanup := startAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
serverConf := ssh.ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["rsa"])
|
||||
incoming := make(chan *ssh.ServerConn, 1)
|
||||
go func() {
|
||||
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Server: %v", err)
|
||||
}
|
||||
incoming <- conn
|
||||
}()
|
||||
|
||||
conf := ssh.ClientConfig{}
|
||||
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientConn: %v", err)
|
||||
}
|
||||
client := ssh.NewClient(conn, chans, reqs)
|
||||
|
||||
if err := ForwardToRemote(client, socket); err != nil {
|
||||
t.Fatalf("SetupForwardAgent: %v", err)
|
||||
}
|
||||
|
||||
server := <-incoming
|
||||
ch, reqs, err := server.OpenChannel(channelType, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenChannel(%q): %v", channelType, err)
|
||||
}
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
agentClient := NewClient(ch)
|
||||
testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
|
||||
conn.Close()
|
||||
}
|
||||
64
modules/crypto/ssh/agent/testdata_test.go
Executable file
64
modules/crypto/ssh/agent/testdata_test.go
Executable file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// IMPLEMENTOR NOTE: To avoid a package loop, this file is in three places:
|
||||
// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
|
||||
// instances.
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogits/gogs/modules/crypto/ssh"
|
||||
"github.com/gogits/gogs/modules/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
var (
|
||||
testPrivateKeys map[string]interface{}
|
||||
testSigners map[string]ssh.Signer
|
||||
testPublicKeys map[string]ssh.PublicKey
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
n := len(testdata.PEMBytes)
|
||||
testPrivateKeys = make(map[string]interface{}, n)
|
||||
testSigners = make(map[string]ssh.Signer, n)
|
||||
testPublicKeys = make(map[string]ssh.PublicKey, n)
|
||||
for t, k := range testdata.PEMBytes {
|
||||
testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
|
||||
}
|
||||
testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
|
||||
}
|
||||
testPublicKeys[t] = testSigners[t].PublicKey()
|
||||
}
|
||||
|
||||
// Create a cert and sign it for use in tests.
|
||||
testCert := &ssh.Certificate{
|
||||
Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
|
||||
ValidAfter: 0, // unix epoch
|
||||
ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
|
||||
Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
|
||||
Key: testPublicKeys["ecdsa"],
|
||||
SignatureKey: testPublicKeys["rsa"],
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
},
|
||||
}
|
||||
testCert.SignCert(rand.Reader, testSigners["rsa"])
|
||||
testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
|
||||
testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
|
||||
}
|
||||
}
|
||||
122
modules/crypto/ssh/benchmark_test.go
Executable file
122
modules/crypto/ssh/benchmark_test.go
Executable file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
*ServerConn
|
||||
chans <-chan NewChannel
|
||||
}
|
||||
|
||||
func newServer(c net.Conn, conf *ServerConfig) (*server, error) {
|
||||
sconn, chans, reqs, err := NewServerConn(c, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
return &server{sconn, chans}, nil
|
||||
}
|
||||
|
||||
func (s *server) Accept() (NewChannel, error) {
|
||||
n, ok := <-s.chans
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func sshPipe() (Conn, *server, error) {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
clientConf := ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
serverConf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
serverConf.AddHostKey(testSigners["ecdsa"])
|
||||
done := make(chan *server, 1)
|
||||
go func() {
|
||||
server, err := newServer(c2, &serverConf)
|
||||
if err != nil {
|
||||
done <- nil
|
||||
}
|
||||
done <- server
|
||||
}()
|
||||
|
||||
client, _, reqs, err := NewClientConn(c1, "", &clientConf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
server := <-done
|
||||
if server == nil {
|
||||
return nil, nil, errors.New("server handshake failed.")
|
||||
}
|
||||
go DiscardRequests(reqs)
|
||||
|
||||
return client, server, nil
|
||||
}
|
||||
|
||||
func BenchmarkEndToEnd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
client, server, err := sshPipe()
|
||||
if err != nil {
|
||||
b.Fatalf("sshPipe: %v", err)
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
size := (1 << 20)
|
||||
input := make([]byte, size)
|
||||
output := make([]byte, size)
|
||||
b.SetBytes(int64(size))
|
||||
done := make(chan int, 1)
|
||||
|
||||
go func() {
|
||||
newCh, err := server.Accept()
|
||||
if err != nil {
|
||||
b.Fatalf("Client: %v", err)
|
||||
}
|
||||
ch, incoming, err := newCh.Accept()
|
||||
go DiscardRequests(incoming)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := io.ReadFull(ch, output); err != nil {
|
||||
b.Fatalf("ReadFull: %v", err)
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
done <- 1
|
||||
}()
|
||||
|
||||
ch, in, err := client.OpenChannel("speed", nil)
|
||||
if err != nil {
|
||||
b.Fatalf("OpenChannel: %v", err)
|
||||
}
|
||||
go DiscardRequests(in)
|
||||
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := ch.Write(input); err != nil {
|
||||
b.Fatalf("WriteFull: %v", err)
|
||||
}
|
||||
}
|
||||
ch.Close()
|
||||
b.StopTimer()
|
||||
|
||||
<-done
|
||||
}
|
||||
98
modules/crypto/ssh/buffer.go
Executable file
98
modules/crypto/ssh/buffer.go
Executable file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// buffer provides a linked list buffer for data exchange
|
||||
// between producer and consumer. Theoretically the buffer is
|
||||
// of unlimited capacity as it does no allocation of its own.
|
||||
type buffer struct {
|
||||
// protects concurrent access to head, tail and closed
|
||||
*sync.Cond
|
||||
|
||||
head *element // the buffer that will be read first
|
||||
tail *element // the buffer that will be read last
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
// An element represents a single link in a linked list.
|
||||
type element struct {
|
||||
buf []byte
|
||||
next *element
|
||||
}
|
||||
|
||||
// newBuffer returns an empty buffer that is not closed.
|
||||
func newBuffer() *buffer {
|
||||
e := new(element)
|
||||
b := &buffer{
|
||||
Cond: newCond(),
|
||||
head: e,
|
||||
tail: e,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// write makes buf available for Read to receive.
|
||||
// buf must not be modified after the call to write.
|
||||
func (b *buffer) write(buf []byte) {
|
||||
b.Cond.L.Lock()
|
||||
e := &element{buf: buf}
|
||||
b.tail.next = e
|
||||
b.tail = e
|
||||
b.Cond.Signal()
|
||||
b.Cond.L.Unlock()
|
||||
}
|
||||
|
||||
// eof closes the buffer. Reads from the buffer once all
|
||||
// the data has been consumed will receive os.EOF.
|
||||
func (b *buffer) eof() error {
|
||||
b.Cond.L.Lock()
|
||||
b.closed = true
|
||||
b.Cond.Signal()
|
||||
b.Cond.L.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads data from the internal buffer in buf. Reads will block
|
||||
// if no data is available, or until the buffer is closed.
|
||||
func (b *buffer) Read(buf []byte) (n int, err error) {
|
||||
b.Cond.L.Lock()
|
||||
defer b.Cond.L.Unlock()
|
||||
|
||||
for len(buf) > 0 {
|
||||
// if there is data in b.head, copy it
|
||||
if len(b.head.buf) > 0 {
|
||||
r := copy(buf, b.head.buf)
|
||||
buf, b.head.buf = buf[r:], b.head.buf[r:]
|
||||
n += r
|
||||
continue
|
||||
}
|
||||
// if there is a next buffer, make it the head
|
||||
if len(b.head.buf) == 0 && b.head != b.tail {
|
||||
b.head = b.head.next
|
||||
continue
|
||||
}
|
||||
|
||||
// if at least one byte has been copied, return
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// if nothing was read, and there is nothing outstanding
|
||||
// check to see if the buffer is closed.
|
||||
if b.closed {
|
||||
err = io.EOF
|
||||
break
|
||||
}
|
||||
// out of buffers, wait for producer
|
||||
b.Cond.Wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
87
modules/crypto/ssh/buffer_test.go
Executable file
87
modules/crypto/ssh/buffer_test.go
Executable file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var alphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
func TestBufferReadwrite(t *testing.T) {
|
||||
b := newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
r, _ := b.Read(make([]byte, 10))
|
||||
if r != 10 {
|
||||
t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:5])
|
||||
r, _ = b.Read(make([]byte, 10))
|
||||
if r != 5 {
|
||||
t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
r, _ = b.Read(make([]byte, 5))
|
||||
if r != 5 {
|
||||
t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:5])
|
||||
b.write(alphabet[5:15])
|
||||
r, _ = b.Read(make([]byte, 10))
|
||||
r2, _ := b.Read(make([]byte, 10))
|
||||
if r != 10 || r2 != 5 || 15 != r+r2 {
|
||||
t.Fatal("Expected written == read == 15")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferClose(t *testing.T) {
|
||||
b := newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
_, err := b.Read(make([]byte, 5))
|
||||
if err != nil {
|
||||
t.Fatal("expected read of 5 to not return EOF")
|
||||
}
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
r, err := b.Read(make([]byte, 5))
|
||||
r2, err2 := b.Read(make([]byte, 10))
|
||||
if r != 5 || r2 != 5 || err != nil || err2 != nil {
|
||||
t.Fatal("expected reads of 5 and 5")
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(alphabet[:10])
|
||||
b.eof()
|
||||
r, err = b.Read(make([]byte, 5))
|
||||
r2, err2 = b.Read(make([]byte, 10))
|
||||
r3, err3 := b.Read(make([]byte, 10))
|
||||
if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
|
||||
t.Fatal("expected reads of 5 and 5 and 0, with EOF")
|
||||
}
|
||||
|
||||
b = newBuffer()
|
||||
b.write(make([]byte, 5))
|
||||
b.write(make([]byte, 10))
|
||||
b.eof()
|
||||
r, err = b.Read(make([]byte, 9))
|
||||
r2, err2 = b.Read(make([]byte, 3))
|
||||
r3, err3 = b.Read(make([]byte, 3))
|
||||
r4, err4 := b.Read(make([]byte, 10))
|
||||
if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
|
||||
t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
|
||||
}
|
||||
if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
|
||||
t.Fatal("Expected written == read == 15", r, r2, r3, r4)
|
||||
}
|
||||
}
|
||||
501
modules/crypto/ssh/certs.go
Executable file
501
modules/crypto/ssh/certs.go
Executable file
@@ -0,0 +1,501 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
||||
// for certificate types supported by this package.
|
||||
const (
|
||||
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
||||
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
||||
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
||||
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
||||
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
||||
)
|
||||
|
||||
// Certificate types distinguish between host and user
|
||||
// certificates. The values can be set in the CertType field of
|
||||
// Certificate.
|
||||
const (
|
||||
UserCert = 1
|
||||
HostCert = 2
|
||||
)
|
||||
|
||||
// Signature represents a cryptographic signature.
|
||||
type Signature struct {
|
||||
Format string
|
||||
Blob []byte
|
||||
}
|
||||
|
||||
// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that
|
||||
// a certificate does not expire.
|
||||
const CertTimeInfinity = 1<<64 - 1
|
||||
|
||||
// An Certificate represents an OpenSSH certificate as defined in
|
||||
// [PROTOCOL.certkeys]?rev=1.8.
|
||||
type Certificate struct {
|
||||
Nonce []byte
|
||||
Key PublicKey
|
||||
Serial uint64
|
||||
CertType uint32
|
||||
KeyId string
|
||||
ValidPrincipals []string
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
Permissions
|
||||
Reserved []byte
|
||||
SignatureKey PublicKey
|
||||
Signature *Signature
|
||||
}
|
||||
|
||||
// genericCertData holds the key-independent part of the certificate data.
|
||||
// Overall, certificates contain an nonce, public key fields and
|
||||
// key-independent fields.
|
||||
type genericCertData struct {
|
||||
Serial uint64
|
||||
CertType uint32
|
||||
KeyId string
|
||||
ValidPrincipals []byte
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
CriticalOptions []byte
|
||||
Extensions []byte
|
||||
Reserved []byte
|
||||
SignatureKey []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
func marshalStringList(namelist []string) []byte {
|
||||
var to []byte
|
||||
for _, name := range namelist {
|
||||
s := struct{ N string }{name}
|
||||
to = append(to, Marshal(&s)...)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
type optionsTuple struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type optionsTupleValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// serialize a map of critical options or extensions
|
||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||
// we need two length prefixes for a non-empty string value
|
||||
func marshalTuples(tups map[string]string) []byte {
|
||||
keys := make([]string, 0, len(tups))
|
||||
for key := range tups {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var ret []byte
|
||||
for _, key := range keys {
|
||||
s := optionsTuple{Key: key}
|
||||
if value := tups[key]; len(value) > 0 {
|
||||
s.Value = Marshal(&optionsTupleValue{value})
|
||||
}
|
||||
ret = append(ret, Marshal(&s)...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation,
|
||||
// we need two length prefixes for a non-empty option value
|
||||
func parseTuples(in []byte) (map[string]string, error) {
|
||||
tups := map[string]string{}
|
||||
var lastKey string
|
||||
var haveLastKey bool
|
||||
|
||||
for len(in) > 0 {
|
||||
var key, val, extra []byte
|
||||
var ok bool
|
||||
|
||||
if key, in, ok = parseString(in); !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
keyStr := string(key)
|
||||
// according to [PROTOCOL.certkeys], the names must be in
|
||||
// lexical order.
|
||||
if haveLastKey && keyStr <= lastKey {
|
||||
return nil, fmt.Errorf("ssh: certificate options are not in lexical order")
|
||||
}
|
||||
lastKey, haveLastKey = keyStr, true
|
||||
// the next field is a data field, which if non-empty has a string embedded
|
||||
if val, in, ok = parseString(in); !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
if len(val) > 0 {
|
||||
val, extra, ok = parseString(val)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
if len(extra) > 0 {
|
||||
return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value")
|
||||
}
|
||||
tups[keyStr] = string(val)
|
||||
} else {
|
||||
tups[keyStr] = ""
|
||||
}
|
||||
}
|
||||
return tups, nil
|
||||
}
|
||||
|
||||
func parseCert(in []byte, privAlgo string) (*Certificate, error) {
|
||||
nonce, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
|
||||
key, rest, err := parsePubKey(rest, privAlgo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var g genericCertData
|
||||
if err := Unmarshal(rest, &g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Certificate{
|
||||
Nonce: nonce,
|
||||
Key: key,
|
||||
Serial: g.Serial,
|
||||
CertType: g.CertType,
|
||||
KeyId: g.KeyId,
|
||||
ValidAfter: g.ValidAfter,
|
||||
ValidBefore: g.ValidBefore,
|
||||
}
|
||||
|
||||
for principals := g.ValidPrincipals; len(principals) > 0; {
|
||||
principal, rest, ok := parseString(principals)
|
||||
if !ok {
|
||||
return nil, errShortRead
|
||||
}
|
||||
c.ValidPrincipals = append(c.ValidPrincipals, string(principal))
|
||||
principals = rest
|
||||
}
|
||||
|
||||
c.CriticalOptions, err = parseTuples(g.CriticalOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Extensions, err = parseTuples(g.Extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Reserved = g.Reserved
|
||||
k, err := ParsePublicKey(g.SignatureKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.SignatureKey = k
|
||||
c.Signature, rest, ok = parseSignatureBody(g.Signature)
|
||||
if !ok || len(rest) > 0 {
|
||||
return nil, errors.New("ssh: signature parse error")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type openSSHCertSigner struct {
|
||||
pub *Certificate
|
||||
signer Signer
|
||||
}
|
||||
|
||||
// NewCertSigner returns a Signer that signs with the given Certificate, whose
|
||||
// private key is held by signer. It returns an error if the public key in cert
|
||||
// doesn't match the key used by signer.
|
||||
func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) {
|
||||
if bytes.Compare(cert.Key.Marshal(), signer.PublicKey().Marshal()) != 0 {
|
||||
return nil, errors.New("ssh: signer and cert have different public key")
|
||||
}
|
||||
|
||||
return &openSSHCertSigner{cert, signer}, nil
|
||||
}
|
||||
|
||||
func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
||||
return s.signer.Sign(rand, data)
|
||||
}
|
||||
|
||||
func (s *openSSHCertSigner) PublicKey() PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
const sourceAddressCriticalOption = "source-address"
|
||||
|
||||
// CertChecker does the work of verifying a certificate. Its methods
|
||||
// can be plugged into ClientConfig.HostKeyCallback and
|
||||
// ServerConfig.PublicKeyCallback. For the CertChecker to work,
|
||||
// minimally, the IsAuthority callback should be set.
|
||||
type CertChecker struct {
|
||||
// SupportedCriticalOptions lists the CriticalOptions that the
|
||||
// server application layer understands. These are only used
|
||||
// for user certificates.
|
||||
SupportedCriticalOptions []string
|
||||
|
||||
// IsAuthority should return true if the key is recognized as
|
||||
// an authority. This allows for certificates to be signed by other
|
||||
// certificates.
|
||||
IsAuthority func(auth PublicKey) bool
|
||||
|
||||
// Clock is used for verifying time stamps. If nil, time.Now
|
||||
// is used.
|
||||
Clock func() time.Time
|
||||
|
||||
// UserKeyFallback is called when CertChecker.Authenticate encounters a
|
||||
// public key that is not a certificate. It must implement validation
|
||||
// of user keys or else, if nil, all such keys are rejected.
|
||||
UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
|
||||
|
||||
// HostKeyFallback is called when CertChecker.CheckHostKey encounters a
|
||||
// public key that is not a certificate. It must implement host key
|
||||
// validation or else, if nil, all such keys are rejected.
|
||||
HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// IsRevoked is called for each certificate so that revocation checking
|
||||
// can be implemented. It should return true if the given certificate
|
||||
// is revoked and false otherwise. If nil, no certificates are
|
||||
// considered to have been revoked.
|
||||
IsRevoked func(cert *Certificate) bool
|
||||
}
|
||||
|
||||
// CheckHostKey checks a host key certificate. This method can be
|
||||
// plugged into ClientConfig.HostKeyCallback.
|
||||
func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error {
|
||||
cert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
if c.HostKeyFallback != nil {
|
||||
return c.HostKeyFallback(addr, remote, key)
|
||||
}
|
||||
return errors.New("ssh: non-certificate host key")
|
||||
}
|
||||
if cert.CertType != HostCert {
|
||||
return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType)
|
||||
}
|
||||
|
||||
return c.CheckCert(addr, cert)
|
||||
}
|
||||
|
||||
// Authenticate checks a user certificate. Authenticate can be used as
|
||||
// a value for ServerConfig.PublicKeyCallback.
|
||||
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
|
||||
cert, ok := pubKey.(*Certificate)
|
||||
if !ok {
|
||||
if c.UserKeyFallback != nil {
|
||||
return c.UserKeyFallback(conn, pubKey)
|
||||
}
|
||||
return nil, errors.New("ssh: normal key pairs not accepted")
|
||||
}
|
||||
|
||||
if cert.CertType != UserCert {
|
||||
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType)
|
||||
}
|
||||
|
||||
if err := c.CheckCert(conn.User(), cert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cert.Permissions, nil
|
||||
}
|
||||
|
||||
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
|
||||
// the signature of the certificate.
|
||||
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
|
||||
if c.IsRevoked != nil && c.IsRevoked(cert) {
|
||||
return fmt.Errorf("ssh: certicate serial %d revoked", cert.Serial)
|
||||
}
|
||||
|
||||
for opt, _ := range cert.CriticalOptions {
|
||||
// sourceAddressCriticalOption will be enforced by
|
||||
// serverAuthenticate
|
||||
if opt == sourceAddressCriticalOption {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, supp := range c.SupportedCriticalOptions {
|
||||
if supp == opt {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cert.ValidPrincipals) > 0 {
|
||||
// By default, certs are valid for all users/hosts.
|
||||
found := false
|
||||
for _, p := range cert.ValidPrincipals {
|
||||
if p == principal {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals)
|
||||
}
|
||||
}
|
||||
|
||||
if !c.IsAuthority(cert.SignatureKey) {
|
||||
return fmt.Errorf("ssh: certificate signed by unrecognized authority")
|
||||
}
|
||||
|
||||
clock := c.Clock
|
||||
if clock == nil {
|
||||
clock = time.Now
|
||||
}
|
||||
|
||||
unixNow := clock().Unix()
|
||||
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
||||
return fmt.Errorf("ssh: cert is not yet valid")
|
||||
}
|
||||
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||
return fmt.Errorf("ssh: cert has expired")
|
||||
}
|
||||
if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil {
|
||||
return fmt.Errorf("ssh: certificate signature does not verify")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignCert sets c.SignatureKey to the authority's public key and stores a
|
||||
// Signature, by authority, in the certificate.
|
||||
func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
|
||||
c.Nonce = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand, c.Nonce); err != nil {
|
||||
return err
|
||||
}
|
||||
c.SignatureKey = authority.PublicKey()
|
||||
|
||||
sig, err := authority.Sign(rand, c.bytesForSigning())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Signature = sig
|
||||
return nil
|
||||
}
|
||||
|
||||
var certAlgoNames = map[string]string{
|
||||
KeyAlgoRSA: CertAlgoRSAv01,
|
||||
KeyAlgoDSA: CertAlgoDSAv01,
|
||||
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
||||
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
||||
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
||||
}
|
||||
|
||||
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
||||
// Panics if a non-certificate algorithm is passed.
|
||||
func certToPrivAlgo(algo string) string {
|
||||
for privAlgo, pubAlgo := range certAlgoNames {
|
||||
if pubAlgo == algo {
|
||||
return privAlgo
|
||||
}
|
||||
}
|
||||
panic("unknown cert algorithm")
|
||||
}
|
||||
|
||||
func (cert *Certificate) bytesForSigning() []byte {
|
||||
c2 := *cert
|
||||
c2.Signature = nil
|
||||
out := c2.Marshal()
|
||||
// Drop trailing signature length.
|
||||
return out[:len(out)-4]
|
||||
}
|
||||
|
||||
// Marshal serializes c into OpenSSH's wire format. It is part of the
|
||||
// PublicKey interface.
|
||||
func (c *Certificate) Marshal() []byte {
|
||||
generic := genericCertData{
|
||||
Serial: c.Serial,
|
||||
CertType: c.CertType,
|
||||
KeyId: c.KeyId,
|
||||
ValidPrincipals: marshalStringList(c.ValidPrincipals),
|
||||
ValidAfter: uint64(c.ValidAfter),
|
||||
ValidBefore: uint64(c.ValidBefore),
|
||||
CriticalOptions: marshalTuples(c.CriticalOptions),
|
||||
Extensions: marshalTuples(c.Extensions),
|
||||
Reserved: c.Reserved,
|
||||
SignatureKey: c.SignatureKey.Marshal(),
|
||||
}
|
||||
if c.Signature != nil {
|
||||
generic.Signature = Marshal(c.Signature)
|
||||
}
|
||||
genericBytes := Marshal(&generic)
|
||||
keyBytes := c.Key.Marshal()
|
||||
_, keyBytes, _ = parseString(keyBytes)
|
||||
prefix := Marshal(&struct {
|
||||
Name string
|
||||
Nonce []byte
|
||||
Key []byte `ssh:"rest"`
|
||||
}{c.Type(), c.Nonce, keyBytes})
|
||||
|
||||
result := make([]byte, 0, len(prefix)+len(genericBytes))
|
||||
result = append(result, prefix...)
|
||||
result = append(result, genericBytes...)
|
||||
return result
|
||||
}
|
||||
|
||||
// Type returns the key name. It is part of the PublicKey interface.
|
||||
func (c *Certificate) Type() string {
|
||||
algo, ok := certAlgoNames[c.Key.Type()]
|
||||
if !ok {
|
||||
panic("unknown cert key type")
|
||||
}
|
||||
return algo
|
||||
}
|
||||
|
||||
// Verify verifies a signature against the certificate's public
|
||||
// key. It is part of the PublicKey interface.
|
||||
func (c *Certificate) Verify(data []byte, sig *Signature) error {
|
||||
return c.Key.Verify(data, sig)
|
||||
}
|
||||
|
||||
func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||
format, in, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out = &Signature{
|
||||
Format: string(format),
|
||||
}
|
||||
|
||||
if out.Blob, in, ok = parseString(in); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
return out, in, ok
|
||||
}
|
||||
|
||||
func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) {
|
||||
sigBytes, rest, ok := parseString(in)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
out, trailing, ok := parseSignatureBody(sigBytes)
|
||||
if !ok || len(trailing) > 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
return
|
||||
}
|
||||
216
modules/crypto/ssh/certs_test.go
Executable file
216
modules/crypto/ssh/certs_test.go
Executable file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cert generated by ssh-keygen 6.0p1 Debian-4.
|
||||
// % ssh-keygen -s ca-key -I test user-key
|
||||
const exampleSSHCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgb1srW/W3ZDjYAO45xLYAwzHBDLsJ4Ux6ICFIkTjb1LEAAAADAQABAAAAYQCkoR51poH0wE8w72cqSB8Sszx+vAhzcMdCO0wqHTj7UNENHWEXGrU0E0UQekD7U+yhkhtoyjbPOVIP7hNa6aRk/ezdh/iUnCIt4Jt1v3Z1h1P+hA4QuYFMHNB+rmjPwAcAAAAAAAAAAAAAAAEAAAAEdGVzdAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAHcAAAAHc3NoLXJzYQAAAAMBAAEAAABhANFS2kaktpSGc+CcmEKPyw9mJC4nZKxHKTgLVZeaGbFZOvJTNzBspQHdy7Q1uKSfktxpgjZnksiu/tFF9ngyY2KFoc+U88ya95IZUycBGCUbBQ8+bhDtw/icdDGQD5WnUwAAAG8AAAAHc3NoLXJzYQAAAGC8Y9Z2LQKhIhxf52773XaWrXdxP0t3GBVo4A10vUWiYoAGepr6rQIoGGXFxT4B9Gp+nEBJjOwKDXPrAevow0T9ca8gZN+0ykbhSrXLE5Ao48rqr3zP4O1/9P7e6gp0gw8=`
|
||||
|
||||
func TestParseCert(t *testing.T) {
|
||||
authKeyBytes := []byte(exampleSSHCert)
|
||||
|
||||
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
t.Errorf("rest: got %q, want empty", rest)
|
||||
}
|
||||
|
||||
if _, ok := key.(*Certificate); !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
|
||||
marshaled := MarshalAuthorizedKey(key)
|
||||
// Before comparison, remove the trailing newline that
|
||||
// MarshalAuthorizedKey adds.
|
||||
marshaled = marshaled[:len(marshaled)-1]
|
||||
if !bytes.Equal(authKeyBytes, marshaled) {
|
||||
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
|
||||
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
|
||||
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
|
||||
// Critical Options:
|
||||
// force-command /bin/sleep
|
||||
// source-address 192.168.1.0/24
|
||||
// Extensions:
|
||||
// permit-X11-forwarding
|
||||
// permit-agent-forwarding
|
||||
// permit-port-forwarding
|
||||
// permit-pty
|
||||
// permit-user-rc
|
||||
const exampleSSHCertWithOptions = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgDyysCJY0XrO1n03EeRRoITnTPdjENFmWDs9X58PP3VUAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMNAAAAAAAAAAAAAAABAAAACHRlc3RjZXJ0AAAAAAAAAAAAAAAA//////////8AAABLAAAADWZvcmNlLWNvbW1hbmQAAAAOAAAACi9iaW4vc2xlZXAAAAAOc291cmNlLWFkZHJlc3MAAAASAAAADjE5Mi4xNjguMS4wLzI0AAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwU+c5ui5A8+J/CFpjW8wCa52bEODA808WWQDCSuTG/eMXNf59v9Y8Pk0F1E9dGCosSNyVcB/hacUrc6He+i97+HJCyKavBsE6GDxrjRyxYqAlfcOXi/IVmaUGiO8OQ39d4GHrjToInKvExSUeleQyH4Y4/e27T/pILAqPFL3fyrvMLT5qU9QyIt6zIpa7GBP5+urouNavMprV3zsfIqNBbWypinOQAw823a5wN+zwXnhZrgQiHZ/USG09Y6k98y1dTVz8YHlQVR4D3lpTAsKDKJ5hCH9WU4fdf+lU8OyNGaJ/vz0XNqxcToe1l4numLTnaoSuH89pHryjqurB7lJKwAAAQ8AAAAHc3NoLXJzYQAAAQCaHvUIoPL1zWUHIXLvu96/HU1s/i4CAW2IIEuGgxCUCiFj6vyTyYtgxQxcmbfZf6eaITlS6XJZa7Qq4iaFZh75C1DXTX8labXhRSD4E2t//AIP9MC1rtQC5xo6FmbQ+BoKcDskr+mNACcbRSxs3IL3bwCfWDnIw2WbVox9ZdcthJKk4UoCW4ix4QwdHw7zlddlz++fGEEVhmTbll1SUkycGApPFBsAYRTMupUJcYPIeReBI/m8XfkoMk99bV8ZJQTAd7OekHY2/48Ff53jLmyDjP7kNw1F8OaPtkFs6dGJXta4krmaekPy87j+35In5hFj7yoOqvSbmYUkeX70/GGQ`
|
||||
|
||||
func TestParseCertWithOptions(t *testing.T) {
|
||||
opts := map[string]string{
|
||||
"source-address": "192.168.1.0/24",
|
||||
"force-command": "/bin/sleep",
|
||||
}
|
||||
exts := map[string]string{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
}
|
||||
authKeyBytes := []byte(exampleSSHCertWithOptions)
|
||||
|
||||
key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
t.Errorf("rest: got %q, want empty", rest)
|
||||
}
|
||||
cert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
if !reflect.DeepEqual(cert.CriticalOptions, opts) {
|
||||
t.Errorf("unexpected critical options - got %v, want %v", cert.CriticalOptions, opts)
|
||||
}
|
||||
if !reflect.DeepEqual(cert.Extensions, exts) {
|
||||
t.Errorf("unexpected Extensions - got %v, want %v", cert.Extensions, exts)
|
||||
}
|
||||
marshaled := MarshalAuthorizedKey(key)
|
||||
// Before comparison, remove the trailing newline that
|
||||
// MarshalAuthorizedKey adds.
|
||||
marshaled = marshaled[:len(marshaled)-1]
|
||||
if !bytes.Equal(authKeyBytes, marshaled) {
|
||||
t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCert(t *testing.T) {
|
||||
key, _, _, _, err := ParseAuthorizedKey([]byte(exampleSSHCert))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAuthorizedKey: %v", err)
|
||||
}
|
||||
validCert, ok := key.(*Certificate)
|
||||
if !ok {
|
||||
t.Fatalf("got %v (%T), want *Certificate", key, key)
|
||||
}
|
||||
checker := CertChecker{}
|
||||
checker.IsAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal())
|
||||
}
|
||||
|
||||
if err := checker.CheckCert("user", validCert); err != nil {
|
||||
t.Errorf("Unable to validate certificate: %v", err)
|
||||
}
|
||||
invalidCert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
SignatureKey: testPublicKeys["ecdsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
Signature: &Signature{},
|
||||
}
|
||||
if err := checker.CheckCert("user", invalidCert); err == nil {
|
||||
t.Error("Invalid cert signature passed validation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCertTime(t *testing.T) {
|
||||
cert := Certificate{
|
||||
ValidPrincipals: []string{"user"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidAfter: 50,
|
||||
ValidBefore: 100,
|
||||
}
|
||||
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
for ts, ok := range map[int64]bool{
|
||||
25: false,
|
||||
50: true,
|
||||
99: true,
|
||||
100: false,
|
||||
125: false,
|
||||
} {
|
||||
checker := CertChecker{
|
||||
Clock: func() time.Time { return time.Unix(ts, 0) },
|
||||
}
|
||||
checker.IsAuthority = func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(),
|
||||
testPublicKeys["ecdsa"].Marshal())
|
||||
}
|
||||
|
||||
if v := checker.CheckCert("user", &cert); (v == nil) != ok {
|
||||
t.Errorf("Authenticate(%d): %v", ts, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(hanwen): tests for
|
||||
//
|
||||
// host keys:
|
||||
// * fallbacks
|
||||
|
||||
func TestHostKeyCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
ValidPrincipals: []string{"hostname", "hostname.domain"},
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: HostCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
|
||||
checker := &CertChecker{
|
||||
IsAuthority: func(p PublicKey) bool {
|
||||
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
|
||||
},
|
||||
}
|
||||
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Errorf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
for _, name := range []string{"hostname", "otherhost"} {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
conf := ServerConfig{
|
||||
NoClientAuth: true,
|
||||
}
|
||||
conf.AddHostKey(certSigner)
|
||||
_, _, _, err := NewServerConn(c1, &conf)
|
||||
errc <- err
|
||||
}()
|
||||
|
||||
config := &ClientConfig{
|
||||
User: "user",
|
||||
HostKeyCallback: checker.CheckHostKey,
|
||||
}
|
||||
_, _, _, err = NewClientConn(c2, name, config)
|
||||
|
||||
succeed := name == "hostname"
|
||||
if (err == nil) != succeed {
|
||||
t.Fatalf("NewClientConn(%q): %v", name, err)
|
||||
}
|
||||
|
||||
err = <-errc
|
||||
if (err == nil) != succeed {
|
||||
t.Fatalf("NewServerConn(%q): %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
631
modules/crypto/ssh/channel.go
Executable file
631
modules/crypto/ssh/channel.go
Executable file
@@ -0,0 +1,631 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
minPacketLength = 9
|
||||
// channelMaxPacket contains the maximum number of bytes that will be
|
||||
// sent in a single packet. As per RFC 4253, section 6.1, 32k is also
|
||||
// the minimum.
|
||||
channelMaxPacket = 1 << 15
|
||||
// We follow OpenSSH here.
|
||||
channelWindowSize = 64 * channelMaxPacket
|
||||
)
|
||||
|
||||
// NewChannel represents an incoming request to a channel. It must either be
|
||||
// accepted for use by calling Accept, or rejected by calling Reject.
|
||||
type NewChannel interface {
|
||||
// Accept accepts the channel creation request. It returns the Channel
|
||||
// and a Go channel containing SSH requests. The Go channel must be
|
||||
// serviced otherwise the Channel will hang.
|
||||
Accept() (Channel, <-chan *Request, error)
|
||||
|
||||
// Reject rejects the channel creation request. After calling
|
||||
// this, no other methods on the Channel may be called.
|
||||
Reject(reason RejectionReason, message string) error
|
||||
|
||||
// ChannelType returns the type of the channel, as supplied by the
|
||||
// client.
|
||||
ChannelType() string
|
||||
|
||||
// ExtraData returns the arbitrary payload for this channel, as supplied
|
||||
// by the client. This data is specific to the channel type.
|
||||
ExtraData() []byte
|
||||
}
|
||||
|
||||
// A Channel is an ordered, reliable, flow-controlled, duplex stream
|
||||
// that is multiplexed over an SSH connection.
|
||||
type Channel interface {
|
||||
// Read reads up to len(data) bytes from the channel.
|
||||
Read(data []byte) (int, error)
|
||||
|
||||
// Write writes len(data) bytes to the channel.
|
||||
Write(data []byte) (int, error)
|
||||
|
||||
// Close signals end of channel use. No data may be sent after this
|
||||
// call.
|
||||
Close() error
|
||||
|
||||
// CloseWrite signals the end of sending in-band
|
||||
// data. Requests may still be sent, and the other side may
|
||||
// still send data
|
||||
CloseWrite() error
|
||||
|
||||
// SendRequest sends a channel request. If wantReply is true,
|
||||
// it will wait for a reply and return the result as a
|
||||
// boolean, otherwise the return value will be false. Channel
|
||||
// requests are out-of-band messages so they may be sent even
|
||||
// if the data stream is closed or blocked by flow control.
|
||||
SendRequest(name string, wantReply bool, payload []byte) (bool, error)
|
||||
|
||||
// Stderr returns an io.ReadWriter that writes to this channel
|
||||
// with the extended data type set to stderr. Stderr may
|
||||
// safely be read and written from a different goroutine than
|
||||
// Read and Write respectively.
|
||||
Stderr() io.ReadWriter
|
||||
}
|
||||
|
||||
// Request is a request sent outside of the normal stream of
|
||||
// data. Requests can either be specific to an SSH channel, or they
|
||||
// can be global.
|
||||
type Request struct {
|
||||
Type string
|
||||
WantReply bool
|
||||
Payload []byte
|
||||
|
||||
ch *channel
|
||||
mux *mux
|
||||
}
|
||||
|
||||
// Reply sends a response to a request. It must be called for all requests
|
||||
// where WantReply is true and is a no-op otherwise. The payload argument is
|
||||
// ignored for replies to channel-specific requests.
|
||||
func (r *Request) Reply(ok bool, payload []byte) error {
|
||||
if !r.WantReply {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.ch == nil {
|
||||
return r.mux.ackRequest(ok, payload)
|
||||
}
|
||||
|
||||
return r.ch.ackRequest(ok)
|
||||
}
|
||||
|
||||
// RejectionReason is an enumeration used when rejecting channel creation
|
||||
// requests. See RFC 4254, section 5.1.
|
||||
type RejectionReason uint32
|
||||
|
||||
const (
|
||||
Prohibited RejectionReason = iota + 1
|
||||
ConnectionFailed
|
||||
UnknownChannelType
|
||||
ResourceShortage
|
||||
)
|
||||
|
||||
// String converts the rejection reason to human readable form.
|
||||
func (r RejectionReason) String() string {
|
||||
switch r {
|
||||
case Prohibited:
|
||||
return "administratively prohibited"
|
||||
case ConnectionFailed:
|
||||
return "connect failed"
|
||||
case UnknownChannelType:
|
||||
return "unknown channel type"
|
||||
case ResourceShortage:
|
||||
return "resource shortage"
|
||||
}
|
||||
return fmt.Sprintf("unknown reason %d", int(r))
|
||||
}
|
||||
|
||||
func min(a uint32, b int) uint32 {
|
||||
if a < uint32(b) {
|
||||
return a
|
||||
}
|
||||
return uint32(b)
|
||||
}
|
||||
|
||||
type channelDirection uint8
|
||||
|
||||
const (
|
||||
channelInbound channelDirection = iota
|
||||
channelOutbound
|
||||
)
|
||||
|
||||
// channel is an implementation of the Channel interface that works
|
||||
// with the mux class.
|
||||
type channel struct {
|
||||
// R/O after creation
|
||||
chanType string
|
||||
extraData []byte
|
||||
localId, remoteId uint32
|
||||
|
||||
// maxIncomingPayload and maxRemotePayload are the maximum
|
||||
// payload sizes of normal and extended data packets for
|
||||
// receiving and sending, respectively. The wire packet will
|
||||
// be 9 or 13 bytes larger (excluding encryption overhead).
|
||||
maxIncomingPayload uint32
|
||||
maxRemotePayload uint32
|
||||
|
||||
mux *mux
|
||||
|
||||
// decided is set to true if an accept or reject message has been sent
|
||||
// (for outbound channels) or received (for inbound channels).
|
||||
decided bool
|
||||
|
||||
// direction contains either channelOutbound, for channels created
|
||||
// locally, or channelInbound, for channels created by the peer.
|
||||
direction channelDirection
|
||||
|
||||
// Pending internal channel messages.
|
||||
msg chan interface{}
|
||||
|
||||
// Since requests have no ID, there can be only one request
|
||||
// with WantReply=true outstanding. This lock is held by a
|
||||
// goroutine that has such an outgoing request pending.
|
||||
sentRequestMu sync.Mutex
|
||||
|
||||
incomingRequests chan *Request
|
||||
|
||||
sentEOF bool
|
||||
|
||||
// thread-safe data
|
||||
remoteWin window
|
||||
pending *buffer
|
||||
extPending *buffer
|
||||
|
||||
// windowMu protects myWindow, the flow-control window.
|
||||
windowMu sync.Mutex
|
||||
myWindow uint32
|
||||
|
||||
// writeMu serializes calls to mux.conn.writePacket() and
|
||||
// protects sentClose and packetPool. This mutex must be
|
||||
// different from windowMu, as writePacket can block if there
|
||||
// is a key exchange pending.
|
||||
writeMu sync.Mutex
|
||||
sentClose bool
|
||||
|
||||
// packetPool has a buffer for each extended channel ID to
|
||||
// save allocations during writes.
|
||||
packetPool map[uint32][]byte
|
||||
}
|
||||
|
||||
// writePacket sends a packet. If the packet is a channel close, it updates
|
||||
// sentClose. This method takes the lock c.writeMu.
|
||||
func (c *channel) writePacket(packet []byte) error {
|
||||
c.writeMu.Lock()
|
||||
if c.sentClose {
|
||||
c.writeMu.Unlock()
|
||||
return io.EOF
|
||||
}
|
||||
c.sentClose = (packet[0] == msgChannelClose)
|
||||
err := c.mux.conn.writePacket(packet)
|
||||
c.writeMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *channel) sendMessage(msg interface{}) error {
|
||||
if debugMux {
|
||||
log.Printf("send %d: %#v", c.mux.chanList.offset, msg)
|
||||
}
|
||||
|
||||
p := Marshal(msg)
|
||||
binary.BigEndian.PutUint32(p[1:], c.remoteId)
|
||||
return c.writePacket(p)
|
||||
}
|
||||
|
||||
// WriteExtended writes data to a specific extended stream. These streams are
|
||||
// used, for example, for stderr.
|
||||
func (c *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) {
|
||||
if c.sentEOF {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
||||
opCode := byte(msgChannelData)
|
||||
headerLength := uint32(9)
|
||||
if extendedCode > 0 {
|
||||
headerLength += 4
|
||||
opCode = msgChannelExtendedData
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
packet := c.packetPool[extendedCode]
|
||||
// We don't remove the buffer from packetPool, so
|
||||
// WriteExtended calls from different goroutines will be
|
||||
// flagged as errors by the race detector.
|
||||
c.writeMu.Unlock()
|
||||
|
||||
for len(data) > 0 {
|
||||
space := min(c.maxRemotePayload, len(data))
|
||||
if space, err = c.remoteWin.reserve(space); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if want := headerLength + space; uint32(cap(packet)) < want {
|
||||
packet = make([]byte, want)
|
||||
} else {
|
||||
packet = packet[:want]
|
||||
}
|
||||
|
||||
todo := data[:space]
|
||||
|
||||
packet[0] = opCode
|
||||
binary.BigEndian.PutUint32(packet[1:], c.remoteId)
|
||||
if extendedCode > 0 {
|
||||
binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode))
|
||||
}
|
||||
binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo)))
|
||||
copy(packet[headerLength:], todo)
|
||||
if err = c.writePacket(packet); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
n += len(todo)
|
||||
data = data[len(todo):]
|
||||
}
|
||||
|
||||
c.writeMu.Lock()
|
||||
c.packetPool[extendedCode] = packet
|
||||
c.writeMu.Unlock()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) handleData(packet []byte) error {
|
||||
headerLen := 9
|
||||
isExtendedData := packet[0] == msgChannelExtendedData
|
||||
if isExtendedData {
|
||||
headerLen = 13
|
||||
}
|
||||
if len(packet) < headerLen {
|
||||
// malformed data packet
|
||||
return parseError(packet[0])
|
||||
}
|
||||
|
||||
var extended uint32
|
||||
if isExtendedData {
|
||||
extended = binary.BigEndian.Uint32(packet[5:])
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen])
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
if length > c.maxIncomingPayload {
|
||||
// TODO(hanwen): should send Disconnect?
|
||||
return errors.New("ssh: incoming packet exceeds maximum payload size")
|
||||
}
|
||||
|
||||
data := packet[headerLen:]
|
||||
if length != uint32(len(data)) {
|
||||
return errors.New("ssh: wrong packet length")
|
||||
}
|
||||
|
||||
c.windowMu.Lock()
|
||||
if c.myWindow < length {
|
||||
c.windowMu.Unlock()
|
||||
// TODO(hanwen): should send Disconnect with reason?
|
||||
return errors.New("ssh: remote side wrote too much")
|
||||
}
|
||||
c.myWindow -= length
|
||||
c.windowMu.Unlock()
|
||||
|
||||
if extended == 1 {
|
||||
c.extPending.write(data)
|
||||
} else if extended > 0 {
|
||||
// discard other extended data.
|
||||
} else {
|
||||
c.pending.write(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) adjustWindow(n uint32) error {
|
||||
c.windowMu.Lock()
|
||||
// Since myWindow is managed on our side, and can never exceed
|
||||
// the initial window setting, we don't worry about overflow.
|
||||
c.myWindow += uint32(n)
|
||||
c.windowMu.Unlock()
|
||||
return c.sendMessage(windowAdjustMsg{
|
||||
AdditionalBytes: uint32(n),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) {
|
||||
switch extended {
|
||||
case 1:
|
||||
n, err = c.extPending.Read(data)
|
||||
case 0:
|
||||
n, err = c.pending.Read(data)
|
||||
default:
|
||||
return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended)
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
err = c.adjustWindow(uint32(n))
|
||||
// sendWindowAdjust can return io.EOF if the remote
|
||||
// peer has closed the connection, however we want to
|
||||
// defer forwarding io.EOF to the caller of Read until
|
||||
// the buffer has been drained.
|
||||
if n > 0 && err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *channel) close() {
|
||||
c.pending.eof()
|
||||
c.extPending.eof()
|
||||
close(c.msg)
|
||||
close(c.incomingRequests)
|
||||
c.writeMu.Lock()
|
||||
// This is not necesary for a normal channel teardown, but if
|
||||
// there was another error, it is.
|
||||
c.sentClose = true
|
||||
c.writeMu.Unlock()
|
||||
// Unblock writers.
|
||||
c.remoteWin.close()
|
||||
}
|
||||
|
||||
// responseMessageReceived is called when a success or failure message is
|
||||
// received on a channel to check that such a message is reasonable for the
|
||||
// given channel.
|
||||
func (c *channel) responseMessageReceived() error {
|
||||
if c.direction == channelInbound {
|
||||
return errors.New("ssh: channel response message received on inbound channel")
|
||||
}
|
||||
if c.decided {
|
||||
return errors.New("ssh: duplicate response received for channel")
|
||||
}
|
||||
c.decided = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *channel) handlePacket(packet []byte) error {
|
||||
switch packet[0] {
|
||||
case msgChannelData, msgChannelExtendedData:
|
||||
return c.handleData(packet)
|
||||
case msgChannelClose:
|
||||
c.sendMessage(channelCloseMsg{PeersId: c.remoteId})
|
||||
c.mux.chanList.remove(c.localId)
|
||||
c.close()
|
||||
return nil
|
||||
case msgChannelEOF:
|
||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
||||
// it is logical to signal EOF at the same time.
|
||||
c.extPending.eof()
|
||||
c.pending.eof()
|
||||
return nil
|
||||
}
|
||||
|
||||
decoded, err := decode(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg := decoded.(type) {
|
||||
case *channelOpenFailureMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.mux.chanList.remove(msg.PeersId)
|
||||
c.msg <- msg
|
||||
case *channelOpenConfirmMsg:
|
||||
if err := c.responseMessageReceived(); err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
||||
return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize)
|
||||
}
|
||||
c.remoteId = msg.MyId
|
||||
c.maxRemotePayload = msg.MaxPacketSize
|
||||
c.remoteWin.add(msg.MyWindow)
|
||||
c.msg <- msg
|
||||
case *windowAdjustMsg:
|
||||
if !c.remoteWin.add(msg.AdditionalBytes) {
|
||||
return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes)
|
||||
}
|
||||
case *channelRequestMsg:
|
||||
req := Request{
|
||||
Type: msg.Request,
|
||||
WantReply: msg.WantReply,
|
||||
Payload: msg.RequestSpecificData,
|
||||
ch: c,
|
||||
}
|
||||
|
||||
c.incomingRequests <- &req
|
||||
default:
|
||||
c.msg <- msg
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
|
||||
ch := &channel{
|
||||
remoteWin: window{Cond: newCond()},
|
||||
myWindow: channelWindowSize,
|
||||
pending: newBuffer(),
|
||||
extPending: newBuffer(),
|
||||
direction: direction,
|
||||
incomingRequests: make(chan *Request, 16),
|
||||
msg: make(chan interface{}, 16),
|
||||
chanType: chanType,
|
||||
extraData: extraData,
|
||||
mux: m,
|
||||
packetPool: make(map[uint32][]byte),
|
||||
}
|
||||
ch.localId = m.chanList.add(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
var errUndecided = errors.New("ssh: must Accept or Reject channel")
|
||||
var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once")
|
||||
|
||||
type extChannel struct {
|
||||
code uint32
|
||||
ch *channel
|
||||
}
|
||||
|
||||
func (e *extChannel) Write(data []byte) (n int, err error) {
|
||||
return e.ch.WriteExtended(data, e.code)
|
||||
}
|
||||
|
||||
func (e *extChannel) Read(data []byte) (n int, err error) {
|
||||
return e.ch.ReadExtended(data, e.code)
|
||||
}
|
||||
|
||||
func (c *channel) Accept() (Channel, <-chan *Request, error) {
|
||||
if c.decided {
|
||||
return nil, nil, errDecidedAlready
|
||||
}
|
||||
c.maxIncomingPayload = channelMaxPacket
|
||||
confirm := channelOpenConfirmMsg{
|
||||
PeersId: c.remoteId,
|
||||
MyId: c.localId,
|
||||
MyWindow: c.myWindow,
|
||||
MaxPacketSize: c.maxIncomingPayload,
|
||||
}
|
||||
c.decided = true
|
||||
if err := c.sendMessage(confirm); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return c, c.incomingRequests, nil
|
||||
}
|
||||
|
||||
func (ch *channel) Reject(reason RejectionReason, message string) error {
|
||||
if ch.decided {
|
||||
return errDecidedAlready
|
||||
}
|
||||
reject := channelOpenFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
Language: "en",
|
||||
}
|
||||
ch.decided = true
|
||||
return ch.sendMessage(reject)
|
||||
}
|
||||
|
||||
func (ch *channel) Read(data []byte) (int, error) {
|
||||
if !ch.decided {
|
||||
return 0, errUndecided
|
||||
}
|
||||
return ch.ReadExtended(data, 0)
|
||||
}
|
||||
|
||||
func (ch *channel) Write(data []byte) (int, error) {
|
||||
if !ch.decided {
|
||||
return 0, errUndecided
|
||||
}
|
||||
return ch.WriteExtended(data, 0)
|
||||
}
|
||||
|
||||
func (ch *channel) CloseWrite() error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
ch.sentEOF = true
|
||||
return ch.sendMessage(channelEOFMsg{
|
||||
PeersId: ch.remoteId})
|
||||
}
|
||||
|
||||
func (ch *channel) Close() error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
|
||||
return ch.sendMessage(channelCloseMsg{
|
||||
PeersId: ch.remoteId})
|
||||
}
|
||||
|
||||
// Extended returns an io.ReadWriter that sends and receives data on the given,
|
||||
// SSH extended stream. Such streams are used, for example, for stderr.
|
||||
func (ch *channel) Extended(code uint32) io.ReadWriter {
|
||||
if !ch.decided {
|
||||
return nil
|
||||
}
|
||||
return &extChannel{code, ch}
|
||||
}
|
||||
|
||||
func (ch *channel) Stderr() io.ReadWriter {
|
||||
return ch.Extended(1)
|
||||
}
|
||||
|
||||
func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) {
|
||||
if !ch.decided {
|
||||
return false, errUndecided
|
||||
}
|
||||
|
||||
if wantReply {
|
||||
ch.sentRequestMu.Lock()
|
||||
defer ch.sentRequestMu.Unlock()
|
||||
}
|
||||
|
||||
msg := channelRequestMsg{
|
||||
PeersId: ch.remoteId,
|
||||
Request: name,
|
||||
WantReply: wantReply,
|
||||
RequestSpecificData: payload,
|
||||
}
|
||||
|
||||
if err := ch.sendMessage(msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if wantReply {
|
||||
m, ok := (<-ch.msg)
|
||||
if !ok {
|
||||
return false, io.EOF
|
||||
}
|
||||
switch m.(type) {
|
||||
case *channelRequestFailureMsg:
|
||||
return false, nil
|
||||
case *channelRequestSuccessMsg:
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ackRequest either sends an ack or nack to the channel request.
|
||||
func (ch *channel) ackRequest(ok bool) error {
|
||||
if !ch.decided {
|
||||
return errUndecided
|
||||
}
|
||||
|
||||
var msg interface{}
|
||||
if !ok {
|
||||
msg = channelRequestFailureMsg{
|
||||
PeersId: ch.remoteId,
|
||||
}
|
||||
} else {
|
||||
msg = channelRequestSuccessMsg{
|
||||
PeersId: ch.remoteId,
|
||||
}
|
||||
}
|
||||
return ch.sendMessage(msg)
|
||||
}
|
||||
|
||||
func (ch *channel) ChannelType() string {
|
||||
return ch.chanType
|
||||
}
|
||||
|
||||
func (ch *channel) ExtraData() []byte {
|
||||
return ch.extraData
|
||||
}
|
||||
549
modules/crypto/ssh/cipher.go
Executable file
549
modules/crypto/ssh/cipher.go
Executable file
@@ -0,0 +1,549 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rc4"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const (
|
||||
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
||||
|
||||
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
|
||||
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
|
||||
// indicates implementations SHOULD be able to handle larger packet sizes, but then
|
||||
// waffles on about reasonable limits.
|
||||
//
|
||||
// OpenSSH caps their maxPacket at 256kB so we choose to do
|
||||
// the same. maxPacket is also used to ensure that uint32
|
||||
// length fields do not overflow, so it should remain well
|
||||
// below 4G.
|
||||
maxPacket = 256 * 1024
|
||||
)
|
||||
|
||||
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
||||
// by the transport before the first key-exchange.
|
||||
type noneCipher struct{}
|
||||
|
||||
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
||||
copy(dst, src)
|
||||
}
|
||||
|
||||
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(c, iv), nil
|
||||
}
|
||||
|
||||
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
||||
return rc4.NewCipher(key)
|
||||
}
|
||||
|
||||
type streamCipherMode struct {
|
||||
keySize int
|
||||
ivSize int
|
||||
skip int
|
||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
||||
}
|
||||
|
||||
func (c *streamCipherMode) createStream(key, iv []byte) (cipher.Stream, error) {
|
||||
if len(key) < c.keySize {
|
||||
panic("ssh: key length too small for cipher")
|
||||
}
|
||||
if len(iv) < c.ivSize {
|
||||
panic("ssh: iv too small for cipher")
|
||||
}
|
||||
|
||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var streamDump []byte
|
||||
if c.skip > 0 {
|
||||
streamDump = make([]byte, 512)
|
||||
}
|
||||
|
||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
||||
dumpThisTime := remainingToDump
|
||||
if dumpThisTime > len(streamDump) {
|
||||
dumpThisTime = len(streamDump)
|
||||
}
|
||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
||||
remainingToDump -= dumpThisTime
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
||||
// are not supported and will not be negotiated, even if explicitly requested in
|
||||
// ClientConfig.Crypto.Ciphers.
|
||||
var cipherModes = map[string]*streamCipherMode{
|
||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
||||
// are defined in the order specified in the RFC.
|
||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
||||
|
||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
||||
// They are defined in the order specified in the RFC.
|
||||
"arcfour128": {16, 0, 1536, newRC4},
|
||||
"arcfour256": {32, 0, 1536, newRC4},
|
||||
|
||||
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
|
||||
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
|
||||
// RC4) has problems with weak keys, and should be used with caution."
|
||||
// RFC4345 introduces improved versions of Arcfour.
|
||||
"arcfour": {16, 0, 0, newRC4},
|
||||
|
||||
// AES-GCM is not a stream cipher, so it is constructed with a
|
||||
// special case. If we add any more non-stream ciphers, we
|
||||
// should invest a cleaner way to do this.
|
||||
gcmCipherID: {16, 12, 0, nil},
|
||||
|
||||
// insecure cipher, see http://www.isg.rhul.ac.uk/~kp/SandPfinal.pdf
|
||||
// uncomment below to enable it.
|
||||
// aes128cbcID: {16, aes.BlockSize, 0, nil},
|
||||
}
|
||||
|
||||
// prefixLen is the length of the packet prefix that contains the packet length
|
||||
// and number of padding bytes.
|
||||
const prefixLen = 5
|
||||
|
||||
// streamPacketCipher is a packetCipher using a stream cipher.
|
||||
type streamPacketCipher struct {
|
||||
mac hash.Hash
|
||||
cipher cipher.Stream
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
prefix [prefixLen]byte
|
||||
seqNumBytes [4]byte
|
||||
padding [2 * packetSizeMultiple]byte
|
||||
packetData []byte
|
||||
macResult []byte
|
||||
}
|
||||
|
||||
// readPacket reads and decrypt a single packet from the reader argument.
|
||||
func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
if _, err := io.ReadFull(r, s.prefix[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
length := binary.BigEndian.Uint32(s.prefix[0:4])
|
||||
paddingLength := uint32(s.prefix[4])
|
||||
|
||||
var macSize uint32
|
||||
if s.mac != nil {
|
||||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
s.mac.Write(s.prefix[:])
|
||||
macSize = uint32(s.mac.Size())
|
||||
}
|
||||
|
||||
if length <= paddingLength+1 {
|
||||
return nil, errors.New("ssh: invalid packet length, packet too small")
|
||||
}
|
||||
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: invalid packet length, packet too large")
|
||||
}
|
||||
|
||||
// the maxPacket check above ensures that length-1+macSize
|
||||
// does not overflow.
|
||||
if uint32(cap(s.packetData)) < length-1+macSize {
|
||||
s.packetData = make([]byte, length-1+macSize)
|
||||
} else {
|
||||
s.packetData = s.packetData[:length-1+macSize]
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, s.packetData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := s.packetData[length-1:]
|
||||
data := s.packetData[:length-1]
|
||||
s.cipher.XORKeyStream(data, data)
|
||||
|
||||
if s.mac != nil {
|
||||
s.mac.Write(data)
|
||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
||||
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
|
||||
return nil, errors.New("ssh: MAC failure")
|
||||
}
|
||||
}
|
||||
|
||||
return s.packetData[:length-paddingLength-1], nil
|
||||
}
|
||||
|
||||
// writePacket encrypts and sends a packet of data to the writer argument
|
||||
func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
if len(packet) > maxPacket {
|
||||
return errors.New("ssh: packet too large")
|
||||
}
|
||||
|
||||
paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple
|
||||
if paddingLength < 4 {
|
||||
paddingLength += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := len(packet) + 1 + paddingLength
|
||||
binary.BigEndian.PutUint32(s.prefix[:], uint32(length))
|
||||
s.prefix[4] = byte(paddingLength)
|
||||
padding := s.padding[:paddingLength]
|
||||
if _, err := io.ReadFull(rand, padding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.mac != nil {
|
||||
s.mac.Reset()
|
||||
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
|
||||
s.mac.Write(s.seqNumBytes[:])
|
||||
s.mac.Write(s.prefix[:])
|
||||
s.mac.Write(packet)
|
||||
s.mac.Write(padding)
|
||||
}
|
||||
|
||||
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
|
||||
s.cipher.XORKeyStream(packet, packet)
|
||||
s.cipher.XORKeyStream(padding, padding)
|
||||
|
||||
if _, err := w.Write(s.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(packet); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(padding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.mac != nil {
|
||||
s.macResult = s.mac.Sum(s.macResult[:0])
|
||||
if _, err := w.Write(s.macResult); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type gcmCipher struct {
|
||||
aead cipher.AEAD
|
||||
prefix [4]byte
|
||||
iv []byte
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newGCMCipher(iv, key, macKey []byte) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gcmCipher{
|
||||
aead: aead,
|
||||
iv: iv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const gcmTagSize = 16
|
||||
|
||||
func (c *gcmCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
// Pad out to multiple of 16 bytes. This is different from the
|
||||
// stream cipher because that encrypts the length too.
|
||||
padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple)
|
||||
if padding < 4 {
|
||||
padding += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := uint32(len(packet) + int(padding) + 1)
|
||||
binary.BigEndian.PutUint32(c.prefix[:], length)
|
||||
if _, err := w.Write(c.prefix[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length) {
|
||||
c.buf = make([]byte, length)
|
||||
} else {
|
||||
c.buf = c.buf[:length]
|
||||
}
|
||||
|
||||
c.buf[0] = padding
|
||||
copy(c.buf[1:], packet)
|
||||
if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil {
|
||||
return err
|
||||
}
|
||||
c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
||||
if _, err := w.Write(c.buf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.incIV()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *gcmCipher) incIV() {
|
||||
for i := 4 + 7; i >= 4; i-- {
|
||||
c.iv[i]++
|
||||
if c.iv[i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gcmCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
if _, err := io.ReadFull(r, c.prefix[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := binary.BigEndian.Uint32(c.prefix[:])
|
||||
if length > maxPacket {
|
||||
return nil, errors.New("ssh: max packet length exceeded.")
|
||||
}
|
||||
|
||||
if cap(c.buf) < int(length+gcmTagSize) {
|
||||
c.buf = make([]byte, length+gcmTagSize)
|
||||
} else {
|
||||
c.buf = c.buf[:length+gcmTagSize]
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, c.buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.incIV()
|
||||
|
||||
padding := plain[0]
|
||||
if padding < 4 || padding >= 20 {
|
||||
return nil, fmt.Errorf("ssh: illegal padding %d", padding)
|
||||
}
|
||||
|
||||
if int(padding+1) >= len(plain) {
|
||||
return nil, fmt.Errorf("ssh: padding %d too large", padding)
|
||||
}
|
||||
plain = plain[1 : length-uint32(padding)]
|
||||
return plain, nil
|
||||
}
|
||||
|
||||
// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1
|
||||
type cbcCipher struct {
|
||||
mac hash.Hash
|
||||
macSize uint32
|
||||
decrypter cipher.BlockMode
|
||||
encrypter cipher.BlockMode
|
||||
|
||||
// The following members are to avoid per-packet allocations.
|
||||
seqNumBytes [4]byte
|
||||
packetData []byte
|
||||
macResult []byte
|
||||
|
||||
// Amount of data we should still read to hide which
|
||||
// verification error triggered.
|
||||
oracleCamouflage uint32
|
||||
}
|
||||
|
||||
func newAESCBCCipher(iv, key, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cbc := &cbcCipher{
|
||||
mac: macModes[algs.MAC].new(macKey),
|
||||
decrypter: cipher.NewCBCDecrypter(c, iv),
|
||||
encrypter: cipher.NewCBCEncrypter(c, iv),
|
||||
packetData: make([]byte, 1024),
|
||||
}
|
||||
if cbc.mac != nil {
|
||||
cbc.macSize = uint32(cbc.mac.Size())
|
||||
}
|
||||
|
||||
return cbc, nil
|
||||
}
|
||||
|
||||
func maxUInt32(a, b int) uint32 {
|
||||
if a > b {
|
||||
return uint32(a)
|
||||
}
|
||||
return uint32(b)
|
||||
}
|
||||
|
||||
const (
|
||||
cbcMinPacketSizeMultiple = 8
|
||||
cbcMinPacketSize = 16
|
||||
cbcMinPaddingSize = 4
|
||||
)
|
||||
|
||||
// cbcError represents a verification error that may leak information.
|
||||
type cbcError string
|
||||
|
||||
func (e cbcError) Error() string { return string(e) }
|
||||
|
||||
func (c *cbcCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
p, err := c.readPacketLeaky(seqNum, r)
|
||||
if err != nil {
|
||||
if _, ok := err.(cbcError); ok {
|
||||
// Verification error: read a fixed amount of
|
||||
// data, to make distinguishing between
|
||||
// failing MAC and failing length check more
|
||||
// difficult.
|
||||
io.CopyN(ioutil.Discard, r, int64(c.oracleCamouflage))
|
||||
}
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (c *cbcCipher) readPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) {
|
||||
blockSize := c.decrypter.BlockSize()
|
||||
|
||||
// Read the header, which will include some of the subsequent data in the
|
||||
// case of block ciphers - this is copied back to the payload later.
|
||||
// How many bytes of payload/padding will be read with this first read.
|
||||
firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize)
|
||||
firstBlock := c.packetData[:firstBlockLength]
|
||||
if _, err := io.ReadFull(r, firstBlock); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength
|
||||
|
||||
c.decrypter.CryptBlocks(firstBlock, firstBlock)
|
||||
length := binary.BigEndian.Uint32(firstBlock[:4])
|
||||
if length > maxPacket {
|
||||
return nil, cbcError("ssh: packet too large")
|
||||
}
|
||||
if length+4 < maxUInt32(cbcMinPacketSize, blockSize) {
|
||||
// The minimum size of a packet is 16 (or the cipher block size, whichever
|
||||
// is larger) bytes.
|
||||
return nil, cbcError("ssh: packet too small")
|
||||
}
|
||||
// The length of the packet (including the length field but not the MAC) must
|
||||
// be a multiple of the block size or 8, whichever is larger.
|
||||
if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 {
|
||||
return nil, cbcError("ssh: invalid packet length multiple")
|
||||
}
|
||||
|
||||
paddingLength := uint32(firstBlock[4])
|
||||
if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 {
|
||||
return nil, cbcError("ssh: invalid packet length")
|
||||
}
|
||||
|
||||
// Positions within the c.packetData buffer:
|
||||
macStart := 4 + length
|
||||
paddingStart := macStart - paddingLength
|
||||
|
||||
// Entire packet size, starting before length, ending at end of mac.
|
||||
entirePacketSize := macStart + c.macSize
|
||||
|
||||
// Ensure c.packetData is large enough for the entire packet data.
|
||||
if uint32(cap(c.packetData)) < entirePacketSize {
|
||||
// Still need to upsize and copy, but this should be rare at runtime, only
|
||||
// on upsizing the packetData buffer.
|
||||
c.packetData = make([]byte, entirePacketSize)
|
||||
copy(c.packetData, firstBlock)
|
||||
} else {
|
||||
c.packetData = c.packetData[:entirePacketSize]
|
||||
}
|
||||
|
||||
if n, err := io.ReadFull(r, c.packetData[firstBlockLength:]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
c.oracleCamouflage -= uint32(n)
|
||||
}
|
||||
|
||||
remainingCrypted := c.packetData[firstBlockLength:macStart]
|
||||
c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted)
|
||||
|
||||
mac := c.packetData[macStart:]
|
||||
if c.mac != nil {
|
||||
c.mac.Reset()
|
||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
||||
c.mac.Write(c.seqNumBytes[:])
|
||||
c.mac.Write(c.packetData[:macStart])
|
||||
c.macResult = c.mac.Sum(c.macResult[:0])
|
||||
if subtle.ConstantTimeCompare(c.macResult, mac) != 1 {
|
||||
return nil, cbcError("ssh: MAC failure")
|
||||
}
|
||||
}
|
||||
|
||||
return c.packetData[prefixLen:paddingStart], nil
|
||||
}
|
||||
|
||||
func (c *cbcCipher) writePacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error {
|
||||
effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize())
|
||||
|
||||
// Length of encrypted portion of the packet (header, payload, padding).
|
||||
// Enforce minimum padding and packet size.
|
||||
encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPaddingSize)
|
||||
// Enforce block size.
|
||||
encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize
|
||||
|
||||
length := encLength - 4
|
||||
paddingLength := int(length) - (1 + len(packet))
|
||||
|
||||
// Overall buffer contains: header, payload, padding, mac.
|
||||
// Space for the MAC is reserved in the capacity but not the slice length.
|
||||
bufferSize := encLength + c.macSize
|
||||
if uint32(cap(c.packetData)) < bufferSize {
|
||||
c.packetData = make([]byte, encLength, bufferSize)
|
||||
} else {
|
||||
c.packetData = c.packetData[:encLength]
|
||||
}
|
||||
|
||||
p := c.packetData
|
||||
|
||||
// Packet header.
|
||||
binary.BigEndian.PutUint32(p, length)
|
||||
p = p[4:]
|
||||
p[0] = byte(paddingLength)
|
||||
|
||||
// Payload.
|
||||
p = p[1:]
|
||||
copy(p, packet)
|
||||
|
||||
// Padding.
|
||||
p = p[len(packet):]
|
||||
if _, err := io.ReadFull(rand, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.mac != nil {
|
||||
c.mac.Reset()
|
||||
binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum)
|
||||
c.mac.Write(c.seqNumBytes[:])
|
||||
c.mac.Write(c.packetData)
|
||||
// The MAC is now appended into the capacity reserved for it earlier.
|
||||
c.packetData = c.mac.Sum(c.packetData)
|
||||
}
|
||||
|
||||
c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength])
|
||||
|
||||
if _, err := w.Write(c.packetData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
127
modules/crypto/ssh/cipher_test.go
Executable file
127
modules/crypto/ssh/cipher_test.go
Executable file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultCiphersExist(t *testing.T) {
|
||||
for _, cipherAlgo := range supportedCiphers {
|
||||
if _, ok := cipherModes[cipherAlgo]; !ok {
|
||||
t.Errorf("default cipher %q is unknown", cipherAlgo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPacketCiphers(t *testing.T) {
|
||||
// Still test aes128cbc cipher althought it's commented out.
|
||||
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
|
||||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
for cipher := range cipherModes {
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: cipher,
|
||||
MAC: "hmac-sha1",
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Errorf("newPacketCipher(client, %q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket(%q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
|
||||
packet, err := server.readPacket(0, buf)
|
||||
if err != nil {
|
||||
t.Errorf("readPacket(%q): %v", cipher, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(packet) != want {
|
||||
t.Errorf("roundtrip(%q): got %q, want %q", cipher, packet, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCBCOracleCounterMeasure(t *testing.T) {
|
||||
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
|
||||
defer delete(cipherModes, aes128cbcID)
|
||||
|
||||
kr := &kexResult{Hash: crypto.SHA1}
|
||||
algs := directionAlgorithms{
|
||||
Cipher: aes128cbcID,
|
||||
MAC: "hmac-sha1",
|
||||
Compression: "none",
|
||||
}
|
||||
client, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Fatalf("newPacketCipher(client): %v", err)
|
||||
}
|
||||
|
||||
want := "bla bla"
|
||||
input := []byte(want)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
|
||||
t.Errorf("writePacket: %v", err)
|
||||
}
|
||||
|
||||
packetSize := buf.Len()
|
||||
buf.Write(make([]byte, 2*maxPacket))
|
||||
|
||||
// We corrupt each byte, but this usually will only test the
|
||||
// 'packet too large' or 'MAC failure' cases.
|
||||
lastRead := -1
|
||||
for i := 0; i < packetSize; i++ {
|
||||
server, err := newPacketCipher(clientKeys, algs, kr)
|
||||
if err != nil {
|
||||
t.Fatalf("newPacketCipher(client): %v", err)
|
||||
}
|
||||
|
||||
fresh := &bytes.Buffer{}
|
||||
fresh.Write(buf.Bytes())
|
||||
fresh.Bytes()[i] ^= 0x01
|
||||
|
||||
before := fresh.Len()
|
||||
_, err = server.readPacket(0, fresh)
|
||||
if err == nil {
|
||||
t.Errorf("corrupt byte %d: readPacket succeeded ", i)
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(cbcError); !ok {
|
||||
t.Errorf("corrupt byte %d: got %v (%T), want cbcError", i, err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
after := fresh.Len()
|
||||
bytesRead := before - after
|
||||
if bytesRead < maxPacket {
|
||||
t.Errorf("corrupt byte %d: read %d bytes, want more than %d", i, bytesRead, maxPacket)
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 && bytesRead != lastRead {
|
||||
t.Errorf("corrupt byte %d: read %d bytes, want %d bytes read", i, bytesRead, lastRead)
|
||||
}
|
||||
lastRead = bytesRead
|
||||
}
|
||||
}
|
||||
213
modules/crypto/ssh/client.go
Executable file
213
modules/crypto/ssh/client.go
Executable file
@@ -0,0 +1,213 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Client implements a traditional SSH client that supports shells,
|
||||
// subprocesses, port forwarding and tunneled dialing.
|
||||
type Client struct {
|
||||
Conn
|
||||
|
||||
forwards forwardList // forwarded tcpip connections from the remote side
|
||||
mu sync.Mutex
|
||||
channelHandlers map[string]chan NewChannel
|
||||
}
|
||||
|
||||
// HandleChannelOpen returns a channel on which NewChannel requests
|
||||
// for the given type are sent. If the type already is being handled,
|
||||
// nil is returned. The channel is closed when the connection is closed.
|
||||
func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.channelHandlers == nil {
|
||||
// The SSH channel has been closed.
|
||||
c := make(chan NewChannel)
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
|
||||
ch := c.channelHandlers[channelType]
|
||||
if ch != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ch = make(chan NewChannel, 16)
|
||||
c.channelHandlers[channelType] = ch
|
||||
return ch
|
||||
}
|
||||
|
||||
// NewClient creates a Client on top of the given connection.
|
||||
func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client {
|
||||
conn := &Client{
|
||||
Conn: c,
|
||||
channelHandlers: make(map[string]chan NewChannel, 1),
|
||||
}
|
||||
|
||||
go conn.handleGlobalRequests(reqs)
|
||||
go conn.handleChannelOpens(chans)
|
||||
go func() {
|
||||
conn.Wait()
|
||||
conn.forwards.closeAll()
|
||||
}()
|
||||
go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip"))
|
||||
return conn
|
||||
}
|
||||
|
||||
// NewClientConn establishes an authenticated SSH connection using c
|
||||
// as the underlying transport. The Request and NewChannel channels
|
||||
// must be serviced or the connection will hang.
|
||||
func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) {
|
||||
fullConf := *config
|
||||
fullConf.SetDefaults()
|
||||
conn := &connection{
|
||||
sshConn: sshConn{conn: c},
|
||||
}
|
||||
|
||||
if err := conn.clientHandshake(addr, &fullConf); err != nil {
|
||||
c.Close()
|
||||
return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %v", err)
|
||||
}
|
||||
conn.mux = newMux(conn.transport)
|
||||
return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil
|
||||
}
|
||||
|
||||
// clientHandshake performs the client side key exchange. See RFC 4253 Section
|
||||
// 7.
|
||||
func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error {
|
||||
if config.ClientVersion != "" {
|
||||
c.clientVersion = []byte(config.ClientVersion)
|
||||
} else {
|
||||
c.clientVersion = []byte(packageVersion)
|
||||
}
|
||||
var err error
|
||||
c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.transport = newClientTransport(
|
||||
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
|
||||
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
|
||||
if err := c.transport.requestKeyChange(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if packet, err := c.transport.readPacket(); err != nil {
|
||||
return err
|
||||
} else if packet[0] != msgNewKeys {
|
||||
return unexpectedMessageError(msgNewKeys, packet[0])
|
||||
}
|
||||
|
||||
// We just did the key change, so the session ID is established.
|
||||
c.sessionID = c.transport.getSessionID()
|
||||
|
||||
return c.clientAuthenticate(config)
|
||||
}
|
||||
|
||||
// verifyHostKeySignature verifies the host key obtained in the key
|
||||
// exchange.
|
||||
func verifyHostKeySignature(hostKey PublicKey, result *kexResult) error {
|
||||
sig, rest, ok := parseSignatureBody(result.Signature)
|
||||
if len(rest) > 0 || !ok {
|
||||
return errors.New("ssh: signature parse error")
|
||||
}
|
||||
|
||||
return hostKey.Verify(result.H, sig)
|
||||
}
|
||||
|
||||
// NewSession opens a new Session for this client. (A session is a remote
|
||||
// execution of a program.)
|
||||
func (c *Client) NewSession() (*Session, error) {
|
||||
ch, in, err := c.OpenChannel("session", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSession(ch, in)
|
||||
}
|
||||
|
||||
func (c *Client) handleGlobalRequests(incoming <-chan *Request) {
|
||||
for r := range incoming {
|
||||
// This handles keepalive messages and matches
|
||||
// the behaviour of OpenSSH.
|
||||
r.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// handleChannelOpens channel open messages from the remote side.
|
||||
func (c *Client) handleChannelOpens(in <-chan NewChannel) {
|
||||
for ch := range in {
|
||||
c.mu.Lock()
|
||||
handler := c.channelHandlers[ch.ChannelType()]
|
||||
c.mu.Unlock()
|
||||
|
||||
if handler != nil {
|
||||
handler <- ch
|
||||
} else {
|
||||
ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType()))
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
for _, ch := range c.channelHandlers {
|
||||
close(ch)
|
||||
}
|
||||
c.channelHandlers = nil
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Dial starts a client connection to the given SSH server. It is a
|
||||
// convenience function that connects to the given network address,
|
||||
// initiates the SSH handshake, and then sets up a Client. For access
|
||||
// to incoming channels and requests, use net.Dial with NewClientConn
|
||||
// instead.
|
||||
func Dial(network, addr string, config *ClientConfig) (*Client, error) {
|
||||
conn, err := net.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, chans, reqs, err := NewClientConn(conn, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(c, chans, reqs), nil
|
||||
}
|
||||
|
||||
// A ClientConfig structure is used to configure a Client. It must not be
|
||||
// modified after having been passed to an SSH function.
|
||||
type ClientConfig struct {
|
||||
// Config contains configuration that is shared between clients and
|
||||
// servers.
|
||||
Config
|
||||
|
||||
// User contains the username to authenticate as.
|
||||
User string
|
||||
|
||||
// Auth contains possible authentication methods to use with the
|
||||
// server. Only the first instance of a particular RFC 4252 method will
|
||||
// be used during authentication.
|
||||
Auth []AuthMethod
|
||||
|
||||
// HostKeyCallback, if not nil, is called during the cryptographic
|
||||
// handshake to validate the server's host key. A nil HostKeyCallback
|
||||
// implies that all host keys are accepted.
|
||||
HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
|
||||
|
||||
// ClientVersion contains the version identification string that will
|
||||
// be used for the connection. If empty, a reasonable default is used.
|
||||
ClientVersion string
|
||||
|
||||
// HostKeyAlgorithms lists the key types that the client will
|
||||
// accept from the server as host key, in order of
|
||||
// preference. If empty, a reasonable default is used. Any
|
||||
// string returned from PublicKey.Type method may be used, or
|
||||
// any of the CertAlgoXxxx and KeyAlgoXxxx constants.
|
||||
HostKeyAlgorithms []string
|
||||
}
|
||||
441
modules/crypto/ssh/client_auth.go
Executable file
441
modules/crypto/ssh/client_auth.go
Executable file
@@ -0,0 +1,441 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// clientAuthenticate authenticates with the remote server. See RFC 4252.
|
||||
func (c *connection) clientAuthenticate(config *ClientConfig) error {
|
||||
// initiate user auth session
|
||||
if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
|
||||
return err
|
||||
}
|
||||
packet, err := c.transport.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var serviceAccept serviceAcceptMsg
|
||||
if err := Unmarshal(packet, &serviceAccept); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// during the authentication phase the client first attempts the "none" method
|
||||
// then any untried methods suggested by the server.
|
||||
tried := make(map[string]bool)
|
||||
var lastMethods []string
|
||||
for auth := AuthMethod(new(noneAuth)); auth != nil; {
|
||||
ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
// success
|
||||
return nil
|
||||
}
|
||||
tried[auth.method()] = true
|
||||
if methods == nil {
|
||||
methods = lastMethods
|
||||
}
|
||||
lastMethods = methods
|
||||
|
||||
auth = nil
|
||||
|
||||
findNext:
|
||||
for _, a := range config.Auth {
|
||||
candidateMethod := a.method()
|
||||
if tried[candidateMethod] {
|
||||
continue
|
||||
}
|
||||
for _, meth := range methods {
|
||||
if meth == candidateMethod {
|
||||
auth = a
|
||||
break findNext
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
|
||||
}
|
||||
|
||||
func keys(m map[string]bool) []string {
|
||||
s := make([]string, 0, len(m))
|
||||
|
||||
for key := range m {
|
||||
s = append(s, key)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// An AuthMethod represents an instance of an RFC 4252 authentication method.
|
||||
type AuthMethod interface {
|
||||
// auth authenticates user over transport t.
|
||||
// Returns true if authentication is successful.
|
||||
// If authentication is not successful, a []string of alternative
|
||||
// method names is returned. If the slice is nil, it will be ignored
|
||||
// and the previous set of possible methods will be reused.
|
||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
||||
|
||||
// method returns the RFC 4252 method name.
|
||||
method() string
|
||||
}
|
||||
|
||||
// "none" authentication, RFC 4252 section 5.2.
|
||||
type noneAuth int
|
||||
|
||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
if err := c.writePacket(Marshal(&userAuthRequestMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "none",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return handleAuthResponse(c)
|
||||
}
|
||||
|
||||
func (n *noneAuth) method() string {
|
||||
return "none"
|
||||
}
|
||||
|
||||
// passwordCallback is an AuthMethod that fetches the password through
|
||||
// a function call, e.g. by prompting the user.
|
||||
type passwordCallback func() (password string, err error)
|
||||
|
||||
func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
type passwordAuthMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Reply bool
|
||||
Password string
|
||||
}
|
||||
|
||||
pw, err := cb()
|
||||
// REVIEW NOTE: is there a need to support skipping a password attempt?
|
||||
// The program may only find out that the user doesn't have a password
|
||||
// when prompting.
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if err := c.writePacket(Marshal(&passwordAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
Reply: false,
|
||||
Password: pw,
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return handleAuthResponse(c)
|
||||
}
|
||||
|
||||
func (cb passwordCallback) method() string {
|
||||
return "password"
|
||||
}
|
||||
|
||||
// Password returns an AuthMethod using the given password.
|
||||
func Password(secret string) AuthMethod {
|
||||
return passwordCallback(func() (string, error) { return secret, nil })
|
||||
}
|
||||
|
||||
// PasswordCallback returns an AuthMethod that uses a callback for
|
||||
// fetching a password.
|
||||
func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
|
||||
return passwordCallback(prompt)
|
||||
}
|
||||
|
||||
type publickeyAuthMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
// HasSig indicates to the receiver packet that the auth request is signed and
|
||||
// should be used for authentication of the request.
|
||||
HasSig bool
|
||||
Algoname string
|
||||
PubKey []byte
|
||||
// Sig is tagged with "rest" so Marshal will exclude it during
|
||||
// validateKey
|
||||
Sig []byte `ssh:"rest"`
|
||||
}
|
||||
|
||||
// publicKeyCallback is an AuthMethod that uses a set of key
|
||||
// pairs for authentication.
|
||||
type publicKeyCallback func() ([]Signer, error)
|
||||
|
||||
func (cb publicKeyCallback) method() string {
|
||||
return "publickey"
|
||||
}
|
||||
|
||||
func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
// Authentication is performed in two stages. The first stage sends an
|
||||
// enquiry to test if each key is acceptable to the remote. The second
|
||||
// stage attempts to authenticate with the valid keys obtained in the
|
||||
// first stage.
|
||||
|
||||
signers, err := cb()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var validKeys []Signer
|
||||
for _, signer := range signers {
|
||||
if ok, err := validateKey(signer.PublicKey(), user, c); ok {
|
||||
validKeys = append(validKeys, signer)
|
||||
} else {
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// methods that may continue if this auth is not successful.
|
||||
var methods []string
|
||||
for _, signer := range validKeys {
|
||||
pub := signer.PublicKey()
|
||||
|
||||
pubKey := pub.Marshal()
|
||||
sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
}, []byte(pub.Type()), pubKey))
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// manually wrap the serialized signature in a string
|
||||
s := Marshal(sign)
|
||||
sig := make([]byte, stringLength(len(s)))
|
||||
marshalString(sig, s)
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: cb.method(),
|
||||
HasSig: true,
|
||||
Algoname: pub.Type(),
|
||||
PubKey: pubKey,
|
||||
Sig: sig,
|
||||
}
|
||||
p := Marshal(&msg)
|
||||
if err := c.writePacket(p); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var success bool
|
||||
success, methods, err = handleAuthResponse(c)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if success {
|
||||
return success, methods, err
|
||||
}
|
||||
}
|
||||
return false, methods, nil
|
||||
}
|
||||
|
||||
// validateKey validates the key provided is acceptable to the server.
|
||||
func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
msg := publickeyAuthMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "publickey",
|
||||
HasSig: false,
|
||||
Algoname: key.Type(),
|
||||
PubKey: pubKey,
|
||||
}
|
||||
if err := c.writePacket(Marshal(&msg)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return confirmKeyAck(key, c)
|
||||
}
|
||||
|
||||
func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
||||
pubKey := key.Marshal()
|
||||
algoname := key.Type()
|
||||
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO(gpaul): add callback to present the banner to the user
|
||||
case msgUserAuthPubKeyOk:
|
||||
var msg userAuthPubKeyOkMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
case msgUserAuthFailure:
|
||||
return false, nil
|
||||
default:
|
||||
return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKeys returns an AuthMethod that uses the given key
|
||||
// pairs.
|
||||
func PublicKeys(signers ...Signer) AuthMethod {
|
||||
return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
|
||||
}
|
||||
|
||||
// PublicKeysCallback returns an AuthMethod that runs the given
|
||||
// function to obtain a list of key pairs.
|
||||
func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
|
||||
return publicKeyCallback(getSigners)
|
||||
}
|
||||
|
||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
||||
// along with a list of remaining authentication methods to try next and
|
||||
// an error if an unexpected response was received.
|
||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: add callback to present the banner to the user
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
case msgDisconnect:
|
||||
return false, nil, io.EOF
|
||||
default:
|
||||
return false, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KeyboardInteractiveChallenge should print questions, optionally
|
||||
// disabling echoing (e.g. for passwords), and return all the answers.
|
||||
// Challenge may be called multiple times in a single session. After
|
||||
// successful authentication, the server may send a challenge with no
|
||||
// questions, for which the user and instruction messages should be
|
||||
// printed. RFC 4256 section 3.3 details how the UI should behave for
|
||||
// both CLI and GUI environments.
|
||||
type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
|
||||
|
||||
// KeyboardInteractive returns a AuthMethod using a prompt/response
|
||||
// sequence controlled by the server.
|
||||
func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
|
||||
return challenge
|
||||
}
|
||||
|
||||
func (cb KeyboardInteractiveChallenge) method() string {
|
||||
return "keyboard-interactive"
|
||||
}
|
||||
|
||||
func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
||||
type initiateMsg struct {
|
||||
User string `sshtype:"50"`
|
||||
Service string
|
||||
Method string
|
||||
Language string
|
||||
Submethods string
|
||||
}
|
||||
|
||||
if err := c.writePacket(Marshal(&initiateMsg{
|
||||
User: user,
|
||||
Service: serviceSSH,
|
||||
Method: "keyboard-interactive",
|
||||
})); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
packet, err := c.readPacket()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// like handleAuthResponse, but with less options.
|
||||
switch packet[0] {
|
||||
case msgUserAuthBanner:
|
||||
// TODO: Print banners during userauth.
|
||||
continue
|
||||
case msgUserAuthInfoRequest:
|
||||
// OK
|
||||
case msgUserAuthFailure:
|
||||
var msg userAuthFailureMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return false, msg.Methods, nil
|
||||
case msgUserAuthSuccess:
|
||||
return true, nil, nil
|
||||
default:
|
||||
return false, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
|
||||
}
|
||||
|
||||
var msg userAuthInfoRequestMsg
|
||||
if err := Unmarshal(packet, &msg); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// Manually unpack the prompt/echo pairs.
|
||||
rest := msg.Prompts
|
||||
var prompts []string
|
||||
var echos []bool
|
||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
||||
prompt, r, ok := parseString(rest)
|
||||
if !ok || len(r) == 0 {
|
||||
return false, nil, errors.New("ssh: prompt format error")
|
||||
}
|
||||
prompts = append(prompts, string(prompt))
|
||||
echos = append(echos, r[0] != 0)
|
||||
rest = r[1:]
|
||||
}
|
||||
|
||||
if len(rest) != 0 {
|
||||
return false, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
|
||||
}
|
||||
|
||||
answers, err := cb(msg.User, msg.Instruction, prompts, echos)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if len(answers) != len(prompts) {
|
||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
||||
}
|
||||
responseLength := 1 + 4
|
||||
for _, a := range answers {
|
||||
responseLength += stringLength(len(a))
|
||||
}
|
||||
serialized := make([]byte, responseLength)
|
||||
p := serialized
|
||||
p[0] = msgUserAuthInfoResponse
|
||||
p = p[1:]
|
||||
p = marshalUint32(p, uint32(len(answers)))
|
||||
for _, a := range answers {
|
||||
p = marshalString(p, []byte(a))
|
||||
}
|
||||
|
||||
if err := c.writePacket(serialized); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
393
modules/crypto/ssh/client_auth_test.go
Executable file
393
modules/crypto/ssh/client_auth_test.go
Executable file
@@ -0,0 +1,393 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type keyboardInteractive map[string]string
|
||||
|
||||
func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) {
|
||||
var answers []string
|
||||
for _, q := range questions {
|
||||
answers = append(answers, cr[q])
|
||||
}
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
// reused internally by tests
|
||||
var clientPassword = "tiger"
|
||||
|
||||
// tryAuth runs a handshake with a given config against an SSH server
|
||||
// with config serverConfig
|
||||
func tryAuth(t *testing.T, config *ClientConfig) error {
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
certChecker := CertChecker{
|
||||
IsAuthority: func(k PublicKey) bool {
|
||||
return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal())
|
||||
},
|
||||
UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
|
||||
},
|
||||
IsRevoked: func(c *Certificate) bool {
|
||||
return c.Serial == 666
|
||||
},
|
||||
}
|
||||
|
||||
serverConfig := &ServerConfig{
|
||||
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
|
||||
if conn.User() == "testuser" && string(pass) == clientPassword {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("password auth failed")
|
||||
},
|
||||
PublicKeyCallback: certChecker.Authenticate,
|
||||
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
|
||||
ans, err := challenge("user",
|
||||
"instruction",
|
||||
[]string{"question1", "question2"},
|
||||
[]bool{true, true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2"
|
||||
if ok {
|
||||
challenge("user", "motd", nil, nil)
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.New("keyboard-interactive failed")
|
||||
},
|
||||
AuthLogCallback: func(conn ConnMetadata, method string, err error) {
|
||||
t.Logf("user %q, method %q: %v", conn.User(), method, err)
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
go newServer(c1, serverConfig)
|
||||
_, _, _, err = NewClientConn(c2, "", config)
|
||||
return err
|
||||
}
|
||||
|
||||
func TestClientAuthPublicKey(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodPassword(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
Password(clientPassword),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodFallback(t *testing.T) {
|
||||
var passwordCalled bool
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
PasswordCallback(
|
||||
func() (string, error) {
|
||||
passwordCalled = true
|
||||
return "WRONG", nil
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
|
||||
if passwordCalled {
|
||||
t.Errorf("password auth tried before public-key auth.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodWrongPassword(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
Password("wrong"),
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodKeyboardInteractive(t *testing.T) {
|
||||
answers := keyboardInteractive(map[string]string{
|
||||
"question1": "answer1",
|
||||
"question2": "answer2",
|
||||
})
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("unable to dial remote side: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthMethodWrongKeyboardInteractive(t *testing.T) {
|
||||
answers := keyboardInteractive(map[string]string{
|
||||
"question1": "answer1",
|
||||
"question2": "WRONG",
|
||||
})
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
KeyboardInteractive(answers.Challenge),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive")
|
||||
}
|
||||
}
|
||||
|
||||
// the mock server will only authenticate ssh-rsa keys
|
||||
func TestAuthMethodInvalidPublicKey(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"]),
|
||||
},
|
||||
}
|
||||
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Fatalf("dsa private key should not have authenticated with rsa public key")
|
||||
}
|
||||
}
|
||||
|
||||
// the client should authenticate with the second key
|
||||
func TestAuthMethodRSAandDSA(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["dsa"], testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with rsa key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientHMAC(t *testing.T) {
|
||||
for _, mac := range supportedMACs {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
Config: Config{
|
||||
MACs: []string{mac},
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err != nil {
|
||||
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// issue 4285.
|
||||
func TestClientUnsupportedCipher(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(),
|
||||
},
|
||||
Config: Config{
|
||||
Ciphers: []string{"aes128-cbc"}, // not currently supported
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil {
|
||||
t.Errorf("expected no ciphers in common")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientUnsupportedKex(t *testing.T) {
|
||||
config := &ClientConfig{
|
||||
User: "testuser",
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(),
|
||||
},
|
||||
Config: Config{
|
||||
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported
|
||||
},
|
||||
}
|
||||
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
|
||||
t.Errorf("got %v, expected 'common algorithm'", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientLoginCert(t *testing.T) {
|
||||
cert := &Certificate{
|
||||
Key: testPublicKeys["rsa"],
|
||||
ValidBefore: CertTimeInfinity,
|
||||
CertType: UserCert,
|
||||
}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
certSigner, err := NewCertSigner(cert, testSigners["rsa"])
|
||||
if err != nil {
|
||||
t.Fatalf("NewCertSigner: %v", err)
|
||||
}
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
User: "user",
|
||||
}
|
||||
clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
|
||||
|
||||
t.Log("should succeed")
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("corrupted signature")
|
||||
cert.Signature.Blob[0]++
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with corrupted sig")
|
||||
}
|
||||
|
||||
t.Log("revoked")
|
||||
cert.Serial = 666
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("revoked cert login succeeded")
|
||||
}
|
||||
cert.Serial = 1
|
||||
|
||||
t.Log("sign with wrong key")
|
||||
cert.SignCert(rand.Reader, testSigners["dsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with non-authoritive key")
|
||||
}
|
||||
|
||||
t.Log("host cert")
|
||||
cert.CertType = HostCert
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with wrong type")
|
||||
}
|
||||
cert.CertType = UserCert
|
||||
|
||||
t.Log("principal specified")
|
||||
cert.ValidPrincipals = []string{"user"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("wrong principal specified")
|
||||
cert.ValidPrincipals = []string{"fred"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with wrong principal")
|
||||
}
|
||||
cert.ValidPrincipals = nil
|
||||
|
||||
t.Log("added critical option")
|
||||
cert.CriticalOptions = map[string]string{"root-access": "yes"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login passed with unrecognized critical option")
|
||||
}
|
||||
|
||||
t.Log("allowed source address")
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err != nil {
|
||||
t.Errorf("cert login with source-address failed: %v", err)
|
||||
}
|
||||
|
||||
t.Log("disallowed source address")
|
||||
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42"}
|
||||
cert.SignCert(rand.Reader, testSigners["ecdsa"])
|
||||
if err := tryAuth(t, clientConfig); err == nil {
|
||||
t.Errorf("cert login with source-address succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func testPermissionsPassing(withPermissions bool, t *testing.T) {
|
||||
serverConfig := &ServerConfig{
|
||||
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
|
||||
if conn.User() == "nopermissions" {
|
||||
return nil, nil
|
||||
} else {
|
||||
return &Permissions{}, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
serverConfig.AddHostKey(testSigners["rsa"])
|
||||
|
||||
clientConfig := &ClientConfig{
|
||||
Auth: []AuthMethod{
|
||||
PublicKeys(testSigners["rsa"]),
|
||||
},
|
||||
}
|
||||
if withPermissions {
|
||||
clientConfig.User = "permissions"
|
||||
} else {
|
||||
clientConfig.User = "nopermissions"
|
||||
}
|
||||
|
||||
c1, c2, err := netPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("netPipe: %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
go NewClientConn(c2, "", clientConfig)
|
||||
serverConn, err := newServer(c1, serverConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p := serverConn.Permissions; (p != nil) != withPermissions {
|
||||
t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionsPassing(t *testing.T) {
|
||||
testPermissionsPassing(true, t)
|
||||
}
|
||||
|
||||
func TestNoPermissionsPassing(t *testing.T) {
|
||||
testPermissionsPassing(false, t)
|
||||
}
|
||||
39
modules/crypto/ssh/client_test.go
Executable file
39
modules/crypto/ssh/client_test.go
Executable file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testClientVersion(t *testing.T, config *ClientConfig, expected string) {
|
||||
clientConn, serverConn := net.Pipe()
|
||||
defer clientConn.Close()
|
||||
receivedVersion := make(chan string, 1)
|
||||
go func() {
|
||||
version, err := readVersion(serverConn)
|
||||
if err != nil {
|
||||
receivedVersion <- ""
|
||||
} else {
|
||||
receivedVersion <- string(version)
|
||||
}
|
||||
serverConn.Close()
|
||||
}()
|
||||
NewClientConn(clientConn, "", config)
|
||||
actual := <-receivedVersion
|
||||
if actual != expected {
|
||||
t.Fatalf("got %s; want %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomClientVersion(t *testing.T) {
|
||||
version := "Test-Client-Version-0.0"
|
||||
testClientVersion(t, &ClientConfig{ClientVersion: version}, version)
|
||||
}
|
||||
|
||||
func TestDefaultClientVersion(t *testing.T) {
|
||||
testClientVersion(t, &ClientConfig{}, packageVersion)
|
||||
}
|
||||
354
modules/crypto/ssh/common.go
Executable file
354
modules/crypto/ssh/common.go
Executable file
@@ -0,0 +1,354 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
)
|
||||
|
||||
// These are string constants in the SSH protocol.
|
||||
const (
|
||||
compressionNone = "none"
|
||||
serviceUserAuth = "ssh-userauth"
|
||||
serviceSSH = "ssh-connection"
|
||||
)
|
||||
|
||||
// supportedCiphers specifies the supported ciphers in preference order.
|
||||
var supportedCiphers = []string{
|
||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||
"aes128-gcm@openssh.com",
|
||||
"arcfour256", "arcfour128",
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
||||
// preference order.
|
||||
var supportedKexAlgos = []string{
|
||||
kexAlgoCurve25519SHA256,
|
||||
// P384 and P521 are not constant-time yet, but since we don't
|
||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
||||
}
|
||||
|
||||
// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods
|
||||
// of authenticating servers) in preference order.
|
||||
var supportedHostKeyAlgos = []string{
|
||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
|
||||
CertAlgoECDSA384v01, CertAlgoECDSA521v01,
|
||||
|
||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
||||
KeyAlgoRSA, KeyAlgoDSA,
|
||||
}
|
||||
|
||||
// supportedMACs specifies a default set of MAC algorithms in preference order.
|
||||
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
|
||||
// because they have reached the end of their useful life.
|
||||
var supportedMACs = []string{
|
||||
"hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
|
||||
}
|
||||
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
||||
// hashFuncs keeps the mapping of supported algorithms to their respective
|
||||
// hashes needed for signature verification.
|
||||
var hashFuncs = map[string]crypto.Hash{
|
||||
KeyAlgoRSA: crypto.SHA1,
|
||||
KeyAlgoDSA: crypto.SHA1,
|
||||
KeyAlgoECDSA256: crypto.SHA256,
|
||||
KeyAlgoECDSA384: crypto.SHA384,
|
||||
KeyAlgoECDSA521: crypto.SHA512,
|
||||
CertAlgoRSAv01: crypto.SHA1,
|
||||
CertAlgoDSAv01: crypto.SHA1,
|
||||
CertAlgoECDSA256v01: crypto.SHA256,
|
||||
CertAlgoECDSA384v01: crypto.SHA384,
|
||||
CertAlgoECDSA521v01: crypto.SHA512,
|
||||
}
|
||||
|
||||
// unexpectedMessageError results when the SSH message that we received didn't
|
||||
// match what we wanted.
|
||||
func unexpectedMessageError(expected, got uint8) error {
|
||||
return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected)
|
||||
}
|
||||
|
||||
// parseError results from a malformed SSH message.
|
||||
func parseError(tag uint8) error {
|
||||
return fmt.Errorf("ssh: parse error in message type %d", tag)
|
||||
}
|
||||
|
||||
func findCommon(what string, client []string, server []string) (common string, err error) {
|
||||
for _, c := range client {
|
||||
for _, s := range server {
|
||||
if c == s {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
|
||||
}
|
||||
|
||||
type directionAlgorithms struct {
|
||||
Cipher string
|
||||
MAC string
|
||||
Compression string
|
||||
}
|
||||
|
||||
type algorithms struct {
|
||||
kex string
|
||||
hostKey string
|
||||
w directionAlgorithms
|
||||
r directionAlgorithms
|
||||
}
|
||||
|
||||
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
|
||||
result := &algorithms{}
|
||||
|
||||
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.hostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.w.Compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result.r.Compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// If rekeythreshold is too small, we can't make any progress sending
|
||||
// stuff.
|
||||
const minRekeyThreshold uint64 = 256
|
||||
|
||||
// Config contains configuration data common to both ServerConfig and
|
||||
// ClientConfig.
|
||||
type Config struct {
|
||||
// Rand provides the source of entropy for cryptographic
|
||||
// primitives. If Rand is nil, the cryptographic random reader
|
||||
// in package crypto/rand will be used.
|
||||
Rand io.Reader
|
||||
|
||||
// The maximum number of bytes sent or received after which a
|
||||
// new key is negotiated. It must be at least 256. If
|
||||
// unspecified, 1 gigabyte is used.
|
||||
RekeyThreshold uint64
|
||||
|
||||
// The allowed key exchanges algorithms. If unspecified then a
|
||||
// default set of algorithms is used.
|
||||
KeyExchanges []string
|
||||
|
||||
// The allowed cipher algorithms. If unspecified then a sensible
|
||||
// default is used.
|
||||
Ciphers []string
|
||||
|
||||
// The allowed MAC algorithms. If unspecified then a sensible default
|
||||
// is used.
|
||||
MACs []string
|
||||
}
|
||||
|
||||
// SetDefaults sets sensible values for unset fields in config. This is
|
||||
// exported for testing: Configs passed to SSH functions are copied and have
|
||||
// default values set automatically.
|
||||
func (c *Config) SetDefaults() {
|
||||
if c.Rand == nil {
|
||||
c.Rand = rand.Reader
|
||||
}
|
||||
if c.Ciphers == nil {
|
||||
c.Ciphers = supportedCiphers
|
||||
}
|
||||
var ciphers []string
|
||||
for _, c := range c.Ciphers {
|
||||
if cipherModes[c] != nil {
|
||||
// reject the cipher if we have no cipherModes definition
|
||||
ciphers = append(ciphers, c)
|
||||
}
|
||||
}
|
||||
c.Ciphers = ciphers
|
||||
|
||||
if c.KeyExchanges == nil {
|
||||
c.KeyExchanges = supportedKexAlgos
|
||||
}
|
||||
|
||||
if c.MACs == nil {
|
||||
c.MACs = supportedMACs
|
||||
}
|
||||
|
||||
if c.RekeyThreshold == 0 {
|
||||
// RFC 4253, section 9 suggests rekeying after 1G.
|
||||
c.RekeyThreshold = 1 << 30
|
||||
}
|
||||
if c.RekeyThreshold < minRekeyThreshold {
|
||||
c.RekeyThreshold = minRekeyThreshold
|
||||
}
|
||||
}
|
||||
|
||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
||||
// possession of a private key. See RFC 4252, section 7.
|
||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
||||
data := struct {
|
||||
Session []byte
|
||||
Type byte
|
||||
User string
|
||||
Service string
|
||||
Method string
|
||||
Sign bool
|
||||
Algo []byte
|
||||
PubKey []byte
|
||||
}{
|
||||
sessionId,
|
||||
msgUserAuthRequest,
|
||||
req.User,
|
||||
req.Service,
|
||||
req.Method,
|
||||
true,
|
||||
algo,
|
||||
pubKey,
|
||||
}
|
||||
return Marshal(data)
|
||||
}
|
||||
|
||||
func appendU16(buf []byte, n uint16) []byte {
|
||||
return append(buf, byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendU32(buf []byte, n uint32) []byte {
|
||||
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendU64(buf []byte, n uint64) []byte {
|
||||
return append(buf,
|
||||
byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
|
||||
byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func appendInt(buf []byte, n int) []byte {
|
||||
return appendU32(buf, uint32(n))
|
||||
}
|
||||
|
||||
func appendString(buf []byte, s string) []byte {
|
||||
buf = appendU32(buf, uint32(len(s)))
|
||||
buf = append(buf, s...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func appendBool(buf []byte, b bool) []byte {
|
||||
if b {
|
||||
return append(buf, 1)
|
||||
}
|
||||
return append(buf, 0)
|
||||
}
|
||||
|
||||
// newCond is a helper to hide the fact that there is no usable zero
|
||||
// value for sync.Cond.
|
||||
func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
|
||||
|
||||
// window represents the buffer available to clients
|
||||
// wishing to write to a channel.
|
||||
type window struct {
|
||||
*sync.Cond
|
||||
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
|
||||
writeWaiters int
|
||||
closed bool
|
||||
}
|
||||
|
||||
// add adds win to the amount of window available
|
||||
// for consumers.
|
||||
func (w *window) add(win uint32) bool {
|
||||
// a zero sized window adjust is a noop.
|
||||
if win == 0 {
|
||||
return true
|
||||
}
|
||||
w.L.Lock()
|
||||
if w.win+win < win {
|
||||
w.L.Unlock()
|
||||
return false
|
||||
}
|
||||
w.win += win
|
||||
// It is unusual that multiple goroutines would be attempting to reserve
|
||||
// window space, but not guaranteed. Use broadcast to notify all waiters
|
||||
// that additional window is available.
|
||||
w.Broadcast()
|
||||
w.L.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// close sets the window to closed, so all reservations fail
|
||||
// immediately.
|
||||
func (w *window) close() {
|
||||
w.L.Lock()
|
||||
w.closed = true
|
||||
w.Broadcast()
|
||||
w.L.Unlock()
|
||||
}
|
||||
|
||||
// reserve reserves win from the available window capacity.
|
||||
// If no capacity remains, reserve will block. reserve may
|
||||
// return less than requested.
|
||||
func (w *window) reserve(win uint32) (uint32, error) {
|
||||
var err error
|
||||
w.L.Lock()
|
||||
w.writeWaiters++
|
||||
w.Broadcast()
|
||||
for w.win == 0 && !w.closed {
|
||||
w.Wait()
|
||||
}
|
||||
w.writeWaiters--
|
||||
if w.win < win {
|
||||
win = w.win
|
||||
}
|
||||
w.win -= win
|
||||
if w.closed {
|
||||
err = io.EOF
|
||||
}
|
||||
w.L.Unlock()
|
||||
return win, err
|
||||
}
|
||||
|
||||
// waitWriterBlocked waits until some goroutine is blocked for further
|
||||
// writes. It is used in tests only.
|
||||
func (w *window) waitWriterBlocked() {
|
||||
w.Cond.L.Lock()
|
||||
for w.writeWaiters == 0 {
|
||||
w.Cond.Wait()
|
||||
}
|
||||
w.Cond.L.Unlock()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user