Merge branch 'master' into feat/mirror_webhook

This commit is contained in:
Lauris BH 2018-07-21 21:53:55 +03:00 committed by GitHub
commit 6b0598e935
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
674 changed files with 19014 additions and 171082 deletions

View File

@ -4,7 +4,7 @@ workspace:
clone:
git:
image: plugins/git:1
image: plugins/git:next
depth: 50
tags: true
@ -75,6 +75,7 @@ pipeline:
- make lint
- make fmt-check
- make swagger-check
# - make swagger-validate
- make misspell-check
- make test-vendor
- make build
@ -202,7 +203,7 @@ pipeline:
when:
event: [ push, tag ]
build_docs:
build-docs:
image: webhippie/hugo:latest
pull: true
commands:
@ -211,26 +212,12 @@ pipeline:
- make clean
- make build
docker_docs:
image: plugins/docker:17.05
publish-docs:
image: lucap/drone-netlify:latest
pull: true
secrets: [ docker_username, docker_password ]
repo: gitea/docs
context: docs
dockerfile: docs/Dockerfile
tags: [ '${DRONE_BRANCH##release/v}' ]
when:
event: [ push ]
branch: [ release/* ]
docker_docs:
image: plugins/docker:17.05
pull: true
secrets: [ docker_username, docker_password ]
repo: gitea/docs
context: docs
dockerfile: docs/Dockerfile
tags: [ 'latest' ]
secrets: [ netlify_token ]
site_id: d2260bae-7861-4c02-8646-8f6440b12672
path: docs/public/
when:
event: [ push ]
branch: [ master ]
@ -254,6 +241,18 @@ pipeline:
when:
event: [ push, tag ]
gpg-sign:
image: plugins/gpgsign:1
pull: true
secrets: [ gpgsign_key, gpgsign_passphrase ]
detach_sign: true
files:
- dist/release/*
excludes:
- dist/release/*.sha256
when:
event: [ push, tag ]
release:
image: plugins/s3:1
pull: true

51
BSDmakefile Normal file
View File

@ -0,0 +1,51 @@
# GNU makefile proxy script for BSD make
# Written and maintained by Mahmoud Al-Qudsi <mqudsi@neosmart.net>
# Copyright NeoSmart Technologies <https://neosmart.net/> 2014-2018
# Obtain updates from <https://github.com/neosmart/gmake-proxy>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
JARG =
GMAKE = "gmake"
#When gmake is called from another make instance, -w is automatically added
#which causes extraneous messages about directory changes to be emitted.
#--no-print-directory silences these messages.
GARGS = "--no-print-directory"
.if "$(.MAKE.JOBS)" != ""
JARG = -j$(.MAKE.JOBS)
.endif
#by default bmake will cd into ./obj first
.OBJDIR: ./
.PHONY: FRC
$(.TARGETS): FRC
$(GMAKE) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
.DONE .DEFAULT: .SILENT
$(GMAKE) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
.ERROR: .SILENT
if ! which $(GMAKE) > /dev/null; then \
echo "GNU Make is required!"; \
fi

View File

@ -4,6 +4,66 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.5.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.5.0-rc1) - 2018-07-04
* SECURITY
* Limit uploaded avatar image-size to 4096px x 3072px by default (#4353)
* Do not allow to reuse TOTP passcode (#3878)
* FEATURE
* Add cli commands to regen hooks & keys (#3979)
* Add support for FIDO U2F (#3971)
* Added user language setting (#3875)
* LDAP Public SSH Keys synchronization (#1844)
* Add topic support (#3711)
* Multiple assignees (#3705)
* Add protected branch whitelists for merging (#3689)
* Global code search support (#3664)
* Add label descriptions (#3662)
* Add issue search via API (#3612)
* Add repository setting to enable/disable health checks (#3607)
* Emoji Autocomplete (#3433)
* Implements generator cli for secrets (#3531)
* ENHANCEMENT
* Add more webhooks support and refactor webhook templates directory (#3929)
* Add new option to allow only OAuth2/OpenID user registration (#3910)
* Add option to use paged LDAP search when synchronizing users (#3895)
* Symlink icons (#1416)
* Improve release page UI (#3693)
* Add admin dashboard option to run health checks (#3606)
* Add branch link in branch list (#3576)
* Reduce sql query times in retrieveFeeds (#3547)
* Option to enable or disable swagger endpoints (#3502)
* Add missing licenses (#3497)
* Reduce repo indexer disk usage (#3452)
* Enable caching on assets and avatars (#3376)
* Add repository search ordered by stars/forks. Forks column in admin repo list (#3969)
* Add Environment Variables to Docker template (#4012)
* LFS: make HTTP auth period configurable (#4035)
* Add config path as an optionial flag when changing pass via CLI (#4184)
* Refactor User Settings sections (#3900)
* Allow square brackets in external issue patterns (#3408)
* Add Attachment API (#3478)
* Add EnableTimetracking option to app settings (#3719)
* Add config option to enable or disable log executed SQL (#3726)
* Shows total tracked time in issue and milestone list (#3341)
* TRANSLATION
* Improve English grammar and consistency (#3614)
* DEPLOYMENT
* Allow Gitea to run as different USER in Docker (#3961)
* Provide compressed release binaries (#3991)
* Sign release binaries (#4188)
## [1.4.3](https://github.com/go-gitea/gitea/releases/tag/v1.4.3) - 2018-06-26
* SECURITY
* HTML-escape plain-text READMEs (#4192) (#4214)
* Fix open redirect vulnerability on login screen (#4312) (#4312)
* BUGFIXES
* Fix broken monitoring page when running processes are shown (#4203) (#4208)
* Fix delete comment bug (#4216) (#4228)
* Delete reactions added to issues and comments when deleting repository (#4232) (#4237)
* Fix wiki URL encoding bug (#4091) (#4254)
* Fix code tab link when viewing tags (#3908) (#4263)
* Fix webhook type conflation (#4285) (#4285)
## [1.4.2](https://github.com/go-gitea/gitea/releases/tag/v1.4.2) - 2018-06-04
* BUGFIXES
* Adjust z-index for floating labels (#3939) (#3950)

View File

@ -58,3 +58,4 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
COPY docker /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea

186
Gopkg.lock generated
View File

@ -11,7 +11,7 @@
branch = "master"
name = "code.gitea.io/sdk"
packages = ["gitea"]
revision = "b2308e3f700875a3642a78bd3f6e5db8ef6f974d"
revision = "ec80752c9512cf07fc62ddc42565118183743942"
[[projects]]
name = "github.com/PuerkitoBio/goquery"
@ -143,17 +143,6 @@
packages = ["."]
revision = "098da33fde5f9220736531b3cb26a2dec86a8367"
[[projects]]
name = "github.com/coreos/etcd"
packages = ["error"]
revision = "01c303113d0a3d5a8075864321c3aedb72035bdd"
[[projects]]
branch = "master"
name = "github.com/coreos/go-etcd"
packages = ["etcd"]
revision = "003851be7bb0694fe3cc457a49529a19388ee7cf"
[[projects]]
branch = "master"
name = "github.com/couchbase/vellum"
@ -294,27 +283,24 @@
[[projects]]
name = "github.com/go-sql-driver/mysql"
packages = ["."]
revision = "ce924a41eea897745442daaa1739089b0f3f561d"
revision = "d523deb1b23d913de5bdada721a6071e71283618"
[[projects]]
name = "github.com/go-xorm/builder"
packages = ["."]
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738"
revision = "dc8bf48f58fab2b4da338ffd25191905fd741b8f"
version = "v0.3.0"
[[projects]]
name = "github.com/go-xorm/core"
packages = ["."]
revision = "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9"
[[projects]]
name = "github.com/go-xorm/tidb"
packages = ["."]
revision = "21e49190ce47a766fa741cf7edc831a30c12c6ac"
revision = "c10e21e7e1cec20e09398f2dfae385e58c8df555"
version = "v0.6.0"
[[projects]]
name = "github.com/go-xorm/xorm"
packages = ["."]
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[projects]]
branch = "master"
@ -370,11 +356,6 @@
packages = ["."]
revision = "8fb95d837f7d6db1913fecfd7bcc5333e6499596"
[[projects]]
name = "github.com/juju/errors"
packages = ["."]
revision = "b2c7a7da5b2995941048f60146e67702a292e468"
[[projects]]
name = "github.com/kballard/go-shellquote"
packages = ["."]
@ -497,134 +478,12 @@
packages = ["."]
revision = "891127d8d1b52734debe1b3c3d7e747502b6c366"
[[projects]]
name = "github.com/ngaut/deadline"
packages = ["."]
revision = "fae8f9dfd7048de16575b9d4c255278e38c28a4f"
[[projects]]
branch = "master"
name = "github.com/ngaut/go-zookeeper"
packages = ["zk"]
revision = "9c3719e318c7cfd072e41eb48cb71fcaa49d5e05"
[[projects]]
name = "github.com/ngaut/log"
packages = ["."]
revision = "d2af3a61f64d093457fb23b25d20f4ce3cd551ce"
[[projects]]
branch = "master"
name = "github.com/ngaut/pools"
packages = ["."]
revision = "b7bc8c42aac787667ba45adea78233f53f548443"
[[projects]]
branch = "master"
name = "github.com/ngaut/sync2"
packages = ["."]
revision = "7a24ed77b2efb460c1468b7dc917821c66e80e55"
[[projects]]
branch = "master"
name = "github.com/ngaut/tso"
packages = [
"client",
"proto",
"util"
]
revision = "118f6c141d58f1e72577ff61f43f649bf39355ee"
[[projects]]
branch = "master"
name = "github.com/ngaut/zkhelper"
packages = ["."]
revision = "6738bdc138d469112c6687fbfcfe049ccabd6a0a"
[[projects]]
branch = "master"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
name = "github.com/philhofer/fwd"
packages = ["."]
revision = "bb6d471dc95d4fe11e432687f8b70ff496cf3136"
version = "v1.0.0"
[[projects]]
name = "github.com/pingcap/go-hbase"
packages = [
".",
"iohelper",
"proto"
]
revision = "7a98d1fe4e9e115de8c77ae0e158c0d08732c550"
[[projects]]
branch = "master"
name = "github.com/pingcap/go-themis"
packages = [
".",
"oracle",
"oracle/oracles"
]
revision = "dbb996606c1d1fe8571fd9ac6da2254c76d2c5c9"
[[projects]]
name = "github.com/pingcap/tidb"
packages = [
".",
"ast",
"column",
"context",
"ddl",
"domain",
"evaluator",
"executor",
"infoschema",
"inspectkv",
"kv",
"kv/memkv",
"meta",
"meta/autoid",
"model",
"mysql",
"optimizer",
"optimizer/plan",
"parser",
"parser/opcode",
"perfschema",
"privilege",
"privilege/privileges",
"sessionctx",
"sessionctx/autocommit",
"sessionctx/db",
"sessionctx/forupdate",
"sessionctx/variable",
"store/hbase",
"store/localstore",
"store/localstore/boltdb",
"store/localstore/engine",
"store/localstore/goleveldb",
"structure",
"table",
"table/tables",
"terror",
"util",
"util/bytes",
"util/charset",
"util/codec",
"util/distinct",
"util/hack",
"util/segmentmap",
"util/sqlexec",
"util/stringutil",
"util/types"
]
revision = "33197485abe227dcb254644cf5081c9a3c281669"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
@ -673,24 +532,6 @@
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
name = "github.com/syndtr/goleveldb"
packages = [
"leveldb",
"leveldb/cache",
"leveldb/comparer",
"leveldb/errors",
"leveldb/filter",
"leveldb/iterator",
"leveldb/journal",
"leveldb/memdb",
"leveldb/opt",
"leveldb/storage",
"leveldb/table",
"leveldb/util"
]
revision = "917f41c560270110ceb73c5b38be2a9127387071"
[[projects]]
branch = "master"
name = "github.com/tinylib/msgp"
@ -703,17 +544,6 @@
packages = ["."]
revision = "d21a03e0b1d9fc1df59ff54e7a513655c1748b0c"
[[projects]]
name = "github.com/twinj/uuid"
packages = ["."]
revision = "89173bcdda19db0eb88aef1e1cb1cb2505561d31"
version = "0.10.0"
[[projects]]
name = "github.com/ugorji/go"
packages = ["codec"]
revision = "c062049c1793b01a3cc3fe786108edabbaf7756b"
[[projects]]
name = "github.com/urfave/cli"
packages = ["."]
@ -873,6 +703,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "036b8c882671cf8d2c5e2fdbe53b1bdfbd39f7ebd7765bd50276c7c4ecf16687"
inputs-digest = "5ae18d543bbb8186589c003422b333097d67bb5fed8b4c294be70c012ccffc94"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -30,15 +30,14 @@ ignored = ["google.golang.org/appengine*"]
revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
name = "golang.org/x/net"
[[constraint]]
#version = "v1.0.0"
revision = "33197485abe227dcb254644cf5081c9a3c281669"
name = "github.com/pingcap/tidb"
[[override]]
name = "github.com/go-xorm/xorm"
#version = "0.6.5"
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[override]]
name = "github.com/go-sql-driver/mysql"
revision = "d523deb1b23d913de5bdada721a6071e71283618"
[[override]]
name = "github.com/gorilla/mux"

View File

@ -23,3 +23,4 @@ Matti Ranta <matti@mdranta.net> (@techknowlogick)
Michael Lustfield <mtecknology@debian.org> (@MTecknology)
Jonas Franz <info@jonasfranz.software> (@JonasFranzDEV)
Flynn Lufmons <fluf@warpmail.net> (@flufmonster)
Alexey Terentyev <axifnx@gmail.com> (@axifive)

View File

@ -21,7 +21,19 @@ GOFMT ?= gofmt -s
GOFLAGS := -i -v
EXTRA_GOFLAGS ?=
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG))
GITEA_VERSION := $(VERSION)
else
ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
else
VERSION ?= master
endif
GITEA_VERSION := $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
endif
LDFLAGS := -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list ./... | grep -v /vendor/))
SOURCES ?= $(shell find . -name "*.go" -type f)
@ -45,15 +57,8 @@ else
EXECUTABLE := gitea
endif
ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG))
else
ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
else
VERSION ?= master
endif
endif
# $(call strip-suffix,filename)
strip-suffix = $(firstword $(subst ., ,$(1)))
.PHONY: all
all: build
@ -301,7 +306,7 @@ public/js/index.js: $(JAVASCRIPTS)
.PHONY: stylesheets-check
stylesheets-check: generate-stylesheets
@diff=$$(git diff public/css/index.css); \
@diff=$$(git diff public/css/*); \
if [ -n "$$diff" ]; then \
echo "Please run 'make generate-stylesheets' and commit the result:"; \
echo "$${diff}"; \
@ -311,6 +316,7 @@ stylesheets-check: generate-stylesheets
.PHONY: generate-stylesheets
generate-stylesheets:
node_modules/.bin/lessc --clean-css public/less/index.less public/css/index.css
$(foreach file, $(filter-out public/less/themes/_base.less, $(wildcard public/less/themes/*)),node_modules/.bin/lessc --clean-css public/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;)
.PHONY: swagger-ui
swagger-ui:

View File

@ -90,6 +90,10 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
Gitea is pronounced [/ɡɪti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
**Why is this not hosted on a Gitea instance?**
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
## License
This project is licensed under the MIT License.

View File

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type="manifest" name="export">
<service name="gitea" type="service" version="1">
<create_default_instance enabled="false"/>
<dependency name="network" grouping="require_all" restart_on="refresh" type="service">
<service_fmri value="svc:/milestone/network:default"/>
</dependency>
<dependency name="filesystem" grouping="require_all" restart_on="refresh" type="service">
<service_fmri value="svc:/system/filesystem/local"/>
</dependency>
<exec_method
type="method"
name="start"
exec="/opt/local/bin/gitea web"
timeout_seconds="60">
<method_context>
<method_credential user="git" group="git" />
<method_environment>
<envvar name='GITEA_WORK_DIR' value='/opt/local/share/gitea'/>
<envvar name='GITEA_CUSTOM' value='/opt/local/etc/gitea'/>
<envvar name='HOME' value='/var/db/gitea'/>
<envvar name='PATH' value='/opt/local/bin:${PATH}'/>
<envvar name='USER' value='git'/>
</method_environment>
</method_context>
</exec_method>
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
<property_group name="application" type="application"></property_group>
<property_group name="startd" type="framework">
<propval name="duration" type="astring" value="child"/>
<propval name="ignore_error" type="astring" value="core,signal"/>
</property_group>
<template>
<common_name>
<loctext xml:lang="C">A painless, self-hosted Git service</loctext>
</common_name>
</template>
</service>
</service_bundle>

View File

@ -75,6 +75,8 @@ THEME_COLOR_META_TAG = `#6cc644`
MAX_DISPLAY_FILE_SIZE = 8388608
; Whether the email of the user should be shown in the Explore Users page
SHOW_USER_EMAIL = true
; Set the default theme for the Gitea install
DEFAULT_THEME = gitea
[ui.admin]
; Number of users that are displayed on one page
@ -301,13 +303,22 @@ ENABLE_NOTIFY_MAIL = false
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; Enable captcha validation for registration
ENABLE_CAPTCHA = true
ENABLE_CAPTCHA = false
; Type of captcha you want to use. Options: image, recaptcha
CAPTCHA_TYPE = image
; Enable recaptcha to use Google's recaptcha service
; Go to https://www.google.com/recaptcha/admin to sign up for a key
RECAPTCHA_SECRET =
RECAPTCHA_SITEKEY =
; Default value for KeepEmailPrivate
; Each new user will get the value of this setting copied into their profile
DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
; Default value for EnableDependencies
; Repositories will use depencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
; Enable Timetracking
ENABLE_TIMETRACKING = true
; Default value for EnableTimetracking
@ -402,6 +413,10 @@ SESSION_LIFE_TIME = 86400
[picture]
AVATAR_UPLOAD_PATH = data/avatars
; Max Width and Height of uploaded avatars. This is to limit the amount of RAM
; used when resizing the image.
AVATAR_MAX_WIDTH = 4096
AVATAR_MAX_HEIGHT = 3072
; Chinese users can choose "duoshuo"
; or a custom avatar source, like: http://cn.gravatar.com/avatar/
GRAVATAR_SOURCE = gravatar
@ -601,9 +616,9 @@ ko-KR = ko
[U2F]
; Two Factor authentication with security keys
; https://developers.yubico.com/U2F/App_ID.html
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
; Comma seperated list of truisted facets
TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
; Extension mapping to highlight class
; e.g. .toml=ini

View File

@ -1,7 +1,5 @@
#!/bin/bash
/usr/sbin/update-ca-certificates
if [ ! -d /data/git/.ssh ]; then
mkdir -p /data/git/.ssh
chmod 700 /data/git/.ssh

View File

@ -4,6 +4,9 @@ RUN_MODE = $RUN_MODE
[repository]
ROOT = /data/git/repositories
[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
[repository.upload]
TEMP_PATH = /data/gitea/uploads
@ -14,6 +17,7 @@ HTTP_PORT = $HTTP_PORT
ROOT_URL = $ROOT_URL
DISABLE_SSH = $DISABLE_SSH
SSH_PORT = $SSH_PORT
LFS_CONTENT_PATH = /data/git/lfs
[database]
PATH = /data/gitea/gitea.db
@ -23,6 +27,9 @@ NAME = $DB_NAME
USER = $DB_USER
PASSWD = $DB_PASSWD
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER_CONFIG = /data/gitea/sessions

View File

@ -1,22 +0,0 @@
# build stage
FROM golang:alpine AS build-env
RUN apk add --no-cache git
RUN go get -d -v github.com/mholt/caddy/caddy github.com/pedronasser/caddy-search github.com/simia-tech/caddy-locale
WORKDIR /go/src/github.com/mholt/caddy/caddy
RUN sed -i '/This is where other plugins get plugged in (imported)/a _ "github.com/pedronasser/caddy-search"' caddymain/run.go \
&& sed -i '/This is where other plugins get plugged in (imported)/a _ "github.com/simia-tech/caddy-locale"' caddymain/run.go \
&& go install -v . \
&& /go/bin/caddy -version
FROM alpine:edge
EXPOSE 80
RUN apk add --no-cache wget mailcap ca-certificates
COPY --from=build-env /go/bin/caddy /usr/sbin/caddy
COPY docker/caddy.conf /etc/caddy.conf
COPY public /srv/www
CMD ["/usr/sbin/caddy", "-conf", "/etc/caddy.conf"]

View File

@ -31,7 +31,7 @@ menu:
post: active
- name: API
url: https://try.gitea.io/api/swagger
weight: 25
weight: 45
pre: plug
- name: Blog
url: https://blog.gitea.io/
@ -79,7 +79,7 @@ languages:
post: active
- name: API
url: https://try.gitea.io/api/swagger
weight: 25
weight: 45
pre: plug
- name: 博客
url: https://blog.gitea.io/
@ -122,7 +122,7 @@ languages:
post: active
- name: API
url: https://try.gitea.io/api/swagger
weight: 25
weight: 45
pre: plug
- name: 部落格
url: https://blog.gitea.io/
@ -165,7 +165,7 @@ languages:
post: active
- name: API
url: https://try.gitea.io/api/swagger
weight: 25
weight: 45
pre: plug
- name: Blog
url: https://blog.gitea.io/
@ -208,7 +208,7 @@ languages:
post: active
- name: API
url: https://try.gitea.io/api/swagger
weight: 25
weight: 45
pre: plug
- name: Blog
url: https://blog.gitea.io/
@ -241,17 +241,17 @@ languages:
menu:
page:
- name: Site
url: /fr-fr/
url: https://gitea.io/en-us/
weight: 10
pre: home
post: active
- name: Documentation
url: https://docs.gitea.io/fr-fr/
url: /fr-fr/
weight: 20
pre: question
- name: API
url: https://try.gitea.io/api/swagger
weight: 25
weight: 45
pre: plug
- name: Blog
url: https://blog.gitea.io/

View File

@ -0,0 +1,75 @@
---
date: "2018-06-24:00:00+02:00"
title: "API Usage"
slug: "api-usage"
weight: 40
toc: true
draft: false
menu:
sidebar:
parent: "advanced"
name: "API Usage"
weight: 40
identifier: "api-usage"
---
# Gitea API Usage
## Enabling/configuring API access
By default, `ENABLE_SWAGGER_ENDPOINT` is true, and
`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat
Sheet](https://docs.gitea.io/en-us/config-cheat-sheet/) for more
information.
## Authentication via the API
Gitea supports these methods of API authentication:
- HTTP basic authentication
- `token=...` parameter in URL query string
- `access_token=...` parameter in URL query string
- `Authorization: token ...` header in HTTP headers
All of these methods accept the same apiKey token type. You can
better understand this by looking at the code -- as of this writing,
Gitea parses queries and headers to find the token in
[modules/auth/auth.go](https://github.com/go-gitea/gitea/blob/6efdcaed86565c91a3dc77631372a9cc45a58e89/modules/auth/auth.go#L47).
You can create an apiKey token via your gitea install's web interface:
`Settings | Applications | Generate New Token`.
### More on the `Authorization:` header
For historical reasons, Gitea needs the word `token` included before
the apiKey token in an authorization header, like this:
```
Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675
```
In a `curl` command, for instance, this would look like:
```
curl -X POST "http://localhost:4000/api/v1/repos/test1/test1/issues" \
-H "accept: application/json" \
-H "Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675" \
-H "Content-Type: application/json" -d "{ \"body\": \"testing\", \"title\": \"test 20\"}" -i
```
As mentioned above, the token used is the same one you would use in
the `token=` string in a GET request.
## Listing your issued tokens via the API
As mentioned in
[#3842](https://github.com/go-gitea/gitea/issues/3842#issuecomment-397743346),
`/users/:name/tokens` is special and requires you to authenticate
using BasicAuth, as follows:
### Using basic authentication:
```
$ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens
[{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}]
```

View File

@ -68,6 +68,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page.
- `ISSUE_PAGING_NUM`: **10**: Number of issues that are shown in one page (for all pages that list issues).
- `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed.
- `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install.
### UI - Admin (`ui.admin`)
@ -177,7 +178,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication.
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration
for reverse authentication.
- `ENABLE_CAPTCHA`: **true**: Enable this to use captcha validation for registration.
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha
- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
## Webhook (`webhook`)
@ -279,6 +284,13 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`.
## Git - Timeout settings (`git.timeout`)
- `MIGRATE`: **600**: Migrate external repositories timeout seconds.
- `MIRROR`: **300**: Mirror external repositories timeout seconds.
- `CLONE`: **300**: Git clone from internal repositories timeout seconds.
- `PULL`: **300**: Git pull from internal repositories timeout seconds.
- `GC`: **60**: Git repository GC timeout seconds.
## API (`api`)
- `ENABLE_SWAGGER_ENDPOINT`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.

View File

@ -187,6 +187,13 @@ menu:
- `MAX_GIT_DIFF_FILES`: 比较视图中的最大现实文件数目。
- `GC_ARGS`: 执行 `git gc` 命令的参数, 比如: `--aggressive --auto`
## Git - 超时设置 (`git.timeout`)
- `MIGRATE`: **600**: 迁移外部仓库时的超时时间,单位秒
- `MIRROR`: **300**: 镜像外部仓库的超时时间,单位秒
- `CLONE`: **300**: 内部仓库间克隆的超时时间,单位秒
- `PULL`: **300**: 内部仓库间拉取的超时时间,单位秒
- `GC`: **60**: git仓库GC的超时时间单位秒
## markup (`markup`)
外部渲染工具支持,你可以用你熟悉的文档渲染工具. 比如一下将新增一个名字为 `asciidoc` 的渲染工具which is followed `markup.` ini section. And there are some config items below.

View File

@ -91,3 +91,7 @@ Apart from `extra_links.tmpl` and `extra_tabs.tmpl`, there are other useful temp
## Customizing gitignores, labels, licenses, locales, and readmes.
Place custom files in corresponding sub-folder under `custom/options`.
## Customizing the look of Gitea
Gitea has two built-in themes, the default theme `gitea`, and a dark theme `arc-green`. To change the look of your Gitea install change the value of `DEFAULT_THEME` in the [ui](https://docs.gitea.io/en-us/config-cheat-sheet/#ui-ui) section of `app.ini` to another one of the available options.

View File

@ -27,659 +27,93 @@ _Symbols used in table:_
* _✘ - unsupported_
<table border="1" cellpadding="4">
<thead>
<tr>
<td>Feature</td>
<td>Gitea</td>
<td>Gogs</td>
<td>GitHub EE</td>
<td>GitLab CE</td>
<td>GitLab EE</td>
<td>BitBucket</td>
<td>RhodeCode CE</td>
</tr>
</thead>
<tbody>
<tr>
<td>Open source and free</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Issue tracker</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pull/Merge requests</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Squash merging</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Rebase merging</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pull/Merge request inline comments</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pull/Merge request approval</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Merge conflict resolution</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Restrict push and merge access to certain users</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Markdown support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Issues and pull/merge requests templates</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Revert specific commits or a merge request</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Labels</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Time tracking</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Multiple assignees for issues</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Related issues</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Confidential issues</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Comment reactions</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Lock Discussion</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Batch issue handling</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Issue Boards</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Create new branches from issues</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Commit graph</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Web code editor</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Branch manager</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Create new branches</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Repository topics</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Repository code search</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Global code search</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Issue search</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Global issue search</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Git LFS 2.0</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Integrated Git-powered wiki</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Static Git-powered pages</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Group Milestones</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Granular user roles (Code, Issues, Wiki etc)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Cherry-picking changes</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>GPG Signed Commits</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Reject unsigned commits</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Verified Committer</td>
<td></td>
<td></td>
<td>?</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Subgroups: groups within groups</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Custom Git Hooks</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Repository Activity page</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Deploy Tokens</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Repository Tokens with write rights</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Easy upgrade process</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Built-in Container Registry</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>External git mirroring</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>AD / LDAP integration</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Multiple LDAP / AD server support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>LDAP user synchronization</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>OpenId Connect support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>?</td>
<td></td>
</tr>
<tr>
<td>OAuth 2.0 integration (external authorization)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>?</td>
<td></td>
</tr>
<tr>
<td>Act as OAuth 2.0 provider</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Two factor authentication (2FA)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>FIDO U2F (2FA)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Webhook support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Mattermost/Slack integration</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Discord integration</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Built-in CI/CD</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>External CI/CD status display</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Multiple database support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Multiple OS support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Low resource usage (RAM/CPU)</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
#### General Features
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
| Open source and free | ✓ | ✓ | ✘| ✓ | ✘ | ✘ | ✓ |
| Low resource usage (RAM/CPU) | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ |
| Multiple database support | ✓ | ✓ | ✘ | | | ✓ | ✓ |
| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ |
| Easy upgrade process | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Static Git-powered pages | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
| Built-in Container Registry | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Built-in CI/CD | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Subgroups: groups within groups | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ |
#### Code management
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Global code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | | ✓ |
| Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Verified Committer | ✘ | ✘ | ? | ✓ | ✓ | ✓ | ✘ |
| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Reject unsigned commits | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
#### Issue Tracker
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Multiple assignees for issues | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Related issues | ✘ | ✘ | | ✘ | ✓ | ✘ | ✘ |
| Confidential issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Lock Discussion | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Issue Boards | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Global issue search | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
#### Pull/Merge requests
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Squash merging | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ |
| Rebase merging | ✓ | ✓ | ✓ | ✘ | | ✘ | ✓ |
| Pull/Merge request inline comments | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull/Merge request approval | ✘ | ✘ | | ✓ | ✓ | ✓ | ✓ |
| Merge conflict resolution | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ |
| Revert specific commits or a merge request | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Cherry-picking changes | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
#### 3rd-party integrations
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
| Webhook support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Custom Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
| LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| OpenId Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ |
| OAuth 2.0 integration (external authorization) | ✓ | ✘ | | ✓ | ✓ | ? | ✓ |
| Act as OAuth 2.0 provider | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Two factor authentication (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Mattermost/Slack integration | ✓ | ✓ | | ✓ | ✓ | | ✓ |
| Discord integration | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ |
| External CI/CD status display | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |

View File

@ -21,10 +21,18 @@ the destination platform from the [downloads page](https://dl.gitea.io/gitea), c
the URL and replace the URL within the commands below:
```sh
wget -O gitea https://dl.gitea.io/gitea/1.4.2/gitea-1.4.2-linux-amd64
wget -O gitea https://dl.gitea.io/gitea/1.4.3/gitea-1.4.3-linux-amd64
chmod +x gitea
```
## Verify GPG signature
Gitea signs all binaries with a [GPG key](https://pgp.mit.edu/pks/lookup?op=vindex&fingerprint=on&search=0x2D9AE806EC1592E2) to prevent against unwanted modification of binaries. To validate the binary download the signature file which ends in `.asc` for the binary you downloaded and use the gpg command line tool.
```sh
gpg --keyserver pgp.mit.edu --recv 0x2D9AE806EC1592E2
gpg --verify gitea-1.5.0-linux-amd64.asc gitea-1.5.0-linux-amd64
```
## Test
After getting a binary, it can be tested with `./gitea web` or moved to a permanent

View File

@ -245,6 +245,8 @@ You can configure some of Gitea's settings via environment variables:
* `SECRET_KEY`: **""**: Global secret key. This should be changed. If this has a value and `INSTALL_LOCK` is empty, `INSTALL_LOCK` will automatically set to `true`.
* `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users.
* `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
* `USER_UID`: **1000**: The UID (Unix user ID) of the user that runs Gitea within the container. Match this to the UID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
* `USER_GID`: **1000**: The GID (Unix group ID) of the user that runs Gitea within the container. Match this to the GID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
# Customization

View File

@ -1,44 +0,0 @@
:80 {
root /srv/www
locale en-US zh-CN zh-TW pt-BR nl-NL fr-FR {
detect header
}
redir 301 {
if {path} match ^/$
/ /{>Detected-Locale}/
}
rewrite /en-US/ {
regexp (.*)
to /en-us/{1}
}
rewrite /zh-CN/ {
regexp (.*)
to /zh-cn/{1}
}
rewrite /zh-TW/ {
regexp (.*)
to /zh-tw/{1}
}
rewrite /pt-BR/ {
regexp (.*)
to /pt-br/{1}
}
rewrite /nl-NL/ {
regexp (.*)
to /nl-nl/{1}
}
rewrite /fr-FR/ {
regexp (.*)
to /fr-fr/{1}
}
header / Vary "Accept-Language"
}

View File

6
docs/static/_headers vendored Normal file
View File

@ -0,0 +1,6 @@
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' data: https://cdnjs.cloudflare.com https://fonts.gstatic.com
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

8
docs/static/_redirects vendored Normal file
View File

@ -0,0 +1,8 @@
https://gitea-docs.netlify.com/* https://docs.gitea.io/:splat 302!
/ /fr-fr/ 302! Language=fr
/ /nl-nl/ 302! Language=nl
/ /pt-br/ 302! Language=pt-br
/ /zh-cn/ 302! Language=zh-cn
/ /zh-tw/ 302! Language=zh-tw
/ /en-us/ 302!

View File

@ -13,7 +13,7 @@ Make sure to perform a clean build before running tests:
## Run all tests via local drone
```
drone exec --local --build.event "pull_request"
drone exec --local --build-event "pull_request"
```
## Run sqlite integrations tests

View File

@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
expectedResults
}{
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
nil: {count: 15},
user: {count: 15},
user2: {count: 15}},
nil: {count: 16},
user: {count: 16},
user2: {count: 16}},
},
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
nil: {count: 10},
@ -235,3 +235,53 @@ func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/repositories/2")
sess.MakeRequest(t, req, http.StatusNotFound)
}
func TestAPIRepoMigrate(t *testing.T) {
testCases := []struct {
ctxUserID, userID int64
cloneURL, repoName string
expectedStatus int
}{
{ctxUserID: 1, userID: 2, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-admin", expectedStatus: http.StatusCreated},
{ctxUserID: 2, userID: 2, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-own", expectedStatus: http.StatusCreated},
{ctxUserID: 2, userID: 1, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-bad", expectedStatus: http.StatusForbidden},
{ctxUserID: 2, userID: 3, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-org", expectedStatus: http.StatusCreated},
{ctxUserID: 2, userID: 6, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-bad-org", expectedStatus: http.StatusForbidden},
}
prepareTestEnv(t)
for _, testCase := range testCases {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User)
session := loginUser(t, user.Name)
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &api.MigrateRepoOption{
CloneAddr: testCase.cloneURL,
UID: int(testCase.userID),
RepoName: testCase.repoName,
})
session.MakeRequest(t, req, testCase.expectedStatus)
}
}
func TestAPIOrgRepoCreate(t *testing.T) {
testCases := []struct {
ctxUserID int64
orgName, repoName string
expectedStatus int
}{
{ctxUserID: 1, orgName: "user3", repoName: "repo-admin", expectedStatus: http.StatusCreated},
{ctxUserID: 2, orgName: "user3", repoName: "repo-own", expectedStatus: http.StatusCreated},
{ctxUserID: 2, orgName: "user6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden},
}
prepareTestEnv(t)
for _, testCase := range testCases {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User)
session := loginUser(t, user.Name)
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{
Name: testCase.repoName,
})
session.MakeRequest(t, req, testCase.expectedStatus)
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2018 The Gitea 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 integrations
import (
"net/http"
"testing"
"code.gitea.io/gitea/models"
api "code.gitea.io/sdk/gitea"
)
// TestAPICreateAndDeleteToken tests that token that was just created can be deleted
func TestAPICreateAndDeleteToken(t *testing.T) {
prepareTestEnv(t)
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{
"name": "test-key-1",
})
req = AddBasicAuthHeader(req, user.Name)
resp := MakeRequest(t, req, http.StatusCreated)
var newAccessToken api.AccessToken
DecodeJSON(t, resp, &newAccessToken)
models.AssertExistsAndLoadBean(t, &models.AccessToken{
ID: newAccessToken.ID,
Name: newAccessToken.Name,
Sha1: newAccessToken.Sha1,
UID: user.ID,
})
req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID)
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)
models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID})
}
// TestAPIDeleteMissingToken ensures that error is thrown when token not found
func TestAPIDeleteMissingToken(t *testing.T) {
prepareTestEnv(t)
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", models.NonexistentID)
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNotFound)
}

View File

@ -256,6 +256,11 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *ht
return request
}
func AddBasicAuthHeader(request *http.Request, username string) *http.Request {
request.SetBasicAuth(username, userPassword)
return request
}
const NoExpectedStatus = -1
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {

View File

@ -68,3 +68,25 @@ func TestSettingShowUserEmailProfile(t *testing.T) {
setting.UI.ShowUserEmail = showUserEmail
}
func TestSettingLandingPage(t *testing.T) {
prepareTestEnv(t)
landingPage := setting.LandingPageURL
setting.LandingPageURL = setting.LandingPageHome
req := NewRequest(t, "GET", "/")
MakeRequest(t, req, http.StatusOK)
setting.LandingPageURL = setting.LandingPageExplore
req = NewRequest(t, "GET", "/")
resp := MakeRequest(t, req, http.StatusFound)
assert.Equal(t, "/explore", resp.Header().Get("Location"))
setting.LandingPageURL = setting.LandingPageOrganizations
req = NewRequest(t, "GET", "/")
resp = MakeRequest(t, req, http.StatusFound)
assert.Equal(t, "/explore/organizations", resp.Header().Get("Location"))
setting.LandingPageURL = landingPage
}

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
// register supported doc types
_ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode"

View File

@ -22,8 +22,12 @@ func TestAccessLevel(t *testing.T) {
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
// A public repository owned by User 2
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
assert.False(t, repo1.IsPrivate)
// A private repository owned by Org 3
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.True(t, repo2.IsPrivate)
level, err := AccessLevel(user1.ID, repo1)
assert.NoError(t, err)
@ -47,8 +51,12 @@ func TestHasAccess(t *testing.T) {
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
// A public repository owned by User 2
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
assert.False(t, repo1.IsPrivate)
// A private repository owned by Org 3
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.True(t, repo2.IsPrivate)
for _, accessMode := range accessModes {
has, err := HasAccess(user1.ID, repo1, accessMode)

View File

@ -125,6 +125,12 @@ func (a *Action) loadRepo() {
}
}
// GetActFullName gets the action's user full name.
func (a *Action) GetActFullName() string {
a.loadActUser()
return a.ActUser.FullName
}
// GetActUserName gets the action's user name.
func (a *Action) GetActUserName() string {
a.loadActUser()
@ -474,6 +480,10 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
}
if err = issue.ChangeStatus(doer, repo, true); err != nil {
// Don't return an error when dependencies are open as this would let the push fail
if IsErrDependenciesLeft(err) {
return nil
}
return err
}
}

View File

@ -1259,3 +1259,88 @@ func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
// | |\___ \ \___ \| | /\ ___/ | ` \ ___/| |_> > ___/| | \/ /_/ \ ___/| | \ \___| \ ___/ \___ \
// |___/____ >____ >____/ \___ >_______ /\___ > __/ \___ >___| /\____ |\___ >___| /\___ >__|\___ >____ >
// \/ \/ \/ \/ \/|__| \/ \/ \/ \/ \/ \/ \/ \/
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyExists checks if an error is a ErrDependencyExists.
func IsErrDependencyExists(err error) bool {
_, ok := err.(ErrDependencyExists)
return ok
}
func (err ErrDependencyExists) Error() string {
return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
func IsErrDependencyNotExists(err error) bool {
_, ok := err.(ErrDependencyNotExists)
return ok
}
func (err ErrDependencyNotExists) Error() string {
return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct {
IssueID int64
DependencyID int64
}
// IsErrCircularDependency checks if an error is a ErrCircularDependency.
func IsErrCircularDependency(err error) bool {
_, ok := err.(ErrCircularDependency)
return ok
}
func (err ErrCircularDependency) Error() string {
return fmt.Sprintf("circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
type ErrDependenciesLeft struct {
IssueID int64
}
// IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
func IsErrDependenciesLeft(err error) bool {
_, ok := err.(ErrDependenciesLeft)
return ok
}
func (err ErrDependenciesLeft) Error() string {
return fmt.Sprintf("issue has open dependencies [issue id: %d]", err.IssueID)
}
// ErrUnknownDependencyType represents an error where an unknown dependency type was passed
type ErrUnknownDependencyType struct {
Type DependencyType
}
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
func IsErrUnknownDependencyType(err error) bool {
_, ok := err.(ErrUnknownDependencyType)
return ok
}
func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
}

View File

@ -351,7 +351,7 @@
is_mirror: true
num_forks: 1
is_fork: false
-
id: 29
fork_id: 27
@ -365,7 +365,7 @@
num_closed_pulls: 0
is_mirror: false
is_fork: true
-
id: 30
fork_id: 28
@ -389,3 +389,14 @@
num_forks: 0
num_issues: 0
is_mirror: false
-
id: 32
owner_id: 3
lower_name: repo21
name: repo21
is_private: false
num_stars: 0
num_forks: 0
num_issues: 0
is_mirror: false

View File

@ -4,9 +4,8 @@
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 2
num_repos: 3
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'
-
id: 2
@ -16,7 +15,6 @@
authorize: 2 # write
num_repos: 1
num_members: 2
unit_types: '[1,2,3,4,5,6,7]'
-
id: 3
@ -26,7 +24,6 @@
authorize: 4 # owner
num_repos: 0
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'
-
id: 4
@ -36,7 +33,6 @@
authorize: 4 # owner
num_repos: 0
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'
-
id: 5
@ -46,7 +42,6 @@
authorize: 4 # owner
num_repos: 2
num_members: 2
unit_types: '[1,2,3,4,5,6,7]'
-
id: 6
@ -56,4 +51,3 @@
authorize: 4 # owner
num_repos: 2
num_members: 1
unit_types: '[1,2,3,4,5,6,7]'

View File

@ -33,9 +33,15 @@
org_id: 19
team_id: 6
repo_id: 27
-
id: 7
org_id: 19
team_id: 6
repo_id: 28
repo_id: 28
-
id: 8
org_id: 3
team_id: 1
repo_id: 32

View File

@ -0,0 +1,209 @@
-
id: 1
team_id: 1
type: 1
-
id: 2
team_id: 1
type: 2
-
id: 3
team_id: 1
type: 3
-
id: 4
team_id: 1
type: 4
-
id: 5
team_id: 1
type: 5
-
id: 6
team_id: 1
type: 6
-
id: 7
team_id: 1
type: 7
-
id: 8
team_id: 2
type: 1
-
id: 9
team_id: 2
type: 2
-
id: 10
team_id: 2
type: 3
-
id: 11
team_id: 2
type: 4
-
id: 12
team_id: 2
type: 5
-
id: 13
team_id: 2
type: 6
-
id: 14
team_id: 2
type: 7
-
id: 15
team_id: 3
type: 1
-
id: 16
team_id: 3
type: 2
-
id: 17
team_id: 3
type: 3
-
id: 18
team_id: 3
type: 4
-
id: 19
team_id: 3
type: 5
-
id: 20
team_id: 3
type: 6
-
id: 21
team_id: 3
type: 7
-
id: 22
team_id: 4
type: 1
-
id: 23
team_id: 4
type: 2
-
id: 24
team_id: 4
type: 3
-
id: 25
team_id: 4
type: 4
-
id: 26
team_id: 4
type: 5
-
id: 27
team_id: 4
type: 6
-
id: 28
team_id: 4
type: 7
-
id: 29
team_id: 5
type: 1
-
id: 30
team_id: 5
type: 2
-
id: 31
team_id: 5
type: 3
-
id: 32
team_id: 5
type: 4
-
id: 33
team_id: 5
type: 5
-
id: 34
team_id: 5
type: 6
-
id: 35
team_id: 5
type: 7
-
id: 36
team_id: 6
type: 1
-
id: 37
team_id: 6
type: 2
-
id: 38
team_id: 6
type: 3
-
id: 39
team_id: 6
type: 4
-
id: 40
team_id: 6
type: 5
-
id: 41
team_id: 6
type: 6
-
id: 42
team_id: 6
type: 7

View File

@ -45,7 +45,7 @@
is_admin: false
avatar: avatar3
avatar_email: user3@example.com
num_repos: 2
num_repos: 3
num_members: 2
num_teams: 2

View File

@ -66,7 +66,7 @@ func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
var ascii string
var data = "|||||||"
lines := strings.Split(s, "DATA:")
lines := strings.SplitN(s, "DATA:", 2)
switch len(lines) {
case 1:

View File

@ -5,6 +5,7 @@
package models
import (
"fmt"
"testing"
"code.gitea.io/git"
@ -43,3 +44,32 @@ func BenchmarkParseCommitString(b *testing.B) {
}
}
}
func TestCommitStringParsing(t *testing.T) {
dataFirstPart := "* DATA:||4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Author|user@mail.something|4e61bac|"
tests := []struct {
shouldPass bool
testName string
commitMessage string
}{
{true, "normal", "not a fancy message"},
{true, "extra pipe", "An extra pipe: |"},
{true, "extra 'Data:'", "DATA: might be trouble"},
}
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
testString := fmt.Sprintf("%s%s", dataFirstPart, test.commitMessage)
graphItem, err := graphItemFromString(testString, nil)
if err != nil && test.shouldPass {
t.Errorf("Could not parse %s", testString)
return
}
if test.commitMessage != graphItem.Subject {
t.Errorf("%s does not match %s", test.commitMessage, graphItem.Subject)
}
})
}
}

View File

@ -649,6 +649,20 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
if issue.IsClosed == isClosed {
return nil
}
// Check for open dependencies
if isClosed && issue.Repo.IsDependenciesEnabled() {
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
noDeps, err := IssueNoDependenciesLeft(issue)
if err != nil {
return err
}
if !noDeps {
return ErrDependenciesLeft{issue.ID}
}
}
issue.IsClosed = isClosed
if isClosed {
issue.ClosedUnix = util.TimeStampNow()
@ -1283,7 +1297,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
And("`comment`.type = ?", CommentTypeComment).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `comment`.poster_id").
Join("INNER", "`user`", "`user`.id = `comment`.poster_id").
Distinct("poster_id").
Find(&userIDs); err != nil {
return nil, fmt.Errorf("get poster IDs: %v", err)
@ -1598,3 +1612,33 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User)
return sess.Commit()
}
// Get Blocked By Dependencies, aka all issues this issue is blocked by.
func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_id = ?", issue.ID).
Find(&issueDeps)
}
// Get Blocking Dependencies, aka all issues this issue blocks.
func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.issue_id").
Where("dependency_id = ?", issue.ID).
Find(&issueDeps)
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies() ([]*Issue, error) {
return issue.getBlockedByDependencies(x)
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
func (issue *Issue) BlockingDependencies() ([]*Issue, error) {
return issue.getBlockingDependencies(x)
}

View File

@ -66,6 +66,10 @@ const (
CommentTypeModifiedDeadline
// Removed a due date
CommentTypeRemovedDeadline
// Dependency added
CommentTypeAddDependency
//Dependency removed
CommentTypeRemoveDependency
)
// CommentTag defines comment tag type
@ -81,23 +85,25 @@ const (
// Comment represents a comment in commit and issue page.
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
OldTitle string
NewTitle string
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
OldTitle string
NewTitle string
DependentIssueID int64
DependentIssue *Issue `xorm:"-"`
CommitID int64
Line int64
@ -281,6 +287,15 @@ func (c *Comment) LoadAssigneeUser() error {
return nil
}
// LoadDepIssueDetails loads Dependent Issue Details
func (c *Comment) LoadDepIssueDetails() (err error) {
if c.DependentIssueID <= 0 || c.DependentIssue != nil {
return nil
}
c.DependentIssue, err = getIssueByID(x, c.DependentIssueID)
return err
}
// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
@ -332,22 +347,24 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if opts.Label != nil {
LabelID = opts.Label.ID
}
comment := &Comment{
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
DependentIssueID: opts.DependentIssueID,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
@ -549,6 +566,39 @@ func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, is
})
}
// Creates issue dependency comment
func createIssueDependencyComment(e *xorm.Session, doer *User, issue *Issue, dependentIssue *Issue, add bool) (err error) {
cType := CommentTypeAddDependency
if !add {
cType = CommentTypeRemoveDependency
}
// Make two comments, one in each issue
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
DependentIssueID: dependentIssue.ID,
})
if err != nil {
return
}
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: dependentIssue,
DependentIssueID: issue.ID,
})
if err != nil {
return
}
return
}
// CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct {
Type CommentType
@ -557,17 +607,18 @@ type CreateCommentOptions struct {
Issue *Issue
Label *Label
OldMilestoneID int64
MilestoneID int64
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
DependentIssueID int64
OldMilestoneID int64
MilestoneID int64
AssigneeID int64
RemovedAssignee bool
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
}
// CreateComment creates comment of issue or commit.

137
models/issue_dependency.go Normal file
View File

@ -0,0 +1,137 @@
// Copyright 2018 The Gitea 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 (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// IssueDependency represents an issue dependency
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
DependencyID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
CreatedUnix util.TimeStamp `xorm:"created"`
UpdatedUnix util.TimeStamp `xorm:"updated"`
}
// DependencyType Defines Dependency Type Constants
type DependencyType int
// Define Dependency Types
const (
DependencyTypeBlockedBy DependencyType = iota
DependencyTypeBlocking
)
// CreateIssueDependency creates a new dependency for an issue
func CreateIssueDependency(user *User, issue, dep *Issue) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// Check if it aleready exists
exists, err := issueDepExists(sess, issue.ID, dep.ID)
if err != nil {
return err
}
if exists {
return ErrDependencyExists{issue.ID, dep.ID}
}
// And if it would be circular
circular, err := issueDepExists(sess, dep.ID, issue.ID)
if err != nil {
return err
}
if circular {
return ErrCircularDependency{issue.ID, dep.ID}
}
if _, err := sess.Insert(&IssueDependency{
UserID: user.ID,
IssueID: issue.ID,
DependencyID: dep.ID,
}); err != nil {
return err
}
// Add comment referencing the new dependency
if err = createIssueDependencyComment(sess, user, issue, dep, true); err != nil {
return err
}
return sess.Commit()
}
// RemoveIssueDependency removes a dependency from an issue
func RemoveIssueDependency(user *User, issue *Issue, dep *Issue, depType DependencyType) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
var issueDepToDelete IssueDependency
switch depType {
case DependencyTypeBlockedBy:
issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
case DependencyTypeBlocking:
issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
default:
return ErrUnknownDependencyType{depType}
}
affected, err := sess.Delete(&issueDepToDelete)
if err != nil {
return err
}
// If we deleted nothing, the dependency did not exist
if affected <= 0 {
return ErrDependencyNotExists{issue.ID, dep.ID}
}
// Add comment referencing the removed dependency
if err = createIssueDependencyComment(sess, user, issue, dep, false); err != nil {
return err
}
return sess.Commit()
}
// Check if the dependency already exists
func issueDepExists(e Engine, issueID int64, depID int64) (bool, error) {
return e.Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{})
}
// IssueNoDependenciesLeft checks if issue can be closed
func IssueNoDependenciesLeft(issue *Issue) (bool, error) {
exists, err := x.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_dependency.issue_id = ?", issue.ID).
And("issue.is_closed = ?", "0").
Exist(&Issue{})
return !exists, err
}
// IsDependenciesEnabled returns if dependecies are enabled and returns the default setting if not set.
func (repo *Repository) IsDependenciesEnabled() bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnit(UnitTypeIssues); err != nil {
log.Trace("%s", err)
return setting.Service.DefaultEnableDependencies
}
return u.IssuesConfig().EnableDependencies
}

View File

@ -0,0 +1,57 @@
// Copyright 2018 The Gitea 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 (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateIssueDependency(t *testing.T) {
// Prepare
assert.NoError(t, PrepareTestDatabase())
user1, err := GetUserByID(1)
assert.NoError(t, err)
issue1, err := GetIssueByID(1)
assert.NoError(t, err)
issue2, err := GetIssueByID(2)
assert.NoError(t, err)
// Create a dependency and check if it was successful
err = CreateIssueDependency(user1, issue1, issue2)
assert.NoError(t, err)
// Do it again to see if it will check if the dependency already exists
err = CreateIssueDependency(user1, issue1, issue2)
assert.Error(t, err)
assert.True(t, IsErrDependencyExists(err))
// Check for circular dependencies
err = CreateIssueDependency(user1, issue2, issue1)
assert.Error(t, err)
assert.True(t, IsErrCircularDependency(err))
_ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID})
// Check if dependencies left is correct
left, err := IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.False(t, left)
// Close #2 and check again
err = issue2.ChangeStatus(user1, issue2.Repo, true)
assert.NoError(t, err)
left, err = IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.True(t, left)
// Test removing the dependency
err = RemoveIssueDependency(user1, issue1, issue2, DependencyTypeBlockedBy)
assert.NoError(t, err)
}

View File

@ -166,7 +166,7 @@ func (issues IssueList) loadAssignees(e Engine) error {
var assignees = make(map[int64][]*User, len(issues))
rows, err := e.Table("issue_assignees").
Join("INNER", "user", "`user`.id = `issue_assignees`.assignee_id").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issues.getIssueIDs()).
Rows(new(AssigneeIssue))
if err != nil {

View File

@ -67,7 +67,19 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error
Where("`issue_watch`.issue_id = ?", issueID).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "user", "`user`.id = `issue_watch`.user_id").
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id").
Find(&watches)
return
}
func removeIssueWatchersByRepoID(e Engine, userID int64, repoID int64) error {
iw := &IssueWatch{
IsWatching: false,
}
_, err := e.
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", repoID).
Cols("is_watching", "updated_unix").
Where("`issue_watch`.user_id = ?", userID).
Update(iw)
return err
}

View File

@ -186,6 +186,14 @@ var migrations = []Migration{
NewMigration("add u2f", addU2FReg),
// v66 -> v67
NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
// v67 -> v68
NewMigration("remove stale watches", removeStaleWatches),
// v68 -> V69
NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics),
// v69 -> v70
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable),
// v70 -> v71
NewMigration("add issue_dependencies", addIssueDependencies),
}
// Migrate database to current version

View File

@ -25,10 +25,15 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
Created time.Time `xorm:"-"`
}
type Team struct {
ID int64
UnitTypes []int `xorm:"json"`
}
// Update team unit types
const batchSize = 100
for start := 0; ; start += batchSize {
teams := make([]*models.Team, 0, batchSize)
teams := make([]*Team, 0, batchSize)
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
return err
}
@ -36,7 +41,7 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
break
}
for _, team := range teams {
ut := make([]models.UnitType, 0, len(team.UnitTypes))
ut := make([]int, 0, len(team.UnitTypes))
for _, u := range team.UnitTypes {
if u < V16UnitTypeCommits {
ut = append(ut, u)

View File

@ -120,6 +120,14 @@ func addMultipleAssignees(x *xorm.Engine) error {
}
}
// Commit and begin new transaction for dropping columns
if err := sess.Commit(); err != nil {
return err
}
if err := sess.Begin(); err != nil {
return err
}
if err := dropTableColumns(sess, "issue", "assignee_id"); err != nil {
return err
}

158
models/migrations/v67.go Normal file
View File

@ -0,0 +1,158 @@
// Copyright 2018 The Gitea 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 migrations
import (
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func removeStaleWatches(x *xorm.Engine) error {
type Watch struct {
ID int64
UserID int64
RepoID int64
}
type IssueWatch struct {
ID int64
UserID int64
RepoID int64
IsWatching bool
}
type Repository struct {
ID int64
IsPrivate bool
OwnerID int64
}
type Access struct {
UserID int64
RepoID int64
Mode int
}
const (
// AccessModeNone no access
AccessModeNone int = iota // 0
// AccessModeRead read access
AccessModeRead // 1
)
accessLevel := func(userID int64, repo *Repository) (int, error) {
mode := AccessModeNone
if !repo.IsPrivate {
mode = AccessModeRead
}
if userID == 0 {
return mode, nil
}
if userID == repo.OwnerID {
return 4, nil
}
a := &Access{UserID: userID, RepoID: repo.ID}
if has, err := x.Get(a); !has || err != nil {
return mode, err
}
return a.Mode, nil
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
repoCache := make(map[int64]*Repository)
err := x.BufferSize(setting.IterateBufferSize).Iterate(new(Watch),
func(idx int, bean interface{}) error {
watch := bean.(*Watch)
repo := repoCache[watch.RepoID]
if repo == nil {
repo = &Repository{
ID: watch.RepoID,
}
if _, err := x.Get(repo); err != nil {
return err
}
repoCache[watch.RepoID] = repo
}
// Remove watches from now unaccessible repositories
mode, err := accessLevel(watch.UserID, repo)
if err != nil {
return err
}
has := AccessModeRead <= mode
if has {
return nil
}
if _, err = sess.Delete(&Watch{0, watch.UserID, repo.ID}); err != nil {
return err
}
_, err = sess.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repo.ID)
return err
})
if err != nil {
return err
}
repoCache = make(map[int64]*Repository)
err = x.BufferSize(setting.IterateBufferSize).
Distinct("issue_watch.user_id", "issue.repo_id").
Join("INNER", "issue", "issue_watch.issue_id = issue.id").
Where("issue_watch.is_watching = ?", true).
Iterate(new(IssueWatch),
func(idx int, bean interface{}) error {
watch := bean.(*IssueWatch)
repo := repoCache[watch.RepoID]
if repo == nil {
repo = &Repository{
ID: watch.RepoID,
}
if _, err := x.Get(repo); err != nil {
return err
}
repoCache[watch.RepoID] = repo
}
// Remove issue watches from now unaccssible repositories
mode, err := accessLevel(watch.UserID, repo)
if err != nil {
return err
}
has := AccessModeRead <= mode
if has {
return nil
}
iw := &IssueWatch{
IsWatching: false,
}
_, err = sess.
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", watch.RepoID).
Cols("is_watching", "updated_unix").
Where("`issue_watch`.user_id = ?", watch.UserID).
Update(iw)
return err
})
if err != nil {
return err
}
return sess.Commit()
}

213
models/migrations/v68.go Normal file
View File

@ -0,0 +1,213 @@
// Copyright 2018 The Gitea 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 migrations
import (
"fmt"
"regexp"
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
func validateTopic(topic string) bool {
return len(topic) <= 35 && topicPattern.MatchString(topic)
}
func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
log.Info("This migration could take up to minutes, please be patient.")
type Topic struct {
ID int64
Name string `xorm:"UNIQUE"`
RepoCount int
CreatedUnix int64 `xorm:"INDEX created"`
UpdatedUnix int64 `xorm:"INDEX updated"`
}
type RepoTopic struct {
RepoID int64 `xorm:"UNIQUE(s)"`
TopicID int64 `xorm:"UNIQUE(s)"`
}
type Repository struct {
ID int64 `xorm:"pk autoincr"`
Topics []string `xorm:"TEXT JSON"`
}
if err := x.Sync2(new(Topic)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
if err := x.Sync2(new(RepoTopic)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
sess := x.NewSession()
defer sess.Close()
const batchSize = 100
touchedRepo := make(map[int64]struct{})
delTopicIDs := make([]int64, 0, batchSize)
log.Info("Validating existed topics...")
if err := sess.Begin(); err != nil {
return err
}
for start := 0; ; start += batchSize {
topics := make([]*Topic, 0, batchSize)
if err := x.Cols("id", "name").Asc("id").Limit(batchSize, start).Find(&topics); err != nil {
return err
}
if len(topics) == 0 {
break
}
for _, topic := range topics {
if validateTopic(topic.Name) {
continue
}
log.Info("Incorrect topic: id = %v, name = %q", topic.ID, topic.Name)
topic.Name = strings.Replace(strings.TrimSpace(strings.ToLower(topic.Name)), " ", "-", -1)
ids := make([]int64, 0, 30)
if err := sess.Table("repo_topic").Cols("repo_id").
Where("topic_id = ?", topic.ID).Find(&ids); err != nil {
return err
}
log.Info("Touched repo ids: %v", ids)
for _, id := range ids {
touchedRepo[id] = struct{}{}
}
if validateTopic(topic.Name) {
unifiedTopic := Topic{Name: topic.Name}
exists, err := sess.Cols("id", "name").Get(&unifiedTopic)
log.Info("Exists topic with the name %q? %v, id = %v", topic.Name, exists, unifiedTopic.ID)
if err != nil {
return err
}
if exists {
log.Info("Updating repo_topic rows with topic_id = %v to topic_id = %v", topic.ID, unifiedTopic.ID)
if _, err := sess.Where("topic_id = ? AND repo_id NOT IN "+
"(SELECT rt1.repo_id FROM repo_topic rt1 INNER JOIN repo_topic rt2 "+
"ON rt1.repo_id = rt2.repo_id WHERE rt1.topic_id = ? AND rt2.topic_id = ?)",
topic.ID, topic.ID, unifiedTopic.ID).Update(&RepoTopic{TopicID: unifiedTopic.ID}); err != nil {
return err
}
log.Info("Updating topic `repo_count` field")
if _, err := sess.Exec(
"UPDATE topic SET repo_count = (SELECT COUNT(*) FROM repo_topic WHERE topic_id = ? GROUP BY topic_id) WHERE id = ?",
unifiedTopic.ID, unifiedTopic.ID); err != nil {
return err
}
} else {
log.Info("Updating topic: id = %v, name = %q", topic.ID, topic.Name)
if _, err := sess.Table("topic").ID(topic.ID).
Update(&Topic{Name: topic.Name}); err != nil {
return err
}
continue
}
}
delTopicIDs = append(delTopicIDs, topic.ID)
}
}
if err := sess.Commit(); err != nil {
return err
}
sess.Init()
log.Info("Deleting incorrect topics...")
if err := sess.Begin(); err != nil {
return err
}
log.Info("Deleting 'repo_topic' rows for topics with ids = %v", delTopicIDs)
if _, err := sess.In("topic_id", delTopicIDs).Delete(&RepoTopic{}); err != nil {
return err
}
log.Info("Deleting topics with id = %v", delTopicIDs)
if _, err := sess.In("id", delTopicIDs).Delete(&Topic{}); err != nil {
return err
}
if err := sess.Commit(); err != nil {
return err
}
delRepoTopics := make([]*RepoTopic, 0, batchSize)
log.Info("Checking the number of topics in the repositories...")
for start := 0; ; start += batchSize {
repoTopics := make([]*RepoTopic, 0, batchSize)
if err := x.Cols("repo_id").Asc("repo_id").Limit(batchSize, start).
GroupBy("repo_id").Having("COUNT(*) > 25").Find(&repoTopics); err != nil {
return err
}
if len(repoTopics) == 0 {
break
}
log.Info("Number of repositories with more than 25 topics: %v", len(repoTopics))
for _, repoTopic := range repoTopics {
touchedRepo[repoTopic.RepoID] = struct{}{}
tmpRepoTopics := make([]*RepoTopic, 0, 30)
if err := x.Where("repo_id = ?", repoTopic.RepoID).Find(&tmpRepoTopics); err != nil {
return err
}
log.Info("Repository with id = %v has %v topics", repoTopic.RepoID, len(tmpRepoTopics))
for i := len(tmpRepoTopics) - 1; i > 24; i-- {
delRepoTopics = append(delRepoTopics, tmpRepoTopics[i])
}
}
}
sess.Init()
log.Info("Deleting superfluous topics for repositories (more than 25 topics)...")
if err := sess.Begin(); err != nil {
return err
}
for _, repoTopic := range delRepoTopics {
log.Info("Deleting 'repo_topic' rows for 'repository' with id = %v. Topic id = %v",
repoTopic.RepoID, repoTopic.TopicID)
if _, err := sess.Where("repo_id = ? AND topic_id = ?", repoTopic.RepoID,
repoTopic.TopicID).Delete(&RepoTopic{}); err != nil {
return err
}
if _, err := sess.Exec(
"UPDATE topic SET repo_count = (SELECT repo_count FROM topic WHERE id = ?) - 1 WHERE id = ?",
repoTopic.TopicID, repoTopic.TopicID); err != nil {
return err
}
}
log.Info("Updating repositories 'topics' fields...")
for repoID := range touchedRepo {
topicNames := make([]string, 0, 30)
if err := sess.Table("topic").Cols("name").
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil {
return err
}
log.Info("Updating 'topics' field for repository with id = %v", repoID)
if _, err := sess.ID(repoID).Cols("topics").
Update(&Repository{Topics: topicNames}); err != nil {
return err
}
}
if err := sess.Commit(); err != nil {
return err
}
return nil
}

88
models/migrations/v69.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2018 The Gitea 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 migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
func moveTeamUnitsToTeamUnitTable(x *xorm.Engine) error {
// Team see models/team.go
type Team struct {
ID int64
OrgID int64
UnitTypes []int `xorm:"json"`
}
// TeamUnit see models/org_team.go
type TeamUnit struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
Type int `xorm:"UNIQUE(s)"`
}
if err := x.Sync2(new(TeamUnit)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// Update team unit types
const batchSize = 100
for start := 0; ; start += batchSize {
teams := make([]*Team, 0, batchSize)
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
return err
}
if len(teams) == 0 {
break
}
for _, team := range teams {
var unitTypes []int
if len(team.UnitTypes) == 0 {
unitTypes = allUnitTypes
} else {
unitTypes = team.UnitTypes
}
// insert units for team
var units = make([]TeamUnit, 0, len(unitTypes))
for _, tp := range unitTypes {
units = append(units, TeamUnit{
OrgID: team.OrgID,
TeamID: team.ID,
Type: tp,
})
}
if _, err := sess.Insert(&units); err != nil {
return fmt.Errorf("Insert team units: %v", err)
}
}
}
// Commit and begin new transaction for dropping columns
if err := sess.Commit(); err != nil {
return err
}
if err := sess.Begin(); err != nil {
return err
}
if err := dropTableColumns(sess, "team", "unit_types"); err != nil {
return err
}
return sess.Commit()
}

100
models/migrations/v70.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2018 The Gitea 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 migrations
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addIssueDependencies(x *xorm.Engine) (err error) {
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"NOT NULL"`
DependencyID int64 `xorm:"NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"created"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"updated"`
}
if err = x.Sync(new(IssueDependency)); err != nil {
return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
}
// Update Comment definition
// This (copied) struct does only contain fields used by xorm as the only use here is to update the database
// CommentType defines the comment type
type CommentType int
// TimeStamp defines a timestamp
type TimeStamp int64
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
IssueID int64 `xorm:"INDEX"`
LabelID int64
OldMilestoneID int64
MilestoneID int64
OldAssigneeID int64
AssigneeID int64
OldTitle string
NewTitle string
DependentIssueID int64
CommitID int64
Line int64
Content string `xorm:"TEXT"`
CreatedUnix TimeStamp `xorm:"INDEX created"`
UpdatedUnix TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
}
if err = x.Sync(new(Comment)); err != nil {
return fmt.Errorf("Error updating issue_comment table column definition: %v", err)
}
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Config map[string]interface{} `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
}
//Updating existing issue units
units := make([]*RepoUnit, 0, 100)
err = x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
return fmt.Errorf("Query repo units: %v", err)
}
for _, unit := range units {
if unit.Config == nil {
unit.Config = make(map[string]interface{})
}
if _, ok := unit.Config["EnableDependencies"]; !ok {
unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}
}
return err
}

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -117,10 +118,12 @@ func init() {
new(TrackedTime),
new(DeletedBranch),
new(RepoIndexerStatus),
new(IssueDependency),
new(LFSLock),
new(Reaction),
new(IssueAssignees),
new(U2FRegistration),
new(TeamUnit),
)
gonicNames := []string{"SSL", "UID"}
@ -184,6 +187,18 @@ func parsePostgreSQLHostPort(info string) (string, string) {
return host, port
}
func getPostgreSQLConnectionString(DBHost, DBUser, DBPasswd, DBName, DBParam, DBSSLMode string) (connStr string) {
host, port := parsePostgreSQLHostPort(DBHost)
if host[0] == '/' { // looks like a unix socket
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
url.PathEscape(DBUser), url.PathEscape(DBPasswd), port, DBName, DBParam, DBSSLMode, host)
} else {
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
url.PathEscape(DBUser), url.PathEscape(DBPasswd), host, port, DBName, DBParam, DBSSLMode)
}
return
}
func parseMSSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "1433"
if strings.Contains(info, ":") {
@ -214,14 +229,7 @@ func getEngine() (*xorm.Engine, error) {
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
}
case "postgres":
host, port := parsePostgreSQLHostPort(DbCfg.Host)
if host[0] == '/' { // looks like a unix socket
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), port, DbCfg.Name, Param, DbCfg.SSLMode, host)
} else {
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode)
}
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
case "mssql":
host, port := parseMSSQLHostPort(DbCfg.Host)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)

View File

@ -1,4 +1,5 @@
// Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@ -53,3 +54,42 @@ func Test_parsePostgreSQLHostPort(t *testing.T) {
assert.Equal(t, test.Port, port)
}
}
func Test_getPostgreSQLConnectionString(t *testing.T) {
tests := []struct {
Host string
Port string
User string
Passwd string
Name string
Param string
SSLMode string
Output string
}{
{
Host: "/tmp/pg.sock",
Port: "4321",
User: "testuser",
Passwd: "space space !#$%^^%^```-=?=",
Name: "gitea",
Param: "",
SSLMode: "false",
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/giteasslmode=false&host=/tmp/pg.sock",
},
{
Host: "localhost",
Port: "1234",
User: "pgsqlusername",
Passwd: "I love Gitea!",
Name: "gitea",
Param: "",
SSLMode: "true",
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/giteasslmode=true",
},
}
for _, test := range tests {
connStr := getPostgreSQLConnectionString(test.Host, test.User, test.Passwd, test.Name, test.Param, test.SSLMode)
assert.Equal(t, test.Output, connStr)
}
}

View File

@ -1,18 +0,0 @@
// +build tidb
// 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")
}

View File

@ -119,7 +119,17 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
}
}
issue.loadRepo(e)
for _, watch := range watches {
issue.Repo.Units = nil
if issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypePullRequests) {
continue
}
if !issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypeIssues) {
continue
}
if err := notifyUser(watch.UserID); err != nil {
return err
}

View File

@ -154,12 +154,26 @@ func CreateOrganization(org, owner *User) (err error) {
Name: ownerTeamName,
Authorize: AccessModeOwner,
NumMembers: 1,
UnitTypes: allRepUnitTypes,
}
if _, err = sess.Insert(t); err != nil {
return fmt.Errorf("insert owner team: %v", err)
}
// insert units for team
var units = make([]TeamUnit, 0, len(allRepUnitTypes))
for _, tp := range allRepUnitTypes {
units = append(units, TeamUnit{
OrgID: org.ID,
TeamID: t.ID,
Type: tp,
})
}
if _, err = sess.Insert(&units); err != nil {
sess.Rollback()
return err
}
if _, err = sess.Insert(&TeamUser{
UID: owner.ID,
OrgID: org.ID,
@ -238,6 +252,7 @@ func deleteOrg(e *xorm.Session, u *User) error {
&Team{OrgID: u.ID},
&OrgUser{OrgID: u.ID},
&TeamUser{OrgID: u.ID},
&TeamUnit{OrgID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
@ -368,7 +383,7 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) {
ous := make([]*OrgUser, 0, 10)
sess := x.
Join("LEFT", "user", "`org_user`.org_id=`user`.id").
Join("LEFT", "`user`", "`org_user`.org_id=`user`.id").
Where("`org_user`.uid=?", uid)
if !all {
// Only show public organizations
@ -560,7 +575,7 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team,
return teams, e.
Where("`team_user`.org_id = ?", org.ID).
Join("INNER", "team_user", "`team_user`.team_id = team.id").
Join("INNER", "user", "`user`.id=team_user.uid").
Join("INNER", "`user`", "`user`.id=team_user.uid").
And("`team_user`.uid = ?", userID).
Asc("`user`.name").
Cols(cols...).

View File

@ -1,3 +1,4 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Copyright 2016 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.
@ -10,7 +11,6 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
)
@ -28,15 +28,16 @@ type Team struct {
Members []*User `xorm:"-"`
NumRepos int
NumMembers int
UnitTypes []UnitType `xorm:"json"`
Units []*TeamUnit `xorm:"-"`
}
// GetUnitTypes returns unit types the team owned, empty means all the unit types
func (t *Team) GetUnitTypes() []UnitType {
if len(t.UnitTypes) == 0 {
return allRepUnitTypes
func (t *Team) getUnits(e Engine) (err error) {
if t.Units != nil {
return nil
}
return t.UnitTypes
t.Units, err = getUnitsByTeamID(e, t.ID)
return err
}
// HasWriteAccess returns true if team has at least write level access mode.
@ -178,6 +179,11 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
return err
}
// Remove all IssueWatches a user has subscribed to in the repositories
if err := removeIssueWatchersByRepoID(e, teamUser.UID, repo.ID); err != nil {
return err
}
}
return nil
@ -209,11 +215,12 @@ func (t *Team) RemoveRepository(repoID int64) error {
// UnitEnabled returns if the team has the given unit type enabled
func (t *Team) UnitEnabled(tp UnitType) bool {
if len(t.UnitTypes) == 0 {
return true
if err := t.getUnits(x); err != nil {
log.Warn("Error loading repository (ID: %d) units: %s", t.ID, err.Error())
}
for _, u := range t.UnitTypes {
if u == tp {
for _, unit := range t.Units {
if unit.Type == tp {
return true
}
}
@ -270,6 +277,17 @@ func NewTeam(t *Team) (err error) {
return err
}
// insert units for team
if len(t.Units) > 0 {
for _, unit := range t.Units {
unit.TeamID = t.ID
}
if _, err = sess.Insert(&t.Units); err != nil {
sess.Rollback()
return err
}
}
// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
sess.Rollback()
@ -374,11 +392,34 @@ func DeleteTeam(t *Team) error {
return err
}
if err := t.getMembers(sess); err != nil {
return err
}
// Delete all accesses.
for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
return err
}
// Remove watches from all users and now unaccessible repos
for _, user := range t.Members {
has, err := hasAccess(sess, user.ID, repo, AccessModeRead)
if err != nil {
return err
} else if has {
continue
}
if err = watchRepo(sess, user.ID, repo.ID, false); err != nil {
return err
}
// Remove all IssueWatches a user has subscribed to in the repositories
if err = removeIssueWatchersByRepoID(sess, user.ID, repo.ID); err != nil {
return err
}
}
}
// Delete team-repo
@ -396,6 +437,13 @@ func DeleteTeam(t *Team) error {
return err
}
// Delete team-unit.
if _, err := sess.
Where("team_id=?", t.ID).
Delete(new(TeamUnit)); err != nil {
return err
}
// Delete team.
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
return err
@ -518,6 +566,10 @@ func AddTeamMember(team *Team, userID int64) error {
if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
return err
}
if err = watchRepo(sess, userID, repo.ID, true); err != nil {
return err
}
}
return sess.Commit()
@ -558,6 +610,23 @@ func removeTeamMember(e *xorm.Session, team *Team, userID int64) error {
if err := repo.recalculateTeamAccesses(e, 0); err != nil {
return err
}
// Remove watches from now unaccessible
has, err := hasAccess(e, userID, repo, AccessModeRead)
if err != nil {
return err
} else if has {
continue
}
if err = watchRepo(e, userID, repo.ID, false); err != nil {
return err
}
// Remove all IssueWatches a user has subscribed to in the repositories
if err := removeIssueWatchersByRepoID(e, userID, repo.ID); err != nil {
return err
}
}
// Check if the user is a member of any team in the organization.
@ -646,3 +715,47 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er
And("team_repo.repo_id = ?", repoID).
Find(&teams)
}
// ___________ ____ ___ .__ __
// \__ ___/___ _____ _____ | | \____ |__|/ |_
// | |_/ __ \\__ \ / \| | / \| \ __\
// | |\ ___/ / __ \| Y Y \ | / | \ || |
// |____| \___ >____ /__|_| /______/|___| /__||__|
// \/ \/ \/ \/
// TeamUnit describes all units of a repository
type TeamUnit struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
Type UnitType `xorm:"UNIQUE(s)"`
}
// Unit returns Unit
func (t *TeamUnit) Unit() Unit {
return Units[t.Type]
}
func getUnitsByTeamID(e Engine, teamID int64) (units []*TeamUnit, err error) {
return units, e.Where("team_id = ?", teamID).Find(&units)
}
// UpdateTeamUnits updates a teams's units
func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil {
return err
}
if _, err = sess.Insert(units); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
}

View File

@ -489,8 +489,8 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, expectedCount, count)
}
testSuccess(2, 2)
testSuccess(4, 1)
testSuccess(2, 3)
testSuccess(4, 2)
}
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
@ -503,8 +503,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expectedRepoIDs, repoIDs)
}
testSuccess(2, 1, 100, []int64{3, 5})
testSuccess(4, 0, 100, []int64{3})
testSuccess(2, 1, 100, []int64{3, 5, 32})
testSuccess(4, 0, 100, []int64{3, 32})
}
func TestAccessibleReposEnv_Repos(t *testing.T) {
@ -522,8 +522,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
}
assert.Equal(t, expectedRepos, repos)
}
testSuccess(2, []int64{3, 5})
testSuccess(4, []int64{3})
testSuccess(2, []int64{3, 5, 32})
testSuccess(4, []int64{3, 32})
}
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {

View File

@ -448,6 +448,11 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error {
}
}
rel.Repo = repo
if err = rel.LoadAttributes(); err != nil {
return fmt.Errorf("LoadAttributes: %v", err)
}
mode, _ := accessLevel(x, u.ID, rel.Repo)
if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{
Action: api.HookReleaseDeleted,

View File

@ -365,22 +365,14 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (
return err
}
var allTypes = make(map[UnitType]struct{}, len(allRepUnitTypes))
for _, team := range teams {
// Administrators can not be limited
if team.Authorize >= AccessModeAdmin {
return nil
}
for _, unitType := range team.UnitTypes {
allTypes[unitType] = struct{}{}
}
}
// unique
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
for _, u := range repo.Units {
if _, ok := allTypes[u.Type]; ok {
newRepoUnits = append(newRepoUnits, u)
for _, team := range teams {
if team.UnitEnabled(u.Type) {
newRepoUnits = append(newRepoUnits, u)
break
}
}
}
@ -789,7 +781,7 @@ var (
// DescriptionHTML does special handles to description and return HTML string.
func (repo *Repository) DescriptionHTML() template.HTML {
sanitize := func(s string) string {
return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener">%[1]s</a>`, s)
return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener noreferrer">%[1]s</a>`, s)
}
return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize))
}
@ -1353,7 +1345,17 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking, AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime},
Config: &IssuesConfig{
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
EnableDependencies: setting.Service.DefaultEnableDependencies,
},
})
} else if tp == UnitTypePullRequests {
units = append(units, RepoUnit{
RepoID: repo.ID,
Type: tp,
Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowSquash: true},
})
} else {
units = append(units, RepoUnit{
@ -1848,6 +1850,12 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}); err != nil {
return err
}
if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
return err
}
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}); err != nil {
return err
}
attachments := make([]*Attachment, 0, 5)
if err = sess.
@ -1950,7 +1958,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
var repo Repository
has, err := x.Select("repository.*").
Join("INNER", "user", "`user`.id = repository.owner_id").
Join("INNER", "`user`", "`user`.id = repository.owner_id").
Where("repository.lower_name = ?", strings.ToLower(repoName)).
And("`user`.lower_name = ?", strings.ToLower(ownerName)).
Get(&repo)

View File

@ -172,5 +172,14 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
return err
}
if err = watchRepo(sess, uid, repo.ID, false); err != nil {
return err
}
// Remove all IssueWatches a user has subscribed to in the repository
if err := removeIssueWatchersByRepoID(sess, uid, repo.ID); err != nil {
return err
}
return sess.Commit()
}

View File

@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 14},
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
count: 15},
count: 16},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 19},
count: 20},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 13},
@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 11},
{name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
count: 15},
count: 16},
}
for _, testCase := range testCases {

View File

@ -73,6 +73,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
type IssuesConfig struct {
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableDependencies bool
}
// FromDB fills up a IssuesConfig from serialized format.
@ -165,7 +166,6 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig {
func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
return r.Config.(*ExternalTrackerConfig)
}
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
return units, e.Where("repo_id = ?", repoID).Find(&units)
}

View File

@ -54,7 +54,7 @@ func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
return watches, e.Where("`watch`.repo_id=?", repoID).
And("`user`.is_active=?", true).
And("`user`.prohibit_login=?", false).
Join("INNER", "user", "`user`.id = `watch`.user_id").
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
Find(&watches)
}
@ -109,6 +109,23 @@ func notifyWatchers(e Engine, act *Action) error {
act.ID = 0
act.UserID = watches[i].UserID
act.Repo.Units = nil
switch act.OpType {
case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch:
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeCode) {
continue
}
case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeIssues) {
continue
}
case ActionCreatePullRequest, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest:
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypePullRequests) {
continue
}
}
if _, err = e.InsertOne(act); err != nil {
return fmt.Errorf("insert new action: %v", err)
}

View File

@ -6,6 +6,7 @@ package models
import (
"fmt"
"regexp"
"strings"
"code.gitea.io/gitea/modules/util"
@ -20,10 +21,12 @@ func init() {
)
}
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
// Topic represents a topic of repositories
type Topic struct {
ID int64
Name string `xorm:"unique"`
Name string `xorm:"UNIQUE"`
RepoCount int
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
@ -31,8 +34,8 @@ type Topic struct {
// RepoTopic represents associated repositories and topics
type RepoTopic struct {
RepoID int64 `xorm:"unique(s)"`
TopicID int64 `xorm:"unique(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
TopicID int64 `xorm:"UNIQUE(s)"`
}
// ErrTopicNotExist represents an error that a topic is not exist
@ -51,6 +54,11 @@ func (err ErrTopicNotExist) Error() string {
return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
}
// ValidateTopic checks topics by length and match pattern rules
func ValidateTopic(topic string) bool {
return len(topic) <= 35 && topicPattern.MatchString(topic)
}
// GetTopicByName retrieves topic by name
func GetTopicByName(name string) (*Topic, error) {
var topic Topic
@ -182,6 +190,13 @@ func SaveTopics(repoID int64, topicNames ...string) error {
}
}
topicNames = make([]string, 0, 25)
if err := sess.Table("topic").Cols("name").
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil {
return err
}
if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
Topics: topicNames,
}); err != nil {

View File

@ -55,3 +55,16 @@ func TestAddTopic(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 2, len(topics))
}
func TestTopicValidator(t *testing.T) {
assert.True(t, ValidateTopic("12345"))
assert.True(t, ValidateTopic("2-test"))
assert.True(t, ValidateTopic("test-3"))
assert.True(t, ValidateTopic("first"))
assert.True(t, ValidateTopic("second-test-topic"))
assert.True(t, ValidateTopic("third-project-topic-with-max-length"))
assert.False(t, ValidateTopic("$fourth-test,topic"))
assert.False(t, ValidateTopic("-fifth-test-topic"))
assert.False(t, ValidateTopic("sixth-go-project-topic-with-excess-length"))
}

View File

@ -374,9 +374,9 @@ func (u *User) GetFollowers(page int) ([]*User, error) {
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("follow.follow_id=?", u.ID)
if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`)
sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
} else {
sess = sess.Join("LEFT", "follow", "user.id=follow.user_id")
sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
}
return users, sess.Find(&users)
}
@ -393,9 +393,9 @@ func (u *User) GetFollowing(page int) ([]*User, error) {
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
Where("follow.user_id=?", u.ID)
if setting.UsePostgreSQL {
sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`)
sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
} else {
sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id")
sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
}
return users, sess.Find(&users)
}
@ -433,6 +433,17 @@ func (u *User) IsPasswordSet() bool {
// 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 {
imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("DecodeConfig: %v", err)
}
if imgCfg.Width > setting.AvatarMaxWidth {
return fmt.Errorf("Image width is to large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth)
}
if imgCfg.Height > setting.AvatarMaxHeight {
return fmt.Errorf("Image height is to large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight)
}
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("Decode: %v", err)
@ -546,28 +557,46 @@ func (u *User) GetRepositories(page, pageSize int) (err error) {
return err
}
// GetRepositoryIDs returns repositories IDs where user owned
func (u *User) GetRepositoryIDs() ([]int64, error) {
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes
func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) {
var ids []int64
return ids, x.Table("repository").Cols("id").Where("owner_id = ?", u.ID).Find(&ids)
sess := x.Table("repository").Cols("repository.id")
if len(units) > 0 {
sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id")
sess = sess.In("repo_unit.type", units)
}
return ids, sess.Where("owner_id = ?", u.ID).Find(&ids)
}
// GetOrgRepositoryIDs returns repositories IDs where user's team owned
func (u *User) GetOrgRepositoryIDs() ([]int64, error) {
// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes
func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) {
var ids []int64
return ids, x.Table("repository").
sess := x.Table("repository").
Cols("repository.id").
Join("INNER", "team_user", "repository.owner_id = team_user.org_id AND team_user.uid = ?", u.ID).
Join("INNER", "team_user", "repository.owner_id = team_user.org_id").
Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true)
if len(units) > 0 {
sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id")
sess = sess.In("team_unit.type", units)
}
return ids, sess.
Where("team_user.uid = ?", u.ID).
GroupBy("repository.id").Find(&ids)
}
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
func (u *User) GetAccessRepoIDs() ([]int64, error) {
ids, err := u.GetRepositoryIDs()
func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) {
ids, err := u.GetRepositoryIDs(units...)
if err != nil {
return nil, err
}
ids2, err := u.GetOrgRepositoryIDs()
ids2, err := u.GetOrgRepositoryIDs(units...)
if err != nil {
return nil, err
}
@ -927,7 +956,7 @@ func deleteUser(e *xorm.Session, u *User) error {
Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil {
return fmt.Errorf("get all watches: %v", err)
}
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).Update(new(Repository)); err != nil {
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil {
return fmt.Errorf("decrease repository num_watches: %v", err)
}
// ***** END: Watch *****
@ -937,7 +966,7 @@ func deleteUser(e *xorm.Session, u *User) error {
if err = e.Table("star").Cols("star.repo_id").
Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil {
return fmt.Errorf("get all stars: %v", err)
} else if _, err = e.Decr("num_stars").In("id", starredRepoIDs).Update(new(Repository)); err != nil {
} else if _, err = e.Decr("num_stars").In("id", starredRepoIDs).NoAutoTime().Update(new(Repository)); err != nil {
return fmt.Errorf("decrease repository num_stars: %v", err)
}
// ***** END: Star *****

View File

@ -159,3 +159,25 @@ func BenchmarkHashPassword(b *testing.B) {
u.HashPassword(pass)
}
}
func TestGetOrgRepositoryIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
accessibleRepos, err := user2.GetOrgRepositoryIDs()
assert.NoError(t, err)
// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization
assert.Equal(t, []int64{3, 5, 32}, accessibleRepos)
accessibleRepos, err = user4.GetOrgRepositoryIDs()
assert.NoError(t, err)
// User 4's team has access to private repo 3, repo 32 is a public repo of the organization
assert.Equal(t, []int64{3, 32}, accessibleRepos)
accessibleRepos, err = user5.GetOrgRepositoryIDs()
assert.NoError(t, err)
// User 5's team has no access to any repo
assert.Len(t, accessibleRepos, 0)
}

View File

@ -163,7 +163,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo
profileURL = customURLMapping.ProfileURL
}
}
provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL)
provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user")
case "gplus":
provider = gplus.New(clientID, clientSecret, callbackURL, "email")
case "openidConnect":

View File

@ -113,6 +113,7 @@ type RepoSettingForm struct {
PullsAllowSquash bool
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
// Admin settings
EnableHealthCheck bool

View File

@ -72,10 +72,11 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
// RegisterForm form for registering
type RegisterForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
Retype string
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
Retype string
GRecaptchaResponse string `form:"g-recaptcha-response"`
}
// Validate valideates the fields

View File

@ -22,8 +22,9 @@ func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) b
// SignUpOpenIDForm form for signin up with OpenID
type SignUpOpenIDForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
GRecaptchaResponse string `form:"g-recaptcha-response"`
}
// Validate valideates the fields

View File

@ -37,12 +37,6 @@ func Toggle(options *ToggleOptions) macaron.Handler {
return
}
// Check non-logged users landing page.
if !ctx.IsSigned && ctx.Req.RequestURI == "/" && setting.LandingPageURL != setting.LandingPageHome {
ctx.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
return
}
// Redirect to dashboard if user tries to visit any non-login page.
if options.SignOutRequired && ctx.IsSigned && ctx.Req.RequestURI != "/" {
ctx.Redirect(setting.AppSubURL + "/")

View File

@ -104,6 +104,11 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
r.IsWriter() || issue.IsPoster(user.ID) || isAssigned)
}
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
return r.Repository.IsDependenciesEnabled() && r.IsWriter()
}
// GetCommitsCount returns cached commit count for current view
func (r *Repository) GetCommitsCount() (int64, error) {
var contextName string

View File

@ -85,9 +85,12 @@ type link struct {
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
func isOidValid(oid string) bool {
return oidRegExp.MatchString(oid)
}
// ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler(ctx *context.Context) {
if !setting.LFS.StartServer {
writeStatus(ctx, 404)
return
@ -110,6 +113,11 @@ func ObjectOidHandler(ctx *context.Context) {
}
func getAuthenticatedRepoAndMeta(ctx *context.Context, rv *RequestVars, requireWrite bool) (*models.LFSMetaObject, *models.Repository) {
if !isOidValid(rv.Oid) {
writeStatus(ctx, 404)
return nil, nil
}
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
if err != nil {
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
@ -222,7 +230,7 @@ func PostHandler(ctx *context.Context) {
return
}
if !oidRegExp.MatchString(rv.Oid) {
if !isOidValid(rv.Oid) {
writeStatus(ctx, 404)
return
}
@ -249,7 +257,6 @@ func PostHandler(ctx *context.Context) {
// BatchHandler provides the batch api
func BatchHandler(ctx *context.Context) {
if !setting.LFS.StartServer {
writeStatus(ctx, 404)
return
@ -266,6 +273,10 @@ func BatchHandler(ctx *context.Context) {
// Create a response object
for _, object := range bv.Objects {
if !isOidValid(object.Oid) {
continue
}
repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo)
if err != nil {
@ -292,12 +303,10 @@ func BatchHandler(ctx *context.Context) {
continue
}
if oidRegExp.MatchString(object.Oid) {
// Object is not found
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
if err == nil {
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
}
// Object is not found
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
if err == nil {
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
}
}

58
modules/markup/csv/csv.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2018 The Gitea 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 markup
import (
"bytes"
"encoding/csv"
"html"
"io"
"code.gitea.io/gitea/modules/markup"
)
func init() {
markup.RegisterParser(Parser{})
}
// Parser implements markup.Parser for orgmode
type Parser struct {
}
// Name implements markup.Parser
func (Parser) Name() string {
return "csv"
}
// Extensions implements markup.Parser
func (Parser) Extensions() []string {
return []string{".csv"}
}
// Render implements markup.Parser
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
rd := csv.NewReader(bytes.NewReader(rawBytes))
var tmpBlock bytes.Buffer
tmpBlock.WriteString(`<table class="table">`)
for {
fields, err := rd.Read()
if err == io.EOF {
break
}
if err != nil {
continue
}
tmpBlock.WriteString("<tr>")
for _, field := range fields {
tmpBlock.WriteString("<td>")
tmpBlock.WriteString(html.EscapeString(field))
tmpBlock.WriteString("</td>")
}
tmpBlock.WriteString("<tr>")
}
tmpBlock.WriteString("</table>")
return tmpBlock.Bytes()
}

View File

@ -0,0 +1,25 @@
// Copyright 2018 The Gitea 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 markup
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderCSV(t *testing.T) {
var parser Parser
var kases = map[string]string{
"a": "<table class=\"table\"><tr><td>a</td><tr></table>",
"1,2": "<table class=\"table\"><tr><td>1</td><td>2</td><tr></table>",
"<br/>": "<table class=\"table\"><tr><td>&lt;br/&gt;</td><tr></table>",
}
for k, v := range kases {
res := parser.Render([]byte(k), "", nil, false)
assert.EqualValues(t, v, string(res))
}
}

View File

@ -469,6 +469,9 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) {
} else {
link = strings.Replace(link, " ", "-", -1)
}
if !strings.Contains(link, "/") {
link = url.PathEscape(link)
}
}
urlPrefix := ctx.urlPrefix
if image {

View File

@ -82,12 +82,18 @@ func TestRender_ShortLinks(t *testing.T) {
rawtree := util.URLJoin(AppSubURL, "raw", "master")
url := util.URLJoin(tree, "Link")
otherURL := util.URLJoin(tree, "Other-Link")
encodedURL := util.URLJoin(tree, "Link%3F")
imgurl := util.URLJoin(rawtree, "Link.jpg")
otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg")
encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg")
notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg")
urlWiki := util.URLJoin(AppSubURL, "wiki", "Link")
otherURLWiki := util.URLJoin(AppSubURL, "wiki", "Other-Link")
encodedURLWiki := util.URLJoin(AppSubURL, "wiki", "Link%3F")
imgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg")
otherImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link+Other.jpg")
encodedImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link+%23.jpg")
notencodedImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "some", "path", "Link+#.jpg")
favicon := "http://google.com/favicon.ico"
test(
@ -134,4 +140,24 @@ func TestRender_ShortLinks(t *testing.T) {
"[[Link]] [[Other Link]]",
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a></p>`)
test(
"[[Link?]]",
`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
`<p><a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
test(
"[[Link]] [[Other Link]] [[Link?]]",
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
test(
"[[Link #.jpg]]",
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`"/></a></p>`,
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`"/></a></p>`)
test(
"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Title" alt="AltName"/></a></p>`,
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
test(
"[[some/path/Link #.jpg]]",
`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`"/></a></p>`,
`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`"/></a></p>`)
}

View File

@ -101,7 +101,7 @@ var (
func (r *Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
prefix := r.URLPrefix
if r.IsWiki {
prefix = util.URLJoin(prefix, "wiki", "src")
prefix = util.URLJoin(prefix, "wiki", "raw")
}
prefix = strings.Replace(prefix, "/src/", "/raw/", 1)
if len(link) > 0 {

View File

@ -0,0 +1,47 @@
// Copyright 2018 The Gitea 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 recaptcha
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"code.gitea.io/gitea/modules/setting"
)
// Response is the structure of JSON returned from API
type Response struct {
Success bool `json:"success"`
ChallengeTS time.Time `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes []string `json:"error-codes"`
}
const apiURL = "https://www.google.com/recaptcha/api/siteverify"
// Verify calls Google Recaptcha API to verify token
func Verify(response string) (bool, error) {
resp, err := http.PostForm(apiURL,
url.Values{"secret": {setting.Service.RecaptchaSecret}, "response": {response}})
if err != nil {
return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err)
}
var jsonResponse Response
err = json.Unmarshal(body, &jsonResponse)
if err != nil {
return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err)
}
return jsonResponse.Success, nil
}

View File

@ -75,6 +75,12 @@ const (
RepoCreatingPublic = "public"
)
// enumerates all the types of captchas
const (
ImageCaptcha = "image"
ReCaptcha = "recaptcha"
)
// settings
var (
// AppVer settings
@ -274,6 +280,7 @@ var (
ThemeColorMetaTag string
MaxDisplayFileSize int64
ShowUserEmail bool
DefaultTheme string
Admin struct {
UserPagingNum int
@ -297,6 +304,7 @@ var (
ReactionMaxUserNum: 10,
ThemeColorMetaTag: `#6cc644`,
MaxDisplayFileSize: 8388608,
DefaultTheme: `gitea`,
Admin: struct {
UserPagingNum int
RepoPagingNum int
@ -341,6 +349,8 @@ var (
// Picture settings
AvatarUploadPath string
AvatarMaxWidth int
AvatarMaxHeight int
GravatarSource string
GravatarSourceURL *url.URL
DisableGravatar bool
@ -1024,6 +1034,8 @@ func NewContext() {
if !filepath.IsAbs(AvatarUploadPath) {
AvatarUploadPath = path.Join(AppWorkPath, AvatarUploadPath)
}
AvatarMaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096)
AvatarMaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072)
switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
case "duoshuo":
GravatarSource = "http://gravatar.duoshuo.com/avatar/"
@ -1161,10 +1173,14 @@ var Service struct {
EnableReverseProxyAuth bool
EnableReverseProxyAutoRegister bool
EnableCaptcha bool
CaptchaType string
RecaptchaSecret string
RecaptchaSitekey string
DefaultKeepEmailPrivate bool
DefaultAllowCreateOrganization bool
EnableTimetracking bool
DefaultEnableTimetracking bool
DefaultEnableDependencies bool
DefaultAllowOnlyContributorsToTrackTime bool
NoReplyAddress string
@ -1185,13 +1201,17 @@ func newService() {
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool()
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)
Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")
Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
if Service.EnableTimetracking {
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
}
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")

View File

@ -186,6 +186,9 @@ func NewFuncMap() []template.FuncMap {
"ParseDeadline": func(deadline string) []string {
return strings.Split(deadline, "|")
},
"DefaultTheme": func() string {
return setting.UI.DefaultTheme
},
}}
}
@ -323,7 +326,7 @@ func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.H
// IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
func IsMultilineCommitMessage(msg string) bool {
return strings.Count(strings.TrimSpace(msg), "\n") > 1
return strings.Count(strings.TrimSpace(msg), "\n") >= 1
}
// Actioner describes an action

View File

@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// OptionalBool a boolean that can be "null"
@ -78,6 +79,18 @@ func URLJoin(base string, elems ...string) string {
return joinedURL
}
// IsExternalURL checks if rawURL points to an external URL like http://example.com
func IsExternalURL(rawURL string) bool {
parsed, err := url.Parse(rawURL)
if err != nil {
return true
}
if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(setting.Domain, "www.", "", 1) {
return true
}
return false
}
// Min min of two ints
func Min(a, b int) int {
if a > b {

View File

@ -7,6 +7,8 @@ package util
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@ -42,3 +44,36 @@ func TestURLJoin(t *testing.T) {
assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...))
}
}
func TestIsExternalURL(t *testing.T) {
setting.Domain = "try.gitea.io"
type test struct {
Expected bool
RawURL string
}
newTest := func(expected bool, rawURL string) test {
return test{Expected: expected, RawURL: rawURL}
}
for _, test := range []test{
newTest(false,
"https://try.gitea.io"),
newTest(true,
"https://example.com/"),
newTest(true,
"//example.com"),
newTest(true,
"http://example.com"),
newTest(false,
"a/"),
newTest(false,
"https://try.gitea.io/test?param=false"),
newTest(false,
"test?param=false"),
newTest(false,
"//try.gitea.io/test?param=false"),
newTest(false,
"/hey/hey/hey#3244"),
} {
assert.Equal(t, test.Expected, IsExternalURL(test.RawURL))
}
}

View File

@ -27,6 +27,7 @@ Enrico Testori hypertesto AT gmail DOT com
Ezequiel Gonzalez Rial <gonrial AT gmail DOT com>
Gabriel Dugny <gabriel DOT dugny AT gmail DOT com>
Gregor Santner <gdev AT live DOT de>
Guilhem Marion <gmarion AT netc DOT fr>
Halil Kaya <halil AT halilkaya DOT net>
Hamid Feizabadi <hamidfzm AT gmail DOT com>
Hilton Wichwski Silva <hilton AT hiltonws DOT com DOT br>
@ -55,6 +56,7 @@ Morten Sørensen <klim8d AT gmail DOT com>
Muhammad Fawwaz Orabi <mfawwaz93 AT gmail DOT com>
Nakao Takamasa <at.mattenn AT gmail DOT com>
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
Niclas Kroon <niclas DOT kroon AT gmail DOT com>
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
Pablo Saavedra <psaavedra AT igalia DOT com>
Richard Bukovansky <richard DOT bukovansky @ gmail DOT com>

View File

@ -11,6 +11,7 @@ version=Версия
page=Страница
template=Шаблон
language=Език
notifications=Известия
signed_in_as=Вписан като
username=Потребител
@ -28,6 +29,9 @@ manage_org=Управление на организации
account_settings=Настройки на профила
settings=Настройки
all=Всичко
sources=Източници
mirrors=Огледала
activities=Активности
pull_requests=Заявки за сливане
@ -69,7 +73,9 @@ issues.in_your_repos=Във Вашите хранилища
[explore]
repos=Хранилища
users=Потребители
organizations=Организации
search=Търсене
code=Код
[auth]
register_helper_msg=Вече имате профил? Впишете се сега!
@ -79,11 +85,14 @@ has_unconfirmed_mail=Здравейте %s, имате непотвърден а
resend_mail=Щракнете тук, за да се изпрати ново писмо за потвърждение
reset_password=Нулиране на паролата
reset_password_helper=Щракнете тук, за да нулирате паролата си
openid_connect_submit=Свързване
[mail]
activate_account=Моля активирайте Вашия профил
activate_email=Провери адрес на ел. поща
reset_password=Нулиране на паролата
register_success=Успешна регистрация
register_notify=Добре дошли в Gitea
[modal]
yes=Да
@ -117,6 +126,7 @@ url_error=` не е валиден URL адрес.`
include_error=` трябва да съдържа текст '%s'.`
unknown_error=Неизвестна грешка:
user_not_exist=Потребителят не съществува.
auth_failed=Неуспешно удостоверяване: %v
@ -136,9 +146,11 @@ unfollow=Не следвай
[settings]
profile=Профил
password=Парола
security=Сигурност
avatar=Аватар
social=Социални профили
delete=Изтрий профил
twofa=Двуфакторно удостоверяване
uid=UID
public_profile=Публичен профил
@ -169,6 +181,8 @@ key_content=Съдържание
add_on=Добавен на
last_used=Последно използван на
no_activity=Няма скорошна дейност
show_openid=Показване в профила
hide_openid=Скриване от профила
manage_social=Управление на свързани профили в социалните мрежи
@ -239,6 +253,7 @@ file_view_raw=Виж директен файл
file_permalink=Постоянна връзка
editor.preview_changes=Преглед на промени
editor.name_your_file=Име на файла ви…
editor.or=или
editor.commit_changes=Промени в ревизия
editor.add_tmpl=Добави '%s/<filename>'
@ -247,6 +262,7 @@ editor.update=Модифицирай '%s'
editor.delete=Изтрий '%s'
editor.commit_directly_to_this_branch=Запази ревизия директно в клон <strong class="branch-name">%s</strong>.
editor.create_new_branch=Създай <strong>нов клон</strong> от тази ревизия и изпрати заявки за сливане.
editor.new_branch_name_desc=Име на новия клон…
editor.cancel=Отказ
editor.branch_already_exists=Клон '%s' вече съществува в това хранилище.
editor.no_changes_to_show=Няма промени.
@ -255,6 +271,7 @@ editor.unable_to_upload_files=Невъзможно качване на файл
editor.upload_files_to_dir=Качи файлове в '%s'
commits.commits=Ревизии
commits.find=Търсене
commits.author=Автор
commits.message=Съобщение
commits.date=Дата
@ -277,6 +294,7 @@ issues.create_label=Създай етикет
issues.label_templates.title=Зареждане на предварително зададен набор от етикети
issues.label_templates.helper=Изберете набор етикети
issues.label_templates.fail_to_load_file=Неуспешно зареждане на шаблон с етикети '%s': %v
issues.deleted_milestone=`(изтрито)`
issues.open_tab=%d отворени
issues.close_tab=%d затворени
issues.filter_label=Етикет
@ -294,6 +312,11 @@ issues.filter_sort.recentupdate=Последно променени
issues.filter_sort.leastupdate=Отдавна променени
issues.filter_sort.mostcomment=Най-много коментирани
issues.filter_sort.leastcomment=Най-малко коментирани
issues.action_open=Отваряне
issues.action_close=Затваряне
issues.action_label=Етикет
issues.action_milestone=Етап
issues.action_milestone_no_select=Няма етап
issues.opened_by=отворен %[1]s от <a href="%[2]s">%[3]s</a>
issues.opened_by_fake=отворен %[1]s от %[2]s
issues.previous=Предишна
@ -323,9 +346,17 @@ issues.label_count=%d етикети
issues.label_open_issues=%d отворени задачи
issues.label_edit=Редакция
issues.label_delete=Изтрий
issues.label.filter_sort.alphabetically=По азбучен ред
issues.label.filter_sort.by_size=Големина
issues.num_participants=%d участника
issues.attachment.open_tab=`Щракнете за да прегледате "%s" в нов раздел`
issues.attachment.download=`Щракнете за да изтеглите "%s"`
issues.start_tracking_short=Начало
issues.stop_tracking=Спиране
issues.add_time_cancel=Отказ
issues.add_time_hours=Часа
issues.add_time_minutes=Минути
issues.cancel_tracking=Отказ
pulls.new=Нова заявка за сливане
pulls.filter_branch=Филтър по клон
@ -367,7 +398,19 @@ wiki.page_already_exists=Страница със същото име вече с
wiki.pages=Страници
wiki.last_updated=Последна модификация на %s
activity.period.filter_label=Период:
activity.period.daily=1 ден
activity.period.halfweekly=3 дни
activity.period.weekly=1 седмица
activity.period.monthly=1 месец
activity.title.user_1=%d потребител
activity.title.user_n=%d потребителя
activity.closed_issue_label=Затворено
activity.new_issue_label=Отворено
activity.unresolved_conv_label=Отваряне
activity.published_release_label=Публикувано
search=Търсене
settings=Настройки
settings.collaboration.write=За писане
@ -389,6 +432,7 @@ settings.transfer=Прехвърли притежание
settings.delete=Изтрий това хранилище
settings.delete_notices_1=- Тази операция <strong>НЕ МОЖЕ</strong> да бъде отменена в последствие.
settings.transfer_owner=Нов притежател
settings.search_user_placeholder=Търсене на потребител…
settings.add_webhook=Добави уеб-кука
settings.webhook.test_delivery=Тестово изпращане
settings.webhook.request=Заявка
@ -402,6 +446,8 @@ settings.update_githook=Запази куката
settings.secret=Тайна
settings.slack_username=Потребителско име
settings.slack_icon_url=URL адрес на икона
settings.discord_username=Потребителско име
settings.discord_icon_url=URL адрес на икона
settings.slack_color=Цвят
settings.event_create=Създаване
settings.event_pull_request=Заявка за сливане
@ -416,6 +462,11 @@ settings.deploy_keys=Ключове за внедряване
settings.add_deploy_key=Добави ключ за внедряване
settings.title=Заглавие
settings.deploy_key_content=Съдържание
settings.branches=Клонове
settings.protected_branch=Защита на клона
settings.add_protected_branch=Включване на защита
settings.delete_protected_branch=Изключване на защита
settings.choose_branch=Изберете клон…
diff.browse_source=Преглед на файлове
diff.parent=родител
@ -424,6 +475,7 @@ diff.show_diff_stats=Покажи статистика за разликите
diff.show_split_view=Разделен изглед
diff.show_unified_view=Обединен изглед
diff.stats_desc=променени са <strong>%d файла</strong>, в които са <strong>добавени %d</strong> реда и са <strong>изтрити %d</strong> реда
diff.bin=Двоични данни
diff.view_file=Целия файл
diff.file_suppressed=Файловите разлики са ограничени, защото са твърде много
diff.too_many_files=Някои файлове не бяха показани, защото твърде много файлове са промени
@ -442,11 +494,17 @@ release.title=Заглавие
release.content=Съдържание
release.write=Редактор
release.preview=Преглед
release.loading=Зарежда се…
release.cancel=Отказ
release.publish=Публикувай версия
release.save_draft=Запис на чернова
release.downloads=Изтегляния
branch.search=Търсене на клонове
branch.delete_head=Изтриване
branch.delete_html=Изтриване на клон
branch.create_from=от '%s'
branch.deleted_by=Изтрито от %s
[org]
@ -458,6 +516,8 @@ people=Участници
teams=Екипи
lower_members=участници
lower_repositories=хранилища
create_new_team=Нов отбор
create_team=Създаване на отбор
org_desc=Описание
team_name=Име на екипа
team_desc=Описание
@ -509,7 +569,6 @@ total=Общо: %d
dashboard.operation_name=Име на операцията
dashboard.operation_switch=Превключи
dashboard.operation_run=Изпълни
dashboard.delete_inactivate_accounts=Изтрий всички неактивни профили
dashboard.server_uptime=Операционно време
dashboard.current_goroutine=Текущи Goroutines
dashboard.current_memory_usage=Текущо използвана памет
@ -546,6 +605,7 @@ users.edit=Редакция
users.auth_source=Начин на удостоверяване
users.local=Локално
orgs.org_manage_panel=Управление на организацията
orgs.name=Име
orgs.teams=Екипи
orgs.members=Участници
@ -556,6 +616,7 @@ repos.private=Частно
repos.watches=Наблюдавания
repos.stars=Харесвания
repos.issues=Задачи
repos.size=Големина
auths.name=Име
auths.type=Тип
@ -580,12 +641,15 @@ auths.allowed_domains=Разрешени домейни
auths.enable_tls=Включи TLS криптиране
auths.skip_tls_verify=Пропусни проверка на TLS сертификат
auths.pam_service_name=Име на PAM услуга
auths.oauth2_profileURL=URL адрес на профила
auths.oauth2_emailURL=Имейл адрес
auths.enable_auto_register=Включи автоматична регистрация
auths.tips=Съвети
config.server_config=Сървърни настройки
config.disable_router_log=Изключи журнал на маршрутизатора
config.run_mode=Режим на изпълнение
config.git_version=Версия на Git
config.repo_root_path=Основен път към хранилища
config.static_file_root_path=Път към статични файлове
config.script_type=Тип на скрипта
@ -700,6 +764,7 @@ push_tag=предаде маркер <a href="%s/src/%s">%[2]s</a> към <a hre
ago=преди %s
from_now=след %s
now=сега
future=в бъдеще
1s=1 секунда
1m=1 минута
1h=1 час
@ -721,6 +786,12 @@ raw_minutes=минути
remove_file=Премахни файл
[notification]
notifications=Известия
unread=Непрочетенo
read=За четене
mark_as_read=Бележа като прочетено
mark_as_unread=Бележа като непрочетено
mark_all_as_read=Бележа всичко като прочетено
[gpg]

View File

@ -508,7 +508,6 @@ total=Celkem: %d
dashboard.operation_name=Název operace
dashboard.operation_switch=Přepnout
dashboard.operation_run=Spustit
dashboard.delete_inactivate_accounts=Smazat všechny neaktivní účty
dashboard.server_uptime=Doba provozu serveru
dashboard.current_goroutine=Aktuální Goroutines
dashboard.current_memory_usage=Aktuální využití paměti

File diff suppressed because it is too large Load Diff

View File

@ -32,16 +32,16 @@ twofa_scratch = Two-Factor Scratch Code
passcode = Passcode
u2f_insert_key = Insert your security key
u2f_sign_in = Press the button on your security key. If you can't find a button, re-insert it.
u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it.
u2f_press_button = Please press the button on your security key…
u2f_use_twofa = Use a two-factor code from your phone
u2f_error = We can't read your security key!
u2f_unsupported_browser = Your browser don't support U2F keys. Please try another browser.
u2f_error_1 = An unknown error occured. Please retry.
u2f_error_2 = Please make sure that you're using an encrypted connection (https://) and visiting the correct URL.
u2f_error_3 = The server could not proceed your request.
u2f_error_4 = The presented key is not eligible for this request. If you try to register it, make sure that the key isn't already registered.
u2f_error_5 = Timeout reached before your key could be read. Please reload to retry.
u2f_error = Could not read your security key.
u2f_unsupported_browser = Your browser does not support U2F security keys.
u2f_error_1 = An unknown error occurred. Please retry.
u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL.
u2f_error_3 = The server could not process your request.
u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered.
u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry.
u2f_reload = Reload
repository = Repository
@ -75,7 +75,7 @@ cancel = Cancel
[install]
install = Installation
title = Initial Configuration
docker_helper = If you run Gitea inside Docker, please read the <a target="_blank" rel="noopener" href="%s">documentation</a> before changing any settings.
docker_helper = If you run Gitea inside Docker, please read the <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> before changing any settings.
requite_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB.
db_title = Database Settings
db_type = Database Type
@ -130,7 +130,7 @@ federated_avatar_lookup = Enable Federated Avatars
federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar.
disable_registration = Disable Self-Registration
disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts.
allow_only_external_registration_popup=Enable the registration only through external services.
allow_only_external_registration_popup = Allow Registration Only Through External Services
openid_signin = Enable OpenID Sign-In
openid_signin_popup = Enable user sign-in via OpenID.
openid_signup = Enable OpenID Self-Registration
@ -463,13 +463,13 @@ then_enter_passcode = And enter the passcode shown in the application:
passcode_invalid = The passcode is incorrect. Try again.
twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
u2f_desc = Security keys are hardware devices containing cryptographic keys. They could be used for two factor authentication. The security key must support the <a href="https://fidoalliance.org/">FIDO U2F</a> standard.
u2f_require_twofa = Two-Factor-Authentication must be enrolled in order to use security keys.
u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" href="https://fidoalliance.org/">FIDO U2F</a> standard.
u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys.
u2f_register_key = Add Security Key
u2f_nickname = Nickname
u2f_press_button = Press the button on your security key to register it.
u2f_delete_key = Remove Security Key
u2f_delete_key_desc= If you remove a security key you cannot login with it anymore. Are you sure?
u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
manage_account_links = Manage Linked Accounts
manage_account_links_desc = These external accounts are linked to your Gitea account.
@ -492,13 +492,13 @@ owner = Owner
repo_name = Repository Name
repo_name_helper = Good repository names use short, memorable and unique keywords.
visibility = Visibility
visiblity_helper = Make Repository Private
visiblity_helper_forced = Your site administrator forces new repositories to be private.
visiblity_fork_helper = (Changing this will affect all forks.)
clone_helper = Need help cloning? Visit <a target="_blank" rel="noopener" href="%s">Help</a>.
visibility_helper = Make Repository Private
visibility_helper_forced = Your site administrator forces new repositories to be private.
visibility_fork_helper = (Changing this will affect all forks.)
clone_helper = Need help cloning? Visit <a target="_blank" rel="noopener noreferrer" href="%s">Help</a>.
fork_repo = Fork Repository
fork_from = Fork From
fork_visiblity_helper = The visibility of a forked repository cannot be changed.
fork_visibility_helper = The visibility of a forked repository cannot be changed.
repo_desc = Description
repo_lang = Language
repo_gitignore_helper = Select .gitignore templates.
@ -613,7 +613,7 @@ editor.directory_is_a_file = Directory name '%s' is already used as a filename i
editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor
editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository.
editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
editor.file_changed_while_editing = The file contents have changed since you started editing. <a target="_blank" rel="noopener noreferrer" href="%s">Click here</a> to see them or <strong>Commit Changes again</strong> to overwrite them.
editor.file_already_exists = A file named '%s' already exists in this repository.
editor.no_changes_to_show = There are no changes to show.
editor.fail_to_update_file = Failed to update/create file '%s' with error: %v
@ -650,7 +650,7 @@ issues.new.open_milestone = Open Milestones
issues.new.closed_milestone = Closed Milestones
issues.new.assignees = Assignees
issues.new.clear_assignees = Clear assignees
issues.new.no_assignees = Nobody assigned
issues.new.no_assignees = No Assignees
issues.no_ref = No Branch/Tag Specified
issues.create = Create Issue
issues.new_label = New Label
@ -781,6 +781,33 @@ issues.due_date_added = "added the due date %s %s"
issues.due_date_modified = "modified the due date to %s from %s %s"
issues.due_date_remove = "removed the due date %s %s"
issues.due_date_overdue = "Overdue"
issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'."
issues.dependency.title = Dependencies
issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies.
issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies.
issues.dependency.add = Add dependency...
issues.dependency.cancel = Cancel
issues.dependency.remove = Remove
issues.dependency.added_dependency = `<a href="%[1]s">%[2]s</a> added a new dependency %[3]s`
issues.dependency.removed_dependency = `<a href="%[1]s">%[2]s</a> removed a dependency %[3]s`
issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues
issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues
issues.dependency.issue_close_blocks = This issue blocks closing of the following issues
issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues
issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it.
issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it.
issues.dependency.blocks_short = Blocks
issues.dependency.blocked_by_short = Depends on
issues.dependency.remove_header = Remove Dependency
issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue?
issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue?
issues.dependency.setting = Enable Dependencies For Issues and Pull Requests
issues.dependency.add_error_same_issue = You cannot make an issue depend on itself.
issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist.
issues.dependency.add_error_dep_not_exist = Dependency does not exist.
issues.dependency.add_error_dep_exists = Dependency already exists.
issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other.
issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository.
pulls.desc = Enable merge requests and code reviews.
pulls.new = New Pull Request
@ -994,7 +1021,7 @@ settings.search_user_placeholder = Search user…
settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator.
settings.user_is_org_member = The user is an organization member who cannot be added as a collaborator.
settings.add_webhook = Add Webhook
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the <a target="_blank" rel="noopener" href="%s">webhooks guide</a>.
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
settings.webhook_deletion = Remove Webhook
settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue?
settings.webhook_deletion_success = The webhook has been removed.
@ -1011,7 +1038,7 @@ settings.githook_edit_desc = If the hook is inactive, sample content will be pre
settings.githook_name = Hook Name
settings.githook_content = Hook Content
settings.update_githook = Update Hook
settings.add_webhook_desc = Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target="_blank" rel="noopener" href="%s">webhooks guide</a>.
settings.add_webhook_desc = Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
settings.payload_url = Target URL
settings.content_type = POST Content Type
settings.secret = Secret
@ -1097,6 +1124,7 @@ settings.protected_branch_deletion_desc = Disabling branch protection allows use
settings.default_branch_desc = Select a default repository branch for pull requests and code commits:
settings.choose_branch = Choose a branch…
settings.no_protected_branch = There are no protected branches.
settings.edit_protected_branch = Edit
diff.browse_source = Browse Source
diff.parent = parent
@ -1167,6 +1195,8 @@ branch.protected_deletion_failed = Branch '%s' is protected. It cannot be delete
topic.manage_topics = Manage Topics
topic.done = Done
topic.count_prompt = You can not select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
[org]
org_name_holder = Organization Name
@ -1271,8 +1301,8 @@ dashboard.operation_switch = Switch
dashboard.operation_run = Run
dashboard.clean_unbind_oauth = Clean unbound OAuth connections
dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted.
dashboard.delete_inactivate_accounts = Delete all inactive accounts
dashboard.delete_inactivate_accounts_success = All inactive accounts have been deleted.
dashboard.delete_inactivate_accounts = Delete all unactivated accounts
dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted.
dashboard.delete_repo_archives = Delete all repository archives
dashboard.delete_repo_archives_success = All repository archives have been deleted.
dashboard.delete_missing_repos = Delete all repositories missing their Git files
@ -1480,7 +1510,7 @@ config.db_path = Path
config.service_config = Service Configuration
config.register_email_confirm = Require Email Confirmation to Register
config.disable_register = Disable Self-Registration
config.allow_only_external_registration = Enable the registration only through external services
config.allow_only_external_registration = Allow Registration Only Through External Services
config.enable_openid_signup = Enable OpenID Self-Registration
config.enable_openid_signin = Enable OpenID Sign-In
config.show_registration_button = Show Register Button
@ -1496,6 +1526,7 @@ config.enable_timetracking = Enable Time Tracking
config.default_enable_timetracking = Enable Time Tracking by Default
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
config.no_reply_address = Hidden Email Domain
config.default_enable_dependencies = Enable Issue Dependencies by Default
config.webhook_config = Webhook Configuration
config.queue_length = Queue Length

View File

@ -1,11 +1,15 @@
app_desc=Un servicio de Git auto alojado y sin complicaciones
home=Inicio
dashboard=Panel de control
explore=Explorar
help=Ayuda
sign_in=Iniciar sesión
sign_in_with=Iniciar sesión con
sign_out=Cerrar sesión
sign_up=Registro
link_account=Vincular Cuenta
link_account_signin_or_signup=Inicia sesión con credenciales existentes para vincular tu cuenta a esta cuenta. O registra una nueva.
register=Registro
website=Página web
version=Versión
@ -13,12 +17,22 @@ page=Página
template=Plantilla
language=Idioma
notifications=Notificaciones
create_new=Crear…
user_profile_and_more=Perfil y ajustes…
signed_in_as=Identificado como
enable_javascript=Este sitio web funciona mejor con JavaScript.
username=Nombre de usuario
email=Correo electrónico
password=Contraseña
re_type=Vuelva a escribir la contraseña
captcha=CAPTCHA
twofa=Autenticación en dos pasos
passcode=Contraseña
u2f_insert_key=Inserte su clave de seguridad
u2f_use_twofa=Use un código de dos factores de su celular
u2f_reload=Recargar
repository=Repositorio
organization=Organización
@ -31,6 +45,9 @@ new_org=Nueva organización
manage_org=Administrar organizaciones
account_settings=Configuraciones de la cuenta
settings=Configuraciones
your_profile=Perfil
your_starred=Destacado
your_settings=Configuración
all=Todos
sources=Fuentes
@ -46,29 +63,63 @@ cancel=Cancelar
[install]
install=Instalación
title=Configuración inicial
requite_db_desc=Gitea requiere una base de datos MySQL, PostgreSQL, MSSQL, SQLite3 o TiDB.
db_title=Configuración de base de datos
db_type=Tipo de base de datos
host=Servidor
user=Nombre de usuario
password=Contraseña
db_name=Nombre de la base de datos
db_helper=Nota para usuarios de la base de datos MySQL: por favor use el motor InnoDB y el esquema de caracteres 'utf8_general_ci'.
ssl_mode=SSL
path=Ruta
general_title=Configuración general
app_name=Título del Sitio
app_name_helper=Puedes colocar aquí el nombre de tu empresa.
repo_path=Ruta del repositorio de Raiz (Root)
run_user=Ejecutar como usuario
domain=Dominio del servidor SSH
log_root_path=Ruta del registro
optional_title=Configuración opcional
email_title=Configuración de Correo
smtp_host=Servidor SMTP
mailer_user=Nombre de usuario SMTP
mailer_password=Contraseña SMTP
offline_mode=Habilitar autenticación Local
disable_gravatar=Desactivar Gravatar
federated_avatar_lookup_popup=Habilitar búsqueda de avatares federador para usar el servicio federado de código abierto basado en libravatar.
enable_captcha=Activar CAPTCHA
enable_captcha_popup=Requerir CAPTCHA para auto-registro de usuario.
require_sign_in_view=Debes iniciar sesión para ver las páginas
admin_password=Contraseña
confirm_password=Confirmar Contraseña
admin_email=Correo electrónico
install_btn_confirm=Instalar Gitea
test_git_failed=Fallo al probar el comando 'git': %v
invalid_db_setting=La configuración de la base de datos no es válida: %v
invalid_repo_path=La ruta de la raíz del repositorio no es válida: %v
run_user_not_match=El nombre de usuario 'ejecutar como' no es el nombre actual de usuario: %s -> %s
save_config_failed=Error al guardar la configuración: %v
invalid_admin_setting=La configuración de la cuenta de administración no es válida: %v
install_success=¡Bienvenido! Gracias por elegir Gitea. ¡Diviértete y cuidate!
invalid_log_root_path=La ruta para los registros no es válida: %v
default_keep_email_private=Ocultar direcciones de correo electrónico por defecto
default_keep_email_private_popup=Ocultar direcciones de correo electrónico de nuevas cuentas de usuario por defecto.
default_allow_create_organization=Permitir la creación de organizaciones por defecto
default_allow_create_organization_popup=Permitir crear organizaciones a las nuevas cuentas de usuario de forma predeterminada.
default_enable_timetracking=Activar el seguimiento de tiempo por defecto
default_enable_timetracking_popup=Activar el seguimiento de tiempo para nuevos repositorios por defecto.
no_reply_address=Dominio de correos electrónicos ocultos
[home]
uname_holder=Nombre de usuario o correo electrónico
password_holder=Contraseña
switch_dashboard_context=Cambiar el contexto del Dashboard
my_repos=Repositorios
show_more_repos=Mostrar más repositorios…
collaborative_repos=Repositorios colaborativos
my_orgs=Mis organizaciones
my_mirrors=Mis réplicas
@ -81,28 +132,46 @@ repos=Repositorios
users=Usuarios
organizations=Organizaciones
search=Buscar
code=Código
repo_no_results=No se ha encontrado ningún repositorio coincidente.
user_no_results=No se ha encontrado ningún usuario coincidente.
org_no_results=No se ha encontrado ninguna organización coincidente.
code_no_results=No se ha encontrado código de fuente que coincida con su término de búsqueda.
code_search_results=Resultados de búsqueda para '%s'
[auth]
create_new_account=Registrar una cuenta
register_helper_msg=¿Ya tienes una cuenta? ¡Inicia sesión!
social_register_helper_msg=¿Ya tienes una cuenta? ¡Enlázala!
disable_register_prompt=Registro deshabilitado. Por favor, póngase en contacto con el administrador del sitio.
disable_register_mail=Correo electrónico de confirmación de registro deshabilitado.
remember_me=Recuérdame
forgot_password_title=He olvidado mi contraseña
forgot_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>. Comprueba tu bandeja de entrada en las siguientes %s para completar el registro.
reset_password_mail_sent_prompt=Un correo de confirmación se ha enviado a <b>%s</b>. Comprueba tu bandeja de entrada en las siguientes %s para completar el reinicio de contraseña.
active_your_account=Activa tu cuenta
prohibit_login=Ingreso prohibido
prohibit_login_desc=Su cuenta tiene prohibido ingresar al sistema. Por favor contacte con el administrador del sistema.
resent_limit_prompt=Ya ha solicitado recientemente un correo de activación. Por favor, espere 3 minutos y vuelva a intentarlo.
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.
resend_mail=Haz click aquí para reenviar tu correo electrónico de activación
email_not_associate=Esta dirección de correo electrónico no esta asociada a ninguna cuenta.
send_reset_mail=Haz clic aquí para reenviar tu email de restauración de contraseña
reset_password=Restablecer su contraseña
invalid_code=Su código de confirmación no es válido o ha caducado.
reset_password_helper=Haga Clic aquí para restablecer su contraseña
non_local_account=Los usuarios no locales no pueden actualizar su contraseña a través de la interfaz web de Gitea.
verify=Verificar
twofa_scratch_used=Ya has utilizado el código. Has sido redirigido a la página de configuración de dos factores poder remover la inscripción del dispositivo o generar un nuevo código.
twofa_scratch_token_incorrect=El código cero es incorrecto.
login_userpass=Iniciar sesión
login_openid=OpenID
openid_connect_submit=Conectar
openid_connect_title=Accede con una cuenta existente
openid_register_title=Crear una nueva cuenta
disable_forgot_password_mail=El restablecimiento de contraseña está desactivado. Por favor, contacte con el administrador del sitio.
[mail]
activate_account=Por favor, active su cuenta
@ -114,12 +183,14 @@ register_notify=¡Bienvenido a Gitea
[modal]
yes=
no=No
modify=Actualizar
[form]
UserName=Nombre de usuario
RepoName=Nombre del repositorio
Email=Dirección de correo electrónico
Password=Contraseña
Retype=Vuelva a escribir la contraseña
SSHTitle=Nombre de la Clave de SSH
HttpsUrl=URL HTTPS
PayloadUrl=URL de carga
@ -135,6 +206,7 @@ TreeName=Ruta del archivo
Content=Contenido
require_error=` no puede estar vacío.`
alpha_dash_error=` solo debe contener caracteres alfanuméricos, guiones medios ('-') y guiones bajos ('_').`
size_error=` debe ser de tamaño %s.`
min_size_error=` debe contener al menos %s caracteres.`
max_size_error=` debe contener como máximo %s caracteres.`
@ -142,7 +214,10 @@ email_error=` no es una dirección de correo válida.`
url_error=` no es una URL válida.`
include_error=` debe contener la subcadena '%s'.`
unknown_error=Error desconocido:
captcha_incorrect=El código CAPTCHA no es correcto.
password_not_match=Las contraseñas no coinciden.
username_been_taken=El nombre de usuario ya está en uso.
user_not_exist=Este usuario no existe.
auth_failed=Autenticación fallo: %v
@ -248,6 +323,7 @@ fork_from=Crear un Fork desde
repo_desc=Descripción
repo_lang=Idioma
license=Licencia
auto_init=Inicializar el repositorio (añade .gitignore, licencia y README)
create_repo=Crear repositorio
default_branch=Rama por defecto
mirror_prune=Purgar
@ -343,8 +419,8 @@ issues.label_templates.helper=Seleccionar un conjunto de etiquetas
issues.label_templates.fail_to_load_file=Error al cargar el archivo de plantilla de etiqueta '%s': %v
issues.add_label_at=añadida la etiqueta <div class="ui label" style="color: %s\; background-color: %s">%s</div> %s
issues.remove_label_at=eliminada la etiqueta <div class="ui label" style="color: %s\; background-color: %s">%s</div> %s
issues.add_milestone_at=`agregado esto al <b>%s</b> hito %s '
issues.change_milestone_at=` modificó el hito de <b>%s</b> to <b>%s</b> %s`
issues.add_milestone_at=`ha añadido esto al hito <b>%s</b> %s '
issues.change_milestone_at=`modificó el hito de <b>%s</b> a <b>%s</b> %s`
issues.remove_milestone_at=`eliminado esto del <b>%s</b> hito %s '
issues.deleted_milestone=`(eliminado)`
issues.self_assign_at=`auto asignado este %s`
@ -411,6 +487,8 @@ issues.attachment.open_tab='Haga clic para ver "%s" en una pestaña nueva'
issues.attachment.download=`Haga clic para descargar "%s"`
issues.subscribe=Suscribir
issues.unsubscribe=Desuscribirse
issues.start_tracking_short=Iniciar
issues.start_tracking_history=`ha empezado a trabajar %s`
issues.tracking_already_started='Ya has comenzado el tiempo de seguimiento en este <a href="%s">tema</a>!'
issues.add_time_hours=Horas
issues.add_time_minutes=Minutos
@ -461,6 +539,8 @@ wiki.page_already_exists=Ya existe una página con el mismo nombre.
wiki.pages=Páginas
wiki.last_updated=Última actualización %s
activity=Actividad
activity.period.filter_label=Periodo:
activity.period.daily=1 día
activity.period.halfweekly=3 días
activity.period.weekly=1 semana
@ -503,6 +583,7 @@ settings.new_owner_has_same_repo=El nuevo propietario tiene un repositorio con e
settings.transfer=Transferir la propiedad
settings.delete=Eliminar este repositorio
settings.delete_notices_1=- Esta operación <strong>NO PUEDE</strong> revertirse.
settings.delete_notices_fork_1=Los forks de este repositorio serán independientes después de eliminarlo.
settings.transfer_owner=Nuevo Propietario
settings.add_webhook=Añadir Webhook
settings.webhook.test_delivery=Test de entrega
@ -531,6 +612,7 @@ settings.deploy_keys=Claves de Despliegue
settings.add_deploy_key=Añadir Clave de Despliegue
settings.title=Título
settings.deploy_key_content=Contenido
settings.protect_merge_whitelist_committers_desc=Permitir a los usuarios o equipos de la lista a fusionar peticiones pull dentro de esta rama.
settings.add_protected_branch=Activar protección
settings.delete_protected_branch=Desactivar protección
@ -574,6 +656,7 @@ branch.create_from=desde '%s'
branch.branch_already_exists=La rama '%s' ya existe en este repositorio.
branch.deleted_by=Eliminada por %s
topic.done=Hecho
[org]
org_name_holder=Nombre de la organización
@ -613,6 +696,7 @@ teams.join=Unirse
teams.leave=Abandonar
teams.read_access=Acceso de Lectura
teams.write_access=Acceso de Escritura
teams.admin_access_helper=Los miembros pueden hacer pull y push a los repositorios del equipo y añadir colaboradores a ellos.
teams.no_desc=Este equipo no tiene descripción
teams.settings=Configuración
teams.members=Miembros del equipo
@ -638,8 +722,7 @@ dashboard.operation_name=Nombre de la operación
dashboard.operation_switch=Interruptor
dashboard.operation_run=Ejecutar
dashboard.clean_unbind_oauth_success=Se han eliminado las conexiones de OAuth no vinculadas.
dashboard.delete_inactivate_accounts=Eliminar todas las cuentas inactivas
dashboard.delete_inactivate_accounts_success=Todas las cuentas inactivas han sido eliminadas.
dashboard.resync_all_sshkeys=Actualizar el archivo '.ssh/authorized_keys' con las claves SSH de Gitea (no es necesario para el servidor SSH incorporado).
dashboard.reinit_missing_repos=Reiniciar todos los repositorios Git faltantes de los que existen registros
dashboard.reinit_missing_repos_success=Todos los repositorios Git faltantes para los que existen registros se han reinicializado.
dashboard.server_uptime=Tiempo de actividad del servidor
@ -859,6 +942,7 @@ file_too_big=El tamaño del archivo ({{filesize}} MB) excede el tamaño máximo
remove_file=Eliminar archivo
[notification]
notifications=Notificaciones
unread=Sin leer
read=Leídas
mark_as_read=Marcar como leído

View File

@ -1,10 +1,15 @@
app_desc=Ongelmaton, itsehostattu Git-palvelu
home=Etusivu
dashboard=Kojelauta
explore=Tutki
help=Apua
sign_in=Kirjaudu sisään
sign_in_with=Kirjaudu sisään tunnuksilla
sign_out=Kirjaudu ulos
sign_up=Rekisteröidy
link_account=Yhdistä tili
link_account_signin_or_signup=Kirjaudu sisään olemassaolevilla tunnuksilla yhdistääksesi tilisi käyttäjätunnukseen. Tai rekisteröi uusi käyttäjätunnus.
register=Rekisteröidy
website=Nettisivut
version=Versio
@ -12,10 +17,18 @@ page=Sivu
template=Malli
language=Kieli
notifications=Ilmoitukset
create_new=Luo…
user_profile_and_more=Profiili ja asetukset…
signed_in_as=Kirjautuneena käyttäjänä
enable_javascript=Tämä sivusto toimii paremmin JavaScriptillä.
username=Käyttäjätunnus
email=Sähköpostiosoite
password=Salasana
re_type=Kirjoita salasana uudelleen
captcha=CAPTCHA
twofa=Kaksivaiheinen todennus
twofa_scratch=Kaksivaiheinen kertakäyttöinen koodi
passcode=Tunnuskoodi
@ -25,11 +38,21 @@ mirror=Peili
new_repo=Uusi repo
new_migrate=Uusi migraatio
new_mirror=Uusi peilaus
new_fork=Uusi repositorio
new_org=Uusi organisaatio
manage_org=Ylläpidä organisaatioita
admin_panel=Sivuston ylläpito
account_settings=Tilin asetukset
settings=Asetukset
your_profile=Profiili
your_starred=Tähdelliset
your_settings=Asetukset
all=Kaikki
sources=Lähteet
mirrors=Peilit
collaborative=Yhteistyössä
forks=Haarat
activities=Toimet
pull_requests=Pull requestit
@ -39,19 +62,59 @@ cancel=Peruuta
[install]
install=Asennus
title=Alkuperäiset asetukset
requite_db_desc=Gitea tarvitsee toimiakseen MySQL-, PostgreSQL-, MSSQL-, SQLite3 tai TiDB-tietokannan.
db_title=Tietokanta asetukset
db_type=Tietokanta tyyppi
host=Isäntä
user=Käyttäjätunnus
password=Salasana
db_name=Tietokannan nimi
db_helper=Huomio MySQL-käyttäjille: käytäthän InnoDB-kantamoottoria ja 'utf8_general_ci'-merkistöä.
ssl_mode=SSL
path=Polku
sqlite_helper=Tiedostopolku SQLite3- tai TiDB-tietokantaan.<br>Kirjoita absoluuttinen polku, jos ajat Giteaa palveluna.
err_empty_db_path=SQLite3- tai TiDB-tietokantapolku ei voi olla tyhjä.
err_invalid_tidb_name=TiDB-tietokannan nimi ei voi sisältää '.'- tai '-'-merkkejä.
no_admin_and_disable_registration=Et voi kytkeä rekisteröintiä pois luomatta sitä ennen ylläpitotiliä.
err_empty_admin_password=Ylläpitäjän salasana ei voi olla tyhjä.
general_title=Yleiset asetukset
app_name=Sivuston otsikko
repo_path=Repon juuren polku
repo_path_helper=Muualla olevat git-repositoriot tullaan tallentamaan tähän kansioon.
lfs_path=Git LFS -juuripolku
lfs_path_helper=Git LFS:n ylläpitämät tiedostot tullaan tallentamaan tähän hakemistoon. Jätä tyhjäksi kytkeäksesi toiminnon pois.
run_user=Aja käyttäjänä
run_user_helper=Anna käyttäjätunnus, jona Giteaa ajetaan. Käyttäjällä on oltava oikeudet repositorioiden juuripolkuun.
domain=SSH-palvelimen osoite (hostname)
domain_helper=Domain tai osoite SSH-klooniosoitteille.
ssh_port=SSH-palvelimen portti
ssh_port_helper=Porttinumero, jossa SSH-palvelimesi kuuntelee. Jätä tyhjäksi kytkeäksesi pois.
http_port=Gitean HTTP-kuunteluportti
http_port_helper=Portti, jossa Gitean web-palvelin kuuntelee.
app_url=Gitean juuriosoite
app_url_helper=Juuriosoite HTTP(S)-klooniosoitteille ja sähköpostimuistutuksille.
log_root_path=Lokin polku
log_root_path_helper=Lokitiedostot kirjoitetaan tähän kansioon.
optional_title=Valinnaiset asetukset
email_title=Sähköpostiasetukset
smtp_host=SMTP isäntä
smtp_from=Lähetä sähköpostit osoitteella
smtp_from_helper=Sähköpostiosoite, jota Gitea käyttää. Kirjoita osoite ”nimi” <email@example.com> -muodossa.
mailer_user=SMTP-käyttäjätunnus
mailer_password=SMTP-salasana
register_confirm=Vaadi sähköpostin vahvistaminen rekisteröintiin
mail_notify=Ota käyttöön sähköpostiilmoitukset
server_service_title=Palvelin ja kolmansien osapuolten palveluiden asetukset
offline_mode=Ota käyttöön lokaali tila
offline_mode_popup=Poista kolmannen osapuolen sisällöstä jakeluverkot ja tarjoa kaikki resurssit paikallisesti.
disable_gravatar=Poista Gravatar käytöstä
disable_gravatar_popup=Poista Gravatar ja kolmannen osapuolen avaratir käytöstä. Oletus-avatar näytetään, ellei käyttäjä ole ladannut omaansa.
federated_avatar_lookup=Käytä ulkopuolisia profiilikuvia
federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar.
disable_registration=Poista rekisteröinti käytöstä
enable_captcha_popup=Pakollinen captcha käyttäjän itse rekisteröityessä.
admin_password=Salasana
confirm_password=Varmista salasana
@ -73,32 +136,69 @@ repos=Repot
users=Käyttäjät
organizations=Organisaatiot
search=Hae
code_no_results=Hakuehtoasi vastaavaa lähdekoodia ei löytynyt.
code_search_results=Hakutulokset: '%s '
[auth]
create_new_account=Rekisteröi tili
register_helper_msg=On jo tili? Kirjaudu sisään nyt!
social_register_helper_msg=Onko sinulla jo tili? Linkitä se nyt!
disable_register_prompt=Rekisteröinti on estetty. Ota yhteys ylläpitäjääsi.
disable_register_mail=Sähköpostivahvistus rekisteröinnille on estetty.
remember_me=Muista minut
forgot_password_title=Unohtuiko salasana
forgot_password=Unohtuiko salasana?
sign_up_now=Tarvitsetko tilin? Rekisteröidy nyt.
confirmation_mail_sent_prompt=Uusi varmistussähköposti on lähetetty osoitteeseen <b>%s</b>, ole hyvä ja tarkista saapuneet seuraavan %s tunnin sisällä saadaksesi rekisteröintiprosessin valmiiksi.
reset_password_mail_sent_prompt=Varmistussähköposti on lähetetty osoitteeseen <b>%s</b>, ole hyvä ja tarkista saapuneet seuraavan %s tunnin sisällä saadaksesi salasananvaihdon valmiiksi.
active_your_account=Aktivoi tilisi
prohibit_login=Kirjautuminen estetty
prohibit_login_desc=Käyttäjätilisi kirjautuminen on estetty. Ota yhteys sivuston ylläpitäjään.
resent_limit_prompt=Olet jo tilannut aktivointisähköpostin hetki sitten. Ole hyvä ja odota 3 minuuttia ja yritä sitten uudelleen.
has_unconfirmed_mail=Hei %s, sinulla on varmistamaton sähköposti osoite (<b>%s</b>). Jos et ole saanut varmistus sähköpostia tai tarvitset uudelleenlähetyksen, ole hyvä ja klikkaa allaolevaa painiketta.
resend_mail=Klikkaa tästä uudelleenlähettääksesi aktivointi sähköpostisi
email_not_associate=Tätä sähköpostiosoitetta ei ole liitetty mihinkään tiliin.
send_reset_mail=Klikkaa tästä (uudelleen) lähettääksesi salasanan nollaussähköpostin
reset_password=Nollaa salasanasi
invalid_code=Vahvistusavain on virheellinen tai vanhentunut.
reset_password_helper=Klikkaa tästä nollataksesi salasanasi
non_local_account=Ei-lokaalit käyttäjät eivät voi päivittää salasanojaan Gitean web-käyttöliittymän kautta.
verify=Vahvista
scratch_code=Kertakäyttökoodi
use_scratch_code=Käytä kertakäyttökoodia
twofa_scratch_used=Olet käyttänyt kertakäyttökoodisi. Sinut on uudelleenohjattu kaksivaiheisen kirjautumisen asetussivulle, jotta voit kytkeä sen pois tai luoda uuden kertakäyttökoodin.
twofa_passcode_incorrect=Salasanasi on väärä. Jos olet hukannut laitteesi, käytäthän kertakäyttökoodia sisäänkirjautumiseen.
twofa_scratch_token_incorrect=Kertakäyttökoodisi on virheellinen.
login_userpass=Kirjaudu sisään
login_openid=OpenID
openid_connect_submit=Connect
openid_connect_title=Yhdistä olemassaolevaan tiliin
openid_connect_desc=Valittu OpenID-osoite on tuntematon. Liitä se uuteen tiliin täällä.
openid_register_title=Luo uusi tili
openid_register_desc=Valittu OpenID-osoite on tuntematon. Liitä se uuteen tiliin täällä.
openid_signin_desc=Anna OpenID-osoitteesi. Esimerkiksi: https://anne.me, bob.openid.org.cn tai gnusocial.net/carry.
disable_forgot_password_mail=Salasanan nollaus on estetty. Ota yhteys ylläpitäjääsi.
[mail]
activate_account=Ole hyvä ja aktivoi tilisi
activate_email=Vahvista sähköpostiosoitteesi
reset_password=Tyhjennä salasana
register_success=Rekisteröinti onnistui
register_notify=Tervetuloa Giteaan
[modal]
yes=Kyllä
no=Ei
modify=Päivitys
[form]
UserName=Käyttäjätunnus
RepoName=Repon nimi
Email=Sähköposti osoite
Password=Salasana
Retype=Kirjoita salasana uudelleen
SSHTitle=SSH avain nimi
HttpsUrl=HTTPS-osoite
TeamName=Tiimin nimi
AuthName=Luvan nimi
AdminEmail=Ylläpito sähköposti
@ -488,7 +588,6 @@ total=Yhteensä: %d
dashboard.operation_name=Toiminnon nimi
dashboard.operation_switch=Vaihda
dashboard.operation_run=Suorita
dashboard.delete_inactivate_accounts=Poista kaikki passiiviset tunnukset
dashboard.server_uptime=Palvelimen Uptime
dashboard.current_goroutine=Nykyiset Goroutinet
dashboard.current_memory_usage=Nykyinen muistinkäyttö

File diff suppressed because it is too large Load Diff

View File

@ -708,8 +708,6 @@ dashboard.operation_switch=Váltás
dashboard.operation_run=Futtatás
dashboard.clean_unbind_oauth=Megszüntetett OAuth kapcsolatok törlése
dashboard.clean_unbind_oauth_success=Az összes megszüntetett OAuth kapcsolat törölve.
dashboard.delete_inactivate_accounts=Minden inaktív fiók törlése
dashboard.delete_inactivate_accounts_success=Minden inaktív fiók törölve.
dashboard.reinit_missing_repos=Az összes Git tároló újra-inicializálása amihez léteznek bejegyzések
dashboard.reinit_missing_repos_success=Az összes Git tároló amihez létezett bejegyzés újra lett iniciaizálva.
dashboard.sync_external_users=Külső felhasználói adatok szinkronizálása

View File

@ -1,11 +1,15 @@
app_desc=Sebuah layanan Git hosting pribadi yang mudah
home=Beranda
dashboard=Dasbor
explore=Jelajahi
help=Bantuan
sign_in=Masuk
sign_in_with=Masuk Dengan
sign_out=Keluar
sign_up=Daftar
link_account=Tautan Akun
link_account_signin_or_signup=Masuk dengan kredensial yang ada untuk menautkan akun anda yang ada ke akun ini atau daftar akun yang baru.
register=Daftar
website=Situs Web
version=Versi
@ -13,12 +17,24 @@ page=Halaman
template=Contoh
language=Bahasa
notifications=Notifikasi
create_new=Buat…
user_profile_and_more=Profil dan Pengaturan…
signed_in_as=Masuk sebagai
enable_javascript=Situs web ini bekerja lebih baik dengan JavaScript.
username=Nama Pengguna
email=Alamat Email
password=Kata Sandi
re_type=Ketik Ulang Kata Sandi
captcha=CAPTCHA
twofa=Otentikasi Dua Faktor
twofa_scratch=Kode Awal Dua Faktor
passcode=Kode Akses
u2f_insert_key=Masukkan kunci keamanan anda
u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda…
u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda
u2f_reload=Muat Ulang
repository=Repositori
organization=Organisasi
@ -29,8 +45,12 @@ new_mirror=Duplikat Baru
new_fork=Fork Repositori Baru
new_org=Organisasi Baru
manage_org=Mengelola Organisasi
admin_panel=Administrasi Situs
account_settings=Pengaturan Akun
settings=Pengaturan
your_profile=Profil
your_starred=Dibintangi
your_settings=Pengaturan
all=Semua
sources=Sumber
@ -46,34 +66,85 @@ cancel=Batal
[install]
install=Pemasangan
title=Konfigurasi Awal
requite_db_desc=Gitea memerlukan MySQL, PostgreSQL, MSSQL, SQLite3 atau TiDB.
db_title=Pengaturan Basis Data
db_type=Tipe Basis Data
host=Host
user=Nama Pengguna
password=Kata Sandi
db_name=Nama Basis Data
db_helper=Catatan untuk pengguna MySQL: Gunakan mesin penyimpanan InnoDB dan karakter set 'utf8_general_ci'.
ssl_mode=SSL
path=Jalur
sqlite_helper=Path berkas untuk basis data SQLite3 atau TiDB.<br>Masukkan path absolut jika anda menjalankan Gitea sebagai layanan.
err_empty_db_path=Path basis data SQLite3 atau TiDB tidak boleh kosong.
err_invalid_tidb_name=Nama basis data TiDB tidak boleh berisi karakter '.' dan '-'.
no_admin_and_disable_registration=Anda tidak dapat menonaktifkan pendaftaran tanpa membuat akun admin.
err_empty_admin_password=Sandi administrator tidak boleh kosong.
general_title=Pengaturan Umum
app_name=Judul Situs
app_name_helper=Anda dapat memasukkan nama perusahaan anda di sini.
repo_path=Path Root Repositori
repo_path_helper=Repositori Git remote akan disimpan ke direktori ini.
lfs_path=Path Akar Git LFS
lfs_path_helper=Berkas yang tersimpan dengan Git LFS akan disimpan ke direktori ini. Biarkan kosong untuk menonaktifkan LFS.
run_user=Jalankan Sebagai Nama Pengguna
run_user_helper=Masukkan nama pengguna sistem operasi yang menjalankan Gitea. Perhatikan bahwa pengguna ini harus memiliki akses ke path akar dari repositori.
domain=SSH Server Domain
domain_helper=Alamat domain atau host untuk URL klon SSH.
ssh_port=Port Server SSH
ssh_port_helper=Nomor port server SSH anda. Biarkan kosong untuk menonaktifkan.
http_port=Port HTTP Gitea
http_port_helper=Nomor port web server dimana Gitea akan berjalan.
app_url=URL Dasar Gitea
app_url_helper=Alamat dasar untuk klon URL HTTP(S) dan pemberitahuan lewat surel.
log_root_path=Path Log
log_root_path_helper=Berkas log akan ditulis ke direktori ini.
optional_title=Pengaturan Opsional
email_title=Pengaturan Surel
smtp_host=Host SMTP
smtp_from=Kirim Surel sebagai
smtp_from_helper=Alamat surel Gitea akan digunakan. Masukkan alamat surel atau gunakan fomat "Nama" <email@example.com>.
mailer_user=Nama Pengguna SMTP
mailer_password=Sandi SMTP
register_confirm=Memerlukan Konfirmasi Surel Untuk Mendaftar
mail_notify=Aktifkan Pemberitahuan Surel
server_service_title=Server dan Pengaturan Layanan Pihak Ketiga
offline_mode=Aktifkan Mode Lokal
offline_mode_popup=Non-aktifkan jaringan pengiriman konten dari pihak ketiga dan layani semua sumber daya secara lokal.
disable_gravatar=Non-aktifkan Gravatar
federated_avatar_lookup_popup=Mengaktifkan pencarian avatar federasi menggunakan Libravatar.
openid_signin=Aktifkan Login OpenID
openid_signup=Aktifkan Pendaftaran OpenID
openid_signup_popup=Aktifkan pendaftaran berdasarkan OpenID.
enable_captcha=Aktifkan CAPTCHA
enable_captcha_popup=Membutukan CAPTCHA untuk pendaftaran.
require_sign_in_view=Anda Harus Login untuk Melihat Halaman
admin_title=Pengaturan Akun Admin
admin_name=Nama Pengguna Admin
admin_password=Kata sandi
confirm_password=Konfirmasi Kata Sandi
admin_email=Alamat Surel
install_btn_confirm=Memasang Gitea
test_git_failed=Tidak dapat menguji perintah 'git': %v
sqlite3_not_available=Gitea versi ini tidak mendukung SQLite3, Silahkan untuh versi biner resmi dari %s (bukan versi 'gobuild').
invalid_db_setting=Pengaturan basis data tidak valid: %v
save_config_failed=Gagal menyimpan konfigurasi: %v
[home]
uname_holder=Nama Pengguna atau Alamat Surel
password_holder=Kata Sandi
switch_dashboard_context=Alihkan Dasbor Konteks
my_repos=Repositori
show_more_repos=Tampilkan repositori lainnya…
collaborative_repos=Repositori Kolaboratif
my_orgs=Organisasi Saya
my_mirrors=Duplikat Saya
view_home=Lihat %s
search_repos=Cari repositori…
issues.in_your_repos=Dalam repositori anda
@ -82,9 +153,16 @@ repos=Repositori
users=Pengguna
organizations=Organisasi
search=Cari
code=Kode
repo_no_results=Tidak ditemukan repositori yang cocok.
org_no_results=Tidak ada organisasi yang cocok ditemukan.
code_no_results=Tidak ada kode sumber yang cocok dengan istilah yang anda cari.
code_search_results=Hasil pencarian untuk '%s'
[auth]
create_new_account=Daftar Akun
register_helper_msg=Sudah memiliki akun? Masuk sekarang!
social_register_helper_msg=Sudah memiliki akun? Hubungkan sekarang!
remember_me=Ingat Saya
forgot_password_title=Lupa Kata Sandi
forgot_password=Lupa kata sandi?
@ -306,14 +384,17 @@ file_permalink=Permalink
stored_lfs=Tersimpan dengan GIT LFS
editor.preview_changes=Tinjau Perubahan
editor.name_your_file=Nama berkas…
editor.or=atau
editor.commit_changes=Perubahan komitmen
editor.add_tmpl=Tambah '%s/<filename>'
editor.add=Menambah '%s'
editor.update=Memperbarui '%s'
editor.delete=Menghapus '%s'
editor.commit_message_desc=Tambahkan deskripsi opsional yang panjang…
editor.commit_directly_to_this_branch=Komitmen langsung ke <strong class="branch-name">%s</strong> cabang.
editor.create_new_branch=Membuat <strong>new branch</strong> untuk tarik komit ini mulai permintaan.
editor.new_branch_name_desc=Nama branch baru…
editor.cancel=Membatalkan
editor.branch_already_exists=Cabang '%s' sudah ada di repositori ini.
editor.no_changes_to_show=Tidak ada perubahan untuk ditampilkan.
@ -406,6 +487,7 @@ issues.edit=Sunting
issues.cancel=Batal
issues.save=Simpan
issues.label_title=Nama label
issues.label_description=Keterangan label
issues.label_color=Warna label
issues.label_count=%d label
issues.label_open_issues=%d masalah terbuka
@ -547,6 +629,7 @@ settings.transfer=Transfer Kepemilikan
settings.delete=Menghapus Repositori Ini
settings.delete_notices_1=- Operasi ini <strong>TIDAK BISA</strong> dibatalkan.
settings.transfer_owner=Pemilik Baru
settings.search_user_placeholder=Cari pengguna…
settings.add_webhook=Tambahkan Webhook
settings.webhook.test_delivery=Percobaan Pengiriman
settings.webhook.request=Permintaan
@ -584,6 +667,7 @@ settings.protected_branch_can_push_yes=Anda dapat mendorong
settings.protected_branch_can_push_no=Anda tidak dapat mendorong
settings.add_protected_branch=Aktifkan perlindungan
settings.delete_protected_branch=Nonaktifkan perlindungan
settings.choose_branch=Pilih branch…
diff.browse_source=Telusuri Sumber
diff.parent=orang tua
@ -612,6 +696,7 @@ release.title=Judul
release.content=Konten
release.write=Menulis
release.preview=Pratinjau
release.loading=Memuat…
release.cancel=Membatalkan
release.publish=Mempublikasikan Rilis
release.save_draft=Simpan Draft
@ -636,6 +721,8 @@ people=Orang
teams=Tim
lower_members=anggota
lower_repositories=repositori
create_new_team=Tim Baru
create_team=Buat Tim Baru
org_desc=Deskripsi
team_name=Nama tim
team_desc=Deskripsi
@ -672,6 +759,7 @@ teams.update_settings=Memperbarui pengaturan
teams.add_team_member=Tambahkan Anggota Tim
teams.delete_team_success=Tim sudah di hapus.
teams.repositories=Tim repositori
teams.search_repo_placeholder=Cari repositori…
teams.add_team_repository=Tambahkan Tim Repositori
teams.remove_repo=Menghapus
teams.add_nonexistent_repo=Repositori yang ingin Anda tambahkan tidak ada; Silahkan buat terlebih dahulu.
@ -692,11 +780,10 @@ dashboard.operation_switch=Beralih
dashboard.operation_run=Lari
dashboard.clean_unbind_oauth=Bersihkan koneksi OAuth yang tidak terikat
dashboard.clean_unbind_oauth_success=Semua koneksi OAuth yang tidak terikat telah dihapus.
dashboard.delete_inactivate_accounts=Hapus semua akun yang tidak aktif
dashboard.delete_inactivate_accounts_success=Semua akun yang tidak aktif telah dihapus.
dashboard.reinit_missing_repos=Menginstal kembali semua repositori Git yang hilang dimana ada catatan
dashboard.reinit_missing_repos_success=Semua repositori Git yang hilang yang catatannya dan telah diinisialisasi ulang.
dashboard.sync_external_users=Sinkronkan data pengguna eksternal
dashboard.git_fsck=Lakukan pemeriksaan kesehatan pada semua repositori
dashboard.server_uptime=Waktu tambahan server
dashboard.current_goroutine=Goroutin saat ini
dashboard.current_memory_usage=Penggunaan memori saat ini
@ -709,6 +796,8 @@ dashboard.heap_memory_idle=Tumpukan memori yang menganggur
dashboard.heap_memory_in_use=Tumpukan memori yang digunakan
dashboard.heap_memory_released=Tumpukan memori dirilis
dashboard.heap_objects=Benda tumpukan
dashboard.bootstrap_stack_usage=Penggunaan bootstrap Stack
dashboard.stack_memory_obtained=Memori Stack Didapat
dashboard.mspan_structures_usage=Penggunaan struktur MSpan
dashboard.mspan_structures_obtained=Struktur MSpan didapatkan
dashboard.mcache_structures_usage=Penggunaan struktur MCache
@ -725,6 +814,7 @@ dashboard.gc_times=Waktu GC
users.activated=Diaktifkan
users.admin=Pengelola
users.repos=Repo
users.created=Dibuat
users.edit=Edit
users.auth_source=Sumber Otentikasi
@ -795,16 +885,24 @@ config.disable_router_log=Menonaktifkan router log
config.run_mode=Jalankan mode
config.git_version=Versi Git
config.repo_root_path=Jalur akar repositori
config.lfs_root_path=Path Root LFS
config.static_file_root_path=Jalur akar berkas statis
config.script_type=Jenis skrip
config.reverse_auth_user=Mengembalikan pengguna otentikasi
config.ssh_config=Konfigurasi SSH
config.ssh_enabled=Aktif
config.ssh_port=Port
config.ssh_listen_port=Listen Port
config.ssh_root_path=Path Induk
config.ssh_key_test_path=Path Key Test
config.ssh_keygen_path=Path Keygen ('ssh-keygen')
config.ssh_minimum_key_size_check=Periksa ukuran kunci minimum
config.ssh_minimum_key_sizes=Ukuran kunci minimum
config.db_config=Konfigurasi basis data
config.db_type=Tipe
config.db_host=Host
config.db_name=Nama
config.db_path=Jalur

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More