diff --git a/.drone.yml b/.drone.yml index c07179b95d..1cddc80d40 100644 --- a/.drone.yml +++ b/.drone.yml @@ -66,12 +66,24 @@ steps: image: golang:1.11 # this step is kept as the lowest version of golang that we support environment: GO111MODULE: on + GOPROXY: off commands: - go build -mod=vendor -o gitea_no_gcc # test if build succeeds without the sqlite tag + - name: build-linux-386 + pull: always + image: golang:1.13 + environment: + GO111MODULE: on + GOPROXY: off + GOOS: linux + GOARCH: 386 + commands: + - go build -mod=vendor -o gitea_linux_386 # test if compatible with 32 bit + - name: build pull: always - image: golang:1.12 + image: golang:1.13 commands: - make clean - make generate @@ -82,14 +94,17 @@ steps: - make test-vendor - make build environment: + GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not + GOSUMDB: sum.golang.org TAGS: bindata sqlite sqlite_unlock_notify - name: unit-test pull: always - image: golang:1.12 + image: golang:1.13 commands: - make unit-test-coverage environment: + GOPROXY: off TAGS: bindata sqlite sqlite_unlock_notify depends_on: - build @@ -102,10 +117,11 @@ steps: - name: release-test pull: always - image: golang:1.12 + image: golang:1.13 commands: - make test environment: + GOPROXY: off TAGS: bindata sqlite sqlite_unlock_notify depends_on: - build @@ -129,10 +145,11 @@ steps: - name: tag-test pull: always - image: golang:1.12 + image: golang:1.13 commands: - make test environment: + GOPROXY: off TAGS: bindata depends_on: - tag-pre-condition @@ -142,26 +159,28 @@ steps: - name: test-sqlite pull: always - image: golang:1.12 + image: golang:1.13 commands: - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" - apt-get install -y git-lfs - timeout -s ABRT 20m make test-sqlite-migration - timeout -s ABRT 20m make test-sqlite environment: + GOPROXY: off TAGS: bindata depends_on: - build - name: test-mysql pull: always - image: golang:1.12 + image: golang:1.13 commands: - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" - apt-get install -y git-lfs - make test-mysql-migration - make integration-test-coverage environment: + GOPROXY: off TAGS: bindata TEST_LDAP: 1 depends_on: @@ -175,13 +194,14 @@ steps: - name: tag-test-mysql pull: always - image: golang:1.12 + image: golang:1.13 commands: - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" - apt-get install -y git-lfs - timeout -s ABRT 20m make test-mysql-migration - timeout -s ABRT 20m make test-mysql environment: + GOPROXY: off TAGS: bindata TEST_LDAP: 1 depends_on: @@ -192,13 +212,14 @@ steps: - name: test-mysql8 pull: always - image: golang:1.12 + image: golang:1.13 commands: - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" - apt-get install -y git-lfs - timeout -s ABRT 20m make test-mysql8-migration - timeout -s ABRT 20m make test-mysql8 environment: + GOPROXY: off TAGS: bindata TEST_LDAP: 1 depends_on: @@ -206,13 +227,14 @@ steps: - name: test-pgsql pull: always - image: golang:1.12 + image: golang:1.13 commands: - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" - apt-get install -y git-lfs - timeout -s ABRT 20m make test-pgsql-migration - timeout -s ABRT 20m make test-pgsql environment: + GOPROXY: off TAGS: bindata TEST_LDAP: 1 depends_on: @@ -220,13 +242,14 @@ steps: - name: test-mssql pull: always - image: golang:1.12 + image: golang:1.13 commands: - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" - apt-get install -y git-lfs - make test-mssql-migration - make test-mssql environment: + GOPROXY: off TAGS: bindata TEST_LDAP: 1 depends_on: @@ -234,10 +257,11 @@ steps: - name: generate-coverage pull: always - image: golang:1.12 + image: golang:1.13 commands: - make coverage environment: + GOPROXY: off TAGS: bindata depends_on: - unit-test @@ -370,6 +394,7 @@ steps: - make generate - make release environment: + GOPROXY: off TAGS: bindata sqlite sqlite_unlock_notify - name: gpg-sign @@ -455,7 +480,6 @@ trigger: depends_on: - testing - - translations steps: - name: fetch-tags @@ -472,6 +496,7 @@ steps: - make generate - make release environment: + GOPROXY: off TAGS: bindata sqlite sqlite_unlock_notify - name: gpg-sign @@ -561,7 +586,7 @@ steps: --- kind: pipeline -name: docker +name: docker-linux-amd64 platform: os: linux @@ -571,6 +596,15 @@ workspace: base: /go path: src/code.gitea.io/gitea +depends_on: + - testing + +trigger: + ref: + - refs/heads/master + - "refs/tags/**" + - "refs/pull/**" + steps: - name: fetch-tags pull: default @@ -584,56 +618,129 @@ steps: - name: dryrun pull: always - image: plugins/docker:18.09 + image: plugins/docker:linux-amd64 settings: - cache_from: gitea/gitea dry_run: true repo: gitea/gitea + tags: linux-amd64 + build_args: + - GOPROXY=off when: event: - pull_request - - name: release + - name: publish pull: always - image: plugins/docker:18.09 + image: plugins/docker:linux-amd64 settings: - cache_from: gitea/gitea + auto_tag: true + auto_tag_suffix: linux-amd64 repo: gitea/gitea - tags: - - "${DRONE_BRANCH##release/v}" - environment: - DOCKER_PASSWORD: + build_args: + - GOPROXY=off + password: from_secret: docker_password - DOCKER_USERNAME: + username: from_secret: docker_username - depends_on: - - dryrun when: - branch: - - "release/*" event: - - push + exclude: + - pull_request - - name: latest - pull: always - image: plugins/docker:18.09 - settings: - cache_from: gitea/gitea - default_tags: true - repo: gitea/gitea - environment: - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - depends_on: - - dryrun + + +--- +kind: pipeline +name: docker-linux-arm64 + +platform: + os: linux + arch: arm64 + +workspace: + base: /go + path: src/code.gitea.io/gitea + +depends_on: + - testing + +trigger: + ref: + - refs/heads/master + - "refs/tags/**" + - "refs/pull/**" + +steps: + - name: fetch-tags + pull: default + image: docker:git + commands: + - git fetch --tags --force when: - branch: - - master event: - - push - - tag + exclude: + - pull_request + + - name: dryrun + pull: always + image: plugins/docker:linux-arm64 + settings: + dry_run: true + repo: gitea/gitea + tags: linux-arm64 + build_args: + - GOPROXY=off + when: + event: + - pull_request + + - name: publish + pull: always + image: plugins/docker:linux-arm64 + settings: + auto_tag: true + auto_tag_suffix: linux-arm64 + repo: gitea/gitea + build_args: + - GOPROXY=off + password: + from_secret: docker_password + username: + from_secret: docker_username + when: + event: + exclude: + - pull_request + +--- +kind: pipeline +name: docker-manifest + +platform: + os: linux + arch: amd64 + +steps: + - name: manifest + pull: always + image: plugins/manifest + settings: + auto_tag: true + ignore_missing: true + spec: docker/manifest.tmpl + password: + from_secret: docker_password + username: + from_secret: docker_username + +trigger: + ref: + - refs/heads/master + - "refs/tags/**" + +depends_on: + - docker-linux-amd64 + - docker-linux-arm64 --- kind: pipeline @@ -657,7 +764,9 @@ depends_on: - translations - release-version - release-master - - docker + - docker-linux-amd64 + - docker-linux-arm64 + - docker-manifest - docs steps: diff --git a/.eslintrc b/.eslintrc index 41cad889b1..c7013f2785 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,3 +23,5 @@ globals: rules: no-unused-vars: [error, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, ignoreRestSiblings: true}] + prefer-const: [2, {destructuring: all}] + no-var: [2] diff --git a/.golangci.yml b/.golangci.yml index 82d0e46694..9c78a83451 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -86,12 +86,6 @@ issues: - path: models/issue_comment_list.go linters: - dupl - # "Destroy" is misspelled in github.com/go-macaron/session/session.go:213 so it's not our responsability to fix it - - path: modules/session/virtual.go - linters: + - linters: - misspell - text: '`Destory` is a misspelling of `Destroy`' - - path: modules/session/memory.go - linters: - - misspell - text: '`Destory` is a misspelling of `Destroy`' + text: '`Unknwon` is a misspelling of `Unknown`' diff --git a/CHANGELOG.md b/CHANGELOG.md index 790263d641..540b1a9790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,71 @@ 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.9.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.9.0-rc1) - 2019-07-06 +## [1.9.3](https://github.com/go-gitea/gitea/releases/tag/v1.9.3) - 2019-09-06 +* BUGFIXES + * Fix go get from a private repository with Go 1.13 (#8100) + * Strict name matching for Repository.GetTagID() (#8082) + * Avoid ambiguity of branch/directory names for the git-diff-tree command (#8070) + * Add change title notification for issues (#8064) + * Run CORS handler first for /api routes (#7967) (#8053) + * Evaluate emojis in commit messages in list view (#8044) + * Fix failed to synchronize tags to releases for repository (#7990) (#7994) + * Fix adding default Telegram webhook (#7972) (#7992) + * Abort synchronization from LDAP source if there is some error (#7965) + * Fix deformed emoji in commit message (#8071) +* ENHANCEMENT + * Keep blame view buttons sequence consistent with normal view when viewing a file (#8007) (#8009) + +## [1.9.2](https://github.com/go-gitea/gitea/releases/tag/v1.9.2) - 2019-08-22 +* BUGFIXES + * Fix wrong sender when send slack webhook (#7918) (#7924) + * Upload support text/plain; charset=utf8 (#7899) + * Lfs/lock: round locked_at timestamp to second (#7872) (#7875) + * Fix non existent milestone with 500 error (#7867) (#7873) +* SECURITY + * Fix No PGP signature on 1.9.1 tag (#7874) + * Release built with go 1.12.9 to fix security fixes in golang std lib, ref: https://groups.google.com/forum/#!msg/golang-announce/oeMaeUnkvVE/a49yvTLqAAAJ +* ENHANCEMENT + * Fix pull creation with empty changes (#7920) (#7926) +* BUILD + * Drone/docker: prepare multi-arch release + provide arm64 image (#7571) (#7884) + +## [1.9.1](https://github.com/go-gitea/gitea/releases/tag/v1.9.1) - 2019-08-14 +* BREAKING + * Add pagination for admin api get orgs and fix only list public orgs bug (#7742) (#7752) +* SECURITY + * Be more strict with git arguments (#7715) (#7762) + * Release built with go 1.12.8 to fix security fixes in golang std lib, ref: https://groups.google.com/forum/#!topic/golang-nuts/fCQWxqxP8aA +* BUGFIXES + * Fix local runs of ssh-requiring integration tests (#7855) (#7857) + * Fix hook problem (#7856) (#7754) + * Use .ExpiredUnix.IsZero to display green color of forever valid gpg key (#7850) (#7846) + * Do not fetch all refs (#7797) (#7837) + * Fix duplicate call of webhook (#7824) (#7821) + * Enable switching to a different source branch when PR already exists (#7823) + * Rewrite existing repo units if setting is not included in api body (#7811) + * Prevent Commit Status and Message From Overflowing On Branch Page (#7800) (#7808) + * API: fix multiple bugs with statuses endpoints (Backport #7785) (#7807) + * Fix Slack webhook fork message (1.9 release backport) (#7783) + * Fix approvals counting (#7757) (#7777) + * Fix rename failed when rewrite public keys (#7761) (#7769) + * Fix dropTableColumns sqlite implementation (#7710) (#7765) + * Fix repo_index_status lingering when deleting a repository (#7738) + * Fix milestone completness calculation when migrating (#7725) (#7732) + * Fixes indexed repos keeping outdated indexes when files grow too large (#7731) + * Skip non-regular files (e.g. submodules) on repo indexing (#7717) + * Improve branches list performance and fix protected branch icon when no-login (#7695) (#7704) + * Correct wrong datetime format for git (#7689) (#7690) + +## [1.9.0](https://github.com/go-gitea/gitea/releases/tag/v1.9.0) - 2019-07-30 * BREAKING * Better logging (#6038) (#6095) +* SECURITY + * Shadow the password on cache and session config on admin panel (#7300) + * Fix markdown invoke sequence (#7513) (#7560) + * Reserve .well-known username (#7638) + * Do not leak secrets via timing side channel (#7364) + * Ensure that decryption of cookie actually suceeds (#7363) * FEATURE * Content API for Creating, Updating, Deleting Files (#6314) * Enable tls-alpn-01: Use certmanager provided TLSConfig for LetsEncrypt (#7229) @@ -29,6 +91,39 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Implement Default Webhooks (#4299) * Telegram webhook (#4227) * BUGFIXES + * Send webhook after commit when creating issue with assignees (#7681) (#7684) + * Upgrade macaron/captcha to fix random error problem (#7407) (#7683) + * Move add to hook queue for created repo to outside xorm session. (#7682) (#7675) + * Show protection symbol if needed on default branch (#7660) (#7668) + * Hide delete/restore button on archived repos (#7660) + * Fix bug on migrating milestone from github (#7665) (#7666) + * Use flex to fix floating paginate (#7656) (#7662) + * Change length of some repository's columns (#7652) (#7655) + * Fix wrong email when use gitea as OAuth2 provider (#7640) (#7647) + * Fix syntax highlight initialization (#7617) (#7626) + * Fix bug create/edit wiki pages when code master branch protected (#7580) (#7623) + * Fix panic on push at #7611 (#7615) (#7618) + * Handle ErrUserProhibitLogin in http git (#7586, #7591) (#7590) + * Fix color of split-diff view in dark theme (#7587) (#7589) + * Fix file header overflow in file and blame views (#7562) (#7579) + * Malformed URLs in API git/commits response (#7565) (#7567) + * Fix empty commits now showing in repo overview (#7521) (#7563) + * Fix repository's pull request count error (#7518) (#7524) + * Remove duplicated webhook trigger (#7511) (#7516) + * Handles all redirects for Web UI File CRUD (#7478) (#7507) + * Fix regex for issues in commit messages (#7444) (#7466) + * cmd/serv: actually exit after fatal errors (#7458) (#7460) + * Fix an issue with some pages throwing 'not defined' js exceptions #7450 (#7453) + * Fix Dropzone.js integration (#7445) (#7448) + * Create class for inline positioned lists (#7439) (#7393) + * Diff: Fix indentation on unhighlighted code (#7435) (#7443) + * jQuery 3 (#7442) (#7425) + * Only show "New Pull Request" button if repo allows pulls (#7426) (#7432) + * Fix vendor references (#7394) (#7396) + * Only return head: null if source branch was deleted (#6705) (#7376) + * Add missing template variable on organisation settings (#7386) (#7385) + * Fix post parameter on issue list which had unset assignee (#7380) (#7383) + * Fix migration tests due to issue 7 being resolved (#7375) (#7381) * Correctly adjust mirror url (#6593) * Handle early git version's lack of get-url (#7065) * Fix icon position in issue view (#7354) @@ -166,6 +261,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Disable benchmarking during tag events on DroneIO (#6365) * Comments list performance optimization (#5305) * ENHANCEMENT + * Update Drone docker generation to standard format (#7480) (#7496) (#7504) * Add API Endpoint for Repo Edit (#7006) * Add state param to milestone listing API (#7131) * Make captcha and password optional for external accounts (#6606) @@ -285,8 +381,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io). * Refactor: append, build variable and type switch (#4940) * Git statistics in Activity tab (#4724) * Drop the bits argument when generating an ed25519 key (#6504) -* SECURITY - * Shadow the password on cache and session config on admin panel (#7300) * TESTING * Exclude pull_request from fetch-tags step, fixes #7108 (#7120) * Refactor and improve git test (#7086) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82ab83fed5..95799e92b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ - [Translation](#translation) - [Code review](#code-review) - [Styleguide](#styleguide) - - [Sign-off your work](#sign-off-your-work) + - [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco) - [Release Cycle](#release-cycle) - [Maintainers](#maintainers) - [Owners](#owners) @@ -77,7 +77,7 @@ Here's how to run the test suite: creates (a default AWS or GCE disk size won't work -- see [#6243](https://github.com/go-gitea/gitea/issues/6243)). - Change into the base directory of your copy of the gitea repository, - and run `drone exec --local --build-event pull_request`. + and run `drone exec --event pull_request`. The drone version, command line, and disk requirements do change over time (see [#4053](https://github.com/go-gitea/gitea/issues/4053) and @@ -157,22 +157,21 @@ import ( ) ``` -## Sign-off your work +## Developer Certificate of Origin (DCO) -The sign-off is a simple line at the end of the explanation for the -patch. Your signature certifies that you wrote the patch or otherwise -have the right to pass it on as an open-source patch. The rules are -pretty simple: If you can certify [DCO](DCO), then you just add a line -to every git commit message: +We consider the act of contributing to the code by submitting a Pull +Request as the "Sign off" or agreement to the certifications and terms +of the [DCO](DCO) and [MIT license](LICENSE). No further action is required. +Additionally you could add a line at the end of your commit message. ``` Signed-off-by: Joe Smith ``` -Please use your real name; we really dislike pseudonyms or anonymous -contributions. We are in the open-source world without secrets. If you -set your `user.name` and `user.email` git configs, you can sign-off your -commit automatically with `git commit -s`. +If you set your `user.name` and `user.email` git configs, you can add the +line to the end of your commit automatically with `git commit -s`. + +We assume in good faith that the information you provide is legally binding. ## Release Cycle diff --git a/Dockerfile b/Dockerfile index f13fdbe55f..cb1b2f71e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,10 @@ ################################### #Build stage -FROM golang:1.12-alpine3.10 AS build-env +FROM golang:1.13-alpine3.10 AS build-env + +ARG GOPROXY +ENV GOPROXY ${GOPROXY:-direct} ARG GITEA_VERSION ARG TAGS="sqlite sqlite_unlock_notify" diff --git a/MAINTAINERS b/MAINTAINERS index b97a452db6..bf657fabe2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -30,3 +30,6 @@ John Olheiser (@jolheiser) Richard Mahn (@richmahn) Mrsdizzie (@mrsdizzie) silverwind (@silverwind) +Gary Kim (@gary-kim) +Guillermo Prandi (@guillep2k) +Mura Li (@typeless) diff --git a/Makefile b/Makefile index 2c6a6ef72e..b881bc9553 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ BINDATA := modules/{options,public,templates}/bindata.go GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go") GOFMT ?= gofmt -s -GOFLAGS := -i -v +GOFLAGS := -v EXTRA_GOFLAGS ?= MAKE_VERSION := $(shell make -v | head -n 1) @@ -41,13 +41,15 @@ endif LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list ./... | grep -v /vendor/))) +PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell GO111MODULE=on $(GO) list -mod=vendor ./... | grep -v /vendor/))) SOURCES ?= $(shell find . -name "*.go" -type f) TAGS ?= TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'gitea-temp') +#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.20.1 +SWAGGER := GO111MODULE=on $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl}}/api/v1"|g SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl}}/api/v1"|"basePath": "/api/v1"|g @@ -101,10 +103,7 @@ generate: .PHONY: generate-swagger generate-swagger: - @hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - GO111MODULE="on" $(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.19.0; \ - fi - swagger generate spec -o './$(SWAGGER_SPEC)' + $(SWAGGER) generate spec -o './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' $(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)' @@ -119,11 +118,8 @@ swagger-check: generate-swagger .PHONY: swagger-validate swagger-validate: - @hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \ - fi $(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)' - swagger validate './$(SWAGGER_SPEC)' + $(SWAGGER) validate './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' .PHONY: errcheck @@ -208,59 +204,84 @@ test-sqlite\#%: integrations.sqlite.test test-sqlite-migration: migrations.sqlite.test GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test -generate-ini: +generate-ini-mysql: sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \ -e 's|{{TEST_MYSQL_DBNAME}}|${TEST_MYSQL_DBNAME}|g' \ -e 's|{{TEST_MYSQL_USERNAME}}|${TEST_MYSQL_USERNAME}|g' \ -e 's|{{TEST_MYSQL_PASSWORD}}|${TEST_MYSQL_PASSWORD}|g' \ integrations/mysql.ini.tmpl > integrations/mysql.ini + +.PHONY: test-mysql +test-mysql: integrations.mysql.test generate-ini-mysql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test + +.PHONY: test-mysql\#% +test-mysql\#%: integrations.mysql.test generate-ini-mysql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.run $* + +.PHONY: test-mysql-migration +test-mysql-migration: migrations.mysql.test generate-ini-mysql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./migrations.mysql.test + + +generate-ini-mysql8: sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \ -e 's|{{TEST_MYSQL8_DBNAME}}|${TEST_MYSQL8_DBNAME}|g' \ -e 's|{{TEST_MYSQL8_USERNAME}}|${TEST_MYSQL8_USERNAME}|g' \ -e 's|{{TEST_MYSQL8_PASSWORD}}|${TEST_MYSQL8_PASSWORD}|g' \ integrations/mysql8.ini.tmpl > integrations/mysql8.ini + +.PHONY: test-mysql8 +test-mysql8: integrations.mysql8.test generate-ini-mysql8 + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test + +.PHONY: test-mysql8\#% +test-mysql8\#%: integrations.mysql8.test generate-ini-mysql8 + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test -test.run $* + +.PHONY: test-mysql8-migration +test-mysql8-migration: migrations.mysql8.test generate-ini-mysql8 + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./migrations.mysql8.test + + +generate-ini-pgsql: sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \ -e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \ -e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \ -e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \ integrations/pgsql.ini.tmpl > integrations/pgsql.ini + +.PHONY: test-pgsql +test-pgsql: integrations.pgsql.test generate-ini-pgsql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test + +.PHONY: test-pgsql\#% +test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.run $* + +.PHONY: test-pgsql-migration +test-pgsql-migration: migrations.pgsql.test generate-ini-pgsql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./migrations.pgsql.test + + +generate-ini-mssql: sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \ -e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \ -e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \ -e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \ integrations/mssql.ini.tmpl > integrations/mssql.ini -.PHONY: test-mysql -test-mysql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test - -.PHONY: test-mysql-migration -test-mysql-migration: migrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./migrations.test - -.PHONY: test-mysql8 -test-mysql8: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./integrations.test - -.PHONY: test-mysql8-migration -test-mysql8-migration: migrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql8.ini ./migrations.test - -.PHONY: test-pgsql -test-pgsql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test - -.PHONY: test-pgsql-migration -test-pgsql-migration: migrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./migrations.test - .PHONY: test-mssql -test-mssql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.test +test-mssql: integrations.mssql.test generate-ini-mssql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test + +.PHONY: test-mssql\#% +test-mssql\#%: integrations.mssql.test generate-ini-mssql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.run $* .PHONY: test-mssql-migration -test-mssql-migration: migrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./migrations.test +test-mssql-migration: migrations.mssql.test generate-ini-mssql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test .PHONY: bench-sqlite @@ -268,24 +289,33 @@ bench-sqlite: integrations.sqlite.test GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: bench-mysql -bench-mysql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . +bench-mysql: integrations.mysql.test generate-ini-mysql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: bench-mssql -bench-mssql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . +bench-mssql: integrations.mssql.test generate-ini-mssql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: bench-pgsql -bench-pgsql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . +bench-pgsql: integrations.pgsql.test generate-ini-pgsql + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: integration-test-coverage -integration-test-coverage: integrations.cover.test generate-ini +integration-test-coverage: integrations.cover.test generate-ini-mysql GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out -integrations.test: $(SOURCES) - GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.test +integrations.mysql.test: $(SOURCES) + GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql.test + +integrations.mysql8.test: $(SOURCES) + GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mysql8.test + +integrations.pgsql.test: $(SOURCES) + GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.pgsql.test + +integrations.mssql.test: $(SOURCES) + GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.mssql.test integrations.sqlite.test: $(SOURCES) GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags 'sqlite sqlite_unlock_notify' @@ -293,9 +323,21 @@ integrations.sqlite.test: $(SOURCES) integrations.cover.test: $(SOURCES) GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(PACKAGES) | tr ' ' ',') -o integrations.cover.test -.PHONY: migrations.test -migrations.test: $(SOURCES) - $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.test +.PHONY: migrations.mysql.test +migrations.mysql.test: $(SOURCES) + $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql.test + +.PHONY: migrations.mysql8.test +migrations.mysql8.test: $(SOURCES) + $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.mysql8.test + +.PHONY: migrations.pgsql.test +migrations.pgsql.test: $(SOURCES) + $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.pgsql.test + +.PHONY: migrations.mssql.test +migrations.mssql.test: $(SOURCES) + $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.mssql.test .PHONY: migrations.sqlite.test migrations.sqlite.test: $(SOURCES) @@ -473,6 +515,6 @@ pr: golangci-lint: @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ export BINARY="golangci-lint"; \ - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.16.0; \ + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.18.0; \ fi - golangci-lint run + golangci-lint run --deadline=3m diff --git a/README.md b/README.md index c16eb87607..92ed78a497 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ [![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest) [![Help Contribute to Open Source](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea) -[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea) +[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) ## Purpose diff --git a/README_ZH.md b/README_ZH.md index c22bcba7ed..e143f23b41 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -3,13 +3,14 @@ # Gitea - Git with a cup of tea [![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea) -[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ) +[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/gitea) [![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") [![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea) [![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest) -[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea) +[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) ## 目标 @@ -25,7 +26,7 @@ Gitea 的首要目标是创建一个极易安装,运行非常快速,安装 ## 文档 -关于如何安装请访问我们的 [文档站](https://docs.gitea.io/zh-cn/),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/NsatcWJ) 和 QQ群 328432459 来和我们交流。 +关于如何安装请访问我们的 [文档站](https://docs.gitea.io/zh-cn/),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。 ## 贡献流程 diff --git a/cmd/cmd.go b/cmd/cmd.go index 5a55ac318c..d05eb8b1a2 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -38,7 +38,7 @@ func initDB() error { func initDBDisableConsole(disableConsole bool) error { setting.NewContext() - models.LoadConfigs() + setting.InitDBConfig() setting.NewXORMLogService(disableConsole) if err := models.SetEngine(); err != nil { diff --git a/cmd/convert.go b/cmd/convert.go index cb0510c722..23a3d8dbe9 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -31,9 +31,9 @@ func runConvert(ctx *cli.Context) error { log.Trace("AppWorkPath: %s", setting.AppWorkPath) log.Trace("Custom path: %s", setting.CustomPath) log.Trace("Log path: %s", setting.LogRootPath) - models.LoadConfigs() + setting.InitDBConfig() - if models.DbCfg.Type != "mysql" { + if !setting.Database.UseMySQL { fmt.Println("This command can only be used with a MySQL database") return nil } diff --git a/cmd/dump.go b/cmd/dump.go index dd1123a254..1bf6901769 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -17,8 +17,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" + "github.com/unknwon/cae/zip" + "github.com/unknwon/com" "github.com/urfave/cli" ) @@ -58,7 +58,6 @@ It can be used for backup and capture Gitea server image to send to maintainer`, func runDump(ctx *cli.Context) error { setting.NewContext() setting.NewServices() // cannot access session settings otherwise - models.LoadConfigs() err := models.SetEngine() if err != nil { @@ -104,8 +103,8 @@ func runDump(ctx *cli.Context) error { } targetDBType := ctx.String("database") - if len(targetDBType) > 0 && targetDBType != models.DbCfg.Type { - log.Printf("Dumping database %s => %s...", models.DbCfg.Type, targetDBType) + if len(targetDBType) > 0 && targetDBType != setting.Database.Type { + log.Printf("Dumping database %s => %s...", setting.Database.Type, targetDBType) } else { log.Printf("Dumping database...") } diff --git a/cmd/hook.go b/cmd/hook.go index ca876f02a3..f5b7962aab 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -96,6 +96,7 @@ func runHookPreReceive(c *cli.Context) error { UserID: userID, GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories), GitObjectDirectory: os.Getenv(private.GitObjectDirectory), + GitQuarantinePath: os.Getenv(private.GitQuarantinePath), ProtectedBranchID: prID, }) switch statusCode { diff --git a/cmd/migrate.go b/cmd/migrate.go index dde50a455f..1fa1d09e25 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -30,7 +30,7 @@ func runMigrate(ctx *cli.Context) error { log.Trace("AppWorkPath: %s", setting.AppWorkPath) log.Trace("Custom path: %s", setting.CustomPath) log.Trace("Log path: %s", setting.LogRootPath) - models.LoadConfigs() + setting.InitDBConfig() if err := models.NewEngine(migrations.Migrate); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) diff --git a/cmd/serv.go b/cmd/serv.go index 32dd8cbd3e..6533b0371c 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -22,8 +22,8 @@ import ( "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/dgrijalva/jwt-go" + "github.com/unknwon/com" "github.com/urfave/cli" ) @@ -73,7 +73,6 @@ func fail(userMessage, logMessage string, args ...interface{}) { if !setting.ProdMode { fmt.Fprintf(os.Stderr, logMessage+"\n", args...) } - return } os.Exit(1) @@ -110,7 +109,7 @@ func runServ(c *cli.Context) error { if key.Type == models.KeyTypeDeploy { println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.") } else { - println("Hi there: " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.") + println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.") } println("If this is unexpected, please log in with password and setup Gitea under another user.") return nil diff --git a/cmd/web.go b/cmd/web.go index d8bcba76d1..9a5ce5d2b6 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -18,8 +18,8 @@ import ( "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/routes" - "github.com/Unknwon/com" context2 "github.com/gorilla/context" + "github.com/unknwon/com" "github.com/urfave/cli" "golang.org/x/crypto/acme/autocert" ini "gopkg.in/ini.v1" diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index 4b39c8e9f2..490d6760c8 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -27,9 +27,9 @@ import ( "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/routes" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" context2 "github.com/gorilla/context" + "github.com/unknwon/com" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/plumbing" @@ -79,16 +79,16 @@ func runPR() { setting.CheckLFSVersion() //models.LoadConfigs() /* - models.DbCfg.Type = "sqlite3" - models.DbCfg.Path = ":memory:" - models.DbCfg.Timeout = 500 + setting.Database.Type = "sqlite3" + setting.Database.Path = ":memory:" + setting.Database.Timeout = 500 */ db := setting.Cfg.Section("database") db.NewKey("DB_TYPE", "sqlite3") db.NewKey("PATH", ":memory:") - setting.LogSQL = true - models.LoadConfigs() + routers.NewServices() + setting.Database.LogSQL = true //x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") var helper testfixtures.Helper = &testfixtures.SQLite{} diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index e44cc90a4b..9bfddc97e8 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -116,6 +116,8 @@ DEFAULT_THEME = gitea THEMES = gitea,arc-green ; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. DEFAULT_SHOW_FULL_NAME = false +; Whether to search within description at repository search on explore page. +SEARCH_REPO_DESCRIPTION = true [ui.admin] ; Number of users that are displayed on one page @@ -275,13 +277,17 @@ LOG_SQL = true DB_RETRIES = 10 ; Backoff time per DB retry (time.Duration) DB_RETRY_BACKOFF = 3s +; Max idle database connections on connnection pool, default is 0 +MAX_IDLE_CONNS = 0 +; Database connection max life time, default is 3s +CONN_MAX_LIFETIME = 3s [indexer] ; Issue indexer type, currently support: bleve or db, default is bleve ISSUE_INDEXER_TYPE = bleve ; Issue indexer storage path, available when ISSUE_INDEXER_TYPE is bleve ISSUE_INDEXER_PATH = indexers/issues.bleve -; Issue indexer queue, currently support: channel or levelqueue, default is levelqueue +; Issue indexer queue, currently support: channel, levelqueue or redis, default is levelqueue ISSUE_INDEXER_QUEUE_TYPE = levelqueue ; When ISSUE_INDEXER_QUEUE_TYPE is levelqueue, this will be the queue will be saved path, ; default is indexers/issues.queue @@ -296,17 +302,24 @@ REPO_INDEXER_ENABLED = false REPO_INDEXER_PATH = indexers/repos.bleve UPDATE_BUFFER_LEN = 20 MAX_FILE_SIZE = 1048576 +; A comma separated list of glob patterns (see https://github.com/gobwas/glob) to include +; in the index; default is empty +REPO_INDEXER_INCLUDE = +; A comma separated list of glob patterns to exclude from the index; ; default is empty +REPO_INDEXER_EXCLUDE = [admin] ; Disallow regular (non-admin) users from creating organizations. DISABLE_REGULAR_ORG_CREATION = false +; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled +DEFAULT_EMAIL_NOTIFICATIONS = enabled [security] ; Whether the installer is disabled INSTALL_LOCK = false ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! SECRET_KEY = !#@FDEWREWR&*( -; How long to remember that an user is logged in before requiring relogin (in days) +; How long to remember that a user is logged in before requiring relogin (in days) LOGIN_REMEMBER_DAYS = 7 COOKIE_USERNAME = gitea_awesome COOKIE_REMEMBER_NAME = gitea_incredible @@ -321,6 +334,8 @@ IMPORT_LOCAL_PATHS = false DISABLE_GIT_HOOKS = false ; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt" PASSWORD_HASH_ALGO = pbkdf2 +; Set false to allow JavaScript to read CSRF cookie +CSRF_COOKIE_HTTP_ONLY = true [openid] ; @@ -399,6 +414,9 @@ DEFAULT_ALLOW_CREATE_ORGANIZATION = true ; Private is only for member of the organization ; Public is for everyone DEFAULT_ORG_VISIBILITY = public +; Default value for DefaultOrgMemberVisible +; True will make the membership of the users visible when added to the organisation +DEFAULT_ORG_MEMBER_VISIBLE = false ; Default value for EnableDependencies ; Repositories will use dependencies by default depending on this setting DEFAULT_ENABLE_DEPENDENCIES = true @@ -545,6 +563,9 @@ MAX_FILES = 5 ; Special supported values are ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro and StampNano ; For more information about the format see http://golang.org/pkg/time/#pkg-constants FORMAT = +; Location the UI time display i.e. Asia/Shanghai +; Empty means server's location setting +DEFAULT_UI_LOCATION = [log] ROOT_PATH = @@ -722,7 +743,7 @@ ACCESS_TOKEN_EXPIRATION_TIME=3600 REFRESH_TOKEN_EXPIRATION_TIME=730 ; Check if refresh token got already used INVALIDATE_REFRESH_TOKENS=false -; OAuth2 authentication secret for access and refresh tokens, change this a unique string. +; OAuth2 authentication secret for access and refresh tokens, change this to a unique string. JWT_SECRET=Bk0yK7Y9g_p56v86KaHqjSbxvNvu3SbKoOdOt2ZcXvU [i18n] diff --git a/docker/manifest.tmpl b/docker/manifest.tmpl new file mode 100644 index 0000000000..9678449628 --- /dev/null +++ b/docker/manifest.tmpl @@ -0,0 +1,19 @@ +image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - + image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 + platform: + architecture: amd64 + os: linux + - + image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 + platform: + architecture: arm64 + os: linux + variant: v8 diff --git a/docker/root/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup index c4fbf5d65e..f87ce9115e 100755 --- a/docker/root/etc/s6/gitea/setup +++ b/docker/root/etc/s6/gitea/setup @@ -31,6 +31,7 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then ROOT_URL=${ROOT_URL:-""} \ DISABLE_SSH=${DISABLE_SSH:-"false"} \ SSH_PORT=${SSH_PORT:-"22"} \ + SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \ LFS_START_SERVER=${LFS_START_SERVER:-"false"} \ DB_TYPE=${DB_TYPE:-"sqlite3"} \ DB_HOST=${DB_HOST:-"localhost:3306"} \ diff --git a/docker/root/etc/templates/app.ini b/docker/root/etc/templates/app.ini index 212cd854d3..e05c24a09b 100644 --- a/docker/root/etc/templates/app.ini +++ b/docker/root/etc/templates/app.ini @@ -17,6 +17,7 @@ HTTP_PORT = $HTTP_PORT ROOT_URL = $ROOT_URL DISABLE_SSH = $DISABLE_SSH SSH_PORT = $SSH_PORT +SSH_LISTEN_PORT = $SSH_LISTEN_PORT LFS_START_SERVER = $LFS_START_SERVER LFS_CONTENT_PATH = /data/git/lfs diff --git a/docker/root/etc/templates/sshd_config b/docker/root/etc/templates/sshd_config index ba92e236e1..bf0b936d7c 100644 --- a/docker/root/etc/templates/sshd_config +++ b/docker/root/etc/templates/sshd_config @@ -25,7 +25,7 @@ ChallengeResponseAuthentication no PasswordAuthentication no PermitEmptyPasswords no -AllowUsers git +AllowUsers ${USER} Banner none Subsystem sftp /usr/lib/ssh/sftp-server diff --git a/docker/root/usr/bin/entrypoint b/docker/root/usr/bin/entrypoint index d8e68b9404..a3c03ecff3 100755 --- a/docker/root/usr/bin/entrypoint +++ b/docker/root/usr/bin/entrypoint @@ -3,8 +3,6 @@ if [ "${USER}" != "git" ]; then # rename user sed -i -e "s/^git\:/${USER}\:/g" /etc/passwd - # switch sshd config to different user - sed -i -e "s/AllowUsers git$/AllowUsers ${USER}/g" /etc/ssh/sshd_config fi if [ -z "${USER_GID}" ]; then diff --git a/docs/config.yaml b/docs/config.yaml index 03751cf2bb..353011378a 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -18,6 +18,7 @@ params: description: Git with a cup of tea author: The Gitea Authors website: https://docs.gitea.io + version: 1.9.3 menu: page: diff --git a/docs/content/doc/advanced/ci-cd.en-us.md b/docs/content/doc/advanced/ci-cd.en-us.md new file mode 100644 index 0000000000..7d22078a9d --- /dev/null +++ b/docs/content/doc/advanced/ci-cd.en-us.md @@ -0,0 +1,34 @@ +--- +date: "2019-08-27:00:00+02:00" +title: "CI/CD Usage" +slug: "ci-cd" +weight: 40 +toc: true +draft: false +menu: + sidebar: + parent: "advanced" + name: "CI/CD Usage" + weight: 40 + identifier: "ci-cd" +--- + +# Gitea and CI/CD + +**NOTE:** These tools are not endorsed by Gitea. They are listed here for convenience only. + +## Listing + +CI/CD solutions that have integration with Gitea. Following list is not complete, +the purpose is to give a starting point to integrate a CI/CD process with your Gitea instance. + + - [Drone](https://drone.io) with [Gitea documentation](https://docs.drone.io/installation/providers/gitea/) + - [Jenkins](https://jenkins.io/) with [Gitea plugin](https://plugins.jenkins.io/gitea) + - [Agola](https://agola.io) + - [Buildkite](https://buildkite.com) with [Gitea connector](https://github.com/techknowlogick/gitea-buildkite-connector) + - [AppVeyor](https://www.appveyor.com) with [built-in Gitea support](https://www.appveyor.com/blog/2019/09/05/gitea-receives-first-class-support-in-appveyor/) + - [Buildbot](https://www.buildbot.net/) with [Gitea plugin](https://github.com/lab132/buildbot-gitea) + + +Others CI/CD solutions that partially can be integrated with Gitea: + - [Concourse](https://www.concourse-ci.org), see more information at [Concourse community forum](https://discuss.concourse-ci.org/t/concourse-ci-and-gitea-oauth/1475) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 61905f8ad8..198cff6f04 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -96,6 +96,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. - `DEFAULT_SHOW_FULL_NAME`: false: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. +- `SEARCH_REPO_DESCRIPTION`: true: Whether to search within description at repository search on explore page. ### UI - Admin (`ui.admin`) @@ -132,6 +133,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. - `SSH_PORT`: **22**: SSH port displayed in clone URL. +- `SSH_LISTEN_HOST`: **0.0.0.0**: Listen address for the built-in SSH server. - `SSH_LISTEN_PORT`: **%(SSH\_PORT)s**: Port for the built-in SSH server. - `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures. - `DISABLE_ROUTER_LOG`: **false**: Mute printing of the router log. @@ -155,7 +157,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. ## Database (`database`) - `DB_TYPE`: **mysql**: The database type in use \[mysql, postgres, mssql, sqlite3\]. -- `HOST`: **127.0.0.1:3306**: Database host address and port. +- `HOST`: **127.0.0.1:3306**: Database host address and port or absolute path for unix socket \[mysql, postgres\] (ex: /var/run/mysqld/mysqld.sock). - `NAME`: **gitea**: Database name. - `USER`: **root**: Database username. - `PASSWD`: **\**: Database user password. Use \`your password\` for quoting if you use special characters in the password. @@ -165,6 +167,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LOG_SQL`: **true**: Log the executed SQL. - `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed. - `DB_RETRY_BACKOFF`: **3s**: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occured. +- `MAX_IDLE_CONNS` **0**: Max idle database connections on connnection pool, default is 0 +- `CONN_MAX_LIFETIME` **3s**: Database connection max lifetime ## Indexer (`indexer`) @@ -177,9 +181,14 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `REPO_INDEXER_ENABLED`: **false**: Enables code search (uses a lot of disk space, about 6 times more than the repository size). - `REPO_INDEXER_PATH`: **indexers/repos.bleve**: Index file used for code search. +- `REPO_INDEXER_INCLUDE`: **empty**: A comma separated list of glob patterns (see https://github.com/gobwas/glob) to **include** in the index. Use `**.txt` to match any files with .txt extension. An empty list means include all files. +- `REPO_INDEXER_EXCLUDE`: **empty**: A comma separated list of glob patterns (see https://github.com/gobwas/glob) to **exclude** from the index. Files that match this list will not be indexed, even if they match in `REPO_INDEXER_INCLUDE`. - `UPDATE_BUFFER_LEN`: **20**: Buffer length of index request. - `MAX_FILE_SIZE`: **1048576**: Maximum size in bytes of files to be indexed. +## Admin (`admin`) +- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled + ## Security (`security`) - `INSTALL_LOCK`: **false**: Disallow access to the install page. @@ -198,6 +207,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `INTERNAL_TOKEN`: **\**: Secret used to validate communication within Gitea binary. - `INTERNAL_TOKEN_URI`: ****: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) - `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[pbkdf2, argon2, scrypt, bcrypt\]. +- `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. ## OpenID (`openid`) @@ -242,6 +252,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button - `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created - `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private". +- `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation. ## Webhook (`webhook`) @@ -281,7 +292,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only. - `HOST`: **\**: Connection string for `redis` and `memcache`. - Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180` - - Memache: `127.0.0.1:9090;127.0.0.1:9091` + - Memcache: `127.0.0.1:9090;127.0.0.1:9091` +- `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to 0 disables caching. ## Session (`session`) @@ -501,6 +513,10 @@ Two special environment variables are passed to the render command: - `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links. - `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths. +## Time (`time`) +- `FORMAT`: Time format to diplay on UI. i.e. RFC1123 or 2006-01-02 15:04:05 +- `DEFAULT_UI_LOCATION`: Default location of time on the UI, so that we can display correct user's time on UI. i.e. Shanghai/Asia + ## Other (`other`) - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index b9a16dd844..541d66f4e9 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -82,6 +82,8 @@ menu: - `CHARSET`: **utf8**: 仅当数据库为 MySQL 时有效, 可以为 "utf8" 或 "utf8mb4"。注意:如果使用 "utf8mb4",你的 MySQL InnoDB 版本必须在 5.6 以上。 - `PATH`: Tidb 或者 SQLite3 数据文件存放路径。 - `LOG_SQL`: **true**: 显示生成的SQL,默认为真。 +- `MAX_IDLE_CONNS` **0**: 最大空闲数据库连接 +- `CONN_MAX_LIFETIME` **3s**: 数据库连接最大存活时间 ## Indexer (`indexer`) @@ -142,11 +144,12 @@ menu: ## Cache (`cache`) -- `ADAPTER`: 缓存引擎,可以为 `memory`, `redis` 或 `memcache`。 -- `INTERVAL`: 只对内存缓存有效,GC间隔,单位秒。 -- `HOST`: 针对redis和memcache有效,主机地址和端口。 +- `ADAPTER`: **memory**: 缓存引擎,可以为 `memory`, `redis` 或 `memcache`。 +- `INTERVAL`: **60**: 只对内存缓存有效,GC间隔,单位秒。 +- `HOST`: **\**: 针对redis和memcache有效,主机地址和端口。 - Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180` - Memache: `127.0.0.1:9090;127.0.0.1:9091` +- `ITEM_TTL`: **16h**: 缓存项目失效时间,设置为 0 则禁用缓存。 ## Session (`session`) @@ -237,7 +240,9 @@ IS_INPUT_FILE = false - RENDER_COMMAND: 工具的命令行命令及参数。 - IS_INPUT_FILE: 输入方式是最后一个参数为文件路径还是从标准输入读取。 - +## Time (`time`) +- `FORMAT`: 显示在界面上的时间格式。比如: RFC1123 或者 2006-01-02 15:04:05 +- `DEFAULT_UI_LOCATION`: 默认显示在界面上的时区,默认为本地时区。比如: Asia/Shanghai ## Other (`other`) diff --git a/docs/content/doc/advanced/customizing-gitea.en-us.md b/docs/content/doc/advanced/customizing-gitea.en-us.md index 460ea61eac..89b4a489cb 100644 --- a/docs/content/doc/advanced/customizing-gitea.en-us.md +++ b/docs/content/doc/advanced/customizing-gitea.en-us.md @@ -60,7 +60,7 @@ the url `http://gitea.domain.tld/image.png`. ## Changing the default avatar -Place the png image at the following path: `custom/public/img/avatar\_default.png` +Place the png image at the following path: `custom/public/img/avatar_default.png` ## Customizing Gitea pages diff --git a/docs/content/doc/advanced/external-renderers.en-us.md b/docs/content/doc/advanced/external-renderers.en-us.md index e1d773725c..e3a122448d 100644 --- a/docs/content/doc/advanced/external-renderers.en-us.md +++ b/docs/content/doc/advanced/external-renderers.en-us.md @@ -17,6 +17,7 @@ menu: Gitea supports custom file renderings (i.e., Jupyter notebooks, asciidoc, etc.) through external binaries, it is just a matter of: + * installing external binaries * add some configuration to your `app.ini` file * restart your Gitea instance @@ -27,13 +28,13 @@ In order to get file rendering through external binaries, their associated packa If you're using a Docker image, your `Dockerfile` should contain something along this lines: ``` -FROM gitea/gitea:1.6.0 +FROM gitea/gitea:{{< version >}} [...] COPY custom/app.ini /data/gitea/conf/app.ini [...] -RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng python-dev py-pip python3-dev py3-pip +RUN apk --no-cache add asciidoctor freetype freetype-dev gcc g++ libpng python-dev py-pip python3-dev py3-pip py3-zmq # install any other package you need for your external renderers RUN pip3 install --upgrade pip diff --git a/docs/content/doc/advanced/hacking-on-gitea.en-us.md b/docs/content/doc/advanced/hacking-on-gitea.en-us.md index ef29051e7e..8c8249c048 100644 --- a/docs/content/doc/advanced/hacking-on-gitea.en-us.md +++ b/docs/content/doc/advanced/hacking-on-gitea.en-us.md @@ -108,10 +108,10 @@ and look at our [`.drone.yml`](https://github.com/go-gitea/gitea/blob/master/.drone.yml) to see how our continuous integration works. -### Formatting, linting, vetting and spell-check +### Formatting, code analysis and spell check Our continous integration will reject PRs that are not properly formatted, fail -linting, vet or spell-check. +code analysis or spell check. You should format your code with `go fmt` using: @@ -130,10 +130,10 @@ You should run the same version of go that is on the continuous integration server as mentioned above. `make fmt-check` will only check if your `go` would format differently - this may be different from the CI server version. -You should lint, vet and spell-check with: +You should run revive, vet and spell-check on the code with: ```bash -make vet lint misspell-check +make revive vet misspell-check ``` ### Updating CSS @@ -178,7 +178,7 @@ make generate-swagger You should validate your generated Swagger file and spell-check it with: ```bash -make swagger-validate mispell-check +make swagger-validate misspell-check ``` You should commit the changed swagger JSON file. The continous integration diff --git a/docs/content/doc/advanced/migrations.en-us.md b/docs/content/doc/advanced/migrations.en-us.md index 7db9cad817..2511f7af89 100644 --- a/docs/content/doc/advanced/migrations.en-us.md +++ b/docs/content/doc/advanced/migrations.en-us.md @@ -34,6 +34,7 @@ create a Downloader. ```Go type Downloader interface { GetRepoInfo() (*Repository, error) + GetTopics() ([]string, error) GetMilestones() ([]*Milestone, error) GetReleases() ([]*Release, error) GetLabels() ([]*Label, error) diff --git a/docs/content/doc/advanced/repo-indexer.en-us.md b/docs/content/doc/advanced/repo-indexer.en-us.md new file mode 100644 index 0000000000..66f09df939 --- /dev/null +++ b/docs/content/doc/advanced/repo-indexer.en-us.md @@ -0,0 +1,58 @@ +--- +date: "2019-09-06T01:35:00-03:00" +title: "Repository indexer" +slug: "repo-indexer" +weight: 45 +toc: true +draft: false +menu: + sidebar: + parent: "advanced" + name: "Repository indexer" + weight: 45 + identifier: "repo-indexer" +--- + +# Repository indexer + +## Setting up the repository indexer + +Gitea can search through the files of the repositories by enabling this function in your [`app.ini`](https://docs.gitea.io/en-us/config-cheat-sheet/): + +``` +[indexer] +; ... +REPO_INDEXER_ENABLED = true +REPO_INDEXER_PATH = indexers/repos.bleve +UPDATE_BUFFER_LEN = 20 +MAX_FILE_SIZE = 1048576 +REPO_INDEXER_INCLUDE = +REPO_INDEXER_EXCLUDE = resources/bin/** +``` + +Please bear in mind that indexing the contents can consume a lot of system resources, especially when the index is created for the first time or globally updated (e.g. after upgrading Gitea). + +### Choosing the files for indexing by size + +The `MAX_FILE_SIZE` option will make the indexer skip all files larger than the specified value. + +### Choosing the files for indexing by path + +Gitea applies glob pattern matching from the [`gobwas/glob` library](https://github.com/gobwas/glob) to choose which files will be included in the index. + +Limiting the list of files prevents the indexes from becoming polluted with derived or irrelevant files (e.g. lss, sym, map, etc.), so the search results are more relevant. It can also help reduce the index size. + +`REPO_INDEXER_INCLUDE` (default: empty) is a comma separated list of glob patterns to **include** in the index. An empty list means "_include all files_". +`REPO_INDEXER_EXCLUDE` (default: empty) is a comma separated list of glob patterns to **exclude** from the index. Files that match this list will not be indexed. `REPO_INDEXER_EXCLUDE` takes precedence over `REPO_INDEXER_INCLUDE`. + +Pattern matching works as follows: + +* To match all files with a `.txt` extension no matter what directory, use `**.txt`. +* To match all files with a `.txt` extension _only at the root level of the repository_, use `*.txt`. +* To match all files inside `resources/bin` and below, use `resources/bin/**`. +* To match all files _immediately inside_ `resources/bin`, use `resources/bin/*`. +* To match all files named `Makefile`, use `**Makefile`. +* Matching a directory has no effect; the pattern `resources/bin` will not include/exclude files inside that directory; `resources/bin/**` will. +* All files and patterns are normalized to lower case, so `**Makefile`, `**makefile` and `**MAKEFILE` are equivalent. + + diff --git a/docs/content/doc/advanced/third-party-tools.en-us.md b/docs/content/doc/advanced/third-party-tools.en-us.md index de5ab3721a..9eed753586 100644 --- a/docs/content/doc/advanced/third-party-tools.en-us.md +++ b/docs/content/doc/advanced/third-party-tools.en-us.md @@ -19,10 +19,11 @@ menu: *This is by no means a complete list, so feel free to ask about adding more!* ### Continuous Integration -[BuildKite Connector](https://github.com/techknowlogick/gitea-buildkite-connector) -[Jenkins Plugin](https://github.com/jenkinsci/gitea-plugin) -[Using Gitea with Drone](https://docs.drone.io/installation/gitea/) +Check our [CI/CD page]({{< relref "doc/advanced/ci-cd.en-us.md" >}}) + +### Internationalization +[Weblate](https://docs.weblate.org/en/latest/admin/continuous.html#gitea-setup) ### Migrating [Installation script for Gitea](https://git.coolaj86.com/coolaj86/gitea-installer.sh) diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md index 45170bfa84..6bbbfba3b4 100644 --- a/docs/content/doc/features/comparison.en-us.md +++ b/docs/content/doc/features/comparison.en-us.md @@ -62,7 +62,7 @@ _Symbols used in table:_ | Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Verified Committer | ✘ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | | GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Reject unsigned commits | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | +| Reject unsigned commits | [✘](https://github.com/go-gitea/gitea/issues/2770) | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | | Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | diff --git a/docs/content/doc/help/faq.en-us.md b/docs/content/doc/help/faq.en-us.md index 1154141a09..4e4d1aeecf 100644 --- a/docs/content/doc/help/faq.en-us.md +++ b/docs/content/doc/help/faq.en-us.md @@ -28,7 +28,8 @@ Also see [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}}) * [What is Swagger?](#what-is-swagger) * [Adjusting your server for public/private use](#adjusting-your-server-for-public-private-use) * [Preventing spammers](#preventing-spammers) - * [Only allow/block certain email domains](#only-allow-block-certain-email-domains) + * [Only allow certain email domains](#only-allow-certain-email-domains) + * [Only allow/block certain OpenID providers](#only-allow-block-certain-openid-providers) * [Issue only users](#issue-only-users) * [Enable Fail2ban](#enable-fail2ban) * [Adding custom themes](#how-to-add-use-custom-themes) @@ -133,8 +134,11 @@ There are multiple things you can combine to prevent spammers. 2. Setting `ENABLE_CAPTCHA` to `true` in your `app.ini` and properly configuring `RECAPTCHA_SECRET` and `RECAPTCHA_SITEKEY` 3. Settings `DISABLE_REGISTRATION` to `true` and creating new users via the [CLI]({{< relref "doc/usage/command-line.en-us.md" >}}), [API]({{< relref "doc/advanced/api-usage.en-us.md" >}}), or Gitea's Admin UI -### Only allow/block certain email domains -If using OpenID, you can configure `WHITELISTED_URIS` or `BLACKLISTED_URIS` in your `app.ini` +### Only allow certain email domains +You can configure `EMAIL_DOMAIN_WHITELIST` in your app.ini under `[service]` + +### Only allow/block certain OpenID providers +You can configure `WHITELISTED_URIS` or `BLACKLISTED_URIS` under `[openid]` in your `app.ini` **NOTE:** whitelisted takes precedence, so if it is non-blank then blacklisted is ignored ### Issue only users diff --git a/docs/content/doc/installation/from-binary.en-us.md b/docs/content/doc/installation/from-binary.en-us.md index e41028eeb2..c93973f222 100644 --- a/docs/content/doc/installation/from-binary.en-us.md +++ b/docs/content/doc/installation/from-binary.en-us.md @@ -21,7 +21,7 @@ the destination platform from the [downloads page](https://dl.gitea.io/gitea/), the URL and replace the URL within the commands below: ```sh -wget -O gitea https://dl.gitea.io/gitea/1.8.3/gitea-1.8.3-linux-amd64 +wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64 chmod +x gitea ``` @@ -30,7 +30,7 @@ Gitea signs all binaries with a [GPG key](https://pgp.mit.edu/pks/lookup?op=vind ```sh gpg --keyserver pgp.mit.edu --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 -gpg --verify gitea-1.8.3-linux-amd64.asc gitea-1.8.3-linux-amd64 +gpg --verify gitea-{{< version >}}-linux-amd64.asc gitea-{{< version >}}-linux-amd64 ``` ## Test diff --git a/docs/content/doc/installation/from-binary.fr-fr.md b/docs/content/doc/installation/from-binary.fr-fr.md index 6cf9c03998..ea6a8725f6 100644 --- a/docs/content/doc/installation/from-binary.fr-fr.md +++ b/docs/content/doc/installation/from-binary.fr-fr.md @@ -18,7 +18,7 @@ menu: Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.io/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle: ``` -wget -O gitea https://dl.gitea.io/gitea/1.3.2/gitea-1.3.2-linux-amd64 +wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64 chmod +x gitea ``` diff --git a/docs/content/doc/installation/from-binary.zh-cn.md b/docs/content/doc/installation/from-binary.zh-cn.md index 37719a6186..186cbf5722 100644 --- a/docs/content/doc/installation/from-binary.zh-cn.md +++ b/docs/content/doc/installation/from-binary.zh-cn.md @@ -18,7 +18,7 @@ menu: 所有下载均包括 SQLite, MySQL 和 PostgreSQL 的支持,同时所有资源均已嵌入到可执行程序中,这一点和老版本有所不同。 基于二进制的安装非常简单,只要从 [下载页面](https://dl.gitea.io/gitea) 选择对应平台,拷贝下载URL,执行以下命令即可(以Linux为例): ``` -wget -O gitea https://dl.gitea.io/gitea/1.3.2/gitea-1.3.2-linux-amd64 +wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64 chmod +x gitea ``` diff --git a/docs/content/doc/installation/from-binary.zh-tw.md b/docs/content/doc/installation/from-binary.zh-tw.md index a797d4426a..78e8913ebc 100644 --- a/docs/content/doc/installation/from-binary.zh-tw.md +++ b/docs/content/doc/installation/from-binary.zh-tw.md @@ -18,7 +18,7 @@ menu: 所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.io/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了: ``` -wget -O gitea https://dl.gitea.io/gitea/1.3.2/gitea-1.3.2-linux-amd64 +wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64 chmod +x gitea ``` diff --git a/docs/content/doc/installation/from-source.en-us.md b/docs/content/doc/installation/from-source.en-us.md index cf6d42802e..9455f93d80 100644 --- a/docs/content/doc/installation/from-source.en-us.md +++ b/docs/content/doc/installation/from-source.en-us.md @@ -53,7 +53,7 @@ To work with tagged releases, the following commands can be used: ```bash git branch -a -git checkout v1.0 +git checkout v{{< version >}} ``` To validate a Pull Request, first enable the new branch (`xyz` is the PR id; @@ -63,14 +63,14 @@ for example `2663` for [#2663](https://github.com/go-gitea/gitea/pull/2663)): git fetch origin pull/xyz/head:pr-xyz ``` -To build Gitea from source at a specific tagged release (like v1.0.0), list the +To build Gitea from source at a specific tagged release (like v{{< version >}}), list the available tags and check out the specific tag. List available tags with the following. ```bash git tag -l -git checkout v1.0.0 # or git checkout pr-xyz +git checkout v{{< version >}} # or git checkout pr-xyz ``` ## Build diff --git a/docs/content/doc/installation/from-source.fr-fr.md b/docs/content/doc/installation/from-source.fr-fr.md index d413334f4c..def4c4ee41 100644 --- a/docs/content/doc/installation/from-source.fr-fr.md +++ b/docs/content/doc/installation/from-source.fr-fr.md @@ -35,7 +35,7 @@ Si vous souhaitez compiler la dernière version stable, utilisez les étiquettes ``` git branch -a -git checkout v1.0 +git checkout v{{< version >}} ``` Si vous souhaitez valider une demande d'ajout (_Pull request_), vous devez activer cette branche en premier : @@ -44,11 +44,11 @@ Si vous souhaitez valider une demande d'ajout (_Pull request_), vous devez activ git fetch origin pull/xyz/head:pr-xyz # xyz is PR value ``` -Enfin, vous pouvez directement utiliser les versions étiquettées (ex : `v1.0.0`). Pour utiliser les étiquettes, vous devez lister les étiquettes disponibles et choisir une étiquette spécifique avec les commandes suivantes : +Enfin, vous pouvez directement utiliser les versions étiquettées (ex : `v{{< version >}}`). Pour utiliser les étiquettes, vous devez lister les étiquettes disponibles et choisir une étiquette spécifique avec les commandes suivantes : ``` git tag -l -git checkout v1.0.0 +git checkout v{{< version >}} git checkout pr-xyz ``` diff --git a/docs/content/doc/installation/from-source.zh-cn.md b/docs/content/doc/installation/from-source.zh-cn.md index 99b2c62b92..6281b45bdc 100644 --- a/docs/content/doc/installation/from-source.zh-cn.md +++ b/docs/content/doc/installation/from-source.zh-cn.md @@ -32,14 +32,14 @@ cd $GOPATH/src/code.gitea.io/gitea ``` git branch -a -git checkout v1.0 +git checkout v{{< version >}} ``` -最后,你也可以直接使用标签版本如 `v1.0.0`。你可以执行以下命令列出可用的版本并选择某个版本签出: +最后,你也可以直接使用标签版本如 `v{{< version >}}`。你可以执行以下命令列出可用的版本并选择某个版本签出: ``` git tag -l -git checkout v1.0.0 +git checkout v{{< version >}} ``` ## 编译 diff --git a/docs/content/doc/installation/from-source.zh-tw.md b/docs/content/doc/installation/from-source.zh-tw.md index e8450121b0..461e9f0748 100644 --- a/docs/content/doc/installation/from-source.zh-tw.md +++ b/docs/content/doc/installation/from-source.zh-tw.md @@ -32,14 +32,14 @@ cd $GOPATH/src/code.gitea.io/gitea ``` git branch -a -git checkout v1.0 +git checkout v{{< version >}} ``` -最後您也可以直接編譯最新的標籤版本像是 `v1.0.0`,假如您想要從原始碼編譯,這方法是最合適的,在編譯標籤版本前,您需要列出當下所有標籤,並且直接切換到標籤版本,請使用底下指令:: +最後您也可以直接編譯最新的標籤版本像是 `v{{< version >}}`,假如您想要從原始碼編譯,這方法是最合適的,在編譯標籤版本前,您需要列出當下所有標籤,並且直接切換到標籤版本,請使用底下指令:: ``` git tag -l -git checkout v1.0.0 +git checkout v{{< version >}} ``` ## 編譯 diff --git a/docs/content/doc/installation/with-docker.en-us.md b/docs/content/doc/installation/with-docker.en-us.md index a403d18778..becbc25431 100644 --- a/docs/content/doc/installation/with-docker.en-us.md +++ b/docs/content/doc/installation/with-docker.en-us.md @@ -31,7 +31,7 @@ Create a directory like `gitea` and paste the following content into a file name Note that the volume should be owned by the user/group with the UID/GID specified in the config file. If you don't give the volume correct permissions, the container may not start. Also be aware that the tag `:latest` will install the current development version. -For a stable release you can use `:1` or specify a certain release like `:1.5.1`. +For a stable release you can use `:1` or specify a certain release like `:{{< version >}}`. ```yaml version: "2" @@ -245,6 +245,7 @@ You can configure some of Gitea's settings via environment variables: * `RUN_MODE`: **dev**: For performance and other purposes, change this to `prod` when deployed to a production environment. * `SSH_DOMAIN`: **localhost**: Domain name of this server, used for the displayed clone URL in Gitea's UI. * `SSH_PORT`: **22**: SSH port displayed in clone URL. +* `SSH_LISTEN_PORT`: **%(SSH\_PORT)s**: Port for the built-in SSH server. * `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. * `HTTP_PORT`: **3000**: HTTP listen port. * `ROOT_URL`: **""**: Overwrite the automatically generated public URL. This is useful if the internal and the external URL don't match (e.g. in Docker). diff --git a/docs/content/doc/upgrade/from-gogs.en-us.md b/docs/content/doc/upgrade/from-gogs.en-us.md index e3a3aa89ea..46f2f76b6c 100644 --- a/docs/content/doc/upgrade/from-gogs.en-us.md +++ b/docs/content/doc/upgrade/from-gogs.en-us.md @@ -21,7 +21,7 @@ There are some basic steps to follow. On a Linux system run as the Gogs user: * Create a Gogs backup with `gogs backup`. This creates `gogs-backup-[timestamp].zip` file containing all important Gogs data. You would need it if you wanted to move to the `gogs` back later. -* Download the file matching the destination platform from the [downloads page](https://dl.gitea.io/gitea). +* Download the file matching the destination platform from the [downloads page](https://dl.gitea.io/gitea/). It should be `1.0.x` version. Migrating from `gogs` to any other version is impossible. * Put the binary at the desired install location. * Copy `gogs/custom/conf/app.ini` to `gitea/custom/conf/app.ini`. diff --git a/docs/content/doc/usage/backup-and-restore.en-us.md b/docs/content/doc/usage/backup-and-restore.en-us.md index 5cf2f4aec8..a3887bc64a 100644 --- a/docs/content/doc/usage/backup-and-restore.en-us.md +++ b/docs/content/doc/usage/backup-and-restore.en-us.md @@ -79,3 +79,9 @@ mysql -u$USER -p$PASS $DATABASE Docker), or if Gitea is installed to a different directory than the previous installation. + +With Gitea running, and from the directory Gitea's binary is located, execute: `./gitea admin regenerate hooks` + +This ensures that application and configuration file paths in repository git-hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail. diff --git a/docs/content/doc/usage/https-support.md b/docs/content/doc/usage/https-support.md index 5e1f4be56e..22cbc684aa 100644 --- a/docs/content/doc/usage/https-support.md +++ b/docs/content/doc/usage/https-support.md @@ -24,11 +24,11 @@ To use Gitea's built-in HTTPS support, you must change your `app.ini` file: ```ini [server] -PROTOCOL=https -ROOT_URL = `https://git.example.com:3000/` +PROTOCOL = https +ROOT_URL = https://git.example.com:3000/ HTTP_PORT = 3000 CERT_FILE = cert.pem -KEY_FILE = key.pem +KEY_FILE = key.pem ``` To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server). diff --git a/docs/content/doc/usage/reverse-proxies.en-us.md b/docs/content/doc/usage/reverse-proxies.en-us.md index d828d5b226..47a5b95572 100644 --- a/docs/content/doc/usage/reverse-proxies.en-us.md +++ b/docs/content/doc/usage/reverse-proxies.en-us.md @@ -53,7 +53,8 @@ If you want Apache HTTPD to serve your Gitea instance, you can add the following ... ProxyPreserveHost On ProxyRequests off - ProxyPass / http://localhost:3000/ + AllowEncodedSlashes NoDecode + ProxyPass / http://localhost:3000/ nocanon ProxyPassReverse / http://localhost:3000/ ``` @@ -71,9 +72,10 @@ In case you already have a site, and you want Gitea to share the domain name, yo Order allow,deny Allow from all - - ProxyPass /git http://localhost:3000 # Note: no trailing slash after either /git or port - ProxyPassReverse /git http://localhost:3000 # Note: no trailing slash after either /git or port + AllowEncodedSlashes NoDecode + # Note: no trailing slash after either /git or port + ProxyPass /git http://localhost:3000 nocanon + ProxyPassReverse /git http://localhost:3000 ``` diff --git a/docs/content/doc/usage/reverse-proxies.zh-cn.md b/docs/content/doc/usage/reverse-proxies.zh-cn.md index c3f5758c16..f52adccbbe 100644 --- a/docs/content/doc/usage/reverse-proxies.zh-cn.md +++ b/docs/content/doc/usage/reverse-proxies.zh-cn.md @@ -54,7 +54,8 @@ server { ... ProxyPreserveHost On ProxyRequests off - ProxyPass / http://localhost:3000/ + AllowEncodedSlashes NoDecode + ProxyPass / http://localhost:3000/ nocanon ProxyPassReverse / http://localhost:3000/ ``` @@ -72,9 +73,10 @@ server { Order allow,deny Allow from all - - ProxyPass /git http://localhost:3000 # Note: no trailing slash after either /git or port - ProxyPassReverse /git http://localhost:3000 # Note: no trailing slash after either /git or port + AllowEncodedSlashes NoDecode + # Note: no trailing slash after either /git or port + ProxyPass /git http://localhost:3000 nocanon + ProxyPassReverse /git http://localhost:3000 ``` diff --git a/docs/content/page/index.en-us.md b/docs/content/page/index.en-us.md index a9ccfce42c..3b0cfe2df5 100644 --- a/docs/content/page/index.en-us.md +++ b/docs/content/page/index.en-us.md @@ -255,6 +255,9 @@ Windows, on architectures like amd64, i386, ARM, PowerPC, and others. - 2 CPU cores and 1GB RAM is typically sufficient for small teams/projects. - Gitea should be run with a dedicated non-root system account on UNIX-type systems. - Note: Gitea manages the `~/.ssh/authorized_keys` file. Running Gitea as a regular user could break that user's ability to log in. +- [Git](https://git-scm.com/) version 1.7.2 or later is required. Version 1.9.0 or later is recommended. Also please note: + - Git [large file storage](https://git-lfs.github.com/) will be available if enabled when git >= 2.1.2. + - Git commit-graph rendering will be enabled automatically when git >= 2.18. ## Browser Support diff --git a/docs/layouts/shortcodes/version.html b/docs/layouts/shortcodes/version.html new file mode 100644 index 0000000000..2fd81d651c --- /dev/null +++ b/docs/layouts/shortcodes/version.html @@ -0,0 +1 @@ +{{ .Site.Params.version }} \ No newline at end of file diff --git a/go.mod b/go.mod index 8fdf57b3fc..e1a2b7b404 100644 --- a/go.mod +++ b/go.mod @@ -1,34 +1,34 @@ module code.gitea.io/gitea -go 1.12 +go 1.13 require ( - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 + cloud.google.com/go v0.45.0 // indirect + gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b + gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 + gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae + gitea.com/macaron/cors v0.0.0-20190821152825-7dcef4a17175 + gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 + gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 + gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a + gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb + gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 + gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 + github.com/PuerkitoBio/goquery v1.5.0 github.com/RoaringBitmap/roaring v0.4.7 // indirect - github.com/Unknwon/cae v0.0.0-20160715032808-c6aac99ea2ca - github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755 - github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966 - github.com/Unknwon/paginater v0.0.0-20151104151617-7748a72e0141 - github.com/andybalholm/cascadia v0.0.0-20161224141413-349dd0209470 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/blevesearch/bleve v0.0.0-20190214220507-05d86ea8f6e3 github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 // indirect github.com/blevesearch/go-porterstemmer v0.0.0-20141230013033-23a2c8e5cf1f // indirect github.com/blevesearch/segment v0.0.0-20160105220820-db70c57796cc // indirect github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26 // indirect - github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a // indirect github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f - github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c // indirect - github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect github.com/couchbase/vellum v0.0.0-20190111184608-e91b68ff3efe // indirect - github.com/couchbaselabs/go-couchbase v0.0.0-20190117181324-d904413d884d // indirect github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect - github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952 + github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538 github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect github.com/emirpasic/gods v1.12.0 github.com/etcd-io/bbolt v1.3.2 // indirect github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a @@ -43,22 +43,15 @@ require ( github.com/gliderlabs/ssh v0.2.2 github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd // indirect github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e // indirect - github.com/go-macaron/binding v0.0.0-20160711225916-9440f336b443 - github.com/go-macaron/cache v0.0.0-20151013081102-561735312776 - github.com/go-macaron/captcha v0.0.0-20190710000913-8dc5911259df - github.com/go-macaron/cors v0.0.0-20190309005821-6fd6a9bfe14e9 - github.com/go-macaron/csrf v0.0.0-20180426211211-503617c6b372 - github.com/go-macaron/i18n v0.0.0-20160612092837-ef57533c3b0f - github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 - github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193 - github.com/go-macaron/toolbox v0.0.0-20180818072302-a77f45a7ce90 + github.com/go-openapi/jsonreference v0.19.3 // indirect + github.com/go-openapi/runtime v0.19.5 // indirect github.com/go-redis/redis v6.15.2+incompatible github.com/go-sql-driver/mysql v1.4.1 - github.com/go-xorm/core v0.6.0 // indirect - github.com/go-xorm/xorm v0.7.3-0.20190620151208-f1b4f8368459 + github.com/go-swagger/go-swagger v0.20.1 + github.com/go-xorm/xorm v0.7.9 + github.com/gobwas/glob v0.2.3 github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 - github.com/gogits/cron v0.0.0-20160810035002-7f3990acf183 - github.com/google/go-cmp v0.3.0 // indirect + github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 github.com/google/go-github/v24 v24.0.1 github.com/gorilla/context v1.1.1 github.com/issue9/assert v1.3.2 // indirect @@ -71,16 +64,15 @@ require ( github.com/klauspost/compress v0.0.0-20161025140425-8df558b6cb6f github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc // indirect github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 // indirect - github.com/lafriks/xormstore v1.0.0 - github.com/lib/pq v1.1.0 + github.com/lafriks/xormstore v1.3.1 + github.com/lib/pq v1.2.0 github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e - github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de // indirect - github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af // indirect - github.com/markbates/goth v1.49.0 + github.com/mailru/easyjson v0.7.0 // indirect + github.com/markbates/goth v1.56.0 github.com/mattn/go-isatty v0.0.7 github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d // indirect - github.com/mattn/go-sqlite3 v1.10.0 + github.com/mattn/go-sqlite3 v1.11.0 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect @@ -88,8 +80,10 @@ require ( github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/oliamb/cutter v0.2.2 github.com/philhofer/fwd v1.0.0 // indirect + github.com/pkg/errors v0.8.1 github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e - github.com/prometheus/client_golang v0.9.3 + github.com/prometheus/client_golang v1.1.0 + github.com/prometheus/procfs v0.0.4 // indirect github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect @@ -98,41 +92,36 @@ require ( github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b // indirect github.com/shurcooL/sanitized_anchor_name v0.0.0-20160918041101-1dba4b3954bc // indirect github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd - github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d // indirect - github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff // indirect github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 // indirect - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200 // indirect github.com/tstranex/u2f v1.0.0 + github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1 + github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e + github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 + github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 github.com/urfave/cli v1.20.0 github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 - go.etcd.io/bbolt v1.3.2 // indirect - golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 - golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b - golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759 - golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 + golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad + golang.org/x/net v0.0.0-20190909003024-a7b16738d86b + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20190620154339-431033348dd0 // indirect + golang.org/x/tools v0.0.0-20190910221609-7f5965fd7709 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect - gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect - gopkg.in/editorconfig/editorconfig-core-go.v1 v1.2.0 + gopkg.in/editorconfig/editorconfig-core-go.v1 v1.3.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - gopkg.in/ini.v1 v1.42.0 + gopkg.in/ini.v1 v1.46.0 gopkg.in/ldap.v3 v3.0.2 - gopkg.in/macaron.v1 v1.3.2 - gopkg.in/redis.v2 v2.3.2 // indirect - gopkg.in/src-d/go-billy.v4 v4.3.0 - gopkg.in/src-d/go-git.v4 v4.12.0 + gopkg.in/src-d/go-billy.v4 v4.3.2 + gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/stretchr/testify.v1 v1.2.2 // indirect gopkg.in/testfixtures.v2 v2.5.0 - gopkg.in/yaml.v2 v2.2.2 // indirect mvdan.cc/xurls/v2 v2.0.0 strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a - xorm.io/builder v0.3.5 - xorm.io/core v0.6.3 + xorm.io/builder v0.3.6 + xorm.io/core v0.7.2 ) - -replace github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20180315180555-6a30f4e59a44 diff --git a/go.sum b/go.sum index 8a617be49c..c068caa2f7 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,76 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3 h1:0sMegbmn/8uTwpNkB0q9cLEpZ2W5a6kl+wtBQgPWBJQ= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.0 h1:bALuGBSgE+BD4rxsopAYlqjcwqcQtye6pWG4bC3N/k0= +cloud.google.com/go v0.45.0/go.mod h1:452BcPOeI9AZfbvDw0Tbo7D32wA+WX9WME8AZwMEDZU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ= +gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b/go.mod h1:Cxadig6POWpPYYSfg23E7jo35Yf0yvsdC1lifoKWmPo= +gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 h1:mMsMEg90c5KXQgRWsH8D6GHXfZIW1RAe5S9VYIb12lM= +gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76/go.mod h1:NFHb9Of+LUnU86bU20CiXXg6ZlgCJ4XytP14UsHOXFs= +gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae h1:9C31eOCpMPbW9rDVq8M1UJ+5HZVYA38HHaKCVcRYDpI= +gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae/go.mod h1:J5h3N+1nKTXtU1x4GxexaQKgAz8UiWecNwi/CfX7CtQ= +gitea.com/macaron/cors v0.0.0-20190821152825-7dcef4a17175 h1:ikzdAGB6SsUGByW5wKlK+JwzfgQHX+GJnBwEfsaCTNY= +gitea.com/macaron/cors v0.0.0-20190821152825-7dcef4a17175/go.mod h1:rtOK4J20kpMD9XcNsnO5YA843YSTe/MUMbDj/TJ/Q7A= +gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 h1:88c34YM29a1GlWLrLBaG/GTT2htDdJz1u3n9+lmPolg= +gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439/go.mod h1:IsQPHx73HnnqFBYiVHjg87q4XBZyGXXu77xANukvZuk= +gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 h1:iZWwQif/LHMjBgfY/ua8CFVa4XMDfbbs7EZ0Q1dYguU= +gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223/go.mod h1:+qsc10s4hBsHKU/9luGGumFh4m5FFVc7uih+8/mM1NY= +gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM= +gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok= +gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM= +gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw= +gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA= +gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs= +gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 h1:mvkQGAlON1Z6Y8pqa/+FpYIskk54mazuECUfZK5oTg0= +gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA= +gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14mefLzGluqV7w2mGU3u+iZU+jCeWk= +gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 h1:UsHpWO0Elp6NaWVARdZHjiYwkhrspHVEGsyIKPb9OI8= -github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= +github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RoaringBitmap/roaring v0.4.7 h1:eGUudvFzvF7Kxh7JjYvXfI1f7l22/2duFby7r5+d4oc= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= -github.com/Unknwon/cae v0.0.0-20160715032808-c6aac99ea2ca h1:xU8R31tsvj6TesCBog973+UgI3TXjh/LqN5clki6hcc= -github.com/Unknwon/cae v0.0.0-20160715032808-c6aac99ea2ca/go.mod h1:IRSre9/SEhVuy972TVuJLyaPTS73+8Owhe0Y0l9NXHc= -github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755 h1:1B7wb36fHLSwZfHg6ngZhhtIEHQjiC5H4p7qQGBEffg= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68= -github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966 h1:Mp8GNJ/tdTZIEdLdZfykEJaL3mTyEYrSzYNcdoQKpJk= -github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966/go.mod h1:SFtfq0zFPsENI7DpE87QM2hcYu5QQ0fRdCgP+P1Hrqo= -github.com/Unknwon/paginater v0.0.0-20151104151617-7748a72e0141 h1:SSvHGK7iMpeypcHjI8UzNMz7zW/K8/dcgqk/82lCYP0= -github.com/Unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:fw0McLecf/G5NFwddCRmDckU6yovtk1YsgWIoepMbYo= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/cascadia v0.0.0-20161224141413-349dd0209470 h1:4jHLmof+Hba81591gfH5xYA8QXzuvgksxwPNrmjR2BA= -github.com/andybalholm/cascadia v0.0.0-20161224141413-349dd0209470/go.mod h1:3I+3V7B6gTBYfdpYgIG2ymALS9H+5VDKUl3lHH7ToM4= +github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blevesearch/bleve v0.0.0-20190214220507-05d86ea8f6e3 h1:vinCy/rcjbtxWnMiw11CbMKcuyNi+y4L4MbZUpk7m4M= @@ -42,21 +83,36 @@ github.com/blevesearch/segment v0.0.0-20160105220820-db70c57796cc h1:7OfDAkuAGx7 github.com/blevesearch/segment v0.0.0-20160105220820-db70c57796cc/go.mod h1:IInt5XRvpiGE09KOk9mmCMLjHhydIhNPKPPFLFBB7L8= github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26 h1:NGpwhs9FOwddM6TptNrq2ycby4s24TcppSe5uG4DA/Q= github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a h1:k5TuEkqEYCRs8+66WdOkswWOj+L/YbP5ruainvn94wg= -github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA= +github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f h1:REH9VH5ubNR0skLaOxK7TRJeRbE2dDfvaouQo8FsRcA= github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f/go.mod h1:6QaC0vFoKWYDth94dHFNgRT2YkT5FHdQp/Yx15aAAi0= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c h1:K4FIibkr4//ziZKOKmt4RL0YImuTjLLBtwElf+F2lSQ= -github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= -github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8= -github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= +github.com/corbym/gocrest v1.0.3 h1:gwEdq6RkTmq+09CTuM29DfKOCtZ7G7bcyxs3IZ6EVdU= +github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d h1:XMf4E1U+b9E3ElF0mjvfXZdflBRZz4gLp16nQ/QSHQM= +github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= +github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b h1:bZ9rKU2/V8sY+NulSfxDOnXTWcs1rySqdF1sVepihvo= +github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= github.com/couchbase/vellum v0.0.0-20190111184608-e91b68ff3efe h1:2o6Y7KMjJNsuMTF8f2H2eTKRhqH7+bQbjr+D+LnhE5M= github.com/couchbase/vellum v0.0.0-20190111184608-e91b68ff3efe/go.mod h1:prYTC8EgTu3gwbqJihkud9zRXISvyulAplQ6exdCo1g= -github.com/couchbaselabs/go-couchbase v0.0.0-20190117181324-d904413d884d h1:lsBRLJe/ET6DjCaRblGwls80dOcOzhFVNJrO6uaMrMQ= -github.com/couchbaselabs/go-couchbase v0.0.0-20190117181324-d904413d884d/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc= +github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7 h1:1XjEY/gnjQ+AfXef2U6dxCquhiRzkEpxZuWqs+QxTL8= +github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso= @@ -66,13 +122,20 @@ github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20180315180555-6a30f4e59a44 h1:DWxZh2sImfCFn/79OUBhzFkPTKnsdDzXH/JTxpw5n6w= -github.com/denisenkom/go-mssqldb v0.0.0-20180315180555-6a30f4e59a44/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538 h1:bpWCJ5MddHsv4Xtl3azkK89mZzd/vvut32mvAnKbyUA= +github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/etcd-io/bbolt v1.3.2 h1:RLRQ0TKLX7DlBRXAJHvbmXL17Q3KNnTBtZ9B6Qo+/Y0= @@ -99,73 +162,155 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd h1:r04MMPyLHj/QwZuMJ5+7tJcBr1AQjpiAK/rZWRrQT7o= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e h1:SiEs4J3BKVIeaWrH3tKaz3QLZhJ68iJ/A4xrzIoE5+Y= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-macaron/binding v0.0.0-20160711225916-9440f336b443 h1:i801KPR7j76uRMLLlGVyb0hiYbgX1FM5+ur81TJWzIw= -github.com/go-macaron/binding v0.0.0-20160711225916-9440f336b443/go.mod h1:u+H6rwW+HQwUL+w5uaEJSpIlVZDye1o9MB4Su0JfRfM= -github.com/go-macaron/cache v0.0.0-20151013081102-561735312776 h1:UYIHS1r0WotqB5cIa0PAiV0m6GzD9rDBcn4alp5JgCw= -github.com/go-macaron/cache v0.0.0-20151013081102-561735312776/go.mod h1:hHAsZm/oBZVcY+S7qdQL6Vbg5VrXF6RuKGuqsszt3Ok= -github.com/go-macaron/captcha v0.0.0-20190710000913-8dc5911259df h1:MdgvtI3Y1u/DHNj7xUGOqAv+KGoTikjy8xQtCm12L78= -github.com/go-macaron/captcha v0.0.0-20190710000913-8dc5911259df/go.mod h1:j9TJ+0nwUOWBvNnm0bheHIPFf3cC62EQo7n7O6PbjZA= -github.com/go-macaron/cors v0.0.0-20190309005821-6fd6a9bfe14e9 h1:A0QGzY6UHHEil0I2e7C21JenNNG0mmrj5d9SFWTlgr8= -github.com/go-macaron/cors v0.0.0-20190309005821-6fd6a9bfe14e9/go.mod h1:utmMRnVIrXPSfA9MFcpIYKEpKawjKxf62vv62k4707E= -github.com/go-macaron/csrf v0.0.0-20180426211211-503617c6b372 h1:acrx8CnDmlKl+BPoOOLEK9Ko+SrWFB5pxRuGkKj4iqo= -github.com/go-macaron/csrf v0.0.0-20180426211211-503617c6b372/go.mod h1:oZGMxI7MBnicI0jJqJvH4qQzyrWKhtiKxLSJKHC+ydc= -github.com/go-macaron/i18n v0.0.0-20160612092837-ef57533c3b0f h1:wDKrZFc9pYJlqFOf7EzGbFMrSFFtyHt3plr2uTdo8Rg= -github.com/go-macaron/i18n v0.0.0-20160612092837-ef57533c3b0f/go.mod h1:MePM/dStkAh+PNzAdNSNl4SGDM2EZvZGken+KpJhM7s= -github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= -github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= -github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193 h1:z/nqwd+ql/r6Q3QGnwNd6B89UjPytM0be5pDQV9TuWw= -github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193/go.mod h1:ScEJm9Gk+ez5JJTml5WlBIqavAfuE5nF8e4Gvyz/X+A= -github.com/go-macaron/toolbox v0.0.0-20180818072302-a77f45a7ce90 h1:3wYKrRg9IjUMfaf3H0Hh7M5Li9ge79Y7aw2yujHa2jQ= -github.com/go-macaron/toolbox v0.0.0-20180818072302-a77f45a7ce90/go.mod h1:Ut/NmkIMGVYlEdJBzEZgWVWG5ZpYS9BLmUgXfAgi+qM= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4 h1:1TjOzrWkj+9BrjnM1yPAICbaoC0FyfD49oVkTBrSSa0= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2 h1:rf5ArTHmIJxyV5Oiks+Su0mUens1+AjpkPoWr5xFRcI= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3 h1:jwIoahqCmaA5OBoc/B+1+Mu2L0Gr8xYQnbeyQEo/7b0= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.5 h1:h4Zk7oTfB3ZYM2oMNliQvL+3BrDstTIX8lqP7yaYCuI= +github.com/go-openapi/runtime v0.19.5/go.mod h1:WIH6IYPXOrtgTClTV8xzdrD20jBlrK25D0aQbdSlqp8= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2 h1:clPGfBnJohokno0e+d7hs6Yocrzjlgz6EsQSDncCRnE= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2 h1:ky5l57HjyVRrsJfd2+Ro5Z9PjGuKbsmftwyMtk8H7js= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww/kCg4= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-xorm/core v0.6.0 h1:tp6hX+ku4OD9khFZS8VGBDRY3kfVCtelPfmkgCyHxL0= -github.com/go-xorm/core v0.6.0/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8= +github.com/go-swagger/go-swagger v0.20.1 h1:37XFujv7lYHLOKawfzLDg4STwwgB5zhPjodN33asJto= +github.com/go-swagger/go-swagger v0.20.1/go.mod h1:LoTpv6FHYXUvYnECHNLvi/qYNybk0d9wkJGH1cTANWE= +github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= +github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= -github.com/go-xorm/xorm v0.7.3-0.20190620151208-f1b4f8368459 h1:JGEuhH169J7Wtm1hN/HFOGENsAq+6FDHfuhGEZj/1e4= -github.com/go-xorm/xorm v0.7.3-0.20190620151208-f1b4f8368459/go.mod h1:UK1YDlWscDspd23xW9HC24749jhvwO6riZ/HUt3gbHQ= +github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0= +github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 h1:deE7ritpK04PgtpyVOS2TYcQEld9qLCD5b5EbVNOuLA= github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:YgYOrVn3Nj9Tq0EvjmFbphRytDj7JNRoWSStJZWDJTQ= -github.com/gogits/cron v0.0.0-20160810035002-7f3990acf183 h1:EBTlva3AOSb80G3JSwY6ZMdILEZJ1JKuewrbqrNjWuE= -github.com/gogits/cron v0.0.0-20160810035002-7f3990acf183/go.mod h1:pX+V62FFmklia2fhP3P4YSY6iJdPO5jIDKFQ5fEd5QE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8= +github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v24 v24.0.1 h1:KCt1LjMJEey1qvPXxa9SjaWxwTsCWSq6p2Ju57UR4Q4= github.com/google/go-github/v24 v24.0.1/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY= @@ -174,6 +319,18 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE= github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/issue9/assert v1.3.2 h1:IaTa37u4m1fUuTH9K9ldO5IONKVDXjLiUO1T9vj0OF0= @@ -182,8 +339,7 @@ github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c h1:A/PDn117UYld5m github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c/go.mod h1:5mTb/PQNkqmq2x3IxlQZE0aSnTksJg7fg/oWmJ5SKXQ= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= -github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d h1:ig/iUfDDg06RVW8OMby+GrmW6K2nPO3AFHlEIdvJSd4= github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -195,16 +351,23 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 h1:vE7J1m7cCpiRVEIr1B5ccDxRpbPsWT5JU3if2Di5nE4= github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= -github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/keybase/go-crypto v0.0.0-20170605145657-00ac4db533f6 h1:9mszGwKDxHEY2cy+9XxCQKWIfkGPSAEFrcN8ghzyAKg= github.com/keybase/go-crypto v0.0.0-20170605145657-00ac4db533f6/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v0.0.0-20161025140425-8df558b6cb6f h1:tCnZKEmDovgV4jmsclh6CuKk9AMzTzyVWfejgkgccVg= github.com/klauspost/compress v0.0.0-20161025140425-8df558b6cb6f/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc h1:WW8B7p7QBnFlqRVv/k6ro/S8Z7tCnYjJHcQNScx9YVs= @@ -212,18 +375,20 @@ github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc/go.mod h1:Pj4uuM52 github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lafriks/xormstore v1.0.0 h1:P/IJzNSIpjXl/Up3o2Td5ZU/x4v6DEKLMaPQJGtmJCk= -github.com/lafriks/xormstore v1.0.0/go.mod h1:dD8vHNRfEp3Uy+JvX9cMi2SXcRKJ0x4pYKsZuy843Ic= -github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= +github.com/lafriks/xormstore v1.3.1 h1:KpzRUamSV3zmA85Kzw+PZOU9wgMbYsNzuDzLuBMbxpA= +github.com/lafriks/xormstore v1.3.1/go.mod h1:qALRD4Vto2Ic7/A5eplMpu5V62mugtSqFysRwz8FETs= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY= github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ= github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e h1:GSprKUrG9wNgwQgROvjPGXmcZrg4OLslOuZGB0uJjx8= @@ -232,15 +397,26 @@ github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEg github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ= github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af h1:UaWHNBdukWrSG3DRvHFR/hyfg681fceqQDYVTBncKfQ= github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= -github.com/markbates/goth v1.49.0 h1:qQ4Ti4WaqAxNAggOC+4s5M85sMVfMJwQn/Xkp73wfgI= -github.com/markbates/goth v1.49.0/go.mod h1:zZmAw0Es0Dpm7TT/4AdN14QrkiWLMrrU9Xei1o+/mdA= +github.com/markbates/goth v1.56.0 h1:XEYedCgMNz5pi3ojXI8z2XUmXtBnMeuKUpx4Z6HlNj8= +github.com/markbates/goth v1.56.0/go.mod h1:zZmAw0Es0Dpm7TT/4AdN14QrkiWLMrrU9Xei1o+/mdA= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d h1:m+dSK37rFf2fqppZhg15yI2IwC9BtucBiRwSDm9VL8g= github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 h1:Pijfgr7ZuvX7QIQiEwLdRVr3RoMG+i0SbBO1Qu+7yVk= @@ -249,6 +425,12 @@ github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a h1:d18LCO3 github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= @@ -256,6 +438,7 @@ github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc h1:z1PgdCCmYYVL0BoJTUgmAq1p7ca8fzYIPsNyfsN3xAU= github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -264,36 +447,62 @@ github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e h1:ApqncJ84HYN8x8x5WV1T1YWDuPRF/0aXZhr91LnRMCQ= github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78= +github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 h1:YDeskXpkNDhPdWN3REluVa46HQOVuVkjkd2sWnrABNQ= github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff h1:g9ZlAHmkc/h5So+OjNCkZWh+FjuKEOOOoyRkqlGA8+c= github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= @@ -310,135 +519,281 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20160918041101-1dba4b3954bc h1: github.com/shurcooL/sanitized_anchor_name v0.0.0-20160918041101-1dba4b3954bc/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68= github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY= +github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= +github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys= -github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= +github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 h1:JNEGSiWg6D3lcBCMCBqN3ELniXujt+0QNHLhNnO0w3s= github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 h1:AwmkkZT+TucFotNCL+aNJ/0KCMsRtlXN9fs8uoOMSRk= github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 h1:HOxvxvnntLiPn123Fk+twfUhCQdMDaqmb0cclArW0T0= github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200 h1:ZVvr38DYEyOPyelySqvF0I9I++85NnUMsWkroBDS4fs= github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= +github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1 h1:SpoCl3+Pta5/ubQyF+Fmx65obtpfkyzeaOIneCE3MTw= +github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU= +github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM= +github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE= +github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= +github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 h1:Z79lyIznnziKADUf0J7EP8Z4ZL7YJDiPuaazlfUBSy4= +github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 h1:E8u341JM/N8LCnPXBV6ZFD1RKo/j+qHl1XOqSV+GstA= github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 h1:HsIQ6yAjfjQ3IxPGrTusxp6Qxn92gNVq2x5CbvQvx3w= github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53/go.mod h1:f6elajwZV+xceiaqgRL090YzLEDGSbqr3poGL3ZgXYo= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.0 h1:aeOqSrhl9eDRAap/3T5pCfMBEBxZ0vuXBP+RMtp2KX8= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU= -golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ= +golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190814143026-e8b3e6111d02/go.mod h1:z5wpDCy2wbnXyFdvEuY3LhY9gBUL86/IOILm+Hsjx+E= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190909003024-a7b16738d86b h1:XfVGCX+0T4WOStkaOsJRllbsiImhB2jgVBGc9L0lPGc= +golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759 h1:TMrx+Qdx7uJAeUbv15N72h5Hmyb5+VDjEiMufAEAM04= -golang.org/x/oauth2 v0.0.0-20181101160152-c453e0c75759/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 h1:Dl2hc890lrizvUppGbRWhnIh2f8jOTCQpY5IKWRS0oM= -golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b h1:3S2h5FadpNr0zUUCVZjlKIEYF+KaX/OBplTGo89CYHI= +golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190620154339-431033348dd0 h1:qUGDNmGEM+ZBtwF9vuzEv+9nQQPL+l/oNBZ+DCDTAyo= -golang.org/x/tools v0.0.0-20190620154339-431033348dd0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190820033707-85edb9ef3283/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910221609-7f5965fd7709 h1:2Ep+/X9v6ij0U1YP++QCLyZgWQHUwVJZkC6tSrH1Iuw= +golang.org/x/tools v0.0.0-20190910221609-7f5965fd7709/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= +google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.4 h1:WiKh4+/eMB2HaY7QhCfW/R7MuRAoA8QMCSJA6jP5/fo= +google.golang.org/appengine v1.6.4/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 h1:nn6Zav2sOQHCFJHEspya8KqxhFwKci30UxHy3HXPTyQ= gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= -gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU= -gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/editorconfig/editorconfig-core-go.v1 v1.2.0 h1:CO465/foR4+bY1xNYjZEl6l8By1g/iMsImoruxfEt84= -gopkg.in/editorconfig/editorconfig-core-go.v1 v1.2.0/go.mod h1:s2mQFI9McjArkyCwyEwU//+luQENTnD/Lfb/7Sj3/kQ= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/editorconfig/editorconfig-core-go.v1 v1.3.0 h1:oxOEwvhxLMpWpN+0pb2r9TWrM0DCFBHxbuIlS27tmFg= +gopkg.in/editorconfig/editorconfig-core-go.v1 v1.3.0/go.mod h1:s2mQFI9McjArkyCwyEwU//+luQENTnD/Lfb/7Sj3/kQ= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= -gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= +gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag= +gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= -gopkg.in/macaron.v1 v1.3.2 h1:AvWIaPmwBUA87/OWzePkoxeaw6YJWDfBt1pDFPBnLf8= -gopkg.in/macaron.v1 v1.3.2/go.mod h1:PrsiawTWAGZs6wFbT5hlr7SQ2Ns9h7cUVtcUu4lQOVo= -gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs= -gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU= -gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= -gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.12.0 h1:CKgvBCJCcdfNnyXPYI4Cp8PaDDAmAPEN0CtfEdEAbd8= -gopkg.in/src-d/go-git.v4 v4.12.0/go.mod h1:zjlNnzc1Wjn43v3Mtii7RVxiReNP0fIu9npcXKzuNp4= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= gopkg.in/testfixtures.v2 v2.5.0 h1:N08B7l2GzFQenyYbzqthDnKAA+cmb17iAZhhFxr7JHw= @@ -447,15 +802,24 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= mvdan.cc/xurls/v2 v2.0.0 h1:r1zSOSNS/kqtpmATyMMMvaZ4/djsesbYz5kr0+qMRWc= mvdan.cc/xurls/v2 v2.0.0/go.mod h1:2/webFPYOXN9jp/lzuj0zuAVlF+9g4KPFJANH1oJhRU= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a h1:8q33ShxKXRwQ7JVd1ZnhIU3hZhwwn0Le+4fTeAackuM= strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= -xorm.io/builder v0.3.5 h1:EilU39fvWDxjb1cDaELpYhsF+zziRBhew8xk4pngO+A= -xorm.io/builder v0.3.5/go.mod h1:ZFbByS/KxZI1FKRjL05PyJ4YrK2bcxlUaAxdum5aTR8= -xorm.io/core v0.6.3 h1:n1NhVZt1s2oLw1BZfX2ocIJsHyso259uPgg63BGr37M= -xorm.io/core v0.6.3/go.mod h1:8kz/C6arVW/O9vk3PgCiMJO2hIAm1UcuOL3dSPyZ2qo= +xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= +xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= +xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb h1:msX3zG3BPl8Ti+LDzP33/9K7BzO/WqFXk610K1kYKfo= +xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= +xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= +xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= diff --git a/integrations/README.md b/integrations/README.md index a88cec6e4f..6ffd76815c 100644 --- a/integrations/README.md +++ b/integrations/README.md @@ -27,31 +27,45 @@ make test-sqlite ## Run mysql integrations tests Setup a mysql database inside docker ``` -docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" --rm --name mysql mysql:5.7 #(just ctrl-c to stop db and clean the container) +docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:5.7 #(just ctrl-c to stop db and clean the container) ``` Start tests based on the database container ``` -TEST_MYSQL_HOST="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql):3306" TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-mysql +TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-mysql ``` ## Run pgsql integrations tests Setup a pgsql database inside docker ``` -docker run -e "POSTGRES_DB=test" --rm --name pgsql postgres:9.5 #(just ctrl-c to stop db and clean the container) +docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:9.5 #(just ctrl-c to stop db and clean the container) ``` Start tests based on the database container ``` -TEST_PGSQL_HOST=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' pgsql) TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +``` + +## Run mssql integrations tests +Setup a mssql database inside docker +``` +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) +``` +Start tests based on the database container +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql ``` ## Running individual tests -Example command to run GPG test with sqlite backend: +Example command to run GPG test: + +For sqlite: + ``` -go test -c code.gitea.io/gitea/integrations \ - -o integrations.sqlite.test -tags 'sqlite' && - GITEA_ROOT="$GOPATH/src/code.gitea.io/gitea" \ - GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test \ - -test.v -test.run GPG +make test-sqlite#GPG ``` +For other databases(replace MSSQL to MYSQL, MYSQL8, PGSQL): + +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql#GPG +``` \ No newline at end of file diff --git a/integrations/README_ZH.md b/integrations/README_ZH.md index 62d9cccfcf..fd31764560 100644 --- a/integrations/README_ZH.md +++ b/integrations/README_ZH.md @@ -26,31 +26,46 @@ make test-sqlite ## 如何使用 mysql 数据库进行集成测试 首先在docker容器里部署一个 mysql 数据库 ``` -docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" --rm --name mysql mysql:5.7 #(just ctrl-c to stop db and clean the container) +docker run -e "MYSQL_DATABASE=test" -e "MYSQL_ALLOW_EMPTY_PASSWORD=yes" -p 3306:3306 --rm --name mysql mysql:5.7 #(just ctrl-c to stop db and clean the container) ``` 之后便可以基于这个数据库进行集成测试 ``` -TEST_MYSQL_HOST="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql):3306" TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-mysql +TEST_MYSQL_HOST=localhost:3306 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=root TEST_MYSQL_PASSWORD='' make test-mysql ``` ## 如何使用 pgsql 数据库进行集成测试 同上,首先在 docker 容器里部署一个 pgsql 数据库 ``` -docker run -e "POSTGRES_DB=test" --rm --name pgsql postgres:9.5 #(just ctrl-c to stop db and clean the container) +docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:9.5 #(just ctrl-c to stop db and clean the container) ``` 之后便可以基于这个数据库进行集成测试 ``` -TEST_PGSQL_HOST=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' pgsql) TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql +``` + +## Run mssql integrations tests +同上,首先在 docker 容器里部署一个 mssql 数据库 +``` +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) +``` +之后便可以基于这个数据库进行集成测试 +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql ``` ## 如何进行自定义的集成测试 -下面的示例展示了怎样基于 sqlite 数据库进行 GPG 测试: +下面的示例展示了怎样在集成测试中只进行 GPG 测试: + +sqlite 数据库: + ``` -go test -c code.gitea.io/gitea/integrations \ - -o integrations.sqlite.test -tags 'sqlite' && - GITEA_ROOT="$GOPATH/src/code.gitea.io/gitea" \ - GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test \ - -test.v -test.run GPG +make test-sqlite#GPG +``` + +其它数据库(把 MSSQL 替换为 MYSQL, MYSQL8, PGSQL): + +``` +TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql#GPG ``` diff --git a/integrations/api_helper_for_declarative_test.go b/integrations/api_helper_for_declarative_test.go index 42c271e3af..805a986ae3 100644 --- a/integrations/api_helper_for_declarative_test.go +++ b/integrations/api_helper_for_declarative_test.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" api "code.gitea.io/gitea/modules/structs" + "github.com/stretchr/testify/assert" ) diff --git a/integrations/api_keys_test.go b/integrations/api_keys_test.go index ca3d4b2d7a..d9686a063c 100644 --- a/integrations/api_keys_test.go +++ b/integrations/api_keys_test.go @@ -10,10 +10,10 @@ import ( "net/url" "testing" - "github.com/stretchr/testify/assert" - "code.gitea.io/gitea/models" api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" ) func TestViewDeployKeysNoLogin(t *testing.T) { diff --git a/integrations/api_repo_edit_test.go b/integrations/api_repo_edit_test.go index 3b2c916ab0..c1b513d075 100644 --- a/integrations/api_repo_edit_test.go +++ b/integrations/api_repo_edit_test.go @@ -23,12 +23,35 @@ func getRepoEditOptionFromRepo(repo *models.Repository) *api.EditRepoOption { website := repo.Website private := repo.IsPrivate hasIssues := false - if _, err := repo.GetUnit(models.UnitTypeIssues); err == nil { + var internalTracker *api.InternalTracker + var externalTracker *api.ExternalTracker + if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil { + config := unit.IssuesConfig() hasIssues = true + internalTracker = &api.InternalTracker{ + EnableTimeTracker: config.EnableTimetracker, + AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime, + EnableIssueDependencies: config.EnableDependencies, + } + } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil { + config := unit.ExternalTrackerConfig() + hasIssues = true + externalTracker = &api.ExternalTracker{ + ExternalTrackerURL: config.ExternalTrackerURL, + ExternalTrackerFormat: config.ExternalTrackerFormat, + ExternalTrackerStyle: config.ExternalTrackerStyle, + } } hasWiki := false + var externalWiki *api.ExternalWiki if _, err := repo.GetUnit(models.UnitTypeWiki); err == nil { hasWiki = true + } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil { + hasWiki = true + config := unit.ExternalWikiConfig() + externalWiki = &api.ExternalWiki{ + ExternalWikiURL: config.ExternalWikiURL, + } } defaultBranch := repo.DefaultBranch hasPullRequests := false @@ -53,7 +76,10 @@ func getRepoEditOptionFromRepo(repo *models.Repository) *api.EditRepoOption { Website: &website, Private: &private, HasIssues: &hasIssues, + ExternalTracker: externalTracker, + InternalTracker: internalTracker, HasWiki: &hasWiki, + ExternalWiki: externalWiki, DefaultBranch: &defaultBranch, HasPullRequests: &hasPullRequests, IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, @@ -143,16 +169,94 @@ func TestAPIRepoEdit(t *testing.T) { assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived) assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private) assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki) + + //Test editing repo1 to use internal issue and wiki (default) + *repoEditOption.HasIssues = true + repoEditOption.ExternalTracker = nil + repoEditOption.InternalTracker = &api.InternalTracker{ + EnableTimeTracker: false, + AllowOnlyContributorsToTrackTime: false, + EnableIssueDependencies: false, + } + *repoEditOption.HasWiki = true + repoEditOption.ExternalWiki = nil + url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check repo1 was written to database + repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1editedOption = getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repo1editedOption.HasIssues, true) + assert.Nil(t, repo1editedOption.ExternalTracker) + assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker) + assert.Equal(t, *repo1editedOption.HasWiki, true) + assert.Nil(t, repo1editedOption.ExternalWiki) + + //Test editing repo1 to use external issue and wiki + repoEditOption.ExternalTracker = &api.ExternalTracker{ + ExternalTrackerURL: "http://www.somewebsite.com", + ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}", + ExternalTrackerStyle: "alphanumeric", + } + repoEditOption.ExternalWiki = &api.ExternalWiki{ + ExternalWikiURL: "http://www.somewebsite.com", + } + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check repo1 was written to database + repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1editedOption = getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repo1editedOption.HasIssues, true) + assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker) + assert.Equal(t, *repo1editedOption.HasWiki, true) + assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki) + + // Do some tests with invalid URL for external tracker and wiki + repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com" + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com" + repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}" + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}" + repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com" + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + //Test small repo change through API with issue and wiki option not set; They shall not be touched. + *repoEditOption.Description = "small change" + repoEditOption.HasIssues = nil + repoEditOption.ExternalTracker = nil + repoEditOption.HasWiki = nil + repoEditOption.ExternalWiki = nil + req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repo) + assert.NotNil(t, repo) + // check repo1 was written to database + repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) + repo1editedOption = getRepoEditOptionFromRepo(repo1edited) + assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description) + assert.Equal(t, *repo1editedOption.HasIssues, true) + assert.NotNil(t, *repo1editedOption.ExternalTracker) + assert.Equal(t, *repo1editedOption.HasWiki, true) + assert.NotNil(t, *repo1editedOption.ExternalWiki) + // reset repo in db url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) - resp = session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) // Test editing a non-existing repo name := "repodoesnotexist" url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, name, token2) req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{Name: &name}) - resp = session.MakeRequest(t, req, http.StatusNotFound) + _ = session.MakeRequest(t, req, http.StatusNotFound) // Test editing repo16 by user4 who does not have write access origRepoEditOption = getRepoEditOptionFromRepo(repo16) @@ -166,18 +270,18 @@ func TestAPIRepoEdit(t *testing.T) { repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) - resp = session.MakeRequest(t, req, http.StatusNotFound) + _ = session.MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns origRepoEditOption = getRepoEditOptionFromRepo(repo16) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) - resp = session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) // reset repo in db url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) - resp = session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) // Test making a repo public that is private repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) @@ -188,14 +292,14 @@ func TestAPIRepoEdit(t *testing.T) { } url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) - resp = session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.False(t, repo16.IsPrivate) // Make it private again private = true repoEditOption.Private = &private req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) - resp = session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) // Test using org repo "user3/repo3" where user2 is a collaborator origRepoEditOption = getRepoEditOptionFromRepo(repo3) @@ -206,7 +310,7 @@ func TestAPIRepoEdit(t *testing.T) { // reset repo in db url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) - resp = session.MakeRequest(t, req, http.StatusOK) + _ = session.MakeRequest(t, req, http.StatusOK) // Test using org repo "user3/repo3" with no user token origRepoEditOption = getRepoEditOptionFromRepo(repo3) diff --git a/integrations/api_repo_git_commits_test.go b/integrations/api_repo_git_commits_test.go index 587e9de5b2..16db1e871c 100644 --- a/integrations/api_repo_git_commits_test.go +++ b/integrations/api_repo_git_commits_test.go @@ -9,6 +9,9 @@ import ( "testing" "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" ) func TestAPIReposGitCommits(t *testing.T) { @@ -30,3 +33,58 @@ func TestAPIReposGitCommits(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/unknown?token="+token, user.Name) session.MakeRequest(t, req, http.StatusNotFound) } + +func TestAPIReposGitCommitList(t *testing.T) { + prepareTestEnv(t) + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting commits (Page 1) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token, user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Equal(t, 3, len(apiData)) + assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) + assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) + assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) +} + +func TestAPIReposGitCommitListPage2Empty(t *testing.T) { + prepareTestEnv(t) + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting commits (Page=2) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&page=2", user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Equal(t, 0, len(apiData)) +} + +func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { + prepareTestEnv(t) + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + // Test getting commits (Page=1, Branch=good-sign) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&sha=good-sign", user.Name) + resp := session.MakeRequest(t, req, http.StatusOK) + + var apiData []api.Commit + DecodeJSON(t, resp, &apiData) + + assert.Equal(t, 1, len(apiData)) + assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) +} diff --git a/integrations/api_repo_lfs_locks_test.go b/integrations/api_repo_lfs_locks_test.go index 657933dd5c..4fe92b33b5 100644 --- a/integrations/api_repo_lfs_locks_test.go +++ b/integrations/api_repo_lfs_locks_test.go @@ -104,8 +104,11 @@ func TestAPILFSLocksLogged(t *testing.T) { req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) req.Header.Set("Accept", "application/vnd.git-lfs+json") req.Header.Set("Content-Type", "application/vnd.git-lfs+json") - session.MakeRequest(t, req, test.httpResult) + resp := session.MakeRequest(t, req, test.httpResult) if len(test.addTime) > 0 { + var lfsLock api.LFSLockResponse + DecodeJSON(t, resp, &lfsLock) + assert.EqualValues(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) //locked at should be rounded to second for _, id := range test.addTime { resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) } @@ -123,7 +126,8 @@ func TestAPILFSLocksLogged(t *testing.T) { assert.Len(t, lfsLocks.Locks, test.totalCount) for i, lock := range lfsLocks.Locks { assert.EqualValues(t, test.locksOwners[i].DisplayName(), lock.Owner.Name) - assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 3*time.Second) + assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) + assert.EqualValues(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) //locked at should be rounded to second } req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 8dedfd1ae0..60fe4a3649 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -70,9 +70,9 @@ func TestAPISearchRepo(t *testing.T) { expectedResults }{ {name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ - nil: {count: 21}, - user: {count: 21}, - user2: {count: 21}}, + nil: {count: 22}, + user: {count: 22}, + user2: {count: 22}}, }, {name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ nil: {count: 10}, diff --git a/integrations/api_repo_topic_test.go b/integrations/api_repo_topic_test.go new file mode 100644 index 0000000000..34c33d1b25 --- /dev/null +++ b/integrations/api_repo_topic_test.go @@ -0,0 +1,124 @@ +// Copyright 2019 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 ( + "fmt" + "net/http" + "testing" + + "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIRepoTopic(t *testing.T) { + prepareTestEnv(t) + user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of repo2 + user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of repo3 + user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // write access to repo 3 + repo2 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository) + repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) + + // Get user2's token + session := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session) + + // Test read topics using login + url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) + req := NewRequest(t, "GET", url) + res := session.MakeRequest(t, req, http.StatusOK) + var topics *api.TopicName + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) + + // Log out user2 + session = emptyTestSession(t) + url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user2.Name, repo2.Name, token2) + + // Test delete a topic + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2) + res = session.MakeRequest(t, req, http.StatusNoContent) + + // Test add an existing topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Golang", token2) + res = session.MakeRequest(t, req, http.StatusNoContent) + + // Test add a topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "topicName3", token2) + res = session.MakeRequest(t, req, http.StatusNoContent) + + // Test read topics using token + req = NewRequest(t, "GET", url) + res = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) + + // Test replace topics + newTopics := []string{" windows ", " ", "MAC "} + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + res = session.MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", url) + res = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + + // Test replace topics with something invalid + newTopics = []string{"topicname1", "topicname2", "topicname!"} + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + res = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + req = NewRequest(t, "GET", url) + res = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + + // Test with some topics multiple times, less than 25 unique + newTopics = []string{"t1", "t2", "t1", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "t13", "t14", "t15", "t16", "17", "t18", "t19", "t20", "t21", "t22", "t23", "t24", "t25"} + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + res = session.MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", url) + res = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Equal(t, 25, len(topics.TopicNames)) + + // Test writing more topics than allowed + newTopics = append(newTopics, "t26") + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }) + res = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test add a topic when there is already maximum + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "t26", token2) + res = session.MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test delete a topic that repo doesn't have + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2) + res = session.MakeRequest(t, req, http.StatusNotFound) + + // Get user4's token + session = loginUser(t, user4.Name) + token4 := getTokenForLoggedInUser(t, session) + session = emptyTestSession(t) + + // Test read topics with write access + url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user3.Name, repo3.Name, token4) + req = NewRequest(t, "GET", url) + res = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Equal(t, 0, len(topics.TopicNames)) + + // Test add a topic to repo with write access (requires repo admin access) + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user3.Name, repo3.Name, "topicName", token4) + res = session.MakeRequest(t, req, http.StatusForbidden) + +} diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go index b3ea883658..e25ffdf7b1 100644 --- a/integrations/api_team_test.go +++ b/integrations/api_team_test.go @@ -41,10 +41,10 @@ func TestAPITeam(t *testing.T) { session = loginUser(t, user2.Name) token = getTokenForLoggedInUser(t, session) req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamUser.TeamID) - resp = session.MakeRequest(t, req, http.StatusForbidden) + _ = session.MakeRequest(t, req, http.StatusForbidden) req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID) - resp = session.MakeRequest(t, req, http.StatusUnauthorized) + _ = session.MakeRequest(t, req, http.StatusUnauthorized) // Get an admin user able to create, update and delete teams. user = models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) @@ -115,3 +115,32 @@ func checkTeamBean(t *testing.T, id int64, name, description string, includesAll assert.NoError(t, team.GetUnits(), "GetUnits") checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units) } + +type TeamSearchResults struct { + OK bool `json:"ok"` + Data []*api.Team `json:"data"` +} + +func TestAPITeamSearch(t *testing.T) { + prepareTestEnv(t) + + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + org := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) + + var results TeamSearchResults + + session := loginUser(t, user.Name) + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team") + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + assert.Equal(t, 1, len(results.Data)) + assert.Equal(t, "test_team", results.Data[0].Name) + + // no access if not organization member + user5 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) + session = loginUser(t, user5.Name) + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team") + resp = session.MakeRequest(t, req, http.StatusForbidden) + +} diff --git a/integrations/api_team_user_test.go b/integrations/api_team_user_test.go new file mode 100644 index 0000000000..70d52c1360 --- /dev/null +++ b/integrations/api_team_user_test.go @@ -0,0 +1,36 @@ +// Copyright 2019 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" + "time" + + "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/convert" + "github.com/stretchr/testify/assert" +) + +func TestAPITeamUser(t *testing.T) { + prepareTestEnv(t) + + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session) + req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1?token="+token) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", "/api/v1/teams/1/members/user2?token="+token) + resp := session.MakeRequest(t, req, http.StatusOK) + var user2 *api.User + DecodeJSON(t, resp, &user2) + user2.Created = user2.Created.In(time.Local) + user2.LastLogin = user2.LastLogin.In(time.Local) + user := models.AssertExistsAndLoadBean(t, &models.User{Name: "user2"}).(*models.User) + + assert.Equal(t, convert.ToUser(user, true, false), user2) +} diff --git a/integrations/api_user_heatmap_test.go b/integrations/api_user_heatmap_test.go index 5c65dc1bca..5245bb0a26 100644 --- a/integrations/api_user_heatmap_test.go +++ b/integrations/api_user_heatmap_test.go @@ -5,11 +5,13 @@ package integrations import ( - "code.gitea.io/gitea/models" "fmt" - "github.com/stretchr/testify/assert" "net/http" "testing" + + "code.gitea.io/gitea/models" + + "github.com/stretchr/testify/assert" ) func TestUserHeatmap(t *testing.T) { diff --git a/integrations/auth_ldap_test.go b/integrations/auth_ldap_test.go index 52fe0fd73f..9ea3184ae7 100644 --- a/integrations/auth_ldap_test.go +++ b/integrations/auth_ldap_test.go @@ -12,8 +12,8 @@ import ( "code.gitea.io/gitea/models" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) type ldapUser struct { diff --git a/integrations/branches_test.go b/integrations/branches_test.go index e74f338aa2..ea38821d21 100644 --- a/integrations/branches_test.go +++ b/integrations/branches_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/PuerkitoBio/goquery" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) func TestViewBranches(t *testing.T) { diff --git a/integrations/create_no_session_test.go b/integrations/create_no_session_test.go index 0cdf7e2310..2d21f25005 100644 --- a/integrations/create_no_session_test.go +++ b/integrations/create_no_session_test.go @@ -16,7 +16,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/routes" - "github.com/go-macaron/session" + "gitea.com/macaron/session" "github.com/stretchr/testify/assert" ) diff --git a/integrations/git_helper_for_declarative_test.go b/integrations/git_helper_for_declarative_test.go index 235f4b4a9b..628611d2d7 100644 --- a/integrations/git_helper_for_declarative_test.go +++ b/integrations/git_helper_for_declarative_test.go @@ -19,25 +19,30 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/ssh" - "github.com/Unknwon/com" + "github.com/stretchr/testify/assert" + "github.com/unknwon/com" ) func withKeyFile(t *testing.T, keyname string, callback func(string)) { - keyFile := filepath.Join(setting.AppDataPath, keyname) - err := ssh.GenKeyPair(keyFile) + + tmpDir, err := ioutil.TempDir("", "key-file") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + err = os.Chmod(tmpDir, 0700) + assert.NoError(t, err) + + keyFile := filepath.Join(tmpDir, keyname) + err = ssh.GenKeyPair(keyFile) assert.NoError(t, err) //Setup ssh wrapper os.Setenv("GIT_SSH_COMMAND", - "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i "+ - filepath.Join(setting.AppWorkPath, keyFile)) + "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i \""+keyFile+"\"") os.Setenv("GIT_SSH_VARIANT", "ssh") callback(keyFile) - - defer os.RemoveAll(keyFile) - defer os.RemoveAll(keyFile + ".pub") } func createSSHUrl(gitPath string, u *url.URL) *url.URL { diff --git a/integrations/gitea-repositories-meta/user2/glob.git/HEAD b/integrations/gitea-repositories-meta/user2/glob.git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/integrations/gitea-repositories-meta/user2/glob.git/config b/integrations/gitea-repositories-meta/user2/glob.git/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/integrations/gitea-repositories-meta/user2/glob.git/description b/integrations/gitea-repositories-meta/user2/glob.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/applypatch-msg.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/commit-msg.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/fsmonitor-watchman.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000000..e673bb3980 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/fsmonitor-watchman.sample @@ -0,0 +1,114 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 1) and a time in nanoseconds +# formatted as a string and outputs to stdout all files that have been +# modified since the given time. Paths must be relative to the root of +# the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $time) = @ARGV; + +# Check the hook interface version + +if ($version == 1) { + # convert nanoseconds to seconds + $time = int $time / 1000000000; +} else { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree; +if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $git_work_tree = Win32::GetCwd(); + $git_work_tree =~ tr/\\/\//; +} else { + require Cwd; + $git_work_tree = Cwd::cwd(); +} + +my $retry = 1; + +launch_watchman(); + +sub launch_watchman { + + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $time but were not transient (ie created after + # $time but no longer exist). + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + # + # The category of transient files that we want to ignore will have a + # creation clock (cclock) newer than $time_t value and will also not + # currently exist. + + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $time, + "fields": ["name"], + "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] + }] + END + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + my $json_pkg; + eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; + } or do { + require JSON::PP; + $json_pkg = "JSON::PP"; + }; + + my $o = $json_pkg->new->utf8->decode($response); + + if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { + print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; + $retry--; + qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + print "/\0"; + eval { launch_watchman() }; + exit 0; + } + + die "Watchman: $o->{error}.\n" . + "Falling back to scanning...\n" if $o->{error}; + + binmode STDOUT, ":utf8"; + local $, = "\0"; + print @{$o->{files}}; +} diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/post-update.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-applypatch.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-commit.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-commit.sample new file mode 100755 index 0000000000..6a75641638 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-push.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-rebase.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-rebase.sample new file mode 100755 index 0000000000..6cbef5c370 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-receive.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-receive.sample new file mode 100755 index 0000000000..a1fd29ec14 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/prepare-commit-msg.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..10fa14c5ab --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/integrations/gitea-repositories-meta/user2/glob.git/hooks/update.sample b/integrations/gitea-repositories-meta/user2/glob.git/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/integrations/gitea-repositories-meta/user2/glob.git/info/exclude b/integrations/gitea-repositories-meta/user2/glob.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/48/06cb9df135782b818c968c2fadbd2c150d23d6 b/integrations/gitea-repositories-meta/user2/glob.git/objects/48/06cb9df135782b818c968c2fadbd2c150d23d6 new file mode 100644 index 0000000000..a393a43282 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/48/06cb9df135782b818c968c2fadbd2c150d23d6 differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/59/fee614e09d1f1cd1e15e4b2a7e9c8873a81498 b/integrations/gitea-repositories-meta/user2/glob.git/objects/59/fee614e09d1f1cd1e15e4b2a7e9c8873a81498 new file mode 100644 index 0000000000..a55c8cc6e6 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/59/fee614e09d1f1cd1e15e4b2a7e9c8873a81498 differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/7c/8ac2f8d82a1eb5f6aaece6629ff11015f91eb4 b/integrations/gitea-repositories-meta/user2/glob.git/objects/7c/8ac2f8d82a1eb5f6aaece6629ff11015f91eb4 new file mode 100644 index 0000000000..d5176e68c6 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/7c/8ac2f8d82a1eb5f6aaece6629ff11015f91eb4 differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/8e/592e636d27ac144f92f7fe8c33631cbdea594d b/integrations/gitea-repositories-meta/user2/glob.git/objects/8e/592e636d27ac144f92f7fe8c33631cbdea594d new file mode 100644 index 0000000000..8034110ebc Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/8e/592e636d27ac144f92f7fe8c33631cbdea594d differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/95/aff026f99a9ab76fbd01decb63dd3dbc03e498 b/integrations/gitea-repositories-meta/user2/glob.git/objects/95/aff026f99a9ab76fbd01decb63dd3dbc03e498 new file mode 100644 index 0000000000..0883f2b3eb Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/95/aff026f99a9ab76fbd01decb63dd3dbc03e498 differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/ae/d1ffed24cc3cf9b80490795e893cae4bddd684 b/integrations/gitea-repositories-meta/user2/glob.git/objects/ae/d1ffed24cc3cf9b80490795e893cae4bddd684 new file mode 100644 index 0000000000..03fa05de22 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/ae/d1ffed24cc3cf9b80490795e893cae4bddd684 differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/bf/d6a6583f9a9ac59bd726c1df26c64a89427ede b/integrations/gitea-repositories-meta/user2/glob.git/objects/bf/d6a6583f9a9ac59bd726c1df26c64a89427ede new file mode 100644 index 0000000000..9475433e66 Binary files /dev/null and b/integrations/gitea-repositories-meta/user2/glob.git/objects/bf/d6a6583f9a9ac59bd726c1df26c64a89427ede differ diff --git a/integrations/gitea-repositories-meta/user2/glob.git/objects/c8/eb3b6c767ccb68411d0a1f6c769be69fb4d95a b/integrations/gitea-repositories-meta/user2/glob.git/objects/c8/eb3b6c767ccb68411d0a1f6c769be69fb4d95a new file mode 100644 index 0000000000..2b6297f726 --- /dev/null +++ b/integrations/gitea-repositories-meta/user2/glob.git/objects/c8/eb3b6c767ccb68411d0a1f6c769be69fb4d95a @@ -0,0 +1 @@ +x+)JMU03d040031QH+(a:㆖o 0 { ourSkip += skip[0] @@ -201,6 +202,7 @@ func (s *TestSession) GetCookie(name string) *http.Cookie { } func (s *TestSession) MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { + t.Helper() baseURL, err := url.Parse(setting.AppURL) assert.NoError(t, err) for _, c := range s.jar.Cookies(baseURL) { @@ -217,6 +219,7 @@ func (s *TestSession) MakeRequest(t testing.TB, req *http.Request, expectedStatu } func (s *TestSession) MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseRecorder { + t.Helper() baseURL, err := url.Parse(setting.AppURL) assert.NoError(t, err) for _, c := range s.jar.Cookies(baseURL) { @@ -237,6 +240,7 @@ const userPassword = "password" var loginSessionCache = make(map[string]*TestSession, 10) func emptyTestSession(t testing.TB) *TestSession { + t.Helper() jar, err := cookiejar.New(nil) assert.NoError(t, err) @@ -244,6 +248,7 @@ func emptyTestSession(t testing.TB) *TestSession { } func loginUser(t testing.TB, userName string) *TestSession { + t.Helper() if session, ok := loginSessionCache[userName]; ok { return session } @@ -253,6 +258,7 @@ func loginUser(t testing.TB, userName string) *TestSession { } func loginUserWithPassword(t testing.TB, userName, password string) *TestSession { + t.Helper() req := NewRequest(t, "GET", "/user/login") resp := MakeRequest(t, req, http.StatusOK) @@ -278,6 +284,7 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession } func getTokenForLoggedInUser(t testing.TB, session *TestSession) string { + t.Helper() req := NewRequest(t, "GET", "/user/settings/applications") resp := session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) @@ -294,14 +301,17 @@ func getTokenForLoggedInUser(t testing.TB, session *TestSession) string { } func NewRequest(t testing.TB, method, urlStr string) *http.Request { + t.Helper() return NewRequestWithBody(t, method, urlStr, nil) } func NewRequestf(t testing.TB, method, urlFormat string, args ...interface{}) *http.Request { + t.Helper() return NewRequest(t, method, fmt.Sprintf(urlFormat, args...)) } func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string]string) *http.Request { + t.Helper() urlValues := url.Values{} for key, value := range values { urlValues[key] = []string{value} @@ -312,6 +322,7 @@ func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string } func NewRequestWithJSON(t testing.TB, method, urlStr string, v interface{}) *http.Request { + t.Helper() jsonBytes, err := json.Marshal(v) assert.NoError(t, err) req := NewRequestWithBody(t, method, urlStr, bytes.NewBuffer(jsonBytes)) @@ -320,6 +331,7 @@ func NewRequestWithJSON(t testing.TB, method, urlStr string, v interface{}) *htt } func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *http.Request { + t.Helper() request, err := http.NewRequest(method, urlStr, body) assert.NoError(t, err) request.RequestURI = urlStr @@ -334,6 +346,7 @@ func AddBasicAuthHeader(request *http.Request, username string) *http.Request { const NoExpectedStatus = -1 func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { + t.Helper() recorder := httptest.NewRecorder() mac.ServeHTTP(recorder, req) if expectedStatus != NoExpectedStatus { @@ -346,6 +359,7 @@ func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest. } func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedStatus int) *NilResponseRecorder { + t.Helper() recorder := NewNilResponseRecorder() mac.ServeHTTP(recorder, req) if expectedStatus != NoExpectedStatus { @@ -359,6 +373,7 @@ func MakeRequestNilResponseRecorder(t testing.TB, req *http.Request, expectedSta // logUnexpectedResponse logs the contents of an unexpected response. func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) { + t.Helper() respBytes := recorder.Body.Bytes() if len(respBytes) == 0 { return @@ -381,11 +396,13 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) { } func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v interface{}) { + t.Helper() decoder := json.NewDecoder(resp.Body) assert.NoError(t, decoder.Decode(v)) } func GetCSRF(t testing.TB, session *TestSession, urlStr string) string { + t.Helper() req := NewRequest(t, "GET", urlStr) resp := session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 10084000db..0b153607ee 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -5,6 +5,7 @@ package integrations import ( + "fmt" "net/http" "path" "strconv" @@ -136,7 +137,7 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content return issueURL } -func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) { +func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 { req := NewRequest(t, "GET", issueURL) resp := session.MakeRequest(t, req, http.StatusOK) @@ -161,6 +162,13 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").Eq(commentCount).Text() assert.Equal(t, content, val) + + idAttr, has := htmlDoc.doc.Find(".comment-list .comments .comment").Eq(commentCount).Attr("id") + idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:] + assert.True(t, has) + id, err := strconv.Atoi(idStr) + assert.NoError(t, err) + return int64(id) } func TestNewIssue(t *testing.T) { @@ -184,3 +192,97 @@ func TestIssueCommentClose(t *testing.T) { val := htmlDoc.doc.Find(".comment-list .comments .comment .render-content p").First().Text() assert.Equal(t, "Description", val) } + +func TestIssueCrossReference(t *testing.T) { + prepareTestEnv(t) + + // Issue that will be referenced + _, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description") + + // Ref from issue title + issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description") + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNone}) + + // Edit title, neuter ref + testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref") + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNeutered}) + + // Ref from issue content + issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index)) + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNone}) + + // Edit content, neuter ref + testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref") + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNeutered}) + + // Ref from a comment + session := loginUser(t, "user2") + commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "") + comment := &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 1, + RefIssueID: issueRef.ID, + RefCommentID: commentID, + RefIsPull: false, + RefAction: models.XRefActionNone} + models.AssertExistsAndLoadBean(t, comment) + + // Ref from a different repository + issueRefURL, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index)) + models.AssertExistsAndLoadBean(t, &models.Comment{ + IssueID: issueBase.ID, + RefRepoID: 10, + RefIssueID: issueRef.ID, + RefCommentID: 0, + RefIsPull: false, + RefAction: models.XRefActionNone}) +} + +func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *models.Issue) { + session := loginUser(t, user) + issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content) + indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:] + index, err := strconv.Atoi(indexStr) + assert.NoError(t, err, "Invalid issue href: %s", issueURL) + issue := &models.Issue{RepoID: repoID, Index: int64(index)} + models.AssertExistsAndLoadBean(t, issue) + return issueURL, issue +} + +func testIssueChangeInfo(t *testing.T, user, issueURL, info string, value string) { + session := loginUser(t, user) + + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + info: value, + }) + _ = session.MakeRequest(t, req, http.StatusOK) +} diff --git a/integrations/lfs_getobject_test.go b/integrations/lfs_getobject_test.go index 567cf13b45..bb269d5eeb 100644 --- a/integrations/lfs_getobject_test.go +++ b/integrations/lfs_getobject_test.go @@ -18,9 +18,9 @@ import ( "code.gitea.io/gitea/modules/gzip" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" - "github.com/stretchr/testify/assert" gzipp "github.com/klauspost/compress/gzip" + "github.com/stretchr/testify/assert" ) func GenerateLFSOid(content io.Reader) (string, error) { @@ -39,7 +39,7 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string assert.NoError(t, err) var lfsMetaObject *models.LFSMetaObject - if setting.UsePostgreSQL { + if setting.Database.UsePostgreSQL { lfsMetaObject = &models.LFSMetaObject{ID: lfsID, Oid: oid, Size: int64(len(*content)), RepositoryID: repositoryID} } else { lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: int64(len(*content)), RepositoryID: repositoryID} diff --git a/integrations/migration-test/gitea-v1.3.3.sqlite3.sql.gz b/integrations/migration-test/gitea-v1.3.3.sqlite3.sql.gz new file mode 100644 index 0000000000..8375018d9f Binary files /dev/null and b/integrations/migration-test/gitea-v1.3.3.sqlite3.sql.gz differ diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go index 15b086c6e6..3b47f0d7fc 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/setting" "github.com/go-xorm/xorm" @@ -53,7 +54,7 @@ func initMigrationTest(t *testing.T) { setting.NewContext() setting.CheckLFSVersion() - models.LoadConfigs() + setting.InitDBConfig() setting.NewLogServices(true) } @@ -63,7 +64,7 @@ func availableVersions() ([]string, error) { return nil, err } defer migrationsDir.Close() - versionRE, err := regexp.Compile("gitea-v(?P.+)\\." + regexp.QuoteMeta(models.DbCfg.Type) + "\\.sql.gz") + versionRE, err := regexp.Compile("gitea-v(?P.+)\\." + regexp.QuoteMeta(setting.Database.Type) + "\\.sql.gz") if err != nil { return nil, err } @@ -84,7 +85,7 @@ func availableVersions() ([]string, error) { } func readSQLFromFile(version string) (string, error) { - filename := fmt.Sprintf("integrations/migration-test/gitea-v%s.%s.sql.gz", version, models.DbCfg.Type) + filename := fmt.Sprintf("integrations/migration-test/gitea-v%s.%s.sql.gz", version, setting.Database.Type) if _, err := os.Stat(filename); os.IsNotExist(err) { return "", nil @@ -106,24 +107,24 @@ func readSQLFromFile(version string) (string, error) { if err != nil { return "", err } - return string(base.RemoveBOMIfPresent(bytes)), nil + return string(charset.RemoveBOMIfPresent(bytes)), nil } func restoreOldDB(t *testing.T, version string) bool { data, err := readSQLFromFile(version) assert.NoError(t, err) if len(data) == 0 { - integrations.Printf("No db found to restore for %s version: %s\n", models.DbCfg.Type, version) + integrations.Printf("No db found to restore for %s version: %s\n", setting.Database.Type, version) return false } switch { - case setting.UseSQLite3: - os.Remove(models.DbCfg.Path) - err := os.MkdirAll(path.Dir(models.DbCfg.Path), os.ModePerm) + case setting.Database.UseSQLite3: + os.Remove(setting.Database.Path) + err := os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm) assert.NoError(t, err) - db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d", models.DbCfg.Path, models.DbCfg.Timeout)) + db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d", setting.Database.Path, setting.Database.Timeout)) assert.NoError(t, err) defer db.Close() @@ -131,20 +132,20 @@ func restoreOldDB(t *testing.T, version string) bool { assert.NoError(t, err) db.Close() - case setting.UseMySQL: + case setting.Database.UseMySQL: db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/", - models.DbCfg.User, models.DbCfg.Passwd, models.DbCfg.Host)) + setting.Database.User, setting.Database.Passwd, setting.Database.Host)) assert.NoError(t, err) defer db.Close() - _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", models.DbCfg.Name)) + _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) assert.NoError(t, err) - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", models.DbCfg.Name)) + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)) assert.NoError(t, err) db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?multiStatements=true", - models.DbCfg.User, models.DbCfg.Passwd, models.DbCfg.Host, models.DbCfg.Name)) + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name)) assert.NoError(t, err) defer db.Close() @@ -152,21 +153,21 @@ func restoreOldDB(t *testing.T, version string) bool { assert.NoError(t, err) db.Close() - case setting.UsePostgreSQL: + case setting.Database.UsePostgreSQL: db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s", - models.DbCfg.User, models.DbCfg.Passwd, models.DbCfg.Host, models.DbCfg.SSLMode)) + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode)) assert.NoError(t, err) defer db.Close() - _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", models.DbCfg.Name)) + _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) assert.NoError(t, err) - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", models.DbCfg.Name)) + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)) assert.NoError(t, err) db.Close() db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", - models.DbCfg.User, models.DbCfg.Passwd, models.DbCfg.Host, models.DbCfg.Name, models.DbCfg.SSLMode)) + setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) assert.NoError(t, err) defer db.Close() @@ -174,18 +175,26 @@ func restoreOldDB(t *testing.T, version string) bool { assert.NoError(t, err) db.Close() - case setting.UseMSSQL: - host, port := models.ParseMSSQLHostPort(models.DbCfg.Host) + case setting.Database.UseMSSQL: + host, port := setting.ParseMSSQLHostPort(setting.Database.Host) db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", - host, port, "master", models.DbCfg.User, models.DbCfg.Passwd)) + host, port, "master", setting.Database.User, setting.Database.Passwd)) assert.NoError(t, err) defer db.Close() - _, err = db.Exec("DROP DATABASE IF EXISTS gitea") + _, err = db.Exec("DROP DATABASE IF EXISTS [gitea]") assert.NoError(t, err) statements := strings.Split(data, "\nGO\n") for _, statement := range statements { + if len(statement) > 5 && statement[:5] == "USE [" { + dbname := statement[5 : len(statement)-1] + db.Close() + db, err = sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", + host, port, dbname, setting.Database.User, setting.Database.Passwd)) + assert.NoError(t, err) + defer db.Close() + } _, err = db.Exec(statement) assert.NoError(t, err, "Failure whilst running: %s\nError: %v", statement, err) } @@ -201,7 +210,7 @@ func wrappedMigrate(x *xorm.Engine) error { func doMigrationTest(t *testing.T, version string) { integrations.PrintCurrentTest(t) - integrations.Printf("Performing migration test for %s version: %s\n", models.DbCfg.Type, version) + integrations.Printf("Performing migration test for %s version: %s\n", setting.Database.Type, version) if !restoreOldDB(t, version) { return } @@ -218,7 +227,7 @@ func doMigrationTest(t *testing.T, version string) { func TestMigrations(t *testing.T) { initMigrationTest(t) - dialect := models.DbCfg.Type + dialect := setting.Database.Type versions, err := availableVersions() assert.NoError(t, err) diff --git a/integrations/mssql.ini.tmpl b/integrations/mssql.ini.tmpl index 06d9de6107..d38d038a4e 100644 --- a/integrations/mssql.ini.tmpl +++ b/integrations/mssql.ini.tmpl @@ -62,7 +62,7 @@ PROVIDER_CONFIG = data/sessions-mssql [log] MODE = test,file -ROOT_PATH = sqlite-log +ROOT_PATH = mssql-log REDIRECT_MACARON_LOG = true ROUTER = , MACARON = , diff --git a/integrations/mysql.ini.tmpl b/integrations/mysql.ini.tmpl index 44ca51ff8b..6eed7e1578 100644 --- a/integrations/mysql.ini.tmpl +++ b/integrations/mysql.ini.tmpl @@ -62,7 +62,7 @@ PROVIDER_CONFIG = data/sessions-mysql [log] MODE = test,file -ROOT_PATH = sqlite-log +ROOT_PATH = mysql-log REDIRECT_MACARON_LOG = true ROUTER = , MACARON = , diff --git a/integrations/mysql8.ini.tmpl b/integrations/mysql8.ini.tmpl index 6db3c880ff..1e14bc1356 100644 --- a/integrations/mysql8.ini.tmpl +++ b/integrations/mysql8.ini.tmpl @@ -59,7 +59,7 @@ PROVIDER_CONFIG = data/sessions-mysql8 [log] MODE = test,file -ROOT_PATH = sqlite-log +ROOT_PATH = mysql8-log REDIRECT_MACARON_LOG = true ROUTER = , MACARON = , diff --git a/integrations/pgsql.ini.tmpl b/integrations/pgsql.ini.tmpl index e69198c868..cd5dc44ea8 100644 --- a/integrations/pgsql.ini.tmpl +++ b/integrations/pgsql.ini.tmpl @@ -62,7 +62,7 @@ PROVIDER_CONFIG = data/sessions-pgsql [log] MODE = test,file -ROOT_PATH = sqlite-log +ROOT_PATH = pgsql-log REDIRECT_MACARON_LOG = true ROUTER = , MACARON = , diff --git a/integrations/pull_create_test.go b/integrations/pull_create_test.go index 8f39e8b028..84bcbff0dc 100644 --- a/integrations/pull_create_test.go +++ b/integrations/pull_create_test.go @@ -100,7 +100,7 @@ func TestPullCreate_TitleEscape(t *testing.T) { htmlDoc = NewHTMLParser(t, resp.Body) titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html() assert.NoError(t, err) - assert.Equal(t, "<i>XSS PR</i>", titleHTML) + assert.Equal(t, "<i>XSS PR</i>", titleHTML) titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html() assert.NoError(t, err) assert.Equal(t, "<u>XSS PR</u>", titleHTML) diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go index f3efa63b07..27f9fc6bb9 100644 --- a/integrations/pull_merge_test.go +++ b/integrations/pull_merge_test.go @@ -15,8 +15,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/test" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum string, mergeStyle models.MergeStyle) *httptest.ResponseRecorder { @@ -54,6 +54,10 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str func TestPullMerge(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -63,11 +67,19 @@ func TestPullMerge(t *testing.T) { elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) + + hookTasks, err = models.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) }) } func TestPullRebase(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -77,12 +89,21 @@ func TestPullRebase(t *testing.T) { elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebase) + + hookTasks, err = models.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) }) } func TestPullRebaseMerge(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { prepareTestEnv(t) + + hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -92,12 +113,21 @@ func TestPullRebaseMerge(t *testing.T) { elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleRebaseMerge) + + hookTasks, err = models.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) }) } func TestPullSquash(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { prepareTestEnv(t) + + hookTasks, err := models.HookTasks(1, 1) //Retrieve previous hook number + assert.NoError(t, err) + hookTasksLenBefore := len(hookTasks) + session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -108,6 +138,10 @@ func TestPullSquash(t *testing.T) { elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleSquash) + + hookTasks, err = models.HookTasks(1, 1) + assert.NoError(t, err) + assert.Len(t, hookTasks, hookTasksLenBefore+1) }) } diff --git a/integrations/pull_status_test.go b/integrations/pull_status_test.go index 2a4d8e0b68..fde2d3cc9b 100644 --- a/integrations/pull_status_test.go +++ b/integrations/pull_status_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "path" + "strings" "testing" "code.gitea.io/gitea/models" @@ -93,3 +94,28 @@ func TestPullCreate_CommitStatus(t *testing.T) { } }) } + +func TestPullCreate_EmptyChangesWithCommits(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1") + testEditFileToNewBranch(t, session, "user1", "repo1", "status1", "status1", "README.md", "# repo1\n\nDescription for repo1") + + url := path.Join("user1", "repo1", "compare", "master...status1") + req := NewRequestWithValues(t, "POST", url, + map[string]string{ + "_csrf": GetCSRF(t, session, url), + "title": "pull request from status1", + }, + ) + session.MakeRequest(t, req, http.StatusFound) + + req = NewRequest(t, "GET", "/user1/repo1/pulls/1") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + text := strings.TrimSpace(doc.doc.Find(".item.text.green").Text()) + assert.EqualValues(t, "This pull request can be merged automatically.", text) + }) +} diff --git a/integrations/release_test.go b/integrations/release_test.go index 492d224a53..135607153e 100644 --- a/integrations/release_test.go +++ b/integrations/release_test.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/modules/test" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) { diff --git a/integrations/repo_branch_test.go b/integrations/repo_branch_test.go index a5447cfb66..dbde276993 100644 --- a/integrations/repo_branch_test.go +++ b/integrations/repo_branch_test.go @@ -13,8 +13,8 @@ import ( "code.gitea.io/gitea/modules/test" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { diff --git a/integrations/repo_commits_search_test.go b/integrations/repo_commits_search_test.go index f477550c0e..c8bc1b56e6 100644 --- a/integrations/repo_commits_search_test.go +++ b/integrations/repo_commits_search_test.go @@ -28,10 +28,16 @@ func testRepoCommitsSearch(t *testing.T, query, commit string) { } func TestRepoCommitsSearch(t *testing.T) { + testRepoCommitsSearch(t, "e8eabd", "") + testRepoCommitsSearch(t, "38a9cb", "") + testRepoCommitsSearch(t, "6e8e", "6e8eabd9a7") + testRepoCommitsSearch(t, "58e97", "58e97d1a24") testRepoCommitsSearch(t, "author:alice", "6e8eabd9a7") + testRepoCommitsSearch(t, "author:alice 6e8ea", "6e8eabd9a7") testRepoCommitsSearch(t, "committer:Tom", "58e97d1a24") testRepoCommitsSearch(t, "author:bob commit-4", "58e97d1a24") testRepoCommitsSearch(t, "author:bob commit after:2019-03-03", "58e97d1a24") + testRepoCommitsSearch(t, "committer:alice 6e8e before:2019-03-02", "6e8eabd9a7") testRepoCommitsSearch(t, "committer:alice commit before:2019-03-02", "6e8eabd9a7") testRepoCommitsSearch(t, "committer:alice author:tom commit before:2019-03-04 after:2019-03-02", "0a8499a22a") } diff --git a/integrations/repo_commits_test.go b/integrations/repo_commits_test.go index 1f70d69251..7a4761adc6 100644 --- a/integrations/repo_commits_test.go +++ b/integrations/repo_commits_test.go @@ -5,10 +5,13 @@ package integrations import ( + "encoding/json" "net/http" + "net/http/httptest" "path" "testing" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -67,6 +70,29 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { for _, class := range classes { assert.True(t, sel.HasClass(class)) } + + //By SHA + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/"+path.Base(commitURL)+"/statuses") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), state) + //By Ref + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/master/statuses") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), state) + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/v1.1/statuses") + testRepoCommitsWithStatus(t, session.MakeRequest(t, req, http.StatusOK), state) +} + +func testRepoCommitsWithStatus(t *testing.T, resp *httptest.ResponseRecorder, state string) { + decoder := json.NewDecoder(resp.Body) + statuses := []*api.Status{} + assert.NoError(t, decoder.Decode(&statuses)) + assert.Len(t, statuses, 1) + for _, s := range statuses { + assert.Equal(t, api.StatusState(state), s.State) + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/statuses/65f1bf27bc3bf70f64657658635e66094edbcb4d", s.URL) + assert.Equal(t, "http://test.ci/", s.TargetURL) + assert.Equal(t, "", s.Description) + assert.Equal(t, "testci", s.Context) + } } func TestRepoCommitsWithStatusPending(t *testing.T) { diff --git a/integrations/repo_search_test.go b/integrations/repo_search_test.go index 282caa707e..32a872e409 100644 --- a/integrations/repo_search_test.go +++ b/integrations/repo_search_test.go @@ -10,6 +10,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" @@ -33,19 +34,41 @@ func TestSearchRepo(t *testing.T) { repo, err := models.GetRepositoryByOwnerAndName("user2", "repo1") assert.NoError(t, err) + executeIndexer(t, repo, models.UpdateRepoIndexer) + + testSearch(t, "/user2/repo1/search?q=Description&page=1", []string{"README.md"}) + + setting.Indexer.IncludePatterns = setting.IndexerGlobFromString("**.txt") + setting.Indexer.ExcludePatterns = setting.IndexerGlobFromString("**/y/**") + + repo, err = models.GetRepositoryByOwnerAndName("user2", "glob") + assert.NoError(t, err) + + executeIndexer(t, repo, models.DeleteRepoFromIndexer) + executeIndexer(t, repo, models.UpdateRepoIndexer) + + testSearch(t, "/user2/glob/search?q=loren&page=1", []string{"a.txt"}) + testSearch(t, "/user2/glob/search?q=file3&page=1", []string{"x/b.txt"}) + testSearch(t, "/user2/glob/search?q=file4&page=1", []string{}) + testSearch(t, "/user2/glob/search?q=file5&page=1", []string{}) +} + +func testSearch(t *testing.T, url string, expected []string) { + req := NewRequestf(t, "GET", url) + resp := MakeRequest(t, req, http.StatusOK) + + filenames := resultFilenames(t, NewHTMLParser(t, resp.Body)) + assert.EqualValues(t, expected, filenames) +} + +func executeIndexer(t *testing.T, repo *models.Repository, op func(*models.Repository, ...chan<- error)) { waiter := make(chan error, 1) - models.UpdateRepoIndexer(repo, waiter) + op(repo, waiter) select { case err := <-waiter: assert.NoError(t, err) case <-time.After(1 * time.Minute): - assert.Fail(t, "UpdateRepoIndexer took too long") + assert.Fail(t, "Repository indexer took too long") } - - req := NewRequestf(t, "GET", "/user2/repo1/search?q=Description&page=1") - resp := MakeRequest(t, req, http.StatusOK) - - filenames := resultFilenames(t, NewHTMLParser(t, resp.Body)) - assert.EqualValues(t, []string{"README.md"}, filenames) } diff --git a/integrations/signin_test.go b/integrations/signin_test.go index 9eab5b5f03..4a2bb8857a 100644 --- a/integrations/signin_test.go +++ b/integrations/signin_test.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/models" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) func testLoginFailed(t *testing.T, username, password, message string) { diff --git a/integrations/ssh_key_test.go b/integrations/ssh_key_test.go index e8fbf17c55..944d2f6bed 100644 --- a/integrations/ssh_key_test.go +++ b/integrations/ssh_key_test.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" + "github.com/stretchr/testify/assert" ) diff --git a/integrations/testlogger.go b/integrations/testlogger.go index 43a1471f66..91b94ac9f3 100644 --- a/integrations/testlogger.go +++ b/integrations/testlogger.go @@ -10,6 +10,7 @@ import ( "os" "runtime" "strings" + "sync" "testing" "code.gitea.io/gitea/modules/log" @@ -25,11 +26,21 @@ type TestLogger struct { var writerCloser = &testLoggerWriterCloser{} type testLoggerWriterCloser struct { + sync.RWMutex t testing.TB } +func (w *testLoggerWriterCloser) setT(t *testing.TB) { + w.Lock() + w.t = *t + w.Unlock() +} + func (w *testLoggerWriterCloser) Write(p []byte) (int, error) { - if w.t != nil { + w.RLock() + t := w.t + w.RUnlock() + if t != nil { if len(p) > 0 && p[len(p)-1] == '\n' { p = p[:len(p)-1] } @@ -54,7 +65,7 @@ func (w *testLoggerWriterCloser) Write(p []byte) (int, error) { } }() - w.t.Log(string(p)) + t.Log(string(p)) return len(p), nil } return len(p), nil @@ -77,7 +88,7 @@ func PrintCurrentTest(t testing.TB, skip ...int) { } else { fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line) } - writerCloser.t = t + writerCloser.setT(&t) } // Printf takes a format and args and prints the string to os.Stdout diff --git a/integrations/user_test.go b/integrations/user_test.go index fd25f1c570..0a6fdd19d5 100644 --- a/integrations/user_test.go +++ b/integrations/user_test.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/test" - "github.com/Unknwon/i18n" "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" ) func TestViewUser(t *testing.T) { @@ -74,9 +74,22 @@ func TestRenameReservedUsername(t *testing.T) { prepareTestEnv(t) reservedUsernames := []string{ + "admin", + "api", + "attachments", + "avatars", + "explore", "help", - "user", + "install", + "issues", + "login", + "metrics", + "notifications", + "org", + "pulls", + "repo", "template", + "user", } session := loginUser(t, "user2") diff --git a/integrations/version_test.go b/integrations/version_test.go index eaba3e0859..e63ae7aee2 100644 --- a/integrations/version_test.go +++ b/integrations/version_test.go @@ -23,5 +23,5 @@ func TestVersion(t *testing.T) { var version structs.ServerVersion DecodeJSON(t, resp, &version) - assert.Equal(t, setting.AppVer, string(version.Version)) + assert.Equal(t, setting.AppVer, version.Version) } diff --git a/models/access_test.go b/models/access_test.go index db6f655a67..d0f0032547 100644 --- a/models/access_test.go +++ b/models/access_test.go @@ -55,13 +55,13 @@ func TestHasAccess(t *testing.T) { assert.NoError(t, err) assert.True(t, has) - has, err = HasAccess(user1.ID, repo2) + _, err = HasAccess(user1.ID, repo2) assert.NoError(t, err) - has, err = HasAccess(user2.ID, repo1) + _, err = HasAccess(user2.ID, repo1) assert.NoError(t, err) - has, err = HasAccess(user2.ID, repo2) + _, err = HasAccess(user2.ID, repo2) assert.NoError(t, err) } diff --git a/models/action.go b/models/action.go index 21b008e3be..87088101f9 100644 --- a/models/action.go +++ b/models/action.go @@ -21,9 +21,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" + "github.com/unknwon/com" "xorm.io/builder" ) @@ -65,6 +65,7 @@ var ( ) const issueRefRegexpStr = `(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)+` +const issueRefRegexpStrNoKeyword = `(?:\s|^|\(|\[)(?:([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+))?(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))` func assembleKeywordsPattern(words []string) string { return fmt.Sprintf(`(?i)(?:%s)(?::?) %s`, strings.Join(words, "|"), issueRefRegexpStr) @@ -73,7 +74,7 @@ func assembleKeywordsPattern(words []string) string { func init() { issueCloseKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueCloseKeywords)) issueReopenKeywordsPat = regexp.MustCompile(assembleKeywordsPattern(issueReopenKeywords)) - issueReferenceKeywordsPat = regexp.MustCompile(issueRefRegexpStr) + issueReferenceKeywordsPat = regexp.MustCompile(issueRefRegexpStrNoKeyword) } // Action represents user operation type and other information to @@ -91,9 +92,9 @@ type Action struct { Comment *Comment `xorm:"-"` IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"` RefName string - IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` - Content string `xorm:"TEXT"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` + IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"` + Content string `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } // GetOpType gets the ActionType of this action. @@ -385,7 +386,7 @@ func NewPushCommits() *PushCommits { // ToAPIPayloadCommits converts a PushCommits object to // api.PayloadCommit format. -func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit { +func (pc *PushCommits) ToAPIPayloadCommits(repoPath, repoLink string) ([]*api.PayloadCommit, error) { commits := make([]*api.PayloadCommit, len(pc.Commits)) if pc.emailUsers == nil { @@ -417,6 +418,12 @@ func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit } else { committerUsername = committer.Name } + + fileStatus, err := git.GetCommitFileStatus(repoPath, commit.Sha1) + if err != nil { + return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %v", commit.Sha1, err) + } + commits[i] = &api.PayloadCommit{ ID: commit.Sha1, Message: commit.Message, @@ -431,15 +438,21 @@ func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit Email: commit.CommitterEmail, UserName: committerUsername, }, + Added: fileStatus.Added, + Removed: fileStatus.Removed, + Modified: fileStatus.Modified, Timestamp: commit.Timestamp, } } - return commits + return commits, nil } // AvatarLink tries to match user in database with e-mail // in order to show custom avatar, and falls back to general avatar link. func (pc *PushCommits) AvatarLink(email string) string { + if pc.avatars == nil { + pc.avatars = make(map[string]string) + } avatar, ok := pc.avatars[email] if ok { return avatar @@ -499,7 +512,7 @@ func getIssueFromRef(repo *Repository, ref string) (*Issue, error) { return nil, nil } - issue, err := GetIssueByIndex(refRepo.ID, int64(issueIndex)) + issue, err := GetIssueByIndex(refRepo.ID, issueIndex) if err != nil { if IsErrIssueNotExist(err) { return nil, nil @@ -564,7 +577,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra // issue is from another repo if len(m[1]) > 0 && len(m[2]) > 0 { - refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2])) + refRepo, err = GetRepositoryFromMatch(m[1], m[2]) if err != nil { continue } @@ -601,7 +614,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra // issue is from another repo if len(m[1]) > 0 && len(m[2]) > 0 { - refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2])) + refRepo, err = GetRepositoryFromMatch(m[1], m[2]) if err != nil { continue } @@ -630,7 +643,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra // issue is from another repo if len(m[1]) > 0 && len(m[2]) > 0 { - refRepo, err = GetRepositoryFromMatch(string(m[1]), string(m[2])) + refRepo, err = GetRepositoryFromMatch(m[1], m[2]) if err != nil { continue } @@ -654,189 +667,6 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra return nil } -// CommitRepoActionOptions represent options of a new commit action. -type CommitRepoActionOptions struct { - PusherName string - RepoOwnerID int64 - RepoName string - RefFullName string - OldCommitID string - NewCommitID string - Commits *PushCommits -} - -// CommitRepoAction adds new commit action to the repository, and prepare -// corresponding webhooks. -func CommitRepoAction(opts CommitRepoActionOptions) error { - pusher, err := GetUserByName(opts.PusherName) - if err != nil { - return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) - } - - repo, err := GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) - } - - refName := git.RefEndName(opts.RefFullName) - - // Change default branch and empty status only if pushed ref is non-empty branch. - if repo.IsEmpty && opts.NewCommitID != git.EmptySHA && strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { - repo.DefaultBranch = refName - repo.IsEmpty = false - } - - // Change repository empty status and update last updated time. - if err = UpdateRepository(repo, false); err != nil { - return fmt.Errorf("UpdateRepository: %v", err) - } - - isNewBranch := false - opType := ActionCommitRepo - // Check it's tag push or branch. - if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { - opType = ActionPushTag - if opts.NewCommitID == git.EmptySHA { - opType = ActionDeleteTag - } - opts.Commits = &PushCommits{} - } else if opts.NewCommitID == git.EmptySHA { - opType = ActionDeleteBranch - opts.Commits = &PushCommits{} - } else { - // if not the first commit, set the compare URL. - if opts.OldCommitID == git.EmptySHA { - isNewBranch = true - } else { - opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) - } - - if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { - log.Error("updateIssuesCommit: %v", err) - } - } - - if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { - opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] - } - - data, err := json.Marshal(opts.Commits) - if err != nil { - return fmt.Errorf("Marshal: %v", err) - } - - if err = NotifyWatchers(&Action{ - ActUserID: pusher.ID, - ActUser: pusher, - OpType: opType, - Content: string(data), - RepoID: repo.ID, - Repo: repo, - RefName: refName, - IsPrivate: repo.IsPrivate, - }); err != nil { - return fmt.Errorf("NotifyWatchers: %v", err) - } - - defer func() { - go HookQueue.Add(repo.ID) - }() - - apiPusher := pusher.APIFormat() - apiRepo := repo.APIFormat(AccessModeNone) - - var shaSum string - var isHookEventPush = false - switch opType { - case ActionCommitRepo: // Push - isHookEventPush = true - - if isNewBranch { - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) - } - - shaSum, err = gitRepo.GetBranchCommitID(refName) - if err != nil { - log.Error("GetBranchCommitID[%s]: %v", opts.RefFullName, err) - } - if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{ - Ref: refName, - Sha: shaSum, - RefType: "branch", - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - } - - case ActionDeleteBranch: // Delete Branch - isHookEventPush = true - - if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{ - Ref: refName, - RefType: "branch", - PusherType: api.PusherTypeUser, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err) - } - - case ActionPushTag: // Create - isHookEventPush = true - - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) - } - shaSum, err = gitRepo.GetTagCommitID(refName) - if err != nil { - log.Error("GetTagCommitID[%s]: %v", opts.RefFullName, err) - } - if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{ - Ref: refName, - Sha: shaSum, - RefType: "tag", - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - case ActionDeleteTag: // Delete Tag - isHookEventPush = true - - if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{ - Ref: refName, - RefType: "tag", - PusherType: api.PusherTypeUser, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err) - } - } - - if isHookEventPush { - if err = PrepareWebhooks(repo, HookEventPush, &api.PushPayload{ - Ref: opts.RefFullName, - Before: opts.OldCommitID, - After: opts.NewCommitID, - CompareURL: setting.AppURL + opts.Commits.CompareURL, - Commits: opts.Commits.ToAPIPayloadCommits(repo.HTMLURL()), - Repo: apiRepo, - Pusher: apiPusher, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - } - - return nil -} - func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err error) { if err = notifyWatchers(e, &Action{ ActUserID: doer.ID, @@ -918,7 +748,10 @@ func MirrorSyncPushAction(repo *Repository, opts MirrorSyncPushActionOptions) er opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] } - apiCommits := opts.Commits.ToAPIPayloadCommits(repo.HTMLURL()) + apiCommits, err := opts.Commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL()) + if err != nil { + return err + } opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) apiPusher := repo.MustOwner().APIFormat() diff --git a/models/action_test.go b/models/action_test.go index 53a3202894..c90538ebe6 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -86,42 +85,69 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { pushCommits := NewPushCommits() pushCommits.Commits = []*PushCommit{ { - Sha1: "abcdef1", + Sha1: "69554a6", CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user4@example.com", - AuthorName: "User Four", - Message: "message1", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "not signed commit", }, { - Sha1: "abcdef2", + Sha1: "27566bd", CommitterEmail: "user2@example.com", - CommitterName: "User Two", + CommitterName: "User2", AuthorEmail: "user2@example.com", - AuthorName: "User Two", - Message: "message2", + AuthorName: "User2", + Message: "good signed commit (with not yet validated email)", + }, + { + Sha1: "5099b81", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "good signed commit", }, } pushCommits.Len = len(pushCommits.Commits) - payloadCommits := pushCommits.ToAPIPayloadCommits("/username/reponame") - if assert.Len(t, payloadCommits, 2) { - assert.Equal(t, "abcdef1", payloadCommits[0].ID) - assert.Equal(t, "message1", payloadCommits[0].Message) - assert.Equal(t, "/username/reponame/commit/abcdef1", payloadCommits[0].URL) - assert.Equal(t, "User Two", payloadCommits[0].Committer.Name) - assert.Equal(t, "user2", payloadCommits[0].Committer.UserName) - assert.Equal(t, "User Four", payloadCommits[0].Author.Name) - assert.Equal(t, "user4", payloadCommits[0].Author.UserName) + repo := AssertExistsAndLoadBean(t, &Repository{ID: 16}).(*Repository) + payloadCommits, err := pushCommits.ToAPIPayloadCommits(repo.RepoPath(), "/user2/repo16") + assert.NoError(t, err) + assert.EqualValues(t, 3, len(payloadCommits)) - assert.Equal(t, "abcdef2", payloadCommits[1].ID) - assert.Equal(t, "message2", payloadCommits[1].Message) - assert.Equal(t, "/username/reponame/commit/abcdef2", payloadCommits[1].URL) - assert.Equal(t, "User Two", payloadCommits[1].Committer.Name) - assert.Equal(t, "user2", payloadCommits[1].Committer.UserName) - assert.Equal(t, "User Two", payloadCommits[1].Author.Name) - assert.Equal(t, "user2", payloadCommits[1].Author.UserName) - } + assert.Equal(t, "69554a6", payloadCommits[0].ID) + assert.Equal(t, "not signed commit", payloadCommits[0].Message) + assert.Equal(t, "/user2/repo16/commit/69554a6", payloadCommits[0].URL) + assert.Equal(t, "User2", payloadCommits[0].Committer.Name) + assert.Equal(t, "user2", payloadCommits[0].Committer.UserName) + assert.Equal(t, "User2", payloadCommits[0].Author.Name) + assert.Equal(t, "user2", payloadCommits[0].Author.UserName) + assert.EqualValues(t, []string{}, payloadCommits[0].Added) + assert.EqualValues(t, []string{}, payloadCommits[0].Removed) + assert.EqualValues(t, []string{"readme.md"}, payloadCommits[0].Modified) + + assert.Equal(t, "27566bd", payloadCommits[1].ID) + assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message) + assert.Equal(t, "/user2/repo16/commit/27566bd", payloadCommits[1].URL) + assert.Equal(t, "User2", payloadCommits[1].Committer.Name) + assert.Equal(t, "user2", payloadCommits[1].Committer.UserName) + assert.Equal(t, "User2", payloadCommits[1].Author.Name) + assert.Equal(t, "user2", payloadCommits[1].Author.UserName) + assert.EqualValues(t, []string{}, payloadCommits[1].Added) + assert.EqualValues(t, []string{}, payloadCommits[1].Removed) + assert.EqualValues(t, []string{"readme.md"}, payloadCommits[1].Modified) + + assert.Equal(t, "5099b81", payloadCommits[2].ID) + assert.Equal(t, "good signed commit", payloadCommits[2].Message) + assert.Equal(t, "/user2/repo16/commit/5099b81", payloadCommits[2].URL) + assert.Equal(t, "User2", payloadCommits[2].Committer.Name) + assert.Equal(t, "user2", payloadCommits[2].Committer.UserName) + assert.Equal(t, "User2", payloadCommits[2].Author.Name) + assert.Equal(t, "user2", payloadCommits[2].Author.UserName) + assert.EqualValues(t, []string{"readme.md"}, payloadCommits[2].Added) + assert.EqualValues(t, []string{}, payloadCommits[2].Removed) + assert.EqualValues(t, []string{}, payloadCommits[2].Modified) } func TestPushCommits_AvatarLink(t *testing.T) { @@ -147,7 +173,7 @@ func TestPushCommits_AvatarLink(t *testing.T) { pushCommits.Len = len(pushCommits.Commits) assert.Equal(t, - "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon", + "/suburl/user/avatar/user2/-1", pushCommits.AvatarLink("user2@example.com")) assert.Equal(t, @@ -155,6 +181,26 @@ func TestPushCommits_AvatarLink(t *testing.T) { pushCommits.AvatarLink("nonexistent@example.com")) } +func TestRegExp_issueReferenceKeywordsPat(t *testing.T) { + trueTestCases := []string{ + "#2", + "[#2]", + "please see go-gitea/gitea#5", + "#2:", + } + falseTestCases := []string{ + "kb#2", + "#2xy", + } + + for _, testCase := range trueTestCases { + assert.True(t, issueReferenceKeywordsPat.MatchString(testCase)) + } + for _, testCase := range falseTestCases { + assert.False(t, issueReferenceKeywordsPat.MatchString(testCase)) + } +} + func Test_getIssueFromRef(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) @@ -390,119 +436,6 @@ func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { CheckConsistencyFor(t, &Action{}) } -func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) { - AssertNotExistsBean(t, actionBean) - assert.NoError(t, CommitRepoAction(opts)) - AssertExistsAndLoadBean(t, actionBean) - CheckConsistencyFor(t, &Action{}) -} - -func TestCommitRepoAction(t *testing.T) { - samples := []struct { - userID int64 - repositoryID int64 - commitRepoActionOptions CommitRepoActionOptions - action Action - }{ - { - userID: 2, - repositoryID: 2, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: "refName", - OldCommitID: "oldCommitID", - NewCommitID: "newCommitID", - Commits: &PushCommits{ - avatars: make(map[string]string), - Commits: []*PushCommit{ - { - Sha1: "abcdef1", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user4@example.com", - AuthorName: "User Four", - Message: "message1", - }, - { - Sha1: "abcdef2", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user2@example.com", - AuthorName: "User Two", - Message: "message2", - }, - }, - Len: 2, - }, - }, - action: Action{ - OpType: ActionCommitRepo, - RefName: "refName", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: git.TagPrefix + "v1.1", - OldCommitID: git.EmptySHA, - NewCommitID: "newCommitID", - Commits: &PushCommits{}, - }, - action: Action{ - OpType: ActionPushTag, - RefName: "v1.1", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: git.TagPrefix + "v1.1", - OldCommitID: "oldCommitID", - NewCommitID: git.EmptySHA, - Commits: &PushCommits{}, - }, - action: Action{ - OpType: ActionDeleteTag, - RefName: "v1.1", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: git.BranchPrefix + "feature/1", - OldCommitID: "oldCommitID", - NewCommitID: git.EmptySHA, - Commits: &PushCommits{}, - }, - action: Action{ - OpType: ActionDeleteBranch, - RefName: "feature/1", - }, - }, - } - - for _, s := range samples { - PrepareTestEnv(t) - - user := AssertExistsAndLoadBean(t, &User{ID: s.userID}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: s.repositoryID, OwnerID: user.ID}).(*Repository) - repo.Owner = user - - s.commitRepoActionOptions.PusherName = user.Name - s.commitRepoActionOptions.RepoOwnerID = user.ID - s.commitRepoActionOptions.RepoName = repo.Name - - s.action.ActUserID = user.ID - s.action.RepoID = repo.ID - s.action.Repo = repo - s.action.IsPrivate = repo.IsPrivate - - testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action) - } -} - func TestTransferRepoAction(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/admin.go b/models/admin.go index 4480d11480..271c5307a0 100644 --- a/models/admin.go +++ b/models/admin.go @@ -9,9 +9,9 @@ import ( "os" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" + "github.com/unknwon/com" ) //NoticeType describes the notice type @@ -26,8 +26,8 @@ const ( type Notice struct { ID int64 `xorm:"pk autoincr"` Type NoticeType - Description string `xorm:"TEXT"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` + Description string `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } // TrStr returns a translation format string. diff --git a/models/attachment.go b/models/attachment.go index 1740f065d0..a9032f1a86 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" gouuid "github.com/satori/go.uuid" @@ -27,9 +27,9 @@ type Attachment struct { UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added CommentID int64 Name string - DownloadCount int64 `xorm:"DEFAULT 0"` - Size int64 `xorm:"DEFAULT 0"` - CreatedUnix util.TimeStamp `xorm:"created"` + DownloadCount int64 `xorm:"DEFAULT 0"` + Size int64 `xorm:"DEFAULT 0"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } // IncreaseDownloadCount is update download count + 1 @@ -255,3 +255,9 @@ func updateAttachment(e Engine, atta *Attachment) error { _, err := sess.Cols("name", "issue_id", "release_id", "comment_id", "download_count").Update(atta) return err } + +// DeleteAttachmentsByRelease deletes all attachments associated with the given release. +func DeleteAttachmentsByRelease(releaseID int64) error { + _, err := x.Where("release_id = ?", releaseID).Delete(&Attachment{}) + return err +} diff --git a/models/branches.go b/models/branches.go index fa4215d6a0..9daaa487e7 100644 --- a/models/branches.go +++ b/models/branches.go @@ -11,9 +11,10 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" + "github.com/unknwon/com" ) const ( @@ -30,16 +31,18 @@ type ProtectedBranch struct { BranchName string `xorm:"UNIQUE(s)"` CanPush bool `xorm:"NOT NULL DEFAULT false"` EnableWhitelist bool - WhitelistUserIDs []int64 `xorm:"JSON TEXT"` - WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` - EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` - MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` - MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` - ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` - ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` - RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` - CreatedUnix util.TimeStamp `xorm:"created"` - UpdatedUnix util.TimeStamp `xorm:"updated"` + WhitelistUserIDs []int64 `xorm:"JSON TEXT"` + WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` + MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` + MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` + StatusCheckContexts []string `xorm:"JSON TEXT"` + ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` + ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } // IsProtected returns if the branch is protected @@ -101,7 +104,7 @@ func (protectBranch *ProtectedBranch) HasEnoughApprovals(pr *PullRequest) bool { // GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist. func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(pr *PullRequest) int64 { - reviews, err := GetReviewersByPullID(pr.Issue.ID) + reviews, err := GetReviewersByPullID(pr.IssueID) if err != nil { log.Error("GetReviewersByPullID: %v", err) return 0 @@ -374,13 +377,13 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) { // DeletedBranch struct type DeletedBranch struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"UNIQUE(s) NOT NULL"` - Commit string `xorm:"UNIQUE(s) NOT NULL"` - DeletedByID int64 `xorm:"INDEX"` - DeletedBy *User `xorm:"-"` - DeletedUnix util.TimeStamp `xorm:"INDEX created"` + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + Name string `xorm:"UNIQUE(s) NOT NULL"` + Commit string `xorm:"UNIQUE(s) NOT NULL"` + DeletedByID int64 `xorm:"INDEX"` + DeletedBy *User `xorm:"-"` + DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"` } // AddDeletedBranch adds a deleted branch to the database @@ -458,11 +461,6 @@ func (deletedBranch *DeletedBranch) LoadUser() { // RemoveOldDeletedBranches removes old deleted branches func RemoveOldDeletedBranches() { - if !taskStatusTable.StartIfNotRunning(`deleted_branches_cleanup`) { - return - } - defer taskStatusTable.Stop(`deleted_branches_cleanup`) - log.Trace("Doing: DeletedBranchesCleanup") deleteBefore := time.Now().Add(-setting.Cron.DeletedBranchesCleanup.OlderThan) diff --git a/models/commit_status.go b/models/commit_status.go index 74a2756303..6f6cbc387f 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -9,11 +9,14 @@ import ( "crypto/sha1" "fmt" "strings" + "time" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/go-xorm/xorm" ) // CommitStatusState holds the state of a Status @@ -64,8 +67,8 @@ type CommitStatus struct { Creator *User `xorm:"-"` CreatorID int64 - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } func (status *CommitStatus) loadRepo(e Engine) (err error) { @@ -87,7 +90,7 @@ func (status *CommitStatus) loadRepo(e Engine) (err error) { // APIURL returns the absolute APIURL to this commit-status. func (status *CommitStatus) APIURL() string { _ = status.loadRepo(x) - return fmt.Sprintf("%sapi/v1/%s/statuses/%s", + return fmt.Sprintf("%sapi/v1/repos/%s/statuses/%s", setting.AppURL, status.Repo.FullName(), status.SHA) } @@ -132,10 +135,57 @@ func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { return lastStatus } +// CommitStatusOptions holds the options for query commit statuses +type CommitStatusOptions struct { + Page int + State string + SortType string +} + // GetCommitStatuses returns all statuses for a given commit. -func GetCommitStatuses(repo *Repository, sha string, page int) ([]*CommitStatus, error) { - statuses := make([]*CommitStatus, 0, 10) - return statuses, x.Limit(10, page*10).Where("repo_id = ?", repo.ID).And("sha = ?", sha).Find(&statuses) +func GetCommitStatuses(repo *Repository, sha string, opts *CommitStatusOptions) ([]*CommitStatus, int64, error) { + if opts.Page <= 0 { + opts.Page = 1 + } + + countSession := listCommitStatusesStatement(repo, sha, opts) + maxResults, err := countSession.Count(new(CommitStatus)) + if err != nil { + log.Error("Count PRs: %v", err) + return nil, maxResults, err + } + + statuses := make([]*CommitStatus, 0, ItemsPerPage) + findSession := listCommitStatusesStatement(repo, sha, opts) + sortCommitStatusesSession(findSession, opts.SortType) + findSession.Limit(ItemsPerPage, (opts.Page-1)*ItemsPerPage) + return statuses, maxResults, findSession.Find(&statuses) +} + +func listCommitStatusesStatement(repo *Repository, sha string, opts *CommitStatusOptions) *xorm.Session { + sess := x.Where("repo_id = ?", repo.ID).And("sha = ?", sha) + switch opts.State { + case "pending", "success", "error", "failure", "warning": + sess.And("state = ?", opts.State) + } + return sess +} + +func sortCommitStatusesSession(sess *xorm.Session, sortType string) { + switch sortType { + case "oldest": + sess.Asc("created_unix") + case "recentupdate": + sess.Desc("updated_unix") + case "leastupdate": + sess.Asc("updated_unix") + case "leastindex": + sess.Desc("index") + case "highestindex": + sess.Asc("index") + default: + sess.Desc("created_unix") + } } // GetLatestCommitStatus returns all statuses with a unique context for a given commit. @@ -156,6 +206,27 @@ func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitSta return statuses, x.In("id", ids).Find(&statuses) } +// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts +func FindRepoRecentCommitStatusContexts(repoID int64, before time.Duration) ([]string, error) { + start := timeutil.TimeStampNow().AddDuration(-before) + ids := make([]int64, 0, 10) + if err := x.Table("commit_status"). + Where("repo_id = ?", repoID). + And("updated_unix >= ?", start). + Select("max( id ) as id"). + GroupBy("context_hash").OrderBy("max( id ) desc"). + Find(&ids); err != nil { + return nil, err + } + + var contexts = make([]string, 0, len(ids)) + if len(ids) == 0 { + return contexts, nil + } + return contexts, x.Select("context").Table("commit_status").In("id", ids).Find(&contexts) + +} + // NewCommitStatusOptions holds options for creating a CommitStatus type NewCommitStatusOptions struct { Repo *Repository diff --git a/models/commit_status_test.go b/models/commit_status_test.go index 78d2370f9d..97783ae6f1 100644 --- a/models/commit_status_test.go +++ b/models/commit_status_test.go @@ -17,22 +17,28 @@ func TestGetCommitStatuses(t *testing.T) { sha1 := "1234123412341234123412341234123412341234" - statuses, err := GetCommitStatuses(repo1, sha1, 0) + statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{}) assert.NoError(t, err) - if assert.Len(t, statuses, 5) { - assert.Equal(t, statuses[0].Context, "ci/awesomeness") - assert.Equal(t, statuses[0].State, CommitStatusPending) + assert.Equal(t, int(maxResults), 5) + assert.Len(t, statuses, 5) - assert.Equal(t, statuses[1].Context, "cov/awesomeness") - assert.Equal(t, statuses[1].State, CommitStatusWarning) + assert.Equal(t, "ci/awesomeness", statuses[0].Context) + assert.Equal(t, CommitStatusPending, statuses[0].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL()) - assert.Equal(t, statuses[2].Context, "cov/awesomeness") - assert.Equal(t, statuses[2].State, CommitStatusSuccess) + assert.Equal(t, "cov/awesomeness", statuses[1].Context) + assert.Equal(t, CommitStatusWarning, statuses[1].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL()) - assert.Equal(t, statuses[3].Context, "ci/awesomeness") - assert.Equal(t, statuses[3].State, CommitStatusFailure) + assert.Equal(t, "cov/awesomeness", statuses[2].Context) + assert.Equal(t, CommitStatusSuccess, statuses[2].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL()) - assert.Equal(t, statuses[4].Context, "deploy/awesomeness") - assert.Equal(t, statuses[4].State, CommitStatusError) - } + assert.Equal(t, "ci/awesomeness", statuses[3].Context) + assert.Equal(t, CommitStatusFailure, statuses[3].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[3].APIURL()) + + assert.Equal(t, "deploy/awesomeness", statuses[4].Context) + assert.Equal(t, CommitStatusError, statuses[4].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL()) } diff --git a/models/context.go b/models/context.go new file mode 100644 index 0000000000..5f47c595a2 --- /dev/null +++ b/models/context.go @@ -0,0 +1,55 @@ +// Copyright 2019 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 + +// DBContext represents a db context +type DBContext struct { + e Engine +} + +// DefaultDBContext represents a DBContext with default Engine +func DefaultDBContext() DBContext { + return DBContext{x} +} + +// Committer represents an interface to Commit or Close the dbcontext +type Committer interface { + Commit() error + Close() +} + +// TxDBContext represents a transaction DBContext +func TxDBContext() (DBContext, Committer, error) { + sess := x.NewSession() + if err := sess.Begin(); err != nil { + sess.Close() + return DBContext{}, nil, err + } + + return DBContext{sess}, sess, nil +} + +// WithContext represents executing database operations +func WithContext(f func(ctx DBContext) error) error { + return f(DBContext{x}) +} + +// WithTx represents executing database operations on a trasaction +func WithTx(f func(ctx DBContext) error) error { + sess := x.NewSession() + if err := sess.Begin(); err != nil { + sess.Close() + return err + } + + if err := f(DBContext{sess}); err != nil { + sess.Close() + return err + } + + err := sess.Commit() + sess.Close() + return err +} diff --git a/models/convert.go b/models/convert.go index c34be973c6..025f88b503 100644 --- a/models/convert.go +++ b/models/convert.go @@ -4,11 +4,15 @@ package models -import "fmt" +import ( + "fmt" + + "code.gitea.io/gitea/modules/setting" +) // ConvertUtf8ToUtf8mb4 converts database and tables from utf8 to utf8mb4 if it's mysql func ConvertUtf8ToUtf8mb4() error { - _, err := x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci", DbCfg.Name)) + _, err := x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci", setting.Database.Name)) if err != nil { return err } diff --git a/models/error.go b/models/error.go index 11ca6e6863..995617e83b 100644 --- a/models/error.go +++ b/models/error.go @@ -11,6 +11,21 @@ import ( "code.gitea.io/gitea/modules/git" ) +// ErrNotExist represents a non-exist error. +type ErrNotExist struct { + ID int64 +} + +// IsErrNotExist checks if an error is an ErrNotExist +func IsErrNotExist(err error) bool { + _, ok := err.(ErrNotExist) + return ok +} + +func (err ErrNotExist) Error() string { + return fmt.Sprintf("record does not exist [id: %d]", err.ID) +} + // ErrNameReserved represents a "reserved name" error. type ErrNameReserved struct { Name string @@ -1058,6 +1073,37 @@ func (err ErrIssueNotExist) Error() string { return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index) } +// ErrIssueLabelTemplateLoad represents a "ErrIssueLabelTemplateLoad" kind of error. +type ErrIssueLabelTemplateLoad struct { + TemplateFile string + OriginalError error +} + +// IsErrIssueLabelTemplateLoad checks if an error is a ErrIssueLabelTemplateLoad. +func IsErrIssueLabelTemplateLoad(err error) bool { + _, ok := err.(ErrIssueLabelTemplateLoad) + return ok +} + +func (err ErrIssueLabelTemplateLoad) Error() string { + return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError) +} + +// ErrNewIssueInsert is used when the INSERT statement in newIssue fails +type ErrNewIssueInsert struct { + OriginalError error +} + +// IsErrNewIssueInsert checks if an error is a ErrNewIssueInsert. +func IsErrNewIssueInsert(err error) bool { + _, ok := err.(ErrNewIssueInsert) + return ok +} + +func (err ErrNewIssueInsert) Error() string { + return err.OriginalError.Error() +} + // __________ .__ .__ __________ __ // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_ // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ @@ -1354,6 +1400,23 @@ func (err ErrTeamAlreadyExist) Error() string { return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name) } +// ErrTeamNotExist represents a "TeamNotExist" error +type ErrTeamNotExist struct { + OrgID int64 + TeamID int64 + Name string +} + +// IsErrTeamNotExist checks if an error is a ErrTeamNotExist. +func IsErrTeamNotExist(err error) bool { + _, ok := err.(ErrTeamNotExist) + return ok +} + +func (err ErrTeamNotExist) Error() string { + return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name) +} + // // Two-factor authentication // diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml index 5bb23571fc..385492dd68 100644 --- a/models/fixtures/org_user.yml +++ b/models/fixtures/org_user.yml @@ -39,3 +39,9 @@ uid: 20 org_id: 19 is_public: true + +- + id: 8 + uid: 24 + org_id: 25 + is_public: true diff --git a/models/fixtures/repo_topic.yml b/models/fixtures/repo_topic.yml index 7041ccfd09..f166faccc1 100644 --- a/models/fixtures/repo_topic.yml +++ b/models/fixtures/repo_topic.yml @@ -17,3 +17,11 @@ - repo_id: 33 topic_id: 4 + +- + repo_id: 2 + topic_id: 5 + +- + repo_id: 2 + topic_id: 6 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index 43311c4764..014e5155ba 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -402,4 +402,39 @@ repo_id: 11 type: 1 config: "{}" - created_unix: 946684810 \ No newline at end of file + created_unix: 946684810 + +- + id: 59 + repo_id: 42 + type: 1 + config: "{}" + created_unix: 946684810 + +- + id: 60 + repo_id: 42 + type: 4 + config: "{}" + created_unix: 946684810 + +- + id: 61 + repo_id: 42 + type: 5 + config: "{}" + created_unix: 946684810 + +- + id: 62 + repo_id: 42 + type: 2 + config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}" + created_unix: 946684810 + +- + id: 63 + repo_id: 42 + type: 3 + config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index f43fae3d67..2e38c5e1dd 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -165,6 +165,7 @@ owner_id: 14 lower_name: test_repo_14 name: test_repo_14 + description: test_description_14 is_private: false num_issues: 0 num_closed_issues: 0 @@ -496,4 +497,26 @@ num_stars: 0 num_forks: 0 num_issues: 0 + is_mirror: false + +- + id: 42 + owner_id: 2 + lower_name: glob + name: glob + is_private: false + num_stars: 0 + num_forks: 0 + num_issues: 0 + is_mirror: false + +- + id: 43 + owner_id: 26 + lower_name: repo26 + name: repo26 + is_private: true + num_stars: 0 + num_forks: 0 + num_issues: 0 is_mirror: false \ No newline at end of file diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index 2d0dd9cd56..4da87b731f 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -77,4 +77,22 @@ name: review_team authorize: 1 # read num_repos: 1 - num_members: 1 \ No newline at end of file + num_members: 1 + +- + id: 10 + org_id: 25 + lower_name: notowners + name: NotOwners + authorize: 1 # owner + num_repos: 0 + num_members: 1 + +- + id: 11 + org_id: 26 + lower_name: team11 + name: team11 + authorize: 1 # read + num_repos: 0 + num_members: 0 diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index e20b5c9684..4fc609791d 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -62,4 +62,10 @@ id: 11 org_id: 17 team_id: 9 - uid: 20 \ No newline at end of file + uid: 20 + +- + id: 12 + org_id: 25 + team_id: 10 + uid: 24 diff --git a/models/fixtures/topic.yml b/models/fixtures/topic.yml index c868b207cb..6cd0b37fa1 100644 --- a/models/fixtures/topic.yml +++ b/models/fixtures/topic.yml @@ -15,3 +15,11 @@ - id: 4 name: graphql repo_count: 1 + +- id: 5 + name: topicname1 + repo_count: 1 + +- id: 6 + name: topicname2 + repo_count: 2 diff --git a/models/fixtures/two_factor.yml b/models/fixtures/two_factor.yml new file mode 100644 index 0000000000..d8cb85274b --- /dev/null +++ b/models/fixtures/two_factor.yml @@ -0,0 +1,9 @@ +- + id: 1 + uid: 24 + secret: KlDporn6Ile4vFcKI8z7Z6sqK1Scj2Qp0ovtUzCZO6jVbRW2lAoT7UDxDPtrab8d2B9zKOocBRdBJnS8orsrUNrsyETY+jJHb79M82uZRioKbRUz15sfOpmJmEzkFeSg6S4LicUBQos= + scratch_salt: Qb5bq2DyR2 + scratch_hash: 068eb9b8746e0bcfe332fac4457693df1bda55800eb0f6894d14ebb736ae6a24e0fc8fc5333c19f57f81599788f0b8e51ec1 + last_used_passcode: + created_unix: 1564253724 + updated_unix: 1564253724 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index ed60e7f5ea..a204241f9c 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -6,6 +6,7 @@ name: user1 full_name: User One email: user1@example.com + email_notifications_preference: enabled passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 0 # individual salt: ZogKvWdyEx @@ -22,13 +23,14 @@ full_name: " < Ur Tw >< " email: user2@example.com keep_email_private: true + email_notifications_preference: enabled passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 0 # individual salt: ZogKvWdyEx is_admin: false avatar: avatar2 avatar_email: user2@example.com - num_repos: 8 + num_repos: 9 num_stars: 2 num_followers: 2 num_following: 1 @@ -40,6 +42,7 @@ name: user3 full_name: " <<<< >> >> > >> > >>> >> " email: user3@example.com + email_notifications_preference: onmention passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 1 # organization salt: ZogKvWdyEx @@ -56,6 +59,7 @@ name: user4 full_name: " " email: user4@example.com + email_notifications_preference: onmention passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 0 # individual salt: ZogKvWdyEx @@ -72,6 +76,7 @@ name: user5 full_name: User Five email: user5@example.com + email_notifications_preference: enabled passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 0 # individual salt: ZogKvWdyEx @@ -89,6 +94,7 @@ name: user6 full_name: User Six email: user6@example.com + email_notifications_preference: enabled passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 1 # organization salt: ZogKvWdyEx @@ -105,6 +111,7 @@ name: user7 full_name: User Seven email: user7@example.com + email_notifications_preference: disabled passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 1 # organization salt: ZogKvWdyEx @@ -121,6 +128,7 @@ name: user8 full_name: User Eight email: user8@example.com + email_notifications_preference: enabled passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 0 # individual salt: ZogKvWdyEx @@ -138,6 +146,7 @@ name: user9 full_name: User Nine email: user9@example.com + email_notifications_preference: onmention passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password type: 0 # individual salt: ZogKvWdyEx @@ -365,4 +374,57 @@ is_active: true num_members: 0 num_teams: 0 - visibility: 2 \ No newline at end of file + visibility: 2 + +- + id: 24 + lower_name: user24 + name: user24 + full_name: "user24" + email: user24@example.com + keep_email_private: true + passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + type: 0 # individual + salt: ZogKvWdyEx + is_admin: false + avatar: avatar24 + avatar_email: user24@example.com + num_repos: 0 + num_stars: 0 + num_followers: 0 + num_following: 0 + is_active: true + +- + id: 25 + lower_name: org25 + name: org25 + full_name: "org25" + email: org25@example.com + passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + type: 1 # organization + salt: ZogKvWdyEx + is_admin: false + avatar: avatar25 + avatar_email: org25@example.com + num_repos: 0 + num_members: 1 + num_teams: 1 + +- + id: 26 + lower_name: org26 + name: org26 + full_name: "Org26" + email: org26@example.com + email_notifications_preference: onmention + passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + type: 1 # organization + salt: ZogKvWdyEx + is_admin: false + avatar: avatar26 + avatar_email: org26@example.com + num_repos: 1 + num_members: 0 + num_teams: 1 + repo_admin_change_team_access: true \ No newline at end of file diff --git a/models/fixtures/webhook.yml b/models/fixtures/webhook.yml index 11d7439cf4..5563dcada7 100644 --- a/models/fixtures/webhook.yml +++ b/models/fixtures/webhook.yml @@ -22,3 +22,10 @@ content_type: 1 # json events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' is_active: true +- + id: 4 + repo_id: 2 + url: www.example.com/url4 + content_type: 1 # json + events: '{"push_only":true,"branch_filter":"{master,feature*}"}' + is_active: true diff --git a/models/gpg_key.go b/models/gpg_key.go index 8300cdbd21..72c6891d4d 100644 --- a/models/gpg_key.go +++ b/models/gpg_key.go @@ -17,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" "github.com/keybase/go-crypto/openpgp" @@ -27,14 +27,14 @@ import ( // GPGKey represents a GPG key. type GPGKey struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"INDEX NOT NULL"` - KeyID string `xorm:"INDEX CHAR(16) NOT NULL"` - PrimaryKeyID string `xorm:"CHAR(16)"` - Content string `xorm:"TEXT NOT NULL"` - CreatedUnix util.TimeStamp `xorm:"created"` - ExpiredUnix util.TimeStamp - AddedUnix util.TimeStamp + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"INDEX NOT NULL"` + KeyID string `xorm:"INDEX CHAR(16) NOT NULL"` + PrimaryKeyID string `xorm:"CHAR(16)"` + Content string `xorm:"TEXT NOT NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + ExpiredUnix timeutil.TimeStamp + AddedUnix timeutil.TimeStamp SubsKey []*GPGKey `xorm:"-"` Emails []*EmailAddress CanSign bool @@ -51,7 +51,7 @@ type GPGKeyImport struct { // BeforeInsert will be invoked by XORM before inserting a record func (key *GPGKey) BeforeInsert() { - key.AddedUnix = util.TimeStampNow() + key.AddedUnix = timeutil.TimeStampNow() } // AfterLoad is invoked from XORM after setting the values of all fields of this object. @@ -223,8 +223,8 @@ func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, e KeyID: pubkey.KeyIdString(), PrimaryKeyID: primaryID, Content: content, - CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()), - ExpiredUnix: util.TimeStamp(expiry.Unix()), + CreatedUnix: timeutil.TimeStamp(pubkey.CreationTime.Unix()), + ExpiredUnix: timeutil.TimeStamp(expiry.Unix()), CanSign: pubkey.CanSign(), CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(), CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(), @@ -301,8 +301,8 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) { KeyID: pubkey.KeyIdString(), PrimaryKeyID: "", Content: content, - CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()), - ExpiredUnix: util.TimeStamp(expiry.Unix()), + CreatedUnix: timeutil.TimeStamp(pubkey.CreationTime.Unix()), + ExpiredUnix: timeutil.TimeStamp(expiry.Unix()), Emails: emails, SubsKey: subkeys, CanSign: pubkey.CanSign(), diff --git a/models/gpg_key_test.go b/models/gpg_key_test.go index 7880046b2e..e2f92e7f81 100644 --- a/models/gpg_key_test.go +++ b/models/gpg_key_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -112,7 +112,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== key := &GPGKey{ KeyID: pubkey.KeyIdString(), Content: content, - CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()), + CreatedUnix: timeutil.TimeStamp(pubkey.CreationTime.Unix()), CanSign: pubkey.CanSign(), CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(), CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(), @@ -122,7 +122,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== cannotsignkey := &GPGKey{ KeyID: pubkey.KeyIdString(), Content: content, - CreatedUnix: util.TimeStamp(pubkey.CreationTime.Unix()), + CreatedUnix: timeutil.TimeStamp(pubkey.CreationTime.Unix()), CanSign: false, CanEncryptComms: false, CanEncryptStorage: false, diff --git a/models/helper_directory.go b/models/helper_directory.go index 417402b41c..813f0577bc 100644 --- a/models/helper_directory.go +++ b/models/helper_directory.go @@ -14,7 +14,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" + "github.com/unknwon/com" ) // LocalCopyPath returns the local repository temporary copy path. diff --git a/models/helper_environment.go b/models/helper_environment.go index 0460fc3df5..2095205db3 100644 --- a/models/helper_environment.go +++ b/models/helper_environment.go @@ -12,13 +12,13 @@ import ( // PushingEnvironment returns an os environment to allow hooks to work on push func PushingEnvironment(doer *User, repo *Repository) []string { - return FullPushingEnvironment(doer, doer, repo, 0) + return FullPushingEnvironment(doer, doer, repo, repo.Name, 0) } // FullPushingEnvironment returns an os environment to allow hooks to work on push -func FullPushingEnvironment(author, committer *User, repo *Repository, prID int64) []string { +func FullPushingEnvironment(author, committer *User, repo *Repository, repoName string, prID int64) []string { isWiki := "false" - if strings.HasSuffix(repo.Name, ".wiki") { + if strings.HasSuffix(repoName, ".wiki") { isWiki = "true" } @@ -32,7 +32,7 @@ func FullPushingEnvironment(author, committer *User, repo *Repository, prID int6 "GIT_AUTHOR_EMAIL="+authorSig.Email, "GIT_COMMITTER_NAME="+committerSig.Name, "GIT_COMMITTER_EMAIL="+committerSig.Email, - EnvRepoName+"="+repo.Name, + EnvRepoName+"="+repoName, EnvRepoUsername+"="+repo.MustOwnerName(), EnvRepoIsWiki+"="+isWiki, EnvPusherName+"="+committer.Name, diff --git a/models/issue.go b/models/issue.go index 63074cd40c..9590bc04ff 100644 --- a/models/issue.go +++ b/models/issue.go @@ -5,7 +5,6 @@ package models import ( - "errors" "fmt" "path" "regexp" @@ -16,10 +15,11 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" "xorm.io/builder" ) @@ -49,11 +49,11 @@ type Issue struct { NumComments int Ref string - DeadlineUnix util.TimeStamp `xorm:"INDEX"` + DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` - ClosedUnix util.TimeStamp `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ClosedUnix timeutil.TimeStamp `xorm:"INDEX"` Attachments []*Attachment `xorm:"-"` Comments []*Comment `xorm:"-"` @@ -73,6 +73,7 @@ var ( const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)` const issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[x]\]\s.)|(\n\s*[-*]\s\[[x]\]\s.)` +const issueMaxDupIndexAttempts = 3 func init() { issueTasksPat = regexp.MustCompile(issueTasksRegexpStr) @@ -90,7 +91,7 @@ func (issue *Issue) loadTotalTimes(e Engine) (err error) { // IsOverdue checks if the issue is overdue func (issue *Issue) IsOverdue() bool { - return util.TimeStampNow() >= issue.DeadlineUnix + return timeutil.TimeStampNow() >= issue.DeadlineUnix } // LoadRepo loads issue's repository @@ -472,6 +473,18 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { } } +// ReplyReference returns tokenized address to use for email reply headers +func (issue *Issue) ReplyReference() string { + var path string + if issue.IsPull { + path = "pulls" + } else { + path = "issues" + } + + return fmt.Sprintf("%s/%s/%d@%s", issue.Repo.FullName(), path, issue.Index, setting.Domain) +} + func (issue *Issue) addLabel(e *xorm.Session, label *Label, doer *User) error { return newIssueLabel(e, issue, label, doer) } @@ -582,8 +595,9 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { if err = sess.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } + sess.Close() - if err = issue.loadPoster(x); err != nil { + if err = issue.LoadPoster(); err != nil { return fmt.Errorf("loadPoster: %v", err) } @@ -732,7 +746,7 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (er issue.IsClosed = isClosed if isClosed { - issue.ClosedUnix = util.TimeStampNow() + issue.ClosedUnix = timeutil.TimeStampNow() } else { issue.ClosedUnix = 0 } @@ -746,11 +760,6 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (er return err } for idx := range issue.Labels { - if issue.IsClosed { - issue.Labels[idx].NumClosedIssues++ - } else { - issue.Labels[idx].NumClosedIssues-- - } if err = updateLabel(e, issue.Labels[idx]); err != nil { return err } @@ -857,9 +866,18 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { return fmt.Errorf("createChangeTitleComment: %v", err) } + if err = issue.neuterCrossReferences(sess); err != nil { + return err + } + + if err = issue.addCrossReferences(sess, doer); err != nil { + return err + } + if err = sess.Commit(); err != nil { return err } + sess.Close() mode, _ := AccessLevel(issue.Poster, issue.Repo) if issue.IsPull { @@ -926,9 +944,26 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) { oldContent := issue.Content issue.Content = content - if err = UpdateIssueCols(issue, "content"); err != nil { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if err = updateIssueCols(sess, issue, "content"); err != nil { return fmt.Errorf("UpdateIssueCols: %v", err) } + if err = issue.neuterCrossReferences(sess); err != nil { + return err + } + if err = issue.addCrossReferences(sess, doer); err != nil { + return err + } + + if err = sess.Commit(); err != nil { + return err + } + sess.Close() mode, _ := AccessLevel(issue.Poster, issue.Repo) if issue.IsPull { @@ -979,7 +1014,7 @@ func (issue *Issue) GetTasksDone() int { } // GetLastEventTimestamp returns the last user visible event timestamp, either the creation of this issue or the close. -func (issue *Issue) GetLastEventTimestamp() util.TimeStamp { +func (issue *Issue) GetLastEventTimestamp() timeutil.TimeStamp { if issue.IsClosed { return issue.ClosedUnix } @@ -1018,36 +1053,9 @@ type NewIssueOptions struct { IsPull bool } -// GetMaxIndexOfIssue returns the max index on issue -func GetMaxIndexOfIssue(repoID int64) (int64, error) { - return getMaxIndexOfIssue(x, repoID) -} - -func getMaxIndexOfIssue(e Engine, repoID int64) (int64, error) { - var ( - maxIndex int64 - has bool - err error - ) - - has, err = e.SQL("SELECT COALESCE((SELECT MAX(`index`) FROM issue WHERE repo_id = ?),0)", repoID).Get(&maxIndex) - if err != nil { - return 0, err - } else if !has { - return 0, errors.New("Retrieve Max index from issue failed") - } - return maxIndex, nil -} - func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { opts.Issue.Title = strings.TrimSpace(opts.Issue.Title) - maxIndex, err := getMaxIndexOfIssue(e, opts.Issue.RepoID) - if err != nil { - return err - } - opts.Issue.Index = maxIndex + 1 - if opts.Issue.MilestoneID > 0 { milestone, err := getMilestoneByRepoID(e, opts.Issue.RepoID, opts.Issue.MilestoneID) if err != nil && !IsErrMilestoneNotExist(err) { @@ -1096,10 +1104,20 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { } // Milestone and assignee validation should happen before insert actual object. - if _, err = e.Insert(opts.Issue); err != nil { + if _, err := e.SetExpr("`index`", "coalesce(MAX(`index`),0)+1"). + Where("repo_id=?", opts.Issue.RepoID). + Insert(opts.Issue); err != nil { + return ErrNewIssueInsert{err} + } + + inserted, err := getIssueByID(e, opts.Issue.ID) + if err != nil { return err } + // Patch Index with the value calculated by the database + opts.Issue.Index = inserted.Index + if opts.Issue.MilestoneID > 0 { if err = changeMilestoneAssign(e, doer, opts.Issue, -1); err != nil { return err @@ -1164,12 +1182,32 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { } } } - - return opts.Issue.loadAttributes(e) + if err = opts.Issue.loadAttributes(e); err != nil { + return err + } + return opts.Issue.addCrossReferences(e, doer) } // NewIssue creates new issue with labels for repository. func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) { + // Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887 + i := 0 + for { + if err = newIssueAttempt(repo, issue, labelIDs, assigneeIDs, uuids); err == nil { + return nil + } + if !IsErrNewIssueInsert(err) { + return err + } + if i++; i == issueMaxDupIndexAttempts { + break + } + log.Error("NewIssue: error attempting to insert the new issue; will retry. Original error: %v", err) + } + return fmt.Errorf("NewIssue: too many errors attempting to insert the new issue. Last error was: %v", err) +} + +func newIssueAttempt(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) { sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { @@ -1183,7 +1221,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in Attachments: uuids, AssigneeIDs: assigneeIDs, }); err != nil { - if IsErrUserDoesNotHaveAccessToRepo(err) { + if IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { return err } return fmt.Errorf("newIssue: %v", err) @@ -1193,31 +1231,6 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in return fmt.Errorf("Commit: %v", err) } - if err = NotifyWatchers(&Action{ - ActUserID: issue.Poster.ID, - ActUser: issue.Poster, - OpType: ActionCreateIssue, - Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title), - RepoID: repo.ID, - Repo: repo, - IsPrivate: repo.IsPrivate, - }); err != nil { - log.Error("NotifyWatchers: %v", err) - } - - mode, _ := AccessLevel(issue.Poster, issue.Repo) - if err = PrepareWebhooks(repo, HookEventIssues, &api.IssuePayload{ - Action: api.HookIssueOpened, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: repo.APIFormat(mode), - Sender: issue.Poster.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks: %v", err) - } else { - go HookQueue.Add(issue.RepoID) - } - return nil } @@ -1448,7 +1461,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) { userIDs := make([]int64, 0, 5) if err := e.Table("comment").Cols("poster_id"). Where("`comment`.issue_id = ?", issueID). - And("`comment`.type = ?", CommentTypeComment). + And("`comment`.type in (?,?,?)", CommentTypeComment, CommentTypeCode, CommentTypeReview). And("`user`.is_active = ?", true). And("`user`.prohibit_login = ?", false). Join("INNER", "`user`", "`user`.id = `comment`.poster_id"). @@ -1466,7 +1479,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) { // UpdateIssueMentions extracts mentioned people from content and // updates issue-user relations for them. -func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error { +func UpdateIssueMentions(ctx DBContext, issueID int64, mentions []string) error { if len(mentions) == 0 { return nil } @@ -1476,7 +1489,7 @@ func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error { } users := make([]*User, 0, len(mentions)) - if err := e.In("lower_name", mentions).Asc("lower_name").Find(&users); err != nil { + if err := ctx.e.In("lower_name", mentions).Asc("lower_name").Find(&users); err != nil { return fmt.Errorf("find mentioned users: %v", err) } @@ -1488,7 +1501,7 @@ func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error { } memberIDs := make([]int64, 0, user.NumMembers) - orgUsers, err := getOrgUsersByOrgID(e, user.ID) + orgUsers, err := getOrgUsersByOrgID(ctx.e, user.ID) if err != nil { return fmt.Errorf("GetOrgUsersByOrgID [%d]: %v", user.ID, err) } @@ -1500,7 +1513,7 @@ func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error { ids = append(ids, memberIDs...) } - if err := UpdateIssueUsersByMentions(e, issueID, ids); err != nil { + if err := UpdateIssueUsersByMentions(ctx, issueID, ids); err != nil { return fmt.Errorf("UpdateIssueUsersByMentions: %v", err) } @@ -1648,14 +1661,14 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { return nil, err } case FilterModeAssign: - stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). + stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false). Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). And("issue_assignees.assignee_id = ?", opts.UserID). Count(new(Issue)) if err != nil { return nil, err } - stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). + stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true). Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). And("issue_assignees.assignee_id = ?", opts.UserID). Count(new(Issue)) @@ -1675,6 +1688,21 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { if err != nil { return nil, err } + case FilterModeMention: + stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false). + Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true). + And("issue_user.uid = ?", opts.UserID). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true). + Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true). + And("issue_user.uid = ?", opts.UserID). + Count(new(Issue)) + if err != nil { + return nil, err + } } cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed}) @@ -1693,6 +1721,14 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { return nil, err } + stats.MentionCount, err = x.Where(cond). + Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true). + And("issue_user.uid = ?", opts.UserID). + Count(new(Issue)) + if err != nil { + return nil, err + } + stats.YourRepositoriesCount, err = x.Where(cond). And(builder.In("issue.repo_id", opts.UserRepoIDs)). Count(new(Issue)) @@ -1778,11 +1814,28 @@ func updateIssue(e Engine, issue *Issue) error { // UpdateIssue updates all fields of given issue. func UpdateIssue(issue *Issue) error { - return updateIssue(x, issue) + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := updateIssue(sess, issue); err != nil { + return err + } + if err := issue.neuterCrossReferences(sess); err != nil { + return err + } + if err := issue.loadPoster(sess); err != nil { + return err + } + if err := issue.addCrossReferences(sess, issue.Poster); err != nil { + return err + } + return sess.Commit() } // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. -func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User) (err error) { +func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *User) (err error) { // if the deadline hasn't changed do nothing if issue.DeadlineUnix == deadlineUnix { @@ -1837,3 +1890,22 @@ func (issue *Issue) BlockedByDependencies() ([]*Issue, error) { func (issue *Issue) BlockingDependencies() ([]*Issue, error) { return issue.getBlockingDependencies(x) } + +func (issue *Issue) updateClosedNum(e Engine) (err error) { + if issue.IsPull { + _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT count(*) FROM issue WHERE repo_id=? AND is_pull=? AND is_closed=?) WHERE id=?", + issue.RepoID, + true, + true, + issue.RepoID, + ) + } else { + _, err = e.Exec("UPDATE `repository` SET num_closed_issues=(SELECT count(*) FROM issue WHERE repo_id=? AND is_pull=? AND is_closed=?) WHERE id=?", + issue.RepoID, + false, + true, + issue.RepoID, + ) + } + return +} diff --git a/models/issue_assignees.go b/models/issue_assignees.go index f6a2afe2c9..1f504a9950 100644 --- a/models/issue_assignees.go +++ b/models/issue_assignees.go @@ -8,8 +8,8 @@ import ( "fmt" "code.gitea.io/gitea/modules/log" - api "code.gitea.io/gitea/modules/structs" + "github.com/go-xorm/xorm" ) @@ -142,11 +142,15 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) { return err } - return sess.Commit() + if err := sess.Commit(); err != nil { + return err + } + + go HookQueue.Add(issue.RepoID) + return nil } func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID int64, isCreate bool) (err error) { - // Update the assignee removed, err := updateIssueAssignee(sess, issue, assigneeID) if err != nil { @@ -209,7 +213,6 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in return nil } } - go HookQueue.Add(issue.RepoID) return nil } diff --git a/models/issue_comment.go b/models/issue_comment.go index b930b0b12a..e8043c1ec7 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -7,22 +7,18 @@ package models import ( - "bytes" "fmt" "strings" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/markup/markdown" - "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" - "github.com/go-xorm/xorm" - "xorm.io/builder" - - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/markup/markdown" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/go-xorm/xorm" + "github.com/unknwon/com" + "xorm.io/builder" ) // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference. @@ -99,10 +95,10 @@ 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:"-"` + ID int64 `xorm:"pk autoincr"` + Type CommentType `xorm:"index"` + PosterID int64 `xorm:"INDEX"` + Poster *User `xorm:"-"` OriginalAuthor string OriginalAuthorID int64 IssueID int64 `xorm:"INDEX"` @@ -130,8 +126,8 @@ type Comment struct { // Path represents the 4 lines of code cemented by this comment Patch string `xorm:"TEXT"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` // Reference issue in commit message CommitSHA string `xorm:"VARCHAR(40)"` @@ -143,21 +139,37 @@ type Comment struct { ShowTag CommentTag `xorm:"-"` Review *Review `xorm:"-"` - ReviewID int64 + ReviewID int64 `xorm:"index"` Invalidated bool + + // Reference an issue or pull from another comment, issue or PR + // All information is about the origin of the reference + RefRepoID int64 `xorm:"index"` // Repo where the referencing + RefIssueID int64 `xorm:"index"` + RefCommentID int64 `xorm:"index"` // 0 if origin is Issue title or content (or PR's) + RefAction XRefAction `xorm:"SMALLINT"` // What hapens if RefIssueID resolves + RefIsPull bool + + RefRepo *Repository `xorm:"-"` + RefIssue *Issue `xorm:"-"` + RefComment *Comment `xorm:"-"` } // LoadIssue loads issue from database func (c *Comment) LoadIssue() (err error) { + return c.loadIssue(x) +} + +func (c *Comment) loadIssue(e Engine) (err error) { if c.Issue != nil { return nil } - c.Issue, err = GetIssueByID(c.IssueID) + c.Issue, err = getIssueByID(e, c.IssueID) return } func (c *Comment) loadPoster(e Engine) (err error) { - if c.Poster != nil { + if c.PosterID <= 0 || c.Poster != nil { return nil } @@ -326,21 +338,7 @@ func (c *Comment) LoadMilestone() error { // LoadPoster loads comment poster func (c *Comment) LoadPoster() error { - if c.PosterID <= 0 || c.Poster != nil { - return nil - } - - var err error - c.Poster, err = getUserByID(x, c.PosterID) - if err != nil { - if IsErrUserNotExist(err) { - c.PosterID = -1 - c.Poster = NewGhostUser() - } else { - log.Error("getUserByID[%d]: %v", c.ID, err) - } - } - return nil + return c.loadPoster(x) } // LoadAttachments loads attachments @@ -382,40 +380,6 @@ func (c *Comment) LoadDepIssueDetails() (err error) { return err } -// MailParticipants sends new comment emails to repository watchers -// and mentioned people. -func (c *Comment) MailParticipants(opType ActionType, issue *Issue) (err error) { - return c.mailParticipants(x, opType, issue) -} - -func (c *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { - mentions := markup.FindAllMentions(c.Content) - if err = UpdateIssueMentions(e, c.IssueID, mentions); err != nil { - return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err) - } - - if len(c.Content) > 0 { - if err = mailIssueCommentToParticipants(e, issue, c.Poster, c.Content, c, mentions); err != nil { - log.Error("mailIssueCommentToParticipants: %v", err) - } - } - - switch opType { - case ActionCloseIssue: - ct := fmt.Sprintf("Closed #%d.", issue.Index) - if err = mailIssueCommentToParticipants(e, issue, c.Poster, ct, c, mentions); err != nil { - log.Error("mailIssueCommentToParticipants: %v", err) - } - case ActionReopenIssue: - ct := fmt.Sprintf("Reopened #%d.", issue.Index) - if err = mailIssueCommentToParticipants(e, issue, c.Poster, ct, c, mentions); err != nil { - log.Error("mailIssueCommentToParticipants: %v", err) - } - } - - return nil -} - func (c *Comment) loadReactions(e Engine) (err error) { if c.Reactions != nil { return nil @@ -462,7 +426,7 @@ func (c *Comment) checkInvalidation(doer *User, repo *git.Repository, branch str } if c.CommitSHA != "" && c.CommitSHA != commit.ID.String() { c.Invalidated = true - return UpdateComment(doer, c, "") + return UpdateComment(c, doer) } return nil } @@ -489,32 +453,6 @@ func (c *Comment) UnsignedLine() uint64 { return uint64(c.Line) } -// AsDiff returns c.Patch as *Diff -func (c *Comment) AsDiff() (*Diff, error) { - diff, err := ParsePatch(setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch)) - if err != nil { - return nil, err - } - if len(diff.Files) == 0 { - return nil, fmt.Errorf("no file found for comment ID: %d", c.ID) - } - secs := diff.Files[0].Sections - if len(secs) == 0 { - return nil, fmt.Errorf("no sections found for comment ID: %d", c.ID) - } - return diff, nil -} - -// MustAsDiff executes AsDiff and logs the error instead of returning -func (c *Comment) MustAsDiff() *Diff { - diff, err := c.AsDiff() - if err != nil { - log.Warn("MustAsDiff: %v", err) - } - return diff -} - // CodeCommentURL returns the url to a comment in code func (c *Comment) CodeCommentURL() string { err := c.LoadIssue() @@ -556,6 +494,11 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err TreePath: opts.TreePath, ReviewID: opts.ReviewID, Patch: opts.Patch, + RefRepoID: opts.RefRepoID, + RefIssueID: opts.RefIssueID, + RefCommentID: opts.RefCommentID, + RefAction: opts.RefAction, + RefIsPull: opts.RefIsPull, } if _, err = e.Insert(comment); err != nil { return nil, err @@ -569,6 +512,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err return nil, err } + if err = comment.addCrossReferences(e, opts.Doer); err != nil { + return nil, err + } + return comment, nil } @@ -634,12 +581,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen act.OpType = ActionReopenPullRequest } - if opts.Issue.IsPull { - _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID) - } else { - _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) - } - if err != nil { + if err = opts.Issue.updateClosedNum(e); err != nil { return err } @@ -649,12 +591,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen act.OpType = ActionClosePullRequest } - if opts.Issue.IsPull { - _, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID) - } else { - _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) - } - if err != nil { + if err = opts.Issue.updateClosedNum(e); err != nil { return err } } @@ -721,7 +658,7 @@ func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue }) } -func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix util.TimeStamp) (*Comment, error) { +func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) { var content string var commentType CommentType @@ -833,6 +770,11 @@ type CreateCommentOptions struct { ReviewID int64 Content string Attachments []string // UUIDs of attachments + RefRepoID int64 + RefIssueID int64 + RefCommentID int64 + RefAction XRefAction + RefIsPull bool } // CreateComment creates comment of issue or commit. @@ -855,88 +797,6 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) { return comment, nil } -// CreateIssueComment creates a plain issue comment. -func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { - comment, err := CreateComment(&CreateCommentOptions{ - Type: CommentTypeComment, - Doer: doer, - Repo: repo, - Issue: issue, - Content: content, - Attachments: attachments, - }) - if err != nil { - return nil, fmt.Errorf("CreateComment: %v", err) - } - - mode, _ := AccessLevel(doer, repo) - if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{ - Action: api.HookIssueCommentCreated, - Issue: issue.APIFormat(), - Comment: comment.APIFormat(), - Repository: repo.APIFormat(mode), - Sender: doer.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) - } else { - go HookQueue.Add(repo.ID) - } - return comment, nil -} - -// CreateCodeComment creates a plain code comment at the specified line / path -func CreateCodeComment(doer *User, repo *Repository, issue *Issue, content, treePath string, line, reviewID int64) (*Comment, error) { - var commitID, patch string - pr, err := GetPullRequestByIssueID(issue.ID) - if err != nil { - return nil, fmt.Errorf("GetPullRequestByIssueID: %v", err) - } - if err := pr.GetBaseRepo(); err != nil { - return nil, fmt.Errorf("GetHeadRepo: %v", err) - } - gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) - if err != nil { - return nil, fmt.Errorf("OpenRepository: %v", err) - } - - // FIXME validate treePath - // Get latest commit referencing the commented line - // No need for get commit for base branch changes - if line > 0 { - commit, err := gitRepo.LineBlame(pr.GetGitRefName(), gitRepo.Path, treePath, uint(line)) - if err == nil { - commitID = commit.ID.String() - } else if !strings.Contains(err.Error(), "exit status 128 - fatal: no such path") { - return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitRefName(), gitRepo.Path, treePath, line, err) - } - } - - // Only fetch diff if comment is review comment - if reviewID != 0 { - headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) - if err != nil { - return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) - } - patchBuf := new(bytes.Buffer) - if err := GetRawDiffForFile(gitRepo.Path, pr.MergeBase, headCommitID, RawDiffNormal, treePath, patchBuf); err != nil { - return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", err, gitRepo.Path, pr.MergeBase, headCommitID, treePath) - } - patch = CutDiffAroundLine(strings.NewReader(patchBuf.String()), int64((&Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) - } - return CreateComment(&CreateCommentOptions{ - Type: CommentTypeCode, - Doer: doer, - Repo: repo, - Issue: issue, - Content: content, - LineNum: line, - TreePath: treePath, - CommitSHA: commitID, - ReviewID: reviewID, - Patch: patch, - }) -} - // CreateRefComment creates a commit reference comment to issue. func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { if len(commitSHA) == 0 { @@ -1025,48 +885,34 @@ func FindComments(opts FindCommentsOptions) ([]*Comment, error) { } // UpdateComment updates information of comment. -func UpdateComment(doer *User, c *Comment, oldContent string) error { - if _, err := x.ID(c.ID).AllCols().Update(c); err != nil { +func UpdateComment(c *Comment, doer *User) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { return err } - if err := c.LoadPoster(); err != nil { + if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil { return err } - if err := c.LoadIssue(); err != nil { + if err := c.loadIssue(sess); err != nil { return err } - - if err := c.Issue.LoadAttributes(); err != nil { + if err := c.neuterCrossReferences(sess); err != nil { return err } - if err := c.loadPoster(x); err != nil { + if err := c.addCrossReferences(sess, doer); err != nil { return err } - - mode, _ := AccessLevel(doer, c.Issue.Repo) - if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ - Action: api.HookIssueCommentEdited, - Issue: c.Issue.APIFormat(), - Comment: c.APIFormat(), - Changes: &api.ChangesPayload{ - Body: &api.ChangesFromPayload{ - From: oldContent, - }, - }, - Repository: c.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err) - } else { - go HookQueue.Add(c.Issue.Repo.ID) + if err := sess.Commit(); err != nil { + return fmt.Errorf("Commit: %v", err) } return nil } // DeleteComment deletes the comment -func DeleteComment(doer *User, comment *Comment) error { +func DeleteComment(comment *Comment, doer *User) error { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { @@ -1088,40 +934,11 @@ func DeleteComment(doer *User, comment *Comment) error { return err } - if err := sess.Commit(); err != nil { - return err - } - sess.Close() - - if err := comment.LoadPoster(); err != nil { - return err - } - if err := comment.LoadIssue(); err != nil { + if err := comment.neuterCrossReferences(sess); err != nil { return err } - if err := comment.Issue.LoadAttributes(); err != nil { - return err - } - if err := comment.loadPoster(x); err != nil { - return err - } - - mode, _ := AccessLevel(doer, comment.Issue.Repo) - - if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ - Action: api.HookIssueCommentDeleted, - Issue: comment.Issue.APIFormat(), - Comment: comment.APIFormat(), - Repository: comment.Issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err) - } else { - go HookQueue.Add(comment.Issue.Repo.ID) - } - - return nil + return sess.Commit() } // CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS diff --git a/models/issue_dependency.go b/models/issue_dependency.go index ffa972c106..c880bd59c5 100644 --- a/models/issue_dependency.go +++ b/models/issue_dependency.go @@ -7,17 +7,17 @@ package models import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) // 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"` + 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 timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } // DependencyType Defines Dependency Type Constants diff --git a/models/issue_label.go b/models/issue_label.go index 7af6060f87..dab5ba2827 100644 --- a/models/issue_label.go +++ b/models/issue_label.go @@ -11,9 +11,10 @@ import ( "strconv" "strings" - "github.com/go-xorm/xorm" - api "code.gitea.io/gitea/modules/structs" + + "github.com/go-xorm/xorm" + "xorm.io/builder" ) var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})") @@ -127,6 +128,34 @@ func (label *Label) ForegroundColor() template.CSS { return template.CSS("#000") } +func initalizeLabels(e Engine, repoID int64, labelTemplate string) error { + list, err := GetLabelTemplateFile(labelTemplate) + if err != nil { + return ErrIssueLabelTemplateLoad{labelTemplate, err} + } + + labels := make([]*Label, len(list)) + for i := 0; i < len(list); i++ { + labels[i] = &Label{ + RepoID: repoID, + Name: list[i][0], + Description: list[i][2], + Color: list[i][1], + } + } + for _, label := range labels { + if err = newLabel(e, label); err != nil { + return err + } + } + return nil +} + +// InitalizeLabels adds a label set to a repository using a template +func InitalizeLabels(repoID int64, labelTemplate string) error { + return initalizeLabels(x, repoID, labelTemplate) +} + func newLabel(e Engine, label *Label) error { _, err := e.Insert(label) return err @@ -266,7 +295,20 @@ func GetLabelsByIssueID(issueID int64) ([]*Label, error) { } func updateLabel(e Engine, l *Label) error { - _, err := e.ID(l.ID).AllCols().Update(l) + _, err := e.ID(l.ID). + SetExpr("num_issues", + builder.Select("count(*)").From("issue_label"). + Where(builder.Eq{"label_id": l.ID}), + ). + SetExpr("num_closed_issues", + builder.Select("count(*)").From("issue_label"). + InnerJoin("issue", "issue_label.issue_id = issue.id"). + Where(builder.Eq{ + "issue_label.label_id": l.ID, + "issue.is_closed": true, + }), + ). + AllCols().Update(l) return err } @@ -347,10 +389,6 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err return err } - label.NumIssues++ - if issue.IsClosed { - label.NumClosedIssues++ - } return updateLabel(e, label) } @@ -420,10 +458,6 @@ func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) ( return err } - label.NumIssues-- - if issue.IsClosed { - label.NumClosedIssues-- - } return updateLabel(e, label) } diff --git a/models/issue_label_test.go b/models/issue_label_test.go index 5cdc059cff..3cf6cc0e57 100644 --- a/models/issue_label_test.go +++ b/models/issue_label_test.go @@ -205,6 +205,7 @@ func TestNewIssueLabel(t *testing.T) { LabelID: label.ID, Content: "1", }) + label = AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label) assert.EqualValues(t, prevNumIssues+1, label.NumIssues) // re-add existing IssueLabel diff --git a/models/issue_milestone.go b/models/issue_milestone.go index f279dda195..f8f414e716 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -7,10 +7,10 @@ package models import ( "fmt" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + "github.com/go-xorm/xorm" ) @@ -29,8 +29,8 @@ type Milestone struct { IsOverdue bool `xorm:"-"` DeadlineString string `xorm:"-"` - DeadlineUnix util.TimeStamp - ClosedDateUnix util.TimeStamp + DeadlineUnix timeutil.TimeStamp + ClosedDateUnix timeutil.TimeStamp TotalTrackedTime int64 `xorm:"-"` } @@ -53,7 +53,7 @@ func (m *Milestone) AfterLoad() { } m.DeadlineString = m.DeadlineUnix.Format("2006-01-02") - if util.TimeStampNow() >= m.DeadlineUnix { + if timeutil.TimeStampNow() >= m.DeadlineUnix { m.IsOverdue = true } } @@ -393,46 +393,6 @@ func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err if err = sess.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } - - var hookAction api.HookIssueAction - if issue.MilestoneID > 0 { - hookAction = api.HookIssueMilestoned - } else { - hookAction = api.HookIssueDemilestoned - } - - if err = issue.LoadAttributes(); err != nil { - return err - } - - mode, _ := AccessLevel(doer, issue.Repo) - if issue.IsPull { - err = issue.PullRequest.LoadIssue() - if err != nil { - log.Error("LoadIssue: %v", err) - return - } - err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ - Action: hookAction, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{ - Action: hookAction, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } else { - go HookQueue.Add(issue.RepoID) - } return nil } diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go index f9e51aff31..09c6ff7595 100644 --- a/models/issue_milestone_test.go +++ b/models/issue_milestone_test.go @@ -10,7 +10,7 @@ import ( "time" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -29,7 +29,7 @@ func TestMilestone_APIFormat(t *testing.T) { IsClosed: false, NumOpenIssues: 5, NumClosedIssues: 6, - DeadlineUnix: util.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()), + DeadlineUnix: timeutil.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()), } assert.Equal(t, api.Milestone{ ID: milestone.ID, @@ -237,7 +237,7 @@ func TestChangeMilestoneIssueStats(t *testing.T) { "is_closed=0").(*Issue) issue.IsClosed = true - issue.ClosedUnix = util.TimeStampNow() + issue.ClosedUnix = timeutil.TimeStampNow() _, err := x.Cols("is_closed", "closed_unix").Update(issue) assert.NoError(t, err) assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue)) diff --git a/models/issue_reaction.go b/models/issue_reaction.go index e0df6f757b..ab644b4b3e 100644 --- a/models/issue_reaction.go +++ b/models/issue_reaction.go @@ -9,7 +9,7 @@ import ( "fmt" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" "xorm.io/builder" @@ -17,13 +17,13 @@ import ( // Reaction represents a reactions on issues and comments. type Reaction struct { - ID int64 `xorm:"pk autoincr"` - Type string `xorm:"INDEX UNIQUE(s) NOT NULL"` - IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"` - CommentID int64 `xorm:"INDEX UNIQUE(s)"` - UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"` - User *User `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` + ID int64 `xorm:"pk autoincr"` + Type string `xorm:"INDEX UNIQUE(s) NOT NULL"` + IssueID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"` + CommentID int64 `xorm:"INDEX UNIQUE(s)"` + UserID int64 `xorm:"INDEX UNIQUE(s) NOT NULL"` + User *User `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } // FindReactionsOptions describes the conditions to Find reactions diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go index d754e7e19e..d7c3a9f73b 100644 --- a/models/issue_stopwatch.go +++ b/models/issue_stopwatch.go @@ -8,15 +8,15 @@ import ( "fmt" "time" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) // Stopwatch represents a stopwatch for time tracking. type Stopwatch struct { - ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` - UserID int64 `xorm:"INDEX"` - CreatedUnix util.TimeStamp `xorm:"created"` + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + UserID int64 `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) { diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go index 7983240476..41d93c8a14 100644 --- a/models/issue_stopwatch_test.go +++ b/models/issue_stopwatch_test.go @@ -3,7 +3,7 @@ package models import ( "testing" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -63,7 +63,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) { assert.NoError(t, CreateOrStopIssueStopwatch(user3, issue1)) sw := AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch) - assert.Equal(t, true, sw.CreatedUnix <= util.TimeStampNow()) + assert.Equal(t, true, sw.CreatedUnix <= timeutil.TimeStampNow()) assert.NoError(t, CreateOrStopIssueStopwatch(user2, issue2)) AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2}) diff --git a/models/issue_test.go b/models/issue_test.go index 1a7e45ae02..9cd9ff0ad9 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -279,6 +279,19 @@ func TestGetUserIssueStats(t *testing.T) { ClosedCount: 2, }, }, + { + UserIssueStatsOptions{ + UserID: 1, + FilterMode: FilterModeMention, + }, + IssueStats{ + YourRepositoriesCount: 0, + AssignCount: 2, + CreateCount: 2, + OpenCount: 0, + ClosedCount: 0, + }, + }, } { stats, err := GetUserIssueStats(test.Opts) if !assert.NoError(t, err) { @@ -320,3 +333,36 @@ func TestIssue_SearchIssueIDsByKeyword(t *testing.T) { assert.EqualValues(t, 1, total) assert.EqualValues(t, []int64{1}, ids) } + +func testInsertIssue(t *testing.T, title, content string) { + repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + + var issue = Issue{ + RepoID: repo.ID, + PosterID: user.ID, + Title: title, + Content: content, + } + err := NewIssue(repo, &issue, nil, nil, nil) + assert.NoError(t, err) + + var newIssue Issue + has, err := x.ID(issue.ID).Get(&newIssue) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, issue.Title, newIssue.Title) + assert.EqualValues(t, issue.Content, newIssue.Content) + // there are 4 issues and max index is 4 on repository 1, so this one should 5 + assert.EqualValues(t, 5, newIssue.Index) + + _, err = x.ID(issue.ID).Delete(new(Issue)) + assert.NoError(t, err) +} + +func TestIssue_InsertIssue(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + testInsertIssue(t, "my issue1", "special issue's comments?") + testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?") +} diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go index 5482a45f2a..f9313b7653 100644 --- a/models/issue_tracked_time.go +++ b/models/issue_tracked_time.go @@ -26,7 +26,7 @@ type TrackedTime struct { // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (t *TrackedTime) AfterLoad() { - t.Created = time.Unix(t.CreatedUnix, 0).In(setting.UILocation) + t.Created = time.Unix(t.CreatedUnix, 0).In(setting.DefaultUILocation) } // APIFormat converts TrackedTime to API format diff --git a/models/issue_user.go b/models/issue_user.go index 58eb5117f8..d55a0dc2fb 100644 --- a/models/issue_user.go +++ b/models/issue_user.go @@ -94,22 +94,22 @@ func UpdateIssueUserByRead(uid, issueID int64) error { } // UpdateIssueUsersByMentions updates issue-user pairs by mentioning. -func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error { +func UpdateIssueUsersByMentions(ctx DBContext, issueID int64, uids []int64) error { for _, uid := range uids { iu := &IssueUser{ UID: uid, IssueID: issueID, } - has, err := e.Get(iu) + has, err := ctx.e.Get(iu) if err != nil { return err } iu.IsMentioned = true if has { - _, err = e.ID(iu.ID).Cols("is_mentioned").Update(iu) + _, err = ctx.e.ID(iu.ID).Cols("is_mentioned").Update(iu) } else { - _, err = e.Insert(iu) + _, err = ctx.e.Insert(iu) } if err != nil { return err diff --git a/models/issue_user_test.go b/models/issue_user_test.go index a333bc8619..a57ab33f9e 100644 --- a/models/issue_user_test.go +++ b/models/issue_user_test.go @@ -50,7 +50,7 @@ func TestUpdateIssueUsersByMentions(t *testing.T) { issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) uids := []int64{2, 5} - assert.NoError(t, UpdateIssueUsersByMentions(x, issue.ID, uids)) + assert.NoError(t, UpdateIssueUsersByMentions(DefaultDBContext(), issue.ID, uids)) for _, uid := range uids { AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") } diff --git a/models/issue_watch.go b/models/issue_watch.go index 579c915476..2f55c6a84d 100644 --- a/models/issue_watch.go +++ b/models/issue_watch.go @@ -4,16 +4,16 @@ package models -import "code.gitea.io/gitea/modules/util" +import "code.gitea.io/gitea/modules/timeutil" // IssueWatch is connection request for receiving issue notification. type IssueWatch struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(watch) NOT NULL"` - IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"` - IsWatching bool `xorm:"NOT NULL"` - CreatedUnix util.TimeStamp `xorm:"created NOT NULL"` - UpdatedUnix util.TimeStamp `xorm:"updated NOT NULL"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(watch) NOT NULL"` + IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"` + IsWatching bool `xorm:"NOT NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"` } // CreateOrUpdateIssueWatch set watching for a user and issue diff --git a/models/issue_xref.go b/models/issue_xref.go new file mode 100644 index 0000000000..1cc0bcfe6a --- /dev/null +++ b/models/issue_xref.go @@ -0,0 +1,308 @@ +// Copyright 2019 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 ( + "regexp" + "strconv" + + "code.gitea.io/gitea/modules/log" + + "github.com/go-xorm/xorm" + "github.com/unknwon/com" +) + +var ( + // TODO: Unify all regexp treatment of cross references in one place + + // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 + issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(?:#)([0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) + // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository + // e.g. gogits/gogs#12345 + crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+)/([0-9a-zA-Z-_\.]+)#([0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) +) + +// XRefAction represents the kind of effect a cross reference has once is resolved +type XRefAction int64 + +const ( + // XRefActionNone means the cross-reference is a mention (commit, etc.) + XRefActionNone XRefAction = iota // 0 + // XRefActionCloses means the cross-reference should close an issue if it is resolved + XRefActionCloses // 1 - not implemented yet + // XRefActionReopens means the cross-reference should reopen an issue if it is resolved + XRefActionReopens // 2 - Not implemented yet + // XRefActionNeutered means the cross-reference will no longer affect the source + XRefActionNeutered // 3 +) + +type crossReference struct { + Issue *Issue + Action XRefAction +} + +// crossReferencesContext is context to pass along findCrossReference functions +type crossReferencesContext struct { + Type CommentType + Doer *User + OrigIssue *Issue + OrigComment *Comment +} + +func newCrossReference(e *xorm.Session, ctx *crossReferencesContext, xref *crossReference) error { + var refCommentID int64 + if ctx.OrigComment != nil { + refCommentID = ctx.OrigComment.ID + } + _, err := createComment(e, &CreateCommentOptions{ + Type: ctx.Type, + Doer: ctx.Doer, + Repo: xref.Issue.Repo, + Issue: xref.Issue, + RefRepoID: ctx.OrigIssue.RepoID, + RefIssueID: ctx.OrigIssue.ID, + RefCommentID: refCommentID, + RefAction: xref.Action, + RefIsPull: xref.Issue.IsPull, + }) + return err +} + +func neuterCrossReferences(e Engine, issueID int64, commentID int64) error { + active := make([]*Comment, 0, 10) + sess := e.Where("`ref_action` IN (?, ?, ?)", XRefActionNone, XRefActionCloses, XRefActionReopens) + if issueID != 0 { + sess = sess.And("`ref_issue_id` = ?", issueID) + } + if commentID != 0 { + sess = sess.And("`ref_comment_id` = ?", commentID) + } + if err := sess.Find(&active); err != nil || len(active) == 0 { + return err + } + ids := make([]int64, len(active)) + for i, c := range active { + ids[i] = c.ID + } + _, err := e.In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: XRefActionNeutered}) + return err +} + +// .___ +// | | ______ ________ __ ____ +// | |/ ___// ___/ | \_/ __ \ +// | |\___ \ \___ \| | /\ ___/ +// |___/____ >____ >____/ \___ > +// \/ \/ \/ +// + +func (issue *Issue) addCrossReferences(e *xorm.Session, doer *User) error { + var commentType CommentType + if issue.IsPull { + commentType = CommentTypePullRef + } else { + commentType = CommentTypeIssueRef + } + ctx := &crossReferencesContext{ + Type: commentType, + Doer: doer, + OrigIssue: issue, + } + return issue.createCrossReferences(e, ctx, issue.Title+"\n"+issue.Content) +} + +func (issue *Issue) createCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) error { + xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, content) + if err != nil { + return err + } + for _, xref := range xreflist { + if err = newCrossReference(e, ctx, xref); err != nil { + return err + } + } + return nil +} + +func (issue *Issue) getCrossReferences(e *xorm.Session, ctx *crossReferencesContext, content string) ([]*crossReference, error) { + xreflist := make([]*crossReference, 0, 5) + var xref *crossReference + + // Issues in the same repository + // FIXME: Should we support IssueNameStyleAlphanumeric? + matches := issueNumericPattern.FindAllStringSubmatch(content, -1) + for _, match := range matches { + if index, err := strconv.ParseInt(match[1], 10, 64); err == nil { + if err = ctx.OrigIssue.loadRepo(e); err != nil { + return nil, err + } + if xref, err = ctx.OrigIssue.isValidCommentReference(e, ctx, issue.Repo, index); err != nil { + return nil, err + } + if xref != nil { + xreflist = ctx.OrigIssue.updateCrossReferenceList(xreflist, xref) + } + } + } + + // Issues in other repositories + matches = crossReferenceIssueNumericPattern.FindAllStringSubmatch(content, -1) + for _, match := range matches { + if index, err := strconv.ParseInt(match[3], 10, 64); err == nil { + repo, err := getRepositoryByOwnerAndName(e, match[1], match[2]) + if err != nil { + if IsErrRepoNotExist(err) { + continue + } + return nil, err + } + if err = ctx.OrigIssue.loadRepo(e); err != nil { + return nil, err + } + if xref, err = issue.isValidCommentReference(e, ctx, repo, index); err != nil { + return nil, err + } + if xref != nil { + xreflist = issue.updateCrossReferenceList(xreflist, xref) + } + } + } + + return xreflist, nil +} + +func (issue *Issue) updateCrossReferenceList(list []*crossReference, xref *crossReference) []*crossReference { + if xref.Issue.ID == issue.ID { + return list + } + for i, r := range list { + if r.Issue.ID == xref.Issue.ID { + if xref.Action != XRefActionNone { + list[i].Action = xref.Action + } + return list + } + } + return append(list, xref) +} + +func (issue *Issue) isValidCommentReference(e Engine, ctx *crossReferencesContext, repo *Repository, index int64) (*crossReference, error) { + refIssue := &Issue{RepoID: repo.ID, Index: index} + if has, _ := e.Get(refIssue); !has { + return nil, nil + } + if err := refIssue.loadRepo(e); err != nil { + return nil, err + } + // Check user permissions + if refIssue.RepoID != ctx.OrigIssue.RepoID { + perm, err := getUserRepoPermission(e, refIssue.Repo, ctx.Doer) + if err != nil { + return nil, err + } + if !perm.CanReadIssuesOrPulls(refIssue.IsPull) { + return nil, nil + } + } + return &crossReference{ + Issue: refIssue, + Action: XRefActionNone, + }, nil +} + +func (issue *Issue) neuterCrossReferences(e Engine) error { + return neuterCrossReferences(e, issue.ID, 0) +} + +// _________ __ +// \_ ___ \ ____ _____ _____ ____ _____/ |_ +// / \ \/ / _ \ / \ / \_/ __ \ / \ __\ +// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ | +// \______ /\____/|__|_| /__|_| /\___ >___| /__| +// \/ \/ \/ \/ \/ +// + +func (comment *Comment) addCrossReferences(e *xorm.Session, doer *User) error { + if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment { + return nil + } + if err := comment.loadIssue(e); err != nil { + return err + } + ctx := &crossReferencesContext{ + Type: CommentTypeCommentRef, + Doer: doer, + OrigIssue: comment.Issue, + OrigComment: comment, + } + return comment.Issue.createCrossReferences(e, ctx, comment.Content) +} + +func (comment *Comment) neuterCrossReferences(e Engine) error { + return neuterCrossReferences(e, 0, comment.ID) +} + +// LoadRefComment loads comment that created this reference from database +func (comment *Comment) LoadRefComment() (err error) { + if comment.RefComment != nil { + return nil + } + comment.RefComment, err = GetCommentByID(comment.RefCommentID) + return +} + +// LoadRefIssue loads comment that created this reference from database +func (comment *Comment) LoadRefIssue() (err error) { + if comment.RefIssue != nil { + return nil + } + comment.RefIssue, err = GetIssueByID(comment.RefIssueID) + if err == nil { + err = comment.RefIssue.loadRepo(x) + } + return +} + +// CommentTypeIsRef returns true if CommentType is a reference from another issue +func CommentTypeIsRef(t CommentType) bool { + return t == CommentTypeCommentRef || t == CommentTypePullRef || t == CommentTypeIssueRef +} + +// RefCommentHTMLURL returns the HTML URL for the comment that created this reference +func (comment *Comment) RefCommentHTMLURL() string { + if err := comment.LoadRefComment(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefComment(%d): %v", comment.RefCommentID, err) + return "" + } + return comment.RefComment.HTMLURL() +} + +// RefIssueHTMLURL returns the HTML URL of the issue where this reference was created +func (comment *Comment) RefIssueHTMLURL() string { + if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err) + return "" + } + return comment.RefIssue.HTMLURL() +} + +// RefIssueTitle returns the title of the issue where this reference was created +func (comment *Comment) RefIssueTitle() string { + if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err) + return "" + } + return comment.RefIssue.Title +} + +// RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created +func (comment *Comment) RefIssueIdent() string { + if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: + log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err) + return "" + } + // FIXME: check this name for cross-repository references (#7901 if it gets merged) + return "#" + com.ToStr(comment.RefIssue.Index) +} diff --git a/models/lfs.go b/models/lfs.go index 94d3f57905..9b20642777 100644 --- a/models/lfs.go +++ b/models/lfs.go @@ -7,17 +7,17 @@ import ( "fmt" "io" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) // LFSMetaObject stores metadata for LFS tracked files. type LFSMetaObject struct { - ID int64 `xorm:"pk autoincr"` - Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Size int64 `xorm:"NOT NULL"` - RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - Existing bool `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"created"` + ID int64 `xorm:"pk autoincr"` + Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"` + Size int64 `xorm:"NOT NULL"` + RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + Existing bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } // Pointer returns the string representation of an LFS pointer file diff --git a/models/lfs_lock.go b/models/lfs_lock.go index a27a6f495e..7ea1dc8660 100644 --- a/models/lfs_lock.go +++ b/models/lfs_lock.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "github.com/go-xorm/xorm" ) @@ -56,7 +57,7 @@ func (l *LFSLock) APIFormat() *api.LFSLock { return &api.LFSLock{ ID: strconv.FormatInt(l.ID, 10), Path: l.Path, - LockedAt: l.Created, + LockedAt: l.Created.Round(time.Second), Owner: &api.LFSLockOwner{ Name: l.Owner.DisplayName(), }, diff --git a/models/login_source.go b/models/login_source.go index 26544588c1..9381ed034f 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -14,16 +14,16 @@ import ( "regexp" "strings" - "github.com/Unknwon/com" - "github.com/go-xorm/xorm" - "xorm.io/core" - "code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/go-xorm/xorm" + "github.com/unknwon/com" + "xorm.io/core" ) // LoginType represents an login type. @@ -148,8 +148,8 @@ type LoginSource struct { IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` Cfg core.Conversion `xorm:"TEXT"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // Cell2Int64 converts a xorm.Cell type to int64, diff --git a/models/migrate.go b/models/migrate.go index b30e6a9d1c..85be3a312c 100644 --- a/models/migrate.go +++ b/models/migrate.go @@ -62,38 +62,50 @@ func insertIssue(sess *xorm.Session, issue *Issue) error { if _, err := sess.Insert(issueLabels); err != nil { return err } + + cols := make([]string, 0) if !issue.IsPull { sess.ID(issue.RepoID).Incr("num_issues") + cols = append(cols, "num_issues") if issue.IsClosed { sess.Incr("num_closed_issues") + cols = append(cols, "num_closed_issues") } } else { sess.ID(issue.RepoID).Incr("num_pulls") + cols = append(cols, "num_pulls") if issue.IsClosed { sess.Incr("num_closed_pulls") + cols = append(cols, "num_closed_pulls") } } - if _, err := sess.NoAutoTime().Update(issue.Repo); err != nil { + if _, err := sess.NoAutoTime().Cols(cols...).Update(issue.Repo); err != nil { return err } + cols = []string{"num_issues"} sess.Incr("num_issues") if issue.IsClosed { sess.Incr("num_closed_issues") + cols = append(cols, "num_closed_issues") } - if _, err := sess.In("id", labelIDs).NoAutoTime().Update(new(Label)); err != nil { + if _, err := sess.In("id", labelIDs).NoAutoTime().Cols(cols...).Update(new(Label)); err != nil { return err } if issue.MilestoneID > 0 { + cols = []string{"num_issues"} sess.Incr("num_issues") + cl := "num_closed_issues" if issue.IsClosed { sess.Incr("num_closed_issues") + cols = append(cols, "num_closed_issues") + cl = "(num_closed_issues + 1)" } if _, err := sess.ID(issue.MilestoneID). - SetExpr("completeness", "num_closed_issues * 100 / num_issues"). - NoAutoTime(). + SetExpr("completeness", cl+" * 100 / (num_issues + 1)"). + NoAutoTime().Cols(cols...). Update(new(Milestone)); err != nil { return err } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 8a7ef1a769..3b99c85e9a 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -17,14 +17,14 @@ import ( "strings" "time" - "github.com/Unknwon/com" - "github.com/go-xorm/xorm" - gouuid "github.com/satori/go.uuid" - ini "gopkg.in/ini.v1" - "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + + "github.com/go-xorm/xorm" + gouuid "github.com/satori/go.uuid" + "github.com/unknwon/com" + ini "gopkg.in/ini.v1" ) const minDBVersion = 4 @@ -235,7 +235,26 @@ var migrations = []Migration{ // v89 -> v90 NewMigration("add original author/url migration info to issues, comments, and repo ", addOriginalMigrationInfo), // v90 -> v91 + NewMigration("change length of some repository columns", changeSomeColumnsLengthOfRepo), + // v91 -> v92 + NewMigration("add index on owner_id of repository and type, review_id of comment", addIndexOnRepositoryAndComment), + // v92 -> v93 + NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), + // v93 -> v94 + NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), + // v94 -> v95 + NewMigration("add enable_status_check, status_check_contexts to protected_branch", addStatusCheckColumnsForProtectedBranches), + // v95 -> v96 + NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns), + // v96 -> v97 + NewMigration("delete orphaned attachments", deleteOrphanedAttachments), + // v97 -> v98 + NewMigration("add repo_admin_change_team_access to user", addRepoAdminChangeTeamAccessColumnForUser), + // v98 -> v99 + NewMigration("add original author name and id on migrated release", addOriginalAuthorOnMigratedReleases), + // v99 -> v100 NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories), + } // Migrate database to current version @@ -292,7 +311,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin // TODO: This will not work if there are foreign keys switch { - case setting.UseSQLite3: + case setting.Database.UseSQLite3: // First drop the indexes on the columns res, errIndex := sess.Query(fmt.Sprintf("PRAGMA index_list(`%s`)", tableName)) if errIndex != nil { @@ -325,11 +344,25 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin return err } tableSQL := string(res[0]["sql"]) + + // Separate out the column definitions tableSQL = tableSQL[strings.Index(tableSQL, "("):] + + // Remove the required columnNames for _, name := range columnNames { - tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*[,)]").ReplaceAllString(tableSQL, "") + tableSQL = regexp.MustCompile(regexp.QuoteMeta("`"+name+"`")+"[^`,)]*?[,)]").ReplaceAllString(tableSQL, "") } + // Ensure the query is ended properly + tableSQL = strings.TrimSpace(tableSQL) + if tableSQL[len(tableSQL)-1] != ')' { + if tableSQL[len(tableSQL)-1] == ',' { + tableSQL = tableSQL[:len(tableSQL)-1] + } + tableSQL += ")" + } + + // Find all the columns in the table columns := regexp.MustCompile("`([^`]*)`").FindAllString(tableSQL, -1) tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL @@ -354,7 +387,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin return err } - case setting.UsePostgreSQL: + case setting.Database.UsePostgreSQL: cols := "" for _, col := range columnNames { if cols != "" { @@ -365,7 +398,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) } - case setting.UseMySQL, setting.UseTiDB: + case setting.Database.UseMySQL: // Drop indexes on columns first sql := fmt.Sprintf("SHOW INDEX FROM %s WHERE column_name IN ('%s')", tableName, strings.Join(columnNames, "','")) res, err := sess.Query(sql) @@ -391,7 +424,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) } - case setting.UseMSSQL: + case setting.Database.UseMSSQL: cols := "" for _, col := range columnNames { if cols != "" { diff --git a/models/migrations/v13.go b/models/migrations/v13.go index f81271f981..8b6b38cadf 100644 --- a/models/migrations/v13.go +++ b/models/migrations/v13.go @@ -9,8 +9,8 @@ import ( "fmt" "strings" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) func ldapUseSSLToSecurityProtocol(x *xorm.Engine) error { diff --git a/models/migrations/v19.go b/models/migrations/v19.go index 44338eb8ba..7728f5add6 100644 --- a/models/migrations/v19.go +++ b/models/migrations/v19.go @@ -13,8 +13,8 @@ import ( "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) func generateAndMigrateGitHooks(x *xorm.Engine) (err error) { @@ -42,7 +42,7 @@ func generateAndMigrateGitHooks(x *xorm.Engine) (err error) { } ) - return x.Where("id > 0").BufferSize(setting.IterateBufferSize).Iterate(new(Repository), + return x.Where("id > 0").BufferSize(setting.Database.IterateBufferSize).Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) user := new(User) diff --git a/models/migrations/v21.go b/models/migrations/v21.go index 890f4de227..65cae2ac03 100644 --- a/models/migrations/v21.go +++ b/models/migrations/v21.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) const ( diff --git a/models/migrations/v22.go b/models/migrations/v22.go index 7774f6fd9a..faac74343b 100644 --- a/models/migrations/v22.go +++ b/models/migrations/v22.go @@ -13,8 +13,8 @@ import ( "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) func generateAndMigrateWikiGitHooks(x *xorm.Engine) (err error) { @@ -42,7 +42,7 @@ func generateAndMigrateWikiGitHooks(x *xorm.Engine) (err error) { } ) - return x.Where("id > 0").BufferSize(setting.IterateBufferSize).Iterate(new(Repository), + return x.Where("id > 0").BufferSize(setting.Database.IterateBufferSize).Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) user := new(User) diff --git a/models/migrations/v26.go b/models/migrations/v26.go index 636d1f76ca..04277191f5 100644 --- a/models/migrations/v26.go +++ b/models/migrations/v26.go @@ -16,8 +16,8 @@ import ( "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) func generateAndMigrateGitHookChains(x *xorm.Engine) (err error) { @@ -36,7 +36,7 @@ func generateAndMigrateGitHookChains(x *xorm.Engine) (err error) { hookTpl = fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType) ) - return x.Where("id > 0").BufferSize(setting.IterateBufferSize).Iterate(new(Repository), + return x.Where("id > 0").BufferSize(setting.Database.IterateBufferSize).Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) user := new(User) diff --git a/models/migrations/v27.go b/models/migrations/v27.go index e87c7ab68f..12e5fbcdbf 100644 --- a/models/migrations/v27.go +++ b/models/migrations/v27.go @@ -41,8 +41,6 @@ func convertIntervalToDuration(x *xorm.Engine) (err error) { _, err = sess.Exec("ALTER TABLE mirror MODIFY `interval` BIGINT") case "postgres": _, err = sess.Exec("ALTER TABLE mirror ALTER COLUMN \"interval\" SET DATA TYPE bigint") - case "tidb": - _, err = sess.Exec("ALTER TABLE mirror MODIFY `interval` BIGINT") case "mssql": _, err = sess.Exec("ALTER TABLE mirror ALTER COLUMN \"interval\" BIGINT") case "sqlite3": diff --git a/models/migrations/v33.go b/models/migrations/v33.go index ae7612e68c..566951db96 100644 --- a/models/migrations/v33.go +++ b/models/migrations/v33.go @@ -15,9 +15,9 @@ import ( func removeActionColumns(x *xorm.Engine) error { switch { - case setting.UseSQLite3: + case setting.Database.UseSQLite3: log.Warn("Unable to drop columns in SQLite") - case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB: + case setting.Database.UseMySQL, setting.Database.UsePostgreSQL, setting.Database.UseMSSQL: if _, err := x.Exec("ALTER TABLE action DROP COLUMN act_user_name"); err != nil { return fmt.Errorf("DROP COLUMN act_user_name: %v", err) } else if _, err = x.Exec("ALTER TABLE action DROP COLUMN repo_user_name"); err != nil { diff --git a/models/migrations/v45.go b/models/migrations/v45.go index 92cb817819..99baff2c8b 100644 --- a/models/migrations/v45.go +++ b/models/migrations/v45.go @@ -13,9 +13,9 @@ import ( func removeIndexColumnFromRepoUnitTable(x *xorm.Engine) (err error) { switch { - case setting.UseSQLite3: + case setting.Database.UseSQLite3: log.Warn("Unable to drop columns in SQLite") - case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB: + case setting.Database.UseMySQL, setting.Database.UsePostgreSQL, setting.Database.UseMSSQL: if _, err := x.Exec("ALTER TABLE repo_unit DROP COLUMN `index`"); err != nil { // Ignoring this error in case we run this migration second time (after migration reordering) log.Warn("DROP COLUMN index: %v", err) diff --git a/models/migrations/v50.go b/models/migrations/v50.go index a15914f0ee..23b1bb526e 100644 --- a/models/migrations/v50.go +++ b/models/migrations/v50.go @@ -40,9 +40,9 @@ func migrateProtectedBranchStruct(x *xorm.Engine) error { } switch { - case setting.UseSQLite3: + case setting.Database.UseSQLite3: log.Warn("Unable to drop columns in SQLite") - case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB: + case setting.Database.UseMySQL, setting.Database.UsePostgreSQL, setting.Database.UseMSSQL: if _, err := x.Exec("ALTER TABLE protected_branch DROP COLUMN can_push"); err != nil { // Ignoring this error in case we run this migration second time (after migration reordering) log.Warn("DROP COLUMN can_push (skipping): %v", err) diff --git a/models/migrations/v54.go b/models/migrations/v54.go index 96c26739c6..5194624f69 100644 --- a/models/migrations/v54.go +++ b/models/migrations/v54.go @@ -7,7 +7,7 @@ package migrations import ( "fmt" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -19,7 +19,7 @@ func addPullRequestOptions(x *xorm.Engine) error { RepoID int64 `xorm:"INDEX(s)"` Type int `xorm:"INDEX(s)"` Config map[string]interface{} `xorm:"JSON"` - CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` } sess := x.NewSession() diff --git a/models/migrations/v55.go b/models/migrations/v55.go index 32f4e8ac04..c20c51616e 100644 --- a/models/migrations/v55.go +++ b/models/migrations/v55.go @@ -8,6 +8,7 @@ import ( "fmt" "code.gitea.io/gitea/models" + "github.com/go-xorm/xorm" ) diff --git a/models/migrations/v57.go b/models/migrations/v57.go index 3a79a5cca7..fe4bf6b0ee 100644 --- a/models/migrations/v57.go +++ b/models/migrations/v57.go @@ -7,7 +7,7 @@ package migrations import ( "fmt" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -15,7 +15,7 @@ import ( func addIssueClosedTime(x *xorm.Engine) error { // Issue see models/issue.go type Issue struct { - ClosedUnix util.TimeStamp `xorm:"INDEX"` + ClosedUnix timeutil.TimeStamp `xorm:"INDEX"` } if err := x.Sync2(new(Issue)); err != nil { diff --git a/models/migrations/v64.go b/models/migrations/v64.go index e4a360f578..00637ca046 100644 --- a/models/migrations/v64.go +++ b/models/migrations/v64.go @@ -5,7 +5,7 @@ package migrations import ( - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -27,10 +27,10 @@ func addMultipleAssignees(x *xorm.Engine) error { IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. NumComments int - DeadlineUnix util.TimeStamp `xorm:"INDEX"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` - ClosedUnix util.TimeStamp `xorm:"INDEX"` + DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ClosedUnix timeutil.TimeStamp `xorm:"INDEX"` } // Updated the comment table @@ -53,8 +53,8 @@ func addMultipleAssignees(x *xorm.Engine) error { Content string `xorm:"TEXT"` RenderedContent string `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` // Reference issue in commit message CommitSHA string `xorm:"VARCHAR(40)"` diff --git a/models/migrations/v65.go b/models/migrations/v65.go index f73e632877..cc199d34e2 100644 --- a/models/migrations/v65.go +++ b/models/migrations/v65.go @@ -1,7 +1,8 @@ package migrations import ( - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + "github.com/go-xorm/xorm" ) @@ -12,8 +13,8 @@ func addU2FReg(x *xorm.Engine) error { UserID int64 `xorm:"INDEX"` Raw []byte Counter uint32 - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } return x.Sync2(&U2FRegistration{}) } diff --git a/models/migrations/v67.go b/models/migrations/v67.go index 74d3f379cd..6cf3dd4d19 100644 --- a/models/migrations/v67.go +++ b/models/migrations/v67.go @@ -80,7 +80,7 @@ func removeStaleWatches(x *xorm.Engine) error { } repoCache := make(map[int64]*Repository) - err := sess.BufferSize(setting.IterateBufferSize).Iterate(new(Watch), + err := sess.BufferSize(setting.Database.IterateBufferSize).Iterate(new(Watch), func(idx int, bean interface{}) error { watch := bean.(*Watch) @@ -117,7 +117,7 @@ func removeStaleWatches(x *xorm.Engine) error { } repoCache = make(map[int64]*Repository) - err = sess.BufferSize(setting.IterateBufferSize). + err = sess.BufferSize(setting.Database.IterateBufferSize). Distinct("issue_watch.user_id", "issue.repo_id"). Join("INNER", "issue", "issue_watch.issue_id = issue.id"). Where("issue_watch.is_watching = ?", true). diff --git a/models/migrations/v71.go b/models/migrations/v71.go index 8594460370..004f0a3f51 100644 --- a/models/migrations/v71.go +++ b/models/migrations/v71.go @@ -8,11 +8,11 @@ import ( "crypto/sha256" "fmt" + "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/timeutil" + "github.com/go-xorm/xorm" "golang.org/x/crypto/pbkdf2" - - "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/util" ) func addScratchHash(x *xorm.Engine) error { @@ -24,9 +24,9 @@ func addScratchHash(x *xorm.Engine) error { ScratchToken string ScratchSalt string ScratchHash string - LastUsedPasscode string `xorm:"VARCHAR(10)"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + LastUsedPasscode string `xorm:"VARCHAR(10)"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } if err := x.Sync2(new(TwoFactor)); err != nil { diff --git a/models/migrations/v72.go b/models/migrations/v72.go index 4924b41ceb..c99b46afd2 100644 --- a/models/migrations/v72.go +++ b/models/migrations/v72.go @@ -7,7 +7,7 @@ package migrations import ( "fmt" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -20,8 +20,8 @@ func addReview(x *xorm.Engine) error { ReviewerID int64 `xorm:"index"` IssueID int64 `xorm:"index"` Content string - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } if err := x.Sync2(new(Review)); err != nil { diff --git a/models/migrations/v76.go b/models/migrations/v76.go index efab7e0cf0..e1fd6f100b 100644 --- a/models/migrations/v76.go +++ b/models/migrations/v76.go @@ -7,7 +7,7 @@ package migrations import ( "fmt" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -19,7 +19,7 @@ func addPullRequestRebaseWithMerge(x *xorm.Engine) error { RepoID int64 `xorm:"INDEX(s)"` Type int `xorm:"INDEX(s)"` Config map[string]interface{} `xorm:"JSON"` - CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` } sess := x.NewSession() diff --git a/models/migrations/v78.go b/models/migrations/v78.go index 511a4f57fa..8082996b6f 100644 --- a/models/migrations/v78.go +++ b/models/migrations/v78.go @@ -5,13 +5,7 @@ package migrations import ( - "fmt" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/log" - "github.com/go-xorm/xorm" - "xorm.io/core" ) func renameRepoIsBareToIsEmpty(x *xorm.Engine) error { @@ -21,73 +15,28 @@ func renameRepoIsBareToIsEmpty(x *xorm.Engine) error { IsEmpty bool `xorm:"INDEX"` } - // First remove the index sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err } - var err error - if models.DbCfg.Type == core.POSTGRES || models.DbCfg.Type == core.SQLITE { - _, err = sess.Exec("DROP INDEX IF EXISTS IDX_repository_is_bare") - } else if models.DbCfg.Type == core.MSSQL { - _, err = sess.Exec(`DECLARE @ConstraintName VARCHAR(256) - DECLARE @SQL NVARCHAR(256) - SELECT @ConstraintName = obj.name FROM sys.columns col LEFT OUTER JOIN sys.objects obj ON obj.object_id = col.default_object_id AND obj.type = 'D' WHERE col.object_id = OBJECT_ID('repository') AND obj.name IS NOT NULL AND col.name = 'is_bare' - SET @SQL = N'ALTER TABLE [repository] DROP CONSTRAINT [' + @ConstraintName + N']' - EXEC sp_executesql @SQL`) - if err != nil { - return err - } - } else if models.DbCfg.Type == core.MYSQL { - indexes, err := sess.QueryString(`SHOW INDEX FROM repository WHERE KEY_NAME = 'IDX_repository_is_bare'`) - if err != nil { - return err - } - - if len(indexes) >= 1 { - _, err = sess.Exec("DROP INDEX IDX_repository_is_bare ON repository") - if err != nil { - return fmt.Errorf("Drop index failed: %v", err) - } - } - } else { - _, err = sess.Exec("DROP INDEX IDX_repository_is_bare ON repository") - } - - if err != nil { - return fmt.Errorf("Drop index failed: %v", err) - } - - if err = sess.Commit(); err != nil { - return err - } - - if err := sess.Begin(); err != nil { - return err - } - if err := sess.Sync2(new(Repository)); err != nil { return err } if _, err := sess.Exec("UPDATE repository SET is_empty = is_bare;"); err != nil { return err } - - if models.DbCfg.Type != core.SQLITE { - _, err = sess.Exec("ALTER TABLE repository DROP COLUMN is_bare") - if err != nil { - return fmt.Errorf("Drop column failed: %v", err) - } - } - - if err = sess.Commit(); err != nil { + if err := sess.Commit(); err != nil { return err } - if models.DbCfg.Type == core.SQLITE { - log.Warn("TABLE repository's COLUMN is_bare should be DROP but sqlite is not supported, you could manually do that.") + if err := sess.Begin(); err != nil { + return err } - return nil + if err := dropTableColumns(sess, "repository", "is_bare"); err != nil { + return err + } + + return sess.Commit() } diff --git a/models/migrations/v80.go b/models/migrations/v80.go index 8cd2ac80a8..d9040da601 100644 --- a/models/migrations/v80.go +++ b/models/migrations/v80.go @@ -14,5 +14,4 @@ func addIsLockedToIssues(x *xorm.Engine) error { } return x.Sync2(new(Issue)) - } diff --git a/models/migrations/v81.go b/models/migrations/v81.go index 56bb8477e6..48e96508d9 100644 --- a/models/migrations/v81.go +++ b/models/migrations/v81.go @@ -14,8 +14,6 @@ func changeU2FCounterType(x *xorm.Engine) error { var err error switch x.Dialect().DriverName() { - case "tidb": - fallthrough case "mysql": _, err = x.Exec("ALTER TABLE `u2f_registration` MODIFY `counter` BIGINT") case "postgres": diff --git a/models/migrations/v83.go b/models/migrations/v83.go index 947645153c..cdc59292ab 100644 --- a/models/migrations/v83.go +++ b/models/migrations/v83.go @@ -5,7 +5,7 @@ package migrations import ( - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -19,9 +19,9 @@ func addUploaderIDForAttachment(x *xorm.Engine) error { UploaderID int64 `xorm:"INDEX DEFAULT 0"` CommentID int64 Name string - DownloadCount int64 `xorm:"DEFAULT 0"` - Size int64 `xorm:"DEFAULT 0"` - CreatedUnix util.TimeStamp `xorm:"created"` + DownloadCount int64 `xorm:"DEFAULT 0"` + Size int64 `xorm:"DEFAULT 0"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } return x.Sync2(new(Attachment)) diff --git a/models/migrations/v85.go b/models/migrations/v85.go index b8d0ee5443..6066d5ebe9 100644 --- a/models/migrations/v85.go +++ b/models/migrations/v85.go @@ -7,13 +7,11 @@ package migrations import ( "fmt" - "github.com/go-xorm/xorm" - "xorm.io/core" - - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/go-xorm/xorm" ) func hashAppToken(x *xorm.Engine) error { @@ -28,50 +26,15 @@ func hashAppToken(x *xorm.Engine) error { TokenSalt string TokenLastEight string `xorm:"token_last_eight"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` - HasRecentActivity bool `xorm:"-"` - HasUsed bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + HasRecentActivity bool `xorm:"-"` + HasUsed bool `xorm:"-"` } // First remove the index sess := x.NewSession() defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - - var err error - if models.DbCfg.Type == core.POSTGRES || models.DbCfg.Type == core.SQLITE { - _, err = sess.Exec("DROP INDEX IF EXISTS UQE_access_token_sha1") - } else if models.DbCfg.Type == core.MSSQL { - _, err = sess.Exec(`DECLARE @ConstraintName VARCHAR(256) - DECLARE @SQL NVARCHAR(256) - SELECT @ConstraintName = obj.name FROM sys.columns col LEFT OUTER JOIN sys.objects obj ON obj.object_id = col.default_object_id AND obj.type = 'D' WHERE col.object_id = OBJECT_ID('access_token') AND obj.name IS NOT NULL AND col.name = 'sha1' - SET @SQL = N'ALTER TABLE [access_token] DROP CONSTRAINT [' + @ConstraintName + N']' - EXEC sp_executesql @SQL`) - } else if models.DbCfg.Type == core.MYSQL { - indexes, err := sess.QueryString(`SHOW INDEX FROM access_token WHERE KEY_NAME = 'UQE_access_token_sha1'`) - if err != nil { - return err - } - - if len(indexes) >= 1 { - _, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token") - if err != nil { - return err - } - } - } else { - _, err = sess.Exec("DROP INDEX UQE_access_token_sha1 ON access_token") - } - if err != nil { - return fmt.Errorf("Drop index failed: %v", err) - } - - if err = sess.Commit(); err != nil { - return err - } if err := sess.Begin(); err != nil { return err @@ -81,7 +44,7 @@ func hashAppToken(x *xorm.Engine) error { return fmt.Errorf("Sync2: %v", err) } - if err = sess.Commit(); err != nil { + if err := sess.Commit(); err != nil { return err } diff --git a/models/migrations/v90.go b/models/migrations/v90.go index 70a32f6566..09aceae2f9 100644 --- a/models/migrations/v90.go +++ b/models/migrations/v90.go @@ -4,22 +4,15 @@ package migrations -import ( - "github.com/go-xorm/xorm" -) +import "github.com/go-xorm/xorm" -func addTeamIncludesAllRepositories(x *xorm.Engine) error { - - type Team struct { - ID int64 `xorm:"pk autoincr"` - IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` +func changeSomeColumnsLengthOfRepo(x *xorm.Engine) error { + type Repository struct { + ID int64 `xorm:"pk autoincr"` + Description string `xorm:"TEXT"` + Website string `xorm:"VARCHAR(2048)"` + OriginalURL string `xorm:"VARCHAR(2048)"` } - if err := x.Sync2(new(Team)); err != nil { - return err - } - - _, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?", - true, "Owners") - return err + return x.Sync2(new(Repository)) } diff --git a/models/migrations/v91.go b/models/migrations/v91.go new file mode 100644 index 0000000000..fea71b5d3b --- /dev/null +++ b/models/migrations/v91.go @@ -0,0 +1,26 @@ +// Copyright 2019 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 "github.com/go-xorm/xorm" + +func addIndexOnRepositoryAndComment(x *xorm.Engine) error { + type Repository struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"index"` + } + + if err := x.Sync2(new(Repository)); err != nil { + return err + } + + type Comment struct { + ID int64 `xorm:"pk autoincr"` + Type int `xorm:"index"` + ReviewID int64 `xorm:"index"` + } + + return x.Sync2(new(Comment)) +} diff --git a/models/migrations/v92.go b/models/migrations/v92.go new file mode 100644 index 0000000000..090332f151 --- /dev/null +++ b/models/migrations/v92.go @@ -0,0 +1,16 @@ +// Copyright 2019 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 ( + "github.com/go-xorm/xorm" + "xorm.io/builder" +) + +func removeLingeringIndexStatus(x *xorm.Engine) error { + + _, err := x.Exec(builder.Delete(builder.NotIn("`repo_id`", builder.Select("`id`").From("`repository`"))).From("`repo_indexer_status`")) + return err +} diff --git a/models/migrations/v93.go b/models/migrations/v93.go new file mode 100644 index 0000000000..0b0441cd5d --- /dev/null +++ b/models/migrations/v93.go @@ -0,0 +1,16 @@ +// Copyright 2019 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 "github.com/go-xorm/xorm" + +func addEmailNotificationEnabledToUser(x *xorm.Engine) error { + // User see models/user.go + type User struct { + EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"` + } + + return x.Sync2(new(User)) +} diff --git a/models/migrations/v94.go b/models/migrations/v94.go new file mode 100644 index 0000000000..5fe8c3fa12 --- /dev/null +++ b/models/migrations/v94.go @@ -0,0 +1,24 @@ +// Copyright 2019 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 "github.com/go-xorm/xorm" + +func addStatusCheckColumnsForProtectedBranches(x *xorm.Engine) error { + type ProtectedBranch struct { + EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` + StatusCheckContexts []string `xorm:"JSON TEXT"` + } + + if err := x.Sync2(new(ProtectedBranch)); err != nil { + return err + } + + _, err := x.Cols("enable_status_check", "status_check_contexts").Update(&ProtectedBranch{ + EnableStatusCheck: false, + StatusCheckContexts: []string{}, + }) + return err +} diff --git a/models/migrations/v95.go b/models/migrations/v95.go new file mode 100644 index 0000000000..f6e4e41c48 --- /dev/null +++ b/models/migrations/v95.go @@ -0,0 +1,20 @@ +// Copyright 2019 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 "github.com/go-xorm/xorm" + +func addCrossReferenceColumns(x *xorm.Engine) error { + // Comment see models/comment.go + type Comment struct { + RefRepoID int64 `xorm:"index"` + RefIssueID int64 `xorm:"index"` + RefCommentID int64 `xorm:"index"` + RefAction int64 `xorm:"SMALLINT"` + RefIsPull bool + } + + return x.Sync2(new(Comment)) +} diff --git a/models/migrations/v96.go b/models/migrations/v96.go new file mode 100644 index 0000000000..5c2135ffc6 --- /dev/null +++ b/models/migrations/v96.go @@ -0,0 +1,48 @@ +// Copyright 2019 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 ( + "os" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/setting" + + "github.com/go-xorm/xorm" +) + +func deleteOrphanedAttachments(x *xorm.Engine) error { + + type Attachment struct { + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + IssueID int64 `xorm:"INDEX"` + ReleaseID int64 `xorm:"INDEX"` + CommentID int64 + } + + sess := x.NewSession() + defer sess.Close() + + err := sess.BufferSize(setting.Database.IterateBufferSize). + Where("`comment_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").Cols("uuid"). + Iterate(new(Attachment), + func(idx int, bean interface{}) error { + attachment := bean.(*Attachment) + + if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil { + return err + } + + _, err := sess.ID(attachment.ID).NoAutoCondition().Delete(attachment) + return err + }) + + if err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v97.go b/models/migrations/v97.go new file mode 100644 index 0000000000..fa542f2ccd --- /dev/null +++ b/models/migrations/v97.go @@ -0,0 +1,15 @@ +// Copyright 2019 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 "github.com/go-xorm/xorm" + +func addRepoAdminChangeTeamAccessColumnForUser(x *xorm.Engine) error { + type User struct { + RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync2(new(User)) +} diff --git a/models/migrations/v98.go b/models/migrations/v98.go new file mode 100644 index 0000000000..3b9fdbb1c5 --- /dev/null +++ b/models/migrations/v98.go @@ -0,0 +1,17 @@ +// Copyright 2019 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 "github.com/go-xorm/xorm" + +func addOriginalAuthorOnMigratedReleases(x *xorm.Engine) error { + type Release struct { + ID int64 + OriginalAuthor string + OriginalAuthorID int64 `xorm:"index"` + } + + return x.Sync2(new(Release)) +} diff --git a/models/migrations/v99.go b/models/migrations/v99.go new file mode 100644 index 0000000000..70a32f6566 --- /dev/null +++ b/models/migrations/v99.go @@ -0,0 +1,25 @@ +// Copyright 2019 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 ( + "github.com/go-xorm/xorm" +) + +func addTeamIncludesAllRepositories(x *xorm.Engine) error { + + type Team struct { + ID int64 `xorm:"pk autoincr"` + IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` + } + + if err := x.Sync2(new(Team)); err != nil { + return err + } + + _, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?", + true, "Owners") + return err +} diff --git a/models/models.go b/models/models.go index f746f680a5..e802a35a77 100644 --- a/models/models.go +++ b/models/models.go @@ -9,12 +9,6 @@ import ( "database/sql" "errors" "fmt" - "net/url" - "os" - "path" - "path/filepath" - "strings" - "time" "code.gitea.io/gitea/modules/setting" @@ -52,24 +46,11 @@ type Engine interface { } var ( - x *xorm.Engine - supportedDatabases = []string{"mysql", "postgres", "mssql"} - tables []interface{} + x *xorm.Engine + tables []interface{} // HasEngine specifies if we have a xorm.Engine HasEngine bool - - // DbCfg holds the database settings - DbCfg struct { - Type, Host, Name, User, Passwd, Path, SSLMode, Charset string - Timeout int - } - - // EnableSQLite3 use SQLite3 - EnableSQLite3 bool - - // EnableTiDB enable TiDB - EnableTiDB bool ) func init() { @@ -139,120 +120,13 @@ func init() { } } -// LoadConfigs loads the database settings -func LoadConfigs() { - sec := setting.Cfg.Section("database") - DbCfg.Type = sec.Key("DB_TYPE").String() - switch DbCfg.Type { - case "sqlite3": - setting.UseSQLite3 = true - case "mysql": - setting.UseMySQL = true - case "postgres": - setting.UsePostgreSQL = true - case "tidb": - setting.UseTiDB = true - case "mssql": - setting.UseMSSQL = true - } - DbCfg.Host = sec.Key("HOST").String() - DbCfg.Name = sec.Key("NAME").String() - DbCfg.User = sec.Key("USER").String() - if len(DbCfg.Passwd) == 0 { - DbCfg.Passwd = sec.Key("PASSWD").String() - } - DbCfg.SSLMode = sec.Key("SSL_MODE").MustString("disable") - DbCfg.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"}) - DbCfg.Path = sec.Key("PATH").MustString(filepath.Join(setting.AppDataPath, "gitea.db")) - DbCfg.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500) -} - -// parsePostgreSQLHostPort parses given input in various forms defined in -// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING -// and returns proper host and port number. -func parsePostgreSQLHostPort(info string) (string, string) { - host, port := "127.0.0.1", "5432" - if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") { - idx := strings.LastIndex(info, ":") - host = info[:idx] - port = info[idx+1:] - } else if len(info) > 0 { - host = info - } - 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 -} - -// ParseMSSQLHostPort splits the host into host and port -func ParseMSSQLHostPort(info string) (string, string) { - host, port := "127.0.0.1", "1433" - if strings.Contains(info, ":") { - host = strings.Split(info, ":")[0] - port = strings.Split(info, ":")[1] - } else if strings.Contains(info, ",") { - host = strings.Split(info, ",")[0] - port = strings.TrimSpace(strings.Split(info, ",")[1]) - } else if len(info) > 0 { - host = info - } - return host, port -} - func getEngine() (*xorm.Engine, error) { - connStr := "" - var Param = "?" - if strings.Contains(DbCfg.Name, Param) { - Param = "&" - } - switch DbCfg.Type { - case "mysql": - connType := "tcp" - if DbCfg.Host[0] == '/' { // looks like a unix socket - connType = "unix" - } - tls := DbCfg.SSLMode - if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL - tls = "false" - } - connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s", - DbCfg.User, DbCfg.Passwd, connType, DbCfg.Host, DbCfg.Name, Param, DbCfg.Charset, tls) - case "postgres": - 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) - case "sqlite3": - if !EnableSQLite3 { - return nil, errors.New("this binary version does not build support for SQLite3") - } - if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil { - return nil, fmt.Errorf("Failed to create directories: %v", err) - } - connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d", DbCfg.Path, DbCfg.Timeout) - case "tidb": - if !EnableTiDB { - return nil, errors.New("this binary version does not build support for TiDB") - } - if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil { - return nil, fmt.Errorf("Failed to create directories: %v", err) - } - connStr = "goleveldb://" + DbCfg.Path - default: - return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type) + connStr, err := setting.DBConnStr() + if err != nil { + return nil, err } - return xorm.NewEngine(DbCfg.Type, connStr) + return xorm.NewEngine(setting.Database.Type, connStr) } // NewTestEngine sets a new test xorm.Engine @@ -262,6 +136,7 @@ func NewTestEngine(x *xorm.Engine) (err error) { return fmt.Errorf("Connect to database: %v", err) } + x.ShowExecTime(true) x.SetMapper(core.GonicMapper{}) x.SetLogger(NewXORMLogger(!setting.ProdMode)) x.ShowSQL(!setting.ProdMode) @@ -275,14 +150,15 @@ func SetEngine() (err error) { return fmt.Errorf("Failed to connect to database: %v", err) } + x.ShowExecTime(true) x.SetMapper(core.GonicMapper{}) // WARNING: for serv command, MUST remove the output to os.stdout, // so use log file to instead print to stdout. - x.SetLogger(NewXORMLogger(setting.LogSQL)) - x.ShowSQL(setting.LogSQL) - if DbCfg.Type == "mysql" { - x.SetMaxIdleConns(0) - x.SetConnMaxLifetime(3 * time.Second) + x.SetLogger(NewXORMLogger(setting.Database.LogSQL)) + x.ShowSQL(setting.Database.LogSQL) + if setting.Database.UseMySQL { + x.SetMaxIdleConns(setting.Database.MaxIdleConns) + x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) } return nil @@ -374,3 +250,8 @@ func MaxBatchInsertSize(bean interface{}) int { t := x.TableInfo(bean) return 999 / len(t.ColumnsSeq()) } + +// Count returns records number according struct's fields as database query conditions +func Count(bean interface{}) (int64, error) { + return x.Count(bean) +} diff --git a/models/models_test.go b/models/models_test.go index 6df3b4e048..37e9a352f8 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -1,5 +1,4 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. -// Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2019 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. @@ -11,99 +10,19 @@ import ( "path/filepath" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) -func Test_parsePostgreSQLHostPort(t *testing.T) { - tests := []struct { - HostPort string - Host string - Port string - }{ - { - HostPort: "127.0.0.1:1234", - Host: "127.0.0.1", - Port: "1234", - }, - { - HostPort: "127.0.0.1", - Host: "127.0.0.1", - Port: "5432", - }, - { - HostPort: "[::1]:1234", - Host: "[::1]", - Port: "1234", - }, - { - HostPort: "[::1]", - Host: "[::1]", - Port: "5432", - }, - { - HostPort: "/tmp/pg.sock:1234", - Host: "/tmp/pg.sock", - Port: "1234", - }, - { - HostPort: "/tmp/pg.sock", - Host: "/tmp/pg.sock", - Port: "5432", - }, - } - for _, test := range tests { - host, port := parsePostgreSQLHostPort(test.HostPort) - assert.Equal(t, test.Host, host) - 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) - } -} - func TestDumpDatabase(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) dir, err := ioutil.TempDir(os.TempDir(), "dump") assert.NoError(t, err) - for _, dbType := range supportedDatabases { + for _, dbName := range setting.SupportedDatabases { + dbType := setting.GetDBTypeByName(dbName) assert.NoError(t, DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType)) } } diff --git a/models/notification.go b/models/notification.go index f83fe63e5a..5b6ce597d1 100644 --- a/models/notification.go +++ b/models/notification.go @@ -7,7 +7,7 @@ package models import ( "fmt" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) type ( @@ -52,8 +52,8 @@ type Notification struct { Issue *Issue `xorm:"-"` Repository *Repository `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"created INDEX NOT NULL"` - UpdatedUnix util.TimeStamp `xorm:"updated INDEX NOT NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"` } // CreateOrUpdateIssueNotifications creates an issue notification diff --git a/models/oauth2.go b/models/oauth2.go index bf4446229a..ee3ea4b924 100644 --- a/models/oauth2.go +++ b/models/oauth2.go @@ -40,10 +40,17 @@ var OAuth2Providers = map[string]OAuth2Provider{ ProfileURL: oauth2.GetDefaultProfileURL("gitlab"), }, }, - "gplus": {Name: "gplus", DisplayName: "Google+", Image: "/img/auth/google_plus.png"}, + "gplus": {Name: "gplus", DisplayName: "Google", Image: "/img/auth/google.png"}, "openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/img/auth/openid_connect.png"}, "twitter": {Name: "twitter", DisplayName: "Twitter", Image: "/img/auth/twitter.png"}, "discord": {Name: "discord", DisplayName: "Discord", Image: "/img/auth/discord.png"}, + "gitea": {Name: "gitea", DisplayName: "Gitea", Image: "/img/auth/gitea.png", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: oauth2.GetDefaultTokenURL("gitea"), + AuthURL: oauth2.GetDefaultAuthURL("gitea"), + ProfileURL: oauth2.GetDefaultProfileURL("gitea"), + }, + }, } // OAuth2DefaultCustomURLMappings contains the map of default URL's for OAuth2 providers that are allowed to have custom urls @@ -52,6 +59,7 @@ var OAuth2Providers = map[string]OAuth2Provider{ var OAuth2DefaultCustomURLMappings = map[string]*oauth2.CustomURLMapping{ "github": OAuth2Providers["github"].CustomURLMapping, "gitlab": OAuth2Providers["gitlab"].CustomURLMapping, + "gitea": OAuth2Providers["gitea"].CustomURLMapping, } // GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources diff --git a/models/oauth2_application.go b/models/oauth2_application.go index 63d2e7ce5e..46355a0b3f 100644 --- a/models/oauth2_application.go +++ b/models/oauth2_application.go @@ -11,15 +11,14 @@ import ( "net/url" "time" - "github.com/go-xorm/xorm" - uuid "github.com/satori/go.uuid" - "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" "github.com/dgrijalva/jwt-go" + "github.com/go-xorm/xorm" + uuid "github.com/satori/go.uuid" + "github.com/unknwon/com" "golang.org/x/crypto/bcrypt" ) @@ -36,8 +35,8 @@ type OAuth2Application struct { RedirectURIs []string `xorm:"redirect_uris JSON TEXT"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // TableName sets the table name to `oauth2_application` @@ -264,7 +263,7 @@ type OAuth2AuthorizationCode struct { CodeChallenge string CodeChallengeMethod string RedirectURI string - ValidUntil util.TimeStamp `xorm:"index"` + ValidUntil timeutil.TimeStamp `xorm:"index"` } // TableName sets the table name to `oauth2_authorization_code` @@ -348,8 +347,8 @@ type OAuth2Grant struct { Application *OAuth2Application `xorm:"-"` ApplicationID int64 `xorm:"INDEX unique(user_application)"` Counter int64 `xorm:"NOT NULL DEFAULT 1"` - CreatedUnix util.TimeStamp `xorm:"created"` - UpdatedUnix util.TimeStamp `xorm:"updated"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } // TableName sets the table name to `oauth2_grant` diff --git a/models/org.go b/models/org.go index 800a6a623e..07daeaeacc 100644 --- a/models/org.go +++ b/models/org.go @@ -6,24 +6,19 @@ package models import ( - "errors" "fmt" "os" "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" "xorm.io/builder" ) -var ( - // ErrTeamNotExist team does not exist - ErrTeamNotExist = errors.New("Team does not exist") -) - // IsOwnedBy returns true if given user is in the owner team. func (org *User) IsOwnedBy(uid int64) (bool, error) { return IsOrganizationOwner(org.ID, uid) @@ -75,9 +70,12 @@ func (org *User) GetMembers() error { } var ids = make([]int64, len(ous)) + var idsIsPublic = make(map[int64]bool, len(ous)) for i, ou := range ous { ids[i] = ou.UID + idsIsPublic[ou.UID] = ou.IsPublic } + org.MembersIsPublic = idsIsPublic org.Members, err = GetUsersByIDs(ids) return err } @@ -302,15 +300,13 @@ type OrgUser struct { } func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) { - ownerTeam := &Team{ - OrgID: orgID, - Name: ownerTeamName, - } - if has, err := e.Get(ownerTeam); err != nil { + ownerTeam, err := getOwnerTeam(e, orgID) + if err != nil { + if IsErrTeamNotExist(err) { + log.Error("Organization does not have owner team: %d", orgID) + return false, nil + } return false, err - } else if !has { - log.Error("Organization does not have owner team: %d", orgID) - return false, nil } return isTeamMember(e, orgID, ownerTeam.ID, uid) } @@ -483,8 +479,9 @@ func AddOrgUser(orgID, uid int64) error { } ou := &OrgUser{ - UID: uid, - OrgID: orgID, + UID: uid, + OrgID: orgID, + IsPublic: setting.Service.DefaultOrgMemberVisible, } if _, err := sess.Insert(ou); err != nil { diff --git a/models/org_team.go b/models/org_team.go index 60f7a659e0..7430c65e4d 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/setting" "github.com/go-xorm/xorm" + "xorm.io/builder" ) const ownerTeamName = "Owners" @@ -35,6 +36,67 @@ type Team struct { IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"` } +// SearchTeamOptions holds the search options +type SearchTeamOptions struct { + UserID int64 + Keyword string + OrgID int64 + IncludeDesc bool + PageSize int + Page int +} + +// SearchTeam search for teams. Caller is responsible to check permissions. +func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { + if opts.Page <= 0 { + opts.Page = 1 + } + if opts.PageSize == 0 { + // Default limit + opts.PageSize = 10 + } + + var cond = builder.NewCond() + + if len(opts.Keyword) > 0 { + lowerKeyword := strings.ToLower(opts.Keyword) + var keywordCond builder.Cond = builder.Like{"lower_name", lowerKeyword} + if opts.IncludeDesc { + keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", lowerKeyword}) + } + cond = cond.And(keywordCond) + } + + cond = cond.And(builder.Eq{"org_id": opts.OrgID}) + + sess := x.NewSession() + defer sess.Close() + + count, err := sess. + Where(cond). + Count(new(Team)) + + if err != nil { + return nil, 0, err + } + + sess = sess.Where(cond) + if opts.PageSize == -1 { + opts.PageSize = int(count) + } else { + sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + } + + teams := make([]*Team, 0, opts.PageSize) + if err = sess. + OrderBy("lower_name"). + Find(&teams); err != nil { + return nil, 0, err + } + + return teams, count, nil +} + // ColorFormat provides a basic color format for a Team func (t *Team) ColorFormat(s fmt.State) { log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", @@ -387,7 +449,7 @@ func getTeam(e Engine, orgID int64, name string) (*Team, error) { if err != nil { return nil, err } else if !has { - return nil, ErrTeamNotExist + return nil, ErrTeamNotExist{orgID, 0, name} } return t, nil } @@ -397,13 +459,18 @@ func GetTeam(orgID int64, name string) (*Team, error) { return getTeam(x, orgID, name) } +// getOwnerTeam returns team by given team name and organization. +func getOwnerTeam(e Engine, orgID int64) (*Team, error) { + return getTeam(e, orgID, ownerTeamName) +} + func getTeamByID(e Engine, teamID int64) (*Team, error) { t := new(Team) has, err := e.ID(teamID).Get(t) if err != nil { return nil, err } else if !has { - return nil, ErrTeamNotExist + return nil, ErrTeamNotExist{0, teamID, ""} } return t, nil } @@ -798,11 +865,14 @@ func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) { } // UsersInTeamsCount counts the number of users which are in userIDs and teamIDs -func UsersInTeamsCount(userIDs []int64, teamIDs []int64) (count int64, err error) { - if count, err = x.In("uid", userIDs).In("team_id", teamIDs).Count(new(TeamUser)); err != nil { +func UsersInTeamsCount(userIDs []int64, teamIDs []int64) (int64, error) { + var ids []int64 + if err := x.In("uid", userIDs).In("team_id", teamIDs). + Table("team_user"). + Cols("uid").GroupBy("uid").Find(&ids); err != nil { return 0, err } - return + return int64(len(ids)), nil } // ___________ __________ diff --git a/models/org_team_test.go b/models/org_team_test.go index 9194b0f5fc..b7e2ef113d 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -373,9 +373,9 @@ func TestUsersInTeamsCount(t *testing.T) { assert.Equal(t, expected, count) } - test([]int64{2}, []int64{1, 2, 3, 4}, 2) - test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) - test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) + test([]int64{2}, []int64{1, 2, 3, 4}, 1) // only userid 2 + test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4 + test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5 } func TestIncludesAllRepositoriesTeams(t *testing.T) { diff --git a/models/org_test.go b/models/org_test.go index a2ebf1f60b..2f2c5a2d5e 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -7,6 +7,7 @@ package models import ( "testing" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -63,11 +64,11 @@ func TestUser_GetTeam(t *testing.T) { assert.Equal(t, "team1", team.LowerName) _, err = org.GetTeam("does not exist") - assert.Equal(t, ErrTeamNotExist, err) + assert.True(t, IsErrTeamNotExist(err)) nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) _, err = nonOrg.GetTeam("team") - assert.Equal(t, ErrTeamNotExist, err) + assert.True(t, IsErrTeamNotExist(err)) } func TestUser_GetOwnerTeam(t *testing.T) { @@ -79,7 +80,7 @@ func TestUser_GetOwnerTeam(t *testing.T) { nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) _, err = nonOrg.GetOwnerTeam() - assert.Equal(t, ErrTeamNotExist, err) + assert.True(t, IsErrTeamNotExist(err)) } func TestUser_GetTeams(t *testing.T) { @@ -429,20 +430,28 @@ func TestChangeOrgUserStatus(t *testing.T) { func TestAddOrgUser(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - testSuccess := func(orgID, userID int64) { + testSuccess := func(orgID, userID int64, isPublic bool) { org := AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) expectedNumMembers := org.NumMembers if !BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) { expectedNumMembers++ } assert.NoError(t, AddOrgUser(orgID, userID)) - AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID}) + ou := &OrgUser{OrgID: orgID, UID: userID} + AssertExistsAndLoadBean(t, ou) + assert.Equal(t, ou.IsPublic, isPublic) org = AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User) assert.EqualValues(t, expectedNumMembers, org.NumMembers) } - testSuccess(3, 5) - testSuccess(3, 5) - testSuccess(6, 2) + + setting.Service.DefaultOrgMemberVisible = false + testSuccess(3, 5, false) + testSuccess(3, 5, false) + testSuccess(6, 2, false) + + setting.Service.DefaultOrgMemberVisible = true + testSuccess(6, 3, true) + CheckConsistencyFor(t, &User{}, &Team{}) } diff --git a/models/pull.go b/models/pull.go index 7dd6050c67..ff1f7b773b 100644 --- a/models/pull.go +++ b/models/pull.go @@ -23,10 +23,10 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/sync" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ) var pullRequestQueue = sync.NewUniqueQueue(setting.Repository.PullRequestQueueLength) @@ -72,11 +72,11 @@ type PullRequest struct { ProtectedBranch *ProtectedBranch `xorm:"-"` MergeBase string `xorm:"VARCHAR(40)"` - HasMerged bool `xorm:"INDEX"` - MergedCommitID string `xorm:"VARCHAR(40)"` - MergerID int64 `xorm:"INDEX"` - Merger *User `xorm:"-"` - MergedUnix util.TimeStamp `xorm:"updated INDEX"` + HasMerged bool `xorm:"INDEX"` + MergedCommitID string `xorm:"VARCHAR(40)"` + MergerID int64 `xorm:"INDEX"` + Merger *User `xorm:"-"` + MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"` } // Note: don't try to get Issue because will end up recursive querying. @@ -99,6 +99,20 @@ func (pr *PullRequest) LoadAttributes() error { return pr.loadAttributes(x) } +// LoadBaseRepo loads pull request base repository from database +func (pr *PullRequest) LoadBaseRepo() error { + if pr.BaseRepo == nil { + var repo Repository + if has, err := x.ID(pr.BaseRepoID).Get(&repo); err != nil { + return err + } else if !has { + return ErrRepoNotExist{ID: pr.BaseRepoID} + } + pr.BaseRepo = &repo + } + return nil +} + // LoadIssue loads issue information from database func (pr *PullRequest) LoadIssue() (err error) { return pr.loadIssue(x) @@ -339,14 +353,17 @@ func (pr *PullRequest) GetLastCommitStatus() (status *CommitStatus, err error) { return nil, err } - repo := pr.HeadRepo lastCommitID, err := headGitRepo.GetBranchCommitID(pr.HeadBranch) if err != nil { return nil, err } - var statusList []*CommitStatus - statusList, err = GetLatestCommitStatus(repo, lastCommitID, 0) + err = pr.LoadBaseRepo() + if err != nil { + return nil, err + } + + statusList, err := GetLatestCommitStatus(pr.BaseRepo, lastCommitID, 0) if err != nil { return nil, err } @@ -443,7 +460,7 @@ func (pr *PullRequest) manuallyMerged() bool { } if commit != nil { pr.MergedCommitID = commit.ID.String() - pr.MergedUnix = util.TimeStamp(commit.Author.When.Unix()) + pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix()) pr.Status = PullRequestStatusManuallyMerged merger, _ := GetUserByEmail(commit.Author.Email) @@ -598,7 +615,7 @@ func (pr *PullRequest) testPatch(e Engine) (err error) { if err != nil { for i := range patchConflicts { if strings.Contains(stderr, patchConflicts[i]) { - log.Trace("PullRequest[%d].testPatch (apply): has conflict", pr.ID) + log.Trace("PullRequest[%d].testPatch (apply): has conflict: %s", pr.ID, stderr) const prefix = "error: patch failed:" pr.Status = PullRequestStatusConflict pr.ConflictedFiles = make([]string, 0, 5) @@ -640,6 +657,24 @@ func (pr *PullRequest) testPatch(e Engine) (err error) { // NewPullRequest creates new pull request with labels for repository. func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte, assigneeIDs []int64) (err error) { + // Retry several times in case INSERT fails due to duplicate key for (repo_id, index); see #7887 + i := 0 + for { + if err = newPullRequestAttempt(repo, pull, labelIDs, uuids, pr, patch, assigneeIDs); err == nil { + return nil + } + if !IsErrNewIssueInsert(err) { + return err + } + if i++; i == issueMaxDupIndexAttempts { + break + } + log.Error("NewPullRequest: error attempting to insert the new issue; will retry. Original error: %v", err) + } + return fmt.Errorf("NewPullRequest: too many errors attempting to insert the new issue. Last error was: %v", err) +} + +func newPullRequestAttempt(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte, assigneeIDs []int64) (err error) { sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { @@ -654,20 +689,23 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str IsPull: true, AssigneeIDs: assigneeIDs, }); err != nil { - if IsErrUserDoesNotHaveAccessToRepo(err) { + if IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { return err } return fmt.Errorf("newIssue: %v", err) } pr.Index = pull.Index - if err = repo.savePatch(sess, pr.Index, patch); err != nil { - return fmt.Errorf("SavePatch: %v", err) - } - pr.BaseRepo = repo - if err = pr.testPatch(sess); err != nil { - return fmt.Errorf("testPatch: %v", err) + pr.Status = PullRequestStatusChecking + if len(patch) > 0 { + if err = repo.savePatch(sess, pr.Index, patch); err != nil { + return fmt.Errorf("SavePatch: %v", err) + } + + if err = pr.testPatch(sess); err != nil { + return fmt.Errorf("testPatch: %v", err) + } } // No conflict appears after test means mergeable. if pr.Status == PullRequestStatusChecking { @@ -683,33 +721,6 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str return fmt.Errorf("Commit: %v", err) } - if err = NotifyWatchers(&Action{ - ActUserID: pull.Poster.ID, - ActUser: pull.Poster, - OpType: ActionCreatePullRequest, - Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title), - RepoID: repo.ID, - Repo: repo, - IsPrivate: repo.IsPrivate, - }); err != nil { - log.Error("NotifyWatchers: %v", err) - } - - pr.Issue = pull - pull.PullRequest = pr - mode, _ := AccessLevel(pull.Poster, repo) - if err = PrepareWebhooks(repo, HookEventPullRequest, &api.PullRequestPayload{ - Action: api.HookIssueOpened, - Index: pull.Index, - PullRequest: pr.APIFormat(), - Repository: repo.APIFormat(mode), - Sender: pull.Poster.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks: %v", err) - } else { - go HookQueue.Add(repo.ID) - } - return nil } diff --git a/models/release.go b/models/release.go index f8e8c17e74..243cc2fa3c 100644 --- a/models/release.go +++ b/models/release.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 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. @@ -10,11 +11,9 @@ import ( "strings" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" ) @@ -27,18 +26,20 @@ type Release struct { PublisherID int64 `xorm:"INDEX"` Publisher *User `xorm:"-"` TagName string `xorm:"INDEX UNIQUE(n)"` + OriginalAuthor string + OriginalAuthorID int64 `xorm:"index"` LowerTagName string Target string Title string Sha1 string `xorm:"VARCHAR(40)"` NumCommits int64 - NumCommitsBehind int64 `xorm:"-"` - Note string `xorm:"TEXT"` - IsDraft bool `xorm:"NOT NULL DEFAULT false"` - IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` - IsTag bool `xorm:"NOT NULL DEFAULT false"` - Attachments []*Attachment `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"INDEX"` + NumCommitsBehind int64 `xorm:"-"` + Note string `xorm:"TEXT"` + IsDraft bool `xorm:"NOT NULL DEFAULT false"` + IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` + IsTag bool `xorm:"NOT NULL DEFAULT false"` + Attachments []*Attachment `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX"` } func (r *Release) loadAttributes(e Engine) error { @@ -65,7 +66,7 @@ func (r *Release) LoadAttributes() error { // APIURL the api url for a release. release must have attributes loaded func (r *Release) APIURL() string { - return fmt.Sprintf("%sapi/v1/%s/releases/%d", + return fmt.Sprintf("%sapi/v1/repos/%s/releases/%d", setting.AppURL, r.Repo.FullName(), r.ID) } @@ -112,43 +113,20 @@ func IsReleaseExist(repoID int64, tagName string) (bool, error) { return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}) } -func createTag(gitRepo *git.Repository, rel *Release) error { - // Only actual create when publish. - if !rel.IsDraft { - if !gitRepo.IsTagExist(rel.TagName) { - commit, err := gitRepo.GetCommit(rel.Target) - if err != nil { - return fmt.Errorf("GetCommit: %v", err) - } - - // Trim '--' prefix to prevent command line argument vulnerability. - rel.TagName = strings.TrimPrefix(rel.TagName, "--") - if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil { - if strings.Contains(err.Error(), "is not a valid tag name") { - return ErrInvalidTagName{rel.TagName} - } - return err - } - rel.LowerTagName = strings.ToLower(rel.TagName) - } - commit, err := gitRepo.GetTagCommit(rel.TagName) - if err != nil { - return fmt.Errorf("GetTagCommit: %v", err) - } - - rel.Sha1 = commit.ID.String() - rel.CreatedUnix = util.TimeStamp(commit.Author.When.Unix()) - rel.NumCommits, err = commit.CommitsCount() - if err != nil { - return fmt.Errorf("CommitsCount: %v", err) - } - } else { - rel.CreatedUnix = util.TimeStampNow() - } - return nil +// InsertRelease inserts a release +func InsertRelease(rel *Release) error { + _, err := x.Insert(rel) + return err } -func addReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) { +// UpdateRelease updates all columns of a release +func UpdateRelease(rel *Release) error { + _, err := x.ID(rel.ID).AllCols().Update(rel) + return err +} + +// AddReleaseAttachments adds a release attachments +func AddReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error) { // Check attachments var attachments = make([]*Attachment, 0) for _, uuid := range attachmentUUIDs { @@ -173,51 +151,6 @@ func addReleaseAttachments(releaseID int64, attachmentUUIDs []string) (err error return } -// CreateRelease creates a new release of repository. -func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) error { - isExist, err := IsReleaseExist(rel.RepoID, rel.TagName) - if err != nil { - return err - } else if isExist { - return ErrReleaseAlreadyExist{rel.TagName} - } - - if err = createTag(gitRepo, rel); err != nil { - return err - } - rel.LowerTagName = strings.ToLower(rel.TagName) - - _, err = x.InsertOne(rel) - if err != nil { - return err - } - - err = addReleaseAttachments(rel.ID, attachmentUUIDs) - if err != nil { - return err - } - - if !rel.IsDraft { - if err := rel.LoadAttributes(); err != nil { - log.Error("LoadAttributes: %v", err) - } else { - mode, _ := AccessLevel(rel.Publisher, rel.Repo) - if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ - Action: api.HookReleasePublished, - Release: rel.APIFormat(), - Repository: rel.Repo.APIFormat(mode), - Sender: rel.Publisher.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks: %v", err) - } else { - go HookQueue.Add(rel.Repo.ID) - } - } - } - - return nil -} - // GetRelease returns release by given ID. func GetRelease(repoID int64, tagName string) (*Release, error) { isExist, err := IsReleaseExist(repoID, tagName) @@ -385,95 +318,12 @@ func SortReleases(rels []*Release) { sort.Sort(sorter) } -// UpdateRelease updates information of a release. -func UpdateRelease(doer *User, gitRepo *git.Repository, rel *Release, attachmentUUIDs []string) (err error) { - if err = createTag(gitRepo, rel); err != nil { - return err - } - rel.LowerTagName = strings.ToLower(rel.TagName) - - _, err = x.ID(rel.ID).AllCols().Update(rel) - if err != nil { - return err - } - - err = rel.loadAttributes(x) - if err != nil { - return err - } - - err = addReleaseAttachments(rel.ID, attachmentUUIDs) - - mode, _ := AccessLevel(doer, rel.Repo) - if err1 := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ - Action: api.HookReleaseUpdated, - Release: rel.APIFormat(), - Repository: rel.Repo.APIFormat(mode), - Sender: rel.Publisher.APIFormat(), - }); err1 != nil { - log.Error("PrepareWebhooks: %v", err) - } else { - go HookQueue.Add(rel.Repo.ID) - } - +// DeleteReleaseByID deletes a release from database by given ID. +func DeleteReleaseByID(id int64) error { + _, err := x.ID(id).Delete(new(Release)) return err } -// DeleteReleaseByID deletes a release and corresponding Git tag by given ID. -func DeleteReleaseByID(id int64, u *User, delTag bool) error { - rel, err := GetReleaseByID(id) - if err != nil { - return fmt.Errorf("GetReleaseByID: %v", err) - } - - repo, err := GetRepositoryByID(rel.RepoID) - if err != nil { - return fmt.Errorf("GetRepositoryByID: %v", err) - } - - if delTag { - _, stderr, err := process.GetManager().ExecDir(-1, repo.RepoPath(), - fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID), - git.GitExecutable, "tag", "-d", rel.TagName) - if err != nil && !strings.Contains(stderr, "not found") { - return fmt.Errorf("git tag -d: %v - %s", err, stderr) - } - - if _, err = x.ID(rel.ID).Delete(new(Release)); err != nil { - return fmt.Errorf("Delete: %v", err) - } - } else { - rel.IsTag = true - rel.IsDraft = false - rel.IsPrerelease = false - rel.Title = "" - rel.Note = "" - - if _, err = x.ID(rel.ID).AllCols().Update(rel); err != nil { - return fmt.Errorf("Update: %v", err) - } - } - - rel.Repo = repo - if err = rel.LoadAttributes(); err != nil { - return fmt.Errorf("LoadAttributes: %v", err) - } - - mode, _ := AccessLevel(u, rel.Repo) - if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ - Action: api.HookReleaseDeleted, - Release: rel.APIFormat(), - Repository: rel.Repo.APIFormat(mode), - Sender: rel.Publisher.APIFormat(), - }); err != nil { - log.Error("PrepareWebhooks: %v", err) - } else { - go HookQueue.Add(rel.Repo.ID) - } - - return nil -} - // SyncReleasesWithTags synchronizes release table with repository tags func SyncReleasesWithTags(repo *Repository, gitRepo *git.Repository) error { existingRelTags := make(map[string]struct{}) @@ -495,8 +345,8 @@ func SyncReleasesWithTags(repo *Repository, gitRepo *git.Repository) error { return fmt.Errorf("GetTagCommitID: %v", err) } if git.IsErrNotExist(err) || commitID != rel.Sha1 { - if err := pushUpdateDeleteTag(repo, rel.TagName); err != nil { - return fmt.Errorf("pushUpdateDeleteTag: %v", err) + if err := PushUpdateDeleteTag(repo, rel.TagName); err != nil { + return fmt.Errorf("PushUpdateDeleteTag: %v", err) } } else { existingRelTags[strings.ToLower(rel.TagName)] = struct{}{} @@ -509,7 +359,7 @@ func SyncReleasesWithTags(repo *Repository, gitRepo *git.Repository) error { } for _, tagName := range tags { if _, ok := existingRelTags[strings.ToLower(tagName)]; !ok { - if err := pushUpdateAddTag(repo, gitRepo, tagName); err != nil { + if err := PushUpdateAddTag(repo, gitRepo, tagName); err != nil { return fmt.Errorf("pushUpdateAddTag: %v", err) } } diff --git a/models/release_test.go b/models/release_test.go deleted file mode 100644 index 83c3fe2f77..0000000000 --- a/models/release_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// 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" - - "code.gitea.io/gitea/modules/git" - - "github.com/stretchr/testify/assert" -) - -func TestRelease_Create(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - repoPath := RepoPath(user.Name, repo.Name) - - gitRepo, err := git.OpenRepository(repoPath) - assert.NoError(t, err) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.1", - Target: "master", - Title: "v0.1 is released", - Note: "v0.1 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, - }, nil)) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.1.1", - Target: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - Title: "v0.1.1 is released", - Note: "v0.1.1 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, - }, nil)) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.1.2", - Target: "65f1bf2", - Title: "v0.1.2 is released", - Note: "v0.1.2 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, - }, nil)) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.1.3", - Target: "65f1bf2", - Title: "v0.1.3 is released", - Note: "v0.1.3 is released", - IsDraft: true, - IsPrerelease: false, - IsTag: false, - }, nil)) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.1.4", - Target: "65f1bf2", - Title: "v0.1.4 is released", - Note: "v0.1.4 is released", - IsDraft: false, - IsPrerelease: true, - IsTag: false, - }, nil)) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.1.5", - Target: "65f1bf2", - Title: "v0.1.5 is released", - Note: "v0.1.5 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: true, - }, nil)) -} - -func TestRelease_MirrorDelete(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - - user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - repoPath := RepoPath(user.Name, repo.Name) - migrationOptions := MigrateRepoOptions{ - Name: "test_mirror", - Description: "Test mirror", - IsPrivate: false, - IsMirror: true, - RemoteAddr: repoPath, - Wiki: true, - SyncReleasesWithTags: true, - } - mirror, err := MigrateRepository(user, user, migrationOptions) - assert.NoError(t, err) - - gitRepo, err := git.OpenRepository(repoPath) - assert.NoError(t, err) - - findOptions := FindReleasesOptions{IncludeDrafts: true, IncludeTags: true} - initCount, err := GetReleaseCountByRepoID(mirror.ID, findOptions) - assert.NoError(t, err) - - assert.NoError(t, CreateRelease(gitRepo, &Release{ - RepoID: repo.ID, - PublisherID: user.ID, - TagName: "v0.2", - Target: "master", - Title: "v0.2 is released", - Note: "v0.2 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: true, - }, nil)) - - err = mirror.GetMirror() - assert.NoError(t, err) - - _, ok := mirror.Mirror.runSync() - assert.True(t, ok) - - count, err := GetReleaseCountByRepoID(mirror.ID, findOptions) - assert.EqualValues(t, initCount+1, count) - - release, err := GetRelease(repo.ID, "v0.2") - assert.NoError(t, err) - assert.NoError(t, DeleteReleaseByID(release.ID, user, true)) - - _, ok = mirror.Mirror.runSync() - assert.True(t, ok) - - count, err = GetReleaseCountByRepoID(mirror.ID, findOptions) - assert.EqualValues(t, initCount, count) -} diff --git a/models/repo.go b/models/repo.go index 596c39f3df..53d568e70c 100644 --- a/models/repo.go +++ b/models/repo.go @@ -34,10 +34,10 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/sync" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" ini "gopkg.in/ini.v1" "xorm.io/builder" ) @@ -129,14 +129,14 @@ func NewRepoContext() { // Repository represents a git repository. type Repository struct { ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"UNIQUE(s)"` + OwnerID int64 `xorm:"UNIQUE(s) index"` OwnerName string `xorm:"-"` Owner *User `xorm:"-"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` Name string `xorm:"INDEX NOT NULL"` - Description string - Website string - OriginalURL string + Description string `xorm:"TEXT"` + Website string `xorm:"VARCHAR(2048)"` + OriginalURL string `xorm:"VARCHAR(2048)"` DefaultBranch string NumWatches int @@ -175,8 +175,8 @@ type Repository struct { // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols Avatar string `xorm:"VARCHAR(64)"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // ColorFormat returns a colored string to represent this repo @@ -275,12 +275,35 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) } } hasIssues := false - if _, err := repo.getUnit(e, UnitTypeIssues); err == nil { + var externalTracker *api.ExternalTracker + var internalTracker *api.InternalTracker + if unit, err := repo.getUnit(e, UnitTypeIssues); err == nil { + config := unit.IssuesConfig() hasIssues = true + internalTracker = &api.InternalTracker{ + EnableTimeTracker: config.EnableTimetracker, + AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime, + EnableIssueDependencies: config.EnableDependencies, + } + } else if unit, err := repo.getUnit(e, UnitTypeExternalTracker); err == nil { + config := unit.ExternalTrackerConfig() + hasIssues = true + externalTracker = &api.ExternalTracker{ + ExternalTrackerURL: config.ExternalTrackerURL, + ExternalTrackerFormat: config.ExternalTrackerFormat, + ExternalTrackerStyle: config.ExternalTrackerStyle, + } } hasWiki := false + var externalWiki *api.ExternalWiki if _, err := repo.getUnit(e, UnitTypeWiki); err == nil { hasWiki = true + } else if unit, err := repo.getUnit(e, UnitTypeExternalWiki); err == nil { + hasWiki = true + config := unit.ExternalWikiConfig() + externalWiki = &api.ExternalWiki{ + ExternalWikiURL: config.ExternalWikiURL, + } } hasPullRequests := false ignoreWhitespaceConflicts := false @@ -324,7 +347,10 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) Updated: repo.UpdatedUnix.AsTime(), Permissions: permission, HasIssues: hasIssues, + ExternalTracker: externalTracker, + InternalTracker: internalTracker, HasWiki: hasWiki, + ExternalWiki: externalWiki, HasPullRequests: hasPullRequests, IgnoreWhitespaceConflicts: ignoreWhitespaceConflicts, AllowMerge: allowMerge, @@ -508,8 +534,9 @@ func (repo *Repository) mustOwnerName(e Engine) string { func (repo *Repository) ComposeMetas() map[string]string { if repo.ExternalMetas == nil { repo.ExternalMetas = map[string]string{ - "user": repo.MustOwner().Name, - "repo": repo.Name, + "user": repo.MustOwner().Name, + "repo": repo.Name, + "repoPath": repo.RepoPath(), } unit, err := repo.GetUnit(UnitTypeExternalTracker) if err != nil { @@ -970,7 +997,7 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err RepoID: repo.ID, Interval: setting.Mirror.DefaultInterval, EnablePrune: true, - NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), + NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), }); err != nil { return repo, fmt.Errorf("InsertOne: %v", err) } @@ -1097,6 +1124,7 @@ type CreateRepoOptions struct { Description string OriginalURL string Gitignores string + IssueLabels string License string Readme string IsPrivate bool @@ -1306,13 +1334,17 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err return err } - u.NumRepos++ // Remember visibility preference. u.LastRepoVisibility = repo.IsPrivate - if err = updateUser(e, u); err != nil { + if err = updateUserCols(e, u, "last_repo_visibility"); err != nil { return fmt.Errorf("updateUser: %v", err) } + if _, err = e.Incr("num_repos").ID(u.ID).Update(new(User)); err != nil { + return fmt.Errorf("increment user total_repos: %v", err) + } + u.NumRepos++ + // Give access to all members in teams with access to all repositories. if u.IsOrganization() { if err := u.GetTeams(); err != nil { @@ -1333,7 +1365,6 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err }); err != nil { return fmt.Errorf("prepareWebhooks: %v", err) } - go HookQueue.Add(repo.ID) } else if err = repo.recalculateAccesses(e); err != nil { // Organization automatically called this in addRepository method. return fmt.Errorf("recalculateAccesses: %v", err) @@ -1395,6 +1426,13 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err return nil, fmt.Errorf("initRepository: %v", err) } + // Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err = initalizeLabels(sess, repo.ID, opts.IssueLabels); err != nil { + return nil, fmt.Errorf("initalizeLabels: %v", err) + } + } + _, stderr, err := process.GetManager().ExecDir(-1, repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), git.GitExecutable, "update-server-info") @@ -1403,7 +1441,16 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err } } - return repo, sess.Commit() + if err = sess.Commit(); err != nil { + return nil, err + } + + // Add to hook queue for created repo after session commit. + if u.IsOrganization() { + go HookQueue.Add(repo.ID) + } + + return repo, err } func countRepositories(userID int64, private bool) int64 { @@ -1708,6 +1755,12 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { return sess.Commit() } +// UpdateRepositoryUpdatedTime updates a repository's updated time +func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error { + _, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID) + return err +} + // UpdateRepositoryUnits updates a repository's units func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { sess := x.NewSession() @@ -1797,6 +1850,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error { &HookTask{RepoID: repoID}, &Notification{RepoID: repoID}, &CommitStatus{RepoID: repoID}, + &RepoIndexerStatus{RepoID: repoID}, + &Comment{RefRepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %v", err) } @@ -1943,8 +1998,12 @@ func DeleteRepository(doer *User, uid, repoID int64) error { // GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { + return getRepositoryByOwnerAndName(x, ownerName, repoName) +} + +func getRepositoryByOwnerAndName(e Engine, ownerName, repoName string) (*Repository, error) { var repo Repository - has, err := x.Select("repository.*"). + has, err := e.Table("repository").Select("repository.*"). Join("INNER", "`user`", "`user`.id = repository.owner_id"). Where("repository.lower_name = ?", strings.ToLower(repoName)). And("`user`.lower_name = ?", strings.ToLower(ownerName)). @@ -2065,11 +2124,6 @@ func DeleteRepositoryArchives() error { // DeleteOldRepositoryArchives deletes old repository archives. func DeleteOldRepositoryArchives() { - if !taskStatusTable.StartIfNotRunning(archiveCleanup) { - return - } - defer taskStatusTable.Stop(archiveCleanup) - log.Trace("Doing: ArchiveCleanup") if err := x.Where("id > 0").Iterate(new(Repository), deleteOldRepositoryArchives); err != nil { @@ -2196,27 +2250,12 @@ func SyncRepositoryHooks() error { }) } -// Prevent duplicate running tasks. -var taskStatusTable = sync.NewStatusTable() - -const ( - mirrorUpdate = "mirror_update" - gitFsck = "git_fsck" - checkRepos = "check_repos" - archiveCleanup = "archive_cleanup" -) - // GitFsck calls 'git fsck' to check repository health. func GitFsck() { - if !taskStatusTable.StartIfNotRunning(gitFsck) { - return - } - defer taskStatusTable.Stop(gitFsck) - log.Trace("Doing: GitFsck") if err := x. - Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.IterateBufferSize). + Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.Database.IterateBufferSize). Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) @@ -2240,7 +2279,7 @@ func GitFsck() { func GitGcRepos() error { args := append([]string{"gc"}, setting.Git.GCArgs...) return x. - Where("id > 0").BufferSize(setting.IterateBufferSize). + Where("id > 0").BufferSize(setting.Database.IterateBufferSize). Iterate(new(Repository), func(idx int, bean interface{}) error { repo := bean.(*Repository) @@ -2281,11 +2320,6 @@ func repoStatsCheck(checker *repoChecker) { // CheckRepoStats checks the repository stats func CheckRepoStats() { - if !taskStatusTable.StartIfNotRunning(checkRepos) { - return - } - defer taskStatusTable.Stop(checkRepos) - log.Trace("Doing: CheckRepoStats") checkers := []*repoChecker{ @@ -2341,6 +2375,23 @@ func CheckRepoStats() { } // ***** END: Repository.NumClosedIssues ***** + // ***** START: Repository.NumClosedPulls ***** + desc = "repository count 'num_closed_pulls'" + results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true) + if err != nil { + log.Error("Select %s: %v", desc, err) + } else { + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating %s: %d", desc, id) + _, err = x.Exec("UPDATE `repository` SET num_closed_pulls=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, true, id) + if err != nil { + log.Error("Update %s[%d]: %v", desc, id, err) + } + } + } + // ***** END: Repository.NumClosedPulls ***** + // FIXME: use checker when stop supporting old fork repo format. // ***** START: Repository.NumForks ***** results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") @@ -2475,6 +2526,11 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R go HookQueue.Add(oldRepo.ID) } + // Add to hook queue for created repo after session commit. + if u.IsOrganization() { + go HookQueue.Add(repo.ID) + } + if err = repo.UpdateSize(); err != nil { log.Error("Failed to update size for repository: %v", err) } @@ -2566,7 +2622,7 @@ func (repo *Repository) generateRandomAvatar(e Engine) error { // RemoveRandomAvatars removes the randomly generated avatars that were created for repositories func RemoveRandomAvatars() error { return x. - Where("id > 0").BufferSize(setting.IterateBufferSize). + Where("id > 0").BufferSize(setting.Database.IterateBufferSize). Iterate(new(Repository), func(idx int, bean interface{}) error { repository := bean.(*Repository) diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index 0797f50430..40ddf6a28c 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -16,20 +16,6 @@ type Collaboration struct { Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` } -// ModeI18nKey returns the collaboration mode I18n Key -func (c *Collaboration) ModeI18nKey() string { - switch c.Mode { - case AccessModeRead: - return "repo.settings.collaboration.read" - case AccessModeWrite: - return "repo.settings.collaboration.write" - case AccessModeAdmin: - return "repo.settings.collaboration.admin" - default: - return "repo.settings.collaboration.undefined" - } -} - // AddCollaborator adds new collaboration to a repository with default access mode. func (repo *Repository) AddCollaborator(u *User) error { collaboration := &Collaboration{ @@ -183,3 +169,17 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) { return sess.Commit() } + +func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) { + return teams, e. + Join("INNER", "team_repo", "team_repo.team_id = team.id"). + Where("team.org_id = ?", repo.OwnerID). + And("team_repo.repo_id=?", repo.ID). + OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END"). + Find(&teams) +} + +// GetRepoTeams gets the list of teams that has access to the repository +func (repo *Repository) GetRepoTeams() ([]*Team, error) { + return repo.getRepoTeams(x) +} diff --git a/models/repo_collaboration_test.go b/models/repo_collaboration_test.go index f11f3c54c3..0842212460 100644 --- a/models/repo_collaboration_test.go +++ b/models/repo_collaboration_test.go @@ -10,17 +10,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCollaboration_ModeI18nKey(t *testing.T) { - assert.Equal(t, "repo.settings.collaboration.read", - (&Collaboration{Mode: AccessModeRead}).ModeI18nKey()) - assert.Equal(t, "repo.settings.collaboration.write", - (&Collaboration{Mode: AccessModeWrite}).ModeI18nKey()) - assert.Equal(t, "repo.settings.collaboration.admin", - (&Collaboration{Mode: AccessModeAdmin}).ModeI18nKey()) - assert.Equal(t, "repo.settings.collaboration.undefined", - (&Collaboration{Mode: AccessModeNone}).ModeI18nKey()) -} - func TestRepository_AddCollaborator(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/repo_indexer.go b/models/repo_indexer.go index 140ec66c03..b842a1c87f 100644 --- a/models/repo_indexer.go +++ b/models/repo_indexer.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/log" @@ -199,7 +200,7 @@ func addUpdate(update fileUpdate, repo *Repository, batch rupture.FlushingBatch) if size, err := strconv.Atoi(strings.TrimSpace(stdout)); err != nil { return fmt.Errorf("Misformatted git cat-file output: %v", err) } else if int64(size) > setting.Indexer.MaxIndexerFileSize { - return nil + return addDelete(update.Filename, repo, batch) } fileContents, err := git.NewCommand("cat-file", "blob", update.BlobSha). @@ -207,6 +208,7 @@ func addUpdate(update fileUpdate, repo *Repository, batch rupture.FlushingBatch) if err != nil { return err } else if !base.IsTextFile(fileContents) { + // FIXME: UTF-16 files will probably fail here return nil } indexerUpdate := indexer.RepoIndexerUpdate{ @@ -214,7 +216,7 @@ func addUpdate(update fileUpdate, repo *Repository, batch rupture.FlushingBatch) Op: indexer.RepoIndexerOpUpdate, Data: &indexer.RepoIndexerData{ RepoID: repo.ID, - Content: string(fileContents), + Content: string(charset.ToUTF8DropErrors(fileContents)), }, } return indexerUpdate.AddToFlushingBatch(batch) @@ -231,20 +233,42 @@ func addDelete(filename string, repo *Repository, batch rupture.FlushingBatch) e return indexerUpdate.AddToFlushingBatch(batch) } +func isIndexable(entry *git.TreeEntry) bool { + if !entry.IsRegular() && !entry.IsExecutable() { + return false + } + name := strings.ToLower(entry.Name()) + for _, g := range setting.Indexer.ExcludePatterns { + if g.Match(name) { + return false + } + } + for _, g := range setting.Indexer.IncludePatterns { + if g.Match(name) { + return true + } + } + return len(setting.Indexer.IncludePatterns) == 0 +} + // parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command func parseGitLsTreeOutput(stdout []byte) ([]fileUpdate, error) { entries, err := git.ParseTreeEntries(stdout) if err != nil { return nil, err } + var idxCount = 0 updates := make([]fileUpdate, len(entries)) - for i, entry := range entries { - updates[i] = fileUpdate{ - Filename: entry.Name(), - BlobSha: entry.ID.String(), + for _, entry := range entries { + if isIndexable(entry) { + updates[idxCount] = fileUpdate{ + Filename: entry.Name(), + BlobSha: entry.ID.String(), + } + idxCount++ } } - return updates, nil + return updates[:idxCount], nil } // genesisChanges get changes to add repo to the indexer for the first time diff --git a/models/repo_list.go b/models/repo_list.go index 7460c4b0ed..692d4d002f 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -136,6 +136,8 @@ type SearchRepoOptions struct { Mirror util.OptionalBool // only search topic name TopicOnly bool + // include description in keyword search + IncludeDescription bool } //SearchOrderBy is used to sort the result @@ -163,9 +165,9 @@ const ( SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" ) -// SearchRepositoryByName takes keyword and part of repository name to search, +// SearchRepository returns repositories based on search options, // it returns results in given range and number of total results. -func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { +func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { if opts.Page <= 0 { opts.Page = 1 } @@ -248,7 +250,11 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err // separate keyword var subQueryCond = builder.NewCond() for _, v := range strings.Split(opts.Keyword, ",") { - subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) + if opts.TopicOnly { + subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) + } else { + subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) + } } subQuery := builder.Select("repo_topic.repo_id").From("repo_topic"). Join("INNER", "topic", "topic.id = repo_topic.topic_id"). @@ -260,6 +266,9 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err var likes = builder.NewCond() for _, v := range strings.Split(opts.Keyword, ",") { likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) + if opts.IncludeDescription { + likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) + } } keywordCond = keywordCond.Or(likes) } @@ -307,6 +316,13 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err return repos, count, nil } +// SearchRepositoryByName takes keyword and part of repository name to search, +// it returns results in given range and number of total results. +func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { + opts.IncludeDescription = false + return SearchRepository(opts) +} + // FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id func FindUserAccessibleRepoIDs(userID int64) ([]int64, error) { var accessCond builder.Cond = builder.Eq{"is_private": false} diff --git a/models/repo_list_test.go b/models/repo_list_test.go index 645de2a59a..e3a7acd4a4 100644 --- a/models/repo_list_test.go +++ b/models/repo_list_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSearchRepositoryByName(t *testing.T) { +func TestSearchRepository(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) // test search public repository on explore page @@ -74,6 +74,34 @@ func TestSearchRepositoryByName(t *testing.T) { assert.Empty(t, repos) assert.Equal(t, int64(0), count) + // Test search within description + repos, count, err = SearchRepository(&SearchRepoOptions{ + Keyword: "description_14", + Page: 1, + PageSize: 10, + Collaborate: util.OptionalBoolFalse, + IncludeDescription: true, + }) + + assert.NoError(t, err) + if assert.Len(t, repos, 1) { + assert.Equal(t, "test_repo_14", repos[0].Name) + } + assert.Equal(t, int64(1), count) + + // Test NOT search within description + repos, count, err = SearchRepository(&SearchRepoOptions{ + Keyword: "description_14", + Page: 1, + PageSize: 10, + Collaborate: util.OptionalBoolFalse, + IncludeDescription: false, + }) + + assert.NoError(t, err) + assert.Empty(t, repos) + assert.Equal(t, int64(0), count) + testCases := []struct { name string opts *SearchRepoOptions @@ -147,10 +175,10 @@ func TestSearchRepositoryByName(t *testing.T) { count: 14}, {name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, - count: 21}, + count: 22}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, - count: 27}, + count: 28}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, count: 15}, @@ -159,7 +187,7 @@ func TestSearchRepositoryByName(t *testing.T) { count: 13}, {name: "AllPublic/PublicRepositoriesOfOrganization", opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse}, - count: 21}, + count: 22}, } for _, testCase := range testCases { diff --git a/models/repo_mirror.go b/models/repo_mirror.go index c62834f6fb..4e91ea296a 100644 --- a/models/repo_mirror.go +++ b/models/repo_mirror.go @@ -6,26 +6,14 @@ package models import ( - "fmt" - "strings" "time" - "code.gitea.io/gitea/modules/cache" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/sync" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" - "github.com/mcuadros/go-version" ) -// MirrorQueue holds an UniqueQueue object of the mirror -var MirrorQueue = sync.NewUniqueQueue(setting.Repository.MirrorQueueLength) - // Mirror represents mirror information of a repository. type Mirror struct { ID int64 `xorm:"pk autoincr"` @@ -34,17 +22,17 @@ type Mirror struct { Interval time.Duration EnablePrune bool `xorm:"NOT NULL DEFAULT true"` - UpdatedUnix util.TimeStamp `xorm:"INDEX"` - NextUpdateUnix util.TimeStamp `xorm:"INDEX"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX"` + NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"` - address string `xorm:"-"` + Address string `xorm:"-"` } // BeforeInsert will be invoked by XORM before inserting a record func (m *Mirror) BeforeInsert() { if m != nil { - m.UpdatedUnix = util.TimeStampNow() - m.NextUpdateUnix = util.TimeStampNow() + m.UpdatedUnix = timeutil.TimeStampNow() + m.NextUpdateUnix = timeutil.TimeStampNow() } } @@ -64,223 +52,12 @@ func (m *Mirror) AfterLoad(session *xorm.Session) { // ScheduleNextUpdate calculates and sets next update time. func (m *Mirror) ScheduleNextUpdate() { if m.Interval != 0 { - m.NextUpdateUnix = util.TimeStampNow().AddDuration(m.Interval) + m.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(m.Interval) } else { m.NextUpdateUnix = 0 } } -func remoteAddress(repoPath string) (string, error) { - var cmd *git.Command - binVersion, err := git.BinVersion() - if err != nil { - return "", err - } - if version.Compare(binVersion, "2.7", ">=") { - cmd = git.NewCommand("remote", "get-url", "origin") - } else { - cmd = git.NewCommand("config", "--get", "remote.origin.url") - } - - result, err := cmd.RunInDir(repoPath) - if err != nil { - if strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { - return "", nil - } - return "", err - } - if len(result) > 0 { - return result[:len(result)-1], nil - } - return "", nil -} - -func (m *Mirror) readAddress() { - if len(m.address) > 0 { - return - } - var err error - m.address, err = remoteAddress(m.Repo.RepoPath()) - if err != nil { - log.Error("remoteAddress: %v", err) - } -} - -// sanitizeOutput sanitizes output of a command, replacing occurrences of the -// repository's remote address with a sanitized version. -func sanitizeOutput(output, repoPath string) (string, error) { - remoteAddr, err := remoteAddress(repoPath) - if err != nil { - // if we're unable to load the remote address, then we're unable to - // sanitize. - return "", err - } - return util.SanitizeMessage(output, remoteAddr), nil -} - -// Address returns mirror address from Git repository config without credentials. -func (m *Mirror) Address() string { - m.readAddress() - return util.SanitizeURLCredentials(m.address, false) -} - -// FullAddress returns mirror address from Git repository config. -func (m *Mirror) FullAddress() string { - m.readAddress() - return m.address -} - -// SaveAddress writes new address to Git repository config. -func (m *Mirror) SaveAddress(addr string) error { - repoPath := m.Repo.RepoPath() - // Remove old origin - _, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath) - if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { - return err - } - - _, err = git.NewCommand("remote", "add", "origin", "--mirror=fetch", addr).RunInDir(repoPath) - return err -} - -// gitShortEmptySha Git short empty SHA -const gitShortEmptySha = "0000000" - -// mirrorSyncResult contains information of a updated reference. -// If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty. -// If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty. -type mirrorSyncResult struct { - refName string - oldCommitID string - newCommitID string -} - -// parseRemoteUpdateOutput detects create, update and delete operations of references from upstream. -func parseRemoteUpdateOutput(output string) []*mirrorSyncResult { - results := make([]*mirrorSyncResult, 0, 3) - lines := strings.Split(output, "\n") - for i := range lines { - // Make sure reference name is presented before continue - idx := strings.Index(lines[i], "-> ") - if idx == -1 { - continue - } - - refName := lines[i][idx+3:] - - switch { - case strings.HasPrefix(lines[i], " * "): // New reference - results = append(results, &mirrorSyncResult{ - refName: refName, - oldCommitID: gitShortEmptySha, - }) - case strings.HasPrefix(lines[i], " - "): // Delete reference - results = append(results, &mirrorSyncResult{ - refName: refName, - newCommitID: gitShortEmptySha, - }) - case strings.HasPrefix(lines[i], " "): // New commits of a reference - delimIdx := strings.Index(lines[i][3:], " ") - if delimIdx == -1 { - log.Error("SHA delimiter not found: %q", lines[i]) - continue - } - shas := strings.Split(lines[i][3:delimIdx+3], "..") - if len(shas) != 2 { - log.Error("Expect two SHAs but not what found: %q", lines[i]) - continue - } - results = append(results, &mirrorSyncResult{ - refName: refName, - oldCommitID: shas[0], - newCommitID: shas[1], - }) - - default: - log.Warn("parseRemoteUpdateOutput: unexpected update line %q", lines[i]) - } - } - return results -} - -// runSync returns true if sync finished without error. -func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) { - repoPath := m.Repo.RepoPath() - wikiPath := m.Repo.WikiPath() - timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second - - gitArgs := []string{"remote", "update"} - if m.EnablePrune { - gitArgs = append(gitArgs, "--prune") - } - - _, stderr, err := process.GetManager().ExecDir( - timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), - git.GitExecutable, gitArgs...) - if err != nil { - // sanitize the output, since it may contain the remote address, which may - // contain a password - message, err := sanitizeOutput(stderr, repoPath) - if err != nil { - log.Error("sanitizeOutput: %v", err) - return nil, false - } - desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message) - log.Error(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error("CreateRepositoryNotice: %v", err) - } - return nil, false - } - output := stderr - - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - log.Error("OpenRepository: %v", err) - return nil, false - } - if err = SyncReleasesWithTags(m.Repo, gitRepo); err != nil { - log.Error("Failed to synchronize tags to releases for repository: %v", err) - } - - if err := m.Repo.UpdateSize(); err != nil { - log.Error("Failed to update size for mirror repository: %v", err) - } - - if m.Repo.HasWiki() { - if _, stderr, err := process.GetManager().ExecDir( - timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath), - git.GitExecutable, "remote", "update", "--prune"); err != nil { - // sanitize the output, since it may contain the remote address, which may - // contain a password - message, err := sanitizeOutput(stderr, wikiPath) - if err != nil { - log.Error("sanitizeOutput: %v", err) - return nil, false - } - desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message) - log.Error(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error("CreateRepositoryNotice: %v", err) - } - return nil, false - } - } - - branches, err := m.Repo.GetBranches() - if err != nil { - log.Error("GetBranches: %v", err) - return nil, false - } - - for i := range branches { - cache.Remove(m.Repo.GetCommitsCountCacheKey(branches[i].Name, true)) - } - - m.UpdatedUnix = util.TimeStampNow() - return parseRemoteUpdateOutput(output), true -} - func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { m := &Mirror{RepoID: repoID} has, err := e.Get(m) @@ -313,134 +90,10 @@ func DeleteMirrorByRepoID(repoID int64) error { return err } -// MirrorUpdate checks and updates mirror repositories. -func MirrorUpdate() { - if !taskStatusTable.StartIfNotRunning(mirrorUpdate) { - return - } - defer taskStatusTable.Stop(mirrorUpdate) - - log.Trace("Doing: MirrorUpdate") - - if err := x. +// MirrorsIterate iterates all mirror repositories. +func MirrorsIterate(f func(idx int, bean interface{}) error) error { + return x. Where("next_update_unix<=?", time.Now().Unix()). And("next_update_unix!=0"). - Iterate(new(Mirror), func(idx int, bean interface{}) error { - m := bean.(*Mirror) - if m.Repo == nil { - log.Error("Disconnected mirror repository found: %d", m.ID) - return nil - } - - MirrorQueue.Add(m.RepoID) - return nil - }); err != nil { - log.Error("MirrorUpdate: %v", err) - } -} - -// SyncMirrors checks and syncs mirrors. -// TODO: sync more mirrors at same time. -func SyncMirrors() { - sess := x.NewSession() - defer sess.Close() - // Start listening on new sync requests. - for repoID := range MirrorQueue.Queue() { - log.Trace("SyncMirrors [repo_id: %v]", repoID) - MirrorQueue.Remove(repoID) - - m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64()) - if err != nil { - log.Error("GetMirrorByRepoID [%s]: %v", repoID, err) - continue - } - - results, ok := m.runSync() - if !ok { - continue - } - - m.ScheduleNextUpdate() - if err = updateMirror(sess, m); err != nil { - log.Error("UpdateMirror [%s]: %v", repoID, err) - continue - } - - var gitRepo *git.Repository - if len(results) == 0 { - log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID) - } else { - gitRepo, err = git.OpenRepository(m.Repo.RepoPath()) - if err != nil { - log.Error("OpenRepository [%d]: %v", m.RepoID, err) - continue - } - } - - for _, result := range results { - // Discard GitHub pull requests, i.e. refs/pull/* - if strings.HasPrefix(result.refName, "refs/pull/") { - continue - } - - // Create reference - if result.oldCommitID == gitShortEmptySha { - if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil { - log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err) - } - continue - } - - // Delete reference - if result.newCommitID == gitShortEmptySha { - if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil { - log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err) - } - continue - } - - // Push commits - oldCommitID, err := git.GetFullCommitID(gitRepo.Path, result.oldCommitID) - if err != nil { - log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - newCommitID, err := git.GetFullCommitID(gitRepo.Path, result.newCommitID) - if err != nil { - log.Error("GetFullCommitID [%d]: %v", m.RepoID, err) - continue - } - commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID) - if err != nil { - log.Error("CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err) - continue - } - if err = MirrorSyncPushAction(m.Repo, MirrorSyncPushActionOptions{ - RefName: result.refName, - OldCommitID: oldCommitID, - NewCommitID: newCommitID, - Commits: ListToPushCommits(commits), - }); err != nil { - log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err) - continue - } - } - - // Get latest commit date and update to current repository updated time - commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath()) - if err != nil { - log.Error("GetLatestCommitDate [%d]: %v", m.RepoID, err) - continue - } - - if _, err = sess.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", commitDate.Unix(), m.RepoID); err != nil { - log.Error("Update repository 'updated_unix' [%d]: %v", m.RepoID, err) - continue - } - } -} - -// InitSyncMirrors initializes a go routine to sync the mirrors -func InitSyncMirrors() { - go SyncMirrors() + Iterate(new(Mirror), f) } diff --git a/models/repo_permission.go b/models/repo_permission.go index 25239f4dd4..916678d168 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -113,7 +113,7 @@ func (p *Permission) ColorFormat(s fmt.State) { configBytes, err := unit.Config.ToDB() config = string(configBytes) if err != nil { - config = string(err.Error()) + config = err.Error() } } format += "\nUnits[%d]: ID: %d RepoID: %d Type: %-v Config: %s" diff --git a/models/repo_redirect.go b/models/repo_redirect.go index 8847a0889c..2714121a6c 100644 --- a/models/repo_redirect.go +++ b/models/repo_redirect.go @@ -5,8 +5,9 @@ package models import ( - "code.gitea.io/gitea/modules/log" "strings" + + "code.gitea.io/gitea/modules/log" ) // RepoRedirect represents that a repo name should be redirected to another diff --git a/models/repo_test.go b/models/repo_test.go index 02cb5ab993..bf10de8d99 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -14,8 +14,8 @@ import ( "code.gitea.io/gitea/modules/markup" - "github.com/Unknwon/com" "github.com/stretchr/testify/assert" + "github.com/unknwon/com" ) func TestRepo(t *testing.T) { @@ -88,6 +88,7 @@ func TestUpdateRepositoryVisibilityChanged(t *testing.T) { // Get sample repo and change visibility repo, err := GetRepositoryByID(9) + assert.NoError(t, err) repo.IsPrivate = true // Update it diff --git a/models/repo_unit.go b/models/repo_unit.go index 80126270de..2fc1c40fa2 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -7,20 +7,20 @@ package models import ( "encoding/json" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" "xorm.io/core" ) // RepoUnit describes all units of a repository type RepoUnit struct { ID int64 - RepoID int64 `xorm:"INDEX(s)"` - Type UnitType `xorm:"INDEX(s)"` - Config core.Conversion `xorm:"TEXT"` - CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"` + RepoID int64 `xorm:"INDEX(s)"` + Type UnitType `xorm:"INDEX(s)"` + Config core.Conversion `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` } // UnitConfig describes common unit config diff --git a/models/review.go b/models/review.go index 458d58152e..454d16ee88 100644 --- a/models/review.go +++ b/models/review.go @@ -8,8 +8,7 @@ import ( "fmt" "code.gitea.io/gitea/modules/log" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" "xorm.io/builder" @@ -57,8 +56,8 @@ type Review struct { IssueID int64 `xorm:"index"` Content string - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` // CodeComments are the initial code comments of the review CodeComments CodeComments `xorm:"-"` @@ -235,42 +234,6 @@ func createReview(e Engine, opts CreateReviewOptions) (*Review, error) { return nil, err } - var reviewHookType HookEventType - - switch opts.Type { - case ReviewTypeApprove: - reviewHookType = HookEventPullRequestApproved - case ReviewTypeComment: - reviewHookType = HookEventPullRequestComment - case ReviewTypeReject: - reviewHookType = HookEventPullRequestRejected - default: - // unsupported review webhook type here - return review, nil - } - - pr := opts.Issue.PullRequest - - if err := pr.LoadIssue(); err != nil { - return nil, err - } - - mode, err := AccessLevel(opts.Issue.Poster, opts.Issue.Repo) - if err != nil { - return nil, err - } - - if err := PrepareWebhooks(opts.Issue.Repo, reviewHookType, &api.PullRequestPayload{ - Action: api.HookIssueSynchronized, - Index: opts.Issue.Index, - PullRequest: pr.APIFormat(), - Repository: opts.Issue.Repo.APIFormat(mode), - Sender: opts.Reviewer.APIFormat(), - }); err != nil { - return nil, err - } - go HookQueue.Add(opts.Issue.Repo.ID) - return review, nil } @@ -316,7 +279,7 @@ func UpdateReview(r *Review) error { type PullReviewersWithType struct { User `xorm:"extends"` Type ReviewType - ReviewUpdatedUnix util.TimeStamp `xorm:"review_updated_unix"` + ReviewUpdatedUnix timeutil.TimeStamp `xorm:"review_updated_unix"` } // GetReviewersByPullID gets all reviewers for a pull request with the statuses diff --git a/models/ssh_key.go b/models/ssh_key.go index ceb4d97560..b7c5b4fe6e 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -7,8 +7,12 @@ package models import ( "bufio" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" "encoding/base64" "encoding/binary" + "encoding/pem" "errors" "fmt" "io/ioutil" @@ -22,17 +26,17 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" "golang.org/x/crypto/ssh" "xorm.io/builder" ) const ( tplCommentPrefix = `# gitea public key` - tplPublicKey = tplCommentPrefix + "\n" + `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" + tplPublicKey = tplCommentPrefix + "\n" + `command="%s --config='%s' serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" ) var sshOpLocker sync.Mutex @@ -58,16 +62,16 @@ type PublicKey struct { Type KeyType `xorm:"NOT NULL DEFAULT 1"` LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` - CreatedUnix util.TimeStamp `xorm:"created"` - UpdatedUnix util.TimeStamp `xorm:"updated"` - HasRecentActivity bool `xorm:"-"` - HasUsed bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` + HasRecentActivity bool `xorm:"-"` + HasUsed bool `xorm:"-"` } // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (key *PublicKey) AfterLoad() { key.HasUsed = key.UpdatedUnix > key.CreatedUnix - key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow() + key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow() } // OmitEmail returns content of public key without email address. @@ -77,7 +81,7 @@ func (key *PublicKey) OmitEmail() string { // AuthorizedString returns formatted public key string for authorized_keys file. func (key *PublicKey) AuthorizedString() string { - return fmt.Sprintf(tplPublicKey, setting.AppPath, key.ID, setting.CustomConf, key.Content) + return fmt.Sprintf(tplPublicKey, setting.AppPath, setting.CustomConf, key.ID, key.Content) } func extractTypeFromBase64Key(key string) (string, error) { @@ -94,19 +98,74 @@ func extractTypeFromBase64Key(key string) (string, error) { return string(b[4 : 4+keyLength]), nil } +const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----" + // parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253). func parseKeyString(content string) (string, error) { - // Transform all legal line endings to a single "\n". - content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content) - // remove trailing newline (and beginning spaces too) + // remove whitespace at start and end content = strings.TrimSpace(content) - lines := strings.Split(content, "\n") var keyType, keyContent, keyComment string - if len(lines) == 1 { + if content[:len(ssh2keyStart)] == ssh2keyStart { + // Parse SSH2 file format. + + // Transform all legal line endings to a single "\n". + content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content) + + lines := strings.Split(content, "\n") + continuationLine := false + + for _, line := range lines { + // Skip lines that: + // 1) are a continuation of the previous line, + // 2) contain ":" as that are comment lines + // 3) contain "-" as that are begin and end tags + if continuationLine || strings.ContainsAny(line, ":-") { + continuationLine = strings.HasSuffix(line, "\\") + } else { + keyContent += line + } + } + + t, err := extractTypeFromBase64Key(keyContent) + if err != nil { + return "", fmt.Errorf("extractTypeFromBase64Key: %v", err) + } + keyType = t + } else { + if strings.Contains(content, "-----BEGIN") { + // Convert PEM Keys to OpenSSH format + // Transform all legal line endings to a single "\n". + content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content) + + block, _ := pem.Decode([]byte(content)) + if block == nil { + return "", fmt.Errorf("failed to parse PEM block containing the public key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + var pk rsa.PublicKey + _, err2 := asn1.Unmarshal(block.Bytes, &pk) + if err2 != nil { + return "", fmt.Errorf("failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %v", err, err2) + } + pub = &pk + } + + sshKey, err := ssh.NewPublicKey(pub) + if err != nil { + return "", fmt.Errorf("unable to convert to ssh public key: %v", err) + } + content = string(ssh.MarshalAuthorizedKey(sshKey)) + } // Parse OpenSSH format. - parts := strings.SplitN(lines[0], " ", 3) + + // Remove all newlines + content = strings.NewReplacer("\r\n", "", "\n", "").Replace(content) + + parts := strings.SplitN(content, " ", 3) switch len(parts) { case 0: return "", errors.New("empty key") @@ -131,27 +190,11 @@ func parseKeyString(content string) (string, error) { } else if keyType != t { return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t) } - } else { - // Parse SSH2 file format. - continuationLine := false - - for _, line := range lines { - // Skip lines that: - // 1) are a continuation of the previous line, - // 2) contain ":" as that are comment lines - // 3) contain "-" as that are begin and end tags - if continuationLine || strings.ContainsAny(line, ":-") { - continuationLine = strings.HasSuffix(line, "\\") - } else { - keyContent += line - } - } - - t, err := extractTypeFromBase64Key(keyContent) - if err != nil { - return "", fmt.Errorf("extractTypeFromBase64Key: %v", err) - } - keyType = t + } + // Finally we need to check whether we can actually read the proposed key: + _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyType + " " + keyContent + " " + keyComment)) + if err != nil { + return "", fmt.Errorf("invalid ssh public key: %v", err) } return keyType + " " + keyContent + " " + keyComment, nil } @@ -538,7 +581,7 @@ func UpdatePublicKeyUpdated(id int64) error { } _, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{ - UpdatedUnix: util.TimeStampNow(), + UpdatedUnix: timeutil.TimeStampNow(), }) if err != nil { return err @@ -642,12 +685,14 @@ func rewriteAllPublicKeys(e Engine) error { } _, err = t.WriteString(line + "\n") if err != nil { + f.Close() return err } } - defer f.Close() + f.Close() } + t.Close() return os.Rename(tmpPath, fPath) } @@ -669,16 +714,16 @@ type DeployKey struct { Mode AccessMode `xorm:"NOT NULL DEFAULT 1"` - CreatedUnix util.TimeStamp `xorm:"created"` - UpdatedUnix util.TimeStamp `xorm:"updated"` - HasRecentActivity bool `xorm:"-"` - HasUsed bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` + HasRecentActivity bool `xorm:"-"` + HasUsed bool `xorm:"-"` } // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (key *DeployKey) AfterLoad() { key.HasUsed = key.UpdatedUnix > key.CreatedUnix - key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow() + key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow() } // GetContent gets associated public key content. diff --git a/models/ssh_key_test.go b/models/ssh_key_test.go index 5d095f6378..4bb612a671 100644 --- a/models/ssh_key_test.go +++ b/models/ssh_key_test.go @@ -56,6 +56,83 @@ func Test_SSHParsePublicKey(t *testing.T) { } } +func Test_CheckPublicKeyString(t *testing.T) { + for _, test := range []struct { + content string + }{ + {"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment"}, + {"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"}, + {"ssh-rsa AAAAB3NzaC1yc2EA\r\nAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+\r\nBZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNx\r\nfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\r\n\r\n"}, + {"ssh-rsa AAAAB3NzaC1yc2EA\r\nAAADAQABAAAAgQDAu7tvI\nvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+\r\nBZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvW\nqIwC4prx/WVk2wLTJjzBAhyNx\r\nfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\r\n\r\n"}, + {"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf"}, + {"\r\nssh-ed25519 \r\nAAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf\r\n\r\n"}, + {`---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "1024-bit DSA, converted by andrew@phaedra from OpenSSH" +AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3 +ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/ +YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL ++wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8 +A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb +0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgP +aguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxc +Ns4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd6429 +82daopE7zQ/NPAnJfag= +---- END SSH2 PUBLIC KEY ---- +`}, + {`---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "1024-bit RSA, converted by andrew@phaedra from OpenSSH" +AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxB +cQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIV +j0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== +---- END SSH2 PUBLIC KEY ---- +`}, + {`-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAMC7u28i9fpketFe5k1+RHdcsdKy4Ir1mfdfnyXEFxDO6jnFmAHq9HDC +b9C0m4X7Nk+1jmGxAgsEuYX4FnlakpmnWMF5KMfYbuXF632Rtwf6QhWPS08USjIo +j3C9aojALimvH9ZWTbAtMmPMECHI3F8SrsL0J6Jf2lARsSol+QoJAgMBAAE= +-----END RSA PUBLIC KEY----- +`}, + {`-----BEGIN PUBLIC KEY----- +MIIBtzCCASsGByqGSM44BAEwggEeAoGBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn5 +9NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczW +OVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQse +cdKktISwTakzAhUAsyrDtiYTSpS/sMMCxjnC336AJpMCgYBpK7/3xvduajLBD/9v +ASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g ++eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTL +zIyMtkHf/IrPCwlM+pV/M/96YgOBhQACgYEAqQcGn9CKgzgPaguIZooTAOQdvBLM +I5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2 +PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982da +opE7zQ/NPAnJfag= +-----END PUBLIC KEY----- +`}, + {`-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAu7tvIvX6ZHrRXuZNfkR3XLHS +suCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jB +eSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C +9CeiX9pQEbEqJfkKCQIDAQAB +-----END PUBLIC KEY----- +`}, + {`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGV4ftTgVMEh/Q+OcE2s +RK0CDfSKAvcZezCiZKr077+juUUfWFvyCvRW3414F7KaWBobAmaNYRTjrFxzJ3zj +karv8TA8eMj7sryqcOC3jxHIOEw4qWgxbsW1jqnPwVGUWXF7uNUAFnwy6yJ8LJbV +mR0nhu4Y4aWnJeBa1b/VdaUujnOUNTccRM087jS0v/HYma05v2AEEP/gfps1iN8x +LReJomY4wJY1ndS0wT71Nt3dvQ3AZphWoXGeONV2bE3gMBsRv0Oo/DYDV4/VsTHl +sMV1do3gF/xAUqWawlZQkNcibME+sQqfE7gZ04hlmDATU2zmbzwuHtFiNv8mVv7O +RQIDAQAB +-----END PUBLIC KEY----- +`}, + {`---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "256-bit ED25519, converted by andrew@phaedra from OpenSSH" +AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf +---- END SSH2 PUBLIC KEY ---- +`}, + } { + _, err := CheckPublicKeyString(test.content) + assert.NoError(t, err) + } +} + func Test_calcFingerprint(t *testing.T) { testCases := []struct { name string diff --git a/models/token.go b/models/token.go index 030bff8e1b..8bd20a6916 100644 --- a/models/token.go +++ b/models/token.go @@ -9,11 +9,11 @@ import ( "crypto/subtle" "time" - gouuid "github.com/satori/go.uuid" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + + gouuid "github.com/satori/go.uuid" ) // AccessToken represents a personal access token. @@ -26,16 +26,16 @@ type AccessToken struct { TokenSalt string TokenLastEight string `xorm:"token_last_eight"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` - HasRecentActivity bool `xorm:"-"` - HasUsed bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + HasRecentActivity bool `xorm:"-"` + HasUsed bool `xorm:"-"` } // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (t *AccessToken) AfterLoad() { t.HasUsed = t.UpdatedUnix > t.CreatedUnix - t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow() + t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow() } // NewAccessToken creates new access token. diff --git a/models/topic.go b/models/topic.go index 666196ba8e..e4fda03fc4 100644 --- a/models/topic.go +++ b/models/topic.go @@ -9,7 +9,7 @@ import ( "regexp" "strings" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" ) @@ -28,8 +28,8 @@ type Topic struct { ID int64 Name string `xorm:"UNIQUE VARCHAR(25)"` RepoCount int - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // RepoTopic represents associated repositories and topics @@ -54,11 +54,38 @@ 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 +// ValidateTopic checks a topic by length and match pattern rules func ValidateTopic(topic string) bool { return len(topic) <= 35 && topicPattern.MatchString(topic) } +// SanitizeAndValidateTopics sanitizes and checks an array or topics +func SanitizeAndValidateTopics(topics []string) (validTopics []string, invalidTopics []string) { + validTopics = make([]string, 0) + mValidTopics := make(map[string]struct{}) + invalidTopics = make([]string, 0) + + for _, topic := range topics { + topic = strings.TrimSpace(strings.ToLower(topic)) + // ignore empty string + if len(topic) == 0 { + continue + } + // ignore same topic twice + if _, ok := mValidTopics[topic]; ok { + continue + } + if ValidateTopic(topic) { + validTopics = append(validTopics, topic) + mValidTopics[topic] = struct{}{} + } else { + invalidTopics = append(invalidTopics, topic) + } + } + + return validTopics, invalidTopics +} + // GetTopicByName retrieves topic by name func GetTopicByName(name string) (*Topic, error) { var topic Topic @@ -70,6 +97,54 @@ func GetTopicByName(name string) (*Topic, error) { return &topic, nil } +// addTopicByNameToRepo adds a topic name to a repo and increments the topic count. +// Returns topic after the addition +func addTopicByNameToRepo(e Engine, repoID int64, topicName string) (*Topic, error) { + var topic Topic + has, err := e.Where("name = ?", topicName).Get(&topic) + if err != nil { + return nil, err + } + if !has { + topic.Name = topicName + topic.RepoCount = 1 + if _, err := e.Insert(&topic); err != nil { + return nil, err + } + } else { + topic.RepoCount++ + if _, err := e.ID(topic.ID).Cols("repo_count").Update(&topic); err != nil { + return nil, err + } + } + + if _, err := e.Insert(&RepoTopic{ + RepoID: repoID, + TopicID: topic.ID, + }); err != nil { + return nil, err + } + + return &topic, nil +} + +// removeTopicFromRepo remove a topic from a repo and decrements the topic repo count +func removeTopicFromRepo(repoID int64, topic *Topic, e Engine) error { + topic.RepoCount-- + if _, err := e.ID(topic.ID).Cols("repo_count").Update(topic); err != nil { + return err + } + + if _, err := e.Delete(&RepoTopic{ + RepoID: repoID, + TopicID: topic.ID, + }); err != nil { + return err + } + + return nil +} + // FindTopicOptions represents the options when fdin topics type FindTopicOptions struct { RepoID int64 @@ -103,6 +178,50 @@ func FindTopics(opts *FindTopicOptions) (topics []*Topic, err error) { return topics, sess.Desc("topic.repo_count").Find(&topics) } +// GetRepoTopicByName retrives topic from name for a repo if it exist +func GetRepoTopicByName(repoID int64, topicName string) (*Topic, error) { + var cond = builder.NewCond() + var topic Topic + cond = cond.And(builder.Eq{"repo_topic.repo_id": repoID}).And(builder.Eq{"topic.name": topicName}) + sess := x.Table("topic").Where(cond) + sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") + has, err := sess.Get(&topic) + if has { + return &topic, err + } + return nil, err +} + +// AddTopic adds a topic name to a repository (if it does not already have it) +func AddTopic(repoID int64, topicName string) (*Topic, error) { + topic, err := GetRepoTopicByName(repoID, topicName) + if err != nil { + return nil, err + } + if topic != nil { + // Repo already have topic + return topic, nil + } + + return addTopicByNameToRepo(x, repoID, topicName) +} + +// DeleteTopic removes a topic name from a repository (if it has it) +func DeleteTopic(repoID int64, topicName string) (*Topic, error) { + topic, err := GetRepoTopicByName(repoID, topicName) + if err != nil { + return nil, err + } + if topic == nil { + // Repo doesn't have topic, can't be removed + return nil, nil + } + + err = removeTopicFromRepo(repoID, topic, x) + + return topic, err +} + // SaveTopics save topics to a repository func SaveTopics(repoID int64, topicNames ...string) error { topics, err := FindTopics(&FindTopicOptions{ @@ -152,40 +271,15 @@ func SaveTopics(repoID int64, topicNames ...string) error { } for _, topicName := range addedTopicNames { - var topic Topic - if has, err := sess.Where("name = ?", topicName).Get(&topic); err != nil { - return err - } else if !has { - topic.Name = topicName - topic.RepoCount = 1 - if _, err := sess.Insert(&topic); err != nil { - return err - } - } else { - topic.RepoCount++ - if _, err := sess.ID(topic.ID).Cols("repo_count").Update(&topic); err != nil { - return err - } - } - - if _, err := sess.Insert(&RepoTopic{ - RepoID: repoID, - TopicID: topic.ID, - }); err != nil { + _, err := addTopicByNameToRepo(sess, repoID, topicName) + if err != nil { return err } } for _, topic := range removeTopics { - topic.RepoCount-- - if _, err := sess.ID(topic.ID).Cols("repo_count").Update(topic); err != nil { - return err - } - - if _, err := sess.Delete(&RepoTopic{ - RepoID: repoID, - TopicID: topic.ID, - }); err != nil { + err := removeTopicFromRepo(repoID, topic, sess) + if err != nil { return err } } diff --git a/models/topic_test.go b/models/topic_test.go index 65e52afb12..c173c7bf2a 100644 --- a/models/topic_test.go +++ b/models/topic_test.go @@ -11,11 +11,15 @@ import ( ) func TestAddTopic(t *testing.T) { + totalNrOfTopics := 6 + repo1NrOfTopics := 3 + repo2NrOfTopics := 2 + assert.NoError(t, PrepareTestDatabase()) topics, err := FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 4, len(topics)) + assert.EqualValues(t, totalNrOfTopics, len(topics)) topics, err = FindTopics(&FindTopicOptions{ Limit: 2, @@ -27,33 +31,36 @@ func TestAddTopic(t *testing.T) { RepoID: 1, }) assert.NoError(t, err) - assert.EqualValues(t, 3, len(topics)) + assert.EqualValues(t, repo1NrOfTopics, len(topics)) assert.NoError(t, SaveTopics(2, "golang")) + repo2NrOfTopics = 1 topics, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 4, len(topics)) + assert.EqualValues(t, totalNrOfTopics, len(topics)) topics, err = FindTopics(&FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) - assert.EqualValues(t, 1, len(topics)) + assert.EqualValues(t, repo2NrOfTopics, len(topics)) assert.NoError(t, SaveTopics(2, "golang", "gitea")) + repo2NrOfTopics = 2 + totalNrOfTopics++ topic, err := GetTopicByName("gitea") assert.NoError(t, err) assert.EqualValues(t, 1, topic.RepoCount) topics, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 5, len(topics)) + assert.EqualValues(t, totalNrOfTopics, len(topics)) topics, err = FindTopics(&FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) - assert.EqualValues(t, 2, len(topics)) + assert.EqualValues(t, repo2NrOfTopics, len(topics)) } func TestTopicValidator(t *testing.T) { diff --git a/models/twofactor.go b/models/twofactor.go index 7f260248bc..00a465353a 100644 --- a/models/twofactor.go +++ b/models/twofactor.go @@ -16,12 +16,12 @@ import ( "fmt" "io" - "github.com/pquerna/otp/totp" - "golang.org/x/crypto/pbkdf2" - "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/pquerna/otp/totp" + "golang.org/x/crypto/pbkdf2" ) // TwoFactor represents a two-factor authentication token. @@ -31,9 +31,9 @@ type TwoFactor struct { Secret string ScratchSalt string ScratchHash string - LastUsedPasscode string `xorm:"VARCHAR(10)"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + LastUsedPasscode string `xorm:"VARCHAR(10)"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // GenerateScratchToken recreates the scratch token the user is using. diff --git a/models/u2f.go b/models/u2f.go index 1224b4a5fb..28341906fa 100644 --- a/models/u2f.go +++ b/models/u2f.go @@ -6,7 +6,7 @@ package models import ( "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/tstranex/u2f" ) @@ -17,9 +17,9 @@ type U2FRegistration struct { Name string UserID int64 `xorm:"INDEX"` Raw []byte - Counter uint32 `xorm:"BIGINT"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + Counter uint32 `xorm:"BIGINT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // TableName returns a better table name for U2FRegistration diff --git a/models/unit_tests.go b/models/unit_tests.go index 330dc5ee4e..b53302dad4 100644 --- a/models/unit_tests.go +++ b/models/unit_tests.go @@ -17,9 +17,9 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" "github.com/stretchr/testify/assert" + "github.com/unknwon/com" "gopkg.in/testfixtures.v2" "xorm.io/core" ) @@ -49,7 +49,7 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { setting.RunUser = "runuser" setting.SSH.Port = 3000 setting.SSH.Domain = "try.gitea.io" - setting.UseSQLite3 = true + setting.Database.UseSQLite3 = true setting.RepoRootPath, err = ioutil.TempDir(os.TempDir(), "repos") if err != nil { fatalTestError("TempDir: %v\n", err) @@ -65,6 +65,13 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { fatalTestError("url.Parse: %v\n", err) } + if err = removeAllWithRetry(setting.RepoRootPath); err != nil { + fatalTestError("os.RemoveAll: %v\n", err) + } + if err = com.CopyDir(filepath.Join(pathToGiteaRoot, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { + fatalTestError("com.CopyDir: %v\n", err) + } + exitStatus := m.Run() if err = removeAllWithRetry(setting.RepoRootPath); err != nil { fatalTestError("os.RemoveAll: %v\n", err) diff --git a/models/update.go b/models/update.go index 411f7d5be1..c6ea1a845e 100644 --- a/models/update.go +++ b/models/update.go @@ -10,10 +10,8 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) // env keys for git hooks need @@ -64,26 +62,8 @@ type PushUpdateOptions struct { NewCommitID string } -// PushUpdate must be called for any push actions in order to -// generates necessary push action history feeds. -func PushUpdate(branch string, opt PushUpdateOptions) error { - repo, err := pushUpdate(opt) - if err != nil { - return err - } - - pusher, err := GetUserByID(opt.PusherID) - if err != nil { - return err - } - - log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) - - go AddTestPullRequestTask(pusher, repo.ID, branch, true) - return nil -} - -func pushUpdateDeleteTag(repo *Repository, tagName string) error { +// PushUpdateDeleteTag must be called for any push actions to delete tag +func PushUpdateDeleteTag(repo *Repository, tagName string) error { rel, err := GetRelease(repo.ID, tagName) if err != nil { if IsErrReleaseNotExist(err) { @@ -107,7 +87,8 @@ func pushUpdateDeleteTag(repo *Repository, tagName string) error { return nil } -func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) error { +// PushUpdateAddTag must be called for any push actions to add tag +func PushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) error { rel, err := GetRelease(repo.ID, tagName) if err != nil && !IsErrReleaseNotExist(err) { return fmt.Errorf("GetRelease: %v", err) @@ -159,7 +140,7 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) IsDraft: false, IsPrerelease: false, IsTag: true, - CreatedUnix: util.TimeStamp(createdAt.Unix()), + CreatedUnix: timeutil.TimeStamp(createdAt.Unix()), } if author != nil { rel.PublisherID = author.ID @@ -170,7 +151,7 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) } } else { rel.Sha1 = commit.ID.String() - rel.CreatedUnix = util.TimeStamp(createdAt.Unix()) + rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix()) rel.NumCommits = commitsCount rel.IsDraft = false if rel.IsTag && author != nil { @@ -182,95 +163,3 @@ func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) } return nil } - -func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) { - isNewRef := opts.OldCommitID == git.EmptySHA - isDelRef := opts.NewCommitID == git.EmptySHA - if isNewRef && isDelRef { - return nil, fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) - } - - repoPath := RepoPath(opts.RepoUserName, opts.RepoName) - - _, err = git.NewCommand("update-server-info").RunInDir(repoPath) - if err != nil { - return nil, fmt.Errorf("Failed to call 'git update-server-info': %v", err) - } - - owner, err := GetUserByName(opts.RepoUserName) - if err != nil { - return nil, fmt.Errorf("GetUserByName: %v", err) - } - - repo, err = GetRepositoryByName(owner.ID, opts.RepoName) - if err != nil { - return nil, fmt.Errorf("GetRepositoryByName: %v", err) - } - - gitRepo, err := git.OpenRepository(repoPath) - if err != nil { - return nil, fmt.Errorf("OpenRepository: %v", err) - } - - if err = repo.UpdateSize(); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - - var commits = &PushCommits{} - if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { - // If is tag reference - tagName := opts.RefFullName[len(git.TagPrefix):] - if isDelRef { - err = pushUpdateDeleteTag(repo, tagName) - if err != nil { - return nil, fmt.Errorf("pushUpdateDeleteTag: %v", err) - } - } else { - // Clear cache for tag commit count - cache.Remove(repo.GetCommitsCountCacheKey(tagName, true)) - err = pushUpdateAddTag(repo, gitRepo, tagName) - if err != nil { - return nil, fmt.Errorf("pushUpdateAddTag: %v", err) - } - } - } else if !isDelRef { - // If is branch reference - - // Clear cache for branch commit count - cache.Remove(repo.GetCommitsCountCacheKey(opts.RefFullName[len(git.BranchPrefix):], true)) - - newCommit, err := gitRepo.GetCommit(opts.NewCommitID) - if err != nil { - return nil, fmt.Errorf("gitRepo.GetCommit: %v", err) - } - - // Push new branch. - var l *list.List - if isNewRef { - l, err = newCommit.CommitsBeforeLimit(10) - if err != nil { - return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) - } - } else { - l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) - if err != nil { - return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) - } - } - - commits = ListToPushCommits(l) - } - - if err := CommitRepoAction(CommitRepoActionOptions{ - PusherName: opts.PusherName, - RepoOwnerID: owner.ID, - RepoName: repo.Name, - RefFullName: opts.RefFullName, - OldCommitID: opts.OldCommitID, - NewCommitID: opts.NewCommitID, - Commits: commits, - }); err != nil { - return nil, fmt.Errorf("CommitRepoAction: %v", err) - } - return repo, nil -} diff --git a/models/upload.go b/models/upload.go index 65ce1223c8..65b3220c12 100644 --- a/models/upload.go +++ b/models/upload.go @@ -12,10 +12,10 @@ import ( "os" "path" - "github.com/Unknwon/com" - gouuid "github.com/satori/go.uuid" - "code.gitea.io/gitea/modules/setting" + + gouuid "github.com/satori/go.uuid" + "github.com/unknwon/com" ) // ____ ___ .__ .___ ___________.___.__ diff --git a/models/user.go b/models/user.go index fc0dfee187..030e23c383 100644 --- a/models/user.go +++ b/models/user.go @@ -17,6 +17,7 @@ import ( "image/png" "os" "path/filepath" + "strconv" "strings" "time" "unicode/utf8" @@ -29,17 +30,17 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" "github.com/go-xorm/xorm" + "github.com/unknwon/com" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" "golang.org/x/crypto/ssh" "xorm.io/builder" - "xorm.io/core" ) // UserType defines the user type @@ -58,9 +59,14 @@ const ( algoScrypt = "scrypt" algoArgon2 = "argon2" algoPbkdf2 = "pbkdf2" -) -const syncExternalUsers = "sync_external_users" + // EmailNotificationsEnabled indicates that the user would like to receive all email notifications + EmailNotificationsEnabled = "enabled" + // EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned. + EmailNotificationsOnMention = "onmention" + // EmailNotificationsDisabled indicates that the user would not like to be notified via email. + EmailNotificationsDisabled = "disabled" +) var ( // ErrUserNotKeyOwner user does not own this key error @@ -89,10 +95,11 @@ type User struct { Name string `xorm:"UNIQUE NOT NULL"` FullName string // Email is the primary email address (to be used for communication) - Email string `xorm:"NOT NULL"` - KeepEmailPrivate bool - Passwd string `xorm:"NOT NULL"` - PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"` + Email string `xorm:"NOT NULL"` + KeepEmailPrivate bool + EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"` + Passwd string `xorm:"NOT NULL"` + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'pbkdf2'"` // MustChangePassword is an attribute that determines if a user // is to change his/her password after registration. @@ -112,9 +119,9 @@ type User struct { Language string `xorm:"VARCHAR(5)"` Description string - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` - LastLoginUnix util.TimeStamp `xorm:"INDEX"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + LastLoginUnix timeutil.TimeStamp `xorm:"INDEX"` // Remember visibility choice for convenience, true for private LastRepoVisibility bool @@ -141,11 +148,13 @@ type User struct { NumRepos int // For organization - NumTeams int - NumMembers int - Teams []*Team `xorm:"-"` - Members []*User `xorm:"-"` - Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"` + NumTeams int + NumMembers int + Teams []*Team `xorm:"-"` + Members UserList `xorm:"-"` + MembersIsPublic map[int64]bool `xorm:"-"` + Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"` + RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"` // Preferences DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` @@ -191,7 +200,7 @@ func (u *User) AfterLoad() { // SetLastLogin set time to last login func (u *User) SetLastLogin() { - u.LastLoginUnix = util.TimeStampNow() + u.LastLoginUnix = timeutil.TimeStampNow() } // UpdateDiffViewStyle updates the users diff view style @@ -206,9 +215,9 @@ func (u *User) UpdateTheme(themeName string) error { return UpdateUserCols(u, "theme") } -// getEmail returns an noreply email, if the user has set to keep his +// GetEmail returns an noreply email, if the user has set to keep his // email address private, otherwise the primary email address. -func (u *User) getEmail() string { +func (u *User) GetEmail() string { if u.KeepEmailPrivate { return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress) } @@ -221,7 +230,7 @@ func (u *User) APIFormat() *api.User { ID: u.ID, UserName: u.Name, FullName: u.FullName, - Email: u.getEmail(), + Email: u.GetEmail(), AvatarURL: u.AvatarLink(), Language: u.Language, IsAdmin: u.IsAdmin, @@ -366,9 +375,20 @@ func (u *User) generateRandomAvatar(e Engine) error { return nil } -// SizedRelAvatarLink returns a relative link to the user's avatar. When -// applicable, the link is for an avatar of the indicated size (in pixels). +// SizedRelAvatarLink returns a link to the user's avatar via +// the local explore page. Function returns immediately. +// When applicable, the link is for an avatar of the indicated size (in pixels). func (u *User) SizedRelAvatarLink(size int) string { + return strings.TrimRight(setting.AppSubURL, "/") + "/user/avatar/" + u.Name + "/" + strconv.Itoa(size) +} + +// RealSizedAvatarLink returns a link to the user's avatar. When +// applicable, the link is for an avatar of the indicated size (in pixels). +// +// This function make take time to return when federated avatars +// are in use, due to a DNS lookup need +// +func (u *User) RealSizedAvatarLink(size int) string { if u.ID == -1 { return base.DefaultAvatarLink() } @@ -436,7 +456,7 @@ func (u *User) GetFollowing(page int) ([]*User, error) { func (u *User) NewGitSig() *git.Signature { return &git.Signature{ Name: u.GitName(), - Email: u.getEmail(), + Email: u.GetEmail(), When: time.Now(), } } @@ -720,6 +740,21 @@ func (u *User) IsMailable() bool { return u.IsActive } +// EmailNotifications returns the User's email notification preference +func (u *User) EmailNotifications() string { + return u.EmailNotificationsPreference +} + +// SetEmailNotifications sets the user's email notification preference +func (u *User) SetEmailNotifications(set string) error { + u.EmailNotificationsPreference = set + if err := UpdateUserCols(u, "email_notifications_preference"); err != nil { + log.Error("SetEmailNotifications: %v", err) + return err + } + return nil +} + func isUserExist(e Engine, uid int64, name string) (bool, error) { if len(name) == 0 { return false, nil @@ -753,6 +788,7 @@ func NewGhostUser() *User { var ( reservedUsernames = []string{ + "attachments", "admin", "api", "assets", @@ -785,6 +821,7 @@ var ( "robots.txt", ".", "..", + ".well-known", } reservedUserPatterns = []string{"*.keys", "*.gpg"} ) @@ -868,6 +905,7 @@ func CreateUser(u *User) (err error) { } u.HashPassword(u.Passwd) u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation + u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification u.MaxRepoCreation = -1 u.Theme = setting.UI.DefaultTheme @@ -1253,7 +1291,8 @@ func getUserByName(e Engine, name string) (*User, error) { return u, nil } -// GetUserEmailsByNames returns a list of e-mails corresponds to names. +// GetUserEmailsByNames returns a list of e-mails corresponds to names of users +// that have their email notifications set to enabled or onmention. func GetUserEmailsByNames(names []string) []string { return getUserEmailsByNames(x, names) } @@ -1265,7 +1304,7 @@ func getUserEmailsByNames(e Engine, names []string) []string { if err != nil { continue } - if u.IsMailable() { + if u.IsMailable() && u.EmailNotifications() != EmailNotificationsDisabled { mails = append(mails, u.Email) } } @@ -1409,9 +1448,7 @@ type SearchUserOptions struct { } func (opts *SearchUserOptions) toConds() builder.Cond { - - var cond = builder.NewCond() - cond = cond.And(builder.Eq{"type": opts.Type}) + var cond builder.Cond = builder.Eq{"type": opts.Type} if len(opts.Keyword) > 0 { lowerKeyword := strings.ToLower(opts.Keyword) @@ -1433,9 +1470,9 @@ func (opts *SearchUserOptions) toConds() builder.Cond { if opts.OwnerID > 0 { var exprCond builder.Cond - if DbCfg.Type == core.MYSQL { + if setting.Database.UseMySQL { exprCond = builder.Expr("org_user.org_id = user.id") - } else if DbCfg.Type == core.MSSQL { + } else if setting.Database.UseMSSQL { exprCond = builder.Expr("org_user.org_id = [user].id") } else { exprCond = builder.Expr("org_user.org_id = \"user\".id") @@ -1643,11 +1680,6 @@ func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []str // SyncExternalUsers is used to synchronize users with external authorization source func SyncExternalUsers() { - if !taskStatusTable.StartIfNotRunning(syncExternalUsers) { - return - } - defer taskStatusTable.Stop(syncExternalUsers) - log.Trace("Doing: SyncExternalUsers") ls, err := LoginSources() @@ -1680,7 +1712,12 @@ func SyncExternalUsers() { return } - sr := s.LDAP().SearchEntries() + sr, err := s.LDAP().SearchEntries() + if err != nil { + log.Error("SyncExternalUsers LDAP source failure [%s], skipped", s.Name) + continue + } + for _, su := range sr { if len(su.Username) == 0 { continue diff --git a/models/user_heatmap.go b/models/user_heatmap.go index 3fdabbbf79..3d9e0683fc 100644 --- a/models/user_heatmap.go +++ b/models/user_heatmap.go @@ -6,13 +6,13 @@ package models import ( "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) // UserHeatmapData represents the data needed to create a heatmap type UserHeatmapData struct { - Timestamp util.TimeStamp `json:"timestamp"` - Contributions int64 `json:"contributions"` + Timestamp timeutil.TimeStamp `json:"timestamp"` + Contributions int64 `json:"contributions"` } // GetUserHeatmapDataByUser returns an array of UserHeatmapData @@ -21,13 +21,13 @@ func GetUserHeatmapDataByUser(user *User) ([]*UserHeatmapData, error) { var groupBy string var groupByName = "timestamp" // We need this extra case because mssql doesn't allow grouping by alias switch { - case setting.UseSQLite3: + case setting.Database.UseSQLite3: groupBy = "strftime('%s', strftime('%Y-%m-%d', created_unix, 'unixepoch'))" - case setting.UseMySQL: + case setting.Database.UseMySQL: groupBy = "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(created_unix)))" - case setting.UsePostgreSQL: + case setting.Database.UsePostgreSQL: groupBy = "extract(epoch from date_trunc('day', to_timestamp(created_unix)))" - case setting.UseMSSQL: + case setting.Database.UseMSSQL: groupBy = "datediff(SECOND, '19700101', dateadd(DAY, 0, datediff(day, 0, dateadd(s, created_unix, '19700101'))))" groupByName = groupBy } @@ -35,7 +35,7 @@ func GetUserHeatmapDataByUser(user *User) ([]*UserHeatmapData, error) { sess := x.Select(groupBy+" AS timestamp, count(user_id) as contributions"). Table("action"). Where("user_id = ?", user.ID). - And("created_unix > ?", (util.TimeStampNow() - 31536000)) + And("created_unix > ?", (timeutil.TimeStampNow() - 31536000)) // * Heatmaps for individual users only include actions that the user themself // did. diff --git a/models/user_test.go b/models/user_test.go index 10420a143f..bcb955817c 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -5,6 +5,7 @@ package models import ( + "fmt" "math/rand" "strings" "testing" @@ -15,12 +16,66 @@ import ( "github.com/stretchr/testify/assert" ) +func TestUserIsPublicMember(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + tt := []struct { + uid int64 + orgid int64 + expected bool + }{ + {2, 3, true}, + {4, 3, false}, + {5, 6, true}, + {5, 7, false}, + } + for _, v := range tt { + t.Run(fmt.Sprintf("UserId%dIsPublicMemberOf%d", v.uid, v.orgid), func(t *testing.T) { + testUserIsPublicMember(t, v.uid, v.orgid, v.expected) + }) + } +} + +func testUserIsPublicMember(t *testing.T, uid int64, orgID int64, expected bool) { + user, err := GetUserByID(uid) + assert.NoError(t, err) + assert.Equal(t, expected, user.IsPublicMember(orgID)) +} + +func TestIsUserOrgOwner(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + tt := []struct { + uid int64 + orgid int64 + expected bool + }{ + {2, 3, true}, + {4, 3, false}, + {5, 6, true}, + {5, 7, true}, + } + for _, v := range tt { + t.Run(fmt.Sprintf("UserId%dIsOrgOwnerOf%d", v.uid, v.orgid), func(t *testing.T) { + testIsUserOrgOwner(t, v.uid, v.orgid, v.expected) + }) + } +} + +func testIsUserOrgOwner(t *testing.T, uid int64, orgID int64, expected bool) { + user, err := GetUserByID(uid) + assert.NoError(t, err) + assert.Equal(t, expected, user.IsUserOrgOwner(orgID)) +} + func TestGetUserEmailsByNames(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) // ignore none active user email assert.Equal(t, []string{"user8@example.com"}, GetUserEmailsByNames([]string{"user8", "user9"})) assert.Equal(t, []string{"user8@example.com", "user5@example.com"}, GetUserEmailsByNames([]string{"user8", "user5"})) + + assert.Equal(t, []string{"user8@example.com"}, GetUserEmailsByNames([]string{"user8", "user7"})) } func TestUser_APIFormat(t *testing.T) { @@ -83,9 +138,12 @@ func TestSearchUsers(t *testing.T) { []int64{7, 17}) testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2}, - []int64{19}) + []int64{19, 25}) - testOrgSuccess(&SearchUserOptions{Page: 4, PageSize: 2}, + testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 4, PageSize: 2}, + []int64{26}) + + testOrgSuccess(&SearchUserOptions{Page: 5, PageSize: 2}, []int64{}) // test users @@ -95,13 +153,13 @@ func TestSearchUsers(t *testing.T) { } testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1}, - []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21}) + []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24}) testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse}, []int64{9}) testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue}, - []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21}) + []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24}) testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) @@ -143,6 +201,37 @@ func TestDeleteUser(t *testing.T) { test(11) } +func TestEmailNotificationPreferences(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + for _, test := range []struct { + expected string + userID int64 + }{ + {EmailNotificationsEnabled, 1}, + {EmailNotificationsEnabled, 2}, + {EmailNotificationsOnMention, 3}, + {EmailNotificationsOnMention, 4}, + {EmailNotificationsEnabled, 5}, + {EmailNotificationsEnabled, 6}, + {EmailNotificationsDisabled, 7}, + {EmailNotificationsEnabled, 8}, + {EmailNotificationsOnMention, 9}, + } { + user := AssertExistsAndLoadBean(t, &User{ID: test.userID}).(*User) + assert.Equal(t, test.expected, user.EmailNotifications()) + + // Try all possible settings + assert.NoError(t, user.SetEmailNotifications(EmailNotificationsEnabled)) + assert.Equal(t, EmailNotificationsEnabled, user.EmailNotifications()) + + assert.NoError(t, user.SetEmailNotifications(EmailNotificationsOnMention)) + assert.Equal(t, EmailNotificationsOnMention, user.EmailNotifications()) + + assert.NoError(t, user.SetEmailNotifications(EmailNotificationsDisabled)) + assert.Equal(t, EmailNotificationsDisabled, user.EmailNotifications()) + } +} + func TestHashPasswordDeterministic(t *testing.T) { b := make([]byte, 16) rand.Read(b) diff --git a/models/userlist.go b/models/userlist.go new file mode 100644 index 0000000000..a2a4248482 --- /dev/null +++ b/models/userlist.go @@ -0,0 +1,95 @@ +// Copyright 2019 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 ( + "fmt" + + "code.gitea.io/gitea/modules/log" +) + +//UserList is a list of user. +// This type provide valuable methods to retrieve information for a group of users efficiently. +type UserList []*User + +func (users UserList) getUserIDs() []int64 { + userIDs := make([]int64, len(users)) + for _, user := range users { + userIDs = append(userIDs, user.ID) //Considering that user id are unique in the list + } + return userIDs +} + +// IsUserOrgOwner returns true if user is in the owner team of given organization. +func (users UserList) IsUserOrgOwner(orgID int64) map[int64]bool { + results := make(map[int64]bool, len(users)) + for _, user := range users { + results[user.ID] = false //Set default to false + } + ownerMaps, err := users.loadOrganizationOwners(x, orgID) + if err == nil { + for _, owner := range ownerMaps { + results[owner.UID] = true + } + } + return results +} + +func (users UserList) loadOrganizationOwners(e Engine, orgID int64) (map[int64]*TeamUser, error) { + if len(users) == 0 { + return nil, nil + } + ownerTeam, err := getOwnerTeam(e, orgID) + if err != nil { + if IsErrTeamNotExist(err) { + log.Error("Organization does not have owner team: %d", orgID) + return nil, nil + } + return nil, err + } + + userIDs := users.getUserIDs() + ownerMaps := make(map[int64]*TeamUser) + err = e.In("uid", userIDs). + And("org_id=?", orgID). + And("team_id=?", ownerTeam.ID). + Find(&ownerMaps) + if err != nil { + return nil, fmt.Errorf("find team users: %v", err) + } + return ownerMaps, nil +} + +// GetTwoFaStatus return state of 2FA enrollement +func (users UserList) GetTwoFaStatus() map[int64]bool { + results := make(map[int64]bool, len(users)) + for _, user := range users { + results[user.ID] = false //Set default to false + } + tokenMaps, err := users.loadTwoFactorStatus(x) + if err == nil { + for _, token := range tokenMaps { + results[token.UID] = true + } + } + + return results +} + +func (users UserList) loadTwoFactorStatus(e Engine) (map[int64]*TwoFactor, error) { + if len(users) == 0 { + return nil, nil + } + + userIDs := users.getUserIDs() + tokenMaps := make(map[int64]*TwoFactor, len(userIDs)) + err := e. + In("uid", userIDs). + Find(&tokenMaps) + if err != nil { + return nil, fmt.Errorf("find two factor: %v", err) + } + return tokenMaps, nil +} diff --git a/models/userlist_test.go b/models/userlist_test.go new file mode 100644 index 0000000000..ca08cc90ce --- /dev/null +++ b/models/userlist_test.go @@ -0,0 +1,90 @@ +// Copyright 2019 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 ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUserListIsPublicMember(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + tt := []struct { + orgid int64 + expected map[int64]bool + }{ + {3, map[int64]bool{2: true, 4: false}}, + {6, map[int64]bool{5: true}}, + {7, map[int64]bool{5: false}}, + {25, map[int64]bool{24: true}}, + {22, map[int64]bool{}}, + } + for _, v := range tt { + t.Run(fmt.Sprintf("IsPublicMemberOfOrdIg%d", v.orgid), func(t *testing.T) { + testUserListIsPublicMember(t, v.orgid, v.expected) + }) + } +} +func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) { + org, err := GetUserByID(orgID) + assert.NoError(t, err) + assert.NoError(t, org.GetMembers()) + assert.Equal(t, expected, org.MembersIsPublic) + +} + +func TestUserListIsUserOrgOwner(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + tt := []struct { + orgid int64 + expected map[int64]bool + }{ + {3, map[int64]bool{2: true, 4: false}}, + {6, map[int64]bool{5: true}}, + {7, map[int64]bool{5: true}}, + {25, map[int64]bool{24: false}}, // ErrTeamNotExist + {22, map[int64]bool{}}, // No member + } + for _, v := range tt { + t.Run(fmt.Sprintf("IsUserOrgOwnerOfOrdIg%d", v.orgid), func(t *testing.T) { + testUserListIsUserOrgOwner(t, v.orgid, v.expected) + }) + } +} + +func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) { + org, err := GetUserByID(orgID) + assert.NoError(t, err) + assert.NoError(t, org.GetMembers()) + assert.Equal(t, expected, org.Members.IsUserOrgOwner(orgID)) +} + +func TestUserListIsTwoFaEnrolled(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + tt := []struct { + orgid int64 + expected map[int64]bool + }{ + {3, map[int64]bool{2: false, 4: false}}, + {6, map[int64]bool{5: false}}, + {7, map[int64]bool{5: false}}, + {25, map[int64]bool{24: true}}, + {22, map[int64]bool{}}, + } + for _, v := range tt { + t.Run(fmt.Sprintf("IsTwoFaEnrolledOfOrdIg%d", v.orgid), func(t *testing.T) { + testUserListIsTwoFaEnrolled(t, v.orgid, v.expected) + }) + } +} + +func testUserListIsTwoFaEnrolled(t *testing.T, orgID int64, expected map[int64]bool) { + org, err := GetUserByID(orgID) + assert.NoError(t, err) + assert.NoError(t, org.GetMembers()) + assert.Equal(t, expected, org.Members.GetTwoFaStatus()) +} diff --git a/models/webhook.go b/models/webhook.go index e3e11e5963..f657c18792 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -19,13 +19,16 @@ import ( "strings" "time" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/sync" - "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/gobwas/glob" gouuid "github.com/satori/go.uuid" + "github.com/unknwon/com" ) // HookQueue is a global queue of web hooks @@ -83,9 +86,10 @@ type HookEvents struct { // HookEvent represents events that will delivery hook. type HookEvent struct { - PushOnly bool `json:"push_only"` - SendEverything bool `json:"send_everything"` - ChooseEvents bool `json:"choose_events"` + PushOnly bool `json:"push_only"` + SendEverything bool `json:"send_everything"` + ChooseEvents bool `json:"choose_events"` + BranchFilter string `json:"branch_filter"` HookEvents `json:"events"` } @@ -118,8 +122,8 @@ type Webhook struct { Meta string `xorm:"TEXT"` // store hook-specific attributes LastStatus HookStatus // Last delivery status - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } // AfterLoad updates the webhook object upon setting a column @@ -255,6 +259,21 @@ func (w *Webhook) EventsArray() []string { return events } +func (w *Webhook) checkBranch(branch string) bool { + if w.BranchFilter == "" || w.BranchFilter == "*" { + return true + } + + g, err := glob.Compile(w.BranchFilter) + if err != nil { + // should not really happen as BranchFilter is validated + log.Error("CheckBranch failed: %s", err) + return false + } + + return g.Match(branch) +} + // CreateWebhook creates a new web hook. func CreateWebhook(w *Webhook) error { return createWebhook(x, w) @@ -650,6 +669,25 @@ func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Pay return prepareWebhook(x, w, repo, event, p) } +// getPayloadBranch returns branch for hook event, if applicable. +func getPayloadBranch(p api.Payloader) string { + switch pp := p.(type) { + case *api.CreatePayload: + if pp.RefType == "branch" { + return pp.Ref + } + case *api.DeletePayload: + if pp.RefType == "branch" { + return pp.Ref + } + case *api.PushPayload: + if strings.HasPrefix(pp.Ref, git.BranchPrefix) { + return pp.Ref[len(git.BranchPrefix):] + } + } + return "" +} + func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error { for _, e := range w.eventCheckers() { if event == e.typ { @@ -659,6 +697,15 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, } } + // If payload has no associated branch (e.g. it's a new tag, issue, etc.), + // branch filter has no effect. + if branch := getPayloadBranch(p); branch != "" { + if !w.checkBranch(branch) { + log.Info("Branch %q doesn't match branch filter %q, skipping", branch, w.BranchFilter) + return nil + } + } + var payloader api.Payloader var err error // Use separate objects so modifications won't be made on payload on non-Gogs/Gitea type hooks. @@ -890,7 +937,6 @@ func DeliverHooks() { for _, t := range tasks { if err = t.deliver(); err != nil { log.Error("deliver: %v", err) - continue } } @@ -927,6 +973,7 @@ func InitDeliverHooks() { webhookHTTPClient = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify}, + Proxy: http.ProxyFromEnvironment, Dial: func(netw, addr string) (net.Conn, error) { conn, err := net.DialTimeout(netw, addr, timeout) if err != nil { diff --git a/models/webhook_msteams.go b/models/webhook_msteams.go index f42defbd1a..bdbcdbc9d3 100644 --- a/models/webhook_msteams.go +++ b/models/webhook_msteams.go @@ -236,6 +236,7 @@ func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) { ActivityTitle: p.Sender.FullName, ActivitySubtitle: p.Sender.UserName, ActivityImage: p.Sender.AvatarURL, + Text: text, Facts: []MSTeamsFact{ { Name: "Repository:", diff --git a/models/webhook_slack.go b/models/webhook_slack.go index f422953e41..9c179bb24a 100644 --- a/models/webhook_slack.go +++ b/models/webhook_slack.go @@ -120,8 +120,8 @@ func getSlackDeletePayload(p *api.DeletePayload, slack *SlackMeta) (*SlackPayloa // getSlackForkPayload composes Slack payload for forked by a repository. func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, error) { - baseLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) - forkLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) + baseLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName) + forkLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink) return &SlackPayload{ Channel: slack.Channel, diff --git a/models/webhook_test.go b/models/webhook_test.go index d2fb3ea6ed..343000f5b5 100644 --- a/models/webhook_test.go +++ b/models/webhook_test.go @@ -270,6 +270,40 @@ func TestPrepareWebhooks(t *testing.T) { } } +func TestPrepareWebhooksBranchFilterMatch(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + hookTasks := []*HookTask{ + {RepoID: repo.ID, HookID: 4, EventType: HookEventPush}, + } + for _, hookTask := range hookTasks { + AssertNotExistsBean(t, hookTask) + } + // this test also ensures that * doesn't handle / in any special way (like shell would) + assert.NoError(t, PrepareWebhooks(repo, HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791"})) + for _, hookTask := range hookTasks { + AssertExistsAndLoadBean(t, hookTask) + } +} + +func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + hookTasks := []*HookTask{ + {RepoID: repo.ID, HookID: 4, EventType: HookEventPush}, + } + for _, hookTask := range hookTasks { + AssertNotExistsBean(t, hookTask) + } + assert.NoError(t, PrepareWebhooks(repo, HookEventPush, &api.PushPayload{Ref: "refs/heads/fix_weird_bug"})) + + for _, hookTask := range hookTasks { + AssertNotExistsBean(t, hookTask) + } +} + // TODO TestHookTask_deliver // TODO TestDeliverHooks diff --git a/models/wiki.go b/models/wiki.go index 9ae3386333..0460e0f079 100644 --- a/models/wiki.go +++ b/models/wiki.go @@ -15,7 +15,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/sync" - "github.com/Unknwon/com" + "github.com/unknwon/com" ) var ( @@ -217,7 +217,13 @@ func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, con if err := git.Push(basePath, git.PushOptions{ Remote: "origin", Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"), - Env: PushingEnvironment(doer, repo), + Env: FullPushingEnvironment( + doer, + doer, + repo, + repo.Name+".wiki", + 0, + ), }); err != nil { log.Error("%v", err) return fmt.Errorf("Push: %v", err) diff --git a/modules/auth/admin.go b/modules/auth/admin.go index 8f8dd8f22a..6e225891dd 100644 --- a/modules/auth/admin.go +++ b/modules/auth/admin.go @@ -5,9 +5,8 @@ package auth import ( - "gopkg.in/macaron.v1" - - "github.com/go-macaron/binding" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" ) // AdminCreateUserForm form for admin to create user diff --git a/modules/auth/auth.go b/modules/auth/auth.go index 2a2ee40492..624bb15cbf 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -10,18 +10,18 @@ import ( "strings" "time" - "github.com/Unknwon/com" - "github.com/go-macaron/binding" - "github.com/go-macaron/session" - gouuid "github.com/satori/go.uuid" - "gopkg.in/macaron.v1" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/validation" + + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" + "gitea.com/macaron/session" + gouuid "github.com/satori/go.uuid" + "github.com/unknwon/com" ) // IsAPIPath if URL is an api path @@ -29,6 +29,11 @@ func IsAPIPath(url string) bool { return strings.HasPrefix(url, "/api/") } +// IsAttachmentDownload check if request is a file download (GET) with URL to an attachment +func IsAttachmentDownload(ctx *macaron.Context) bool { + return strings.HasPrefix(ctx.Req.URL.Path, "/attachments/") && ctx.Req.Method == "GET" +} + // SignedInID returns the id of signed in user. func SignedInID(ctx *macaron.Context, sess session.Store) int64 { if !models.HasEngine { @@ -36,7 +41,7 @@ func SignedInID(ctx *macaron.Context, sess session.Store) int64 { } // Check access token. - if IsAPIPath(ctx.Req.URL.Path) { + if IsAPIPath(ctx.Req.URL.Path) || IsAttachmentDownload(ctx) { tokenSHA := ctx.Query("token") if len(tokenSHA) == 0 { tokenSHA = ctx.Query("access_token") @@ -68,7 +73,7 @@ func SignedInID(ctx *macaron.Context, sess session.Store) int64 { } return 0 } - t.UpdatedUnix = util.TimeStampNow() + t.UpdatedUnix = timeutil.TimeStampNow() if err = models.UpdateAccessToken(t); err != nil { log.Error("UpdateAccessToken: %v", err) } @@ -210,7 +215,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool) return nil, false } } - token.UpdatedUnix = util.TimeStampNow() + token.UpdatedUnix = timeutil.TimeStampNow() if err = models.UpdateAccessToken(token); err != nil { log.Error("UpdateAccessToken: %v", err) } @@ -305,6 +310,10 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro } data["HasError"] = true + // If the field with name errs[0].FieldNames[0] is not found in form + // somehow, some code later on will panic on Data["ErrorMsg"].(string). + // So initialize it to some default. + data["ErrorMsg"] = l.Tr("form.unknown_error") AssignForm(f, data) typ := reflect.TypeOf(f) @@ -315,16 +324,9 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro val = val.Elem() } - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - + if field, ok := typ.FieldByName(errs[0].FieldNames[0]); ok { fieldName := field.Tag.Get("form") - // Allow ignored fields in the struct - if fieldName == "-" { - continue - } - - if errs[0].FieldNames[0] == field.Name { + if fieldName != "-" { data["Err_"+field.Name] = true trName := field.Tag.Get("locale") @@ -355,6 +357,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro data["ErrorMsg"] = trName + l.Tr("form.url_error") case binding.ERR_INCLUDE: data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) + case validation.ErrGlobPattern: + data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) default: data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification } diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index e44ef58f8e..358472a385 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -5,8 +5,8 @@ package auth import ( - "github.com/go-macaron/binding" - "gopkg.in/macaron.v1" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" ) // AuthenticationForm form for authentication diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index ddeaf12430..ed83a77e12 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -308,12 +308,12 @@ func (ls *Source) UsePagedSearch() bool { } // SearchEntries : search an LDAP source for all users matching userFilter -func (ls *Source) SearchEntries() []*SearchResult { +func (ls *Source) SearchEntries() ([]*SearchResult, error) { l, err := dial(ls) if err != nil { log.Error("LDAP Connect error, %s:%v", ls.Host, err) ls.Enabled = false - return nil + return nil, err } defer l.Close() @@ -321,7 +321,7 @@ func (ls *Source) SearchEntries() []*SearchResult { err := l.Bind(ls.BindDN, ls.BindPassword) if err != nil { log.Debug("Failed to bind as BindDN[%s]: %v", ls.BindDN, err) - return nil + return nil, err } log.Trace("Bound as BindDN %s", ls.BindDN) } else { @@ -350,7 +350,7 @@ func (ls *Source) SearchEntries() []*SearchResult { } if err != nil { log.Error("LDAP Search failed unexpectedly! (%v)", err) - return nil + return nil, err } result := make([]*SearchResult, len(sr.Entries)) @@ -368,5 +368,5 @@ func (ls *Source) SearchEntries() []*SearchResult { } } - return result + return result, nil } diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index a2d7116211..242254e600 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -19,9 +19,10 @@ import ( "github.com/markbates/goth/providers/discord" "github.com/markbates/goth/providers/dropbox" "github.com/markbates/goth/providers/facebook" + "github.com/markbates/goth/providers/gitea" "github.com/markbates/goth/providers/github" "github.com/markbates/goth/providers/gitlab" - "github.com/markbates/goth/providers/gplus" + "github.com/markbates/goth/providers/google" "github.com/markbates/goth/providers/openidConnect" "github.com/markbates/goth/providers/twitter" "github.com/satori/go.uuid" @@ -165,8 +166,8 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo } } provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") - case "gplus": - provider = gplus.New(clientID, clientSecret, callbackURL, "email") + case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work + provider = google.New(clientID, clientSecret, callbackURL) case "openidConnect": if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) @@ -175,6 +176,22 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo provider = twitter.NewAuthenticate(clientID, clientSecret, callbackURL) case "discord": provider = discord.New(clientID, clientSecret, callbackURL, discord.ScopeIdentify, discord.ScopeEmail) + case "gitea": + authURL := gitea.AuthURL + tokenURL := gitea.TokenURL + profileURL := gitea.ProfileURL + if customURLMapping != nil { + if len(customURLMapping.AuthURL) > 0 { + authURL = customURLMapping.AuthURL + } + if len(customURLMapping.TokenURL) > 0 { + tokenURL = customURLMapping.TokenURL + } + if len(customURLMapping.ProfileURL) > 0 { + profileURL = customURLMapping.ProfileURL + } + } + provider = gitea.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL) } // always set the name if provider is created so we can support multiple setups of 1 provider @@ -192,6 +209,8 @@ func GetDefaultTokenURL(provider string) string { return github.TokenURL case "gitlab": return gitlab.TokenURL + case "gitea": + return gitea.TokenURL } return "" } @@ -203,6 +222,8 @@ func GetDefaultAuthURL(provider string) string { return github.AuthURL case "gitlab": return gitlab.AuthURL + case "gitea": + return gitea.AuthURL } return "" } @@ -214,6 +235,8 @@ func GetDefaultProfileURL(provider string) string { return github.ProfileURL case "gitlab": return gitlab.ProfileURL + case "gitea": + return gitea.ProfileURL } return "" } diff --git a/modules/auth/openid/openid.go b/modules/auth/openid/openid.go index ffe0aba2b6..40f38c2d2e 100644 --- a/modules/auth/openid/openid.go +++ b/modules/auth/openid/openid.go @@ -5,8 +5,9 @@ package openid import ( - "github.com/yohcop/openid-go" "time" + + "github.com/yohcop/openid-go" ) // For the demo, we use in-memory infinite storage nonce and discovery diff --git a/modules/auth/org.go b/modules/auth/org.go index 31c46eb199..9384d3571d 100644 --- a/modules/auth/org.go +++ b/modules/auth/org.go @@ -9,8 +9,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/structs" - "github.com/go-macaron/binding" - "gopkg.in/macaron.v1" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" ) // ________ .__ __ .__ @@ -33,13 +33,14 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind // UpdateOrgSettingForm form for updating organization settings type UpdateOrgSettingForm struct { - Name string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"` - FullName string `binding:"MaxSize(100)"` - Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` - Location string `binding:"MaxSize(50)"` - Visibility structs.VisibleType - MaxRepoCreation int + Name string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"` + FullName string `binding:"MaxSize(100)"` + Description string `binding:"MaxSize(255)"` + Website string `binding:"ValidUrl;MaxSize(255)"` + Location string `binding:"MaxSize(50)"` + Visibility structs.VisibleType + MaxRepoCreation int + RepoAdminChangeTeamAccess bool } // Validate validates the fields diff --git a/modules/auth/repo_branch_form.go b/modules/auth/repo_branch_form.go index 57e63741aa..a4baabe354 100644 --- a/modules/auth/repo_branch_form.go +++ b/modules/auth/repo_branch_form.go @@ -5,8 +5,8 @@ package auth import ( - "github.com/go-macaron/binding" - macaron "gopkg.in/macaron.v1" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" ) // NewBranchForm form for creating a new branch diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 0333c3c926..8d10fc1570 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -13,9 +13,9 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/utils" - "github.com/Unknwon/com" - "github.com/go-macaron/binding" - macaron "gopkg.in/macaron.v1" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" + "github.com/unknwon/com" ) // _______________________________________ _________.______________________ _______________.___. @@ -33,6 +33,7 @@ type CreateRepoForm struct { Description string `binding:"MaxSize(255)"` AutoInit bool Gitignores string + IssueLabels string License string Readme string } @@ -98,13 +99,15 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { // RepoSettingForm form for changing repository settings type RepoSettingForm struct { - RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` - Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` - Interval string - MirrorAddress string - Private bool - EnablePrune bool + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Description string `binding:"MaxSize(255)"` + Website string `binding:"ValidUrl;MaxSize(255)"` + Interval string + MirrorAddress string + MirrorUsername string + MirrorPassword string + Private bool + EnablePrune bool // Advanced settings EnableWiki bool @@ -152,6 +155,8 @@ type ProtectBranchForm struct { EnableMergeWhitelist bool MergeWhitelistUsers string MergeWhitelistTeams string + EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` + StatusCheckContexts []string RequiredApprovals int64 ApprovalsWhitelistUsers string ApprovalsWhitelistTeams string @@ -182,6 +187,7 @@ type WebhookForm struct { PullRequest bool Repository bool Active bool + BranchFilter string `binding:"GlobPattern"` } // PushOnly if the hook will be triggered when push diff --git a/modules/auth/repo_form_test.go b/modules/auth/repo_form_test.go index a3369b006e..6bad5d50ba 100644 --- a/modules/auth/repo_form_test.go +++ b/modules/auth/repo_form_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index c117d038be..8ceb961d24 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/modules/setting" - "github.com/go-macaron/binding" - macaron "gopkg.in/macaron.v1" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" ) // InstallForm form for installation page diff --git a/modules/auth/user_form_auth_openid.go b/modules/auth/user_form_auth_openid.go index 275850d09a..b355825483 100644 --- a/modules/auth/user_form_auth_openid.go +++ b/modules/auth/user_form_auth_openid.go @@ -5,8 +5,8 @@ package auth import ( - "github.com/go-macaron/binding" - "gopkg.in/macaron.v1" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" ) // SignInOpenIDForm form for signing in with OpenID diff --git a/modules/base/tool.go b/modules/base/tool.go index 4893abff71..60d96d57e3 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -5,7 +5,6 @@ package base import ( - "bytes" "crypto/md5" "crypto/rand" "crypto/sha1" @@ -13,7 +12,6 @@ import ( "encoding/base64" "encoding/hex" "fmt" - "html/template" "io" "math" "net/http" @@ -26,21 +24,14 @@ import ( "strings" "time" "unicode" - "unicode/utf8" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" - "github.com/Unknwon/i18n" - "github.com/gogits/chardet" + "github.com/unknwon/com" ) -// UTF8BOM is the utf-8 byte-order marker -var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'} - // EncodeMD5 encodes string to md5 hex value. func EncodeMD5(str string) string { m := md5.New() @@ -68,49 +59,6 @@ func ShortSha(sha1 string) string { return TruncateString(sha1, 10) } -// DetectEncoding detect the encoding of content -func DetectEncoding(content []byte) (string, error) { - if utf8.Valid(content) { - log.Debug("Detected encoding: utf-8 (fast)") - return "UTF-8", nil - } - - textDetector := chardet.NewTextDetector() - var detectContent []byte - if len(content) < 1024 { - // Check if original content is valid - if _, err := textDetector.DetectBest(content); err != nil { - return "", err - } - times := 1024 / len(content) - detectContent = make([]byte, 0, times*len(content)) - for i := 0; i < times; i++ { - detectContent = append(detectContent, content...) - } - } else { - detectContent = content - } - result, err := textDetector.DetectBest(detectContent) - if err != nil { - return "", err - } - if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 { - log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset) - return setting.Repository.AnsiCharset, err - } - - log.Debug("Detected encoding: %s", result.Charset) - return result.Charset, err -} - -// RemoveBOMIfPresent removes a UTF-8 BOM from a []byte -func RemoveBOMIfPresent(content []byte) []byte { - if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) { - return content[3:] - } - return content -} - // BasicAuthDecode decode basic auth string func BasicAuthDecode(encoded string) (string, string, error) { s, err := base64.StdEncoding.DecodeString(encoded) @@ -266,154 +214,6 @@ func AvatarLink(email string) string { return SizedAvatarLink(email, DefaultAvatarSize) } -// Seconds-based time units -const ( - Minute = 60 - Hour = 60 * Minute - Day = 24 * Hour - Week = 7 * Day - Month = 30 * Day - Year = 12 * Month -) - -func computeTimeDiff(diff int64, lang string) (int64, string) { - diffStr := "" - switch { - case diff <= 0: - diff = 0 - diffStr = i18n.Tr(lang, "tool.now") - case diff < 2: - diff = 0 - diffStr = i18n.Tr(lang, "tool.1s") - case diff < 1*Minute: - diffStr = i18n.Tr(lang, "tool.seconds", diff) - diff = 0 - - case diff < 2*Minute: - diff -= 1 * Minute - diffStr = i18n.Tr(lang, "tool.1m") - case diff < 1*Hour: - diffStr = i18n.Tr(lang, "tool.minutes", diff/Minute) - diff -= diff / Minute * Minute - - case diff < 2*Hour: - diff -= 1 * Hour - diffStr = i18n.Tr(lang, "tool.1h") - case diff < 1*Day: - diffStr = i18n.Tr(lang, "tool.hours", diff/Hour) - diff -= diff / Hour * Hour - - case diff < 2*Day: - diff -= 1 * Day - diffStr = i18n.Tr(lang, "tool.1d") - case diff < 1*Week: - diffStr = i18n.Tr(lang, "tool.days", diff/Day) - diff -= diff / Day * Day - - case diff < 2*Week: - diff -= 1 * Week - diffStr = i18n.Tr(lang, "tool.1w") - case diff < 1*Month: - diffStr = i18n.Tr(lang, "tool.weeks", diff/Week) - diff -= diff / Week * Week - - case diff < 2*Month: - diff -= 1 * Month - diffStr = i18n.Tr(lang, "tool.1mon") - case diff < 1*Year: - diffStr = i18n.Tr(lang, "tool.months", diff/Month) - diff -= diff / Month * Month - - case diff < 2*Year: - diff -= 1 * Year - diffStr = i18n.Tr(lang, "tool.1y") - default: - diffStr = i18n.Tr(lang, "tool.years", diff/Year) - diff -= (diff / Year) * Year - } - return diff, diffStr -} - -// MinutesToFriendly returns a user friendly string with number of minutes -// converted to hours and minutes. -func MinutesToFriendly(minutes int, lang string) string { - duration := time.Duration(minutes) * time.Minute - return TimeSincePro(time.Now().Add(-duration), lang) -} - -// TimeSincePro calculates the time interval and generate full user-friendly string. -func TimeSincePro(then time.Time, lang string) string { - return timeSincePro(then, time.Now(), lang) -} - -func timeSincePro(then, now time.Time, lang string) string { - diff := now.Unix() - then.Unix() - - if then.After(now) { - return i18n.Tr(lang, "tool.future") - } - if diff == 0 { - return i18n.Tr(lang, "tool.now") - } - - var timeStr, diffStr string - for { - if diff == 0 { - break - } - - diff, diffStr = computeTimeDiff(diff, lang) - timeStr += ", " + diffStr - } - return strings.TrimPrefix(timeStr, ", ") -} - -func timeSince(then, now time.Time, lang string) string { - return timeSinceUnix(then.Unix(), now.Unix(), lang) -} - -func timeSinceUnix(then, now int64, lang string) string { - lbl := "tool.ago" - diff := now - then - if then > now { - lbl = "tool.from_now" - diff = then - now - } - if diff <= 0 { - return i18n.Tr(lang, "tool.now") - } - - _, diffStr := computeTimeDiff(diff, lang) - return i18n.Tr(lang, lbl, diffStr) -} - -// RawTimeSince retrieves i18n key of time since t -func RawTimeSince(t time.Time, lang string) string { - return timeSince(t, time.Now(), lang) -} - -// TimeSince calculates the time interval and generate user-friendly string. -func TimeSince(then time.Time, lang string) template.HTML { - return htmlTimeSince(then, time.Now(), lang) -} - -func htmlTimeSince(then, now time.Time, lang string) template.HTML { - return template.HTML(fmt.Sprintf(`%s`, - then.Format(setting.TimeFormat), - timeSince(then, now, lang))) -} - -// TimeSinceUnix calculates the time interval and generate user-friendly string. -func TimeSinceUnix(then util.TimeStamp, lang string) template.HTML { - return htmlTimeSinceUnix(then, util.TimeStamp(time.Now().Unix()), lang) -} - -func htmlTimeSinceUnix(then, now util.TimeStamp, lang string) template.HTML { - return template.HTML(fmt.Sprintf(`%s`, - then.Format(setting.TimeFormat), - timeSinceUnix(int64(then), int64(now), lang))) -} - // Storage space size types const ( Byte = 1 diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index fa61e5dfb1..3aa86c5cbf 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -2,43 +2,13 @@ package base import ( "net/url" - "os" "testing" - "time" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/i18n" - macaroni18n "github.com/go-macaron/i18n" "github.com/stretchr/testify/assert" ) -var BaseDate time.Time - -// time durations -const ( - DayDur = 24 * time.Hour - WeekDur = 7 * DayDur - MonthDur = 30 * DayDur - YearDur = 12 * MonthDur -) - -func TestMain(m *testing.M) { - // setup - macaroni18n.I18n(macaroni18n.Options{ - Directory: "../../options/locale/", - DefaultLang: "en-US", - Langs: []string{"en-US"}, - Names: []string{"english"}, - }) - BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) - - // run the tests - retVal := m.Run() - - os.Exit(retVal) -} - func TestEncodeMD5(t *testing.T) { assert.Equal(t, "3858f62230ac3c915f300c664312c63f", @@ -64,42 +34,6 @@ func TestShortSha(t *testing.T) { assert.Equal(t, "veryverylo", ShortSha("veryverylong")) } -func TestDetectEncoding(t *testing.T) { - testSuccess := func(b []byte, expected string) { - encoding, err := DetectEncoding(b) - assert.NoError(t, err) - assert.Equal(t, expected, encoding) - } - // utf-8 - b := []byte("just some ascii") - testSuccess(b, "UTF-8") - - // utf-8-sig: "hey" (with BOM) - b = []byte{0xef, 0xbb, 0xbf, 0x68, 0x65, 0x79} - testSuccess(b, "UTF-8") - - // utf-16: "hey" - b = []byte{0xff, 0xfe, 0x68, 0x00, 0x65, 0x00, 0x79, 0x00, 0xf4, 0x01} - testSuccess(b, "UTF-16LE") - - // iso-8859-1: dcor - b = []byte{0x44, 0xe9, 0x63, 0x6f, 0x72, 0x0a} - encoding, err := DetectEncoding(b) - assert.NoError(t, err) - // due to a race condition in `chardet` library, it could either detect - // "ISO-8859-1" or "IS0-8859-2" here. Technically either is correct, so - // we accept either. - assert.Contains(t, encoding, "ISO-8859") - - setting.Repository.AnsiCharset = "placeholder" - testSuccess(b, "placeholder") - - // invalid bytes - b = []byte{0xfa} - _, err = DetectEncoding(b) - assert.Error(t, err) -} - func TestBasicAuthDecode(t *testing.T) { _, _, err := BasicAuthDecode("?") assert.Equal(t, "illegal base64 data at input byte 0", err.Error()) @@ -167,125 +101,6 @@ func TestAvatarLink(t *testing.T) { ) } -func TestComputeTimeDiff(t *testing.T) { - // test that for each offset in offsets, - // computeTimeDiff(base + offset) == (offset, str) - test := func(base int64, str string, offsets ...int64) { - for _, offset := range offsets { - diff, diffStr := computeTimeDiff(base+offset, "en") - assert.Equal(t, offset, diff) - assert.Equal(t, str, diffStr) - } - } - test(0, "now", 0) - test(1, "1 second", 0) - test(2, "2 seconds", 0) - test(Minute, "1 minute", 0, 1, 30, Minute-1) - test(2*Minute, "2 minutes", 0, Minute-1) - test(Hour, "1 hour", 0, 1, Hour-1) - test(5*Hour, "5 hours", 0, Hour-1) - test(Day, "1 day", 0, 1, Day-1) - test(5*Day, "5 days", 0, Day-1) - test(Week, "1 week", 0, 1, Week-1) - test(3*Week, "3 weeks", 0, 4*Day+25000) - test(Month, "1 month", 0, 1, Month-1) - test(10*Month, "10 months", 0, Month-1) - test(Year, "1 year", 0, Year-1) - test(3*Year, "3 years", 0, Year-1) -} - -func TestMinutesToFriendly(t *testing.T) { - // test that a number of minutes yields the expected string - test := func(expected string, minutes int) { - actual := MinutesToFriendly(minutes, "en") - assert.Equal(t, expected, actual) - } - test("1 minute", 1) - test("2 minutes", 2) - test("1 hour", 60) - test("1 hour, 1 minute", 61) - test("1 hour, 2 minutes", 62) - test("2 hours", 120) -} - -func TestTimeSince(t *testing.T) { - assert.Equal(t, "now", timeSince(BaseDate, BaseDate, "en")) - - // test that each diff in `diffs` yields the expected string - test := func(expected string, diffs ...time.Duration) { - for _, diff := range diffs { - actual := timeSince(BaseDate, BaseDate.Add(diff), "en") - assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual) - actual = timeSince(BaseDate.Add(diff), BaseDate, "en") - assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual) - } - } - test("1 second", time.Second, time.Second+50*time.Millisecond) - test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond) - test("1 minute", time.Minute, time.Minute+30*time.Second) - test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second) - test("1 hour", time.Hour, time.Hour+30*time.Minute) - test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute) - test("1 day", DayDur, DayDur+12*time.Hour) - test("2 days", 2*DayDur, 2*DayDur+12*time.Hour) - test("1 week", WeekDur, WeekDur+3*DayDur) - test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur) - test("1 month", MonthDur, MonthDur+15*DayDur) - test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur) - test("1 year", YearDur, YearDur+6*MonthDur) - test("2 years", 2*YearDur, 2*YearDur+6*MonthDur) -} - -func TestTimeSincePro(t *testing.T) { - assert.Equal(t, "now", timeSincePro(BaseDate, BaseDate, "en")) - - // test that a difference of `diff` yields the expected string - test := func(expected string, diff time.Duration) { - actual := timeSincePro(BaseDate, BaseDate.Add(diff), "en") - assert.Equal(t, expected, actual) - assert.Equal(t, "future", timeSincePro(BaseDate.Add(diff), BaseDate, "en")) - } - test("1 second", time.Second) - test("2 seconds", 2*time.Second) - test("1 minute", time.Minute) - test("1 minute, 1 second", time.Minute+time.Second) - test("1 minute, 59 seconds", time.Minute+59*time.Second) - test("2 minutes", 2*time.Minute) - test("1 hour", time.Hour) - test("1 hour, 1 second", time.Hour+time.Second) - test("1 hour, 59 minutes, 59 seconds", time.Hour+59*time.Minute+59*time.Second) - test("2 hours", 2*time.Hour) - test("1 day", DayDur) - test("1 day, 23 hours, 59 minutes, 59 seconds", - DayDur+23*time.Hour+59*time.Minute+59*time.Second) - test("2 days", 2*DayDur) - test("1 week", WeekDur) - test("2 weeks", 2*WeekDur) - test("1 month", MonthDur) - test("3 months", 3*MonthDur) - test("1 year", YearDur) - test("2 years, 3 months, 1 week, 2 days, 4 hours, 12 minutes, 17 seconds", - 2*YearDur+3*MonthDur+WeekDur+2*DayDur+4*time.Hour+ - 12*time.Minute+17*time.Second) -} - -func TestHtmlTimeSince(t *testing.T) { - setting.TimeFormat = time.UnixDate - // test that `diff` yields a result containing `expected` - test := func(expected string, diff time.Duration) { - actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), "en") - assert.Contains(t, actual, `title="Sat Jan 1 00:00:00 UTC 2000"`) - assert.Contains(t, actual, expected) - } - test("1 second", time.Second) - test("3 minutes", 3*time.Minute+5*time.Second) - test("1 day", DayDur+18*time.Hour) - test("1 week", WeekDur+6*DayDur) - test("3 months", 3*MonthDur+3*WeekDur) - test("2 years", 2*YearDur) - test("3 years", 3*YearDur+11*MonthDur+4*WeekDur) -} - func TestFileSize(t *testing.T) { var size int64 = 512 assert.Equal(t, "512B", FileSize(size)) diff --git a/modules/cache/cache.go b/modules/cache/cache.go index a7412f109f..ceb5772fcf 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -10,7 +10,7 @@ import ( "code.gitea.io/gitea/modules/setting" - mc "github.com/go-macaron/cache" + mc "gitea.com/macaron/cache" ) var conn mc.Cache diff --git a/modules/charset/charset.go b/modules/charset/charset.go new file mode 100644 index 0000000000..d3c5ac0b42 --- /dev/null +++ b/modules/charset/charset.go @@ -0,0 +1,152 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package charset + +import ( + "bytes" + "fmt" + "unicode/utf8" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/gogits/chardet" + "golang.org/x/net/html/charset" + "golang.org/x/text/transform" +) + +// UTF8BOM is the utf-8 byte-order marker +var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'} + +// ToUTF8WithErr converts content to UTF8 encoding +func ToUTF8WithErr(content []byte) (string, error) { + charsetLabel, err := DetectEncoding(content) + if err != nil { + return "", err + } else if charsetLabel == "UTF-8" { + return string(RemoveBOMIfPresent(content)), nil + } + + encoding, _ := charset.Lookup(charsetLabel) + if encoding == nil { + return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel) + } + + // If there is an error, we concatenate the nicely decoded part and the + // original left over. This way we won't lose much data. + result, n, err := transform.Bytes(encoding.NewDecoder(), content) + if err != nil { + result = append(result, content[n:]...) + } + + result = RemoveBOMIfPresent(result) + + return string(result), err +} + +// ToUTF8WithFallback detects the encoding of content and coverts to UTF-8 if possible +func ToUTF8WithFallback(content []byte) []byte { + charsetLabel, err := DetectEncoding(content) + if err != nil || charsetLabel == "UTF-8" { + return RemoveBOMIfPresent(content) + } + + encoding, _ := charset.Lookup(charsetLabel) + if encoding == nil { + return content + } + + // If there is an error, we concatenate the nicely decoded part and the + // original left over. This way we won't lose data. + result, n, err := transform.Bytes(encoding.NewDecoder(), content) + if err != nil { + return append(result, content[n:]...) + } + + return RemoveBOMIfPresent(result) +} + +// ToUTF8 converts content to UTF8 encoding and ignore error +func ToUTF8(content string) string { + res, _ := ToUTF8WithErr([]byte(content)) + return res +} + +// ToUTF8DropErrors makes sure the return string is valid utf-8; attempts conversion if possible +func ToUTF8DropErrors(content []byte) []byte { + charsetLabel, err := DetectEncoding(content) + if err != nil || charsetLabel == "UTF-8" { + return RemoveBOMIfPresent(content) + } + + encoding, _ := charset.Lookup(charsetLabel) + if encoding == nil { + return content + } + + // We ignore any non-decodable parts from the file. + // Some parts might be lost + var decoded []byte + decoder := encoding.NewDecoder() + idx := 0 + for { + result, n, err := transform.Bytes(decoder, content[idx:]) + decoded = append(decoded, result...) + if err == nil { + break + } + decoded = append(decoded, ' ') + idx = idx + n + 1 + if idx >= len(content) { + break + } + } + + return RemoveBOMIfPresent(decoded) +} + +// RemoveBOMIfPresent removes a UTF-8 BOM from a []byte +func RemoveBOMIfPresent(content []byte) []byte { + if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) { + return content[3:] + } + return content +} + +// DetectEncoding detect the encoding of content +func DetectEncoding(content []byte) (string, error) { + if utf8.Valid(content) { + log.Debug("Detected encoding: utf-8 (fast)") + return "UTF-8", nil + } + + textDetector := chardet.NewTextDetector() + var detectContent []byte + if len(content) < 1024 { + // Check if original content is valid + if _, err := textDetector.DetectBest(content); err != nil { + return "", err + } + times := 1024 / len(content) + detectContent = make([]byte, 0, times*len(content)) + for i := 0; i < times; i++ { + detectContent = append(detectContent, content...) + } + } else { + detectContent = content + } + result, err := textDetector.DetectBest(detectContent) + if err != nil { + return "", err + } + // FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument + if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 { + log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset) + return setting.Repository.AnsiCharset, err + } + + log.Debug("Detected encoding: %s", result.Charset) + return result.Charset, err +} diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go new file mode 100644 index 0000000000..3c77f12789 --- /dev/null +++ b/modules/charset/charset_test.go @@ -0,0 +1,251 @@ +// Copyright 2019 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 charset + +import ( + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveBOMIfPresent(t *testing.T) { + res := RemoveBOMIfPresent([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + + res = RemoveBOMIfPresent([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) +} + +func TestToUTF8WithErr(t *testing.T) { + var res string + var err error + + // Note: golang compiler seems so behave differently depending on the current + // locale, so some conversions might behave differently. For that reason, we don't + // depend on particular conversions but in expected behaviors. + + res, err = ToUTF8WithErr([]byte{0x41, 0x42, 0x43}) + assert.NoError(t, err) + assert.Equal(t, "ABC", res) + + // "áéíóú" + res, err = ToUTF8WithErr([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.NoError(t, err) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) + + // "áéíóú" + res, err = ToUTF8WithErr([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, + 0xc3, 0xba}) + assert.NoError(t, err) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) + + res, err = ToUTF8WithErr([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e}) + assert.NoError(t, err) + stringMustStartWith(t, "Hola,", res) + stringMustEndWith(t, "AAA.", res) + + res, err = ToUTF8WithErr([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e}) + assert.NoError(t, err) + stringMustStartWith(t, "Hola,", res) + stringMustEndWith(t, "AAA.", res) + + res, err = ToUTF8WithErr([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e}) + assert.NoError(t, err) + stringMustStartWith(t, "Hola,", res) + stringMustEndWith(t, "AAA.", res) + + // Japanese (Shift-JIS) + // 日属秘ぞしちゅ。 + res, err = ToUTF8WithErr([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, + 0xBF, 0x82, 0xE3, 0x81, 0x42}) + assert.NoError(t, err) + assert.Equal(t, []byte{0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, + 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82}, + []byte(res)) + + res, err = ToUTF8WithErr([]byte{0x00, 0x00, 0x00, 0x00}) + assert.NoError(t, err) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res)) +} + +func TestToUTF8WithFallback(t *testing.T) { + // "ABC" + res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}) + assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) + + // "áéíóú" + res = ToUTF8WithFallback([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + + // UTF8 BOM + "áéíóú" + res = ToUTF8WithFallback([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + + // "Hola, así cómo ños" + res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}) + assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, + 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73}, res) + + // "Hola, así cómo " + minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} + + res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}) + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + assert.Equal(t, minmatch, res[0:len(minmatch)]) + + res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}) + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + assert.Equal(t, minmatch, res[0:len(minmatch)]) + + // Japanese (Shift-JIS) + // "日属秘ぞしちゅ。" + res = ToUTF8WithFallback([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}) + assert.Equal(t, []byte{0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, + 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82}, res) + + res = ToUTF8WithFallback([]byte{0x00, 0x00, 0x00, 0x00}) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) +} + +func TestToUTF8(t *testing.T) { + + // Note: golang compiler seems so behave differently depending on the current + // locale, so some conversions might behave differently. For that reason, we don't + // depend on particular conversions but in expected behaviors. + + res := ToUTF8(string([]byte{0x41, 0x42, 0x43})) + assert.Equal(t, "ABC", res) + + // "áéíóú" + res = ToUTF8(string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) + + // BOM + "áéíóú" + res = ToUTF8(string([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, + 0xc3, 0xba})) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) + + // Latin1 + // Hola, así cómo ños + res = ToUTF8(string([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73})) + assert.Equal(t, []byte{0x48, 0x6f, 0x6c, 0x61, 0x2c, 0x20, 0x61, 0x73, 0xc3, 0xad, 0x20, 0x63, + 0xc3, 0xb3, 0x6d, 0x6f, 0x20, 0xc3, 0xb1, 0x6f, 0x73}, []byte(res)) + + // Latin1 + // Hola, así cómo \x07ños + res = ToUTF8(string([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73})) + // Hola, + bytesMustStartWith(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C}, []byte(res)) + + // This test FAILS + // res = ToUTF8("Hola, así cómo \x81ños") + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + // assert.Regexp(t, "^Hola, así cómo", res) + + // Japanese (Shift-JIS) + // 日属秘ぞしちゅ。 + res = ToUTF8(string([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, + 0xBF, 0x82, 0xE3, 0x81, 0x42})) + assert.Equal(t, []byte{0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, + 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82}, + []byte(res)) + + res = ToUTF8("\x00\x00\x00\x00") + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res)) +} + +func TestToUTF8DropErrors(t *testing.T) { + // "ABC" + res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43}) + assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) + + // "áéíóú" + res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + + // UTF8 BOM + "áéíóú" + res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + + // "Hola, así cómo ños" + res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}) + assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73}, res) + + // "Hola, así cómo " + minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} + + res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}) + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + assert.Equal(t, minmatch, res[0:len(minmatch)]) + + res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}) + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + assert.Equal(t, minmatch, res[0:len(minmatch)]) + + // Japanese (Shift-JIS) + // "日属秘ぞしちゅ。" + res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}) + assert.Equal(t, []byte{0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, + 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82}, res) + + res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00}) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) +} + +func TestDetectEncoding(t *testing.T) { + testSuccess := func(b []byte, expected string) { + encoding, err := DetectEncoding(b) + assert.NoError(t, err) + assert.Equal(t, expected, encoding) + } + // utf-8 + b := []byte("just some ascii") + testSuccess(b, "UTF-8") + + // utf-8-sig: "hey" (with BOM) + b = []byte{0xef, 0xbb, 0xbf, 0x68, 0x65, 0x79} + testSuccess(b, "UTF-8") + + // utf-16: "hey" + b = []byte{0xff, 0xfe, 0x68, 0x00, 0x65, 0x00, 0x79, 0x00, 0xf4, 0x01} + testSuccess(b, "UTF-16LE") + + // iso-8859-1: dcor + b = []byte{0x44, 0xe9, 0x63, 0x6f, 0x72, 0x0a} + encoding, err := DetectEncoding(b) + assert.NoError(t, err) + // due to a race condition in `chardet` library, it could either detect + // "ISO-8859-1" or "IS0-8859-2" here. Technically either is correct, so + // we accept either. + assert.Contains(t, encoding, "ISO-8859") + + setting.Repository.AnsiCharset = "placeholder" + testSuccess(b, "placeholder") + + // invalid bytes + b = []byte{0xfa} + _, err = DetectEncoding(b) + assert.Error(t, err) +} + +func stringMustStartWith(t *testing.T, expected string, value string) { + assert.Equal(t, expected, string(value[:len(expected)])) +} + +func stringMustEndWith(t *testing.T, expected string, value string) { + assert.Equal(t, expected, string(value[len(value)-len(expected):])) +} + +func bytesMustStartWith(t *testing.T, expected []byte, value []byte) { + assert.Equal(t, expected, value[:len(expected)]) +} diff --git a/modules/context/api.go b/modules/context/api.go index 0b2f81fd64..024ae487f1 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -10,14 +10,13 @@ import ( "net/url" "strings" - "github.com/go-macaron/csrf" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "gopkg.in/macaron.v1" + "gitea.com/macaron/csrf" + "gitea.com/macaron/macaron" ) // APIContext is a specific macaron context for API service diff --git a/modules/context/auth.go b/modules/context/auth.go index 772403bda9..be63720035 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -10,8 +10,9 @@ import ( "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/go-macaron/csrf" - macaron "gopkg.in/macaron.v1" + + "gitea.com/macaron/csrf" + "gitea.com/macaron/macaron" ) // ToggleOptions contains required or check options diff --git a/modules/context/context.go b/modules/context/context.go index b7c77ac460..ef6c19ed12 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -20,12 +20,13 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" - "github.com/go-macaron/cache" - "github.com/go-macaron/csrf" - "github.com/go-macaron/i18n" - "github.com/go-macaron/session" - "gopkg.in/macaron.v1" + + "gitea.com/macaron/cache" + "gitea.com/macaron/csrf" + "gitea.com/macaron/i18n" + "gitea.com/macaron/macaron" + "gitea.com/macaron/session" + "github.com/unknwon/com" ) // Context represents context of a request. @@ -249,6 +250,19 @@ func Contexter() macaron.Handler { if ctx.Query("go-get") == "1" { ownerName := c.Params(":username") repoName := c.Params(":reponame") + trimmedRepoName := strings.TrimSuffix(repoName, ".git") + + if ownerName == "" || trimmedRepoName == "" { + _, _ = c.Write([]byte(` + + + invalid import path + + +`)) + c.WriteHeader(400) + return + } branchName := "master" repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) @@ -276,7 +290,7 @@ func Contexter() macaron.Handler { `, map[string]string{ - "GoGetImport": ComposeGoGetImport(ownerName, strings.TrimSuffix(repoName, ".git")), + "GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName), "CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName), "GoDocDirectory": prefix + "{/dir}", "GoDocFile": prefix + "{/dir}/{file}#L{line}", diff --git a/modules/context/org.go b/modules/context/org.go index 12d5dbfbf9..4867474334 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -9,7 +9,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" - macaron "gopkg.in/macaron.v1" + + "gitea.com/macaron/macaron" ) // Organization contains organization context diff --git a/modules/context/pagination.go b/modules/context/pagination.go index 390b4dbdd7..9a6ad0b5c4 100644 --- a/modules/context/pagination.go +++ b/modules/context/pagination.go @@ -10,7 +10,7 @@ import ( "net/url" "strings" - "github.com/Unknwon/paginater" + "github.com/unknwon/paginater" ) // Pagination provides a pagination via Paginater and additional configurations for the link params used in rendering diff --git a/modules/context/panic.go b/modules/context/panic.go index 8ed91dea65..e5ef233629 100644 --- a/modules/context/panic.go +++ b/modules/context/panic.go @@ -20,7 +20,8 @@ import ( "fmt" "code.gitea.io/gitea/modules/log" - macaron "gopkg.in/macaron.v1" + + "gitea.com/macaron/macaron" ) // Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so. @@ -30,7 +31,7 @@ func Recovery() macaron.Handler { return func(ctx *Context) { defer func() { if err := recover(); err != nil { - combinedErr := fmt.Errorf("%s\n%s", err, string(log.Stack(2))) + combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) ctx.ServerError("PANIC:", combinedErr) } }() diff --git a/modules/context/permission.go b/modules/context/permission.go index 6ac935686b..f2adf896f9 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -8,7 +8,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" - macaron "gopkg.in/macaron.v1" + "gitea.com/macaron/macaron" ) // RequireRepoAdmin returns a macaron middleware for requiring repository admin permission diff --git a/modules/context/repo.go b/modules/context/repo.go index 096f3c0a5d..3caf583f83 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -18,9 +18,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" + "gitea.com/macaron/macaron" + "github.com/unknwon/com" "gopkg.in/editorconfig/editorconfig-core-go.v1" - "gopkg.in/macaron.v1" ) // PullRequest contains informations to make a pull request @@ -201,10 +201,14 @@ func ComposeGoGetImport(owner, repo string) string { // .netrc file. func EarlyResponseForGoGetMeta(ctx *Context) { username := ctx.Params(":username") - reponame := ctx.Params(":reponame") + reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git") + if username == "" || reponame == "" { + ctx.PlainText(400, []byte("invalid repository path")) + return + } ctx.PlainText(200, []byte(com.Expand(``, map[string]string{ - "GoGetImport": ComposeGoGetImport(username, strings.TrimSuffix(reponame, ".git")), + "GoGetImport": ComposeGoGetImport(username, reponame), "CloneLink": models.ComposeHTTPSCloneURL(username, reponame), }))) } @@ -391,6 +395,7 @@ func RepoAssignment() macaron.Handler { ctx.Data["Owner"] = ctx.Repo.Repository.Owner ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() + ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization() ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode) ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) diff --git a/modules/cron/cron.go b/modules/cron/cron.go index 24457f5013..089f0fa767 100644 --- a/modules/cron/cron.go +++ b/modules/cron/cron.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 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. @@ -7,15 +8,43 @@ package cron import ( "time" - "github.com/gogits/cron" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sync" + mirror_service "code.gitea.io/gitea/services/mirror" + + "github.com/gogs/cron" +) + +const ( + mirrorUpdate = "mirror_update" + gitFsck = "git_fsck" + checkRepos = "check_repos" + archiveCleanup = "archive_cleanup" + syncExternalUsers = "sync_external_users" + deletedBranchesCleanup = "deleted_branches_cleanup" ) var c = cron.New() +// Prevent duplicate running tasks. +var taskStatusTable = sync.NewStatusTable() + +// Func defines a cron function body +type Func func() + +// WithUnique wrap a cron func with an unique running check +func WithUnique(name string, body Func) Func { + return func() { + if !taskStatusTable.StartIfNotRunning(name) { + return + } + defer taskStatusTable.Stop(name) + body() + } +} + // NewContext begins cron tasks func NewContext() { var ( @@ -23,69 +52,69 @@ func NewContext() { err error ) if setting.Cron.UpdateMirror.Enabled { - entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, models.MirrorUpdate) + entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, WithUnique(mirrorUpdate, mirror_service.Update)) if err != nil { log.Fatal("Cron[Update mirrors]: %v", err) } if setting.Cron.UpdateMirror.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go models.MirrorUpdate() + go WithUnique(mirrorUpdate, mirror_service.Update)() } } if setting.Cron.RepoHealthCheck.Enabled { - entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, models.GitFsck) + entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, WithUnique(gitFsck, models.GitFsck)) if err != nil { log.Fatal("Cron[Repository health check]: %v", err) } if setting.Cron.RepoHealthCheck.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go models.GitFsck() + go WithUnique(gitFsck, models.GitFsck)() } } if setting.Cron.CheckRepoStats.Enabled { - entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, models.CheckRepoStats) + entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, WithUnique(checkRepos, models.CheckRepoStats)) if err != nil { log.Fatal("Cron[Check repository statistics]: %v", err) } if setting.Cron.CheckRepoStats.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go models.CheckRepoStats() + go WithUnique(checkRepos, models.CheckRepoStats)() } } if setting.Cron.ArchiveCleanup.Enabled { - entry, err = c.AddFunc("Clean up old repository archives", setting.Cron.ArchiveCleanup.Schedule, models.DeleteOldRepositoryArchives) + entry, err = c.AddFunc("Clean up old repository archives", setting.Cron.ArchiveCleanup.Schedule, WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)) if err != nil { log.Fatal("Cron[Clean up old repository archives]: %v", err) } if setting.Cron.ArchiveCleanup.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go models.DeleteOldRepositoryArchives() + go WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)() } } if setting.Cron.SyncExternalUsers.Enabled { - entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, models.SyncExternalUsers) + entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, WithUnique(syncExternalUsers, models.SyncExternalUsers)) if err != nil { log.Fatal("Cron[Synchronize external users]: %v", err) } if setting.Cron.SyncExternalUsers.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go models.SyncExternalUsers() + go WithUnique(syncExternalUsers, models.SyncExternalUsers)() } } if setting.Cron.DeletedBranchesCleanup.Enabled { - entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, models.RemoveOldDeletedBranches) + entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)) if err != nil { log.Fatal("Cron[Remove old deleted branches]: %v", err) } if setting.Cron.DeletedBranchesCleanup.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go models.RemoveOldDeletedBranches() + go WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)() } } c.Start() diff --git a/modules/git/commit.go b/modules/git/commit.go index c86ece9848..eb442f988d 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -10,6 +10,11 @@ import ( "bytes" "container/list" "fmt" + "image" + "image/color" + _ "image/gif" // for processing gif images + _ "image/jpeg" // for processing jpeg images + _ "image/png" // for processing png images "io" "net/http" "strconv" @@ -158,6 +163,43 @@ func (c *Commit) IsImageFile(name string) bool { return isImage } +// ImageMetaData represents metadata of an image file +type ImageMetaData struct { + ColorModel color.Model + Width int + Height int + ByteSize int64 +} + +// ImageInfo returns information about the dimensions of an image +func (c *Commit) ImageInfo(name string) (*ImageMetaData, error) { + if !c.IsImageFile(name) { + return nil, nil + } + + blob, err := c.GetBlobByPath(name) + if err != nil { + return nil, err + } + reader, err := blob.DataAsync() + if err != nil { + return nil, err + } + defer reader.Close() + config, _, err := image.DecodeConfig(reader) + if err != nil { + return nil, err + } + + metadata := ImageMetaData{ + ColorModel: config.ColorModel, + Width: config.Width, + Height: config.Height, + ByteSize: blob.Size(), + } + return &metadata, nil +} + // GetCommitByPath return the commit of relative path object. func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) { return c.repo.getCommitByPathWithID(c.ID, relpath) @@ -169,6 +211,7 @@ func AddChanges(repoPath string, all bool, files ...string) error { if all { cmd.AddArguments("--all") } + cmd.AddArguments("--") _, err := cmd.AddArguments(files...).RunInDir(repoPath) return err } @@ -304,10 +347,21 @@ func (c *Commit) GetFilesChangedSinceCommit(pastCommit string) ([]string, error) } // FileChangedSinceCommit Returns true if the file given has changed since the the past commit +// YOU MUST ENSURE THAT pastCommit is a valid commit ID. func (c *Commit) FileChangedSinceCommit(filename, pastCommit string) (bool, error) { return c.repo.FileChangedBetweenCommits(filename, pastCommit, c.ID.String()) } +// HasFile returns true if the file given exists on this commit +// This does only mean it's there - it does not mean the file was changed during the commit. +func (c *Commit) HasFile(filename string) (bool, error) { + _, err := c.GetBlobByPath(filename) + if err != nil { + return false, err + } + return true, nil +} + // GetSubModules get all the sub modules of current revision git tree func (c *Commit) GetSubModules() (*ObjectCache, error) { if c.submoduleCache != nil { diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 8417226f8b..d8bf88a47c 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -25,7 +25,7 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom defer commitGraphFile.Close() } - c, err := commitNodeIndex.Get(plumbing.Hash(commit.ID)) + c, err := commitNodeIndex.Get(commit.ID) if err != nil { return nil, nil, err } @@ -68,7 +68,9 @@ func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCom // get it for free during the tree traversal and it's used for listing // pages to display information about newest commit for a given path. var treeCommit *Commit - if rev, ok := revs[""]; ok { + if treePath == "" { + treeCommit = commit + } else if rev, ok := revs[""]; ok { treeCommit = convertCommit(rev) } return commitsInfo, treeCommit, nil diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go index d7d863b032..71637d188a 100644 --- a/modules/git/commit_info_test.go +++ b/modules/git/commit_info_test.go @@ -28,21 +28,27 @@ func cloneRepo(url, dir, name string) (string, error) { func testGetCommitsInfo(t *testing.T, repo1 *Repository) { // these test case are specific to the repo1 test repo testCases := []struct { - CommitID string - Path string - ExpectedIDs map[string]string + CommitID string + Path string + ExpectedIDs map[string]string + ExpectedTreeCommit string }{ {"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", "", map[string]string{ "file1.txt": "95bb4d39648ee7e325106df01a621c530863a653", "file2.txt": "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", - }}, + }, "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2"}, {"2839944139e0de9737a044f78b0e4b40d989a9e3", "", map[string]string{ "file1.txt": "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch1.txt": "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", - }}, + }, "2839944139e0de9737a044f78b0e4b40d989a9e3"}, {"5c80b0245c1c6f8343fa418ec374b13b5d4ee658", "branch2", map[string]string{ "branch2.txt": "5c80b0245c1c6f8343fa418ec374b13b5d4ee658", - }}, + }, "5c80b0245c1c6f8343fa418ec374b13b5d4ee658"}, + {"feaf4ba6bc635fec442f46ddd4512416ec43c2c2", "", map[string]string{ + "file1.txt": "95bb4d39648ee7e325106df01a621c530863a653", + "file2.txt": "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", + "foo": "37991dec2c8e592043f47155ce4808d4580f9123", + }, "feaf4ba6bc635fec442f46ddd4512416ec43c2c2"}, } for _, testCase := range testCases { commit, err := repo1.GetCommit(testCase.CommitID) @@ -51,7 +57,8 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) { assert.NoError(t, err) entries, err := tree.ListEntries() assert.NoError(t, err) - commitsInfo, _, err := entries.GetCommitsInfo(commit, testCase.Path, nil) + commitsInfo, treeCommit, err := entries.GetCommitsInfo(commit, testCase.Path, nil) + assert.Equal(t, testCase.ExpectedTreeCommit, treeCommit.ID.String()) assert.NoError(t, err) assert.Len(t, commitsInfo, len(testCase.ExpectedIDs)) for _, commitInfo := range commitsInfo { diff --git a/modules/git/hook.go b/modules/git/hook.go index 18c00b5838..e966591668 100644 --- a/modules/git/hook.go +++ b/modules/git/hook.go @@ -12,7 +12,7 @@ import ( "path/filepath" "strings" - "github.com/Unknwon/com" + "github.com/unknwon/com" ) // hookNames is a list of Git server hooks' name that are supported. diff --git a/modules/git/notes.go b/modules/git/notes.go index a62c558787..e825923682 100644 --- a/modules/git/notes.go +++ b/modules/git/notes.go @@ -7,7 +7,7 @@ package git import ( "io/ioutil" - "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/object" ) // NotesRef is the git ref where Gitea will look for git-notes data. @@ -27,13 +27,28 @@ func GetNote(repo *Repository, commitID string, note *Note) error { return err } - entry, err := notes.GetTreeEntryByPath(commitID) - if err != nil { - return err + remainingCommitID := commitID + path := "" + currentTree := notes.Tree.gogitTree + var file *object.File + for len(remainingCommitID) > 2 { + file, err = currentTree.File(remainingCommitID) + if err == nil { + path += remainingCommitID + break + } + if err == object.ErrFileNotFound { + currentTree, err = currentTree.Tree(remainingCommitID[0:2]) + path += remainingCommitID[0:2] + "/" + remainingCommitID = remainingCommitID[2:] + } + if err != nil { + return err + } } - blob := entry.Blob() - dataRc, err := blob.DataAsync() + blob := file.Blob + dataRc, err := blob.Reader() if err != nil { return err } @@ -45,26 +60,21 @@ func GetNote(repo *Repository, commitID string, note *Note) error { } note.Message = d - commit, err := repo.gogitRepo.CommitObject(plumbing.Hash(notes.ID)) - if err != nil { - return err - } - commitNodeIndex, commitGraphFile := repo.CommitNodeIndex() if commitGraphFile != nil { defer commitGraphFile.Close() } - commitNode, err := commitNodeIndex.Get(commit.Hash) - if err != nil { - return nil - } - - lastCommits, err := getLastCommitForPaths(commitNode, "", []string{commitID}) + commitNode, err := commitNodeIndex.Get(notes.ID) if err != nil { return err } - note.Commit = convertCommit(lastCommits[commitID]) + + lastCommits, err := getLastCommitForPaths(commitNode, "", []string{path}) + if err != nil { + return err + } + note.Commit = convertCommit(lastCommits[path]) return nil } diff --git a/modules/git/notes_test.go b/modules/git/notes_test.go index a954377f54..bf010b9a71 100644 --- a/modules/git/notes_test.go +++ b/modules/git/notes_test.go @@ -22,3 +22,17 @@ func TestGetNotes(t *testing.T) { assert.Equal(t, []byte("Note contents\n"), note.Message) assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name) } + +func TestGetNestedNotes(t *testing.T) { + repoPath := filepath.Join(testReposDir, "repo3_notes") + repo, err := OpenRepository(repoPath) + assert.NoError(t, err) + + note := Note{} + err = GetNote(repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", ¬e) + assert.NoError(t, err) + assert.Equal(t, []byte("Note 2"), note.Message) + err = GetNote(repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", ¬e) + assert.NoError(t, err) + assert.Equal(t, []byte("Note 1"), note.Message) +} diff --git a/modules/git/parse.go b/modules/git/parse.go index 22861b1d2c..cbd48b9189 100644 --- a/modules/git/parse.go +++ b/modules/git/parse.go @@ -9,7 +9,6 @@ import ( "fmt" "strconv" - "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" "gopkg.in/src-d/go-git.v4/plumbing/object" ) @@ -57,7 +56,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { return nil, fmt.Errorf("Invalid ls-tree output: %v", err) } entry.ID = id - entry.gogitTreeEntry.Hash = plumbing.Hash(id) + entry.gogitTreeEntry.Hash = id pos += 41 // skip over sha and trailing space end := pos + bytes.IndexByte(data[pos:], '\n') diff --git a/modules/git/repo.go b/modules/git/repo.go index 8a40fb1b91..1a9112132f 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -17,7 +17,7 @@ import ( "strings" "time" - "github.com/Unknwon/com" + "github.com/unknwon/com" "gopkg.in/src-d/go-billy.v4/osfs" gogit "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing/cache" @@ -187,8 +187,7 @@ func Pull(repoPath string, opts PullRemoteOptions) error { if opts.All { cmd.AddArguments("--all") } else { - cmd.AddArguments(opts.Remote) - cmd.AddArguments(opts.Branch) + cmd.AddArguments("--", opts.Remote, opts.Branch) } if opts.Timeout <= 0 { @@ -213,7 +212,7 @@ func Push(repoPath string, opts PushOptions) error { if opts.Force { cmd.AddArguments("-f") } - cmd.AddArguments(opts.Remote, opts.Branch) + cmd.AddArguments("--", opts.Remote, opts.Branch) _, err := cmd.RunInDirWithEnv(repoPath, opts.Env) return err } diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go index 80ec50e472..5c023554f1 100644 --- a/modules/git/repo_blame.go +++ b/modules/git/repo_blame.go @@ -20,5 +20,5 @@ func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Comm if len(res) < 40 { return nil, fmt.Errorf("invalid result of blame: %s", res) } - return repo.GetCommit(string(res[:40])) + return repo.GetCommit(res[:40]) } diff --git a/modules/git/repo_blob.go b/modules/git/repo_blob.go index db63491ce4..45ca604402 100644 --- a/modules/git/repo_blob.go +++ b/modules/git/repo_blob.go @@ -9,7 +9,7 @@ import ( ) func (repo *Repository) getBlob(id SHA1) (*Blob, error) { - encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, plumbing.Hash(id)) + encodedObj, err := repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, id) if err != nil { return nil, ErrNotExist{id.String(), ""} } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 05eba1e30e..9209f4a764 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -135,7 +135,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro cmd.AddArguments("-d") } - cmd.AddArguments(name) + cmd.AddArguments("--", name) _, err := cmd.RunInDir(repo.Path) return err diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 8ea2a33145..5808c7600e 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -86,9 +86,9 @@ func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature { func (repo *Repository) getCommit(id SHA1) (*Commit, error) { var tagObject *object.Tag - gogitCommit, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) + gogitCommit, err := repo.gogitRepo.CommitObject(id) if err == plumbing.ErrObjectNotFound { - tagObject, err = repo.gogitRepo.TagObject(plumbing.Hash(id)) + tagObject, err = repo.gogitRepo.TagObject(id) if err == nil { gogitCommit, err = repo.gogitRepo.CommitObject(tagObject.Target) } @@ -117,20 +117,26 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { return commit, nil } -// GetCommit returns commit object of by ID string. -func (repo *Repository) GetCommit(commitID string) (*Commit, error) { +// ConvertToSHA1 returns a Hash object from a potential ID string +func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { if len(commitID) != 40 { var err error - actualCommitID, err := NewCommand("rev-parse", commitID).RunInDir(repo.Path) + actualCommitID, err := NewCommand("rev-parse", "--verify", commitID).RunInDir(repo.Path) if err != nil { - if strings.Contains(err.Error(), "unknown revision or path") { - return nil, ErrNotExist{commitID, ""} + if strings.Contains(err.Error(), "unknown revision or path") || + strings.Contains(err.Error(), "fatal: Needed a single revision") { + return SHA1{}, ErrNotExist{commitID, ""} } - return nil, err + return SHA1{}, err } commitID = actualCommitID } - id, err := NewIDFromString(commitID) + return NewIDFromString(commitID) +} + +// GetCommit returns commit object of by ID string. +func (repo *Repository) GetCommit(commitID string) (*Commit, error) { + id, err := repo.ConvertToSHA1(commitID) if err != nil { return nil, err } @@ -202,36 +208,57 @@ func (repo *Repository) commitsByRange(id SHA1, page int) (*list.List, error) { } func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) (*list.List, error) { - cmd := NewCommand("log", id.String(), "-100", "-i", prettyLogFormat) + cmd := NewCommand("log", id.String(), "-100", prettyLogFormat) + args := []string{"-i"} + if len(opts.Authors) > 0 { + for _, v := range opts.Authors { + args = append(args, "--author="+v) + } + } + if len(opts.Committers) > 0 { + for _, v := range opts.Committers { + args = append(args, "--committer="+v) + } + } + if len(opts.After) > 0 { + args = append(args, "--after="+opts.After) + } + if len(opts.Before) > 0 { + args = append(args, "--before="+opts.Before) + } + if opts.All { + args = append(args, "--all") + } if len(opts.Keywords) > 0 { for _, v := range opts.Keywords { cmd.AddArguments("--grep=" + v) } } - if len(opts.Authors) > 0 { - for _, v := range opts.Authors { - cmd.AddArguments("--author=" + v) - } - } - if len(opts.Committers) > 0 { - for _, v := range opts.Committers { - cmd.AddArguments("--committer=" + v) - } - } - if len(opts.After) > 0 { - cmd.AddArguments("--after=" + opts.After) - } - if len(opts.Before) > 0 { - cmd.AddArguments("--before=" + opts.Before) - } - if opts.All { - cmd.AddArguments("--all") - } + cmd.AddArguments(args...) stdout, err := cmd.RunInDirBytes(repo.Path) if err != nil { return nil, err } - return repo.parsePrettyFormatLogToList(stdout) + if len(stdout) != 0 { + stdout = append(stdout, '\n') + } + if len(opts.Keywords) > 0 { + for _, v := range opts.Keywords { + if len(v) >= 4 { + hashCmd := NewCommand("log", "-1", prettyLogFormat) + hashCmd.AddArguments(args...) + hashCmd.AddArguments(v) + hashMatching, err := hashCmd.RunInDirBytes(repo.Path) + if err != nil || bytes.Contains(stdout, hashMatching) { + continue + } + stdout = append(stdout, hashMatching...) + stdout = append(stdout, '\n') + } + } + } + + return repo.parsePrettyFormatLogToList(bytes.TrimSuffix(stdout, []byte{'\n'})) } func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) { @@ -243,6 +270,7 @@ func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) { } // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2 +// You must ensure that id1 and id2 are valid commit ids. func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) { stdout, err := NewCommand("diff", "--name-only", "-z", id1, id2, "--", filename).RunInDirBytes(repo.Path) if err != nil { @@ -266,6 +294,16 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( return repo.parsePrettyFormatLogToList(stdout) } +// CommitsByFileAndRangeNoFollow return the commits according revison file and the page +func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) (*list.List, error) { + stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*50), + "--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path) + if err != nil { + return nil, err + } + return repo.parsePrettyFormatLogToList(stdout) +} + // FilesCountBetween return the number of files changed between two commits func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) { stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path) diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index 6761a45b7a..6d8ee6453f 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -40,8 +40,9 @@ func TestRepository_GetCommitBranches(t *testing.T) { func TestGetTagCommitWithSignature(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) - commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a") + assert.NoError(t, err) + commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a") assert.NoError(t, err) assert.NotNil(t, commit) assert.NotNil(t, commit.Signature) @@ -52,8 +53,10 @@ func TestGetTagCommitWithSignature(t *testing.T) { func TestGetCommitWithBadCommitID(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") bareRepo1, err := OpenRepository(bareRepo1Path) + assert.NoError(t, err) + commit, err := bareRepo1.GetCommit("bad_branch") assert.Nil(t, commit) assert.Error(t, err) - assert.EqualError(t, err, "object does not exist [id: bad_branch, rel_path: ]") + assert.True(t, IsErrNotExist(err)) } diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 52263852dc..f3a45d8e6e 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -10,6 +10,7 @@ import ( "path" gitealog "code.gitea.io/gitea/modules/log" + "gopkg.in/src-d/go-git.v4/plumbing/format/commitgraph" cgobject "gopkg.in/src-d/go-git.v4/plumbing/object/commitgraph" ) diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index ddc8109720..677201c5e0 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -39,7 +39,7 @@ func (repo *Repository) GetMergeBase(tmpRemote string, base, head string) (strin } } - stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path) + stdout, err := NewCommand("merge-base", "--", base, head).RunInDir(repo.Path) return strings.TrimSpace(stdout), base, err } @@ -54,7 +54,7 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string) if repo.Path != basePath { // Add a temporary remote tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10) - if err = repo.AddRemote(tmpRemote, basePath, true); err != nil { + if err = repo.AddRemote(tmpRemote, basePath, false); err != nil { return nil, fmt.Errorf("AddRemote: %v", err) } defer func() { diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 4d26563a91..2c351e209f 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -12,7 +12,7 @@ import ( // ReadTreeToIndex reads a treeish to the index func (repo *Repository) ReadTreeToIndex(treeish string) error { if len(treeish) != 40 { - res, err := NewCommand("rev-parse", treeish).RunInDir(repo.Path) + res, err := NewCommand("rev-parse", "--verify", treeish).RunInDir(repo.Path) if err != nil { return err } diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 95a6c2ae69..9044dbeadd 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -34,13 +34,13 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { refType := string(ObjectCommit) if ref.Name().IsTag() { // tags can be of type `commit` (lightweight) or `tag` (annotated) - if tagType, _ := repo.GetTagType(SHA1(ref.Hash())); err == nil { + if tagType, _ := repo.GetTagType(ref.Hash()); err == nil { refType = tagType } } r := &Reference{ Name: ref.Name().String(), - Object: SHA1(ref.Hash()), + Object: ref.Hash(), Type: refType, repo: repo, } diff --git a/modules/git/repo_stats_test.go b/modules/git/repo_stats_test.go index 2e8565b9e2..6fbcb7ac13 100644 --- a/modules/git/repo_stats_test.go +++ b/modules/git/repo_stats_test.go @@ -18,17 +18,18 @@ func TestRepository_GetCodeActivityStats(t *testing.T) { assert.NoError(t, err) timeFrom, err := time.Parse(time.RFC3339, "2016-01-01T00:00:00+00:00") + assert.NoError(t, err) code, err := bareRepo1.GetCodeActivityStats(timeFrom, "") assert.NoError(t, err) assert.NotNil(t, code) - assert.EqualValues(t, 8, code.CommitCount) - assert.EqualValues(t, 2, code.AuthorCount) - assert.EqualValues(t, 8, code.CommitCountInAllBranches) + assert.EqualValues(t, 9, code.CommitCount) + assert.EqualValues(t, 3, code.AuthorCount) + assert.EqualValues(t, 9, code.CommitCountInAllBranches) assert.EqualValues(t, 10, code.Additions) assert.EqualValues(t, 1, code.Deletions) - assert.Len(t, code.Authors, 2) + assert.Len(t, code.Authors, 3) assert.Contains(t, code.Authors, "tris.git@shoddynet.org") assert.EqualValues(t, 3, code.Authors["tris.git@shoddynet.org"]) assert.EqualValues(t, 5, code.Authors[""]) diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 6d490af9b5..9e08d00f0d 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -29,13 +29,13 @@ func (repo *Repository) IsTagExist(name string) bool { // CreateTag create one tag in the repository func (repo *Repository) CreateTag(name, revision string) error { - _, err := NewCommand("tag", name, revision).RunInDir(repo.Path) + _, err := NewCommand("tag", "--", name, revision).RunInDir(repo.Path) return err } // CreateAnnotatedTag create one annotated tag in the repository func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error { - _, err := NewCommand("tag", "-a", "-m", message, name, revision).RunInDir(repo.Path) + _, err := NewCommand("tag", "-a", "-m", message, "--", name, revision).RunInDir(repo.Path) return err } @@ -153,15 +153,18 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA) func (repo *Repository) GetTagID(name string) (string, error) { - stdout, err := NewCommand("show-ref", name).RunInDir(repo.Path) + stdout, err := NewCommand("show-ref", "--tags", "--", name).RunInDir(repo.Path) if err != nil { return "", err } - fields := strings.Fields(stdout) - if len(fields) != 2 { - return "", ErrNotExist{ID: name} + // Make sure exact match is used: "v1" != "release/v1" + for _, line := range strings.Split(stdout, "\n") { + fields := strings.Fields(line) + if len(fields) == 2 && fields[1] == "refs/tags/"+name { + return fields[0], nil + } } - return fields[0], nil + return "", ErrNotExist{ID: name} } // GetTag returns a Git tag by given name. diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 4f727c6c66..ab9742afc5 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -62,6 +62,16 @@ func TestRepository_GetTag(t *testing.T) { assert.NotEqual(t, aTagID, aTag.Object.String()) assert.EqualValues(t, aTagCommitID, aTag.Object.String()) assert.EqualValues(t, "tag", aTag.Type) + + rTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0" + rTagName := "release/" + lTagName + bareRepo1.CreateTag(rTagName, rTagCommitID) + rTagID, err := bareRepo1.GetTagID(rTagName) + assert.NoError(t, err) + assert.EqualValues(t, rTagCommitID, rTagID) + oTagID, err := bareRepo1.GetTagID(lTagName) + assert.NoError(t, err) + assert.EqualValues(t, lTagCommitID, oTagID) } func TestRepository_GetAnnotatedTag(t *testing.T) { diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 7d32d3685c..b31e4330cd 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -10,12 +10,10 @@ import ( "os" "strings" "time" - - "gopkg.in/src-d/go-git.v4/plumbing" ) func (repo *Repository) getTree(id SHA1) (*Tree, error) { - gogitTree, err := repo.gogitRepo.TreeObject(plumbing.Hash(id)) + gogitTree, err := repo.gogitRepo.TreeObject(id) if err != nil { return nil, err } @@ -28,7 +26,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { // GetTree find the tree object in the repository. func (repo *Repository) GetTree(idStr string) (*Tree, error) { if len(idStr) != 40 { - res, err := NewCommand("rev-parse", idStr).RunInDir(repo.Path) + res, err := NewCommand("rev-parse", "--verify", idStr).RunInDir(repo.Path) if err != nil { return nil, err } @@ -41,7 +39,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) { return nil, err } resolvedID := id - commitObject, err := repo.gogitRepo.CommitObject(plumbing.Hash(id)) + commitObject, err := repo.gogitRepo.CommitObject(id) if err == nil { id = SHA1(commitObject.TreeHash) } @@ -63,7 +61,7 @@ type CommitTreeOpts struct { // CommitTree creates a commit from a given tree id for the user with provided message func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) { - commitTimeStr := time.Now().Format(time.UnixDate) + commitTimeStr := time.Now().Format(time.RFC3339) // Because this may call hooks we should pass in the environment env := append(os.Environ(), diff --git a/modules/git/tests/repos/repo1_bare/logs/HEAD b/modules/git/tests/repos/repo1_bare/logs/HEAD new file mode 100644 index 0000000000..cef4ca2dcb --- /dev/null +++ b/modules/git/tests/repos/repo1_bare/logs/HEAD @@ -0,0 +1 @@ +37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind 1563741799 +0200 push diff --git a/modules/git/tests/repos/repo1_bare/logs/refs/heads/master b/modules/git/tests/repos/repo1_bare/logs/refs/heads/master new file mode 100644 index 0000000000..cef4ca2dcb --- /dev/null +++ b/modules/git/tests/repos/repo1_bare/logs/refs/heads/master @@ -0,0 +1 @@ +37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind 1563741799 +0200 push diff --git a/modules/git/tests/repos/repo1_bare/objects/fe/af4ba6bc635fec442f46ddd4512416ec43c2c2 b/modules/git/tests/repos/repo1_bare/objects/fe/af4ba6bc635fec442f46ddd4512416ec43c2c2 new file mode 100644 index 0000000000..95edd9a65b Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/fe/af4ba6bc635fec442f46ddd4512416ec43c2c2 differ diff --git a/modules/git/tests/repos/repo1_bare/refs/heads/master b/modules/git/tests/repos/repo1_bare/refs/heads/master index 4804d9d8fe..c5e92eb229 100644 --- a/modules/git/tests/repos/repo1_bare/refs/heads/master +++ b/modules/git/tests/repos/repo1_bare/refs/heads/master @@ -1 +1 @@ -37991dec2c8e592043f47155ce4808d4580f9123 +feaf4ba6bc635fec442f46ddd4512416ec43c2c2 diff --git a/modules/git/tests/repos/repo3_notes/COMMIT_EDITMSG b/modules/git/tests/repos/repo3_notes/COMMIT_EDITMSG new file mode 100644 index 0000000000..0cfbf08886 --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/COMMIT_EDITMSG @@ -0,0 +1 @@ +2 diff --git a/modules/git/tests/repos/repo3_notes/HEAD b/modules/git/tests/repos/repo3_notes/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/modules/git/tests/repos/repo3_notes/config b/modules/git/tests/repos/repo3_notes/config new file mode 100644 index 0000000000..d545cdabdb --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true diff --git a/modules/git/tests/repos/repo3_notes/description b/modules/git/tests/repos/repo3_notes/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/modules/git/tests/repos/repo3_notes/index b/modules/git/tests/repos/repo3_notes/index new file mode 100644 index 0000000000..783158b928 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/index differ diff --git a/modules/git/tests/repos/repo3_notes/logs/HEAD b/modules/git/tests/repos/repo3_notes/logs/HEAD new file mode 100644 index 0000000000..4bd0a61ea6 --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 ba0a96fa63532d6c5087ecef070b0250ed72fa47 Filip Navara 1567767895 +0200 commit (initial): 1 +ba0a96fa63532d6c5087ecef070b0250ed72fa47 3e668dbfac39cbc80a9ff9c61eb565d944453ba4 Filip Navara 1567767909 +0200 commit: 2 diff --git a/modules/git/tests/repos/repo3_notes/logs/refs/heads/master b/modules/git/tests/repos/repo3_notes/logs/refs/heads/master new file mode 100644 index 0000000000..4bd0a61ea6 --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 ba0a96fa63532d6c5087ecef070b0250ed72fa47 Filip Navara 1567767895 +0200 commit (initial): 1 +ba0a96fa63532d6c5087ecef070b0250ed72fa47 3e668dbfac39cbc80a9ff9c61eb565d944453ba4 Filip Navara 1567767909 +0200 commit: 2 diff --git a/modules/git/tests/repos/repo3_notes/objects/29/7128d6553180486c780e2f747cb6d0014bf1f6 b/modules/git/tests/repos/repo3_notes/objects/29/7128d6553180486c780e2f747cb6d0014bf1f6 new file mode 100644 index 0000000000..96fb749521 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/29/7128d6553180486c780e2f747cb6d0014bf1f6 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/2f/7e2ea1e905c14c8a98e7ce47b395592834b9ef b/modules/git/tests/repos/repo3_notes/objects/2f/7e2ea1e905c14c8a98e7ce47b395592834b9ef new file mode 100644 index 0000000000..71cff177b2 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/2f/7e2ea1e905c14c8a98e7ce47b395592834b9ef differ diff --git a/modules/git/tests/repos/repo3_notes/objects/3e/668dbfac39cbc80a9ff9c61eb565d944453ba4 b/modules/git/tests/repos/repo3_notes/objects/3e/668dbfac39cbc80a9ff9c61eb565d944453ba4 new file mode 100644 index 0000000000..8f13b31d71 --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/objects/3e/668dbfac39cbc80a9ff9c61eb565d944453ba4 @@ -0,0 +1,3 @@ +x;0 @s +H&v*!4J(p~ +G`|oxzi;3 6 $`"NRѺXlaiK4r$\P0"ỵPQ'F_VNi*ʗGӳK|YeHff Em \ No newline at end of file diff --git a/modules/git/tests/repos/repo3_notes/objects/42/716fdb6f261867472899d785123e6ecaa5ca02 b/modules/git/tests/repos/repo3_notes/objects/42/716fdb6f261867472899d785123e6ecaa5ca02 new file mode 100644 index 0000000000..3d522eb298 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/42/716fdb6f261867472899d785123e6ecaa5ca02 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/56/a6051ca2b02b04ef92d5150c9ef600403cb1de b/modules/git/tests/repos/repo3_notes/objects/56/a6051ca2b02b04ef92d5150c9ef600403cb1de new file mode 100644 index 0000000000..b17dfe30e6 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/56/a6051ca2b02b04ef92d5150c9ef600403cb1de differ diff --git a/modules/git/tests/repos/repo3_notes/objects/61/6c62e75fce60d806f4afe993211705a00a2544 b/modules/git/tests/repos/repo3_notes/objects/61/6c62e75fce60d806f4afe993211705a00a2544 new file mode 100644 index 0000000000..b09d3a2d2c Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/61/6c62e75fce60d806f4afe993211705a00a2544 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/65/4c8b6b63c08bf37f638d3f521626b7fbbd4d37 b/modules/git/tests/repos/repo3_notes/objects/65/4c8b6b63c08bf37f638d3f521626b7fbbd4d37 new file mode 100644 index 0000000000..0d317cb80e --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/objects/65/4c8b6b63c08bf37f638d3f521626b7fbbd4d37 @@ -0,0 +1 @@ +x;0}"ǿu$RQrK1F1nfR-%wzc{%7h#xQfXѻ?jK S#8צ{M3> 6ZQm8 \ No newline at end of file diff --git a/modules/git/tests/repos/repo3_notes/objects/ba/0a96fa63532d6c5087ecef070b0250ed72fa47 b/modules/git/tests/repos/repo3_notes/objects/ba/0a96fa63532d6c5087ecef070b0250ed72fa47 new file mode 100644 index 0000000000..c21f2b2a22 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/ba/0a96fa63532d6c5087ecef070b0250ed72fa47 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/c9/34d51cee361fdee21a3f3bb1a285f5ea9bc225 b/modules/git/tests/repos/repo3_notes/objects/c9/34d51cee361fdee21a3f3bb1a285f5ea9bc225 new file mode 100644 index 0000000000..f5a8caa759 Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/c9/34d51cee361fdee21a3f3bb1a285f5ea9bc225 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/d8/263ee9860594d2806b0dfd1bfd17528b0ba2a4 b/modules/git/tests/repos/repo3_notes/objects/d8/263ee9860594d2806b0dfd1bfd17528b0ba2a4 new file mode 100644 index 0000000000..4b1baefffb Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/d8/263ee9860594d2806b0dfd1bfd17528b0ba2a4 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/f3/6ad903e408cb8f4ed90bda02e3a1fd2fab7907 b/modules/git/tests/repos/repo3_notes/objects/f3/6ad903e408cb8f4ed90bda02e3a1fd2fab7907 new file mode 100644 index 0000000000..dc2af772ed Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/f3/6ad903e408cb8f4ed90bda02e3a1fd2fab7907 differ diff --git a/modules/git/tests/repos/repo3_notes/objects/fe/c9fe57e9864fe537f02f825e377c4a8a65ad2e b/modules/git/tests/repos/repo3_notes/objects/fe/c9fe57e9864fe537f02f825e377c4a8a65ad2e new file mode 100644 index 0000000000..6372ff10ab Binary files /dev/null and b/modules/git/tests/repos/repo3_notes/objects/fe/c9fe57e9864fe537f02f825e377c4a8a65ad2e differ diff --git a/modules/git/tests/repos/repo3_notes/refs/heads/master b/modules/git/tests/repos/repo3_notes/refs/heads/master new file mode 100644 index 0000000000..e96af8d801 --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/refs/heads/master @@ -0,0 +1 @@ +3e668dbfac39cbc80a9ff9c61eb565d944453ba4 \ No newline at end of file diff --git a/modules/git/tests/repos/repo3_notes/refs/notes/commits b/modules/git/tests/repos/repo3_notes/refs/notes/commits new file mode 100644 index 0000000000..74e3d3ad8d --- /dev/null +++ b/modules/git/tests/repos/repo3_notes/refs/notes/commits @@ -0,0 +1 @@ +654c8b6b63c08bf37f638d3f521626b7fbbd4d37 \ No newline at end of file diff --git a/modules/git/tree.go b/modules/git/tree.go index 6ca893cb7b..58c4d8cad1 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -63,7 +63,7 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) { } func (t *Tree) loadTreeObject() error { - gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID)) + gogitTree, err := t.repo.gogitRepo.TreeObject(t.ID) if err != nil { return err } diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index eb8ddd0946..cdbe031a2c 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -9,7 +9,6 @@ import ( "path" "strings" - "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" "gopkg.in/src-d/go-git.v4/plumbing/object" ) @@ -23,7 +22,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { gogitTreeEntry: &object.TreeEntry{ Name: "", Mode: filemode.Dir, - Hash: plumbing.Hash(t.ID), + Hash: t.ID, }, }, nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 6019e34487..4e18cc8ead 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -108,6 +108,11 @@ func (te *TreeEntry) IsRegular() bool { return te.gogitTreeEntry.Mode == filemode.Regular } +// IsExecutable if the entry is an executable file (not necessarily binary) +func (te *TreeEntry) IsExecutable() bool { + return te.gogitTreeEntry.Mode == filemode.Executable +} + // Blob returns the blob object the entry func (te *TreeEntry) Blob() *Blob { encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash) diff --git a/modules/gzip/gzip.go b/modules/gzip/gzip.go index 0d10071830..d139b09e1c 100644 --- a/modules/gzip/gzip.go +++ b/modules/gzip/gzip.go @@ -15,8 +15,8 @@ import ( "strings" "sync" + "gitea.com/macaron/macaron" "github.com/klauspost/compress/gzip" - "gopkg.in/macaron.v1" ) const ( diff --git a/modules/gzip/gzip_test.go b/modules/gzip/gzip_test.go index d131e240af..5fc56cc7f0 100644 --- a/modules/gzip/gzip_test.go +++ b/modules/gzip/gzip_test.go @@ -12,9 +12,9 @@ import ( "net/http/httptest" "testing" + "gitea.com/macaron/macaron" gzipp "github.com/klauspost/compress/gzip" "github.com/stretchr/testify/assert" - macaron "gopkg.in/macaron.v1" ) func setup(sampleResponse []byte) (*macaron.Macaron, *[]byte) { diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index cb52f6ac2e..ffd88656ae 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -19,55 +19,101 @@ var ( } // File names that are representing highlight classes. - highlightFileNames = map[string]bool{ - "dockerfile": true, - "makefile": true, + highlightFileNames = map[string]string{ + "dockerfile": "dockerfile", + "makefile": "makefile", + "gnumakefile": "makefile", + "cmakelists.txt": "cmake", } // Extensions that are same as highlight classes. + // See hljs.listLanguages() for list of language names. highlightExts = map[string]struct{}{ - ".arm": {}, - ".as": {}, - ".sh": {}, - ".cs": {}, - ".cpp": {}, - ".c": {}, - ".css": {}, - ".cmake": {}, - ".bat": {}, - ".dart": {}, - ".patch": {}, - ".erl": {}, - ".go": {}, - ".html": {}, - ".xml": {}, - ".hs": {}, - ".ini": {}, - ".json": {}, - ".java": {}, - ".js": {}, - ".less": {}, - ".lua": {}, - ".php": {}, - ".py": {}, - ".rb": {}, - ".rs": {}, - ".scss": {}, - ".sql": {}, - ".scala": {}, - ".swift": {}, - ".ts": {}, - ".vb": {}, - ".yml": {}, - ".yaml": {}, + ".applescript": {}, + ".arm": {}, + ".as": {}, + ".bash": {}, + ".bat": {}, + ".c": {}, + ".cmake": {}, + ".cpp": {}, + ".cs": {}, + ".css": {}, + ".dart": {}, + ".diff": {}, + ".django": {}, + ".go": {}, + ".gradle": {}, + ".groovy": {}, + ".haml": {}, + ".handlebars": {}, + ".html": {}, + ".ini": {}, + ".java": {}, + ".json": {}, + ".less": {}, + ".lua": {}, + ".php": {}, + ".scala": {}, + ".scss": {}, + ".sql": {}, + ".swift": {}, + ".ts": {}, + ".xml": {}, + ".yaml": {}, } // Extensions that are not same as highlight classes. highlightMapping = map[string]string{ - ".txt": "nohighlight", + ".ahk": "autohotkey", + ".crmsh": "crmsh", + ".dash": "shell", + ".erl": "erlang", ".escript": "erlang", ".ex": "elixir", ".exs": "elixir", + ".f": "fortran", + ".f77": "fortran", + ".f90": "fortran", + ".f95": "fortran", + ".feature": "gherkin", + ".fish": "shell", + ".for": "fortran", + ".hbs": "handlebars", + ".hs": "haskell", + ".hx": "haxe", + ".js": "javascript", + ".jsx": "javascript", + ".ksh": "shell", + ".kt": "kotlin", + ".l": "ocaml", + ".ls": "livescript", + ".md": "markdown", + ".mjs": "javascript", + ".mli": "ocaml", + ".mll": "ocaml", + ".mly": "ocaml", + ".patch": "diff", + ".pl": "perl", + ".pm": "perl", + ".ps1": "powershell", + ".psd1": "powershell", + ".psm1": "powershell", + ".py": "python", + ".pyw": "python", + ".rb": "ruby", + ".rs": "rust", + ".scpt": "applescript", + ".scptd": "applescript", + ".sh": "bash", + ".tcsh": "shell", + ".ts": "typescript", + ".tsx": "typescript", + ".txt": "plaintext", + ".vb": "vbnet", + ".vbs": "vbscript", + ".yml": "yaml", + ".zsh": "shell", } ) @@ -87,8 +133,8 @@ func FileNameToHighlightClass(fname string) string { return "nohighlight" } - if highlightFileNames[fname] { - return fname + if name, ok := highlightFileNames[fname]; ok { + return name } ext := path.Ext(fname) diff --git a/modules/indexer/issues/queue_disk.go b/modules/indexer/issues/queue_disk.go index e5ac2a7981..b127f8d269 100644 --- a/modules/indexer/issues/queue_disk.go +++ b/modules/indexer/issues/queue_disk.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/modules/log" + "github.com/lunny/levelqueue" ) diff --git a/modules/indexer/issues/queue_redis.go b/modules/indexer/issues/queue_redis.go index aeccd7920c..0344d3c87a 100644 --- a/modules/indexer/issues/queue_redis.go +++ b/modules/indexer/issues/queue_redis.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/modules/log" + "github.com/go-redis/redis" ) diff --git a/modules/indexer/repo.go b/modules/indexer/repo.go index 287d23854b..91ed173aa7 100644 --- a/modules/indexer/repo.go +++ b/modules/indexer/repo.go @@ -12,9 +12,7 @@ import ( "github.com/blevesearch/bleve" "github.com/blevesearch/bleve/analysis/analyzer/custom" - "github.com/blevesearch/bleve/analysis/token/camelcase" "github.com/blevesearch/bleve/analysis/token/lowercase" - "github.com/blevesearch/bleve/analysis/token/unique" "github.com/blevesearch/bleve/analysis/tokenizer/unicode" "github.com/blevesearch/bleve/search/query" "github.com/ethantkoenig/rupture" @@ -24,7 +22,7 @@ const ( repoIndexerAnalyzer = "repoIndexerAnalyzer" repoIndexerDocType = "repoIndexerDocType" - repoIndexerLatestVersion = 1 + repoIndexerLatestVersion = 4 ) // repoIndexer (thread-safe) index for repository contents @@ -111,7 +109,7 @@ func createRepoIndexer(path string, latestVersion int) error { "type": custom.Name, "char_filters": []string{}, "tokenizer": unicode.Name, - "token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name, unique.Name}, + "token_filters": []string{unicodeNormalizeName, lowercase.Name}, }); err != nil { return err } diff --git a/modules/lfs/locks.go b/modules/lfs/locks.go index 4516ba01ae..d7b2429698 100644 --- a/modules/lfs/locks.go +++ b/modules/lfs/locks.go @@ -97,7 +97,7 @@ func GetListLockHandler(ctx *context.Context) { }) return } - lock, err := models.GetLFSLockByID(int64(v)) + lock, err := models.GetLFSLockByID(v) handleLockListOut(ctx, repository, lock, err) return } diff --git a/modules/lfs/server.go b/modules/lfs/server.go index bf5355acfc..652610acf4 100644 --- a/modules/lfs/server.go +++ b/modules/lfs/server.go @@ -17,8 +17,8 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "gitea.com/macaron/macaron" "github.com/dgrijalva/jwt-go" - "gopkg.in/macaron.v1" ) const ( diff --git a/modules/log/colors.go b/modules/log/colors.go index c29741634f..282d0c919e 100644 --- a/modules/log/colors.go +++ b/modules/log/colors.go @@ -378,9 +378,9 @@ func (cv *ColoredValue) Format(s fmt.State, c rune) { return } } - s.Write([]byte(*cv.colorBytes)) + s.Write(*cv.colorBytes) fmt.Fprintf(&protectedANSIWriter{w: s}, fmtString(s, c), *(cv.Value)) - s.Write([]byte(*cv.resetBytes)) + s.Write(*cv.resetBytes) } // SetColorBytes will allow a user to set the colorBytes of a colored value diff --git a/modules/log/level.go b/modules/log/level.go index 6131fcb510..4b89385fe2 100644 --- a/modules/log/level.go +++ b/modules/log/level.go @@ -101,7 +101,7 @@ func (l *Level) UnmarshalJSON(b []byte) error { switch v := tmp.(type) { case string: - *l = FromString(string(v)) + *l = FromString(v) case int: *l = FromString(Level(v).String()) default: diff --git a/modules/log/log.go b/modules/log/log.go index 0ca0f3adc5..71e88491f1 100644 --- a/modules/log/log.go +++ b/modules/log/log.go @@ -8,13 +8,35 @@ import ( "os" "runtime" "strings" + "sync" ) +type loggerMap struct { + sync.Map +} + +func (m *loggerMap) Load(k string) (*Logger, bool) { + v, ok := m.Map.Load(k) + if !ok { + return nil, false + } + l, ok := v.(*Logger) + return l, ok +} + +func (m *loggerMap) Store(k string, v *Logger) { + m.Map.Store(k, v) +} + +func (m *loggerMap) Delete(k string) { + m.Map.Delete(k) +} + var ( // DEFAULT is the name of the default logger DEFAULT = "default" // NamedLoggers map of named loggers - NamedLoggers = make(map[string]*Logger) + NamedLoggers loggerMap prefix string ) @@ -25,16 +47,16 @@ func NewLogger(bufLen int64, name, provider, config string) *Logger { CriticalWithSkip(1, "Unable to create default logger: %v", err) panic(err) } - return NamedLoggers[DEFAULT] + l, _ := NamedLoggers.Load(DEFAULT) + return l } // NewNamedLogger creates a new named logger for a given configuration func NewNamedLogger(name string, bufLen int64, subname, provider, config string) error { - logger, ok := NamedLoggers[name] + logger, ok := NamedLoggers.Load(name) if !ok { logger = newLogger(name, bufLen) - - NamedLoggers[name] = logger + NamedLoggers.Store(name, logger) } return logger.SetLogger(subname, provider, config) @@ -42,16 +64,16 @@ func NewNamedLogger(name string, bufLen int64, subname, provider, config string) // DelNamedLogger closes and deletes the named logger func DelNamedLogger(name string) { - l, ok := NamedLoggers[name] + l, ok := NamedLoggers.Load(name) if ok { - delete(NamedLoggers, name) + NamedLoggers.Delete(name) l.Close() } } // DelLogger removes the named sublogger from the default logger func DelLogger(name string) error { - logger := NamedLoggers[DEFAULT] + logger, _ := NamedLoggers.Load(DEFAULT) found, err := logger.DelLogger(name) if !found { Trace("Log %s not found, no need to delete", name) @@ -61,21 +83,24 @@ func DelLogger(name string) error { // GetLogger returns either a named logger or the default logger func GetLogger(name string) *Logger { - logger, ok := NamedLoggers[name] + logger, ok := NamedLoggers.Load(name) if ok { return logger } - return NamedLoggers[DEFAULT] + logger, _ = NamedLoggers.Load(DEFAULT) + return logger } // GetLevel returns the minimum logger level func GetLevel() Level { - return NamedLoggers[DEFAULT].GetLevel() + l, _ := NamedLoggers.Load(DEFAULT) + return l.GetLevel() } // GetStacktraceLevel returns the minimum logger level func GetStacktraceLevel() Level { - return NamedLoggers[DEFAULT].GetStacktraceLevel() + l, _ := NamedLoggers.Load(DEFAULT) + return l.GetStacktraceLevel() } // Trace records trace log @@ -169,18 +194,18 @@ func IsFatal() bool { // Close closes all the loggers func Close() { - l, ok := NamedLoggers[DEFAULT] + l, ok := NamedLoggers.Load(DEFAULT) if !ok { return } - delete(NamedLoggers, DEFAULT) + NamedLoggers.Delete(DEFAULT) l.Close() } // Log a message with defined skip and at logging level // A skip of 0 refers to the caller of this command func Log(skip int, level Level, format string, v ...interface{}) { - l, ok := NamedLoggers[DEFAULT] + l, ok := NamedLoggers.Load(DEFAULT) if ok { l.Log(skip+1, level, format, v...) } @@ -195,7 +220,8 @@ type LoggerAsWriter struct { // NewLoggerAsWriter creates a Writer representation of the logger with setable log level func NewLoggerAsWriter(level string, ourLoggers ...*Logger) *LoggerAsWriter { if len(ourLoggers) == 0 { - ourLoggers = []*Logger{NamedLoggers[DEFAULT]} + l, _ := NamedLoggers.Load(DEFAULT) + ourLoggers = []*Logger{l} } l := &LoggerAsWriter{ ourLoggers: ourLoggers, diff --git a/modules/log/log_test.go b/modules/log/log_test.go index 9e3d7527b2..0e7583081c 100644 --- a/modules/log/log_test.go +++ b/modules/log/log_test.go @@ -143,7 +143,7 @@ func TestNewNamedLogger(t *testing.T) { level := INFO err := NewNamedLogger("test", 0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) assert.NoError(t, err) - logger := NamedLoggers["test"] + logger, _ := NamedLoggers.Load("test") assert.Equal(t, level, logger.GetLevel()) written, closed := baseConsoleTest(t, logger) diff --git a/modules/log/writer.go b/modules/log/writer.go index 2503f04d76..6f656c7ce1 100644 --- a/modules/log/writer.go +++ b/modules/log/writer.go @@ -203,7 +203,7 @@ func (logger *WriterLogger) createMsg(buf *[]byte, event *Event) { (&protectedANSIWriter{ w: &baw, mode: pawMode, - }).Write([]byte(msg)) + }).Write(msg) *buf = baw if event.stacktrace != "" && logger.StacktraceLevel <= event.level { diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 077947e774..1e3acc9b47 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -9,12 +9,18 @@ import ( "encoding/csv" "html" "io" + "regexp" + "strings" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/util" ) +var quoteRegexp = regexp.MustCompile(`["'][\s\S]+?["']`) + func init() { markup.RegisterParser(Parser{}) + } // Parser implements markup.Parser for orgmode @@ -28,12 +34,13 @@ func (Parser) Name() string { // Extensions implements markup.Parser func (Parser) Extensions() []string { - return []string{".csv"} + return []string{".csv", ".tsv"} } // Render implements markup.Parser -func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { +func (p Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { rd := csv.NewReader(bytes.NewReader(rawBytes)) + rd.Comma = p.bestDelimiter(rawBytes) var tmpBlock bytes.Buffer tmpBlock.WriteString(``) for { @@ -50,9 +57,57 @@ func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, tmpBlock.WriteString(html.EscapeString(field)) tmpBlock.WriteString("") } - tmpBlock.WriteString("") + tmpBlock.WriteString("") } tmpBlock.WriteString("
") return tmpBlock.Bytes() } + +// bestDelimiter scores the input CSV data against delimiters, and returns the best match. +// Reads at most 10k bytes & 10 lines. +func (p Parser) bestDelimiter(data []byte) rune { + maxLines := 10 + maxBytes := util.Min(len(data), 1e4) + text := string(data[:maxBytes]) + text = quoteRegexp.ReplaceAllLiteralString(text, "") + lines := strings.SplitN(text, "\n", maxLines+1) + lines = lines[:util.Min(maxLines, len(lines))] + + delimiters := []rune{',', ';', '\t', '|'} + bestDelim := delimiters[0] + bestScore := 0.0 + for _, delim := range delimiters { + score := p.scoreDelimiter(lines, delim) + if score > bestScore { + bestScore = score + bestDelim = delim + } + } + + return bestDelim +} + +// scoreDelimiter uses a count & regularity metric to evaluate a delimiter against lines of CSV +func (Parser) scoreDelimiter(lines []string, delim rune) (score float64) { + countTotal := 0 + countLineMax := 0 + linesNotEqual := 0 + + for _, line := range lines { + if len(line) == 0 { + continue + } + + countLine := strings.Count(line, string(delim)) + countTotal += countLine + if countLine != countLineMax { + if countLineMax != 0 { + linesNotEqual++ + } + countLineMax = util.Max(countLine, countLineMax) + } + } + + return float64(countTotal) * (1 - float64(linesNotEqual)/float64(len(lines))) +} diff --git a/modules/markup/csv/csv_test.go b/modules/markup/csv/csv_test.go index f050296cee..4d4e0871e9 100644 --- a/modules/markup/csv/csv_test.go +++ b/modules/markup/csv/csv_test.go @@ -13,9 +13,14 @@ import ( func TestRenderCSV(t *testing.T) { var parser Parser var kases = map[string]string{ - "a": "
a
", - "1,2": "
12
", - "
": "
<br/>
", + "a": "
a
", + "1,2": "
12
", + "1;2": "
12
", + "1\t2": "
12
", + "1|2": "
12
", + "1,2,3;4,5,6;7,8,9\na;b;c": "
1,2,34,5,67,8,9
abc
", + "\"1,2,3,4\";\"a\nb\"\nc;d": "
1,2,3,4a\nb
cd
", + "
": "
<br/>
", } for k, v := range kases { diff --git a/modules/markup/html.go b/modules/markup/html.go index 3e976929c7..f07993bc4c 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -13,10 +13,12 @@ import ( "strings" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "github.com/Unknwon/com" + "github.com/unknwon/com" "golang.org/x/net/html" "golang.org/x/net/html/atom" "mvdan.cc/xurls/v2" @@ -38,9 +40,9 @@ var ( mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_\.]+)(?:\s|$|\)|\])`) // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 - issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) + issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(#[0-9]+)(?:\s|$|\)|\]|:|\.(\s|$))`) // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 - issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|\.(\s|$))`) + issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. gogits/gogs#12345 crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+#[0-9]+)(?:\s|$|\)|\]|\.(\s|$))`) @@ -209,6 +211,40 @@ func RenderCommitMessage( return ctx.postProcess(rawHTML) } +var commitMessageSubjectProcessors = []processor{ + fullIssuePatternProcessor, + fullSha1PatternProcessor, + linkProcessor, + mentionProcessor, + issueIndexPatternProcessor, + crossReferenceIssueIndexPatternProcessor, + sha1CurrentPatternProcessor, +} + +// RenderCommitMessageSubject will use the same logic as PostProcess and +// RenderCommitMessage, but will disable the shortLinkProcessor and +// emailAddressProcessor, will add a defaultLinkProcessor if defaultLink is set, +// which changes every text node into a link to the passed default link. +func RenderCommitMessageSubject( + rawHTML []byte, + urlPrefix, defaultLink string, + metas map[string]string, +) ([]byte, error) { + ctx := &postProcessCtx{ + metas: metas, + urlPrefix: urlPrefix, + procs: commitMessageSubjectProcessors, + } + if defaultLink != "" { + // we don't have to fear data races, because being + // commitMessageSubjectProcessors of fixed len and cap, every time we + // append something to it the slice is realloc+copied, so append always + // generates the slice ex-novo. + ctx.procs = append(ctx.procs, genDefaultLinkProcessor(defaultLink)) + } + return ctx.postProcess(rawHTML) +} + // RenderDescriptionHTML will use similar logic as PostProcess, but will // use a single special linkProcessor. func RenderDescriptionHTML( @@ -294,12 +330,17 @@ func (ctx *postProcessCtx) textNode(node *html.Node) { } } -func createLink(href, content string) *html.Node { +func createLink(href, content, class string) *html.Node { a := &html.Node{ Type: html.ElementNode, Data: atom.A.String(), Attr: []html.Attribute{{Key: "href", Val: href}}, } + + if class != "" { + a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class}) + } + text := &html.Node{ Type: html.TextNode, Data: content, @@ -309,12 +350,17 @@ func createLink(href, content string) *html.Node { return a } -func createCodeLink(href, content string) *html.Node { +func createCodeLink(href, content, class string) *html.Node { a := &html.Node{ Type: html.ElementNode, Data: atom.A.String(), Attr: []html.Attribute{{Key: "href", Val: href}}, } + + if class != "" { + a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class}) + } + text := &html.Node{ Type: html.TextNode, Data: content, @@ -362,7 +408,7 @@ func mentionProcessor(_ *postProcessCtx, node *html.Node) { } // Replace the mention with a link to the specified user. mention := node.Data[m[2]:m[3]] - replaceContent(node, m[2], m[3], createLink(util.URLJoin(setting.AppURL, mention[1:]), mention)) + replaceContent(node, m[2], m[3], createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention")) } func shortLinkProcessor(ctx *postProcessCtx, node *html.Node) { @@ -438,7 +484,7 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) { name += tail image := false - switch ext := filepath.Ext(string(link)); ext { + switch ext := filepath.Ext(link); ext { // fast path: empty string, ignore case "": break @@ -482,7 +528,7 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) { title = props["alt"] } if title == "" { - title = path.Base(string(name)) + title = path.Base(name) } alt := props["alt"] if alt == "" { @@ -539,11 +585,11 @@ func fullIssuePatternProcessor(ctx *postProcessCtx, node *html.Node) { if matchOrg == ctx.metas["user"] && matchRepo == ctx.metas["repo"] { // TODO if m[4]:m[5] is not nil, then link is to a comment, // and we should indicate that in the text somehow - replaceContent(node, m[0], m[1], createLink(link, id)) + replaceContent(node, m[0], m[1], createLink(link, id, "issue")) } else { orgRepoID := matchOrg + "/" + matchRepo + id - replaceContent(node, m[0], m[1], createLink(link, orgRepoID)) + replaceContent(node, m[0], m[1], createLink(link, orgRepoID, "issue")) } } @@ -571,9 +617,9 @@ func issueIndexPatternProcessor(ctx *postProcessCtx, node *html.Node) { } else { ctx.metas["index"] = id[1:] } - link = createLink(com.Expand(ctx.metas["format"], ctx.metas), id) + link = createLink(com.Expand(ctx.metas["format"], ctx.metas), id, "issue") } else { - link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "issues", id[1:]), id) + link = createLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "issues", id[1:]), id, "issue") } replaceContent(node, match[2], match[3], link) } @@ -589,7 +635,7 @@ func crossReferenceIssueIndexPatternProcessor(ctx *postProcessCtx, node *html.No repo, issue := parts[0], parts[1] replaceContent(node, m[2], m[3], - createLink(util.URLJoin(setting.AppURL, repo, "issues", issue), ref)) + createLink(util.URLJoin(setting.AppURL, repo, "issues", issue), ref, issue)) } // fullSha1PatternProcessor renders SHA containing URLs @@ -640,12 +686,15 @@ func fullSha1PatternProcessor(ctx *postProcessCtx, node *html.Node) { text += " (" + hash + ")" } - replaceContent(node, start, end, createCodeLink(urlFull, text)) + replaceContent(node, start, end, createCodeLink(urlFull, text, "commit")) } // sha1CurrentPatternProcessor renders SHA1 strings to corresponding links that // are assumed to be in the same repository. func sha1CurrentPatternProcessor(ctx *postProcessCtx, node *html.Node) { + if ctx.metas == nil || ctx.metas["user"] == "" || ctx.metas["repo"] == "" || ctx.metas["repoPath"] == "" { + return + } m := sha1CurrentPattern.FindStringSubmatchIndex(node.Data) if m == nil { return @@ -657,8 +706,17 @@ func sha1CurrentPatternProcessor(ctx *postProcessCtx, node *html.Node) { // but that is not always the case. // Although unlikely, deadbeef and 1234567 are valid short forms of SHA1 hash // as used by git and github for linking and thus we have to do similar. + // Because of this, we check to make sure that a matched hash is actually + // a commit in the repository before making it a link. + if _, err := git.NewCommand("rev-parse", "--verify", hash).RunInDirBytes(ctx.metas["repoPath"]); err != nil { + if !strings.Contains(err.Error(), "fatal: Needed a single revision") { + log.Debug("sha1CurrentPatternProcessor git rev-parse: %v", err) + } + return + } + replaceContent(node, m[2], m[3], - createCodeLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "commit", hash), base.ShortSha(hash))) + createCodeLink(util.URLJoin(setting.AppURL, ctx.metas["user"], ctx.metas["repo"], "commit", hash), base.ShortSha(hash), "commit")) } // emailAddressProcessor replaces raw email addresses with a mailto: link. @@ -668,7 +726,7 @@ func emailAddressProcessor(ctx *postProcessCtx, node *html.Node) { return } mail := node.Data[m[2]:m[3]] - replaceContent(node, m[2], m[3], createLink("mailto:"+mail, mail)) + replaceContent(node, m[2], m[3], createLink("mailto:"+mail, mail, "mailto")) } // linkProcessor creates links for any HTTP or HTTPS URL not captured by @@ -679,7 +737,7 @@ func linkProcessor(ctx *postProcessCtx, node *html.Node) { return } uri := node.Data[m[0]:m[1]] - replaceContent(node, m[0], m[1], createLink(uri, uri)) + replaceContent(node, m[0], m[1], createLink(uri, uri, "link")) } func genDefaultLinkProcessor(defaultLink string) processor { @@ -693,7 +751,10 @@ func genDefaultLinkProcessor(defaultLink string) processor { node.Type = html.ElementNode node.Data = "a" node.DataAtom = atom.A - node.Attr = []html.Attribute{{Key: "href", Val: defaultLink}} + node.Attr = []html.Attribute{ + {Key: "href", Val: defaultLink}, + {Key: "class", Val: "default-link"}, + } node.FirstChild, node.LastChild = ch, ch } } diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index f0d894532b..2824ce3e68 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -20,18 +20,22 @@ const Repo = "gogits/gogs" const AppSubURL = AppURL + Repo + "/" // alphanumLink an HTML link to an alphanumeric-style issue -func alphanumIssueLink(baseURL string, name string) string { - return link(util.URLJoin(baseURL, name), name) +func alphanumIssueLink(baseURL, class, name string) string { + return link(util.URLJoin(baseURL, name), class, name) } // numericLink an HTML to a numeric-style issue -func numericIssueLink(baseURL string, index int) string { - return link(util.URLJoin(baseURL, strconv.Itoa(index)), fmt.Sprintf("#%d", index)) +func numericIssueLink(baseURL, class string, index int) string { + return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("#%d", index)) } // link an HTML link -func link(href, contents string) string { - return fmt.Sprintf("%s", href, contents) +func link(href, class, contents string) string { + if class != "" { + class = " class=\"" + class + "\"" + } + + return fmt.Sprintf("%s", href, class, contents) } var numericMetas = map[string]string{ @@ -89,13 +93,13 @@ func TestRender_IssueIndexPattern2(t *testing.T) { test := func(s, expectedFmt string, indices ...int) { links := make([]interface{}, len(indices)) for i, index := range indices { - links[i] = numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), index) + links[i] = numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), "issue", index) } expectedNil := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expectedNil, &postProcessCtx{metas: localMetas}) for i, index := range indices { - links[i] = numericIssueLink("https://someurl.com/someUser/someRepo/", index) + links[i] = numericIssueLink("https://someurl.com/someUser/someRepo/", "issue", index) } expectedNum := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expectedNum, &postProcessCtx{metas: numericMetas}) @@ -118,6 +122,10 @@ func TestRender_IssueIndexPattern2(t *testing.T) { test("wow (#54321 #1243)", "wow (%s %s)", 54321, 1243) test("(#4)(#5)", "(%s)(%s)", 4, 5) test("#1 (#4321) test", "%s (%s) test", 1, 4321) + + // should render with : + test("#1234: test", "%s: test", 1234) + test("wow (#54321: test)", "wow (%s: test)", 54321) } func TestRender_IssueIndexPattern3(t *testing.T) { @@ -154,7 +162,7 @@ func TestRender_IssueIndexPattern4(t *testing.T) { test := func(s, expectedFmt string, names ...string) { links := make([]interface{}, len(names)) for i, name := range names { - links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", name) + links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", "issue", name) } expected := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expected, &postProcessCtx{metas: alphanumericMetas}) @@ -193,17 +201,17 @@ func TestRender_AutoLink(t *testing.T) { // render valid issue URLs test(util.URLJoin(setting.AppSubURL, "issues", "3333"), - numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), 3333)) + numericIssueLink(util.URLJoin(setting.AppSubURL, "issues"), "issue", 3333)) // render valid commit URLs tmp := util.URLJoin(AppSubURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") - test(tmp, "d8a994ef24") + test(tmp, "d8a994ef24") tmp += "#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + test(tmp, "d8a994ef24 (diff-2)") // render other commit URLs tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + test(tmp, "d8a994ef24 (diff-2)") } func TestRender_FullIssueURLs(t *testing.T) { @@ -224,11 +232,11 @@ func TestRender_FullIssueURLs(t *testing.T) { test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6", "Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6") test("Look here http://localhost:3000/person/repo/issues/4", - `Look here person/repo#4`) + `Look here person/repo#4`) test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", - `person/repo#4`) + `person/repo#4`) test("http://localhost:3000/gogits/gogs/issues/4", - `#4`) + `#4`) } func TestRegExp_issueNumericPattern(t *testing.T) { @@ -237,6 +245,8 @@ func TestRegExp_issueNumericPattern(t *testing.T) { "#0", "#1234567890987654321", " #12", + "#12:", + "ref: #12: msg", } falseTestCases := []string{ "# 1234", @@ -354,6 +364,7 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) { "ABC-123.", "(ABC-123)", "[ABC-123]", + "ABC-123:", } falseTestCases := []string{ "RC-08", diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index daf953c9e6..66e56f71a7 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -17,8 +17,9 @@ import ( ) var localMetas = map[string]string{ - "user": "gogits", - "repo": "gogs", + "user": "gogits", + "repo": "gogs", + "repoPath": "../../integrations/gitea-repositories-meta/user13/repo11.git/", } func TestRender_Commits(t *testing.T) { @@ -27,22 +28,23 @@ func TestRender_Commits(t *testing.T) { test := func(input, expected string) { buffer := RenderString(".md", input, setting.AppSubURL, localMetas) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } - var sha = "b6dd6210eaebc915fd5be5579c58cce4da2e2579" + var sha = "65f1bf27bc3bf70f64657658635e66094edbcb4d" var commit = util.URLJoin(AppSubURL, "commit", sha) var subtree = util.URLJoin(commit, "src") var tree = strings.Replace(subtree, "/commit/", "/tree/", -1) - test(sha, `

b6dd6210ea

`) - test(sha[:7], `

b6dd621

`) - test(sha[:39], `

b6dd6210ea

`) - test(commit, `

b6dd6210ea

`) - test(tree, `

b6dd6210ea/src

`) - test("commit "+sha, `

commit b6dd6210ea

`) + test(sha, `

65f1bf27bc

`) + test(sha[:7], `

65f1bf2

`) + test(sha[:39], `

65f1bf27bc

`) + test(commit, `

65f1bf27bc

`) + test(tree, `

65f1bf27bc/src

`) + test("commit "+sha, `

commit 65f1bf27bc

`) test("/home/gitea/"+sha, "

/home/gitea/"+sha+"

") - + test("deadbeef", `

deadbeef

`) + test("d27ace93", `

d27ace93

`) } func TestRender_CrossReferences(t *testing.T) { @@ -51,7 +53,7 @@ func TestRender_CrossReferences(t *testing.T) { test := func(input, expected string) { buffer := RenderString("a.md", input, setting.AppSubURL, localMetas) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } test( @@ -83,7 +85,7 @@ func TestRender_links(t *testing.T) { test := func(input, expected string) { buffer := RenderString("a.md", input, setting.AppSubURL, nil) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } // Text that should be turned into URL @@ -160,7 +162,7 @@ func TestRender_email(t *testing.T) { test := func(input, expected string) { buffer := RenderString("a.md", input, setting.AppSubURL, nil) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } // Text that should be turned into email link @@ -214,9 +216,9 @@ func TestRender_ShortLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer := markdown.RenderString(input, tree, nil) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer = markdown.RenderWiki([]byte(input), setting.AppSubURL, localMetas) - assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) } rawtree := util.URLJoin(AppSubURL, "raw", "master") diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index aab951c60f..d9fc768891 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -153,7 +153,7 @@ func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { } body = blackfriday.Markdown(body, renderer, exts) - return body + return markup.SanitizeBytes(body) } var ( diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 2128639b9f..669b49367e 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -21,8 +21,9 @@ const AppSubURL = AppURL + Repo + "/" // these values should match the Repo const above var localMetas = map[string]string{ - "user": "gogits", - "repo": "gogs", + "user": "gogits", + "repo": "gogs", + "repoPath": "../../../integrations/gitea-repositories-meta/user13/repo11.git/", } func TestRender_StandardLinks(t *testing.T) { @@ -31,7 +32,7 @@ func TestRender_StandardLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer := RenderString(input, setting.AppSubURL, nil) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) bufferWiki := RenderWiki([]byte(input), setting.AppSubURL, nil) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(bufferWiki)) } @@ -74,7 +75,7 @@ func TestRender_Images(t *testing.T) { test := func(input, expected string) { buffer := RenderString(input, setting.AppSubURL, nil) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } url := "../../.images/src/02/train.jpg" @@ -103,7 +104,7 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
  • Tips
  • -

    See commit fc7f44dadf

    +

    See commit 65f1bf27bc

    Ideas and codes

    @@ -194,7 +195,7 @@ var sameCases = []string{ - [[Links, Language bindings, Engine bindings|Links]] - [[Tips]] -See commit fc7f44dadf +See commit 65f1bf27bc Ideas and codes diff --git a/modules/migrations/base/downloader.go b/modules/migrations/base/downloader.go index f28d0b61e7..ab5ca6dec8 100644 --- a/modules/migrations/base/downloader.go +++ b/modules/migrations/base/downloader.go @@ -8,6 +8,7 @@ package base // Downloader downloads the site repo informations type Downloader interface { GetRepoInfo() (*Repository, error) + GetTopics() ([]string, error) GetMilestones() ([]*Milestone, error) GetReleases() ([]*Release, error) GetLabels() ([]*Label, error) diff --git a/modules/migrations/base/release.go b/modules/migrations/base/release.go index 4ebc37315d..b2541f1bf5 100644 --- a/modules/migrations/base/release.go +++ b/modules/migrations/base/release.go @@ -25,6 +25,9 @@ type Release struct { Body string Draft bool Prerelease bool + PublisherID int64 + PublisherName string + PublisherEmail string Assets []ReleaseAsset Created time.Time Published time.Time diff --git a/modules/migrations/base/uploader.go b/modules/migrations/base/uploader.go index 8c1d649229..a3a9c9fac6 100644 --- a/modules/migrations/base/uploader.go +++ b/modules/migrations/base/uploader.go @@ -9,6 +9,7 @@ package base type Uploader interface { MaxBatchInsertSize(tp string) int CreateRepo(repo *Repository, opts MigrateOptions) error + CreateTopics(topic ...string) error CreateMilestones(milestones ...*Milestone) error CreateReleases(releases ...*Release) error CreateLabels(labels ...*Label) error diff --git a/modules/migrations/git.go b/modules/migrations/git.go index 335d44ec9b..75d05976cd 100644 --- a/modules/migrations/git.go +++ b/modules/migrations/git.go @@ -38,6 +38,11 @@ func (g *PlainGitDownloader) GetRepoInfo() (*base.Repository, error) { }, nil } +// GetTopics returns empty list for plain git repo +func (g *PlainGitDownloader) GetTopics() ([]string, error) { + return []string{}, nil +} + // GetMilestones returns milestones func (g *PlainGitDownloader) GetMilestones() ([]*base.Milestone, error) { return nil, ErrNotSupported diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go index b15aed5f4b..1edac47a6e 100644 --- a/modules/migrations/gitea.go +++ b/modules/migrations/gitea.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "path" "path/filepath" @@ -21,7 +22,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" gouuid "github.com/satori/go.uuid" ) @@ -79,12 +80,22 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate return err } + var remoteAddr = repo.CloneURL + if len(opts.AuthUsername) > 0 { + u, err := url.Parse(repo.CloneURL) + if err != nil { + return err + } + u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword) + remoteAddr = u.String() + } + r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{ Name: g.repoName, Description: repo.Description, OriginalURL: repo.OriginalURL, IsMirror: repo.IsMirror, - RemoteAddr: repo.CloneURL, + RemoteAddr: remoteAddr, IsPrivate: repo.IsPrivate, Wiki: opts.Wiki, SyncReleasesWithTags: !opts.Releases, // if didn't get releases, then sync them from tags @@ -97,26 +108,31 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate return err } +// CreateTopics creates topics +func (g *GiteaLocalUploader) CreateTopics(topics ...string) error { + return models.SaveTopics(g.repo.ID, topics...) +} + // CreateMilestones creates milestones func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error { var mss = make([]*models.Milestone, 0, len(milestones)) for _, milestone := range milestones { - var deadline util.TimeStamp + var deadline timeutil.TimeStamp if milestone.Deadline != nil { - deadline = util.TimeStamp(milestone.Deadline.Unix()) + deadline = timeutil.TimeStamp(milestone.Deadline.Unix()) } if deadline == 0 { - deadline = util.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.UILocation).Unix()) + deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix()) } var ms = models.Milestone{ RepoID: g.repo.ID, Name: milestone.Title, Content: milestone.Description, - IsClosed: milestone.State == "close", + IsClosed: milestone.State == "closed", DeadlineUnix: deadline, } if ms.IsClosed && milestone.Closed != nil { - ms.ClosedDateUnix = util.TimeStamp(milestone.Closed.Unix()) + ms.ClosedDateUnix = timeutil.TimeStamp(milestone.Closed.Unix()) } mss = append(mss, &ms) } @@ -159,18 +175,20 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { var rels = make([]*models.Release, 0, len(releases)) for _, release := range releases { var rel = models.Release{ - RepoID: g.repo.ID, - PublisherID: g.doer.ID, - TagName: release.TagName, - LowerTagName: strings.ToLower(release.TagName), - Target: release.TargetCommitish, - Title: release.Name, - Sha1: release.TargetCommitish, - Note: release.Body, - IsDraft: release.Draft, - IsPrerelease: release.Prerelease, - IsTag: false, - CreatedUnix: util.TimeStamp(release.Created.Unix()), + RepoID: g.repo.ID, + PublisherID: g.doer.ID, + TagName: release.TagName, + LowerTagName: strings.ToLower(release.TagName), + Target: release.TargetCommitish, + Title: release.Name, + Sha1: release.TargetCommitish, + Note: release.Body, + IsDraft: release.Draft, + IsPrerelease: release.Prerelease, + IsTag: false, + CreatedUnix: timeutil.TimeStamp(release.Created.Unix()), + OriginalAuthor: release.PublisherName, + OriginalAuthorID: release.PublisherID, } // calc NumCommits @@ -189,7 +207,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error { Name: asset.Name, DownloadCount: int64(*asset.DownloadCount), Size: int64(*asset.Size), - CreatedUnix: util.TimeStamp(asset.Created.Unix()), + CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()), } // download attachment @@ -260,10 +278,10 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { IsLocked: issue.IsLocked, MilestoneID: milestoneID, Labels: labels, - CreatedUnix: util.TimeStamp(issue.Created.Unix()), + CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()), } if issue.Closed != nil { - is.ClosedUnix = util.TimeStamp(issue.Closed.Unix()) + is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix()) } // TODO: add reactions iss = append(iss, &is) @@ -302,7 +320,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error { OriginalAuthor: comment.PosterName, OriginalAuthorID: comment.PosterID, Content: comment.Content, - CreatedUnix: util.TimeStamp(comment.Created.Unix()), + CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()), }) // TODO: Reactions @@ -383,7 +401,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR } var head = "unknown repository" - if pr.IsForkPullRequest() { + if pr.IsForkPullRequest() && pr.State != "closed" { if pr.Head.OwnerName != "" { remote := pr.Head.OwnerName _, ok := g.prHeadCache[remote] @@ -448,15 +466,15 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR IsClosed: pr.State == "closed", IsLocked: pr.IsLocked, Labels: labels, - CreatedUnix: util.TimeStamp(pr.Created.Unix()), + CreatedUnix: timeutil.TimeStamp(pr.Created.Unix()), }, } if pullRequest.Issue.IsClosed && pr.Closed != nil { - pullRequest.Issue.ClosedUnix = util.TimeStamp(pr.Closed.Unix()) + pullRequest.Issue.ClosedUnix = timeutil.TimeStamp(pr.Closed.Unix()) } if pullRequest.HasMerged && pr.MergedTime != nil { - pullRequest.MergedUnix = util.TimeStamp(pr.MergedTime.Unix()) + pullRequest.MergedUnix = timeutil.TimeStamp(pr.MergedTime.Unix()) pullRequest.MergedCommitID = pr.MergeCommitSHA pullRequest.MergerID = g.doer.ID } diff --git a/modules/migrations/github.go b/modules/migrations/github.go index 93ba108548..754f98941c 100644 --- a/modules/migrations/github.go +++ b/modules/migrations/github.go @@ -118,6 +118,12 @@ func (g *GithubDownloaderV3) GetRepoInfo() (*base.Repository, error) { }, nil } +// GetTopics return github topics +func (g *GithubDownloaderV3) GetTopics() ([]string, error) { + r, _, err := g.client.Repositories.Get(g.ctx, g.repoOwner, g.repoName) + return r.Topics, err +} + // GetMilestones returns milestones func (g *GithubDownloaderV3) GetMilestones() ([]*base.Milestone, error) { var perPage = 100 @@ -208,6 +214,11 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) name = *rel.Name } + var email string + if rel.Author.Email != nil { + email = *rel.Author.Email + } + r := &base.Release{ TagName: *rel.TagName, TargetCommitish: *rel.TargetCommitish, @@ -216,6 +227,9 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease) Draft: *rel.Draft, Prerelease: *rel.Prerelease, Created: rel.CreatedAt.Time, + PublisherID: *rel.Author.ID, + PublisherName: *rel.Author.Login, + PublisherEmail: email, Published: rel.PublishedAt.Time, } diff --git a/modules/migrations/github_test.go b/modules/migrations/github_test.go index 54845ab426..2a0a4edf32 100644 --- a/modules/migrations/github_test.go +++ b/modules/migrations/github_test.go @@ -71,6 +71,10 @@ func TestGitHubDownloadRepo(t *testing.T) { OriginalURL: "https://github.com/go-gitea/gitea", }, repo) + topics, err := downloader.GetTopics() + assert.NoError(t, err) + assert.Contains(t, topics, "gitea") + milestones, err := downloader.GetMilestones() assert.NoError(t, err) // before this tool release, we have 39 milestones on github.com/go-gitea/gitea @@ -163,6 +167,8 @@ func TestGitHubDownloadRepo(t *testing.T) { Body: "Forked source from Gogs into Gitea\n", Created: time.Date(2016, 10, 17, 02, 17, 59, 0, time.UTC), Published: time.Date(2016, 11, 17, 15, 37, 0, 0, time.UTC), + PublisherID: 4726179, + PublisherName: "bkcsoft", }, }, releases[len(releases)-1:]) diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go index a86614c317..27782cb940 100644 --- a/modules/migrations/migrate.go +++ b/modules/migrations/migrate.go @@ -82,6 +82,17 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts return err } + log.Trace("migrating topics") + topics, err := downloader.GetTopics() + if err != nil { + return err + } + if len(topics) > 0 { + if err := uploader.CreateTopics(topics...); err != nil { + return err + } + } + if opts.Milestones { log.Trace("migrating milestones") milestones, err := downloader.GetMilestones() diff --git a/modules/notification/mail/mail.go b/modules/notification/mail/mail.go index 9d0db4f415..e1ae391f78 100644 --- a/modules/notification/mail/mail.go +++ b/modules/notification/mail/mail.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification/base" + "code.gitea.io/gitea/services/mailer" ) type mailNotifier struct { @@ -36,13 +37,13 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models. act = models.ActionCommentIssue } - if err := comment.MailParticipants(act, issue); err != nil { + if err := mailer.MailParticipantsComment(comment, act, issue); err != nil { log.Error("MailParticipants: %v", err) } } func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) { - if err := issue.MailParticipants(issue.Poster, models.ActionCreateIssue); err != nil { + if err := mailer.MailParticipants(issue, issue.Poster, models.ActionCreateIssue); err != nil { log.Error("MailParticipants: %v", err) } } @@ -63,13 +64,13 @@ func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models. } } - if err := issue.MailParticipants(doer, actionType); err != nil { + if err := mailer.MailParticipants(issue, doer, actionType); err != nil { log.Error("MailParticipants: %v", err) } } func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest) { - if err := pr.Issue.MailParticipants(pr.Issue.Poster, models.ActionCreatePullRequest); err != nil { + if err := mailer.MailParticipants(pr.Issue, pr.Issue.Poster, models.ActionCreatePullRequest); err != nil { log.Error("MailParticipants: %v", err) } } @@ -83,7 +84,7 @@ func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models } else if comment.Type == models.CommentTypeComment { act = models.ActionCommentIssue } - if err := comment.MailParticipants(act, pr.Issue); err != nil { + if err := mailer.MailParticipantsComment(comment, act, pr.Issue); err != nil { log.Error("MailParticipants: %v", err) } } diff --git a/modules/options/dynamic.go b/modules/options/dynamic.go index c56d098a2e..7de7cfbd61 100644 --- a/modules/options/dynamic.go +++ b/modules/options/dynamic.go @@ -12,7 +12,8 @@ import ( "path" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" + + "github.com/unknwon/com" ) var ( diff --git a/modules/options/options.go b/modules/options/options.go index 723dd54585..62e8c041bd 100644 --- a/modules/options/options.go +++ b/modules/options/options.go @@ -5,7 +5,6 @@ package options //go:generate go run -mod=vendor main.go -//go:generate go fmt bindata.go type directorySet map[string][]string diff --git a/modules/options/static.go b/modules/options/static.go index 629901f740..1cf841ebb1 100644 --- a/modules/options/static.go +++ b/modules/options/static.go @@ -12,7 +12,8 @@ import ( "path" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" + + "github.com/unknwon/com" ) var ( diff --git a/modules/private/hook.go b/modules/private/hook.go index caa3819555..67496b5132 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -29,12 +29,13 @@ type HookOptions struct { UserName string GitObjectDirectory string GitAlternativeObjectDirectories string + GitQuarantinePath string ProtectedBranchID int64 } // HookPreReceive check whether the provided commits are allowed func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) { - reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&prID=%d", + reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&gitQuarantinePath=%s&prID=%d", url.PathEscape(ownerName), url.PathEscape(repoName), url.QueryEscape(opts.OldCommitID), @@ -43,6 +44,7 @@ func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) opts.UserID, url.QueryEscape(opts.GitObjectDirectory), url.QueryEscape(opts.GitAlternativeObjectDirectories), + url.QueryEscape(opts.GitQuarantinePath), opts.ProtectedBranchID, ) diff --git a/modules/public/dynamic.go b/modules/public/dynamic.go index 282db44970..07f83e1f6a 100644 --- a/modules/public/dynamic.go +++ b/modules/public/dynamic.go @@ -7,7 +7,7 @@ package public import ( - "gopkg.in/macaron.v1" + "gitea.com/macaron/macaron" ) // Static implements the macaron static handler for serving assets. diff --git a/modules/public/public.go b/modules/public/public.go index 8362b42576..c16c8e0009 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -14,11 +14,11 @@ import ( "time" "code.gitea.io/gitea/modules/setting" - "gopkg.in/macaron.v1" + + "gitea.com/macaron/macaron" ) //go:generate go run -mod=vendor main.go -//go:generate go fmt bindata.go // Options represents the available options to configure the macaron handler. type Options struct { diff --git a/modules/public/static.go b/modules/public/static.go index 054b9a806c..3e8865c3f9 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -7,7 +7,7 @@ package public import ( - "gopkg.in/macaron.v1" + "gitea.com/macaron/macaron" ) // Static implements the macaron static handler for serving assets. diff --git a/modules/repofiles/action.go b/modules/repofiles/action.go new file mode 100644 index 0000000000..9467e4fb72 --- /dev/null +++ b/modules/repofiles/action.go @@ -0,0 +1,215 @@ +// Copyright 2019 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 repofiles + +import ( + "encoding/json" + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" +) + +// CommitRepoActionOptions represent options of a new commit action. +type CommitRepoActionOptions struct { + PusherName string + RepoOwnerID int64 + RepoName string + RefFullName string + OldCommitID string + NewCommitID string + Commits *models.PushCommits +} + +// CommitRepoAction adds new commit action to the repository, and prepare +// corresponding webhooks. +func CommitRepoAction(opts CommitRepoActionOptions) error { + pusher, err := models.GetUserByName(opts.PusherName) + if err != nil { + return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) + } + + repo, err := models.GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) + if err != nil { + return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) + } + + refName := git.RefEndName(opts.RefFullName) + + // Change default branch and empty status only if pushed ref is non-empty branch. + if repo.IsEmpty && opts.NewCommitID != git.EmptySHA && strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { + repo.DefaultBranch = refName + repo.IsEmpty = false + if refName != "master" { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + return err + } + if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if !git.IsErrUnsupportedVersion(err) { + return err + } + } + } + } + + // Change repository empty status and update last updated time. + if err = models.UpdateRepository(repo, false); err != nil { + return fmt.Errorf("UpdateRepository: %v", err) + } + + isNewBranch := false + opType := models.ActionCommitRepo + // Check it's tag push or branch. + if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { + opType = models.ActionPushTag + if opts.NewCommitID == git.EmptySHA { + opType = models.ActionDeleteTag + } + opts.Commits = &models.PushCommits{} + } else if opts.NewCommitID == git.EmptySHA { + opType = models.ActionDeleteBranch + opts.Commits = &models.PushCommits{} + } else { + // if not the first commit, set the compare URL. + if opts.OldCommitID == git.EmptySHA { + isNewBranch = true + } else { + opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) + } + + if err = models.UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { + log.Error("updateIssuesCommit: %v", err) + } + } + + if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { + opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] + } + + data, err := json.Marshal(opts.Commits) + if err != nil { + return fmt.Errorf("Marshal: %v", err) + } + + if err = models.NotifyWatchers(&models.Action{ + ActUserID: pusher.ID, + ActUser: pusher, + OpType: opType, + Content: string(data), + RepoID: repo.ID, + Repo: repo, + RefName: refName, + IsPrivate: repo.IsPrivate, + }); err != nil { + return fmt.Errorf("NotifyWatchers: %v", err) + } + + defer func() { + go models.HookQueue.Add(repo.ID) + }() + + apiPusher := pusher.APIFormat() + apiRepo := repo.APIFormat(models.AccessModeNone) + + var shaSum string + var isHookEventPush = false + switch opType { + case models.ActionCommitRepo: // Push + isHookEventPush = true + + if isNewBranch { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) + } + + shaSum, err = gitRepo.GetBranchCommitID(refName) + if err != nil { + log.Error("GetBranchCommitID[%s]: %v", opts.RefFullName, err) + } + if err = models.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ + Ref: refName, + Sha: shaSum, + RefType: "branch", + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } + } + + case models.ActionDeleteBranch: // Delete Branch + isHookEventPush = true + + if err = models.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{ + Ref: refName, + RefType: "branch", + PusherType: api.PusherTypeUser, + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err) + } + + case models.ActionPushTag: // Create + isHookEventPush = true + + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) + } + shaSum, err = gitRepo.GetTagCommitID(refName) + if err != nil { + log.Error("GetTagCommitID[%s]: %v", opts.RefFullName, err) + } + if err = models.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ + Ref: refName, + Sha: shaSum, + RefType: "tag", + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } + case models.ActionDeleteTag: // Delete Tag + isHookEventPush = true + + if err = models.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{ + Ref: refName, + RefType: "tag", + PusherType: api.PusherTypeUser, + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err) + } + } + + if isHookEventPush { + commits, err := opts.Commits.ToAPIPayloadCommits(repo.RepoPath(), repo.HTMLURL()) + if err != nil { + return err + } + if err = models.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ + Ref: opts.RefFullName, + Before: opts.OldCommitID, + After: opts.NewCommitID, + CompareURL: setting.AppURL + opts.Commits.CompareURL, + Commits: commits, + Repo: apiRepo, + Pusher: apiPusher, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } + } + + return nil +} diff --git a/modules/repofiles/action_test.go b/modules/repofiles/action_test.go new file mode 100644 index 0000000000..9ae1042e22 --- /dev/null +++ b/modules/repofiles/action_test.go @@ -0,0 +1,126 @@ +// Copyright 2019 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 repofiles + +import ( + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" +) + +func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *models.Action) { + models.AssertNotExistsBean(t, actionBean) + assert.NoError(t, CommitRepoAction(opts)) + models.AssertExistsAndLoadBean(t, actionBean) + models.CheckConsistencyFor(t, &models.Action{}) +} + +func TestCommitRepoAction(t *testing.T) { + samples := []struct { + userID int64 + repositoryID int64 + commitRepoActionOptions CommitRepoActionOptions + action models.Action + }{ + { + userID: 2, + repositoryID: 16, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: "refName", + OldCommitID: "oldCommitID", + NewCommitID: "newCommitID", + Commits: &models.PushCommits{ + Commits: []*models.PushCommit{ + { + Sha1: "69554a6", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "not signed commit", + }, + { + Sha1: "27566bd", + CommitterEmail: "user2@example.com", + CommitterName: "User2", + AuthorEmail: "user2@example.com", + AuthorName: "User2", + Message: "good signed commit (with not yet validated email)", + }, + }, + Len: 2, + }, + }, + action: models.Action{ + OpType: models.ActionCommitRepo, + RefName: "refName", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: git.TagPrefix + "v1.1", + OldCommitID: git.EmptySHA, + NewCommitID: "newCommitID", + Commits: &models.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionPushTag, + RefName: "v1.1", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: git.TagPrefix + "v1.1", + OldCommitID: "oldCommitID", + NewCommitID: git.EmptySHA, + Commits: &models.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionDeleteTag, + RefName: "v1.1", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: git.BranchPrefix + "feature/1", + OldCommitID: "oldCommitID", + NewCommitID: git.EmptySHA, + Commits: &models.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionDeleteBranch, + RefName: "feature/1", + }, + }, + } + + for _, s := range samples { + models.PrepareTestEnv(t) + + user := models.AssertExistsAndLoadBean(t, &models.User{ID: s.userID}).(*models.User) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: s.repositoryID, OwnerID: user.ID}).(*models.Repository) + repo.Owner = user + + s.commitRepoActionOptions.PusherName = user.Name + s.commitRepoActionOptions.RepoOwnerID = user.ID + s.commitRepoActionOptions.RepoName = repo.Name + + s.action.ActUserID = user.ID + s.action.RepoID = repo.ID + s.action.Repo = repo + s.action.IsPrivate = repo.IsPrivate + + testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action) + } +} diff --git a/modules/repofiles/delete.go b/modules/repofiles/delete.go index 3d9b06b1c1..2210faae6b 100644 --- a/modules/repofiles/delete.go +++ b/modules/repofiles/delete.go @@ -92,6 +92,12 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo // Assigned LastCommitID in opts if it hasn't been set if opts.LastCommitID == "" { opts.LastCommitID = commit.ID.String() + } else { + lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID) + if err != nil { + return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err) + } + opts.LastCommitID = lastCommitID.String() } // Get the files in the index @@ -172,32 +178,6 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo return nil, err } - // Simulate push event. - oldCommitID := opts.LastCommitID - if opts.NewBranch != opts.OldBranch { - oldCommitID = git.EmptySHA - } - - if err = repo.GetOwner(); err != nil { - return nil, fmt.Errorf("GetOwner: %v", err) - } - err = PushUpdate( - repo, - opts.NewBranch, - models.PushUpdateOptions{ - PusherID: doer.ID, - PusherName: doer.Name, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - RefFullName: git.BranchPrefix + opts.NewBranch, - OldCommitID: oldCommitID, - NewCommitID: commitHash, - }, - ) - if err != nil { - return nil, fmt.Errorf("PushUpdate: %v", err) - } - commit, err = t.GetCommit(commitHash) if err != nil { return nil, err diff --git a/modules/repofiles/diff.go b/modules/repofiles/diff.go index 3b5de5fa6f..c98bbc7684 100644 --- a/modules/repofiles/diff.go +++ b/modules/repofiles/diff.go @@ -8,10 +8,11 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/services/gitdiff" ) // GetDiffPreview produces and returns diff result of a file which is not yet committed. -func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*models.Diff, error) { +func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*gitdiff.Diff, error) { if branch == "" { branch = repo.DefaultBranch } diff --git a/modules/repofiles/diff_test.go b/modules/repofiles/diff_test.go index bc7d4ebad6..de5ed1d754 100644 --- a/modules/repofiles/diff_test.go +++ b/modules/repofiles/diff_test.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/gitdiff" "github.com/stretchr/testify/assert" ) @@ -25,10 +26,10 @@ func TestGetDiffPreview(t *testing.T) { treePath := "README.md" content := "# repo1\n\nDescription for repo1\nthis is a new line" - expectedDiff := &models.Diff{ + expectedDiff := &gitdiff.Diff{ TotalAddition: 2, TotalDeletion: 1, - Files: []*models.DiffFile{ + Files: []*gitdiff.DiffFile{ { Name: "README.md", OldName: "README.md", @@ -42,10 +43,10 @@ func TestGetDiffPreview(t *testing.T) { IsLFSFile: false, IsRenamed: false, IsSubmodule: false, - Sections: []*models.DiffSection{ + Sections: []*gitdiff.DiffSection{ { Name: "", - Lines: []*models.DiffLine{ + Lines: []*gitdiff.DiffLine{ { LeftIdx: 0, RightIdx: 0, diff --git a/modules/repofiles/file_test.go b/modules/repofiles/file_test.go index 00feb93fff..7c45139dd9 100644 --- a/modules/repofiles/file_test.go +++ b/modules/repofiles/file_test.go @@ -5,11 +5,11 @@ package repofiles import ( - "code.gitea.io/gitea/modules/setting" "testing" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go index ef75709c23..f791c3cb96 100644 --- a/modules/repofiles/temp_repo.go +++ b/modules/repofiles/temp_repo.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/gitdiff" ) // TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone @@ -249,7 +250,7 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro // CommitTree creates a commit from a given tree for the user with provided message func (t *TemporaryUploadRepository) CommitTree(author, committer *models.User, treeHash string, message string) (string, error) { - commitTimeStr := time.Now().Format(time.UnixDate) + commitTimeStr := time.Now().Format(time.RFC3339) authorSig := author.NewGitSig() committerSig := committer.NewGitSig() @@ -290,7 +291,7 @@ func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, b } // DiffIndex returns a Diff of the current index to the head -func (t *TemporaryUploadRepository) DiffIndex() (diff *models.Diff, err error) { +func (t *TemporaryUploadRepository) DiffIndex() (diff *gitdiff.Diff, err error) { timeout := 5 * time.Minute ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -313,7 +314,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (diff *models.Diff, err error) { pid := process.GetManager().Add(fmt.Sprintf("diffIndex [repo_path: %s]", t.repo.RepoPath()), cmd) defer process.GetManager().Remove(pid) - diff, err = models.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout) + diff, err = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout) if err != nil { return nil, fmt.Errorf("ParsePatch: %v", err) } diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index f011017a5e..ee1b16bce9 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -6,20 +6,22 @@ package repofiles import ( "bytes" + "container/list" "fmt" "path" "strings" - "golang.org/x/net/html/charset" - "golang.org/x/text/transform" - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + + stdcharset "golang.org/x/net/html/charset" + "golang.org/x/text/transform" ) // IdentityOptions for a person's identity like an author or committer @@ -85,15 +87,15 @@ func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string } - encoding, err := base.DetectEncoding(buf) + encoding, err := charset.DetectEncoding(buf) if err != nil { // just default to utf-8 and no bom return "UTF-8", false } if encoding == "UTF-8" { - return encoding, bytes.Equal(buf[0:3], base.UTF8BOM) + return encoding, bytes.Equal(buf[0:3], charset.UTF8BOM) } - charsetEncoding, _ := charset.Lookup(encoding) + charsetEncoding, _ := stdcharset.Lookup(encoding) if charsetEncoding == nil { return "UTF-8", false } @@ -105,7 +107,7 @@ func detectEncodingAndBOM(entry *git.TreeEntry, repo *models.Repository) (string } if n > 2 { - return encoding, bytes.Equal([]byte(result)[0:3], base.UTF8BOM) + return encoding, bytes.Equal([]byte(result)[0:3], charset.UTF8BOM) } return encoding, false @@ -188,6 +190,13 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up // Assigned LastCommitID in opts if it hasn't been set if opts.LastCommitID == "" { opts.LastCommitID = commit.ID.String() + } else { + lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID) + if err != nil { + return nil, fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err) + } + opts.LastCommitID = lastCommitID.String() + } encoding := "UTF-8" @@ -312,12 +321,12 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up content := opts.Content if bom { - content = string(base.UTF8BOM) + content + content = string(charset.UTF8BOM) + content } if encoding != "UTF-8" { - charsetEncoding, _ := charset.Lookup(encoding) + charsetEncoding, _ := stdcharset.Lookup(encoding) if charsetEncoding != nil { - result, _, err := transform.String(charsetEncoding.NewEncoder(), string(content)) + result, _, err := transform.String(charsetEncoding.NewEncoder(), content) if err != nil { // Look if we can't encode back in to the original we should just stick with utf-8 log.Error("Error re-encoding %s (%s) as %s - will stay as UTF-8: %v", opts.TreePath, opts.FromTreePath, encoding, err) @@ -387,32 +396,6 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up return nil, err } - // Simulate push event. - oldCommitID := opts.LastCommitID - if opts.NewBranch != opts.OldBranch || oldCommitID == "" { - oldCommitID = git.EmptySHA - } - - if err = repo.GetOwner(); err != nil { - return nil, fmt.Errorf("GetOwner: %v", err) - } - err = PushUpdate( - repo, - opts.NewBranch, - models.PushUpdateOptions{ - PusherID: doer.ID, - PusherName: doer.Name, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - RefFullName: git.BranchPrefix + opts.NewBranch, - OldCommitID: oldCommitID, - NewCommitID: commitHash, - }, - ) - if err != nil { - return nil, fmt.Errorf("PushUpdate: %v", err) - } - commit, err = t.GetCommit(commitHash) if err != nil { return nil, err @@ -428,11 +411,94 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up // PushUpdate must be called for any push actions in order to // generates necessary push action history feeds and other operations func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOptions) error { - err := models.PushUpdate(branch, opts) - if err != nil { - return fmt.Errorf("PushUpdate: %v", err) + isNewRef := opts.OldCommitID == git.EmptySHA + isDelRef := opts.NewCommitID == git.EmptySHA + if isNewRef && isDelRef { + return fmt.Errorf("Old and new revisions are both %s", git.EmptySHA) } + repoPath := models.RepoPath(opts.RepoUserName, opts.RepoName) + + _, err := git.NewCommand("update-server-info").RunInDir(repoPath) + if err != nil { + return fmt.Errorf("Failed to call 'git update-server-info': %v", err) + } + + gitRepo, err := git.OpenRepository(repoPath) + if err != nil { + return fmt.Errorf("OpenRepository: %v", err) + } + + if err = repo.UpdateSize(); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + + var commits = &models.PushCommits{} + if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { + // If is tag reference + tagName := opts.RefFullName[len(git.TagPrefix):] + if isDelRef { + err = models.PushUpdateDeleteTag(repo, tagName) + if err != nil { + return fmt.Errorf("PushUpdateDeleteTag: %v", err) + } + } else { + // Clear cache for tag commit count + cache.Remove(repo.GetCommitsCountCacheKey(tagName, true)) + err = models.PushUpdateAddTag(repo, gitRepo, tagName) + if err != nil { + return fmt.Errorf("PushUpdateAddTag: %v", err) + } + } + } else if !isDelRef { + // If is branch reference + + // Clear cache for branch commit count + cache.Remove(repo.GetCommitsCountCacheKey(opts.RefFullName[len(git.BranchPrefix):], true)) + + newCommit, err := gitRepo.GetCommit(opts.NewCommitID) + if err != nil { + return fmt.Errorf("gitRepo.GetCommit: %v", err) + } + + // Push new branch. + var l *list.List + if isNewRef { + l, err = newCommit.CommitsBeforeLimit(10) + if err != nil { + return fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err) + } + } else { + l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) + if err != nil { + return fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err) + } + } + + commits = models.ListToPushCommits(l) + } + + if err := CommitRepoAction(CommitRepoActionOptions{ + PusherName: opts.PusherName, + RepoOwnerID: repo.OwnerID, + RepoName: repo.Name, + RefFullName: opts.RefFullName, + OldCommitID: opts.OldCommitID, + NewCommitID: opts.NewCommitID, + Commits: commits, + }); err != nil { + return fmt.Errorf("CommitRepoAction: %v", err) + } + + pusher, err := models.GetUserByID(opts.PusherID) + if err != nil { + return err + } + + log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name) + + go models.AddTestPullRequestTask(pusher, repo.ID, branch, true) + if opts.RefFullName == git.BranchPrefix+repo.DefaultBranch { models.UpdateRepoIndexer(repo) } diff --git a/modules/repofiles/upload.go b/modules/repofiles/upload.go index 2da101c64d..f2ffec7ebc 100644 --- a/modules/repofiles/upload.go +++ b/modules/repofiles/upload.go @@ -11,7 +11,6 @@ import ( "strings" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" ) @@ -177,31 +176,5 @@ func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRep return err } - // Simulate push event. - oldCommitID := opts.LastCommitID - if opts.NewBranch != opts.OldBranch { - oldCommitID = git.EmptySHA - } - - if err = repo.GetOwner(); err != nil { - return fmt.Errorf("GetOwner: %v", err) - } - err = PushUpdate( - repo, - opts.NewBranch, - models.PushUpdateOptions{ - PusherID: doer.ID, - PusherName: doer.Name, - RepoUserName: repo.Owner.Name, - RepoName: repo.Name, - RefFullName: git.BranchPrefix + opts.NewBranch, - OldCommitID: oldCommitID, - NewCommitID: commitHash, - }, - ) - if err != nil { - return fmt.Errorf("PushUpdate: %v", err) - } - return models.DeleteUploads(uploads...) } diff --git a/modules/session/memory.go b/modules/session/memory.go index 9c493bfe15..4f72feac9b 100644 --- a/modules/session/memory.go +++ b/modules/session/memory.go @@ -22,7 +22,7 @@ import ( "sync" "time" - "github.com/go-macaron/session" + "gitea.com/macaron/session" ) // MemStore represents a in-memory session store implementation. @@ -148,8 +148,8 @@ func (p *MemProvider) Exist(sid string) bool { return ok } -// Destory deletes a session by session ID. -func (p *MemProvider) Destory(sid string) error { +// Destroy deletes a session by session ID. +func (p *MemProvider) Destroy(sid string) error { p.lock.Lock() defer p.lock.Unlock() @@ -174,7 +174,7 @@ func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) { return nil, err } - if err = p.Destory(oldsid); err != nil { + if err = p.Destroy(oldsid); err != nil { return nil, err } diff --git a/modules/session/virtual.go b/modules/session/virtual.go index b8ddd2f71b..027960a289 100644 --- a/modules/session/virtual.go +++ b/modules/session/virtual.go @@ -10,13 +10,13 @@ import ( "fmt" "sync" - "github.com/go-macaron/session" - couchbase "github.com/go-macaron/session/couchbase" - memcache "github.com/go-macaron/session/memcache" - mysql "github.com/go-macaron/session/mysql" - nodb "github.com/go-macaron/session/nodb" - postgres "github.com/go-macaron/session/postgres" - redis "github.com/go-macaron/session/redis" + "gitea.com/macaron/session" + couchbase "gitea.com/macaron/session/couchbase" + memcache "gitea.com/macaron/session/memcache" + mysql "gitea.com/macaron/session/mysql" + nodb "gitea.com/macaron/session/nodb" + postgres "gitea.com/macaron/session/postgres" + redis "gitea.com/macaron/session/redis" ) // VirtualSessionProvider represents a shadowed session provider implementation. @@ -75,11 +75,11 @@ func (o *VirtualSessionProvider) Exist(sid string) bool { return true } -// Destory deletes a session by session ID. -func (o *VirtualSessionProvider) Destory(sid string) error { +// Destroy deletes a session by session ID. +func (o *VirtualSessionProvider) Destroy(sid string) error { o.lock.Lock() defer o.lock.Unlock() - return o.provider.Destory(sid) + return o.provider.Destroy(sid) } // Regenerate regenerates a session store from old session ID to new one. diff --git a/modules/setting/cache.go b/modules/setting/cache.go index babb62baea..7be24b865f 100644 --- a/modules/setting/cache.go +++ b/modules/setting/cache.go @@ -9,6 +9,9 @@ import ( "time" "code.gitea.io/gitea/modules/log" + + _ "gitea.com/macaron/cache/memcache" // memcache plugin for cache + _ "gitea.com/macaron/cache/redis" ) // Cache represents cache settings diff --git a/modules/setting/cors.go b/modules/setting/cors.go index c1c3bfb813..04f3120536 100644 --- a/modules/setting/cors.go +++ b/modules/setting/cors.go @@ -9,7 +9,7 @@ import ( "code.gitea.io/gitea/modules/log" - "github.com/go-macaron/cors" + "gitea.com/macaron/cors" ) var ( @@ -28,7 +28,7 @@ func newCORSService() { CORSConfig = cors.Options{ Scheme: sec.Key("SCHEME").String(), - AllowDomain: sec.Key("ALLOW_DOMAIN").String(), + AllowDomain: sec.Key("ALLOW_DOMAIN").Strings(","), AllowSubdomain: sec.Key("ALLOW_SUBDOMAIN").MustBool(), Methods: sec.Key("METHODS").Strings(","), MaxAgeSeconds: int(maxAge.Seconds()), diff --git a/modules/setting/database.go b/modules/setting/database.go new file mode 100644 index 0000000000..2cac4824df --- /dev/null +++ b/modules/setting/database.go @@ -0,0 +1,171 @@ +// Copyright 2019 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 setting + +import ( + "errors" + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +var ( + // SupportedDatabases includes all supported databases type + SupportedDatabases = []string{"MySQL", "PostgreSQL", "MSSQL"} + dbTypes = map[string]string{"MySQL": "mysql", "PostgreSQL": "postgres", "MSSQL": "mssql", "SQLite3": "sqlite3"} + + // EnableSQLite3 use SQLite3, set by build flag + EnableSQLite3 bool + + // Database holds the database settings + Database = struct { + Type string + Host string + Name string + User string + Passwd string + SSLMode string + Path string + LogSQL bool + Charset string + Timeout int // seconds + UseSQLite3 bool + UseMySQL bool + UseMSSQL bool + UsePostgreSQL bool + DBConnectRetries int + DBConnectBackoff time.Duration + MaxIdleConns int + ConnMaxLifetime time.Duration + IterateBufferSize int + }{ + Timeout: 500, + MaxIdleConns: 0, + ConnMaxLifetime: 3 * time.Second, + } +) + +// GetDBTypeByName returns the dataase type as it defined on XORM according the given name +func GetDBTypeByName(name string) string { + return dbTypes[name] +} + +// InitDBConfig loads the database settings +func InitDBConfig() { + sec := Cfg.Section("database") + Database.Type = sec.Key("DB_TYPE").String() + switch Database.Type { + case "sqlite3": + Database.UseSQLite3 = true + case "mysql": + Database.UseMySQL = true + case "postgres": + Database.UsePostgreSQL = true + case "mssql": + Database.UseMSSQL = true + } + Database.Host = sec.Key("HOST").String() + Database.Name = sec.Key("NAME").String() + Database.User = sec.Key("USER").String() + if len(Database.Passwd) == 0 { + Database.Passwd = sec.Key("PASSWD").String() + } + Database.SSLMode = sec.Key("SSL_MODE").MustString("disable") + Database.Charset = sec.Key("CHARSET").In("utf8", []string{"utf8", "utf8mb4"}) + Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db")) + Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500) + Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(0) + Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFE_TIME").MustDuration(3 * time.Second) + + Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50) + Database.LogSQL = sec.Key("LOG_SQL").MustBool(true) + Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) + Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) +} + +// DBConnStr returns database connection string +func DBConnStr() (string, error) { + connStr := "" + var Param = "?" + if strings.Contains(Database.Name, Param) { + Param = "&" + } + switch Database.Type { + case "mysql": + connType := "tcp" + if Database.Host[0] == '/' { // looks like a unix socket + connType = "unix" + } + tls := Database.SSLMode + if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL + tls = "false" + } + connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s", + Database.User, Database.Passwd, connType, Database.Host, Database.Name, Param, Database.Charset, tls) + case "postgres": + connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Param, Database.SSLMode) + case "mssql": + host, port := ParseMSSQLHostPort(Database.Host) + connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd) + case "sqlite3": + if !EnableSQLite3 { + return "", errors.New("this binary version does not build support for SQLite3") + } + if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil { + return "", fmt.Errorf("Failed to create directories: %v", err) + } + connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d", Database.Path, Database.Timeout) + default: + return "", fmt.Errorf("Unknown database type: %s", Database.Type) + } + + return connStr, nil +} + +// parsePostgreSQLHostPort parses given input in various forms defined in +// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING +// and returns proper host and port number. +func parsePostgreSQLHostPort(info string) (string, string) { + host, port := "127.0.0.1", "5432" + if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") { + idx := strings.LastIndex(info, ":") + host = info[:idx] + port = info[idx+1:] + } else if len(info) > 0 { + host = info + } + 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 +} + +// ParseMSSQLHostPort splits the host into host and port +func ParseMSSQLHostPort(info string) (string, string) { + host, port := "127.0.0.1", "1433" + if strings.Contains(info, ":") { + host = strings.Split(info, ":")[0] + port = strings.Split(info, ":")[1] + } else if strings.Contains(info, ",") { + host = strings.Split(info, ",")[0] + port = strings.TrimSpace(strings.Split(info, ",")[1]) + } else if len(info) > 0 { + host = info + } + return host, port +} diff --git a/models/models_sqlite.go b/modules/setting/database_sqlite.go similarity index 77% rename from models/models_sqlite.go rename to modules/setting/database_sqlite.go index 3889aceb84..623326ddc2 100644 --- a/models/models_sqlite.go +++ b/modules/setting/database_sqlite.go @@ -4,7 +4,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package setting import ( _ "github.com/mattn/go-sqlite3" @@ -12,5 +12,5 @@ import ( func init() { EnableSQLite3 = true - supportedDatabases = append(supportedDatabases, "sqlite3") + SupportedDatabases = append(SupportedDatabases, "SQLite3") } diff --git a/modules/setting/database_test.go b/modules/setting/database_test.go new file mode 100644 index 0000000000..a90be2a687 --- /dev/null +++ b/modules/setting/database_test.go @@ -0,0 +1,94 @@ +// Copyright 2019 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 setting + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_parsePostgreSQLHostPort(t *testing.T) { + tests := []struct { + HostPort string + Host string + Port string + }{ + { + HostPort: "127.0.0.1:1234", + Host: "127.0.0.1", + Port: "1234", + }, + { + HostPort: "127.0.0.1", + Host: "127.0.0.1", + Port: "5432", + }, + { + HostPort: "[::1]:1234", + Host: "[::1]", + Port: "1234", + }, + { + HostPort: "[::1]", + Host: "[::1]", + Port: "5432", + }, + { + HostPort: "/tmp/pg.sock:1234", + Host: "/tmp/pg.sock", + Port: "1234", + }, + { + HostPort: "/tmp/pg.sock", + Host: "/tmp/pg.sock", + Port: "5432", + }, + } + for _, test := range tests { + host, port := parsePostgreSQLHostPort(test.HostPort) + assert.Equal(t, test.Host, host) + 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) + } +} diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go index 36fd4a020b..30c670d407 100644 --- a/modules/setting/indexer.go +++ b/modules/setting/indexer.go @@ -7,6 +7,11 @@ package setting import ( "path" "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/log" + + "github.com/gobwas/glob" ) // enumerates all the indexer queue types @@ -29,6 +34,8 @@ var ( IssueQueueDir string IssueQueueConnStr string IssueQueueBatchNumber int + IncludePatterns []glob.Glob + ExcludePatterns []glob.Glob }{ IssueType: "bleve", IssuePath: "indexers/issues.bleve", @@ -51,6 +58,9 @@ func newIndexerService() { if !filepath.IsAbs(Indexer.RepoPath) { Indexer.RepoPath = path.Join(AppWorkPath, Indexer.RepoPath) } + Indexer.IncludePatterns = IndexerGlobFromString(sec.Key("REPO_INDEXER_INCLUDE").MustString("")) + Indexer.ExcludePatterns = IndexerGlobFromString(sec.Key("REPO_INDEXER_EXCLUDE").MustString("")) + Indexer.UpdateQueueLength = sec.Key("UPDATE_BUFFER_LEN").MustInt(20) Indexer.MaxIndexerFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(1024 * 1024) Indexer.IssueQueueType = sec.Key("ISSUE_INDEXER_QUEUE_TYPE").MustString(LevelQueueType) @@ -58,3 +68,19 @@ func newIndexerService() { Indexer.IssueQueueConnStr = sec.Key("ISSUE_INDEXER_QUEUE_CONN_STR").MustString(path.Join(AppDataPath, "")) Indexer.IssueQueueBatchNumber = sec.Key("ISSUE_INDEXER_QUEUE_BATCH_NUMBER").MustInt(20) } + +// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing +func IndexerGlobFromString(globstr string) []glob.Glob { + extarr := make([]glob.Glob, 0, 10) + for _, expr := range strings.Split(strings.ToLower(globstr), ",") { + expr = strings.TrimSpace(expr) + if expr != "" { + if g, err := glob.Compile(expr, '.', '/'); err != nil { + log.Info("Invalid glob expresion '%s' (skipped): %v", expr, err) + } else { + extarr = append(extarr, g) + } + } + } + return extarr +} diff --git a/modules/setting/indexer_test.go b/modules/setting/indexer_test.go new file mode 100644 index 0000000000..ed631747dc --- /dev/null +++ b/modules/setting/indexer_test.go @@ -0,0 +1,73 @@ +// Copyright 2019 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 setting + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type indexerMatchList struct { + value string + position int +} + +func Test_newIndexerGlobSettings(t *testing.T) { + + checkGlobMatch(t, "", []indexerMatchList{}) + checkGlobMatch(t, " ", []indexerMatchList{}) + checkGlobMatch(t, "data, */data, */data/*, **/data/*, **/data/**", []indexerMatchList{ + {"", -1}, + {"don't", -1}, + {"data", 0}, + {"/data", 1}, + {"x/data", 1}, + {"x/data/y", 2}, + {"a/b/c/data/z", 3}, + {"a/b/c/data/x/y/z", 4}, + }) + checkGlobMatch(t, "*.txt, txt, **.txt, **txt, **txt*", []indexerMatchList{ + {"my.txt", 0}, + {"don't", -1}, + {"mytxt", 3}, + {"/data/my.txt", 2}, + {"data/my.txt", 2}, + {"data/txt", 3}, + {"data/thistxtfile", 4}, + {"/data/thistxtfile", 4}, + }) + checkGlobMatch(t, "data/**/*.txt, data/**.txt", []indexerMatchList{ + {"data/a/b/c/d.txt", 0}, + {"data/a.txt", 1}, + }) + checkGlobMatch(t, "**/*.txt, data/**.txt", []indexerMatchList{ + {"data/a/b/c/d.txt", 0}, + {"data/a.txt", 0}, + {"a.txt", -1}, + }) +} + +func checkGlobMatch(t *testing.T, globstr string, list []indexerMatchList) { + glist := IndexerGlobFromString(globstr) + if len(list) == 0 { + assert.Empty(t, glist) + return + } + assert.NotEmpty(t, glist) + for _, m := range list { + found := false + for pos, g := range glist { + if g.Match(m.value) { + assert.Equal(t, m.position, pos, "Test string `%s` doesn't match `%s`@%d, but matches @%d", m.value, globstr, m.position, pos) + found = true + break + } + } + if !found { + assert.Equal(t, m.position, -1, "Test string `%s` doesn't match `%s` anywhere; expected @%d", m.value, globstr, m.position) + } + } +} diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index 3101ed5452..c692e0fe14 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -8,6 +8,7 @@ import ( "net/mail" "code.gitea.io/gitea/modules/log" + shellquote "github.com/kballard/go-shellquote" ) diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 98e3d6e826..728741576d 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -10,7 +10,8 @@ import ( "strings" "code.gitea.io/gitea/modules/log" - "github.com/Unknwon/com" + + "github.com/unknwon/com" ) // enumerates all the policy repository creating diff --git a/modules/setting/service.go b/modules/setting/service.go index 97babc5aaf..905b1326f7 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -42,6 +42,7 @@ var Service struct { NoReplyAddress string EnableUserHeatmap bool AutoWatchNewRepos bool + DefaultOrgMemberVisible bool // OpenID settings EnableOpenIDSignIn bool @@ -82,6 +83,7 @@ func newService() { Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true) Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes)) Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility] + Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool() sec = Cfg.Section("openid") Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock) diff --git a/modules/setting/session.go b/modules/setting/session.go index 313c3c76b5..6e5a28bb75 100644 --- a/modules/setting/session.go +++ b/modules/setting/session.go @@ -11,10 +11,8 @@ import ( "strings" "code.gitea.io/gitea/modules/log" - // This ensures that VirtualSessionProvider is available - _ "code.gitea.io/gitea/modules/session" - "github.com/go-macaron/session" + "gitea.com/macaron/session" ) var ( @@ -34,6 +32,7 @@ func newSessionService() { SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool(false) SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400) SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400) + SessionConfig.Domain = Cfg.Section("session").Key("DOMAIN").String() shadowConfig, err := json.Marshal(SessionConfig) if err != nil { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 7201f0619d..5e476854b2 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -27,18 +27,10 @@ import ( _ "code.gitea.io/gitea/modules/minwinsvc" // import minwinsvc for windows services "code.gitea.io/gitea/modules/user" - "github.com/Unknwon/cae/zip" - "github.com/Unknwon/com" - _ "github.com/go-macaron/cache/memcache" // memcache plugin for cache - _ "github.com/go-macaron/cache/redis" - _ "github.com/go-macaron/session/couchbase" // couchbase plugin for session store - _ "github.com/go-macaron/session/memcache" // memcache plugin for session store - _ "github.com/go-macaron/session/mysql" // mysql plugin for session store - _ "github.com/go-macaron/session/nodb" // nodb plugin for session store - _ "github.com/go-macaron/session/postgres" // postgres plugin for session store - _ "github.com/go-macaron/session/redis" // redis plugin for store session shellquote "github.com/kballard/go-shellquote" version "github.com/mcuadros/go-version" + "github.com/unknwon/cae/zip" + "github.com/unknwon/com" ini "gopkg.in/ini.v1" "strk.kbt.io/projects/go/libravatar" ) @@ -156,31 +148,22 @@ var ( DisableGitHooks bool PasswordHashAlgo string - // Database settings - UseSQLite3 bool - UseMySQL bool - UseMSSQL bool - UsePostgreSQL bool - UseTiDB bool - LogSQL bool - DBConnectRetries int - DBConnectBackoff time.Duration - // UI settings UI = struct { - ExplorePagingNum int - IssuePagingNum int - RepoSearchPagingNum int - FeedMaxCommitNum int - GraphMaxCommitNum int - CodeCommentLines int - ReactionMaxUserNum int - ThemeColorMetaTag string - MaxDisplayFileSize int64 - ShowUserEmail bool - DefaultShowFullName bool - DefaultTheme string - Themes []string + ExplorePagingNum int + IssuePagingNum int + RepoSearchPagingNum int + FeedMaxCommitNum int + GraphMaxCommitNum int + CodeCommentLines int + ReactionMaxUserNum int + ThemeColorMetaTag string + MaxDisplayFileSize int64 + ShowUserEmail bool + DefaultShowFullName bool + DefaultTheme string + Themes []string + SearchRepoDescription bool Admin struct { UserPagingNum int @@ -248,6 +231,7 @@ var ( // Admin settings Admin struct { DisableRegularOrgCreation bool + DefaultEmailNotification string } // Picture settings @@ -286,8 +270,11 @@ var ( // Time settings TimeFormat string + // UILocation is the location on the UI, so that we can display the time on UI. + DefaultUILocation = time.Local - CSRFCookieName = "_csrf" + CSRFCookieName = "_csrf" + CSRFCookieHTTPOnly = true // Mirror settings Mirror struct { @@ -353,16 +340,15 @@ var ( ShowFooterTemplateLoadTime bool // Global setting objects - Cfg *ini.File - CustomPath string // Custom directory path - CustomConf string - CustomPID string - ProdMode bool - RunUser string - IsWindows bool - HasRobotsTxt bool - InternalToken string // internal access token - IterateBufferSize int + Cfg *ini.File + CustomPath string // Custom directory path + CustomConf string + CustomPID string + ProdMode bool + RunUser string + IsWindows bool + HasRobotsTxt bool + InternalToken string // internal access token // UILocation is the location on the UI, so that we can display the time on UI. // Currently only show the default time.Local, it could be added to app.ini after UI is ready @@ -769,6 +755,9 @@ func NewContext() { } } + sec = Cfg.Section("admin") + Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") + sec = Cfg.Section("security") InstallLock = sec.Key("INSTALL_LOCK").MustBool(false) SecretKey = sec.Key("SECRET_KEY").MustString("!#@FDEWREWR&*(") @@ -781,11 +770,9 @@ func NewContext() { ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false) DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(false) PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2") + CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) + InternalToken = loadInternalToken(sec) - IterateBufferSize = Cfg.Section("database").Key("ITERATE_BUFFER_SIZE").MustInt(50) - LogSQL = Cfg.Section("database").Key("LOG_SQL").MustBool(true) - DBConnectRetries = Cfg.Section("database").Key("DB_RETRIES").MustInt(10) - DBConnectBackoff = Cfg.Section("database").Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) sec = Cfg.Section("attachment") AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) @@ -797,32 +784,47 @@ func NewContext() { AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5) AttachmentEnabled = sec.Key("ENABLED").MustBool(true) - TimeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("RFC1123") - TimeFormat = map[string]string{ - "ANSIC": time.ANSIC, - "UnixDate": time.UnixDate, - "RubyDate": time.RubyDate, - "RFC822": time.RFC822, - "RFC822Z": time.RFC822Z, - "RFC850": time.RFC850, - "RFC1123": time.RFC1123, - "RFC1123Z": time.RFC1123Z, - "RFC3339": time.RFC3339, - "RFC3339Nano": time.RFC3339Nano, - "Kitchen": time.Kitchen, - "Stamp": time.Stamp, - "StampMilli": time.StampMilli, - "StampMicro": time.StampMicro, - "StampNano": time.StampNano, - }[TimeFormatKey] - // When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05' - if len(TimeFormat) == 0 { - TimeFormat = TimeFormatKey - TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat) - if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" { - log.Fatal("Can't create time properly, please check your time format has 2006, 01, 02, 15, 04 and 05") + timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("") + if timeFormatKey != "" { + TimeFormat = map[string]string{ + "ANSIC": time.ANSIC, + "UnixDate": time.UnixDate, + "RubyDate": time.RubyDate, + "RFC822": time.RFC822, + "RFC822Z": time.RFC822Z, + "RFC850": time.RFC850, + "RFC1123": time.RFC1123, + "RFC1123Z": time.RFC1123Z, + "RFC3339": time.RFC3339, + "RFC3339Nano": time.RFC3339Nano, + "Kitchen": time.Kitchen, + "Stamp": time.Stamp, + "StampMilli": time.StampMilli, + "StampMicro": time.StampMicro, + "StampNano": time.StampNano, + }[timeFormatKey] + // When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05' + if len(TimeFormat) == 0 { + TimeFormat = timeFormatKey + TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat) + if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" { + log.Fatal("Can't create time properly, please check your time format has 2006, 01, 02, 15, 04 and 05") + } + log.Trace("Custom TimeFormat: %s", TimeFormat) } - log.Trace("Custom TimeFormat: %s", TimeFormat) + } + + zone := Cfg.Section("time").Key("DEFAULT_UI_LOCATION").String() + if zone != "" { + DefaultUILocation, err = time.LoadLocation(zone) + if err != nil { + log.Fatal("Load time zone failed: %v", err) + } else { + log.Info("Default UI Location is %v", zone) + } + } + if DefaultUILocation == nil { + DefaultUILocation = time.Local } RunUser = Cfg.Section("").Key("RUN_USER").MustString(user.CurrentUsername()) @@ -945,6 +947,7 @@ func NewContext() { UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true) UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) + UI.SearchRepoDescription = Cfg.Section("ui").Key("SEARCH_REPO_DESCRIPTION").MustBool(true) HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt")) @@ -1029,6 +1032,7 @@ func loadOrGenerateInternalToken(sec *ini.Section) string { // NewServices initializes the services func NewServices() { + InitDBConfig() newService() NewLogServices(false) newCacheService() diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 1818f33306..7ff0c32326 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -22,8 +22,8 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" "github.com/gliderlabs/ssh" + "github.com/unknwon/com" gossh "golang.org/x/crypto/ssh" ) diff --git a/modules/structs/hook.go b/modules/structs/hook.go index 8dae578ec6..9a25219e36 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -40,17 +40,19 @@ type CreateHookOption struct { // enum: gitea,gogs,slack,discord Type string `json:"type" binding:"Required"` // required: true - Config map[string]string `json:"config" binding:"Required"` - Events []string `json:"events"` + Config map[string]string `json:"config" binding:"Required"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter" binding:"GlobPattern"` // default: false Active bool `json:"active"` } // EditHookOption options when modify one hook type EditHookOption struct { - Config map[string]string `json:"config"` - Events []string `json:"events"` - Active *bool `json:"active"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter" binding:"GlobPattern"` + Active *bool `json:"active"` } // Payloader payload is some part of one hook diff --git a/modules/structs/miscellaneous.go b/modules/structs/miscellaneous.go index 8eca90330e..c21c466cb0 100644 --- a/modules/structs/miscellaneous.go +++ b/modules/structs/miscellaneous.go @@ -44,3 +44,9 @@ type MarkdownRender string type ServerVersion struct { Version string `json:"version"` } + +// APIError is an api error with a message +type APIError struct { + Message string `json:"message"` + URL string `json:"url"` +} diff --git a/modules/structs/org.go b/modules/structs/org.go index 08ab139975..4b79a4e70a 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -6,14 +6,15 @@ package structs // Organization represents an organization type Organization struct { - ID int64 `json:"id"` - UserName string `json:"username"` - FullName string `json:"full_name"` - AvatarURL string `json:"avatar_url"` - Description string `json:"description"` - Website string `json:"website"` - Location string `json:"location"` - Visibility string `json:"visibility"` + ID int64 `json:"id"` + UserName string `json:"username"` + FullName string `json:"full_name"` + AvatarURL string `json:"avatar_url"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility string `json:"visibility"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` } // CreateOrgOption options for creating an organization @@ -26,7 +27,8 @@ type CreateOrgOption struct { Location string `json:"location"` // possible values are `public` (default), `limited` or `private` // enum: public,limited,private - Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` } // EditOrgOption options for editing an organization @@ -37,5 +39,6 @@ type EditOrgOption struct { Location string `json:"location"` // possible values are `public`, `limited` or `private` // enum: public,limited,private - Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` } diff --git a/modules/structs/org_team.go b/modules/structs/org_team.go index ff4fd51cae..5053468b4a 100644 --- a/modules/structs/org_team.go +++ b/modules/structs/org_team.go @@ -14,7 +14,7 @@ type Team struct { IncludesAllRepositories bool `json:"includes_all_repositories"` // enum: none,read,write,admin,owner Permission string `json:"permission"` - // enum: repo.code,repo.issues,repo.ext_issues,repo.wiki,repo.pulls,repo.releases,repo.ext_wiki + // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"] Units []string `json:"units"` } @@ -26,7 +26,7 @@ type CreateTeamOption struct { IncludesAllRepositories bool `json:"includes_all_repositories"` // enum: read,write,admin Permission string `json:"permission"` - // enum: repo.code,repo.issues,repo.ext_issues,repo.wiki,repo.pulls,repo.releases,repo.ext_wiki + // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"] Units []string `json:"units"` } @@ -38,6 +38,6 @@ type EditTeamOption struct { IncludesAllRepositories bool `json:"includes_all_repositories"` // enum: read,write,admin Permission string `json:"permission"` - // enum: repo.code,repo.issues,repo.ext_issues,repo.wiki,repo.pulls,repo.releases,repo.ext_wiki + // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"] Units []string `json:"units"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 81203319e0..87396d6ce9 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -15,6 +15,35 @@ type Permission struct { Pull bool `json:"pull"` } +// InternalTracker represents settings for internal tracker +// swagger:model +type InternalTracker struct { + // Enable time tracking (Built-in issue tracker) + EnableTimeTracker bool `json:"enable_time_tracker"` + // Let only contributors track time (Built-in issue tracker) + AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"` + // Enable dependencies for issues and pull requests (Built-in issue tracker) + EnableIssueDependencies bool `json:"enable_issue_dependencies"` +} + +// ExternalTracker represents settings for external tracker +// swagger:model +type ExternalTracker struct { + // URL of external issue tracker. + ExternalTrackerURL string `json:"external_tracker_url"` + // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. + ExternalTrackerFormat string `json:"external_tracker_format"` + // External Issue Tracker Number Format, either `numeric` or `alphanumeric` + ExternalTrackerStyle string `json:"external_tracker_style"` +} + +// ExternalWiki represents setting for external wiki +// swagger:model +type ExternalWiki struct { + // URL of external wiki. + ExternalWikiURL string `json:"external_wiki_url"` +} + // Repository represents a repository type Repository struct { ID int64 `json:"id"` @@ -42,17 +71,20 @@ type Repository struct { // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time - Updated time.Time `json:"updated_at"` - Permissions *Permission `json:"permissions,omitempty"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPullRequests bool `json:"has_pull_requests"` - IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` - AllowMerge bool `json:"allow_merge_commits"` - AllowRebase bool `json:"allow_rebase"` - AllowRebaseMerge bool `json:"allow_rebase_explicit"` - AllowSquash bool `json:"allow_squash_merge"` - AvatarURL string `json:"avatar_url"` + Updated time.Time `json:"updated_at"` + Permissions *Permission `json:"permissions,omitempty"` + HasIssues bool `json:"has_issues"` + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + HasWiki bool `json:"has_wiki"` + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + HasPullRequests bool `json:"has_pull_requests"` + IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` + AllowMerge bool `json:"allow_merge_commits"` + AllowRebase bool `json:"allow_rebase"` + AllowRebaseMerge bool `json:"allow_rebase_explicit"` + AllowSquash bool `json:"allow_squash_merge"` + AvatarURL string `json:"avatar_url"` } // CreateRepoOption options when creating repository @@ -67,6 +99,8 @@ type CreateRepoOption struct { Description string `json:"description" binding:"MaxSize(255)"` // Whether the repository is private Private bool `json:"private"` + // Issue Label set to use + IssueLabels string `json:"issue_labels"` // Whether the repository should be auto-intialized? AutoInit bool `json:"auto_init"` // Gitignores to use @@ -93,8 +127,14 @@ type EditRepoOption struct { Private *bool `json:"private,omitempty"` // either `true` to enable issues for this repository or `false` to disable them. HasIssues *bool `json:"has_issues,omitempty"` + // set this structure to configure internal issue tracker (requires has_issues) + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + // set this structure to use external issue tracker (requires has_issues) + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` // either `true` to enable the wiki for this repository or `false` to disable it. HasWiki *bool `json:"has_wiki,omitempty"` + // set this structure to use external wiki instead of internal (requires has_wiki) + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` // sets the default branch for this repository. DefaultBranch *string `json:"default_branch,omitempty"` // either `true` to allow pull requests, or `false` to prevent pull request. diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index b2eeb7f13a..cb836e2e23 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -10,9 +10,9 @@ type FileOptions struct { // message (optional) for the commit of this file. if not supplied, a default message will be used Message string `json:"message"` // branch (optional) to base this file from. if not given, the default branch is used - BranchName string `json:"branch"` + BranchName string `json:"branch" binding:"GitRefName;MaxSize(100)"` // new_branch (optional) will make a new branch from `branch` before creating the file - NewBranchName string `json:"new_branch"` + NewBranchName string `json:"new_branch" binding:"GitRefName;MaxSize(100)"` // `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) Author Identity `json:"author"` Committer Identity `json:"committer"` diff --git a/modules/structs/repo_topic.go b/modules/structs/repo_topic.go new file mode 100644 index 0000000000..294d56a953 --- /dev/null +++ b/modules/structs/repo_topic.go @@ -0,0 +1,29 @@ +// Copyright 2019 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 structs + +import ( + "time" +) + +// TopicResponse for returning topics +type TopicResponse struct { + ID int64 `json:"id"` + Name string `json:"topic_name"` + RepoCount int `json:"repo_count"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} + +// TopicName a list of repo topic names +type TopicName struct { + TopicNames []string `json:"topics"` +} + +// RepoTopicOptions a collection of repo topic names +type RepoTopicOptions struct { + // list of topic names + Topics []string `json:"topics"` +} diff --git a/modules/sync/unique_queue.go b/modules/sync/unique_queue.go index ca7c21ac24..de694d8560 100644 --- a/modules/sync/unique_queue.go +++ b/modules/sync/unique_queue.go @@ -5,7 +5,7 @@ package sync import ( - "github.com/Unknwon/com" + "github.com/unknwon/com" ) // UniqueQueue is a queue which guarantees only one instance of same diff --git a/modules/templates/dynamic.go b/modules/templates/dynamic.go index d7c04ccb09..6217f1c3b0 100644 --- a/modules/templates/dynamic.go +++ b/modules/templates/dynamic.go @@ -14,8 +14,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" - "gopkg.in/macaron.v1" + + "gitea.com/macaron/macaron" + "github.com/unknwon/com" ) var ( diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 5a3969c098..b40f7117f5 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -20,16 +20,16 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/gitdiff" + mirror_service "code.gitea.io/gitea/services/mirror" - "golang.org/x/net/html/charset" - "golang.org/x/text/transform" "gopkg.in/editorconfig/editorconfig-core-go.v1" ) @@ -76,9 +76,9 @@ func NewFuncMap() []template.FuncMap { "Safe": Safe, "SafeJS": SafeJS, "Str2html": Str2html, - "TimeSince": base.TimeSince, - "TimeSinceUnix": base.TimeSinceUnix, - "RawTimeSince": base.RawTimeSince, + "TimeSince": timeutil.TimeSince, + "TimeSinceUnix": timeutil.TimeSinceUnix, + "RawTimeSince": timeutil.RawTimeSince, "FileSize": base.FileSize, "Subtract": base.Subtract, "EntryIcon": base.EntryIcon, @@ -93,10 +93,8 @@ func NewFuncMap() []template.FuncMap { "DateFmtShort": func(t time.Time) string { return t.Format("Jan 02, 2006") }, - "SizeFmt": func(s int64) string { - return base.FileSize(s) - }, - "List": List, + "SizeFmt": base.FileSize, + "List": List, "SubStr": func(str string, start, length int) string { if len(str) == 0 { return "" @@ -121,13 +119,14 @@ func NewFuncMap() []template.FuncMap { "EscapePound": func(str string) string { return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) }, - "PathEscapeSegments": util.PathEscapeSegments, - "URLJoin": util.URLJoin, - "RenderCommitMessage": RenderCommitMessage, - "RenderCommitMessageLink": RenderCommitMessageLink, - "RenderCommitBody": RenderCommitBody, - "RenderNote": RenderNote, - "IsMultilineCommitMessage": IsMultilineCommitMessage, + "PathEscapeSegments": util.PathEscapeSegments, + "URLJoin": util.URLJoin, + "RenderCommitMessage": RenderCommitMessage, + "RenderCommitMessageLink": RenderCommitMessageLink, + "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject, + "RenderCommitBody": RenderCommitBody, + "RenderNote": RenderNote, + "IsMultilineCommitMessage": IsMultilineCommitMessage, "ThemeColorMetaTag": func() string { return setting.UI.ThemeColorMetaTag }, @@ -234,6 +233,9 @@ func NewFuncMap() []template.FuncMap { } return float32(n) * 100 / float32(sum) }, + "CommentMustAsDiff": gitdiff.CommentMustAsDiff, + "MirrorAddress": mirror_service.Address, + "MirrorFullAddress": mirror_service.AddressNoCredentials, }} } @@ -276,60 +278,6 @@ func Sha1(str string) string { return base.EncodeSha1(str) } -// ToUTF8WithErr converts content to UTF8 encoding -func ToUTF8WithErr(content []byte) (string, error) { - charsetLabel, err := base.DetectEncoding(content) - if err != nil { - return "", err - } else if charsetLabel == "UTF-8" { - return string(base.RemoveBOMIfPresent(content)), nil - } - - encoding, _ := charset.Lookup(charsetLabel) - if encoding == nil { - return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel) - } - - // If there is an error, we concatenate the nicely decoded part and the - // original left over. This way we won't lose data. - result, n, err := transform.Bytes(encoding.NewDecoder(), content) - if err != nil { - result = append(result, content[n:]...) - } - - result = base.RemoveBOMIfPresent(result) - - return string(result), err -} - -// ToUTF8WithFallback detects the encoding of content and coverts to UTF-8 if possible -func ToUTF8WithFallback(content []byte) []byte { - charsetLabel, err := base.DetectEncoding(content) - if err != nil || charsetLabel == "UTF-8" { - return base.RemoveBOMIfPresent(content) - } - - encoding, _ := charset.Lookup(charsetLabel) - if encoding == nil { - return content - } - - // If there is an error, we concatenate the nicely decoded part and the - // original left over. This way we won't lose data. - result, n, err := transform.Bytes(encoding.NewDecoder(), content) - if err != nil { - return append(result, content[n:]...) - } - - return base.RemoveBOMIfPresent(result) -} - -// ToUTF8 converts content to UTF8 encoding and ignore error -func ToUTF8(content string) string { - res, _ := ToUTF8WithErr([]byte(content)) - return res -} - // ReplaceLeft replaces all prefixes 'oldS' in 's' with 'newS'. func ReplaceLeft(s, oldS, newS string) string { oldLen, newLen, i, n := len(oldS), len(newS), 0, 0 @@ -378,6 +326,24 @@ func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string return template.HTML(msgLines[0]) } +// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to +// the provided default url, handling for special links without email to links. +func RenderCommitMessageLinkSubject(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { + cleanMsg := template.HTMLEscapeString(msg) + // we can safely assume that it will not return any error, since there + // shouldn't be any special HTML. + fullMessage, err := markup.RenderCommitMessageSubject([]byte(cleanMsg), urlPrefix, urlDefault, metas) + if err != nil { + log.Error("RenderCommitMessageSubject: %v", err) + return "" + } + msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n") + if len(msgLines) == 0 { + return template.HTML("") + } + return template.HTML(msgLines[0]) +} + // RenderCommitBody extracts the body of a commit message without its title. func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML { cleanMsg := template.HTMLEscapeString(msg) diff --git a/modules/templates/static.go b/modules/templates/static.go index 3aabe17e4f..f7e53ce887 100644 --- a/modules/templates/static.go +++ b/modules/templates/static.go @@ -17,8 +17,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/Unknwon/com" - "gopkg.in/macaron.v1" + + "gitea.com/macaron/macaron" + "github.com/unknwon/com" ) var ( diff --git a/modules/templates/templates.go b/modules/templates/templates.go index e7fe3b2bfb..af6bf010c1 100644 --- a/modules/templates/templates.go +++ b/modules/templates/templates.go @@ -5,4 +5,3 @@ package templates //go:generate go run -mod=vendor main.go -//go:generate go fmt bindata.go diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go index d5a800d360..92df1c5762 100644 --- a/modules/test/context_tests.go +++ b/modules/test/context_tests.go @@ -14,9 +14,9 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" - "github.com/go-macaron/session" + "gitea.com/macaron/macaron" + "gitea.com/macaron/session" "github.com/stretchr/testify/assert" - "gopkg.in/macaron.v1" ) // MockContext mock context for unit tests diff --git a/modules/timeutil/language.go b/modules/timeutil/language.go new file mode 100644 index 0000000000..121b50f277 --- /dev/null +++ b/modules/timeutil/language.go @@ -0,0 +1,36 @@ +// Copyright 2019 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 timeutil + +import ( + "time" + + "code.gitea.io/gitea/modules/setting" +) + +var ( + langTimeFormats = map[string]string{ + "zh-CN": "2006年01月02日 15时04分05秒", + "en-US": time.RFC1123, + "lv-LV": "02.01.2006. 15:04:05", + } +) + +// GetLangTimeFormat represents the default time format for the language +func GetLangTimeFormat(lang string) string { + return langTimeFormats[lang] +} + +// GetTimeFormat represents the +func GetTimeFormat(lang string) string { + if setting.TimeFormat == "" { + format := GetLangTimeFormat(lang) + if format == "" { + format = time.RFC1123 + } + return format + } + return setting.TimeFormat +} diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go new file mode 100644 index 0000000000..e6c29c19ff --- /dev/null +++ b/modules/timeutil/since.go @@ -0,0 +1,164 @@ +// Copyright 2019 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 timeutil + +import ( + "fmt" + "html/template" + "strings" + "time" + + "code.gitea.io/gitea/modules/setting" + + "github.com/unknwon/i18n" +) + +// Seconds-based time units +const ( + Minute = 60 + Hour = 60 * Minute + Day = 24 * Hour + Week = 7 * Day + Month = 30 * Day + Year = 12 * Month +) + +func computeTimeDiff(diff int64, lang string) (int64, string) { + diffStr := "" + switch { + case diff <= 0: + diff = 0 + diffStr = i18n.Tr(lang, "tool.now") + case diff < 2: + diff = 0 + diffStr = i18n.Tr(lang, "tool.1s") + case diff < 1*Minute: + diffStr = i18n.Tr(lang, "tool.seconds", diff) + diff = 0 + + case diff < 2*Minute: + diff -= 1 * Minute + diffStr = i18n.Tr(lang, "tool.1m") + case diff < 1*Hour: + diffStr = i18n.Tr(lang, "tool.minutes", diff/Minute) + diff -= diff / Minute * Minute + + case diff < 2*Hour: + diff -= 1 * Hour + diffStr = i18n.Tr(lang, "tool.1h") + case diff < 1*Day: + diffStr = i18n.Tr(lang, "tool.hours", diff/Hour) + diff -= diff / Hour * Hour + + case diff < 2*Day: + diff -= 1 * Day + diffStr = i18n.Tr(lang, "tool.1d") + case diff < 1*Week: + diffStr = i18n.Tr(lang, "tool.days", diff/Day) + diff -= diff / Day * Day + + case diff < 2*Week: + diff -= 1 * Week + diffStr = i18n.Tr(lang, "tool.1w") + case diff < 1*Month: + diffStr = i18n.Tr(lang, "tool.weeks", diff/Week) + diff -= diff / Week * Week + + case diff < 2*Month: + diff -= 1 * Month + diffStr = i18n.Tr(lang, "tool.1mon") + case diff < 1*Year: + diffStr = i18n.Tr(lang, "tool.months", diff/Month) + diff -= diff / Month * Month + + case diff < 2*Year: + diff -= 1 * Year + diffStr = i18n.Tr(lang, "tool.1y") + default: + diffStr = i18n.Tr(lang, "tool.years", diff/Year) + diff -= (diff / Year) * Year + } + return diff, diffStr +} + +// MinutesToFriendly returns a user friendly string with number of minutes +// converted to hours and minutes. +func MinutesToFriendly(minutes int, lang string) string { + duration := time.Duration(minutes) * time.Minute + return TimeSincePro(time.Now().Add(-duration), lang) +} + +// TimeSincePro calculates the time interval and generate full user-friendly string. +func TimeSincePro(then time.Time, lang string) string { + return timeSincePro(then, time.Now(), lang) +} + +func timeSincePro(then, now time.Time, lang string) string { + diff := now.Unix() - then.Unix() + + if then.After(now) { + return i18n.Tr(lang, "tool.future") + } + if diff == 0 { + return i18n.Tr(lang, "tool.now") + } + + var timeStr, diffStr string + for { + if diff == 0 { + break + } + + diff, diffStr = computeTimeDiff(diff, lang) + timeStr += ", " + diffStr + } + return strings.TrimPrefix(timeStr, ", ") +} + +func timeSince(then, now time.Time, lang string) string { + return timeSinceUnix(then.Unix(), now.Unix(), lang) +} + +func timeSinceUnix(then, now int64, lang string) string { + lbl := "tool.ago" + diff := now - then + if then > now { + lbl = "tool.from_now" + diff = then - now + } + if diff <= 0 { + return i18n.Tr(lang, "tool.now") + } + + _, diffStr := computeTimeDiff(diff, lang) + return i18n.Tr(lang, lbl, diffStr) +} + +// RawTimeSince retrieves i18n key of time since t +func RawTimeSince(t time.Time, lang string) string { + return timeSince(t, time.Now(), lang) +} + +// TimeSince calculates the time interval and generate user-friendly string. +func TimeSince(then time.Time, lang string) template.HTML { + return htmlTimeSince(then, time.Now(), lang) +} + +func htmlTimeSince(then, now time.Time, lang string) template.HTML { + return template.HTML(fmt.Sprintf(`%s`, + then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang)), + timeSince(then, now, lang))) +} + +// TimeSinceUnix calculates the time interval and generate user-friendly string. +func TimeSinceUnix(then TimeStamp, lang string) template.HTML { + return htmlTimeSinceUnix(then, TimeStamp(time.Now().Unix()), lang) +} + +func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML { + return template.HTML(fmt.Sprintf(`%s`, + then.FormatInLocation(GetTimeFormat(lang), setting.DefaultUILocation), + timeSinceUnix(int64(then), int64(now), lang))) +} diff --git a/modules/timeutil/since_test.go b/modules/timeutil/since_test.go new file mode 100644 index 0000000000..65d481a6aa --- /dev/null +++ b/modules/timeutil/since_test.go @@ -0,0 +1,163 @@ +// Copyright 2019 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 timeutil + +import ( + "os" + "testing" + "time" + + "code.gitea.io/gitea/modules/setting" + + macaroni18n "gitea.com/macaron/i18n" + "github.com/stretchr/testify/assert" + "github.com/unknwon/i18n" +) + +var BaseDate time.Time + +// time durations +const ( + DayDur = 24 * time.Hour + WeekDur = 7 * DayDur + MonthDur = 30 * DayDur + YearDur = 12 * MonthDur +) + +func TestMain(m *testing.M) { + // setup + macaroni18n.I18n(macaroni18n.Options{ + Directory: "../../options/locale/", + DefaultLang: "en-US", + Langs: []string{"en-US"}, + Names: []string{"english"}, + }) + BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) + + // run the tests + retVal := m.Run() + + os.Exit(retVal) +} + +func TestTimeSince(t *testing.T) { + assert.Equal(t, "now", timeSince(BaseDate, BaseDate, "en")) + + // test that each diff in `diffs` yields the expected string + test := func(expected string, diffs ...time.Duration) { + for _, diff := range diffs { + actual := timeSince(BaseDate, BaseDate.Add(diff), "en") + assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual) + actual = timeSince(BaseDate.Add(diff), BaseDate, "en") + assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual) + } + } + test("1 second", time.Second, time.Second+50*time.Millisecond) + test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond) + test("1 minute", time.Minute, time.Minute+30*time.Second) + test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second) + test("1 hour", time.Hour, time.Hour+30*time.Minute) + test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute) + test("1 day", DayDur, DayDur+12*time.Hour) + test("2 days", 2*DayDur, 2*DayDur+12*time.Hour) + test("1 week", WeekDur, WeekDur+3*DayDur) + test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur) + test("1 month", MonthDur, MonthDur+15*DayDur) + test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur) + test("1 year", YearDur, YearDur+6*MonthDur) + test("2 years", 2*YearDur, 2*YearDur+6*MonthDur) +} + +func TestTimeSincePro(t *testing.T) { + assert.Equal(t, "now", timeSincePro(BaseDate, BaseDate, "en")) + + // test that a difference of `diff` yields the expected string + test := func(expected string, diff time.Duration) { + actual := timeSincePro(BaseDate, BaseDate.Add(diff), "en") + assert.Equal(t, expected, actual) + assert.Equal(t, "future", timeSincePro(BaseDate.Add(diff), BaseDate, "en")) + } + test("1 second", time.Second) + test("2 seconds", 2*time.Second) + test("1 minute", time.Minute) + test("1 minute, 1 second", time.Minute+time.Second) + test("1 minute, 59 seconds", time.Minute+59*time.Second) + test("2 minutes", 2*time.Minute) + test("1 hour", time.Hour) + test("1 hour, 1 second", time.Hour+time.Second) + test("1 hour, 59 minutes, 59 seconds", time.Hour+59*time.Minute+59*time.Second) + test("2 hours", 2*time.Hour) + test("1 day", DayDur) + test("1 day, 23 hours, 59 minutes, 59 seconds", + DayDur+23*time.Hour+59*time.Minute+59*time.Second) + test("2 days", 2*DayDur) + test("1 week", WeekDur) + test("2 weeks", 2*WeekDur) + test("1 month", MonthDur) + test("3 months", 3*MonthDur) + test("1 year", YearDur) + test("2 years, 3 months, 1 week, 2 days, 4 hours, 12 minutes, 17 seconds", + 2*YearDur+3*MonthDur+WeekDur+2*DayDur+4*time.Hour+ + 12*time.Minute+17*time.Second) +} + +func TestHtmlTimeSince(t *testing.T) { + setting.TimeFormat = time.UnixDate + setting.DefaultUILocation = time.UTC + // test that `diff` yields a result containing `expected` + test := func(expected string, diff time.Duration) { + actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), "en") + assert.Contains(t, actual, `title="Sat Jan 1 00:00:00 UTC 2000"`) + assert.Contains(t, actual, expected) + } + test("1 second", time.Second) + test("3 minutes", 3*time.Minute+5*time.Second) + test("1 day", DayDur+18*time.Hour) + test("1 week", WeekDur+6*DayDur) + test("3 months", 3*MonthDur+3*WeekDur) + test("2 years", 2*YearDur) + test("3 years", 3*YearDur+11*MonthDur+4*WeekDur) +} + +func TestComputeTimeDiff(t *testing.T) { + // test that for each offset in offsets, + // computeTimeDiff(base + offset) == (offset, str) + test := func(base int64, str string, offsets ...int64) { + for _, offset := range offsets { + diff, diffStr := computeTimeDiff(base+offset, "en") + assert.Equal(t, offset, diff) + assert.Equal(t, str, diffStr) + } + } + test(0, "now", 0) + test(1, "1 second", 0) + test(2, "2 seconds", 0) + test(Minute, "1 minute", 0, 1, 30, Minute-1) + test(2*Minute, "2 minutes", 0, Minute-1) + test(Hour, "1 hour", 0, 1, Hour-1) + test(5*Hour, "5 hours", 0, Hour-1) + test(Day, "1 day", 0, 1, Day-1) + test(5*Day, "5 days", 0, Day-1) + test(Week, "1 week", 0, 1, Week-1) + test(3*Week, "3 weeks", 0, 4*Day+25000) + test(Month, "1 month", 0, 1, Month-1) + test(10*Month, "10 months", 0, Month-1) + test(Year, "1 year", 0, Year-1) + test(3*Year, "3 years", 0, Year-1) +} + +func TestMinutesToFriendly(t *testing.T) { + // test that a number of minutes yields the expected string + test := func(expected string, minutes int) { + actual := MinutesToFriendly(minutes, "en") + assert.Equal(t, expected, actual) + } + test("1 minute", 1) + test("2 minutes", 2) + test("1 hour", 60) + test("1 hour, 1 minute", 61) + test("1 hour, 2 minutes", 62) + test("2 hours", 120) +} diff --git a/modules/util/time_stamp.go b/modules/timeutil/timestamp.go similarity index 60% rename from modules/util/time_stamp.go rename to modules/timeutil/timestamp.go index 56f41882f5..f70da9db74 100644 --- a/modules/util/time_stamp.go +++ b/modules/timeutil/timestamp.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package util +package timeutil import ( "time" @@ -35,19 +35,34 @@ func (ts TimeStamp) Year() int { // AsTime convert timestamp as time.Time in Local locale func (ts TimeStamp) AsTime() (tm time.Time) { - tm = time.Unix(int64(ts), 0).In(setting.UILocation) + return ts.AsTimeInLocation(setting.DefaultUILocation) +} + +// AsTimeInLocation convert timestamp as time.Time in Local locale +func (ts TimeStamp) AsTimeInLocation(loc *time.Location) (tm time.Time) { + tm = time.Unix(int64(ts), 0).In(loc) return } // AsTimePtr convert timestamp as *time.Time in Local locale func (ts TimeStamp) AsTimePtr() *time.Time { - tm := time.Unix(int64(ts), 0).In(setting.UILocation) + return ts.AsTimePtrInLocation(setting.DefaultUILocation) +} + +// AsTimePtrInLocation convert timestamp as *time.Time in customize location +func (ts TimeStamp) AsTimePtrInLocation(loc *time.Location) *time.Time { + tm := time.Unix(int64(ts), 0).In(loc) return &tm } -// Format formats timestamp as +// Format formats timestamp as given format func (ts TimeStamp) Format(f string) string { - return ts.AsTime().Format(f) + return ts.FormatInLocation(f, setting.DefaultUILocation) +} + +// FormatInLocation formats timestamp as given format with spiecific location +func (ts TimeStamp) FormatInLocation(f string, loc *time.Location) string { + return ts.AsTimeInLocation(loc).Format(f) } // FormatLong formats as RFC1123Z @@ -62,5 +77,5 @@ func (ts TimeStamp) FormatShort() string { // IsZero is zero time func (ts TimeStamp) IsZero() bool { - return ts.AsTime().IsZero() + return ts.AsTimeInLocation(time.Local).IsZero() } diff --git a/modules/upload/filetype.go b/modules/upload/filetype.go index 1ec7324ed3..2ab326d116 100644 --- a/modules/upload/filetype.go +++ b/modules/upload/filetype.go @@ -31,19 +31,16 @@ func (err ErrFileTypeForbidden) Error() string { func VerifyAllowedContentType(buf []byte, allowedTypes []string) error { fileType := http.DetectContentType(buf) - allowed := false for _, t := range allowedTypes { t := strings.Trim(t, " ") - if t == "*/*" || t == fileType { - allowed = true - break + + if t == "*/*" || t == fileType || + // Allow directives after type, like 'text/plain; charset=utf-8' + strings.HasPrefix(fileType, t+";") { + return nil } } - if !allowed { - log.Info("Attachment with type %s blocked from upload", fileType) - return ErrFileTypeForbidden{Type: fileType} - } - - return nil + log.Info("Attachment with type %s blocked from upload", fileType) + return ErrFileTypeForbidden{Type: fileType} } diff --git a/modules/upload/filetype_test.go b/modules/upload/filetype_test.go new file mode 100644 index 0000000000..f93a1c5cc3 --- /dev/null +++ b/modules/upload/filetype_test.go @@ -0,0 +1,47 @@ +// Copyright 2019 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 upload + +import ( + "bytes" + "compress/gzip" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUpload(t *testing.T) { + testContent := []byte(`This is a plain text file.`) + var b bytes.Buffer + w := gzip.NewWriter(&b) + w.Write(testContent) + w.Close() + + kases := []struct { + data []byte + allowedTypes []string + err error + }{ + { + data: testContent, + allowedTypes: []string{"text/plain"}, + err: nil, + }, + { + data: testContent, + allowedTypes: []string{"application/x-gzip"}, + err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, + }, + { + data: b.Bytes(), + allowedTypes: []string{"application/x-gzip"}, + err: nil, + }, + } + + for _, kase := range kases { + assert.Equal(t, kase.err, VerifyAllowedContentType(kase.data, kase.allowedTypes)) + } +} diff --git a/modules/validation/binding.go b/modules/validation/binding.go index 03b6f6276d..6ed75b50fb 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -9,12 +9,16 @@ import ( "regexp" "strings" - "github.com/go-macaron/binding" + "gitea.com/macaron/binding" + "github.com/gobwas/glob" ) const ( // ErrGitRefName is git reference name error ErrGitRefName = "GitRefNameError" + + // ErrGlobPattern is returned when glob pattern is invalid + ErrGlobPattern = "GlobPattern" ) var ( @@ -28,6 +32,7 @@ var ( func AddBindingRules() { addGitRefNameBindingRule() addValidURLBindingRule() + addGlobPatternRule() } func addGitRefNameBindingRule() { @@ -82,6 +87,26 @@ func addValidURLBindingRule() { }) } +func addGlobPatternRule() { + binding.AddRule(&binding.Rule{ + IsMatch: func(rule string) bool { + return rule == "GlobPattern" + }, + IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + str := fmt.Sprintf("%v", val) + + if len(str) != 0 { + if _, err := glob.Compile(str); err != nil { + errs.Add([]string{name}, ErrGlobPattern, err.Error()) + return false, errs + } + } + + return true, errs + }, + }) +} + func portOnly(hostport string) string { colon := strings.IndexByte(hostport, ':') if colon == -1 { diff --git a/modules/validation/binding_test.go b/modules/validation/binding_test.go index f55b09266d..9fc9a6db08 100644 --- a/modules/validation/binding_test.go +++ b/modules/validation/binding_test.go @@ -5,14 +5,13 @@ package validation import ( - "fmt" "net/http" "net/http/httptest" "testing" - "github.com/go-macaron/binding" + "gitea.com/macaron/binding" + "gitea.com/macaron/macaron" "github.com/stretchr/testify/assert" - "gopkg.in/macaron.v1" ) const ( @@ -27,8 +26,9 @@ type ( } TestForm struct { - BranchName string `form:"BranchName" binding:"GitRefName"` - URL string `form:"ValidUrl" binding:"ValidUrl"` + BranchName string `form:"BranchName" binding:"GitRefName"` + URL string `form:"ValidUrl" binding:"ValidUrl"` + GlobPattern string `form:"GlobPattern" binding:"GlobPattern"` } ) @@ -37,7 +37,12 @@ func performValidationTest(t *testing.T, testCase validationTestCase) { m := macaron.Classic() m.Post(testRoute, binding.Validate(testCase.data), func(actual binding.Errors) { - assert.Equal(t, fmt.Sprintf("%+v", testCase.expectedErrors), fmt.Sprintf("%+v", actual)) + // see https://github.com/stretchr/testify/issues/435 + if actual == nil { + actual = binding.Errors{} + } + + assert.Equal(t, testCase.expectedErrors, actual) }) req, err := http.NewRequest("POST", testRoute, nil) diff --git a/modules/validation/glob_pattern_test.go b/modules/validation/glob_pattern_test.go new file mode 100644 index 0000000000..26775167b4 --- /dev/null +++ b/modules/validation/glob_pattern_test.go @@ -0,0 +1,62 @@ +// Copyright 2019 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 validation + +import ( + "testing" + + "gitea.com/macaron/binding" + "github.com/gobwas/glob" +) + +func getGlobPatternErrorString(pattern string) string { + // It would be unwise to rely on that glob + // compilation errors don't ever change. + if _, err := glob.Compile(pattern); err != nil { + return err.Error() + } + return "" +} + +var globValidationTestCases = []validationTestCase{ + { + description: "Empty glob pattern", + data: TestForm{ + GlobPattern: "", + }, + expectedErrors: binding.Errors{}, + }, + { + description: "Valid glob", + data: TestForm{ + GlobPattern: "{master,release*}", + }, + expectedErrors: binding.Errors{}, + }, + + { + description: "Invalid glob", + data: TestForm{ + GlobPattern: "[a-", + }, + expectedErrors: binding.Errors{ + binding.Error{ + FieldNames: []string{"GlobPattern"}, + Classification: ErrGlobPattern, + Message: getGlobPatternErrorString("[a-"), + }, + }, + }, +} + +func Test_GlobPatternValidation(t *testing.T) { + AddBindingRules() + + for _, testCase := range globValidationTestCases { + t.Run(testCase.description, func(t *testing.T) { + performValidationTest(t, testCase) + }) + } +} diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go index 9051ee1a0d..cc2a4b720d 100644 --- a/modules/validation/helpers_test.go +++ b/modules/validation/helpers_test.go @@ -7,9 +7,9 @@ package validation import ( "testing" - "github.com/stretchr/testify/assert" - "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" ) func Test_IsValidURL(t *testing.T) { diff --git a/modules/validation/refname_test.go b/modules/validation/refname_test.go index 57e3f1bf8e..521a83fa04 100644 --- a/modules/validation/refname_test.go +++ b/modules/validation/refname_test.go @@ -7,7 +7,7 @@ package validation import ( "testing" - "github.com/go-macaron/binding" + "gitea.com/macaron/binding" ) var gitRefNameValidationTestCases = []validationTestCase{ diff --git a/modules/validation/validurl_test.go b/modules/validation/validurl_test.go index ba4d7d53d9..aed7406c0a 100644 --- a/modules/validation/validurl_test.go +++ b/modules/validation/validurl_test.go @@ -7,7 +7,7 @@ package validation import ( "testing" - "github.com/go-macaron/binding" + "gitea.com/macaron/binding" ) var urlValidationTestCases = []validationTestCase{ diff --git a/options/locale/TRANSLATORS b/options/locale/TRANSLATORS index 4bed366523..c413626ec1 100644 --- a/options/locale/TRANSLATORS +++ b/options/locale/TRANSLATORS @@ -1,6 +1,7 @@ # This file lists all PUBLIC individuals having contributed content to the translation. # Entries are in alphabetical order. +Adam Jurkiewicz Adam Strzelecki Adrian Verde Akihiro YAGASAKI diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index a49eadbdde..48c007b60d 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -198,6 +198,7 @@ delete_token=Изтрий delete_account=Изтриване на собствения профил confirm_delete_account=Потвърди изтриването + [repo] owner=Притежател repo_name=Име на хранилището diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 6df290baec..a103f423d9 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -86,12 +86,18 @@ host=Hostitel user=Uživatelské jméno password=Heslo db_name=Název databáze +db_helper=Poznámka k uživatelům MySQL: používejte prosím formát uložení dat InnoDB a pokud používáte „utf8mb4“, vaše verze InnoDB musí být větší než 5.6. ssl_mode=SSL +charset=Znaková sada path=Cesta sqlite_helper=Cesta k souboru SQLite3 databáze.
    Pokud spouštíte Gitea jako službu, zadejte absolutní cestu. err_empty_db_path=Cesta k SQLite3 databázi nemůže být prázdná. no_admin_and_disable_registration=Nemůžete vypnout registraci účtů bez vytvoření účtu správce. err_empty_admin_password=Heslo administrátora nemůže být prázdné. +err_empty_admin_email=Email administrátora nemůže být prázdný. +err_admin_name_is_reserved=Uživatelské jméno administrátora není platné, uživatelské jméno je rezervované +err_admin_name_pattern_not_allowed=Uživatelské jméno administrátora není platné, vzor uživatelského jména není povolen +err_admin_name_is_invalid=Uživatelské jméno administrátora není platné general_title=Obecná nastavení app_name=Název stránky @@ -293,12 +299,15 @@ max_size_error=` musí obsahovat maximálně %s znaků.` email_error=` není správná e-mailová adresa.` url_error=` není správná URL.` include_error=` musí obsahovat řetězec „%s“.` +glob_pattern_error=`zástupný vzor je neplatný: %s.` unknown_error=Neznámá chyba: captcha_incorrect=CAPTCHA kód není správný. password_not_match=Zadaná hesla nesouhlasí. username_been_taken=Uživatelské jméno je již obsazeno. repo_name_been_taken=Název repozitáře je již použit. +visit_rate_limit=Dosaženo limitu rychlosti dotazů při vzdáleném přístupu. +2fa_auth_required=Vzdálený přístup vyžaduje dvoufaktorové ověřování. org_name_been_taken=Název organizace je již použit. team_name_been_taken=Název týmu je již použit. team_no_units_error=Povolit přístup alespoň do jedné sekce repozitáře. @@ -309,6 +318,7 @@ enterred_invalid_repo_name=Zadaný název repozitáře není správný. enterred_invalid_owner_name=Nové jméno vlastníka není správné. enterred_invalid_password=Zadané heslo není správné. user_not_exist=Tento uživatel neexistuje. +team_not_exist=Tento tým neexistuje. last_org_owner=Nemůžete odstranit posledního uživatele z týmu 'vlastníci'. Musí existovat alespoň jeden vlastník v každém týmu. cannot_add_org_to_team=Organizace nemůže být přidána jako člen týmu. @@ -380,6 +390,7 @@ choose_new_avatar=Vybrat novou ikonu uživatele update_avatar=Aktualizovat ikonu uživatele delete_current_avatar=Smazat aktuální ikonu uživatele uploaded_avatar_not_a_image=Nahraný soubor není obrázek. +uploaded_avatar_is_too_big=Nahraný soubor překročil maximální velikost. update_avatar_success=Vaše uživatelská ikona byla aktualizována. change_password=Aktualizovat heslo @@ -478,6 +489,7 @@ manage_oauth2_applications=Spravovat OAuth2 aplikace edit_oauth2_application=Upravit OAuth2 aplikaci oauth2_applications_desc=OAuth2 aplikace umožní aplikacím třetích stran bezpečně ověřit uživatele v této instanci Gitea. remove_oauth2_application=Odstranit OAuth2 aplikaci +remove_oauth2_application_desc=Odstraněním OAuth2 aplikace odeberete přístup všem podepsaným přístupovým poukázkám. Pokračovat? remove_oauth2_application_success=Aplikace byla odstraněna. create_oauth2_application=Vytvořit novou OAuth2 aplikaci create_oauth2_application_button=Vytvořit aplikaci @@ -546,11 +558,17 @@ confirm_delete_account=Potvrdit smazání delete_account_title=Smazat uživatelský účet delete_account_desc=Jste si jisti, že chcete trvale smazat tento účet? +email_notifications.enable=Povolit e-mailová oznámení +email_notifications.onmention=E-mail pouze při zmínce +email_notifications.disable=Zakázat e-mailová oznámení +email_notifications.submit=Nastavit předvolby e-mailu + [repo] owner=Vlastník repo_name=Název repozitáře repo_name_helper=Dobrý název repozitáře většinou používá krátká, zapamatovatelná a unikátní klíčová slova. visibility=Viditelnost +visibility_description=Pouze majitelé nebo členové organizace to budou moci vidět, pokud mají práva. visibility_helper=Nastavit repozitář jako soukromý visibility_helper_forced=Váš administrátor vynutil, že nové repozitáře budou soukromé. visibility_fork_helper=(Změna tohoto ovlivní všechny rozštěpení repozitáře.) @@ -561,6 +579,8 @@ fork_visibility_helper=Viditelnost rozštěpeného repozitáře nemůže být zm repo_desc=Popis repo_lang=Jazyk repo_gitignore_helper=Vyberte šablony .gitignore. +issue_labels=Štítky úkolů +issue_labels_helper=Vyberte sadu štítků úkolů. license=Licence license_helper=Vyberte licenční soubor. readme=README @@ -573,7 +593,7 @@ mirror_prune_desc=Odstranit zastaralé reference na vzdálené sledování mirror_interval=Interval zrcadlení (platné časové jednotky jsou „h“, „m“ a „s“). 0 zakáže automatickou synchronizaci. mirror_interval_invalid=Interval zrcadlení není platný. mirror_address=Klonovat z URL -mirror_address_desc=Zahrňte požadované přihlašovací údaje do URL. Tyto údaje musí používat správné URL escape sekvence +mirror_address_desc=Zadejte nějaké přístupové údaje do sekce Ověření klonování. mirror_address_url_invalid=Poskytnutá URL je neplatná. Všechny komponenty musíte správně nahradit escape sekvencí. mirror_address_protocol_invalid=Zadaná URL je neplatná. Mohou být zrcadleny pouze umístění http(s):// nebo git://. mirror_last_synced=Poslední synchronizace @@ -594,7 +614,14 @@ form.name_pattern_not_allowed=Vzor „%s“ není povolený v názvu repozitář need_auth=Ověření klonování migrate_type=Typ migrace migrate_type_helper=Tento repozitář bude zrcadlem -migrate_repo=Přenést repozitář +migrate_items=Položky pro migrování +migrate_items_wiki=Wiki +migrate_items_milestones=Milníky +migrate_items_labels=Štítky +migrate_items_issues=Úkoly +migrate_items_pullrequests=Požadavky na natažení +migrate_items_releases=Vydání +migrate_repo=Migrovat repozitář migrate.clone_address=Migrovat / klonovat z URL migrate.clone_address_desc=HTTP(S) nebo URL pro klonování existujícího repozitáře migrate.clone_local_path=nebo místní cesta serveru @@ -602,6 +629,9 @@ migrate.permission_denied=Není dovoleno importovat místní repozitáře. migrate.invalid_local_path=Místní cesta je neplatná, buď neexistuje nebo není adresářem. migrate.failed=Přenesení selhalo: %v migrate.lfs_mirror_unsupported=Zrcadlení LFS objektů není podporováno - použijte místo toho „git lfs fetch --all“ a „git lfs push --all“. +migrate.migrate_items_options=Při přechodu z githubu, zadejte uživatelské jméno a zobrazí se možnosti migrace. +migrated_from=Migrováno z
    %[2]s +migrated_from_fake=Migrováno z %[1]s mirror_from=zrcadlo forked_from=rozštěpen z @@ -635,7 +665,7 @@ tags=Značky issues=Úkoly pulls=Požadavky na natažení labels=Štítky -milestones=Mezníky +milestones=Milníky commits=Revize commit=Revize releases=Vydání @@ -648,6 +678,8 @@ video_not_supported_in_browser=Váš prohlížeč nepodporuje značku pro HTML5 audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku pro HTML5 audio. stored_lfs=Uloženo pomocí Git LFS commit_graph=Graf revizí +blame=Blame +normal_view=Normální zobrazení editor.new_file=Nový soubor editor.upload_file=Nahrát soubor @@ -673,6 +705,7 @@ editor.delete=Smazat „%s“ editor.commit_message_desc=Přidat volitelný rozšířený popis… editor.commit_directly_to_this_branch=Uložte změny revize přímo do větve %s. editor.create_new_branch=Vytvořit novou větev pro tuto revizi a spustit požadavek na natažení. +editor.propose_file_change=Navrhnout změnu souboru editor.new_branch_name_desc=Název nové větve… editor.cancel=Zrušit editor.filename_cannot_be_empty=Jméno nemůže být prázdné. @@ -746,7 +779,7 @@ issues.self_assign_at=`přiřadil(a) sobě toto %s` issues.add_assignee_at=`byl přiřazen %s %s` issues.remove_assignee_at=`byl odstraněn z přiřazení %s %s` issues.remove_self_assignment=`odstranil(a) jejich přiřazení %s` -issues.change_title_at=`změnil(a) název z %s na %s %s` +issues.change_title_at=`změnil(a) název z %s na %s %s` issues.delete_branch_at=`odstranil(a) větev %s %s` issues.open_tab=%d otevřených issues.close_tab=%d zavřených @@ -803,6 +836,10 @@ issues.create_comment=Okomentovat issues.closed_at=`uzavřel(a) %[2]s` issues.reopened_at=`znovuotevřel %[2]s` issues.commit_ref_at=`odkázal na tento úkol z revize %[2]s` +issues.ref_issue_at=`se odkázal(a) na tento úkol %[1]s` +issues.ref_pull_at=`se odkázal(a) na tento požadavek na natažení %[1]s` +issues.ref_issue_ext_at=`se odkázal(a) na tento úkol z %[1]s %[2]s` +issues.ref_pull_ext_at=`se odkázal(a) na tento požadavek na natažení z %[1]s %[2]s` issues.poster=Autor issues.collaborator=Spolupracovník issues.owner=Vlastník @@ -922,7 +959,7 @@ issues.review.reviewers=Posuzovatelé issues.review.show_outdated=Zobrazit zastaralé issues.review.hide_outdated=Skrýt zastaralé -pulls.desc=Povolit požadavky na sloučení a posuzování kódu. +pulls.desc=Povolit požadavky na natažení a posuzování kódu. pulls.new=Nový požadavek na natažení pulls.compare_changes=Nový požadavek na natažení pulls.compare_changes_desc=Vyberte větev pro sloučení a větev pro natažení. @@ -939,13 +976,17 @@ pulls.tab_conversation=Konverzace pulls.tab_commits=Revize pulls.tab_files=Změněné soubory pulls.reopen_to_merge=Prosíme, otevřete znovu tento požadavek na natažení, aby se provedlo sloučení. +pulls.cant_reopen_deleted_branch=Tento požadavek na natažení nemůže být znovu otevřen protože větev byla smazána. pulls.merged=Sloučený +pulls.merged_as=Požadavek na natažení byl sloučen jako %[2]s. pulls.has_merged=Požadavek na natažení byl sloučen. pulls.title_wip_desc=`Začněte název s %s a zamezíte tak nechtěnému sloučení požadavku na natažení.` pulls.cannot_merge_work_in_progress=Požadavek na natažení je označen jako ve vývoji. Odstraňte %s prefix z titulku, až bude hotový pulls.data_broken=Tento požadavek na natažení je rozbitý kvůli chybějícím informacím o rozštěpení. pulls.files_conflicted=Tento požadavek na natažení obsahuje změny, které kolidují s cílovou větví. pulls.is_checking=Právě probíhá kontrola konfliktů při sloučení. Zkuste to za chvíli. +pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné. +pulls.required_status_check_administrator=Jako administrátor stále můžete sloučit tento požadavek na natažení. pulls.blocked_by_approvals=Tento požadavek na natažení ještě nemá dostatek schválení. Uděleno %d z %d schválení. pulls.can_auto_merge_desc=Tento požadavek na natažení může být automaticky sloučen. pulls.cannot_auto_merge_desc=Tento požadavek na natažení nemůže být automaticky sloučen, neboť se v něm nachází konflikty. @@ -953,12 +994,16 @@ pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční slou pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány. pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně. pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený. +pulls.no_merge_status_check=Tento požadavek na natažení nemůže být sloučen, protože ne všechny požadované kontroly stavu byly úspěšné. pulls.merge_pull_request=Sloučit požadavek na natažení pulls.rebase_merge_pull_request=Rebase a sloučit pulls.rebase_merge_commit_pull_request=Rebase a sloučit (--no-ff) pulls.squash_merge_pull_request=Squash a sloučit pulls.invalid_merge_option=Nemůžete použít tuto možnost sloučení pro tento požadavek na natažení. pulls.open_unmerged_pull_exists=`Nemůžete provést operaci znovuotevření protože je tu čekající požadavek na natažení (#%d) s identickými vlastnostmi.` +pulls.status_checking=Některé kontroly jsou nedořešeny +pulls.status_checks_success=Všechny kontroly byly úspěšné +pulls.status_checks_error=Některé kontroly selhaly milestones.new=Nový milník milestones.open_tab=%d otevřených @@ -1007,6 +1052,9 @@ wiki.save_page=Uložit stránku wiki.last_commit_info=%s upravil tuto stránku %s wiki.edit_page_button=Změnit stránku wiki.new_page_button=Nová stránka +wiki.file_revision=Revize stránky +wiki.wiki_page_revisions=Revize Wiki stránky +wiki.back_to_wiki=Zpět na wiki stránku wiki.delete_page_button=Smazat stránku wiki.delete_page_notice_1=Odstranění Wiki stránky „%s“ nemůže být vráceno zpět. Pokračovat? wiki.page_already_exists=Stránka Wiki se stejným názvem již existuje. @@ -1055,6 +1103,27 @@ activity.title.releases_1=%d Vydání activity.title.releases_n=%d Vydání activity.title.releases_published_by=%s publikoval %s activity.published_release_label=Publikováno +activity.no_git_activity=V tomto období nebyla žádná aktivita v revizích. +activity.git_stats_exclude_merges=Při vyloučení slučování, +activity.git_stats_author_1=%d autor +activity.git_stats_author_n=%d autoři +activity.git_stats_pushed_1=nahrál +activity.git_stats_pushed_n=nahrály +activity.git_stats_commit_1=%d revize +activity.git_stats_commit_n=%d revizí +activity.git_stats_push_to_branch=do %s a +activity.git_stats_push_to_all_branches=do všech větví. +activity.git_stats_on_default_branch=Na %s, +activity.git_stats_file_1=%d soubor +activity.git_stats_file_n=%d soubory +activity.git_stats_files_changed_1=se změnil +activity.git_stats_files_changed_n=se změnily +activity.git_stats_additions=a bylo zde +activity.git_stats_addition_1=%d přidání +activity.git_stats_addition_n=%d přidání +activity.git_stats_and_deletions=a +activity.git_stats_deletion_1=%d odebrání +activity.git_stats_deletion_n=%d odebrání search=Vyhledat search.search_repo=Hledat repozitář @@ -1067,6 +1136,7 @@ settings.collaboration=Spolupracovníci settings.collaboration.admin=Správce settings.collaboration.write=Zápis settings.collaboration.read=Čtení +settings.collaboration.owner=Vlastník settings.collaboration.undefined=Neurčeno settings.hooks=Webové háčky settings.githooks=Háčky Gitu @@ -1074,6 +1144,10 @@ settings.basic_settings=Základní nastavení settings.mirror_settings=Nastavení zrcadla settings.sync_mirror=Synchronizovat nyní settings.mirror_sync_in_progress=Právě probíhá synchronizace zrcadla. Zkuste to za chvíli. +settings.email_notifications.enable=Povolit e-mailová oznámení +settings.email_notifications.onmention=E-mail pouze při zmínce +settings.email_notifications.disable=Zakázat e-mailová oznámení +settings.email_notifications.submit=Nastavit předvolby e-mailu settings.site=Webová stránka settings.update_settings=Změnit nastavení settings.advanced_settings=Pokročilá nastavení @@ -1165,6 +1239,7 @@ settings.githook_content=Obsah háčku settings.update_githook=Aktualizovat háček settings.add_webhook_desc=Gitea odešle dotaz POST s nastaveným Content Type na cílovou URL. Čtěte více v průvodci webovými háčky. settings.payload_url=Cílové URL +settings.http_method=HTTP metoda settings.content_type=POST Content Type settings.secret=Tajný klíč settings.slack_username=Uživatelské jméno @@ -1192,6 +1267,8 @@ settings.event_pull_request=Požadavek na stažení settings.event_pull_request_desc=Požadavek na natažení byl otevřen, uzavřen, znovu-otevřen, upraven, schválen, zamítnut, posouzen, přiřazen, zrušeno přiřazení, popis upraven, smazán nebo synchronizován. settings.event_push=Nahrát settings.event_push_desc=Nahrání pomocí Gitu do repozitáře. +settings.branch_filter=Filtr větví +settings.branch_filter_desc=Povolené větve pro události nahrání, vytvoření větve a smazání větve jsou určeny pomocí zástupného vzoru. Pokud je prázdný nebo *, všechny události jsou ohlášeny. Podívejte se na dokumentaci syntaxe na github.com/gobwas/glob. Příklady: master, {master,release*}. settings.event_repository=Repozitář settings.event_repository_desc=Repozitář vytvořen nebo smazán. settings.active=Aktivní @@ -1208,6 +1285,8 @@ settings.slack_domain=Doména settings.slack_channel=Kanál settings.add_discord_hook_desc=Integrovat Discord do vašeho repozitáře. settings.add_dingtalk_hook_desc=Integrovat Dingtalk do vašeho repozitáře. +settings.add_telegram_hook_desc=Integrovat Telegram do vašeho repozitáře. +settings.add_msteams_hook_desc=Integrovat Microsoft Teams do vašeho repozitáře. settings.deploy_keys=Klíče pro nasazení settings.add_deploy_key=Přidat klíč pro nasazení settings.deploy_key_desc=Klíče pro nasazení mají k tomuto repozitáři přístup pouze pro čtení. @@ -1229,7 +1308,9 @@ settings.protected_branch_can_push_yes=Můžete nahrávat settings.protected_branch_can_push_no=Nemůžete nahrávat settings.branch_protection=Ochrana větví pro větev „%s“ settings.protect_this_branch=Povolit ochranu větví +settings.protect_this_branch_desc=Zamezit smazání a zakázat libovolná nahrávání do větve. settings.protect_whitelist_committers=Povolit vyjmenovaným nahrávání +settings.protect_whitelist_committers_desc=Umožnit povoleným uživatelům nahrávání do této větve (ale ne vynucené nahrávání). settings.protect_whitelist_users=Povolení uživatelé pro nahrávání: settings.protect_whitelist_search_users=Hledat uživatele… settings.protect_whitelist_teams=Povolené týmy pro nahrávání: @@ -1238,6 +1319,8 @@ settings.protect_merge_whitelist_committers=Povolit vyjmenovaným slučování settings.protect_merge_whitelist_committers_desc=Povolit pouze vyjmenovaným uživatelům nebo týmům slučovat požadavky na natažení do této větve. settings.protect_merge_whitelist_users=Povolení uživatelé pro slučování: settings.protect_merge_whitelist_teams=Povolené týmy pro slučování: +settings.protect_check_status_contexts=Povolit kontrolu stavu +settings.protect_check_status_contexts_list=Kontroly stavu pro tento repozitář zjištěné během posledního týdne settings.protect_required_approvals=Požadovaná schválení: settings.protect_required_approvals_desc=Povolit pouze požadavky na natažení s dostatečně pozitivním hodnocením povolených uživatelů nebo týmů. settings.protect_approvals_whitelist_users=Povolení posuzovatelé: @@ -1253,19 +1336,26 @@ settings.choose_branch=Vyberte větev… settings.no_protected_branch=Nejsou tu žádné chráněné větve. settings.edit_protected_branch=Upravit settings.protected_branch_required_approvals_min=Požadovaná schválení nesmí být záporné číslo. +settings.bot_token=Poukázka pro robota +settings.chat_id=ID chatu settings.archive.button=Archivovat repozitář settings.archive.header=Archivovat tento repozitář settings.archive.text=Archivní repozitář bude kompletně jen pro čtení. Nezobrazuje se v přehledu, nelze přidávat revize ani nové úkoly a požadavky na natažení. settings.archive.success=Repozitář byl úspěšně archivován. +settings.archive.error=Nastala chyba při archivování repozitáře. Prohlédněte si záznam pro více detailů. settings.archive.error_ismirror=Nemůžete archivovat zrcadlený repozitář. settings.archive.branchsettings_unavailable=Nastavení větví není dostupné, pokud je repozitář archivovaný. settings.unarchive.button=Obnovit repozitář settings.unarchive.header=Obnovit tento repozitář +settings.unarchive.text=Obnovení repozitáře vrátí možnost přijímání revizí a nahrávání. Stejně tak se obnoví i možnost zadávání nových úkolů a požadavků na natažení. settings.unarchive.success=Repozitář byl úspěšně obnoven. +settings.unarchive.error=Nastala chyba při obnovování repozitáře. Prohlédněte si záznam pro více detailů. +settings.update_avatar_success=Ikona repozitáře byla aktualizována. diff.browse_source=Procházet zdrojové kódy diff.parent=rodič diff.commit=revize +diff.git-notes=Poznámky diff.data_not_available=Rozdílový obsah není dostupný diff.show_diff_stats=Ukázat statistiku rozdílových dat diff.show_split_view=Rozdělené zobrazení @@ -1278,6 +1368,11 @@ diff.whitespace_ignore_at_eol=Ignorovat změny v bílých znacích na konci ří diff.stats_desc= %d změnil soubory, kde provedl %d přidání a %d odebrání diff.bin=binární diff.view_file=Zobrazit soubor +diff.file_before=Před +diff.file_after=Za +diff.file_image_width=Šířka +diff.file_image_height=Výška +diff.file_byte_size=Velikost diff.file_suppressed=Diff nebyl zobrazen, protože je příliš veliký diff.too_many_files=Některé soubory nejsou zobrazny, neboť je v této revizi změněno mnoho souborů diff.comment.placeholder=Zanechat komentář @@ -1343,6 +1438,8 @@ branch.deleted_by=Odstranil %s branch.restore_success=Větev „%s“ byla obnovena. branch.restore_failed=Nepodařilo se obnovit větev „%s“. branch.protected_deletion_failed=Větev „%s“ je chráněna. Nemůže být smazána. +branch.restore=Obnovit větev „%s“ +branch.download=Stáhnout větev „%s“ topic.manage_topics=Spravovat témata topic.done=Hotovo @@ -1378,6 +1475,7 @@ settings.options=Organizace settings.full_name=Celé jméno settings.website=Webové stránky settings.location=Umístění +settings.permission=Oprávnění settings.visibility=Viditelnost settings.visibility.public=Veřejná settings.visibility.limited=Omezená (viditelné jen pro přihlášené uživatele) @@ -1467,6 +1565,8 @@ dashboard.delete_repo_archives=Smazat všechny archivy repozitáře dashboard.delete_repo_archives_success=Všechny archivy repozitáře byly smazány. dashboard.delete_missing_repos=Smazat všechny repozitáře, které nemají Git soubory dashboard.delete_missing_repos_success=Všechny repozitáře, které nemají Git soubory, byly smazány. +dashboard.delete_generated_repository_avatars=Odstranit vygenerovanou ikonu repozitáře +dashboard.delete_generated_repository_avatars_success=Generované ikony repozitářů byly zakázány. dashboard.git_gc_repos=Provést úklid všech repozitářů dashboard.git_gc_repos_success=Úklid všech repozitářů byl dokončen. dashboard.resync_all_sshkeys=Aktualizovat soubor „.ssh/authorized_keys“ pomocí SSH klíčů Gitea. (Není nutné pro vestavěný SSH server.) @@ -1622,6 +1722,7 @@ auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzo auths.tip.openid_connect=Použijte OpenID URL pro objevování spojení (/.well-known/openid-configuration) k nastavení koncových bodů auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me +auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://docs.gitea.io/en-us/oauth2-provider/ auths.edit=Upravit zdroj ověřování auths.activated=Tento zdroj ověřování je aktivován auths.new_success=Zdroj ověřování „%s“ byl přidán. @@ -1665,6 +1766,10 @@ config.ssh_keygen_path=Cesta ke generátoru klíčů ('ssh-keygen') config.ssh_minimum_key_size_check=Kontrola minimální velikosti klíčů config.ssh_minimum_key_sizes=Minimální velikost klíčů +config.lfs_config=Nastavení LFS +config.lfs_enabled=Povoleno +config.lfs_content_path=Cesta k obsahu LFS +config.lfs_http_auth_expiry=Vypršení autorizace LFS HTTPS config.db_config=Nastavení databáze config.db_type=Typ @@ -1721,6 +1826,7 @@ config.cache_config=Nastavení mezipaměti config.cache_adapter=Adaptér mezipaměti config.cache_interval=Interval mezipaměti config.cache_conn=Připojení mezipaměti +config.cache_item_ttl=Čas vypršení položky v mezipaměti config.session_config=Nastavení relace config.session_provider=Poskytovatel relace @@ -1804,6 +1910,7 @@ push_tag=nahrána značka %[2]s do % delete_tag=smazána značka %[2]s z %[3]s delete_branch=smazal(a) větev %[2]s z %[3]s compare_commits=Porovnat %d revizí +compare_commits_general=Porovnat revize mirror_sync_push=synchronizoval(a) revize do %[3]s v %[4]s ze zrcadla mirror_sync_create=synchronizoval(a) novou referenci %[2]s do %[3]s ze zrcadla mirror_sync_delete=synchronizoval(a) a smazal(a) referenci %[2]s v %[3]s ze zrcadla diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index d6ee0d2126..3add4be1a9 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -299,6 +299,7 @@ max_size_error=` darf höchstens %s Zeichen enthalten.` email_error=` ist keine gültige E-Mail-Adresse.` url_error=` ist keine gültige URL.` include_error=` muss den Text „%s“ enthalten.` +glob_pattern_error=` Der Glob Pattern ist ungültig: %s.` unknown_error=Unbekannter Fehler: captcha_incorrect=Der eingegebene CAPTCHA-Code ist falsch. password_not_match=Die Passwörter stimmen nicht überein. @@ -329,7 +330,7 @@ still_own_repo=Dein Konto besitzt ein oder mehrere Repositories. Diese müssen z still_has_org=Dein Account ist Mitglied in mindestens einer Organisation. Bitte verlasse diese zuerst. org_still_own_repo=Diese Organisation besitzt noch mindestens ein Repository. Bitte lösche oder übertrage diese zuerst. -target_branch_not_exist=Die Ziel-Branch existiert nicht. +target_branch_not_exist=Der Ziel-Branch existiert nicht. [user] change_avatar=Profilbild ändern… @@ -450,7 +451,7 @@ delete_key=Entfernen ssh_key_deletion=SSH-Schlüssel entfernen gpg_key_deletion=GPG-Schlüssel entfernen ssh_key_deletion_desc=Wenn du einen SSH-Key entfernst, hast du mit diesem Key keinen Zugriff mehr. Fortfahren? -gpg_key_deletion_desc=Wenn du einen GPG-Key entfernst, können damit unterschriebenen Commits nicht mehr verifiziert werden. Fortfahren? +gpg_key_deletion_desc=Wenn du einen GPG-Schlüssel entfernst, können damit unterschriebene Commits nicht mehr verifiziert werden. Fortfahren? ssh_key_deletion_success=Der SSH-Schlüssel wurde entfernt. gpg_key_deletion_success=Der GPG-Schlüssel wurde entfernt. add_on=Hinzugefügt am @@ -556,12 +557,18 @@ confirm_delete_account=Löschen bestätigen delete_account_title=Benutzerkonto löschen delete_account_desc=Bist du sicher, dass du diesen Account dauerhaft löschen möchtest? +email_notifications.enable=E-Mail Benachrichtigungen aktivieren +email_notifications.onmention=Nur E-Mail bei Erwähnung +email_notifications.disable=E-Mail Benachrichtigungen deaktivieren +email_notifications.submit=E-Mail-Einstellungen festlegegen + [repo] owner=Besitzer repo_name=Repository-Name repo_name_helper=Ein guter Repository-Name besteht normalerweise aus kurzen, unvergesslichen und einzigartigen Schlagwörtern. visibility=Sichtbarkeit -visibility_helper=Markiere das Repository als privat +visibility_description=Nur der Besitzer oder Organisationsmitglieder mit entsprechender Berechtigung, werden in der Lage sein, es zu sehen. +visibility_helper=In privates Repository umwandeln visibility_helper_forced=Auf dieser Gitea-Instanz können nur private Repositories angelegt werden. visibility_fork_helper=(Eine Änderung dieses Wertes wirkt sich auf alle Forks aus) clone_helper=Benötigst du Hilfe beim Klonen? Öffne die Hilfe. @@ -571,6 +578,8 @@ fork_visibility_helper=Die Sichtbarkeit eines geforkten Repositorys kann nicht g repo_desc=Beschreibung repo_lang=Sprache repo_gitignore_helper=Wähle eine .gitignore-Vorlage aus. +issue_labels=Issue Label +issue_labels_helper=Wähle ein Issue-Label-Set. license=Lizenz license_helper=Wähle eine Lizenz aus. readme=README @@ -583,7 +592,7 @@ mirror_prune_desc=Entferne veraltete remote-tracking Referenzen mirror_interval=Spiegel-Intervall (gültige Zeiteinheiten sind 'h', 'm', 's'). 0 schaltet die automatische Synchronisierung aus. mirror_interval_invalid=Das Spiegel-Intervall ist ungültig. mirror_address=Klonen via URL -mirror_address_desc=Alle erforderlichen Zugangsdaten müssen in der URL enthalten sein dürfen keine ungültigen Zeichen enthalten. +mirror_address_desc=Gib alle erforderlichen Anmeldedaten im Abschnitt "Autorisierung klonen" ein. mirror_address_url_invalid=Die angegebene URL ist ungültig. Achte darauf, alle URL-Komponenten korrekt zu maskieren. mirror_address_protocol_invalid=Die angegebene URL ist ungültig. Nur Pfade beginnend mit http(s):// oder git:// können gespiegelt werden. mirror_last_synced=Zuletzt synchronisiert @@ -695,6 +704,7 @@ editor.delete=„%s“ löschen editor.commit_message_desc=Eine ausführlichere (optionale) Beschreibung hinzufügen… editor.commit_directly_to_this_branch=Direkt in den Branch „%s“ einchecken. editor.create_new_branch=Einen neuen Branch für diesen Commit erstellen und einen Pull Request starten. +editor.propose_file_change=Dateiänderung vorschlagen editor.new_branch_name_desc=Neuer Branchname… editor.cancel=Abbrechen editor.filename_cannot_be_empty=Der Dateiname darf nicht leer sein. @@ -768,7 +778,7 @@ issues.self_assign_at=`hat sich das Issue %s selbst zugewiesen` issues.add_assignee_at=`wurde von %s %s zugewiesen` issues.remove_assignee_at=`wurde von %s %s nicht zugewiesen` issues.remove_self_assignment=`hat seine Zuweisung %s entfernt` -issues.change_title_at=`Titel von %s nach %s %s geändert` +issues.change_title_at=`hat den Titel von %s zu %s %s geändert` issues.delete_branch_at=`löschte die Branch %s %s` issues.open_tab=%d offen issues.close_tab=%d geschlossen @@ -932,7 +942,7 @@ issues.dependency.add_error_dep_not_exist=Abhängigkeit existiert nicht. issues.dependency.add_error_dep_exists=Abhängigkeit existiert bereits. issues.dependency.add_error_cannot_create_circular=Du kannst keine Abhängigkeit erstellen, bei welcher sich zwei Issues gegenseitig blockieren. issues.dependency.add_error_dep_not_same_repo=Beide Issues müssen sich im selben Repository befinden. -issues.review.self.approval=Du kannst keine Änderungen an deinem eigenen Pull-Request genehmigen. +issues.review.self.approval=Du kannst nicht dein eigenen Pull-Request genehmigen. issues.review.self.rejection=Du kannst keine Änderungen an deinem eigenen Pull-Request anfragen. issues.review.approve=hat die Änderungen %s genehmigt issues.review.comment=hat %s überprüft @@ -944,7 +954,7 @@ issues.review.reviewers=Reviewer issues.review.show_outdated=Veraltete anzeigen issues.review.hide_outdated=Veraltete ausblenden -pulls.desc=Merge-Requests und Code-Überprüfungen aktivieren. +pulls.desc=Pull-Requests und Code-Reviews aktivieren. pulls.new=Neuer Pull-Request pulls.compare_changes=Neuer Pull-Request pulls.compare_changes_desc=Wähle die Ziel- und Quellbranch aus. @@ -963,12 +973,15 @@ pulls.tab_files=Geänderte Dateien pulls.reopen_to_merge=Bitte diesen Pull-Request wieder öffnen, um die Merge-Operation auszuführen. pulls.cant_reopen_deleted_branch=Dieser Pull-Request kann nicht wieder geöffnet werden, da die Branche bereits gelöscht wurde. pulls.merged=Zusammengeführt +pulls.merged_as=Der Pull Request wurde als %[2]s zusammengeführt. pulls.has_merged=Der Pull-Request wurde zusammengeführt. pulls.title_wip_desc=`Beginne den Titel mit %s um zu verhindern, dass der Pull Request versehentlich zusammengeführt wird.` pulls.cannot_merge_work_in_progress=Dieser Pull Request wurde als Work In Progress markiert. Entferne den %s-Präfix vom Titel, wenn dieser fertig ist. pulls.data_broken=Dieser Pull-Requests ist kaputt, da Fork-Informationen gelöscht wurden. pulls.files_conflicted=Dieser Pull-Request hat Änderungen, die im Widerspruch zum Ziel-Branch stehen. pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisiere die Seite in wenigen Augenblicken. +pulls.required_status_check_failed=Einige erforderliche Prüfungen waren nicht erfolgreich. +pulls.required_status_check_administrator=Als Administrator kannst du diesen Pull-Request weiterhin zusammenführen. pulls.blocked_by_approvals=Dieser Pull-Request hat noch nicht genügend Zustimmungen. %d von %d Zustimmungen erteilt. pulls.can_auto_merge_desc=Dieser Pull-Request kann automatisch zusammengeführt werden. pulls.cannot_auto_merge_desc=Dieser Pull-Request kann nicht automatisch zusammengeführt werden, da es Konflikte gibt. @@ -976,12 +989,16 @@ pulls.cannot_auto_merge_helper=Bitte manuell zusammenführen, um die Konflikte z pulls.no_merge_desc=Dieser Pull-Request kann nicht gemerged werden, da keine Mergeoptionen aktiviert sind. pulls.no_merge_helper=Aktiviere Mergeoptionen in den Repositoryeinstellungen oder merge den Pull-Request manuell. pulls.no_merge_wip=Dieser Pull Request kann nicht zusammengeführt werden, da er als Work In Progress gekennzeichnet ist. +pulls.no_merge_status_check=Dieser Pull-Request kann nicht zusammengeführt werden, da nicht alle erforderlichen Statusprüfungen erfolgreich waren. pulls.merge_pull_request=Pull-Request zusammenführen pulls.rebase_merge_pull_request=Rebase und Mergen pulls.rebase_merge_commit_pull_request=Rebasen und Mergen (--no-ff) pulls.squash_merge_pull_request=Zusammenfassen und Mergen pulls.invalid_merge_option=Du kannst diese Mergeoption auf diesen Pull-Request nicht anwenden. pulls.open_unmerged_pull_exists=`Du kannst diesen Pull-Request nicht erneut öffnen, da noch ein anderer (#%d) mit identischen Eigenschaften offen ist.` +pulls.status_checking=Einige Prüfungen sind noch ausstehend +pulls.status_checks_success=Alle Prüfungen waren erfolgreich +pulls.status_checks_error=Einige Prüfungen sind fehlgeschlagen milestones.new=Neuer Meilenstein milestones.open_tab=%d offen @@ -1030,6 +1047,8 @@ wiki.save_page=Seite speichern wiki.last_commit_info=%s hat diese Seite bearbeitet %s wiki.edit_page_button=Bearbeiten wiki.new_page_button=Neue Seite +wiki.file_revision=Seitenversion +wiki.wiki_page_revisions=Wiki Änderungsverlauf wiki.back_to_wiki=Zurück zur Wiki-Seite wiki.delete_page_button=Seite löschen wiki.delete_page_notice_1=Das Löschen der Wiki-Seite „%s“ kann nicht rückgängig gemacht werden. Fortfahren? @@ -1119,6 +1138,10 @@ settings.basic_settings=Grundeinstellungen settings.mirror_settings=Mirror-Einstellungen settings.sync_mirror=Jetzt synchronisieren settings.mirror_sync_in_progress=Mirror-Synchronisierung wird zurzeit ausgeführt. Komm in ein paar Minuten zurück. +settings.email_notifications.enable=E-Mail Benachrichtigungen aktivieren +settings.email_notifications.onmention=E-Mail-Benachrichtigungen nur bei Erwähnung +settings.email_notifications.disable=E-Mail Benachrichtigungen deaktivieren +settings.email_notifications.submit=E-Mail-Einstellungen festlegen settings.site=Webseite settings.update_settings=Einstellungen speichern settings.advanced_settings=Erweiterte Einstellungen @@ -1181,8 +1204,8 @@ settings.transfer_succeed=Das Repository wurde transferiert. settings.confirm_delete=Repository löschen settings.add_collaborator=Mitarbeiter hinzufügen settings.add_collaborator_success=Der Mitarbeiter wurde hinzugefügt. -settings.add_collaborator_inactive_user=Inaktive Benutzer können nicht als Beteiligte hinzufügt werden. -settings.add_collaborator_duplicate=Der Beteiligte ist bereits zu diesem Repository hinzugefügt. +settings.add_collaborator_inactive_user=Inaktive Benutzer können nicht als Mitarbeiter hinzufügt werden. +settings.add_collaborator_duplicate=Der Mitarbeiter ist bereits zu diesem Repository hinzugefügt. settings.delete_collaborator=Entfernen settings.collaborator_deletion=Mitarbeiter entfernen settings.collaborator_deletion_desc=Nach dem Löschen wird dieser Mitarbeiter keinen Zugriff mehr auf dieses Repository haben. Fortfahren? @@ -1238,10 +1261,12 @@ settings.event_pull_request=Pull-Request settings.event_pull_request_desc=Pull-Request geöffnet, geschlossen, wieder geöffnet, bearbeitet, angenommen, abgelehnt, kommentiert, zugewiesen, nicht zugewiesen, Label aktualisiert, Label gelöscht oder synchronisiert. settings.event_push=Push settings.event_push_desc=Git push in ein Repository. +settings.branch_filter=Branch-Filter +settings.branch_filter_desc=Branch-Whitelist für Push, Brancherstellung und Branchlöschung, als glob pattern. Ist dieser leer oder nur * angegeben, werden Ereignisse für alle Branches gemeldet. Siehe Dokumentation unter github.com/gobwas/glob für die Syntax. Beispiele: master, {master,release*}. settings.event_repository=Repository settings.event_repository_desc=Repository erstellt oder gelöscht. settings.active=Aktiv -settings.active_helper=Informationen über ausgelöste Ereignisse werden an dieser Webhook-URL gesendet. +settings.active_helper=Informationen über ausgelöste Ereignisse werden an diese Webhook-URL gesendet. settings.add_hook_success=Webhook wurde hinzugefügt. settings.update_webhook=Webhook aktualisieren settings.update_hook_success=Webhook wurde aktualisiert. @@ -1288,6 +1313,8 @@ settings.protect_merge_whitelist_committers=Merge-Whitelist aktivieren settings.protect_merge_whitelist_committers_desc=Erlaube Nutzern oder Teams auf der Whitelist Pull-Requests in diesen Branch zu mergen. settings.protect_merge_whitelist_users=Nutzer, die mergen dürfen: settings.protect_merge_whitelist_teams=Teams, die mergen dürfen: +settings.protect_check_status_contexts=Statusprüfungen aktivieren +settings.protect_check_status_contexts_list=Statusprüfungen, die in der letzten Woche für dieses Repository gefunden wurden settings.protect_required_approvals=Erforderliche Zustimmungen: settings.protect_required_approvals_desc=Erlaube das Zusammenführen des Pull-Requests nur bei ausreichend positiven Zustimmungen von dafür freigeschalteten Nutzern oder Teams. settings.protect_approvals_whitelist_users=Freigeschaltete Reviewer: @@ -1335,6 +1362,11 @@ diff.whitespace_ignore_at_eol=Ignoriere EOL-whitespace-Änderungen diff.stats_desc= %d geänderte Dateien mit %d neuen und %d gelöschten Zeilen diff.bin=BIN diff.view_file=Datei anzeigen +diff.file_before=Vorher +diff.file_after=Nachher +diff.file_image_width=Breite +diff.file_image_height=Höhe +diff.file_byte_size=Größe diff.file_suppressed=Datei-Diff unterdrückt, da er zu groß ist diff.too_many_files=Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden. diff.comment.placeholder=Kommentieren... @@ -1400,11 +1432,13 @@ branch.deleted_by=Von %s gelöscht branch.restore_success=Branch „%s“ wurde wiederhergestellt. branch.restore_failed=Wiederherstellung des Branches „%s“ fehlgeschlagen. branch.protected_deletion_failed=Branch „%s“ ist geschützt und kann nicht gelöscht werden. +branch.restore=Branch „%s“ wiederherstellen +branch.download=Branch „%s“ herunterladen topic.manage_topics=Themen verwalten topic.done=Fertig topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen -topic.format_prompt=Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein. +topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein. [org] org_name_holder=Name der Organisation @@ -1681,6 +1715,7 @@ auths.tip.google_plus=Du erhältst die OAuth2-Client-Zugangsdaten in der Google- auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (/.well-known/openid-configuration), um die Endpunkte zu spezifizieren auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung. +auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.io/en-us/oauth2-provider/ auths.edit=Authentifikationsquelle bearbeiten auths.activated=Diese Authentifikationsquelle ist aktiviert auths.new_success=Die Authentifizierung „%s“ wurde hinzugefügt. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8f6e46b327..d1d560ee41 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -300,6 +300,7 @@ max_size_error = ` must contain at most %s characters.` email_error = ` is not a valid email address.` url_error = ` is not a valid URL.` include_error = ` must contain substring '%s'.` +glob_pattern_error = ` glob pattern is invalid: %s.` unknown_error = Unknown error: captcha_incorrect = The CAPTCHA code is incorrect. password_not_match = The passwords do not match. @@ -318,6 +319,7 @@ enterred_invalid_repo_name = The repository name you entered is incorrect. enterred_invalid_owner_name = The new owner name is not valid. enterred_invalid_password = The password you entered is incorrect. user_not_exist = The user does not exist. +team_not_exist = The team does not exist. last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team. cannot_add_org_to_team = An organization cannot be added as a team member. @@ -557,11 +559,17 @@ confirm_delete_account = Confirm Deletion delete_account_title = Delete User Account delete_account_desc = Are you sure you want to permanently delete this user account? +email_notifications.enable = Enable Email Notifications +email_notifications.onmention = Only Email on Mention +email_notifications.disable = Disable Email Notifications +email_notifications.submit = Set Email Preference + [repo] owner = Owner repo_name = Repository Name repo_name_helper = Good repository names use short, memorable and unique keywords. visibility = Visibility +visibility_description = Only the owner or the organization members if they have rights, will be able to see it. 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.) @@ -572,6 +580,8 @@ fork_visibility_helper = The visibility of a forked repository cannot be changed repo_desc = Description repo_lang = Language repo_gitignore_helper = Select .gitignore templates. +issue_labels = Issue Labels +issue_labels_helper = Select an issue label set. license = License license_helper = Select a license file. readme = README @@ -584,7 +594,7 @@ mirror_prune_desc = Remove obsolete remote-tracking references mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync. mirror_interval_invalid = The mirror interval is not valid. mirror_address = Clone From URL -mirror_address_desc = Include any required authorization credentials in the URL. These must be url escaped as appropriate +mirror_address_desc = Put any required credentials in the Clone Authorization section. mirror_address_url_invalid = The provided url is invalid. You must escape all components of the url correctly. mirror_address_protocol_invalid = The provided url is invalid. Only http(s):// or git:// locations can be mirrored from. mirror_last_synced = Last Synchronized @@ -696,6 +706,8 @@ editor.delete = Delete '%s' editor.commit_message_desc = Add an optional extended description… editor.commit_directly_to_this_branch = Commit directly to the %s branch. editor.create_new_branch = Create a new branch for this commit and start a pull request. +editor.create_new_branch_np = Create a new branch for this commit. +editor.propose_file_change = Propose file change editor.new_branch_name_desc = New branch name… editor.cancel = Cancel editor.filename_cannot_be_empty = The filename cannot be empty. @@ -769,7 +781,7 @@ issues.self_assign_at = `self-assigned this %s` issues.add_assignee_at = `was assigned by %s %s` issues.remove_assignee_at = `was unassigned by %s %s` issues.remove_self_assignment = `removed their assignment %s` -issues.change_title_at = `changed title from %s to %s %s` +issues.change_title_at = `changed title from %s to %s %s` issues.delete_branch_at = `deleted branch %s %s` issues.open_tab = %d Open issues.close_tab = %d Closed @@ -826,6 +838,10 @@ issues.create_comment = Comment issues.closed_at = `closed %[2]s` issues.reopened_at = `reopened %[2]s` issues.commit_ref_at = `referenced this issue from a commit %[2]s` +issues.ref_issue_at = `referenced this issue %[1]s` +issues.ref_pull_at = `referenced this pull request %[1]s` +issues.ref_issue_ext_at = `referenced this issue from %[1]s %[2]s` +issues.ref_pull_ext_at = `referenced this pull request from %[1]s %[2]s` issues.poster = Poster issues.collaborator = Collaborator issues.owner = Owner @@ -945,7 +961,7 @@ issues.review.reviewers = Reviewers issues.review.show_outdated = Show outdated issues.review.hide_outdated = Hide outdated -pulls.desc = Enable merge requests and code reviews. +pulls.desc = Enable pull requests and code reviews. pulls.new = New Pull Request pulls.compare_changes = New Pull Request pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. @@ -964,12 +980,15 @@ pulls.tab_files = Files Changed pulls.reopen_to_merge = Please reopen this pull request to perform a merge. pulls.cant_reopen_deleted_branch = This pull request cannot be reopened because the branch was deleted. pulls.merged = Merged +pulls.merged_as = The pull request has been merged as %[2]s. pulls.has_merged = The pull request has been merged. pulls.title_wip_desc = `Start the title with %s to prevent the pull request from being merged accidentally.` pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the %s prefix from the title when it's ready pulls.data_broken = This pull request is broken due to missing fork information. pulls.files_conflicted = This pull request has changes conflicting with the target branch. pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." +pulls.required_status_check_failed = Some required checks were not successful. +pulls.required_status_check_administrator = As an administrator, you may still merge this pull request. pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." pulls.can_auto_merge_desc = This pull request can be merged automatically. pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. @@ -977,6 +996,7 @@ pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. +pulls.no_merge_status_check = This pull request cannot be merged because not all required status checkes are successful. pulls.merge_pull_request = Merge Pull Request pulls.rebase_merge_pull_request = Rebase and Merge pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) @@ -1118,6 +1138,7 @@ settings.collaboration = Collaborators settings.collaboration.admin = Administrator settings.collaboration.write = Write settings.collaboration.read = Read +settings.collaboration.owner = Owner settings.collaboration.undefined = Undefined settings.hooks = Webhooks settings.githooks = Git Hooks @@ -1125,6 +1146,10 @@ settings.basic_settings = Basic Settings settings.mirror_settings = Mirror Settings settings.sync_mirror = Synchronize Now settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. +settings.email_notifications.enable = Enable Email Notifications +settings.email_notifications.onmention = Only Email on Mention +settings.email_notifications.disable = Disable Email Notifications +settings.email_notifications.submit = Set Email Preference settings.site = Website settings.update_settings = Update Settings settings.advanced_settings = Advanced Settings @@ -1195,6 +1220,11 @@ settings.collaborator_deletion_desc = Removing a collaborator will revoke their settings.remove_collaborator_success = The collaborator has been removed. settings.search_user_placeholder = Search user… settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator. +settings.change_team_access_not_allowed = Changing team access for repository has been restricted to organization owner +settings.team_not_in_organization = The team is not in the same organization as the repository +settings.add_team_duplicate = Team already has the repository +settings.add_team_success = The team now have access to the repository. +settings.remove_team_success = The team's access to the repository has been removed. settings.add_webhook = Add Webhook settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character. settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the webhooks guide. @@ -1244,6 +1274,8 @@ settings.event_pull_request = Pull Request settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized. settings.event_push = Push settings.event_push_desc = Git push to a repository. +settings.branch_filter = Branch filter +settings.branch_filter_desc = Branch whitelist for push, branch creation and branch deletion events, specified as glob pattern. If empty or *, events for all branches are reported. See github.com/gobwas/glob documentation for syntax. Examples: master, {master,release*}. settings.event_repository = Repository settings.event_repository_desc = Repository created or deleted. settings.active = Active @@ -1294,6 +1326,9 @@ settings.protect_merge_whitelist_committers = Enable Merge Whitelist settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. settings.protect_merge_whitelist_users = Whitelisted users for merging: settings.protect_merge_whitelist_teams = Whitelisted teams for merging: +settings.protect_check_status_contexts = Enable Status Check +settings.protect_check_status_contexts_desc = Require status checks to pass before merging Choose which status checks must pass before branches can be merged into a branch that matches this rule. When enabled, commits must first be pushed to another branch, then merged or pushed directly to a branch that matches this rule after status checks have passed. If no contexts are selected, the last commit must be successful regardless of context. +settings.protect_check_status_contexts_list = Status checks found in the last week for this repository settings.protect_required_approvals = Required approvals: settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. settings.protect_approvals_whitelist_users = Whitelisted reviewers: @@ -1341,6 +1376,11 @@ diff.whitespace_ignore_at_eol = Ignore changes in whitespace at EOL diff.stats_desc = %d changed files with %d additions and %d deletions diff.bin = BIN diff.view_file = View File +diff.file_before = Before +diff.file_after = After +diff.file_image_width = Width +diff.file_image_height = Height +diff.file_byte_size = Size diff.file_suppressed = File diff suppressed because it is too large diff.too_many_files = Some files were not shown because too many files changed in this diff diff.comment.placeholder = Leave a comment @@ -1406,6 +1446,8 @@ branch.deleted_by = Deleted by %s branch.restore_success = Branch '%s' has been restored. branch.restore_failed = Failed to restore branch '%s'. branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted. +branch.restore = Restore Branch '%s' +branch.download = Download Branch '%s' topic.manage_topics = Manage Topics topic.done = Done @@ -1441,6 +1483,8 @@ settings.options = Organization settings.full_name = Full Name settings.website = Website settings.location = Location +settings.permission = Permissions +settings.repoadminchangeteam = Repository admin can add and remove access for teams settings.visibility = Visibility settings.visibility.public = Public settings.visibility.limited = Limited (Visible to logged in users only) @@ -1692,6 +1736,7 @@ auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API con auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me +auths.tip.gitea = Register a new OAuth2 application. Guide can be found at https://docs.gitea.io/en-us/oauth2-provider/ auths.edit = Edit Authentication Source auths.activated = This Authentication Source is Activated auths.new_success = The authentication '%s' has been added. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index cd77156202..675de019b0 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -60,7 +60,7 @@ your_starred=Destacado your_settings=Configuración all=Todos -sources=Proprios +sources=Propios mirrors=Réplica collaborative=Colaborativos forks=Forks @@ -305,6 +305,7 @@ password_not_match=Las contraseñas no coinciden. username_been_taken=El nombre de usuario ya está en uso. repo_name_been_taken=El nombre del repositorio ya está usado. +visit_rate_limit=Remoto tiene limitación de tasa de acceso. 2fa_auth_required=Requerir autenticación de doble factor a visitas remotas. org_name_been_taken=Ya existe una organización con este nombre. team_name_been_taken=Ya existe un equipo con este nombre. @@ -555,6 +556,7 @@ confirm_delete_account=Confirmar Eliminación delete_account_title=Eliminar cuenta de usuario delete_account_desc=¿Está seguro que desea eliminar permanentemente esta cuenta de usuario? + [repo] owner=Propietario repo_name=Nombre del repositorio @@ -582,7 +584,6 @@ mirror_prune_desc=Eliminar referencias de seguimiento de remotes obsoletas mirror_interval=Intervalo de réplica (Las unidades de tiempo válidas son 'h', 'm', 's'). Pone 0 para deshabilitar la sincronización automática. mirror_interval_invalid=El intervalo de réplica no es válido. mirror_address=Clonar desde URL -mirror_address_desc=Incluir cualquier credencial de autorización requerida en la URL. Debe ser la url escapada como corresponda mirror_address_url_invalid=La url proporcionada no es válida. Debe escapar correctamente de todos los componentes de la url. mirror_address_protocol_invalid=La url proporcionada no es válida. Sólo las ubicaciones http(s):// o git:// pueden ser replicadas desde. mirror_last_synced=Sincronizado por última vez @@ -694,6 +695,7 @@ editor.delete=Eliminar '%s' editor.commit_message_desc=Añadir una descripción extendida opcional… editor.commit_directly_to_this_branch=Hacer commit directamente en la rama %s. editor.create_new_branch=Crear una nueva rama para este commit y hacer un pull request. +editor.propose_file_change=Proponer cambio de archivo editor.new_branch_name_desc=Nombre de la rama nueva… editor.cancel=Cancelar editor.filename_cannot_be_empty=El nombre del archivo no puede estar vacío. @@ -767,7 +769,6 @@ issues.self_assign_at=`auto asignado este %s` issues.add_assignee_at='fue asignado por %s %s' issues.remove_assignee_at=`fue desasignado por %s %s` issues.remove_self_assignment=`eliminado su asignación %s` -issues.change_title_at=`título cambiado de %s a %s %s` issues.delete_branch_at=`rama eliminada %s %s` issues.open_tab=%d abiertas issues.close_tab=%d cerradas @@ -943,7 +944,6 @@ issues.review.reviewers=Revisores issues.review.show_outdated=Mostrar obsoletos issues.review.hide_outdated=Ocultar obsoletos -pulls.desc=Habilitar solicitudes de fusión y revisiones de código. pulls.new=Nuevo Pull Request pulls.compare_changes=Nuevo pull request pulls.compare_changes_desc=Seleccione la rama en la que se fusiona y la rama a recuperar. @@ -1404,6 +1404,8 @@ branch.deleted_by=Eliminada por %s branch.restore_success=La rama '%s' ha sido restaurada. branch.restore_failed=Fallo al restaurar la rama %s. branch.protected_deletion_failed=La rama '%s' está protegida. No se puede eliminar. +branch.restore=Restaurar rama '%s' +branch.download=Descargar rama '%s' topic.manage_topics=Administrar temas topic.done=Hecho diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index f0602dbf67..0ff49d19d3 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -8,6 +8,7 @@ sign_in=ورود sign_in_with=ورود به سیستم با sign_out=خروج sign_up=ثبت نام +link_account=پیوند به حساب register=ثبت نام website=وب‌سایت version=نسخه @@ -77,16 +78,26 @@ loading=بارگذاری… [install] install=نصب و راه اندازی title=تنظیمات اولیه +docker_helper=اگر گیتی را با داکر اجرا کرده‌اید، لطفا قبل از هر تغییری مستندات را مطالعه نمایید. +requite_db_desc=گیتی به MySQL, PostgreSQL, MSSQL یا SQLite3 نیاز دارد. db_title=تنظیمات پایگاه داده db_type=نوع پایگاه داده host=میزبان user=نام کاربری password=رمز عبور db_name=نام پایگاه داده +db_helper=نکته برای کاربران MySQL: لطفا از موتور InnoDB استفاده کنید و اگر از نوع کدینگ "utf8mb4" استفاده می کنید، ورژن InnoDB خود را به بالای 5.6 به روز رسانی کنید. ssl_mode=SSL +charset=نوع کدگذاری path=مسیر +sqlite_helper=مسیر فایل برای دیتابیس SQLite3.
    اگر گیتی را به عنوان یک سرویس اجرا میکنید، یک مسیر کامل وارد کنید. +err_empty_db_path=مسیر دیتابیس SQLite3 نمیتواند خالی باشد. no_admin_and_disable_registration=شما بدون ایجاد حساب‌ کاربری مدیر نمی‌توانید عضویت را غیر فعال کنید. err_empty_admin_password=کلمه عبور حساب مدیر نمی تواند خالی باشد. +err_empty_admin_email=رایانامه (ایمیل) مدیر نمی تواند خالی باشد. +err_admin_name_is_reserved=نام کاربری مدیر اشتباه است. نام کاربری قبلا استفاده شده است +err_admin_name_pattern_not_allowed=نام کاربری مدیر اشتباه است. نام کاربری با این الگو امکان پذیر نیست +err_admin_name_is_invalid=نام کابری مدیر اشتباه است general_title=تنظیمات عمومی app_name=عنوان سایت @@ -94,7 +105,9 @@ app_name_helper=شما می توانید نام شرکت خود را در این repo_path=مسیر ریشه مخزن repo_path_helper=تمام مخازن کد راه دور در این پوشه ذخیره می‌شوند. lfs_path=مسیر Git LFS +lfs_path_helper=فایل هایی که توسط Git LFS دنبال میشوند در این پوشه ذخیره خواهند شد. درصورت خالی بودن فیلد این قابلیت غیرفعال خواهد بود. run_user=اجرا به عنوان نام کاربری +run_user_helper=نام کاربری ای که گیتی با آن اجرا میشود را وارد کنید. توجه کنید که کاربر باید به پوشه ریشه مخزن دسترسی داشته باشد. domain=دامنه سرور SSH domain_helper=آدرس دامنه یا میزبان برای SSH کلون آدرس ها. ssh_port=پورت SSH سرور @@ -102,6 +115,7 @@ ssh_port_helper=شماره درگاهی که سرور SSH گوش می دهد. ب http_port=پورت HTTP گیتی http_port_helper=پورت سرور وب گیتی. app_url=آدرس پایه گیتی +app_url_helper=آدرس پایه برای URLهای اجماع HTTP(S) و هشدار های رایانامه (ایمیل). log_root_path=مسیر گزارش‌ها log_root_path_helper=فایل‌های گزارش روی این مسیر ذخیره خواهند شد. @@ -109,107 +123,1846 @@ optional_title=تنظیمات اختیاری email_title=تنظیمات ایمیل smtp_host=میزبان SMTP smtp_from=ارسال ایمیل به عنوان +smtp_from_helper=آدرس ایمیلی که گیتی استفاده میکند. یک ایمیل وارد کنید یا به "Name" شکل استفاده کنید. +mailer_user=نام کاربری SMTP +mailer_password=گذرواژه SMTP +register_confirm=نیاز به تایید ایمیل ثبت نام +mail_notify=فعال‌سازی اعلان‌های ایمیل +server_service_title=تنظیمات سرور و سرویس‌های شخص ثالث +offline_mode=فعال کردن حالت محلی +offline_mode_popup=غیر فعال کردن شبکه های شخص ثالث تحویل محتوا و استفاده از تمام منابع به صورت محلی. +disable_gravatar=غیر فعال کردن Gravatar +disable_gravatar_popup=غیر فعال کردن کلیک و منابع آواتار شخص ثالث. مگر در مواردی که کاربر محلی بارگزاری آواتار پیش فرض استفاده خواهد شد. +federated_avatar_lookup=فعال سازی آواتار مشترک +federated_avatar_lookup_popup=مراجعه مشترک آواتار با استفاده از Libravatar را قادر می سازد. +disable_registration=غیرفعال‌کردن خود ثبت نامی +disable_registration_popup=غیرفعال کردن ثبت نام کاربر. تنها مدیر ها قادر خواهند بود حساب کاربری جدید اضافه کنند. +allow_only_external_registration_popup=اجازه ثبت نام فقط از طریق خدمات خارجی +openid_signin=فعالسازی ورود با OpenID +openid_signin_popup=فعالسازی ورود کاربر با OpenID. +openid_signup=فعالسازی ثبت نام با OpenID +openid_signup_popup=فعال سازی ثبت نام با استفاده از OpenID. +enable_captcha=فعال کردن کپچا +enable_captcha_popup=عضویت افراد نیازمند کپچا است. +require_sign_in_view=فعال‌سازی نیازمند به ورود در هنگام مشاهده صفحات +require_sign_in_view_popup=کاربران وارد شده دسترسی به صفحات را دارند. مهمان‌ها فقط قادر به دیدن صفحه 'ثبت نام' و 'ورود' هستند. +admin_setting_desc=ساخت حساب مدیر اختیاری است. اولین کاربری که ثبت‌نام میکنید مدیر خواهد بود. +admin_title=تنظیمات حساب مدیر +admin_name=نام کاربری مدیر +admin_password=گذرواژه +confirm_password=تکرارگذواژه +admin_email=نشانی رایانامه (ایمیل) +install_btn_confirm=نصب گیتی +test_git_failed=عدم توانایی در آزمایش دستور 'git' توضیح بیشتر: %v +sqlite3_not_available=نسخه مورد استفاده شما از SQLite3 پشتیبانی نمی کند. لطفا نسخه باینری رسمی را از s% دانلود کنید و از ورژن gobuild هم استفاده نکنید. +invalid_db_setting=تنظیمات پایگاه داده معتبر نیست: %v +invalid_repo_path=مسیر ریشه مخزن نامعتبر است: %v +run_user_not_match=نام کاربری 'اجرا به عنوان' نام کاربری فعلی نیست: %s -> %s +save_config_failed=تنظیمات ذخیره نشد: %v +invalid_admin_setting=تنظیمات حساب مدیر نامعتبر است: %v +install_success=خوش آمدی! از شما به خاطر انتخاب گیتی یا گیت‌گو تشکر میکنیم. لذت ببرید و مراقب باشید! +invalid_log_root_path=مسیر گزارش معتبر نیست: %v +default_keep_email_private=مخفی کردن نشانی های ایمیل به صورت پیش فرض +default_keep_email_private_popup=مخفی کردن نشانی های ایمیل از حساب های کاربر جدید به صورت پیش فرض. +default_allow_create_organization=اجازه ایجاد سازمان به صورت پیش فرض +default_allow_create_organization_popup=اجازه به کاربران جدید برای ایجاد سازمان به صورت پیش‌فرض. +default_enable_timetracking=فعال سازی پیگیری زمان به صورت پیش فرض +default_enable_timetracking_popup=فعالسازی پیگیری زمان برای سازمان‌های جدید به صورت پیش‌فرض. +no_reply_address=مخفی کردن دامنه ایمیل +no_reply_address_helper=نام دامنه برای کاربران دارای آدرس ایمیل پنهان است. به عنوان مثال ، اگر نام دامنه ایمیل مخفی روی "noreply.example.org" تنظیم شده باشد ، نام کاربری "joe" در Git به عنوان "joe@noreply.example.org" وارد می شود [home] +uname_holder=نام کاربری یا نشانی ایمیل +password_holder=گذرواژه +switch_dashboard_context=تغییر محتوای پیشخوان +my_repos=مخازن +show_more_repos=نمایش مخازن بیشتر… +collaborative_repos=مخازن همکاری +my_orgs=سازمان های من +my_mirrors=قرینه‌های من +view_home=نمایش %s +search_repos=یافتن مخزن… +issues.in_your_repos=در مخازن شما [explore] +repos=مخازن +users=کاربران +organizations=سازمان ها +search=جستجو +code=کد +repo_no_results=مخزنی مطابق با این مورد یافت نشد. +user_no_results=کاربری مطابق با این مورد یافت نشد. +org_no_results=سازمانی مطابق با این مورد یافت نشد. +code_no_results=کد منبعی مطابق با جستجوی شما یافت نشد. +code_search_results=نتایج جستجو برای '%s ' [auth] +create_new_account=نام‌نویسی حساب کاربری +register_helper_msg=قبلا ثبت نام کردید؟ از اینجا وارد شوید! +social_register_helper_msg=از قبل حساب دارید؟ آن را متصل کنید! +disable_register_prompt=با عرض پوزش، ثبت نام غیرفعال شده است. لطفا با مدیر سایت تماس بگیرید. +disable_register_mail=ایمیل تایید برای ثبت نام غیر فعال است. +remember_me=مرا به خاطر بسپار +forgot_password_title=گذرواژه خود را فراموش کرده ام +forgot_password=گذرواژه خود را فراموش کرده‌اید؟ +sign_up_now=نیاز به یک حساب دارید؟ هم‌اکنون ثبت نام کنید. +sign_up_successful=حساب با موفقیت ایجاد شد. +confirmation_mail_sent_prompt=ایمیل تاییدیه جدیدی به %s ارسال شد. لطفا صندوق ورودی خود را در %d ساعت آینده برای تکمیل فرایند ثبت نام بررسی کنید. +must_change_password=گذرواژه خود را به روز کنید +allow_password_change=نیاز به کاربر برای تغییرگذرواژه (توصیه می شود) +reset_password_mail_sent_prompt=ایمیل تاییدیه جدیدی به %s ارسال شد. لطفا صندوق ورودی خود را در %s آینده برای فرآیند بازیابی حساب کاربری خود بررسی کنید. +active_your_account=حساب خود را فعال کنید +account_activated=حساب فعال شده است +prohibit_login=ورود به سیستم ممنوع است +prohibit_login_desc=ورود به حساب کاربری برای شما ممنوع شده است ، لطفا با مدیر سایت تماس بگیرید. +resent_limit_prompt=با عرض پوزش، شما به تازگی یک ایمیل فعالسازی را درخواست کرده اید. لطفا سه دقیقه منتظر بمانید سپس درخواست خود را تکرار کنید. +has_unconfirmed_mail=سلام %s, شما آدرس ایمیل (%s) را تایید نکرده اید. لطفا اگر شما ایمیلی دریافت نکرداید و یا نیاز به ارسال دوباره دارید، بر روید دکمه زیر کلیک نمایید. +resend_mail=برای ارسال نامه فعال سازی اینجا را کلیک کنید +email_not_associate=این نشانی ایمیل در سیستم ثبت نشده است. +send_reset_mail=ارسال ایمیل بازیابی حساب کاربری +reset_password=بازیابی حساب +invalid_code=کد تایید غیرمعتبر بوده و یا منقضی شده است. +reset_password_helper=بازیابی حساب +reset_password_wrong_user=شما به عنوان %sوارد شدید اما لینک بازیابی حساب برای %s است +password_too_short=طول گذرواژه نمی تواند کمتر از %d کاراکتر باشد. +non_local_account=کاربران غیر محلی نمیتوانند گذرواژه خود را از طریق واسط وب گیتی به روز کنند. +verify=تایید‌کنید +scratch_code=کد پایه +use_scratch_code=استفاده از کدپایه +twofa_scratch_used=شما کد ابتدا خود استفاده کرده اند. بنابراین شما ممکن است ثبت نام دستگاه شما را حذف یا تولید کد جدید ابتدا شما را به صفحه تنظیمات دو عامل هدایت شده. +twofa_passcode_incorrect=گذرواژه شما اشتباه است. اگر شما دستگاه خود را در جای اشتباه قرار داده اید, از کد ابتدای خود برای ورود به سیستم استفاده کنید. +twofa_scratch_token_incorrect=کد ابتدا شما نادرست است. +login_userpass=ورود +login_openid=OpenID +oauth_signup_tab=ثبت نام یک حساب جدید +oauth_signup_title=اضافه کردن ایمیل و گذرواژه (برای بازیابی حساب) +oauth_signup_submit=تکمیل حساب کاربری +oauth_signin_tab=پیوند به حساب های موجود +oauth_signin_title=برای تایید، به حساب کاربری متصل شده وارد شوید +oauth_signin_submit=اتصال به حساب +openid_connect_submit=اتصال +openid_connect_title=اتصال به حساب های موجود +openid_connect_desc=نشانی OpenID URI وارد شده شناخته نشد. آن را با یک حساب جدید متصل کنید. +openid_register_title=ایجاد یک حساب جدید +openid_register_desc=نشانی URI وارد شده شناخته نشد. آن را با یک حساب جدید متصل کنید. +openid_signin_desc=نوع حساب کاربری خود را وارد کنید. به عنوان مثال: https://anne.me و bob.openid.org.cn یا gnusocial.net/carry. +disable_forgot_password_mail=بازیابی حساب غیر فعال شده است. لطفا با مدیر سایت تماس بگیرید. +email_domain_blacklisted=شما نمیتوانید با ایمیل خود ثبت نام کنید. +authorize_application=برنامه احراز هویت +authroize_redirect_notice=اگر شما این برنامه را تایید کنید، به %s منتقل خواهید شد. +authorize_application_created_by=این برنامه توسط %s ساخته شده است. +authorize_application_description=اگر شما دسترسی داشته باشید. میتوانید تمامی فیلد های حساب کاربری خود را تغییر دهید. از جمله مخازن و سازمان های خصوصی. +authorize_title=تاییدیه "%s" برای دسترسی به اکانت شما؟ +authorization_failed=احراز هویت انجام نشد +authorization_failed_desc=تاییدیه ناموفق بود. لذا ما درخواست نامعتبر تشخیص داده ایم. لطفا با صاحب اصلی این برنامه تماس برقرار کنید تا تاییده صادر کند. [mail] +activate_account=لطفا حساب خود را فعال کنید +activate_email=نشانی ایمیل خود را تایید کنید +reset_password=حساب خود را دوباره فعال کنید +register_success=ثبت‌نام با موفقیت انجام شد +register_notify=به گیتی یا گیت‌گو خوش آمدید [modal] +yes=بله +no=خیر +modify=بروزرسانی [form] +UserName=نام‎کاربری +RepoName=نام مخزن +Email=نشانی رایانامه (ایمیل) +Password=گذرواژه +Retype=نوشتن مجدد گذرواژه +SSHTitle=نام کلید SSH +HttpsUrl=نشانی HTTPS +PayloadUrl=نشانی Payload +TeamName=نام تیم +AuthName=نام احراز هویت +AdminEmail=ایمیل مدیر +NewBranchName=نام شاخه‌ی جدید +CommitSummary=خلاصه ی کامیت +CommitMessage=پیام کامیت +CommitChoice=انتخاب کامیت +TreeName=مسیر پرونده +Content=محتوا +require_error=` نمی تواند خالی باشد.` +alpha_dash_error=باید فقط عدد و الفبایی، فاصله باشد ('-') و کاراکتر خط تیره پایین ('_'). +alpha_dash_dot_error=باید فقط عدد و الفبایی، فاصله باشد ('-') و کاراکتر خط تیره پایین ('_') و نقطه ('.') +git_ref_name_error=باید یک نام مرجع کاملاً شکل یافته Git باشد. +size_error=` باید به اندازه %s باشد.` +min_size_error=` حداقل باید شامل %s کاراکتر باشد.` +max_size_error=` حداکثر باید شامل %s کاراکتر باشد.` +email_error=` ساختار نشانی ایمیل صحیح نیست.` +url_error=` ساختار نشانی اینترنتی صحیح نیست.` +include_error=` باید شامل '%s' باشد.` +glob_pattern_error=`الگوی قطره‌ای (glob) نامعتبر است: %s.` +unknown_error=خطای ناشناخته: +captcha_incorrect=کد امنیتی اشتباه است. +password_not_match=گذرواژه‌ها باید یکسان باشند. +username_been_taken=این نام کاربری قبلا ثبت شده است. +repo_name_been_taken=نام مخزن قبلا ثبت شده است. +visit_rate_limit=دسترسی نشانی ثبت شده دارای نرخ محدودیت است. +2fa_auth_required=دسترسی از راه دور بازدید نیازمند دو روش احراز هویت است. +org_name_been_taken=نام سازمان قبلا ثبت شده است. +team_name_been_taken=نام تیم قبلا ثبت شده است. +team_no_units_error=اجازه دسترسی به حداقل یک بخش مخزن. +email_been_used=این ایمیل قبلا ثبت شده. +openid_been_used=آدرس OpenID %s قبلا ثبت شده است. +username_password_incorrect=نام کاربری یا گذرواژه صحیح نیست. +enterred_invalid_repo_name=نام مخزنی که وارد کرده اید صحیح نمی باشد. +enterred_invalid_owner_name=نام مالک جدید معتبر نیست. +enterred_invalid_password=گذرواژه وارد شده صحیح نیست. +user_not_exist=کاربر وجود ندارد. +last_org_owner=قادر به حذف کاربر آخر از تیم "صاحبان" نیست. باید حداقل یک مالک در هر تیم باشد. +cannot_add_org_to_team=یک سازمان را نمی توان به عنوان عضو تیم اضافه کرد. +invalid_ssh_key=کلید SSH شما تأیید نشد: %s +invalid_gpg_key=کلید GPG شما تأیید نشد: %s +unable_verify_ssh_key=کلید SSH شما تأیید نشد. مجددا بررسی کنید. +auth_failed=تشخیص هویت ناموفق: %v +still_own_repo=حساب شما یک یا چند مخزن دارد، ابتدا آنها را حذف یا انتقال دهید. +still_has_org=حساب شما عضو یک یا چند سازمان است. ابتدا آنها ترک کنید. +org_still_own_repo=این سازمان در حال حاضر مالک برخی مخازن است، شما ابتدا باید آن ها را حذف یا منتقل کنید. +target_branch_not_exist=شاخه مورد نظر وجود ندارد. [user] +change_avatar=تغییر آواتار… +join_on=ملحق شد در +repositories=مخازن +activity=فعالیت های عمومی +followers=دنبال کنندگان +starred=مخان ستاره دار +following=دنبال میکنید +follow=دنبال کردن +unfollow=عدم دنبال کردن +heatmap.loading=بارگذاری Heatmap… +user_bio=زندگی‌نامه +form.name_reserved=نام کاربری "%s" استفاده شده است. +form.name_pattern_not_allowed=الگوی %s در نام کاربری مجاز نیست. [settings] +profile=نمایه +account=حساب کاربری +password=گذرواژه +security=امنیت +avatar=آواتار +ssh_gpg_keys=کلید‌های SSH / GPG +social=حساب های اجتماعی +applications=برنامه‌ها +orgs=مدیریت سازمان‌ها +repos=مخازن +delete=حذف حساب کاربری +twofa=احراز هویت دوگانه +account_link=حساب‌های مرتبط +organization=سازمان ها +uid=Uid +u2f=کلید های امنیتی +public_profile=نمایه عمومی +profile_desc=نشانی ایمیل شما برای آگاه سازی و دیگر عملیات مورد استفاده قرار می‌گیرد. +password_username_disabled=حساب‌های غیر محلی مجاز به تغییر نام کاربری نیستند. لطفا با مدیر سایت در ارتباط باشید. +full_name=نام کامل +website=تارنما +location=موقعیت مکانی +update_theme=بروز رسانی پوسته +update_profile=بروز‌رسانی نمایه +update_profile_success=نمایه شما بروزرسانی شد. +change_username=نام کاربری شما تغییر کرد. +change_username_prompt=توجه: تغییر نام‌کاربری، نشانی URL حساب شما را نیز تغییر می‌دهد. +continue=ادامه +cancel=انصراف +language=زبان +ui=پوسته +lookup_avatar_by_mail=جست و جو آواتار توسط نشانی ایمیل +federated_avatar_lookup=جستجو برای آواتار مشترک +enable_custom_avatar=استفاده از آواتار دلخواه +choose_new_avatar=انتخاب آواتار جدید +update_avatar=بروزرسانی آواتار +delete_current_avatar=حذف آواتار فعلی +uploaded_avatar_not_a_image=فایل بار‌گذاری شده تصویر نمی‌باشد. +uploaded_avatar_is_too_big=حجم فایل بارگزاری بیش از حد مجاز است. +update_avatar_success=آواتار شما تغییر کرد. +change_password=تغییر گذرواژه +old_password=گذارواژه فعلی +new_password=گذرواژه جدید +retype_new_password=گذرواژه جدید را دوباره وارد کنید +password_incorrect=گذرواژه فعلی شما اشتباه است. +change_password_success=گذرواژه شما تغییر کرد. از این پس با گذرواژه جدید خود وارد شوید. +password_change_disabled=کاربران غیر محلی نمیتوانند گذرواژه خود را از طریق واسط وب گیتی به روز کنند. +emails=نشانی‌های ایمیل +manage_emails=مدیریت نشانی‌های ایمیل +manage_themes=تم پیش فرض را انتخاب کنید +manage_openid=مدیریت نشانی‌های OpenID +email_desc=نشانی ایمیل اصلی شما برای آگاه سازی و دیگر عملیات مورد استفاده قرار می‌گیرد. +theme_desc=این پوشته پیش فرض شما در سراسر سایت می باشد. +primary=اصلی +primary_email=انتخاب به عنوان اصلی +delete_email=حذف +email_deletion=حذف نشانی ایمیل +email_deletion_desc=نشانی ایمیل و اطلاعات مربوطه از حساب شما حذف خواهد شد. تمامی کامیت ها توسط این نشانی ایمیل بدون تغییر باقی می ماند. ادامه می دهید؟ +email_deletion_success=آدرس ایمیل شما حذف شده است. +theme_update_success=پوسته شما آپدیت شد. +theme_update_error=پوسته انتخاب شده موجود نیست. +openid_deletion=حذف نشانی OpenID +openid_deletion_desc=حذف این نشانی OpenID از حساب شما باعث می شود که نتوانید با آن وارد شوید. ادامه می‌دهید؟ +openid_deletion_success=نشانی OpenID حذف شد. +add_new_email=اضافه کردن نشانی ایمیل جدید +add_new_openid=اضافه کردن نشانی OpenID جدید +add_email=اضافه کردن نشانی ایمیل +add_openid=اضافه کردن نشانی OpenID +add_email_confirmation_sent=یک ایمیل تایید به نشانی %s ارسال شد, لطفا صندوق خود را حداکثر تا %s آینده برای تکمیل فرایند تایید بررسی کنید. +add_email_success=نشانی ایمیل جدید اضافه شده است. +add_openid_success=نشانی OpenID اضافه شد. +keep_email_private=مخفی کردن نشانی ایمیل +keep_email_private_popup=نشانی ایمیل شما به کاربران دیگر نمایش داده نمی‌شود. +openid_desc=OpenID به شما امکان می دهد احراز هویت را به یک ارائه دهنده خارجی واگذار کنید. +manage_ssh_keys=مدیریت کلیدهای اس‌اس‌اچ +manage_gpg_keys=مدیریت کلید GPG +add_key=افزودن کلید +ssh_desc=این کلیدهای عمومی SSH با حساب شما در ارتباط هستند. کلیدهای خصوصی مربوطه اجازه دسترسی کامل به مخازن شما را می دهند. +gpg_desc=این کلید های عمومی GPG به حساب کاربری شما مرتبط مرتبط است. کلید خصوصی خود را محرمانه نگهدارید لذا با آن امکان ارسال commit تایید شده است. +ssh_helper=آیا نمی دانید چگونه؟ + راهنمایی Github را برای ساخت کلید SSH برای خود ببینید +یا ممکن است با راه حل استفاده از SSH در مشکلات متداول مواجه شوید. +gpg_helper=به کمک نیاز دارید؟ نگاهی به در GitHub را راهنمای مورد GPG است. +add_new_key=اضافه کردن کلید SSH +add_new_gpg_key=اضافه کردن کلید GPG +ssh_key_been_used=این کلید SSH پیش از این به سرور افزوده شده است. +ssh_key_name_used=یک کلید SSH با این نام پیش از این به حساب کاربری شما افزوده شده است. +gpg_key_id_used=یک کلید GPG با این ID پیش از این وجود داشته است. +gpg_no_key_email_found=این کلید GPG با هیچ ایمیلی که به حساب شما مرتبط است، قابل استفاده نیست. +subkeys=کلید های زیر مجموعه +key_id=شناسه کلید +key_name=نام کلید +key_content=محتوا +add_key_success=کلید SSH '%s' اضافه شده است. +add_gpg_key_success=کلید GPG '%s' اضافه شده است. +delete_key=حذف +ssh_key_deletion=SSH کلید حذف +gpg_key_deletion=حذف کلید GPG +ssh_key_deletion_desc=حذف کلید SSH خود دسترسی به حساب خود را لغو می شود. ادامه می دهید؟ +gpg_key_deletion_desc=حذف کلید GPG تمامی commit های که با این کلید زده‎اید را غیر معتبر میکند. آیا ادامه می‎دهید؟ +ssh_key_deletion_success=کلید SSH حذف شد. +gpg_key_deletion_success=کلید GPG حذف شد. +add_on=اضافه شده در تاریخ +valid_until=معتبر تا +valid_forever=معتبر برای همیشه +last_used=آخرین تاریخ استفاده +no_activity=اخیراً فعالیتی انجام نشده است +can_read_info=خواندن +can_write_info=نوشتن +key_state_desc=این کلید در ۷ روز گذشته استفاده شده است +token_state_desc=این توکن در ۷ روز گذشته استفاده شده است +show_openid=نمایش بر روی نمایه +hide_openid=مخفی کردن از نمایه +ssh_disabled=SSH غیر فعل شد +manage_social=مدیریت حساب های اجتماعی مرتبط +social_desc=تمامی شبکه های اجتماعی که به حساب کاربری شما متصل است. مطمئن شوید که با تمامی آنها می‎توانید به صورت ایمن وارد شوید. +unbind=لغو ارتباط +unbind_success=حساب کاربری اجتماعی از حساب کابری شما جدا شد. +manage_access_token=مدیریت توکن های دسترسی +generate_new_token=تولید توکن جدید +tokens_desc=این token قابلیت دسترسی به اکانت شما را توسط API میدهد. +new_token_desc=برنامه های از token شما می‎تواندد دسترسی کامل از حساب کاربری شما بگیرند. +token_name=نام توکن +generate_token=ساخت توکن +generate_token_success=اکنون token جدید ساخته شد. همینک آنها را کپی کنید دوباره آن را نخواهید دید. +delete_token=حذف +access_token_deletion=حذف توکن +access_token_deletion_desc=حذف token باعث از کار افتادن تمامی برنامه‎هایی که در آنها به کار رفته می‎شود. آیا ادامه می‎دهید؟ +delete_token_success=token مورد نظر حذف شد. برنامه هایی که از آن استفاده می‎کنند به زودی دسترسی به حساب کاربری شما را از دست می‎دهند. +manage_oauth2_applications=مدیریت برنامه‎های OAuth2 +edit_oauth2_application=ویرایش برنامه OAuth2 +oauth2_applications_desc=برنامه‎های OAuth2 احراز هویت برنامه های شخص ثالث را با بستری امن میسر می‎کند. +remove_oauth2_application=حذف برنامه OAuth2 +remove_oauth2_application_desc=حذف برنامه OAuth2 دسترسی تمام برنامه های متصل با آن را از بین می‎برد. آیا ادامه می‎دهید؟ +remove_oauth2_application_success=برنامه حذف شده است. +create_oauth2_application=ساختن یک برنامه OAuth2 جدید +create_oauth2_application_button=ایجاد برنامه +create_oauth2_application_success=برنامه OAuth2 جدید شما با موفقیت ساخته شد. +update_oauth2_application_success=برنامه OAuth2 با موفقیت به‎روزرسانی شد. +oauth2_application_name=نام برنامه +oauth2_select_type=کدام نوع برنامه متناسب است؟ +oauth2_type_web=وب (مثلا Node.JS, Tomcat, Go) +oauth2_type_native=بومی (مثلا، Mobile, Desktop, Browser) +oauth2_redirect_uri=تغییر مسیر به نشانی اینترنتی +save_application=ذخيره +oauth2_client_id=شناسه کلاینت +oauth2_client_secret=کلمه امن مشتری +oauth2_regenerate_secret=تولید دوباره کلمه امن +oauth2_regenerate_secret_hint=کلمه امن خود را فراموش کرده اید؟ +oauth2_client_secret_hint=اگر این صفحه را مشاهده کنید کلمه محرمانه نمایش داده نخواهد شد. لطفا کلمه محرمانه خود را ذخیره کنید. +oauth2_application_edit=ويرايش +oauth2_application_create_description=برنامه‎های OAuth2 این امکان را به برنامه‎های شخص ثالث می‎دهد یک نمونه از دسترسی به حساب کاربری شما داشته باشد. +oauth2_application_remove_description=حذف برنامه OAuth2 دسترسی حساب هایی که با آن فعال است را مختل می‎کند. آیا ادامه می‎دهید؟ +authorized_oauth2_applications=برنامه OAuth2 تایید شد +authorized_oauth2_applications_description=این به برنامه های شخص ثالث به حساب Gitea شخصی خود دسترسی پیدا کرده. لطفاً دسترسی به برنامه های دیگر مورد نیاز را لغو کنید. +revoke_key=ابطال +revoke_oauth2_grant=ابطال دسترسی +revoke_oauth2_grant_description=لغو دسترسی برای این برنامه شخص ثالث از دسترسی این برنامه به داده های شما جلوگیری می کند. شما مطمئن هستید؟ +revoke_oauth2_grant_success=شما اجازه دسترسی را لغو کردید. +twofa_desc=احراز هویت دو مرحله ای امنیت حساب شما را افزایش میدهد. +twofa_is_enrolled=احراز هویت دو مرحله ای برای حساب شما اجرامیشود. +twofa_not_enrolled=حساب کاربری شما اکنون احراز هویت دو مرحله ای ندارد. +twofa_disable=غیرفعال‌کردن احراز هویت دو مرحله ای +twofa_scratch_token_regenerate=ساخت مجدد Scratch Token +twofa_scratch_token_regenerated=Token چکنویس شما اکنون %s است. آن را در مکانی امن ذخیره نمایید. +twofa_enroll=فعال‌کردن احراز هویت دوگانه +twofa_disable_note=در صورت لزوم، شما می توانید احراز هویت دو مرحله ای را غیر فعال کنید. +twofa_disable_desc=غیر فعال کردن احراز هویت دو مرحله ای امنیت حساب کاربری شما را کمتر می‌کند. آیا ادامه می‌دهید؟ +regenerate_scratch_token_desc=اگر شما token چک‌نویس خود را گم کرده اید می‌توانید پس از ورود آن‌را reset کنید. +twofa_disabled=احراز هویت دو مرحله ای غیر فعال گشت. +scan_this_image=این تصویر را با برنامه احراز هویت خود اسکن نمایید: +or_enter_secret=و یا رمز را وارد کنید: %s +then_enter_passcode=و کد ورود نمایش داده شده در درخواست را وارد کنید: +passcode_invalid=کد ورود نامعتبر است. مجددا تلاش نمایید. +twofa_enrolled=ورود به حسابت کاربری دو مرحله ای فعال شد. لطفا token خود را (%s) نگهداری کنید. لذا فقط یک بار نمایش داده می‌شود! +u2f_desc=کلیدهای امنیتی دستگاههای سخت افزاری هستند که دارای کلیدهای رمزنگاری هستند. از آنها می توان برای احراز هویت دو مرحله ای استفاده کرد کلیدهای امنیتی باید از استاندارد FIDO U2F پشتیبانی کنند. +u2f_require_twofa=احراز هویت دو مرحله ای حساب کاربری شما باید فعال شود که بتوانید از کلیدهای امنیتی استفاده کنید. +u2f_register_key=اضافه کردن کد امنیتی +u2f_nickname=نام مستعار +u2f_press_button=این دکمه را بفشارید تا کلید امنیتی را برای خود ثبت کنید. +u2f_delete_key=حذف کلید امنیتی +u2f_delete_key_desc=اگر کلید امنیت خود را حذف کنید دیگر نمی‌توانید با آن وارد شوید. آیا هنوز مایل به حذف آن هستید؟ +manage_account_links=مدیریت حساب های مرتبط شده +manage_account_links_desc=این حساب های خارجی به حساب Gitea ارتباط دارد. +account_links_not_available=اکنون دیگر هیچ پیوند حساب‌های کاربری خارجی به حساب کاربری شما وجود ندارد. +remove_account_link=حذف حساب پیوند خرده +remove_account_link_desc=با حذف پیوند خارجی حساب کاربری دسترسی شما به حساب کابریتان توسط آن از بین میرود. آیا ادامه می‌دهید؟ +remove_account_link_success=پیوند حساب کاربری از حذف شد. +orgs_none=شما عضو هیچ سازمانی نیستید. +repos_none=شما مالک هیچ مخزنی نیستید + +delete_account=حذف حساب کاربری +delete_prompt=این عملیات به‎طور کامل حساب کاربری شما و محتوای آن را حذف می‎کند و غیرقابل‎بازگشت می‎باشد. +confirm_delete_account=تاییدیه حذف +delete_account_title=حذف حساب کاربری +delete_account_desc=آیا شما مطمئن هستید که می‎خواهیدحساب کاربری خود را حذف کنید؟ + +email_notifications.enable=فعال‌سازی اعلان‌های ایمیل +email_notifications.onmention=فقط یادآوری توسط ایمیل +email_notifications.disable=غیرفعال‌ کردن اعلان‌های ایمیل +email_notifications.submit=ثبت اولویت ایمیل [repo] +owner=مالک +repo_name=نام مخزن +repo_name_helper=نام خوب مخزن معمولا از کلمات کلیدی کوتاه و به یاد ماندنی و منحصر به فرد تشکیل شده است. +visibility=پدیداری +visibility_description=فقط مالک یا عضو سازمان اگر دسترسی داشته باشند. میتوانند این را مشاهده کنند. +visibility_helper=این مخزن را خصوصی کن +visibility_helper_forced=مدیر سایت شما ایجاد مخازن جدید را به صورت خصوصی اجباری کرده است. +visibility_fork_helper=(تغییر این مقدار تمام انشعاب‎ها را تحت تاثیر می گذارد) +clone_helper=کمک برای نسخه‎برداری کمک. +fork_repo=انشعاب از مخزن +fork_from=انشعاب از +fork_visibility_helper=نمایان بودن مخزن منشعب شده غیر قابل تغییر است. +repo_desc=توضیحات +repo_lang=زبان +repo_gitignore_helper=یک قالب برای .gitignore انتخاب کنید. +issue_labels=برچسب‌های مسئله +issue_labels_helper=یک مسئله را برای برچسب زدن انتخاب کنید. +license=مجوز +license_helper=انتخاب فایل مجوز. +readme=README +readme_helper=یک قالب README انتخاب کنید. +auto_init=راه اندازی مخزن (.gitignore, License و README را اضافه میکند) +create_repo=ایجاد مخزن +default_branch=شاخه پیش‌فرض +mirror_prune=هرس کردن +mirror_prune_desc=حذف منابع پیگیری‌راه‌دور منسوخ +mirror_interval=بازه زمانی قرینه سازی (mirror) با 'h', 'm', 's'. برای غیر فعال کردن همگام سازی خودکار 0 بگذارید. +mirror_interval_invalid=بازه زمانی سازی قرینه نیست. +mirror_address=همسان‌سازی از نشانی +mirror_address_desc=هر گواهینامه لازم را در بخش Clone Authority (مجوز همسان‌سازی) قرار دهید. +mirror_address_url_invalid=Url ارائه شده نامعتبر است. شما باید از تمام اجزای Url صحیح گزیر بزنید. +mirror_address_protocol_invalid=نشانی ارائه شده غیرمعتبر است. فقط استفاده از http(s):// یا git:// می‌تواند قرینه شوند. +mirror_last_synced=آخرین همگام سازی +watchers=دنبال‌کنندگان +stargazers=ستاره دهندگان +forks=انشعاب‌ها +pick_reaction=واکنش خود را انتخاب کنید +reactions_more=و %d بیشتر +archive.title=این مخزن بایگانی شد. شما می توانید فایل های آن را مشاهده کنید یا از رونوشت تهیه کنید. اما دیگر نمی‌توانید آن را به‌روزسانی کنید و یا برای آن مسئله یا تقاضای واکشی ایجاد کنید. +archive.issue.nocomment=این مخزن بایگانی شده است. شما نمی‎توانید در مورد مسائل اظهار نظر کنید. +archive.pull.nocomment=این مخزن بایگانی شده. شما نمی توانید دیدگاهی بر روی این تقاضای واکشی ارسال کنید. +form.reach_limit_of_creation=شما قبلاً به حد مجاز مخازن %d رسیده‎اید. +form.name_reserved=یک مخزن با نام '%s' از قبل وجود دارد. +form.name_pattern_not_allowed=الگوی %s در نام مخزن مجاز نیست. +need_auth=مجوز همسان‌سازی +migrate_type=نوع انتقال +migrate_type_helper=این مخزن به عنوان قرینه خواهد بود +migrate_items=مولفه های مهاجرت +migrate_items_wiki=دانشنامه +migrate_items_milestones=نقاط عطف +migrate_items_labels=برچسب‌ها +migrate_items_issues=مسائل +migrate_items_pullrequests=تقاضاهای واکشی +migrate_items_releases=انتشارها +migrate_repo=انتقال مخزن +migrate.clone_address=انتقال / همسان‌سازی از نشانی +migrate.clone_address_desc=HTTP(S) or Git 'همسان‌سازی' نشانی‌های موجود در این مخزن +migrate.clone_local_path=یا مسیر سرویس دهنده محلی +migrate.permission_denied=شما مجاز به واردات مخازن محلی نیستید. +migrate.invalid_local_path=مسیر محلی نامعتبر است. وجود ندارد یا یک پوشه نیست. +migrate.failed=انتقال انجام نشد: %v +migrate.lfs_mirror_unsupported=قرینه سازی LFS اشیا پشتیبانی نمی‌شود به جای آن از 'git lfs fetch --all' و 'git lfs push --all' استفاده کنید. +migrate.migrate_items_options=زمانی که از github مهاجرت می‌کنید. ورودی نام‌کاربری و گزینه‌های مهاجرت نمایش داده می‌شوند. +migrated_from=مهاجرت از %[2]s +migrated_from_fake=مهاجرت از %[1]s +mirror_from=قرینه از +forked_from=انشعاب شده از +fork_from_self=شما نمی‌توانید از مخزن خود انشعاب بگیرید. +fork_guest_user=برای انشعاب از این مخزن باید وارد شوید. +copy_link=رونوشت +copy_link_success=از پیوند رونوشت گرفته شد +copy_link_error=⌘-C یا کنترل-C را برای رونوشت فشار دهید +copied=رونوشت موفقیت‌آمیز بود +unwatch=زیر نظر نگرفتن +watch=زیرنظر گرفتن +unstar=برداشتن ستاره +star=ستاره دار کن +fork=انشعاب +download_archive=دانلود مخزن +no_desc=بدون توضیح +quick_guide=راهنمای سریع +clone_this_repo=همسان‌سازی این مخزن +create_new_repo_command=ایجاد یک مخزن جدید در خط فرمان +push_exist_repo=درج تغییرات مخزن موجود از خط فرمان +empty_message=این مخزن هنوز هیچ محتوایی ندارد. +code=کد +code.desc=دسترسی به کدهای منبع، فایل‎ها، کامیت های و شاخه ها. +branch=شاخه +tree=درخت +filter_branch_and_tag=صافی شاخه یا برچسب +branches=شاخه‎ها +tags=برچسب‎ها +issues=مسائل +pulls=تقاضاهای واکشی +labels=برچسب‌ها +milestones=نقاط عطف +commits=کامیت‌ها +commit=کامیت +releases=انتشارها +file_raw=خام +file_history=تاریخچه +file_view_raw=مشاهده خام +file_permalink=پیوند همیشگی +file_too_large=حجم این پرونده بیشتر از آن است که قابل نمایش باشد. +video_not_supported_in_browser=مرورگر شما از تگ video که در HTML5 تعریف شده است، پشتیبانی نمی کند. +audio_not_supported_in_browser=مرورگر شما از تگ audio که در HTML5 تعریف شده است، پشتیبانی نمی کند. +stored_lfs=ذخیره شده با GIT LFS +commit_graph=نمودار کامیت +blame=سرزنش +normal_view=نمایش عادی +editor.new_file=پرونده جدید +editor.upload_file=بارگذاری پرونده +editor.edit_file=ویرایش پرونده +editor.preview_changes=پیش نمایش تغییرات +editor.cannot_edit_lfs_files=پرونده های LFS در صحفه وب قابل تغییر نیست. +editor.cannot_edit_non_text_files=پرونده‎های دودویی در صفحه وب قابل تغییر نیست. +editor.edit_this_file=ویرایش پرونده +editor.must_be_on_a_branch=شما باید در یک شاخه باشید تا بتوانید در این فایل تغییری ایجاد کنید و یا پیشنهاد تغییر بدهید. +editor.fork_before_edit=شما باید از این مخزن یک انشعاب ایجاد کنید تا در این فایل تغییری ایجاد کنید و یا پیشنهاد تغییر بدهید. +editor.delete_this_file=حذف پرونده‌ +editor.must_have_write_access=شما برای ویرایش و یا ایجاد تغییرات در این پرونده نیاز به دسترسی نوشتن دارید. +editor.file_delete_success=پرونده '%s' حذف شد. +editor.name_your_file=نام پرونده شما… +editor.filename_help=برای اضافه کردن پوشه از slash ('/') استفاده کنید. برای حذف آن ها از backspace در ابتدای فیلد ورودی است استفاده کنید. +editor.or=یا +editor.cancel_lower=انصراف +editor.commit_changes=تغییرات کامیت +editor.add_tmpl=افزودن '' +editor.add=افزودن '%s' +editor.update=به روزرسانی %s +editor.delete=حذف '%s' +editor.commit_message_desc=توضیحی تخصصی به دلخواه اضافه نمایید… +editor.commit_directly_to_this_branch=ثبت کامیت به صورت مستقیم در انشعاب %s. +editor.create_new_branch=یک شاخه جدید برای این commit ایجاد کنید و تقاضای واکشی را شروع کنید. +editor.propose_file_change=پیشنهاد تغییر پرونده +editor.new_branch_name_desc=نام شاخه ی جدید… +editor.cancel=انصراف +editor.filename_cannot_be_empty=نام نمی تواند خالی باشد. +editor.filename_is_invalid=نام پرونده نامتعبر: '%s'. +editor.branch_does_not_exist=شاخه '%s' از قبل در این مخزن وجود نداشته است. +editor.branch_already_exists=انشعاب '%s' از قبل در این مخزن وجود دارد. +editor.directory_is_a_file=این نام پوشه '%s' پیش از این به عنوان پرونده در مخزن استفاده شده است. +editor.file_is_a_symlink=این '%s' پیوند نمادین در ویرایشگر وب قابل ویرایش نیست +editor.filename_is_a_directory=این نام پرونده '%s' پیش از این به عنوان پوشه در مخزن استفاده شده است. +editor.file_editing_no_longer_exists=فایل آماده ویرایش شده است '%s'، مدتی در مخزن از دسترس خارج می‎شود. +editor.file_deleting_no_longer_exists=فایل آماده حذف می‌شود '%s'، مدتی بعد در مخزن از دسترس خارج می‎شود. +editor.file_changed_while_editing=محتوای پرونده تغییر میکند از زمانی که شما شروع به ویرایش می‌کنید.اینجا کلیک کنید تا ببنید آن را یا یا کامیت تغییرات را دوباره اعمال کنید تا روی آن بازنویسی شود. +editor.file_already_exists=فایلی با نام %s از قبل در مخزن موجود است. +editor.no_changes_to_show=تغییری برای نمایش وجود ندارد. +editor.fail_to_update_file=خطا در ساخت/به‌روزرسانی پرونده %s. خطای رخ داده: %v +editor.add_subdir=افزودن پوشه… +editor.unable_to_upload_files=عدم موفقیت در آپلود پرونده به '%s' با خطا: %v +editor.upload_files_to_dir=بارگزاری پرونده به '%s' +editor.cannot_commit_to_protected_branch=شما نمی‌توانید برای شاخه '%s' محافظت شده کامیت ارسال کنید. +commits.desc=تاریخچه تغییرات کد منبع را مرور کنید. +commits.commits=کامیت‌ها +commits.no_commits=هیچ کامیت مشترکی وجود ندارد. '%s' و '%s' دارای تاریجچه متفاوت هستند. +commits.search=جست‌وجو کامیت‌ها… +commits.search.tooltip=شما میتوانید از کلمات کلیدی پیشوند "author:", "committer:", "after:", or "before:" و ... استفاده کنید به عنوان مثال: "revert author:Ali before:1397-04-01.. +commits.find=جستجو +commits.search_all=همه شاخه ها +commits.author=مولف +commits.message=پیام +commits.date=تاریخ +commits.older=قدیمی تر +commits.newer=جدیدتر +commits.signed_by=امضا شده توسط +commits.gpg_key_id=شناسه کلید GPG +ext_issues=مسائل اضافی +ext_issues.desc=پیوند به ردیاب خارجی برای موضوع. +issues.desc=سازمان دهی گزارش باگ ها و وظایف و.... +issues.new=مسئله‌ی جدید +issues.new.title_empty=عنوان نمی تواند خالی باشد +issues.new.labels=برچسب‌ها +issues.new.no_label=بدون برچسب +issues.new.clear_labels=پاک‌کردن برچسب‌ها +issues.new.milestone=نقطه عطف +issues.new.no_milestone=بدون نقطه عطف +issues.new.clear_milestone=پاک‌کردن نقطه عطف +issues.new.open_milestone=نقاط عطف باز +issues.new.closed_milestone=نقاط عطف بسته +issues.new.assignees=تخصیص شده +issues.new.clear_assignees=پاک کردن تخصیص +issues.new.no_assignees=بدون تخصیص +issues.no_ref=بدون شاخه/برچسب مشخص +issues.create=ایجاد مسئله +issues.new_label=برچسب جدید +issues.new_label_placeholder=نام برچسب +issues.new_label_desc_placeholder=شرح +issues.create_label=ایجاد برچسب +issues.label_templates.title=بارگیری مجموعه ای از برچسب های از پیش تعریف شده +issues.label_templates.info=هنوز هیچ برچسبی وجود ندارد. با "برچسب جدید" یک برچسب ایجاد کنید یا از یکی مجموعه برچسب از پیش تعریف شده استفاده کنید: +issues.label_templates.helper=یک مجموعه برچسب انتخاب نمایید +issues.label_templates.use=استفاده از مجموعه برچسب ها +issues.label_templates.fail_to_load_file=بارگیری الگوی برچسب ها ناموفق بود '%s': '%v' +issues.add_label_at=افزودن این
    %s
    برچسب %s +issues.remove_label_at=حذف این
    %s
    برچسب %s +issues.add_milestone_at=` %s را به نقطه عطف %s اضافه کرد ` +issues.change_milestone_at=`عنوان نقطه عطف از %s به %s %s تغییر کرد` +issues.remove_milestone_at=` %s را از نقطه عطف %s حذف شد` +issues.deleted_milestone=`(حذف شده)` +issues.self_assign_at=`این %s را خود اختصاص دهید +issues.add_assignee_at=`اختصاص داده شده توسط %s %s` +issues.remove_assignee_at=`جدا شده توسط %s %s` +issues.remove_self_assignment=`اینان جدا شدند: %s` +issues.change_title_at=تغییر عنوان از %s به %s%s +issues.delete_branch_at=`حذف شاخه %s %s` +issues.open_tab=%d باز +issues.close_tab=%d بسته +issues.filter_label=برچسب +issues.filter_label_no_select=تمامی برچسب‎ها +issues.filter_milestone=نقطه عطف +issues.filter_milestone_no_select=تمام نقاط عطف +issues.filter_assignee=مسئول رسیدگی +issues.filter_assginee_no_select=تمامی مسئولان رسیدگی +issues.filter_type=نوع +issues.filter_type.all_issues=همه مسائل +issues.filter_type.assigned_to_you=به شما محول شده +issues.filter_type.created_by_you=ایجاد شده توسط شما +issues.filter_type.mentioning_you=اشاره به شما +issues.filter_sort=مرتب‌سازی +issues.filter_sort.latest=جدیدترین +issues.filter_sort.oldest=قدیمی‌ترین +issues.filter_sort.recentupdate=اخیراً به روز شده +issues.filter_sort.leastupdate=به تازگی به‎روز شده +issues.filter_sort.mostcomment=بیشترین دیدگاه‌ها +issues.filter_sort.leastcomment=کمترین دیدگاه‌ها +issues.filter_sort.nearduedate=نزدیکترین موعد مقرر +issues.filter_sort.farduedate=دورترین موعد مقرر +issues.filter_sort.moststars=بیشترین ستاره +issues.filter_sort.feweststars=کمترین ستاره +issues.filter_sort.mostforks=بیشترین اشنعاب +issues.filter_sort.fewestforks=کمترین اشنعاب +issues.action_open=باز‌کردن +issues.action_close=بستن +issues.action_label=برچسب +issues.action_milestone=نقطه عطف +issues.action_milestone_no_select=بدون نقطه عطف +issues.action_assignee=مسئول رسیدگی +issues.action_assignee_no_select=بدون مسئول رسیدگی +issues.opened_by=%[1]s باز شده توسط %[3]s +pulls.merged_by=%[1]s ادغام شده توسط %[3]s +pulls.merged_by_fake=%[1]s ادغام شده توسط %[2]s +issues.closed_by=%[1]s بسته شده توسط %[3]s +issues.opened_by_fake=%[1]s باز شده توسط %[2]s +issues.closed_by_fake=%[1]s بسته شده توسط %[2]s +issues.previous=قبلی +issues.next=بعدی +issues.open_title=باز +issues.closed_title=بسته شده +issues.num_comments=%d دیدگاه +issues.commented_at=`دیدگاه ارسال شده %s` +issues.delete_comment_confirm=آیا مطمئن هستید که می خواهید این دیدگاه را حذف کنید؟ +issues.no_content=هنوز محتوایی ایجاد نشده. +issues.close_issue=ببند +issues.close_comment_issue=ثبت دیدگاه و بستن +issues.reopen_issue=بازگشایی +issues.reopen_comment_issue=ثبت دیدگاه و بازگشایی +issues.create_comment=دیدگاه +issues.closed_at=`%[2]s بسته شد` +issues.reopened_at=`%[2]s بازگشایی شد` +issues.commit_ref_at=`ارجاع این مسئله به کامیت %[2]s` +issues.ref_issue_at=‍`منابع این موضوع %[1]s` +issues.ref_pull_at=‍`منابع این تقاضای واکشی %[1]s` +issues.ref_issue_ext_at=‍`منابع این موضوع از %[1]s %[2]s` +issues.ref_pull_ext_at=‍`منابع این درخواست واکشی از %[1]s %[2]s` +issues.poster=نویسنده +issues.collaborator=همكار +issues.owner=مالک +issues.sign_in_require_desc=برای پیوستن به گفتگو، وارد شودید. +issues.edit=ویرایش +issues.cancel=انصراف +issues.save=ذخیره +issues.label_title=نام برچسب +issues.label_description=توضیحات برچسب +issues.label_color=رنگ برچسب +issues.label_count=%d برچسب‌ها +issues.label_open_issues=%d مسئله حل نشده +issues.label_edit=ویرایش +issues.label_delete=حذف +issues.label_modify=ویرایش برچسب +issues.label_deletion=حذف برچسب +issues.label_deletion_desc=برچسب‎ها از تمام مسائل حذف میشوند. مطمئن؟ +issues.label_deletion_success=برچسب حذف شد. +issues.label.filter_sort.alphabetically=الفبایی +issues.label.filter_sort.reverse_alphabetically=برعکس ترتیب الفبا +issues.label.filter_sort.by_size=اندازه +issues.label.filter_sort.reverse_by_size=معکوس اندازه +issues.num_participants=%d مشارکت کننده +issues.attachment.open_tab=برای مشاهده "%s" در زبانه جدید، کلیک کنید +issues.attachment.download=`برای دریافت "%s" کلیک کنید` +issues.subscribe=مشترک شدن +issues.unsubscribe=لغو اشتراک +issues.lock=قفل کردن مکالمه +issues.unlock=بازکردن مکالمه +issues.lock.unknown_reason=نمیتوانید این موضوع را بدون دلیل ببندید. +issues.lock_duplicate=یک مسئله دومرتبه نمی‎تواند بسته شود. +issues.unlock_error=این مسئله نمی‎تواند باز شود لذا قفل نشده بود. +issues.lock_with_reason=قفل شده با عنوان %s و مکالمه همکاران %s محدود شده است +issues.lock_no_reason=قفل شده و مکالمه برای همکاران %s محدود شد +issues.unlock_comment=باز کردن این مکالمه %s +issues.lock_confirm=قفل کردن +issues.unlock_confirm=رفع انسداد +issues.lock.notice_1=- دیگر کاربران نمی‎توانند یک مولفه جدید به مسئله اضافه کنند. +issues.lock.notice_2=- شما و سایر همکاران با دسترسی به این مخزن هنوز می‌توانید اظهار نظر کنید و سایرین آن را مشاهده می‌کنند. +issues.lock.notice_3=- شما همیشه می‎توانید مجدداً در آینده این مسئله را باز کنید. +issues.unlock.notice_1=- همه می توانند بار دیگر درباره این موضوع اظهارنظر کنند. +issues.unlock.notice_2=- شما همیشه می‎توانید مجدداً در آینده این مسئله را قفل کنید. +issues.lock.reason=دلیل انسداد +issues.lock.title=انسداد مکالمه در این مسئله. +issues.unlock.title=رفع انسداد مکالمه در این مسئله. +issues.comment_on_locked=شما نمی‌توانید در مسئله قفل شده اظهار نظر کنید. +issues.tracker=پیگیری زمان +issues.start_tracking_short=آغاز +issues.start_tracking=شروع به پیگیری زمان +issues.start_tracking_history=`شروع به کار %s` +issues.tracker_auto_close=زمان‌سنج به صورت خودکار متوقف میشود زمانی که مسئله بسته شود +issues.tracking_already_started=`شما میتوانید پیگیری زمان را روی این مسئله آغاز کنید!` +issues.stop_tracking=توقف +issues.stop_tracking_history=`توقف کار در %s` +issues.add_time=زمان را به صورت دستی وارد کنید +issues.add_time_short=افزودن زمان +issues.add_time_cancel=انصراف +issues.add_time_history=`زمان صرف شده اضافه شد %s` +issues.add_time_hours=ساعت +issues.add_time_minutes=دقیقه +issues.add_time_sum_to_small=هیچ زمانی وارد نشده. +issues.cancel_tracking=انصراف +issues.cancel_tracking_history=`انصراف از پیگیری زمان %s` +issues.time_spent_total=کل زمان صرف شده +issues.time_spent_from_all_authors=`زمان صرف شده: %s` +issues.due_date=موعد مقرر +issues.invalid_due_date_format=موعد مقرر، باید به سبک 'yyyy-mm-dd' باشد. +issues.error_modifying_due_date=تغییر موعد مقرر با شکست مواجه شد. +issues.error_removing_due_date=حذف موعد مقرر با شکست مواجه شد. +issues.due_date_form=yyyy-mm-dd +issues.due_date_form_add=افزودن موعد مقرر +issues.due_date_form_edit=ویرایش +issues.due_date_form_remove=حذف/ساقط کردن +issues.due_date_not_writer=شما نیازمند دسترسی نوشتن به این مخزن را برای تغییر موعد مقرر این مسئله را دارید. +issues.due_date_not_set=هیچ موعد مقرری ثبت نشده. +issues.due_date_added=موعد مقرر اضافه شد %s %s +issues.due_date_modified=موعد مقرر از %s به %s %s تغییر کرد. +issues.due_date_remove=موعد مقرر %s %s حذف شد +issues.due_date_overdue=تاریخ گذشته +issues.due_date_invalid=موعد مقرر نامعتبر است یا خارج از محدوده. لطفاً از قالب 'yyy-mm-dd' استفاده کنید. +issues.dependency.title=وابستگی ها +issues.dependency.issue_no_dependencies=این مسئله در حال حاضر هیچ وابستگی ندارد. +issues.dependency.pr_no_dependencies=این تقاضای واکشی در حال حاضر هیچ وابستگی ندارد. +issues.dependency.add=اضافه کردن وابستگی… +issues.dependency.cancel=انصراف +issues.dependency.remove=حذف/ساقط کردن +issues.dependency.remove_info=حذف این وابستگی +issues.dependency.added_dependency=`%[2]s افزودن یک وابستگی جدید %[3]s` +issues.dependency.removed_dependency=`%[2]s حذف یک وابستگی %[3]s` +issues.dependency.issue_closing_blockedby=بستن این تقاضای واکشی وسط موضوعات زیر رد/ مسدود شده است +issues.dependency.pr_closing_blockedby=بستن این موضوع وسط موضوعات زیر رد/ مسدود شده است +issues.dependency.issue_close_blocks=این مسئله با توجه به موضوعات مطرح شده مسدود شده است +issues.dependency.pr_close_blocks=این تقاضای واکشی با توجه به موضوعات مطرح شده مسدود شده است +issues.dependency.issue_close_blocked=شما نیاز به بستن تمامی مسائل مسدود شده مسئله قبل بستن آن هستید. +issues.dependency.pr_close_blocked=شما می بایستی تمامی مسائل این تقاضای واکشی را ببنید تا بتوانید آن را ادغام کنید. +issues.dependency.blocks_short=بلوک‌ها +issues.dependency.blocked_by_short=وابسته به +issues.dependency.remove_header=حذف وابستگی +issues.dependency.issue_remove_text=این عمل وابستگی را از مسئله حذف میکند. آیا ادامه می‌دهید؟ +issues.dependency.pr_remove_text=این عمل وابستگی را از تقاضای واکشی حذف میکند. آیا ادامه می‌دهید؟ +issues.dependency.setting=فعال کردن وابستگی برای مسائل یا تقاضاهای واکشی +issues.dependency.add_error_same_issue=شما نمی‌توانید این مسئله را به خود وابسته کنید. +issues.dependency.add_error_dep_issue_not_exist=مسئله وابسته وجود ندارد. +issues.dependency.add_error_dep_not_exist=وابستگی وجود ندارد. +issues.dependency.add_error_dep_exists=این وابستگی پیش از این وجود داشته است. +issues.dependency.add_error_cannot_create_circular=شما نمی‌توانید دو مسئله را که به دیگر مسائل وابسته می‌شوند را وابسته کنید. +issues.dependency.add_error_dep_not_same_repo=هر دو موضوع باید از یک مخزن باشند. +issues.review.self.approval=شما نمی‌توانید تقاضای واکشی خود را تایید کنید. +issues.review.self.rejection=شما نمی‌توانید تقاضا تغییرات تقاضای واکشی خود را تغییر دهید. +issues.review.approve=این تغییرات را تایید شدند %s +issues.review.comment=بازبینی شدند %s +issues.review.content.empty=شما می‌بایستی در مورد تقاضای تغییرات اظهار نظر کنید. +issues.review.reject=تقاضا شد برای تغییر %s +issues.review.pending=در انتظار +issues.review.review=بازبینی +issues.review.reviewers=بازبینی‌کنندگان +issues.review.show_outdated=نمایش از رده خارج‌ها +issues.review.hide_outdated=مخفی کرده از رده خارج ها +pulls.desc=نمایش تقاضای واکشی ها و بازبینی های کد. +pulls.new=ایجاد تقاضای واکشی +pulls.compare_changes=تقاضای واکشی جدید +pulls.compare_changes_desc=یک شاخه را برای ادغام با شاخه مورد نظر انتخاب تا از آن واکشی کنید. +pulls.compare_base=ادغام با +pulls.compare_compare=واکشی از +pulls.filter_branch=صافی شاخه +pulls.no_results=هیچ نتیجه‌ای یافت نشد. +pulls.nothing_to_compare=این شاخه‎ها یکی هستند. نیازی به تقاضای واکشی نیست. +pulls.has_pull_request=`یک تقاضای واکشی بین این شاخه های از پیش وجود دارد: %[2]s#%[3]d` +pulls.create=ایجاد تقاضای واکشی +pulls.title_desc=قصد ادغام %[1]d تغییر را از %[2]s به %[3]s دارد +pulls.merged_title_desc=%[1]d کامیت ادغام شده از %[2]s به %[3]s %[4]s +pulls.tab_conversation=گفتگو +pulls.tab_commits=کامیت‌ها +pulls.tab_files=پرونده تغییر کرده +pulls.reopen_to_merge=برای انجام عملیات ادغام، لطفا این تقاضای واکشی را بازگشایی نمایید. +pulls.cant_reopen_deleted_branch=این تقاضای واکشی غیر قابل بازگشایی است چون شاخه حذف شده است. +pulls.merged=ادغام شده +pulls.merged_as=تقاضای واکشی %[2]s ادغام شده است. +pulls.has_merged=این تقاضای واکشی ادغام شد. +pulls.title_wip_desc=` شروع شذع با عنوان %s برای جلو گیری کردن از تقاضای واکشی که موقع ادغام دچار تصادم میشود.` +pulls.cannot_merge_work_in_progress=این تقاضای واکشی در حال پردازش است. پیشوند %s پس از آنکه آماده شد از عنوانش حذف میشود +pulls.data_broken=این تقاضای واکشی به دلیل از دست رفتن اطلاعات انشعاب با شکست مواجه شد. +pulls.files_conflicted=این تقاضای واکشی دارای تغییراتی است که با شاخه هدف تداخل دارد. +pulls.is_checking=در حال پردازش تداخل در ادغام می‌باشد. لطفاً لحظاتی بعد امتحان کنید. +pulls.required_status_check_failed=برخی بررسی های ضروری موفقیت آمیز نبود. +pulls.blocked_by_approvals=این تقاضای واکشی هنوز به اندازه کافی مورد مورد تایید نیست. %d از %d مورد آن قابل تایید می‌باشد. +pulls.can_auto_merge_desc=این تقاضا واکشی می تواند به صورت خودکار ادغام شود. +pulls.cannot_auto_merge_desc=این تقاضای واکشی به علت تداخل نمی تواند به صورت خودکار ادغام شود. +pulls.cannot_auto_merge_helper=به صورتی دستی ادغام کنید تا مشکل تداخل را حل نمایید. +pulls.no_merge_desc=این تقاضای واکشی قابل ادغام نیست لذا تمامی گزینه های ادغام مخزن غیر فعال هستند. +pulls.no_merge_helper=گزینه های ادغام را در تنظیمات مخزن فعال کنید یا از تقاضای واکشی به صورت دستی ادغام نمایید. +pulls.no_merge_wip=این تقاضای واکشی قابل ادغام نیست لذا اکنون به این مخزن درحال پردازش علامت گذاری شده است. +pulls.no_merge_status_check=این تقاضای واکشی نمی‌تواند ادغام شود لذا تمامی حالت های ضروری با موفقیت بررسی نشدند. +pulls.merge_pull_request=ادغام تقاضای واکشی +pulls.rebase_merge_pull_request=بازگردانی و ادغام +pulls.rebase_merge_commit_pull_request=بازگردانی و ادغام (--no-ff) +pulls.squash_merge_pull_request=له کردن و ادغام +pulls.invalid_merge_option=شما نمی‌توانید از این گزینه برای تقاضای واکشی استفاده کنید. +pulls.open_unmerged_pull_exists=`شما نمی‌توانید یک عملیات را انجام داده یا بازگشایی نمایید لذا (#%d) مورد تقاضای واکشی با ویژگی منحصر به فرد هنوز رسیدگی نشده (معلق) است. ` +pulls.status_checking=برخی از بررسی‎ها در حال تعلیق هستند +pulls.status_checks_success=تمامی بررسی‎ها موفق بودند +pulls.status_checks_error=برخی بررسی‎ها با مشکل مواجه شد +milestones.new=نقطه عطف جدید +milestones.open_tab=%d باز شد +milestones.close_tab=%d بسته +milestones.closed=%s بسته شد +milestones.no_due_date=بدون موعد مقرر +milestones.open=باز +milestones.close=بستن +milestones.new_subheader=نقطه عطف مسائل و فرآیند ردیابی را سازماندهی می‌کند. +milestones.completeness=%d%% کامل شده است +milestones.create=ایجاد نقطه عطف +milestones.title=عنوان +milestones.desc=شرح +milestones.due_date=موعد مقرر (اختیاری) +milestones.clear=پاک کردن +milestones.invalid_due_date_format=موعد مقرر، باید به سبک 'yyyy-mm-dd' باشد. +milestones.create_success=نقطه عطف '%s' ساخته شد. +milestones.edit=ویرایش نقطه عطف +milestones.edit_subheader=نقطه عطف مسائل و فرآیند ردیابی را سازماندهی می‌کند. +milestones.cancel=انصراف +milestones.modify=به روزرسانی نقطه عطف +milestones.edit_success=نقطه عطف '%s' به روز شد. +milestones.deletion=حذف نقطه عطف +milestones.deletion_desc=نقاط عطف از تمام مسائل مرتبط حذف میشوند. آیا ادامه میدهید؟ +milestones.deletion_success=نقطه عطف حذف شد. +milestones.filter_sort.closest_due_date=نزدیکترین موعد مقرر +milestones.filter_sort.furthest_due_date=دورترین موعد مقرر +milestones.filter_sort.least_complete=حداقل کامل شده +milestones.filter_sort.most_complete=بیشترین کامل شده +milestones.filter_sort.most_issues=بیشترین مسائل +milestones.filter_sort.least_issues=کمترین مسائل +ext_wiki=دانشنامه خارجی +ext_wiki.desc=پیوند به یک دانشنامه خارجی. +wiki=دانشنامه +wiki.welcome=به دانشنامه خوش آمدید. +wiki.welcome_desc=به دانشنامه خوش آمدید! +wiki.desc=شروع به نوشتن مستندات کنید و آن‌را با همکاران خود به اشتراک بگذارید. +wiki.create_first_page=ایجاد اولین صفحه +wiki.page=صفحه +wiki.filter_page=صافی صفحه +wiki.new_page=صفحه +wiki.default_commit_message=نوشتن متنی پیرامون به‌روزرسانی این صفحه (اختیاری). +wiki.save_page=نوشتن متنی پیرامون به‌روزرسانی این صفحه (اختیاری). +wiki.last_commit_info=%s این صفحه %s را ویرایش کرده است +wiki.edit_page_button=ویرایش +wiki.new_page_button=صفحه جدید +wiki.file_revision=نسخه صفحه +wiki.wiki_page_revisions=دانشنامه نسخه صفحه +wiki.back_to_wiki=برگشت به دانشنامه +wiki.delete_page_button=حذف صفحه +wiki.delete_page_notice_1=حذف صفحه‌ی دانشنامه غیر قابل بازگشت است. همچنان مایل به حذف می‌باشید؟ +wiki.page_already_exists=صفحه‌ی دانشنامه با همین نام موجود است. +wiki.reserved_page=دانشنامه ای با این نام پیش از این "%s" استفاده شده است. +wiki.pages=صفحات +wiki.last_updated=واپسین روزرسانی در %s +activity=فعالیت +activity.period.filter_label=دوره: +activity.period.daily=۱ روز +activity.period.halfweekly=3 روز +activity.period.weekly=1 هفته +activity.period.monthly=1 ماه +activity.overview=مرور +activity.active_prs_count_1=%d تقاضای واکشی فعال +activity.active_prs_count_n=%d تقاضاهای واکشی فعال +activity.merged_prs_count_1=تقاضای واکشی ادغام شد +activity.merged_prs_count_n=تقاضاهای واکشی ادغام شد +activity.opened_prs_count_1=تقاضای واکشی پیشنهاد شده +activity.opened_prs_count_n=تقاضای واکشی پیشنهاد شده +activity.title.user_1=%d کاربر +activity.title.user_n=%d کاربر +activity.title.prs_1=%d تقاضای واکشی +activity.title.prs_n=%d تقاضاهای واکشی +activity.title.prs_merged_by=%s ادغام شده توسط %s +activity.title.prs_opened_by=%s پیشنهاد شده توسط %s +activity.merged_prs_label=ادغام شده +activity.opened_prs_label=پیشنهاد شده +activity.active_issues_count_1=%d مسئله فعال +activity.active_issues_count_n=%d مسئله فعال +activity.closed_issues_count_1=مسئله حل شده +activity.closed_issues_count_n=مسائل حل شده +activity.title.issues_1=%d مسئله +activity.title.issues_n=%d مسئله +activity.title.issues_closed_by=%s بسته شده توسط %s +activity.title.issues_created_by=%s ایجاد شده توسط %s +activity.closed_issue_label=بسته شده +activity.new_issues_count_1=مسئله‌ی جدید +activity.new_issues_count_n=مسئله‌ی جدید +activity.new_issue_label=باز شده +activity.title.unresolved_conv_1=%d مکالمه حل نشده +activity.title.unresolved_conv_n=%d مکالمه حل نشده +activity.unresolved_conv_desc=این موارد اخیراً تغییر کرده و تقاضاهای واکشی هنوز حل نشده است. +activity.unresolved_conv_label=باز +activity.title.releases_1=%d انتشار +activity.title.releases_n=%d انتشار +activity.title.releases_published_by=%s منشتر شده توسط %s +activity.published_release_label=منتشر شده +activity.no_git_activity=در این دوره فعالیت کامیتی ارسال نشده است. +activity.git_stats_exclude_merges=به استثنای ادغام‎ها ، +activity.git_stats_author_1=%d بانی +activity.git_stats_author_n=%d بانی +activity.git_stats_pushed_1=درج شده است +activity.git_stats_pushed_n=درج شد +activity.git_stats_commit_1=%d دیدگاه +activity.git_stats_commit_n=%d دیدگاه +activity.git_stats_push_to_branch=به %s و +activity.git_stats_push_to_all_branches=به تمامی شاخه ها. +activity.git_stats_on_default_branch=بر %s. +activity.git_stats_file_1=%d پرونده +activity.git_stats_file_n=%d پرونده +activity.git_stats_files_changed_1=تغییر کرده است +activity.git_stats_files_changed_n=تغییر کرد +activity.git_stats_additions=و اینها وجود داشته است +activity.git_stats_addition_1=علاوه بر این %d +activity.git_stats_addition_n=علاوه بر این %d +activity.git_stats_and_deletions=و +activity.git_stats_deletion_1=%d مذحوف +activity.git_stats_deletion_n=%d مذحوف +search=جستجو +search.search_repo=جستجوی مخزن +search.results=نتیجه جستجو برای "%s" در %s +settings=تنظيمات +settings.desc=تنظیمات جایی است که شما می‌توانید تنظیمات مخزن خود را مدیریت کنید +settings.options=مخزن +settings.collaboration=همكار +settings.collaboration.admin=مدیر +settings.collaboration.write=نوشتن +settings.collaboration.read=خواندن +settings.collaboration.undefined=تعریف نشده +settings.hooks=Webhooks +settings.githooks=Git Hooks +settings.basic_settings=تنظیمات پایه +settings.mirror_settings=تنظیمات قرینه +settings.sync_mirror=همگام سازی کن +settings.mirror_sync_in_progress=همگام سازی قرینه در حالت پردازش است. یک دقیقه دیگر مجددا بررسی کنید. +settings.email_notifications.enable=فعال‌سازی اعلان‌های ایمیل +settings.email_notifications.onmention=تنها یادوآری از طریق ایمیل +settings.email_notifications.disable=غیرفعال‌ کردن اعلان‌های ایمیل +settings.email_notifications.submit=ثبت اولویت ایمیل +settings.site=تارنما +settings.update_settings=به‌ روزرسانی تنظیمات +settings.advanced_settings=تنظیمات پیشرفته +settings.wiki_desc=فعال‌کردن دانشنامه مخزن +settings.use_internal_wiki=استفاده از داشنامه درون برنامه ای +settings.use_external_wiki=استفاده از دانشنامه بیرونی +settings.external_wiki_url=نشانی خارجی دانشنامه +settings.external_wiki_url_error=نشانی دانشنامه خارجی نامتعبر است. +settings.external_wiki_url_desc=کاربران به نشانی دانشنامه خارجی هدایت می‌شوند وقتی روی زبانه دانشنامه کلیک کنند. +settings.issues_desc=فعال کردن ردیاب مسائل مخزن +settings.use_internal_issue_tracker=استفاده از ردیاب ساخته شده مسئله +settings.use_external_issue_tracker=استفاده از سیستم رهگیری مسئله خارجی +settings.external_tracker_url=نشانی ردیاب خارجی مسائل +settings.external_tracker_url_error=نشانی ردیاب خارجی نامتعبر است. +settings.external_tracker_url_desc=بازدیدکننده‎گان به نشانی ردیاب خارجی هدایت می‌شوند وقتی روی زبانه مسئله کلیک کنند. +settings.tracker_url_format=قالب نشانی ردیاب مسائل خارجی +settings.tracker_url_format_error=نشانی قالب ردیاب خارجی نامتعبر است. +settings.tracker_issue_style=قالب شماره ردیاب مسائل خارجی +settings.tracker_issue_style.numeric=عددی +settings.tracker_issue_style.alphanumeric=عددی و الفبایی +settings.tracker_url_format_desc=از محل‌های نگهداری {user}, {repo} و و {index} برای نام‌کاربری، نام مخزن و شاخص مسئله استفاده می‌شود. +settings.enable_timetracker=فعال کردن پیگیری زمان +settings.allow_only_contributors_to_track_time=اجازه پیگیری زمان مشارکت فقط +settings.pulls_desc=فعال کردن تقاضای واکشی مخزن +settings.pulls.ignore_whitespace=نادیده گرفتن لیست سفید برای تداخل ها +settings.pulls.allow_merge_commits=فعال کردن ادغام کامیت ها +settings.pulls.allow_rebase_merge=فعال کردن احیا به ادغام کامیت ها +settings.pulls.allow_rebase_merge_commit=فعال کردن «احیاء» با ادغام آشکار کامیت‌ها (--no-ff) +settings.pulls.allow_squash_commits=فعال کردن خاموش کردن ادغام کامیت ها +settings.admin_settings=تنظیمات مدیران +settings.admin_enable_health_check=فعال کردن بررسی سلامت مخزن (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch=اسنداد یک مسئله با کامیت آن شاخه را با غیر پیش فرض تبدیل می‌کند +settings.danger_zone=منطقه خطرناک +settings.new_owner_has_same_repo=مالک جدید مخزن با همین نام است. لطفاً نام دیگری را انتخاب کنید. +settings.convert=تبدیل به یک مخزن عادی +settings.convert_desc=شما می توانید این مخزن قرینه شده را به یک مخزن معمولی تبدیل نمایید. این عمل بازگشت ناپذیر خواهد بود. +settings.convert_notices_1=این عملیات می تواند این مخزن قرینه شده را به یک مخزن معمولی تبدیل نمایید. این عمل بازگشت ناپذیر خواهد بود. +settings.convert_confirm=تبدیل مخزن +settings.convert_succeed=قرینه به یک مخزن معمولی تبدیل شد. +settings.transfer=انتقال مالکیت +settings.transfer_desc=انتقال مالکیت این مخزن به کاربر بانی یا سازمانی که شما حق مدیریت در آن دارید. +settings.transfer_notices_1=- شما دسترسی خود را نسبت مخزن را از دست میدهید اگر مالکیت آن را به یک کاربری مفرد انتقال دهید. +settings.transfer_notices_2=- شما دسترسی خود را نسبت مخزن را حفظ میکنید. اگر مالکیت آن را به یک سازمانی که در آن مالکیت دارید انتقال دهید. +settings.transfer_form_title=نام مخزن را برای تایید عمل خورد اینجا وارد کنید: +settings.wiki_delete=حذف اطلاعات دانشنامه +settings.wiki_delete_desc=حذف اطلاعات دانشنامه مخزن همیشگی بوده و قابل بازگشت نخواهد بود. +settings.wiki_delete_notices_1=- این به صورت کامل حذف خواهد کرد و دانشنامه این برای محزن %s غیر فعال می‌کند. +settings.confirm_wiki_delete=حذف اطلاعات دانشنامه +settings.wiki_deletion_success=اطلاعات دانشنامه این محزن حذف شد. +settings.delete=حذف این مخزن +settings.delete_desc=حذف مخزن همیشگی بوده و قابل بازگشت نخواهد بود. +settings.delete_notices_1=این عملیات غیرقابل برگشت است. +settings.delete_notices_2=این عملیات برای همیشه مخزن %s از بین می‌برد تیز مشمول کد و مسائل، دیدگاه‌ها، دانشنامه، همکاران و تنظیمات نیز می‌شود. +settings.delete_notices_fork_1=- پس از حذف مخازن منشعب شده به صورت مستقل تبدیل می‌شود. +settings.deletion_success=مخزن مورد نظر حذف شد. +settings.update_settings_success=تنظیمات این مخزن به‌روز شد. +settings.transfer_owner=مالک جدید +settings.make_transfer=انتقال را انجام دهید +settings.transfer_succeed=این مخزن با موفقیت منتقل شد. +settings.confirm_delete=حذف مخزن +settings.add_collaborator=اضافه‌کردن همکار +settings.add_collaborator_success=همکار جدید اضافه شد. +settings.add_collaborator_inactive_user=شما نمی‌توانید کاربر غیر فعال را به عنوان همکار اضافه کنید. +settings.add_collaborator_duplicate=این همکار پیش از این به مخزن اضافه شده بود است. +settings.delete_collaborator=حذف +settings.collaborator_deletion=حذف‌کردن همکار +settings.collaborator_deletion_desc=حذف یک همکار از مخزن دسترسی‌های آنها را را مجدد لغو می‌کند. آیا ادامه می‌دهید؟ +settings.remove_collaborator_success=همكار حذف شد. +settings.search_user_placeholder=جستجوی کاربر… +settings.org_not_allowed_to_be_collaborator=سازمان ها را نمیتوان به عنوان همکار افزود. +settings.add_webhook=اضافه‌کردن Webhook +settings.add_webhook.invalid_channel_name=کانال هوک تحت وب نمی‌تواند خالی باشد و نمی‌توانید تنها حاوی این حرف # باشد. +settings.hooks_desc=هوک تحت وب به صورت خودکار درخواست POST HTTP را به سمت سرور روانه می‌کند زمانی که ماشه رخداد Gitea کشیده شود. برای اطلاعات بیشتر به راهنمای هوک تحت وب مراجعه کنید. +settings.webhook_deletion=حذف Webhook +settings.webhook_deletion_desc=حذف هوک تحت وب موجب حذف تنظیمات آن و تاریخچه تحویل آن می‌شود. همچنان ادامه می‌دهید؟ +settings.webhook_deletion_success=هوک تحت وب حذف شد. +settings.webhook.test_delivery=امتحان‌کردن تحویل +settings.webhook.test_delivery_desc=Webhook را با رویداد جعلی امتحان کنید. +settings.webhook.test_delivery_success=یک رویداد آزمایشی به صف تحویل افزوده شد. ممکن است چند ثانیه ای طول بکشد تا اینکه در لیست تاریخچه تحویل ها قرار گیرد. +settings.webhook.request=درخواست +settings.webhook.response=پاسخ +settings.webhook.headers=سربرگ‎ها +settings.webhook.payload=محتوا +settings.webhook.body=پیکر +settings.githooks_desc=هوک های Git از خود Git قدرت گرفته اند. شما نمی‌توانید پرونده های زیر را برای عملیات‌های سفارشی ویرایش کنید. +settings.githook_edit_desc=در صورتیکه hook غیرفعال باشد، محتوای نمونه ای موجود در آن ارائه خواهد شد. برای اینکه به کلی غیر فعال شود، محتوا را پاک کنید تا خالی شود. +settings.githook_name=نام hook +settings.githook_content=محتوای هوک +settings.update_githook=به روزرسانی hook +settings.add_webhook_desc=Gitea یک درخواست POST با نوع محتوی مشخص برای نشانی مقصذ ارسال خواهد کرد. برای کسب اطلاعات بیشتر به راهنمای هوک تحت وب مراجعه کنید. +settings.payload_url=نشانی هدف +settings.http_method=روش HTTP +settings.content_type=نوع محتوای POST +settings.secret=سِری +settings.slack_username=نام‎کاربری +settings.slack_icon_url=نشانی تمثال +settings.discord_username=نام‎کاربری +settings.discord_icon_url=نشانی تمثال +settings.slack_color=رنگ +settings.event_desc=ماشه بر روی: +settings.event_push_only=رویداد درج کردن +settings.event_send_everything=همه رویدادها +settings.event_choose=رویدادهای سفارشی… +settings.event_create=ایجاد +settings.event_create_desc=شاخه یا برچسب ایجاد شد. +settings.event_delete=حذف +settings.event_delete_desc=شاخه یا برچسب حذف شد +settings.event_fork=انشعاب +settings.event_fork_desc=انشعاب از مخزن ایجاد شد +settings.event_issues=مسائل +settings.event_issues_desc=مسئله ای باز شده ، بسته شده، بازگشایی شده، ویرایش شده، به فردی تخصیص یافته، لغو تخصیص از فردی شده، برچسب آن به روزرسانی شده، برچسب آن پاک شده، نقطه عطف برای آن تعیین شده یا نقطه عطف از آن حذف شده. +settings.event_issue_comment=دیدگاه های مسئله +settings.event_issue_comment_desc=نظر در مسئله ایجاد شد، ویرایش شد یا حذف شد. +settings.event_release=انتشار +settings.event_release_desc=منشتر شد. به روز شده یا حذف شده در یک مخزن. +settings.event_pull_request=تقاضای واکشی +settings.event_pull_request_desc=تقاضای واکشی ایجاد شده، مجددا باز شده، ویرایش شده، تایید شده، رد شده، بازبینی شده، تخصیص یافته، لغو تخصیص شده، عنوان آن به تغییر یافته، عنوان آن حذف شده، یا همگام سازی شده. +settings.event_push=درج کردن +settings.event_push_desc=درج در مخزن توسط گیت. +settings.branch_filter=صافی شاخه +settings.branch_filter_desc=لیست سفید برای درج در شاخه، سازنده شاخه و حذف کننده رخداد ها، به عنوان الگوی قطره‌ای تعریف میشوند. اگر خالی یا * باشد. رخداد های تمامی شاخه های گزارش می شوند. به github.com/gobwas/glob برای مستندات املای آن نگاه کنید. مثال ها: master, {master,release*}. +settings.event_repository=مخزن +settings.event_repository_desc=مخزن ساخته یا حذف شد. +settings.active=فعال +settings.active_helper=اطلاعات درباره کشیده شدن ماشه رویدادها به این نشانی هوک تحت وب ارسال خواهد شد. +settings.add_hook_success=یک هوک تحت وب جدید افزوده شده است. +settings.update_webhook=به روزرسانی webhook +settings.update_hook_success=هوک تحت وب به‌روز شد. +settings.delete_webhook=حذف Webhook +settings.recent_deliveries=واپسین تحویل ها +settings.hook_type=نوع هوک +settings.add_slack_hook_desc=درست کردن سستی در مخزن شما. +settings.slack_token=توکن +settings.slack_domain=دامنه +settings.slack_channel=کانال +settings.add_discord_hook_desc=درست کردن اختلاف‌ها در مخزن شما. +settings.add_dingtalk_hook_desc=درست کردن Dingtalk در مخزن شما. +settings.add_telegram_hook_desc=درست کردن تلگرام در مخزن شما. +settings.add_msteams_hook_desc=درست کردن تیم مایکروسافت در مخزن شما. +settings.deploy_keys=کلید های استقرار +settings.add_deploy_key=افزودن کلید استقرار +settings.deploy_key_desc=کلید استقرار فقط-خواندنی است برای دسترس واکشی از این مخزن. +settings.is_writable=فعال کردن دسترسی نوشتن +settings.is_writable_info=مجوز به کلید استقرار برای درج در مخزن داده شدن. +settings.no_deploy_keys=هنوز‌ هیچ کلید استقراری وجود ندارد. +settings.title=عنوان +settings.deploy_key_content=محتوا +settings.key_been_used=یک کلید استقرار دارای محتوای منحصر به فرد پیش از این در حال استفاده است. +settings.key_name_used=کلیدDeploy با همین نام موجود است. +settings.add_key_success=کلید استقرار '%s' اضافه شده است. +settings.deploy_key_deletion=حذف کلید استقرار +settings.deploy_key_deletion_desc=حذف کلید استقرار از مخزن دسترسی‌اش را را مجدد لغو می‌کند. آیا ادامه می‌دهید؟ +settings.deploy_key_deletion_success=کلید استقرار حذف شد. +settings.branches=شاخه‎ها +settings.protected_branch=حفاظت از شاخه +settings.protected_branch_can_push=اجازه درج؟ +settings.protected_branch_can_push_yes=شما می‎توانید درج کنید +settings.protected_branch_can_push_no=شما نمی‎توانید درج کنید +settings.branch_protection=محافظ شاخه برای شاخه‌ی '%s' +settings.protect_this_branch=فعال کردن حفاظت از شاخه +settings.protect_this_branch_desc=جلوگیری از هر گونه حذف یا غیرفعال کردن هر درج شدنی در این شاخه. +settings.protect_whitelist_committers=فعال کردن لیست سفید درج +settings.protect_whitelist_committers_desc=اجازه به کاربران یا تیم‌های موجود لیست سفید برای درج در این شاخه (اما نه درج اجباری). +settings.protect_whitelist_users=کاربران لیست سفید برای درج در مخزن: +settings.protect_whitelist_search_users=جستجوی کاربر… +settings.protect_whitelist_teams=تیم‌های لیست سفید برای درج در مخزن: +settings.protect_whitelist_search_teams=جستجوی تیم ها… +settings.protect_merge_whitelist_committers=فعال کردن لیست سفید ادغام +settings.protect_merge_whitelist_committers_desc=اجازه به کاربران یا تیم‌های موجود لیست سفید برای تقاضا ادغام واکشی در این شاخه. +settings.protect_merge_whitelist_users=کاربران لیست سفید برای ادغام: +settings.protect_merge_whitelist_teams=تیم‌های لیست سفید برای ادغام: +settings.protect_check_status_contexts=فعال کردن حالات بررسی +settings.protect_check_status_contexts_list=آخرین بررسی حالات این مخزن در هفته گذشته اتفاق افتاده است +settings.protect_required_approvals=نیازمند تاییدیه: +settings.protect_required_approvals_desc=فقط اجازه تقاضای ادغام واکشی برای بازبینی‌کننده‌گانی که دارای امتیاز مثبت کافی که از لیست سفید کاربران یا تیم‌ها باشند. +settings.protect_approvals_whitelist_users=لیست‌سفید بازبینی‌کنندگان: +settings.protect_approvals_whitelist_teams=تیم‌های لیست سفید برای بازبینی‌ها: +settings.add_protected_branch=فعال‌سازی محافظ +settings.delete_protected_branch=غیر فعال‌سازی محافظ +settings.update_protect_branch_success=محافظ شاخه برای شاخه‌ی «%s» به‌روز شد. +settings.remove_protected_branch_success=محافظ شاخه برای شاخه‌ی «%s» غیرفعال شد. +settings.protected_branch_deletion=غیرفعال‌کردن حفاظت از شاخه +settings.protected_branch_deletion_desc=غیر‌فعال کردن محافظت از شاخه به کاربرانی که دسترسی نوشتن دارند اجازه درج در شاخه را می‌دهد. آیا ادامه می‌دهید؟ +settings.default_branch_desc=شاخه اصلی مخزن را برای تقاضا واکشی و کامیت کد ها انتخاب نمایید: +settings.choose_branch=شاخه اصلی مخزن را برای تقاضا واکشی و کامیت کد ها انتخاب نمایید: +settings.no_protected_branch=اینجا هیچ شاخه محافظت شده ای وجود ندارد. +settings.edit_protected_branch=ویرایش +settings.protected_branch_required_approvals_min=نیازمند تاییدیه نمی‌تواند منفی باشد. +settings.bot_token=Token ربات +settings.chat_id=شناسه گپ +settings.archive.button=بایگانی مخزن +settings.archive.header=بایگانی این مخزن +settings.archive.text=بایگانی کردن یک مخزن آن را فقط-خواندی می‌کند. و از پیشخوان مخفی می‌شود. و نمیتوان برای آن هیچ کامیتی، مسئله یا تقاضای واکشی‌ای ایجاد نمود. +settings.archive.success=این مخزن با موفقیت بایگانی شد. +settings.archive.error=زمانی که قصد بایگانی مخزن را داشتیم یک خطایی رخ داد. لطفا برای اطلاعات بیشتر به رخداد های ثبت شده نگاه بیاندازید. +settings.archive.error_ismirror=شما نمیتوانید یک مخزن قرینه را بایگانی کنید. +settings.archive.branchsettings_unavailable=تنظیمات شاخه قابل دسترس نیست زمانی که مخزن بایگانی شده است. +settings.unarchive.button=خروج بایگانی مخزن +settings.unarchive.header=خروج بایگانی این مخزن +settings.unarchive.text=خروج از بایگانی مخزن توانایی برای ارسال کامیت، مسئله یا تقاضای واکشی‌ جدید را برای شما بازمی‌گرداند. +settings.unarchive.success=این مخزن با موفقیت از بایگانی خارج شد. +settings.unarchive.error=زمانی که قصد خروج از بایگانی مخزن را داشتیم یک خطایی رخ داد. لطفا برای اطلاعات بیشتر به رخداد های ثبت شده نگاه بیاندازید. +settings.update_avatar_success=زمانی که قصد خروج از بایگانی مخزن را داشتیم یک خطایی رخ داد. لطفا برای اطلاعات بیشتر به رخداد های ثبت شده نگاه بیاندازید. +diff.browse_source=فهرست منبع +diff.parent=والد +diff.commit=کامیت +diff.git-notes=یادداشت‌ها +diff.data_not_available=محتوای تفاوت ها در دسترس نیست +diff.show_diff_stats=نمایش آمار تفاوت ها +diff.show_split_view=مشاهده تقسیم شده +diff.show_unified_view=نمای یکپارچه +diff.whitespace_button=فضای خالی +diff.whitespace_show_everything=نمایش همه تغییرات +diff.whitespace_ignore_all_whitespace=نادیده گرفتن خط‌های فضای خالی در زمان مقایسه +diff.whitespace_ignore_amount_changes=نادیده گرفتن تغییرات در میزان فضاهای خالی +diff.whitespace_ignore_at_eol=نادیده گرفتن تغییرات در فضاهای خالی در انتهای خط +diff.stats_desc= %dفایلهای تغییر یافته به همراه%d افزوده شده و %d حذف شده +diff.bin=دودویی (BIN) +diff.view_file=مشاهده پرونده +diff.file_before=قبل از +diff.file_after=پس از +diff.file_image_width=عرض +diff.file_image_height=ارتفاع +diff.file_byte_size=اندازه +diff.file_suppressed=تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است +diff.too_many_files=برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است +diff.comment.placeholder=اظهار نظر کنید +diff.comment.markdown_info=شیوه markdown پیشتیبانی می‌شود. +diff.comment.add_single_comment=افزودن یک دیدگاه به تنهایی +diff.comment.add_review_comment=افزودن دیدگاه +diff.comment.start_review=شروع بازبینی +diff.comment.reply=پاسخ +diff.review=بازبینی +diff.review.header=ارائه بازبینی +diff.review.placeholder=بازبینی دیدگاه +diff.review.comment=دیدگاه +diff.review.approve=پذیرفتن +diff.review.reject=درخواست تغییر کوکی ها +releases.desc=پیگیری نسخ و دریافت‌های پروژه. +release.releases=انتشارها +release.new_release=انتشار جدید +release.draft=پیش‌نویس +release.prerelease=پیش-انتشار +release.stable=پایدار +release.edit=ويرايش +release.ahead=%d تغییر در %s پس از این انتشار +release.source_code=کُد منبع +release.new_subheader=انتشارها نسخ پروژه را سازماندهی می‌کنند. +release.edit_subheader=انتشارها نسخ پروژه را سازماندهی می‌کنند. +release.tag_name=نام برچسب +release.target=هدف/مقصد +release.tag_helper=از تگ های موجود استفاده کنید یا تگ جدیدی را هنگام انتشار ایجاد کنید. +release.title=عنوان +release.content=محتوا +release.prerelease_desc=علامت‌گذاری به عنوان پیش-انتشار +release.prerelease_helper=علامت گذاری این انتشار به عنوان نامناسب برای استفاده در محصول نهایی. +release.cancel=انصراف +release.publish=نشر نسخه انتشار یافته +release.save_draft=ذخیره پیش نویس +release.edit_release=به روزرسانی انتشار +release.delete_release=حذف انتشار +release.deletion=حذف انتشار +release.deletion_desc=حذف انتشار موجب حذف برچسب گیت از مخزن میشود. محتوای مخزن و تاریخچه آن تغییر نخواهند کرد. آیا ادامه می‌دهید؟ +release.deletion_success=انتشار حذف شد. +release.tag_name_already_exist=انتشاری با این نام موجود است. +release.tag_name_invalid=نام برچسب معتبر نمی‌باشد. +release.downloads=بارگیری‌ها +branch.name=نام شاخه +branch.search=جستجوی شاخه ها +branch.already_exists=نام شاخه «%s» از پیش وجود داشته است. +branch.delete_head=حذف +branch.delete=حذف شاخه '%s' +branch.delete_html=حذف شاخه +branch.delete_desc=حذف شاخه همیشگی است. و این هرگز نمی‌تواتند بازگشت داشته باشد. آیا ادامه می‌دهید؟ +branch.deletion_success=شاخه '%s' حذف شد. +branch.deletion_failed=نا موفق در حذف شاخه '%s'. +branch.delete_branch_has_new_commits=شاخه «%s» نمی‌تواند حذف شود لذا کامیت جدید بعد از ادغام اضافه شده اس. +branch.create_branch=ساختن شاخه %s +branch.create_from=از '%s' +branch.create_success=شاخه '%s' ساخته شد. +branch.branch_already_exists=شاخه '%s' از قبل در این مخزن وجود دارد. +branch.branch_name_conflict=نام شاخه با نام «%s» دارای تداخل با شاخه‌ای موجود در مخزن به نام «%s» است. +branch.tag_collision=شاخه «%s» نمی‌تواند با برچسبی هم‌نام که در مخزن موجود است، ساخته شود. +branch.deleted_by=حذف شده توسط %s +branch.restore_success=شاخه «%s» بازگردانی شد. +branch.restore_failed=ناموفق در بازگرانی شاخه '%s'. +branch.protected_deletion_failed=شاخه «%s» محافظت شده است. نمی‌توانید آن‌را حذف کنید. +branch.restore=بازگردانی شاخه '%s' +branch.download=بارگیری یا دریافت شاخه '%s' +topic.manage_topics=مدیریت موضوعات +topic.done=انجام شد +topic.count_prompt=شما نمی توانید بیش از 25 موضوع انتخاب کنید +topic.format_prompt=موضوع می‌بایستی با حروف یا شماره ها شروع شود. و می‌تواند شامل دَش ('-') باشد و طول آن تا 35 کارکتر نیز امکانپذیر است. [org] +org_name_holder=نام سازمان +org_full_name_holder=نام کامل سازمان +org_name_helper=نام سازمان باید کوتاه و قابل حفظ کردن باشد. +create_org=ایجاد سازمان +repo_updated=به روز رسانی شده +people=افراد +teams=تیم‌ها +lower_members=اعضا +lower_repositories=مخازن +create_new_team=تیم جدید +create_team=ایجاد تیم +org_desc=شرح +team_name=نام تیم +team_desc=شرح +team_name_helper=نام تیم باید کوتاه و قابل حفظ کردن باشد. +team_desc_helper=هدف یا نقش تیم را شرح دهید. +team_permission_desc=مجوز +team_unit_desc=اجازه به دسترسی به قسمت های این مخزن +form.name_reserved=نام سازمان %s رزرو شده است. +form.name_pattern_not_allowed=الگوی %s در نام سازمان مجاز نیست. +form.create_org_not_allowed=شما اجازه ایجاد سازمان را ندارید. +settings=تنظيمات +settings.options=سازمان +settings.full_name=نام کامل +settings.website=تارنما +settings.location=موقعیت مکانی +settings.visibility=پدیداری +settings.visibility.public=عمومی +settings.visibility.limited=محدود شده (پدیدار برای کاربر وارد شده فقط) +settings.visibility.private=خصوصی (پدیدار برای کاربران عضو سازمان) +settings.update_settings=به‌ روزرسانی تنظیمات +settings.update_setting_success=تنظیمات این سازمان به‌روز شد. +settings.change_orgname_prompt=نکته: به‌یاد داشته باشید تغییر نام سازمان موجب تعییر نشانی‌ها آن نیز می‌شود. +settings.update_avatar_success=آواتار این سازمان به‌روز شد. +settings.delete=حذف سازمان +settings.delete_account=حذف این سازمان +settings.delete_prompt=سازمان برای همیشه حذف خواهد شد. این قابل برگشت نخواهد بود! +settings.confirm_delete_account=تاییدیه حذف +settings.delete_org_title=حذف سازمان +settings.delete_org_desc=سازمان برای همیشه حذف خواهد شد. آیا همچنان ادامه می‌دهید؟ +settings.hooks_desc=افزودن webhook های که برای تمام مخازن این سازمان اجرا میشود. +members.membership_visibility=قابل مشاهده بودن عضویت: +members.public=نمایان +members.public_helper=مخفی کردن +members.private=مخفی +members.private_helper=نمایان کردن +members.member_role=نقش عضو: +members.owner=مالک +members.member=عضو +members.remove=حذف +members.leave=ترک‌کردن +members.invite_desc=افزودن عضو جدید به %s: +members.invite_now=دعوت کن +teams.join=پیوستن +teams.leave=ترک‌کردن +teams.read_access=دسترسی خواندن +teams.read_access_helper=اعضا می‌توانند مخازن مربوط به تیم را مشاهده و از آن همسان تهیه کنند. +teams.write_access=دسترسی نوشتن +teams.write_access_helper=اعضا می‌توانند مخازن مربوط به تیم را بخوانند و کامیت ها در آن درج کنند. +teams.admin_access=دسترسی مدیریت +teams.admin_access_helper=اعضا می‌توانند واکشی یا درج بر روی مخازن تیم را انجام دهند و می‌توانند به آنها همکار اضافه کنند. +teams.no_desc=توضیحی برای این تیم ثبت نشده‌است +teams.settings=تنظيمات +teams.owners_permission_desc=مالک ها دسترسی کامل به تمام مخازن و دسترسی مدیریت به سازمان را دارند. +teams.members=اعضای تیم +teams.update_settings=به‌ روزرسانی تنظیمات +teams.delete_team=حذف تیم +teams.add_team_member=افزودن عضو به تیم +teams.delete_team_title=حذف تیم +teams.delete_team_desc=حذف یک تیم از مخزن تمامی دسترسی اعضای آن را از بین می‌برد. آیا مطمئن هستید؟ +teams.delete_team_success=تیم حذف شد. +teams.read_permission_desc=این تیم دسترسی خواندن خواهد داشت: اعضا خواهند توانست مخازن را مشاهده و کپی نمایند. +teams.write_permission_desc=این تیم دسترسی نوشتن خواهد داشت: اعضا خواهند توانست مخازن تیم را خوانده و تغییراتی در آنها اعمال نمایند. +teams.admin_permission_desc=این تیم دسترسی نوشتن خواهد داشت: اعضا خواهند توانست مخازن تیم را خوانده ، تغییراتی در آنها اعمال کرده و یا همکارانشان را به مخازن اضافه نمایند. +teams.repositories=مخازن تیم +teams.search_repo_placeholder=جستجوی مخزن... +teams.add_team_repository=افزودن مخزن تیمی +teams.remove_repo=حذف +teams.add_nonexistent_repo=مخزنی را که شما قصد افزودن آن را دارید موجود نیست، لطفا ابتدا آن را ایجاد کنید. +teams.add_duplicate_users=این کاربر پیش از این عضو تیم بوده است. +teams.repos.none=این تیم به هیچ مخزنی دسترسی ندارد. +teams.members.none=هیچ عضوی در این تیم نیست. [admin] +dashboard=پیشخوان +users=حساب کاربران +organizations=تشکیلات +repositories=مخازن +hooks=افزودن هوک‌های تحت وب پیش فرض +authentication=منابع احراز هویت +config=پیکربندی +notices=هشدارهای سامانه +monitor=نظارت +first_page=نخستین +last_page=واپسین +total=مجموع: %d +dashboard.statistic=چکیده +dashboard.operations=عملیات‌های نگهداری +dashboard.system_status=وضعیت سامانه +dashboard.statistic_info=پایگاه داده‌‌های gitgo شامل %d کاربران, %d سازمان ها, %d کلید های عمومی, %d مخازن, %d مشاهده ها, %d ستاره دار ها, %d فعالیت ها, %d دسترسی ها, %d مسائل, %d نظرات, %d حساب ها کاربری شبکه های اجتماعی, %d دنیال کردن ها, %dmirror, %d ریلیز ها, %d نقاط ورود به سیستم, %d webhooks, %d نقاط عطف یا milestone ها, %d برچسب, %d hook وظایف, %d تیم, %d به روزرسانی ها وظایف افراد, %d پیوست هاست. +dashboard.operation_name=نام عملیات +dashboard.operation_switch=تعویض +dashboard.operation_run=اجرا +dashboard.clean_unbind_oauth=تمیز کردن اتصال بدون مرز OAuth +dashboard.clean_unbind_oauth_success=تمامی اتصالات بدون مرز OAuth حذف شدند. +dashboard.delete_inactivate_accounts=حذف تمام حساب های کاربری غیرفعال +dashboard.delete_inactivate_accounts_success=تمامی حساب های کاربری غیرفعال حذف شدند. +dashboard.delete_repo_archives=حذف تمامی مخازن بایگانی شده +dashboard.delete_repo_archives_success=همه مخازن بایگانی با موفقیت حذف شدند. +dashboard.delete_missing_repos=حذف تمامی مخازنی که پرونده‌های گیت آنها از بین رفته است +dashboard.delete_missing_repos_success=تمامی مخازنی که فایل گیت آنها مفقود شده حذف شده اند. +dashboard.delete_generated_repository_avatars=حذف آواتار هایی که برای مخزن تولید شده اند +dashboard.delete_generated_repository_avatars_success=آواتار هایی که برای مخزن تولید شده‌اند حذف شدند. +dashboard.git_gc_repos=متراکم کردن تمامی زباله‌های مخازن +dashboard.git_gc_repos_success=متراکم سازی زباله های تمامی مخازن حذف تمام شد. +dashboard.resync_all_sshkeys=برای کلید SSH مربوط به Gitea/gitgo پرونده '.ssh/authorized_keys' را به‌روز کنید. (نیاز به ساخت در سرور نمی‌باشد) +dashboard.resync_all_sshkeys_success=کلید عمومی SSH توسط gitea/gitgo بررسی و به روز شده اند. +dashboard.resync_all_hooks=همگام سازی مجدد hook های pre-receive و update و post-receive برای تمامی مخازن. +dashboard.resync_all_hooks_success=کلیه هوک‌های مخزن پیش-دریافت ، به روزرسانی و پس از دریافت مجدداً همگام سازی شده اند. +dashboard.reinit_missing_repos=تمامی مخازنی که سوابقشان وجود دارند مجدداً گیت آنها مفقود شده است مجدداً مقدمات آنها فراهم شود +dashboard.reinit_missing_repos_success=تمامی مخازنی که سوابقشان وجود دارند مجدداً گیت آنها مفقود شده است مجدداً مقدمات آنها فراهم شد. +dashboard.sync_external_users=همگام سازی اطلاعات کاربر خارجی +dashboard.sync_external_users_started=همگام سازی اطلاعات کاربر خارحی شروع شد. +dashboard.git_fsck=اجرای بررسی سلامت تمامی مخازن +dashboard.git_fsck_started=بررسی سلامت مخزن شروع شد. +dashboard.server_uptime=فعالیت بی‌وقفه سرور +dashboard.current_goroutine=Goroutine های فعلی +dashboard.current_memory_usage=میزان مصرف فعلی از حافظه +dashboard.total_memory_allocated=کل حافظه اختصاص داده شده +dashboard.memory_obtained=حافظه به دست آمده +dashboard.pointer_lookup_times=اشاره‌گر زمان‌های جستجو +dashboard.memory_allocate_times=تخصیص یافتن حافظه +dashboard.memory_free_times=آزادسازی حافظه +dashboard.current_heap_usage=میزان مصرف توده فعلی +dashboard.heap_memory_obtained=حافظه به دست آمده برای توده +dashboard.heap_memory_idle=بیکار بوده حافظه توده +dashboard.heap_memory_in_use=حافظه توده در حال استفاده +dashboard.heap_memory_released=حافظه توده آزاد شد +dashboard.heap_objects=شی توده یا Heap +dashboard.bootstrap_stack_usage=میزان استفاده از پشته توسط راهنداز +dashboard.stack_memory_obtained=حافظه پشته به دست آمده +dashboard.mspan_structures_usage=میزان استفاده‌ی ساختار های MSpan +dashboard.mspan_structures_obtained=ساختار های MSpan به دست آمده +dashboard.mcache_structures_usage=میزان استفاده از ساختار های MCache +dashboard.mcache_structures_obtained=ساختار های MCache به دست آمده +dashboard.profiling_bucket_hash_table_obtained=Profiling Bucket Hash Table به دست آمده +dashboard.gc_metadata_obtained=متادیتاهای بدست امده از GC +dashboard.other_system_allocation_obtained=تخصیص های حافظه در سایر قسمت های سیستم +dashboard.next_gc_recycle=بازیافت GC بعدی +dashboard.last_gc_time=زمان از آخرین GC +dashboard.total_gc_time=کل زمان مکث GC +dashboard.total_gc_pause=کل زمان مکث GC +dashboard.last_gc_pause=واپسین مکث در GC +dashboard.gc_times=زمان های GC +users.user_manage_panel=مدیریت حساب کاربری +users.new_account=ایجاد حساب کاربری +users.name=نام‎کاربری +users.activated=فعال شده +users.admin=مدیر +users.repos=مخازن +users.created=ایجاد شده +users.last_login=آخرین ورود +users.never_login=هرگز وارد نشده +users.send_register_notify=ارسال اعلان ثبت نام کاربر +users.new_success=حساب کاربری «%s» ساخته شد. +users.edit=ویرایش +users.auth_source=منبع احراز هویت +users.local=محلی +users.auth_login_name=نام سامانه ورود احراز هویت +users.password_helper=برای عدم تغییر گذرواژه ان خالی رها کنید. +users.update_profile_success=حساب کاربری به روز شد. +users.edit_account=ویرایش حساب کاربری +users.max_repo_creation=حداکثر تعداد مخازن +users.max_repo_creation_desc=(برای استفاده از محدودیت پیش فرض ، مقدار -1 را تنظیم وارد کنید) +users.is_activated=حساب کاربری فعالاست +users.prohibit_login=غیرفعال کردن ورود +users.is_admin=آیا مدیر است +users.allow_git_hook=اجازه ساختن هوک های گیت +users.allow_import_local=اجازه ورود مخازن محلی +users.allow_create_organization=اجازه ساختن سازمان +users.update_profile=به‌روزرسانی حساب کاربری +users.delete_account=حذف حساب کاربری +users.still_own_repo=این کاربر هنوز صاحب یک یا چند مخزن است. ابتدا این مخازن را حذف یا انتقال دهید. +users.still_has_org=این کاربر عضو یک سازمان است. ابتدا کاربر را از هر سازمان متعلق حذف کنید. +users.deletion_success=حساب کاربری حذف شد. +orgs.org_manage_panel=مدیریت سازمان +orgs.name=نام +orgs.teams=تیم‌ها +orgs.members=اعضاء +orgs.new_orga=سازمان جدید +repos.repo_manage_panel=مدیریت مخزن +repos.owner=مالک +repos.name=نام +repos.private=خصوصی +repos.watches=تماشا شده +repos.stars=ستاره ها +repos.forks=انشعاب‌ها +repos.issues=مسائل +repos.size=اندازه +hooks.desc=هوک تحت وب به صورت خودکار درخواست POST HTTP را به سمت سرور روانه می‌کند زمانی که ماشه رخداد Gitea کشیده شود. هوک تحت وب اینجا به صورت پیش فرض اینجا تعریف شده و برای تمامی مخزن جدید کپی خواهد شد. برای اطلاعات بیشتر به e راهنمای هوک تحت وب مراجعه کنید. +hooks.add_webhook=افزودن هوک تحت وب پیش فرض +hooks.update_webhook=به روز رسانی هوک تحت وب پیش فرض +auths.auth_manage_panel=مدیریت منابع احراز هویت +auths.new=افزودن منبع احراز هویت +auths.name=نام +auths.type=نوع +auths.enabled=فعال شده +auths.syncenabled=فعال کردن همگام سازی کاربر +auths.updated=به روز رسانی شده +auths.auth_type=نوع احراز هویت +auths.auth_name=نام احراز هویت +auths.security_protocol=پروتکل امنیتی +auths.domain=دامنه +auths.host=میزبان +auths.port=درگاه (پورت) +auths.bind_dn=DN متصل شده +auths.bind_password=اتصال گذرواژه +auths.bind_password_helper=هشدار: این گذرواژه به صورت متن خام ذخیره می شود. استفاده حساب های کاربری فقط-خواندنی امکان پذیر هست. +auths.user_base=پایگاه جستجوی کاربر +auths.user_dn=کاربر DN +auths.attribute_username=ویژگی نام کاربری +auths.attribute_username_placeholder=نام کاربری را خالی بگذارید برای انتخاب نام کاربری gitea انتخاب شود. +auths.attribute_name=ویژگی نام +auths.attribute_surname=ویژگی نام خانوادگی +auths.attribute_mail=ویژگی ایمیل +auths.attribute_ssh_public_key=ویژگی های کلید SSH عمومی +auths.attributes_in_bind=واکشی ویژگی های DN متصل شده در متن زمینه +auths.use_paged_search=استفاده از جستجو ثبت شده +auths.search_page_size=اندازه صفحه +auths.filter=صافی کاربر +auths.admin_filter=صافی مدیر +auths.ms_ad_sa=ویژگی های جستجو MS AD +auths.smtp_auth=نوع احراز هویت SMTP +auths.smtphost=میزبان SMTP +auths.smtpport=گذرگاه(پورت) SMTP +auths.allowed_domains=دامنه های مجاز +auths.allowed_domains_helper=برای اجازه به تمامی دامنه های آن را خالی رها کنید. یا با ویرگول (',') از یک دیگر جدا کنید. +auths.enable_tls=فعال کردن رمزگذاری TLS +auths.skip_tls_verify=صرف نظر از اعتبار سنجی TLS +auths.pam_service_name=نام سرویس PAM +auths.oauth2_provider=تامین کننده OAuth2 +auths.oauth2_clientID=ID سرویس گیرنده (کلید) +auths.oauth2_clientSecret=کلمه امن سرویس گیرنده +auths.openIdConnectAutoDiscoveryURL=OpenID برای کشف خودکار به نشانی متصل میشود +auths.oauth2_use_custom_url=از نشانی های سفارشی به جای نشانی های پیش فرض استفاده کنید +auths.oauth2_tokenURL=نشانی Token +auths.oauth2_authURL=نشانی مجوز +auths.oauth2_profileURL=نشانی نمایه +auths.oauth2_emailURL=نشانی ایمیل (رایانامه) +auths.enable_auto_register=فعال سازی ثبت نام خودکار +auths.tips=ﻧﮑﺎﺕ +auths.tips.oauth2.general=احراز هویت OAuth2 +auths.tips.oauth2.general.tip=هنگام ثبت احراز هویت OAuth2 جدید ، نشانی callback / redirect باید این گونه باشد: /user/oauth2//callback +auths.tip.oauth2_provider=تامین کننده OAuth2 +auths.tip.bitbucket=ثبت یک OAuth جدید مصرف کننده بر https://bitbucket.org/account/user//oauth-consumers/new و افزودن مجوز 'Account' - 'Read' +auths.tip.dropbox=یک برنامه جدید در https://www.dropbox.com/developers/apps بسازید +auths.tip.facebook=یک برنامه جدید در https://developers.facebook.com/apps بسازید برای ورود از طریق فیس بوک قسمت محصولات "Facebook Login +auths.tip.github=یک برنامه OAuth جدید در https://github.com/settings/applications/new ثبت کنید +auths.tip.gitlab=ثبت یک برنامه جدید در https://gitlab.com/profile/applications +auths.tip.google_plus=اطلاعات مربوط به مشتری OAuth2 را از کلاینت API Google در https://console.developers.google.com/ +auths.tip.openid_connect=برای مشخص کردن نقاط پایانی از آدرس OpenID Connect Discovery URL ( /.well-known/openid-configuration) استفاده کنید. +auths.tip.twitter=به https://dev.twitter.com/apps بروید ، برنامه ای ایجاد کنید و اطمینان حاصل کنید که گزینه "اجازه استفاده از این برنامه برای ورود به سیستم با Twitter" را فعال کنید +auths.tip.discord=یک برنامه جدید را در https://discordapp.com/developers/applications/me ثبت کنید +auths.tip.gitea=یک برنامه OAuth2 ثبت کنید. راهنمایی بیشتر https://docs.gitea.io/en-us/oauth2-provider/ +auths.edit=ویرایش منبع احراز هویت +auths.activated=این منبع احراز هویت فعال شده است +auths.new_success=احراز هویت '%s' افزوده شد. +auths.update_success=احراز هویت منبع به‌روز شد. +auths.update=به‌روزکردن منبع احراز هویت +auths.delete=حذف منبع احراز هویت +auths.delete_auth_title=حذف منبع احراز هویت +auths.delete_auth_desc=حذف یک منبع احراز هویت، از ورود کاربران به سیستم جلوگیری می کند. آیا ادامه می‌دهید؟ +auths.still_in_used=منبع احراز هویت همچنان در حال استفاده است. ابتدا با استفاده از این منبع احراز کاربرانی را تبدیل یا حذف کنید. +auths.deletion_success=احراز هویت منبع حذف شد. +auths.login_source_exist=منبع احراز هویت '%s' پیش از این وجود داشته است. +config.server_config=پیکربندی سرور +config.app_name=عنوان سایت +config.app_ver=نسخه‌ی GitGo +config.app_url=آدرس پایه گیتی +config.custom_conf=مسیر پرونده پیکربندی +config.custom_file_root_path=مسیر ریشه‌ی پرونده سفارشی +config.domain=دامنه سرور SSH +config.offline_mode=شیوه محلی +config.disable_router_log=غیرفعال کردن گزارش مسیریاب +config.run_user=اجرا به عنوان نام کاربری +config.run_mode=حالت اجرا +config.git_version=نسخه‌ی Git +config.repo_root_path=مسیر ریشه مخزن +config.lfs_root_path=مسیر ریشه LFS +config.static_file_root_path=مسیر ریشه فایل استاتیک +config.log_file_root_path=مسیر گزارش‌ها +config.script_type=نوع اسکریپت +config.reverse_auth_user=شیوه ی احرازهویت معکوس +config.ssh_config=پیکربندی SSH +config.ssh_enabled=فعال شده +config.ssh_start_builtin_server=استفاده از ساخته سرور +config.ssh_domain=دامنه سرور +config.ssh_port=درگاه (پورت) +config.ssh_listen_port=گوش دادن به پورت +config.ssh_root_path=مسیر ریشه +config.ssh_key_test_path=مسیر کلید آزمایش +config.ssh_keygen_path=مسیر فایل ssh-keygen +config.ssh_minimum_key_size_check=بررسی حداقل طول کلید +config.ssh_minimum_key_sizes=حداقل اندازه‌ی کلید ها +config.lfs_config=پیکربندی LFS +config.lfs_enabled=فعال شده +config.lfs_content_path=مسیر محتوای LFS +config.lfs_http_auth_expiry=انقضای احراز LFS HTTP +config.db_config=تنظیمات پایگاه داده +config.db_type=نوع +config.db_host=میزبان +config.db_name=نام +config.db_user=نام‎کاربری +config.db_ssl_mode=SSL +config.db_path=مسیر +config.service_config=پیکربندی سرویس +config.register_email_confirm=نیاز به تایید ایمیل (رایانامه) ثبت نام +config.disable_register=غیرفعال‌کردن خود ثبت نامی +config.allow_only_external_registration=اجازه ثبت نام فقط از طریق خدمات خارجی +config.enable_openid_signup=فعالسازی خود ثبت نامی با OpenID +config.enable_openid_signin=فعال کردن ورود با OpenID +config.show_registration_button=نشان دادن دکمه ثبت نام +config.require_sign_in_view=فعال‌سازی نیازمند به ورود در هنگام مشاهده صفحات +config.mail_notify=فعال‌سازی اعلان‌های ایمیل (رایانامه) +config.disable_key_size_check=غیر فعال کردن بررسی حداقل اندازه کلید +config.enable_captcha=فعال کردن کپچا +config.active_code_lives=عمر کد فعال سازی +config.reset_password_code_lives=مدت انقضای کد بازیابی حساب کاربری +config.default_keep_email_private=مخفی کردن نشانی های ایمیل به صورت پیش فرض +config.default_allow_create_organization=اجازه ایجاد سازمان به صورت پیش فرض +config.enable_timetracking=فعال کردن پیگیری زمان +config.default_enable_timetracking=فعال سازی پیگیری زمان به صورت پیش فرض +config.default_allow_only_contributors_to_track_time=اجاز پگیری زمان مشارکت فقط +config.no_reply_address=مخفی کردن دامنه ایمیل +config.default_visibility_organization=وضعیت پیشفرض پدیداری برای سازمان جدید +config.default_enable_dependencies=فعال کردن وابستگی پیشفرض برای مسئله +config.webhook_config=پیکربندی هوک تحت وب +config.queue_length=طول صف +config.deliver_timeout=مهلت تحویل +config.skip_tls_verify=صرف نظر از اعتبارسنجی TLS +config.mailer_config=پیکربندی سامانه ایمیلی SMTP +config.mailer_enabled=فعال شده +config.mailer_disable_helo=غیر فعال کردن HELO +config.mailer_name=نام +config.mailer_host=میزبان +config.mailer_user=کاربر +config.mailer_use_sendmail=استفاده از ارسال رایانامه (ایمیل) مستقیم +config.mailer_sendmail_path=مسیر ارسال ایمیل مستقیم +config.mailer_sendmail_args=برهان های اضافی برای ارسال مستقیم ایمیل +config.send_test_mail=ارسال ایمیل آزمایشی +config.test_mail_failed=ارسال رایانامه یا ایمیل آزمایشی ناموفق بود '%s': %v +config.test_mail_sent=یک رایانامه (ایمیل) آزمایشی به "%s" ارسال شده است. +config.oauth_config=پیکربندی OAuth +config.oauth_enabled=فعال شده +config.cache_config=پیکربندی حافظه پنهان +config.cache_adapter=وفق دهنده حافظه پنهان +config.cache_interval=وقفه حافظه نهان +config.cache_conn=اتصال حافظه نهان +config.cache_item_ttl=مولفه TTL حافظه نهان +config.session_config=پیکربندی نشست ها +config.session_provider=تامین کننده نشست +config.provider_config=پیکربندی تامین کننده +config.cookie_name=نام کوکی +config.enable_set_cookie=فعال سازی تنظیم کردن کوکی +config.gc_interval_time=فاصله زمانی GC +config.session_life_time=طول عمر نشست +config.https_only=فقط HTTPS +config.cookie_life_time=طول عمر کوکی +config.picture_config=پیکربندی عکس و آواتار +config.picture_service=سرویس تصویر +config.disable_gravatar=غیر فعال کردن Gravatar +config.enable_federated_avatar=فعال سازی آواتار مشترک +config.git_config=پیکربندی Git +config.git_disable_diff_highlight=غیرفعال کردن برجسته سازی در Diff +config.git_max_diff_lines=حداکثر خط برای Diff (برای یک فایل) +config.git_max_diff_line_characters=حداکثر کاراکتر در Diff (برای یک خط) +config.git_max_diff_files=حداکثر فایل های Diff (برای نمایش) +config.git_gc_args=آرگومان های GC +config.git_migrate_timeout=آستانه ی زمان مهاجرت +config.git_mirror_timeout=زمان آستانه در به روز رسانی قرینه +config.git_clone_timeout=زمان آستانه ی عملیات Clone +config.git_pull_timeout=زمان آستانه ی عملیات واکشی +config.git_gc_timeout=زمان آستانه ی عملیات GC +config.log_config=پیکربندی ثبت رخداد +config.log_mode=شیوه ثبت رخداد +config.macaron_log_mode=مکرون شیوه ثبت رخداد +config.own_named_logger=نام ثبت کننده رخداد +config.routes_to_default_logger=خط سیر به ثبت کننده رخداد پیشفرض +config.go_log=استفاده از ثبت رخداد (هدایت به پیشفرض) +config.router_log_mode=خط سیر شیوه ثبت رخداد +config.disabled_logger=غیرفعال شده +config.access_log_mode=شیوه ثبت رخداد دسترسی +config.access_log_template=الگو +config.xorm_log_mode=شیوه ثبت رخداد XORM +config.xorm_log_sql=ثبت رخداد SQL +monitor.cron=وظایف Cron +monitor.name=نام +monitor.schedule=زمان بندی +monitor.next=زمان بعدی +monitor.previous=زمان قبلی +monitor.execute_times=اجرا +monitor.process=پردازش های در حال اجرا +monitor.desc=شرح +monitor.start=زمان شروع +monitor.execute_time=زمان مورد نیاز برای اجرا + +notices.system_notice_list=هشدارهای سامانه +notices.view_detail_header=مشاهده جزئیات اخطار +notices.actions=اقدامات +notices.select_all=انتخاب همه +notices.deselect_all=لغو انتخاب همه +notices.inverse_selection=انتخاب معکوس +notices.delete_selected=حذف انتخاب شده ها +notices.delete_all=حذف همه اخطارها +notices.type=نوع +notices.type_1=مخزن +notices.desc=توضیحات +notices.op=عملیات. +notices.delete_success=گزارش سیستم حذف شده است. [action] +create_repo=مخزن ایجاد شده %s +rename_repo=مخزن تغییر نام داد از %[1]s به %[3]s +commit_repo=درج کردن به %[3]s در %[4]s +create_issue=`مسائل باز %s#%[2]s` +close_issue=`مسائل رسیدگی شده%s#%[2]s` +reopen_issue=`مسايل بازگشایی شده %s#%[2]s` +create_pull_request=`تقاضای واکشی برای %s#%[2]sایجاد شده است` +close_pull_request=`تقاضای واکشی %s#%[2]s بسته شد` +reopen_pull_request=`تقاضای واکشی%s#%[2]sبازگشایی شد` +comment_issue=`در مسئله ی %s#%[2]s اظهار نظر کرده` +merge_pull_request=`تقاضای واکشی برای %s#%[2]sایجاد شده است` +transfer_repo=مخزن از %s به %sمنتقل شده است +push_tag=برچسب درج شده %[2]s به %[3]s +delete_tag=برچسب %[2] از %[3] حذف شدند +delete_branch=شاخه %[2] از %[3] حذف شدند +compare_commits=%d کامیت‌‌ مقایسه شد +compare_commits_general=مقایسه کامیت‌‌ها +mirror_sync_push=کامیت های %[3]s در %[4]s از قرینه همگام سازی شدند. +mirror_sync_create=از مرجع جدید %[2]s در%[3]s از قرینه همگام شده +mirror_sync_delete=از مرجع %[2]s در%[3]s حذف شده و از قرینه همگام شده [tool] +ago=%s پیش +from_now=%s از هم اکنون +now=حالا +future=آینده +1s=۱ ثانیه +1m=۱ دقیقه +1h=۱ ساعت +1d=۱ روز +1w=1 هفته +1mon=1 ماه +1y=1 سال +seconds=%d ثانیه +minutes=%d دقیقه +hours=%d ساعت +days=%d روز +weeks=%d هفته +months=%d ماه +years=%d سال +raw_seconds=ثانیه +raw_minutes=دقیقه [dropzone] +default_message=فایل را در این محل رها کنید یا دکمه ی آپلود یا بارگزاری را فشار دهید. +invalid_input_type=شما قادر به ارسال فایل های از این نوع نیستید. +file_too_big=حجم فایل ({{filesize}} MB) بیش از حداکثر مجاز است ({{maxFilesize}} مگابایت). +remove_file=حذف پرونده [notification] +notifications=اعلان‌ها +unread=خوانده نشده +read=خواندن +no_unread=اعلان خوانده نشده‌ای موجود نیست. +no_read=اعلان خوانده شده‌ای موجود نیست. +pin=سنجاق کردن اعلان +mark_as_read=علامتگذاری بعنوان خوانده شده +mark_as_unread=علامتگذاری بعنوان خوانده نشده +mark_all_as_read=علامت همه به عنوان خوانده شده [gpg] +error.extract_sign=خطا در استخراج امضا +error.generate_hash=خطا در ساختن هش کامیت +error.no_committer_account=هیچ ایمیلی به حساب کاربری صاحب کامیت پیونده داده نشده است +error.no_gpg_keys_found=هیچ کلید شناخته شده ای برای این امضا در پایگاه داده ها یافت نشد +error.not_signed_commit=هیچ کامیتی تکلیف نشده است +error.failed_retrieval_gpg_keys=بازیابی هر کلیدی که به حساب کاربری کامیت دهنده پیوست شده بود ناموفق بود [units] +error.no_unit_allowed_repo=شما اجازه دسترسی به هیچ قسمت از این مخزن را ندارید. +error.unit_not_allowed=شما اجازه دسترسی به این قسمت مخزن را ندارید. diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 25098c4a67..12829c527b 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -288,6 +288,7 @@ twofa_disabled=Kaksivaiheinen todennus on otettu pois käytöstä. delete_account=Poista tilisi confirm_delete_account=Varmista poisto + [repo] owner=Omistaja repo_name=Repon nimi diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index e163a7060a..e0e04f86a7 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -556,6 +556,10 @@ confirm_delete_account=Confirmer la suppression delete_account_title=Supprimer un compte delete_account_desc=Êtes-vous sûr de vouloir supprimer définitivement ce compte ? +email_notifications.enable=Activer les notifications par e-mail +email_notifications.disable=Désactiver les notifications par email +email_notifications.submit=Définir la préférence e-mail + [repo] owner=Propriétaire repo_name=Nom du dépôt @@ -583,7 +587,6 @@ mirror_prune_desc=Supprimer les références externes obsolètes mirror_interval=Intervalle de synchronisation ('h', 'm', et 's' sont des unités valides), 0 pour désactiver. mirror_interval_invalid=L'intervalle de synchronisation est invalide. mirror_address=Cloner depuis une URL -mirror_address_desc=Inclure tous les identifiants d'autorisation requis dans l'URL. Ceux-ci doivent être échappés correctement mirror_address_url_invalid=L'url fournie est invalide. Vous devez échapper tous les composants de l'url correctement. mirror_address_protocol_invalid=L'url fournie est invalide. Seuls les protocoles http(s):// ou git:// peuvent être la source du miroir. mirror_last_synced=Dernière synchronisation @@ -695,6 +698,7 @@ editor.delete=Supprimer '%s' editor.commit_message_desc=Ajouter une description détaillée facultative… editor.commit_directly_to_this_branch=Soumettre directement dans la branche %s. editor.create_new_branch=Créer une nouvelle branche pour cette révision et envoyer une nouvelle demande d'ajout. +editor.propose_file_change=Proposer une modification du fichier editor.new_branch_name_desc=Nouveau nom de la branche… editor.cancel=Annuler editor.filename_cannot_be_empty=Le nom de fichier ne peut être vide. @@ -768,7 +772,7 @@ issues.self_assign_at=`s'est assigné cela %s` issues.add_assignee_at=`s'est vu assigner cela par %s %s` issues.remove_assignee_at=`mis en non assigné par %s %s` issues.remove_self_assignment=`a retiré son assignation %s` -issues.change_title_at=`a changé le titre de %s en %s %s` +issues.change_title_at=`a modifié le titre de %s à %s %s` issues.delete_branch_at=`a supprimé la branche %s %s` issues.open_tab=%d Ouvert issues.close_tab=%d Fermé @@ -1124,6 +1128,8 @@ settings.basic_settings=Paramètres de base settings.mirror_settings=Réglages Miroir settings.sync_mirror=Synchroniser maintenant settings.mirror_sync_in_progress=La synchronisation est en cours. Revenez dans une minute. +settings.email_notifications.enable=Activer les notifications par e-mail +settings.email_notifications.disable=Désactiver les notifications par e-mail settings.site=Site Web settings.update_settings=Valider settings.advanced_settings=Paramètres avancés @@ -1405,6 +1411,8 @@ branch.deleted_by=Supprimée par %s branch.restore_success=La branche "%s" a été restaurée. branch.restore_failed=La restauration de la branche '%s' a échoué. branch.protected_deletion_failed=La branche '%s' est protégé. Il ne peut pas être supprimé. +branch.restore=Restaurer la branche '%s' +branch.download=Télécharger la branche '%s' topic.manage_topics=Gérer les sujets topic.done=Terminé diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index cfb1fcfef6..05a83a8b07 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -262,6 +262,7 @@ repos_none=Nincsen egyetlen saját tárolója sem delete_account=Fiókod törlése confirm_delete_account=Törlés megerősítése + [repo] owner=Tulajdonos repo_name=Tároló neve @@ -376,7 +377,6 @@ issues.remove_milestone_at=`eltávolítva a %s mérföldkőből %s` issues.deleted_milestone=`(törölt)` issues.self_assign_at=`önmagához rendelte %s` issues.add_assignee_at=`hozzárendelve %s által %s` -issues.change_title_at=`a cím változott %s-ról/ről %s-ra/re %s` issues.delete_branch_at=`letörölte a(z) %s ágat %s` issues.open_tab=%d Nyitva issues.close_tab=%d Lezárva diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 786f0a079e..b7690f97b7 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -312,6 +312,7 @@ repos_none=Anda tidak memiliki repositori apapun delete_account=Hapus Akun Anda confirm_delete_account=Konfirmasi Penghapusan + [repo] owner=Pemilik repo_name=Nama Repositori @@ -425,7 +426,6 @@ issues.remove_milestone_at=`telah menghapus ini dari %s milestone %s` issues.deleted_milestone=`(dihapus)` issues.self_assign_at=`menugaskan diri %s` issues.add_assignee_at=`telah ditugaskan oleh %s %s` -issues.change_title_at=`telah mengubah judul dari %s ke %s %s` issues.delete_branch_at=`telah dihapus cabang %s %s` issues.open_tab=%d Terbuka issues.close_tab=%d Tertutup diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index bb4fcc0e38..06044101de 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -464,6 +464,7 @@ confirm_delete_account=Conferma Eliminazione delete_account_title=Elimina account utente delete_account_desc=Sei sicuro di voler rimuovere questo account utente permanentemente? + [repo] owner=Proprietario repo_name=Nome Repository @@ -635,7 +636,6 @@ issues.remove_milestone_at=`rimossa dalle pietre miliari %s %s` issues.deleted_milestone='(rimosso)' issues.self_assign_at=`%s auto-assegnato` issues.add_assignee_at=`è stato assegnato da %s %s` -issues.change_title_at=' titolo modificato da %s a %s %s ' issues.delete_branch_at=`branch %s eliminato %s` issues.open_tab=%d Aperti issues.close_tab=%d Chiusi @@ -744,7 +744,6 @@ issues.due_date_modified=data di scadenza modificata da %s a %s %s issues.due_date_remove=rimossa la data di scadenza %s %s issues.due_date_overdue=Scaduto -pulls.desc=Attiva le richieste di merge e le recensioni di codice. pulls.new=Nuova Pull Request pulls.compare_changes=Nuova Pull Request pulls.compare_changes_desc=Selezione il branch su cui eseguire il merge e il branch da cui eseguire il pull. diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index c2f57a7f79..ca38757e1c 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -299,6 +299,7 @@ max_size_error=`は%s文字以下である必要があります。` email_error=`は有効なメールアドレスではありません。` url_error=`は有効なURLではありません。` include_error=`は文字列 '%s' を含んでいる必要があります。` +glob_pattern_error=`のglobパターンが不正です: %s.` unknown_error=不明なエラー: captcha_incorrect=CAPTCHAコードが正しくありません。 password_not_match=パスワードが一致しません。 @@ -317,6 +318,7 @@ enterred_invalid_repo_name=入力したリポジトリ名が間違っていま enterred_invalid_owner_name=新しいオーナーの名前が正しくありません。 enterred_invalid_password=入力されたパスワードが間違っています。 user_not_exist=指定されたユーザーは存在しません。 +team_not_exist=チームが存在していません。 last_org_owner='Owners'チームから最後のユーザーを削除することはできません。特定のチームには少なくとも一人のオーナーが必要です。 cannot_add_org_to_team=組織はチームメンバーとして追加できません。 @@ -556,11 +558,17 @@ confirm_delete_account=削除の続行 delete_account_title=ユーザーアカウントの削除 delete_account_desc=このユーザーアカウントを恒久的に削除してもよろしいですか? +email_notifications.enable=メール通知有効 +email_notifications.onmention=メンションのみメール通知 +email_notifications.disable=メール通知無効 +email_notifications.submit=メール設定を保存 + [repo] owner=オーナー repo_name=リポジトリ名 repo_name_helper=短く、覚えやすく、かつ他と重複しないキーワードを使用するのが良いリポジトリ名です。 visibility=公開/非公開 +visibility_description=オーナー、または権限を持つ組織のメンバーだけが、リポジトリを見ることができます。 visibility_helper=リポジトリをプライベートにする visibility_helper_forced=サイト管理者の設定により、新しいリポジトリは強制的にプライベートになります。 visibility_fork_helper=(この変更はすべてのフォークに適用されます) @@ -571,6 +579,8 @@ fork_visibility_helper=フォークしたリポジトリの公開/非公開は repo_desc=説明 repo_lang=言語 repo_gitignore_helper=.gitignoreテンプレートを選択してください。 +issue_labels=課題ラベル +issue_labels_helper=課題のラベルセットを選択 license=ライセンス license_helper=ライセンス ファイルを選択してください。 readme=README @@ -583,7 +593,7 @@ mirror_prune_desc=不要になった古いリモートトラッキング参照 mirror_interval=ミラー間隔 (有効な時間の単位は'h'、'm'、's')。 自動的な同期を無効にする場合は0。 mirror_interval_invalid=ミラー間隔が不正です。 mirror_address=クローンするURL -mirror_address_desc=必要な認証情報もURLに含めてください。 適宜URLエスケープも必要です。 +mirror_address_desc=必要な資格情報は「クローン時の認証」セクションに設定してください。 mirror_address_url_invalid=入力したURLは無効です。 URLの構成要素はすべて正しくエスケープする必要があります。 mirror_address_protocol_invalid=入力したURLは無効です。 ミラーできるのは、http(s):// または git:// の場所からだけです。 mirror_last_synced=前回の同期 @@ -695,6 +705,7 @@ editor.delete='%s' を削除 editor.commit_message_desc=詳細な説明を追加… editor.commit_directly_to_this_branch=ブランチ%sへ直接コミットする。 editor.create_new_branch=新しいブランチにコミットしてプルリクエストを作成する。 +editor.propose_file_change=ファイル修正を提案 editor.new_branch_name_desc=新しいブランチ名… editor.cancel=キャンセル editor.filename_cannot_be_empty=ファイル名は空にできません。 @@ -768,7 +779,7 @@ issues.self_assign_at=`が自身を担当者に設定 %s` issues.add_assignee_at=`を %[1]s が担当者に指名 %[2]s` issues.remove_assignee_at=`を %[1]s が担当から解除 %[2]s` issues.remove_self_assignment=`が自身を担当から解除 %s` -issues.change_title_at=`がタイトルを %[1]s から %[2]s に変更 %[3]s` +issues.change_title_at=`がタイトルを %[1]s から %[2]s に変更 %[3]s` issues.delete_branch_at=`がブランチ %[1]s を削除 %[2]s` issues.open_tab=%d件 オープン中 issues.close_tab=%d件 クローズ済 @@ -825,6 +836,10 @@ issues.create_comment=コメントする issues.closed_at=`がクローズ %[2]s` issues.reopened_at=`が再オープン %[2]s` issues.commit_ref_at=`がコミットでこの課題を参照 %[2]s` +issues.ref_issue_at=`がこの課題を参照 %[1]s` +issues.ref_pull_at=`がこのプルリクエストを参照 %[1]s` +issues.ref_issue_ext_at=`が %[1]s で、この課題を参照 %[2]s` +issues.ref_pull_ext_at=`が %[1]s で、このプルリクエストを参照 %[2]s` issues.poster=投稿者 issues.collaborator=共同作業者 issues.owner=オーナー @@ -944,7 +959,7 @@ issues.review.reviewers=レビューア issues.review.show_outdated=古い内容を表示 issues.review.hide_outdated=古い内容を隠す -pulls.desc=マージリクエストとコードレビューの有効化。 +pulls.desc=プルリクエストとコードレビューの有効化。 pulls.new=新しいプルリクエスト pulls.compare_changes=新規プルリクエスト pulls.compare_changes_desc=マージ先ブランチとプル元ブランチを選択。 @@ -963,12 +978,15 @@ pulls.tab_files=変更されたファイル pulls.reopen_to_merge=このプルリクエストをマージする場合は再オープンしてください。 pulls.cant_reopen_deleted_branch=このプルリクエストはブランチが削除されているため、再オープンできません。 pulls.merged=マージ済み -pulls.has_merged=プルリクエストをマージしました。 +pulls.merged_as=プルリクエストは %[2]s でマージされています。 +pulls.has_merged=プルリクエストはマージされています。 pulls.title_wip_desc=`誤ってマージされてしまわないよう、タイトルの頭に %s を付けてください。` pulls.cannot_merge_work_in_progress=このプルリクエストはWork in Progressとマークされています。 マージできる状態になったら、タイトルから %s を消してください。 pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。 pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。 pulls.is_checking=マージのコンフリクトを確認中です。 少し待ってからもう一度実行してください。 +pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。 +pulls.required_status_check_administrator=管理者であるため、このプルリクエストをマージすることは可能です。 pulls.blocked_by_approvals=このプルリクエストはまだ承認数が足りません。 %[1]d/%[2]dの承認を得ています。 pulls.can_auto_merge_desc=このプルリクエストは自動的にマージできます。 pulls.cannot_auto_merge_desc=コンフリクトが存在するため、このプルリクエストは自動的にマージできません。 @@ -976,6 +994,7 @@ pulls.cannot_auto_merge_helper=コンフリクトを解消するため手動で pulls.no_merge_desc=リポジトリのマージオプションがすべて無効になっているため、このプルリクエストをマージすることはできせん。 pulls.no_merge_helper=リポジトリ設定でマージを有効にするか、手動でマージしてください。 pulls.no_merge_wip=このプルリクエストはWork in Progressとマークされているため、マージすることはできません。 +pulls.no_merge_status_check=すべての必要なステータスチェックが成功していないため、このプルリクエストはマージできません。 pulls.merge_pull_request=プルリクエストをマージ pulls.rebase_merge_pull_request=リベースしてマージ pulls.rebase_merge_commit_pull_request=リベースしてマージ(--no-ff) @@ -1117,6 +1136,7 @@ settings.collaboration=共同作業者 settings.collaboration.admin=管理者 settings.collaboration.write=書き込み settings.collaboration.read=読み取り +settings.collaboration.owner=オーナー settings.collaboration.undefined=未定義 settings.hooks=Webhook settings.githooks=Gitフック @@ -1124,6 +1144,10 @@ settings.basic_settings=基本設定 settings.mirror_settings=ミラー設定 settings.sync_mirror=今すぐ同期 settings.mirror_sync_in_progress=ミラー同期を実行しています。 しばらくあとでまた確認してください。 +settings.email_notifications.enable=メール通知有効 +settings.email_notifications.onmention=メンションのみメール通知 +settings.email_notifications.disable=メール通知無効 +settings.email_notifications.submit=メール設定を保存 settings.site=Webサイト settings.update_settings=設定を更新 settings.advanced_settings=拡張設定 @@ -1194,6 +1218,11 @@ settings.collaborator_deletion_desc=共同作業者を削除すると、その settings.remove_collaborator_success=共同作業者を削除しました。 settings.search_user_placeholder=ユーザーを検索… settings.org_not_allowed_to_be_collaborator=組織を共同作業者として追加することはできません。 +settings.change_team_access_not_allowed=リポジトリに対するチームアクセス権の変更は、組織のオーナーのみに制限されています。 +settings.team_not_in_organization=チームがリポジトリと同じ組織に属していません。 +settings.add_team_duplicate=チームにはすでにこのリポジトリが登録されています。 +settings.add_team_success=チームがこのリポジトリにアクセスできるようになりました。 +settings.remove_team_success=チームのこのリポジトリへのアクセス権を削除しました。 settings.add_webhook=Webhookを追加 settings.add_webhook.invalid_channel_name=Webhookチャンネル名は、空または'#'1文字だけにはできません。 settings.hooks_desc=Webhookは、指定したGiteaイベントが発生したときにHTTP POSTリクエストを自動的にサーバーに送ります。 詳細はWebhookガイドへ。 @@ -1243,6 +1272,8 @@ settings.event_pull_request=プルリクエスト settings.event_pull_request_desc=プルリクエストのオープン・クローズ・再オープン・編集・承認・却下・レビューコメント・アサイン・アサイン解除・ラベル更新・ラベル消去・同期がされたとき。 settings.event_push=プッシュ settings.event_push_desc=Gitがリポジトリにプッシュを行ったとき。 +settings.branch_filter=ブランチ フィルター +settings.branch_filter_desc=プッシュ、ブランチ作成、ブランチ削除のイベントを通知するブランチを、globパターンで指定するホワイトリストです。 空か*のときは、すべてのブランチのイベントを通知します。 文法は github.com/gobwas/glob を参照してください。 例: master{master,release*} settings.event_repository=リポジトリ settings.event_repository_desc=リポジトリが作成または削除されたとき。 settings.active=有効 @@ -1293,6 +1324,9 @@ settings.protect_merge_whitelist_committers=マージ・ホワイトリストを settings.protect_merge_whitelist_committers_desc=ホワイトリストに登録したユーザーまたはチームにだけ、このブランチに対するプルリクエストのマージを許可します。 settings.protect_merge_whitelist_users=マージ・ホワイトリストに含むユーザー: settings.protect_merge_whitelist_teams=マージ・ホワイトリストに含むチーム: +settings.protect_check_status_contexts=ステータスチェックを有効にする +settings.protect_check_status_contexts_desc=マージの前にステータスチェックがパスしていることを必須にします。 このルールの対象ブランチへのマージが可能となるまでに、事前にパスしている必要があるステータスチェックを選んでください。 有効にする場合は、まずコミットを別のブランチにプッシュし、ステータスチェックがパスしたあと、このルールの対象ブランチにマージするか、直接プッシュするようにします。 条件が選択されていない場合は、最後のコミットが成功していることが必須条件となります。 +settings.protect_check_status_contexts_list=1週間の間にこのリポジトリにあったステータスチェック settings.protect_required_approvals=必要な承認数: settings.protect_required_approvals_desc=ホワイトリストに登録したユーザーやチームがレビューを行い、肯定的なレビューの数を満たしたプルリクエストしかマージできないようにします。 settings.protect_approvals_whitelist_users=ホワイトリストに含めるレビューア: @@ -1340,6 +1374,11 @@ diff.whitespace_ignore_at_eol=行末の空白の違いは無視 diff.stats_desc=%d個のファイルの変更%d行の追加%d行の削除 diff.bin=バイナリ diff.view_file=ファイルの表示 +diff.file_before=変更前 +diff.file_after=変更後 +diff.file_image_width=幅 +diff.file_image_height=高さ +diff.file_byte_size=サイズ diff.file_suppressed=ファイル差分が大きすぎるため省略します diff.too_many_files=変更されたファイルが多すぎるため、一部のファイルは表示されません diff.comment.placeholder=コメントを残す @@ -1390,7 +1429,7 @@ branch.search=ブランチを検索 branch.already_exists=ブランチ '%s' は既に存在します。 branch.delete_head=削除 branch.delete=ブランチ '%s' の削除 -branch.delete_html=ブランチを削除 +branch.delete_html=ブランチ削除 branch.delete_desc=ブランチの削除は恒久的で、元に戻すことはできません。 続行しますか? branch.deletion_success=ブランチ '%s' を削除しました。 branch.deletion_failed=ブランチ '%s' の削除に失敗しました。 @@ -1405,6 +1444,8 @@ branch.deleted_by=%s によって削除 branch.restore_success=ブランチ '%s' を復元しました。 branch.restore_failed=ブランチ '%s' の復元に失敗しました。 branch.protected_deletion_failed=ブランチ '%s' は保護されています。 削除できません。 +branch.restore=ブランチ '%s' の復元 +branch.download=ブランチ '%s' をダウンロード topic.manage_topics=トピックの管理 topic.done=完了 @@ -1440,6 +1481,8 @@ settings.options=組織 settings.full_name=フルネーム settings.website=Webサイト settings.location=場所 +settings.permission=許可 +settings.repoadminchangeteam=リポジトリ管理者はチームのアクセス権の追加・削除が可能 settings.visibility=表示 settings.visibility.public=公開 settings.visibility.limited=限定 (ログインしているユーザーにのみ表示) @@ -1686,6 +1729,7 @@ auths.tip.google_plus=OAuth2クライアント資格情報を、Google APIコン auths.tip.openid_connect=OpenID Connect DiscoveryのURL (/.well-known/openid-configuration) をエンドポイントとして指定してください auths.tip.twitter=https://dev.twitter.com/apps へアクセスしてアプリケーションを作成し、“Allow this application to be used to Sign in with Twitter”オプションを有効にしてください。 auths.tip.discord=新しいアプリケーションを https://discordapp.com/developers/applications/me から登録してください。 +auths.tip.gitea=新しいOAuthアプリケーションを登録してください。 利用ガイドは https://docs.gitea.io/en-us/oauth2-provider/ auths.edit=認証ソースの編集 auths.activated=認証ソースはアクティベート済み auths.new_success=新しい認証 '%s' を追加しました。 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index ecbee9efe0..fe2f0c7fc2 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -227,6 +227,7 @@ repos_none=어떤 레포지터리도 존재하지 않습니다. delete_account=계정 삭제 confirm_delete_account=삭제 승인 + [repo] owner=소유자 repo_name=저장소 이름 diff --git a/options/locale/locale_lt-LT.ini b/options/locale/locale_lt-LT.ini index 5fd9e31c5d..b0597cd1c3 100644 --- a/options/locale/locale_lt-LT.ini +++ b/options/locale/locale_lt-LT.ini @@ -159,6 +159,7 @@ delete_token=Pašalinti + [repo] repo_desc=Aprašymas repo_lang=Kalba diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 9cb62c7010..ff930d8da7 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -556,11 +556,17 @@ confirm_delete_account=Apstiprināt dzēšanu delete_account_title=Dzēst lietotāja kontu delete_account_desc=Vai tiešām vēlaties dzēst šo kontu? +email_notifications.enable=Iespējot e-pasta paziņojumus +email_notifications.onmention=Tikai, ja esmu pieminēts +email_notifications.disable=Nesūtīt paziņojumus +email_notifications.submit=Saglabāt sūtīšanas iestatījumus + [repo] owner=Īpašnieks repo_name=Repozitorija nosaukums repo_name_helper=Labi repozitorija nosaukumi ir īsi, unikāli un tādi, ko viegli atcerēties. visibility=Redzamība +visibility_description=Tikai organizācijas īpašnieks vai tās biedri, kam ir tiesības, varēs piekļūt šim repozitorijam. visibility_helper=Padarīt repozitoriju privātu visibility_helper_forced=Jūsu sistēmas administrators ir noteicis, ka visiem no jauna izveidotajiem repozitorijiem ir jābūt privātiem. visibility_fork_helper=(Šīs vērtības maiņa ietekmēs arī visus atdalītos repozitorijus.) @@ -583,7 +589,7 @@ mirror_prune_desc=Izdzēst visas ārējās atsauces, kas ārējā repozitorijā mirror_interval=Spoguļošanas biežums (atļautās laika vienības 'h', 'm' un 's'). Ievadiet 0, lai atslēgtu automātisko spoguļošanu. mirror_interval_invalid=Nekorekts spoguļošanas intervāls. mirror_address=Spoguļa adrese -mirror_address_desc=Iekļaujiet visus nepieciešamos autorizācijas akreditācijas datus URL. Tiem ir jābūt korekti pierakstītiem +mirror_address_desc=Pieslēgšanās rekvizītus norādiet autorizācijas sadaļā. mirror_address_url_invalid=Norādītais URL nav korekts. Norādiet visas URL daļas korekti. mirror_address_protocol_invalid=Norādītais URL nav korekts. Var spoguļot tikai no http(s):// vai git:// adresēm. mirror_last_synced=Pēdējo reizi sinhronizēts @@ -695,6 +701,7 @@ editor.delete=Dzēst '%s' editor.commit_message_desc=Pievienot neobligātu paplašinātu aprakstu… editor.commit_directly_to_this_branch=Apstiprināt revīzijas izmaiņas atzarā %s. editor.create_new_branch=Izveidot jaunu atzaru un izmaiņu pieprasījumu šai revīzijai. +editor.propose_file_change=Ieteikt faila izmaiņas editor.new_branch_name_desc=Jaunā atzara nosaukums… editor.cancel=Atcelt editor.filename_cannot_be_empty=Faila nosaukums nevar būt tukšs. @@ -768,7 +775,7 @@ issues.self_assign_at=`piešķīra sev %s` issues.add_assignee_at=`tika piešķirta problēma no %s %s` issues.remove_assignee_at=`tika noņemta problēma no %s %s` issues.remove_self_assignment=`noņēma sev problēmu %s` -issues.change_title_at=`nomainīts virsraksts no %s uz %s %s` +issues.change_title_at=`nomainīts nosaukums no %s uz %s %s` issues.delete_branch_at=`izdzēsa atzaru %s %s` issues.open_tab=%d atvērti issues.close_tab=%d aizvērti @@ -944,7 +951,7 @@ issues.review.reviewers=Recenzenti issues.review.show_outdated=Rādīt novecojušu issues.review.hide_outdated=Paslēpt novecojušu -pulls.desc=Izmaiņu pieprasījumi ļauj veikt koda pārskatīšanu un sapludināt izmaiņas. +pulls.desc=Iespējot izmaiņu pieprasījumus un koda recenzēšanu. pulls.new=Jauns izmaiņu pieprasījums pulls.compare_changes=Jauns izmaiņu pieprasījums pulls.compare_changes_desc=Izvēlieties atzaru, kurā sapludināt izmaiņas un atzaru, no kura tās saņemt. @@ -963,6 +970,7 @@ pulls.tab_files=Izmainītie faili pulls.reopen_to_merge=Atkārtoti atveriet izmaiņu pieprasījumu, lai veiktu sapludināšanu. pulls.cant_reopen_deleted_branch=Šo izmaiņu pieprasīju nevar atkāroti atvērt, jo atzars ir izdzēsts. pulls.merged=Sapludināts +pulls.merged_as=Izmaiņu pieprasījums tika sapludināts ar revīziju %[2]s. pulls.has_merged=Šis izmaiņu pieprasījums tika veiksmīgi sapludināts. pulls.title_wip_desc=`Sāciet virsrakstu ar %s, lai ierobežotu, ka izmaiņu pieprasījums netīšām tiktu sapludināts.` pulls.cannot_merge_work_in_progress=Šis izmaiņu pieprasījums ir atzīmēts, ka pie tā vēl notiek izstrāde. Noņemiet %s no virsraksta sākuma, kad tas ir pabeigts. @@ -1033,6 +1041,9 @@ wiki.save_page=Saglabāt lapu wiki.last_commit_info=%s laboja lapu %s wiki.edit_page_button=Labot wiki.new_page_button=Jauna lapa +wiki.file_revision=Labas revīzija +wiki.wiki_page_revisions=Vikivietnes lapas revīzijas +wiki.back_to_wiki=Atpakaļ uz vikivietnes lapu wiki.delete_page_button=Dzēst lapu wiki.delete_page_notice_1=Šī darbība izdzēsīs vikivietnes lapu '%s'. Vai turpināt? wiki.page_already_exists=Vikivietnes lapa ar šādu nosaukumu jau eksistē. @@ -1121,6 +1132,10 @@ settings.basic_settings=Pamatiestatījumi settings.mirror_settings=Spoguļa iestatījumi settings.sync_mirror=Sinhronizēt tagad settings.mirror_sync_in_progress=Notiek spoguļa sinhronizācija. Atjaunojiet lapu, lai pārbaudītu atkārtoti, pēc brīža. +settings.email_notifications.enable=Iespējot e-pasta paziņojumus +settings.email_notifications.onmention=Tikai, ja esmu pieminēts +settings.email_notifications.disable=Nesūtīt paziņojumus +settings.email_notifications.submit=Saglabāt sūtīšanas iestatījumus settings.site=Mājas lapa settings.update_settings=Mainīt iestatījumus settings.advanced_settings=Papildu iestatījumi @@ -1402,6 +1417,8 @@ branch.deleted_by=Izdzēsa %s branch.restore_success=Tika atjaunots atzars '%s'. branch.restore_failed=Neizdevās atjaunot atzaru '%s'. branch.protected_deletion_failed=Atzars '%s' ir aizsargāts. To nevar izdzēst. +branch.restore=Atjaunot atzaru '%s' +branch.download=Lejupielādēt atzaru '%s' topic.manage_topics=Pārvaldīt tēmas topic.done=Gatavs diff --git a/options/locale/locale_ml-IN.ini b/options/locale/locale_ml-IN.ini index 8efc2a4d50..f2571ce9c0 100644 --- a/options/locale/locale_ml-IN.ini +++ b/options/locale/locale_ml-IN.ini @@ -49,6 +49,7 @@ + [repo] diff --git a/options/locale/locale_nb-NO.ini b/options/locale/locale_nb-NO.ini index bfb274bf60..763bfec6a6 100644 --- a/options/locale/locale_nb-NO.ini +++ b/options/locale/locale_nb-NO.ini @@ -153,6 +153,7 @@ forgot_password=Glemt passord? + [repo] diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index f0a9d02981..92ea18dd91 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -511,6 +511,7 @@ confirm_delete_account=Bevestig verwijdering delete_account_title=Verwijder gebruikers account delete_account_desc=Weet je zeker dat je dit gebruikersaccount permanent wil verwijderen? + [repo] owner=Eigenaar repo_name=Naam van repository @@ -677,7 +678,6 @@ issues.remove_milestone_at=' %s is verwijderd uit de %s mijlpaal' issues.deleted_milestone=` (verwijderd)` issues.self_assign_at=`heeft dit %s aan zichzelf toegewezen` issues.add_assignee_at=`was toegekend door %s %s` -issues.change_title_at='titel aangepast van %s naar %s %s' issues.open_tab=%d Open issues.close_tab=%d gesloten issues.filter_label=Label diff --git a/options/locale/locale_nn-NO.ini b/options/locale/locale_nn-NO.ini index 8efc2a4d50..f2571ce9c0 100644 --- a/options/locale/locale_nn-NO.ini +++ b/options/locale/locale_nn-NO.ini @@ -49,6 +49,7 @@ + [repo] diff --git a/options/locale/locale_no-NO.ini b/options/locale/locale_no-NO.ini index 45624fa3be..79198d129b 100644 --- a/options/locale/locale_no-NO.ini +++ b/options/locale/locale_no-NO.ini @@ -87,6 +87,7 @@ smtp_host=SMTP-vert + [repo] diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 94bf3eaa7c..52cddf4735 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -39,6 +39,7 @@ u2f_unsupported_browser=Twoja przeglądarka nie wspiera kluczy bezpieczeństwa U u2f_error_1=Wystąpił nieznany błąd. Spróbuj ponownie. u2f_error_2=Upewnij się, że używasz właściwego, szyfrowanego (https://) adresu URL. u2f_error_3=Serwer nie mógł obsłużyć Twojego żądania. +u2f_error_4=Klucz bezpieczeństwa nie jest dozwolony dla tego żądania. Upewnij się, że klucz nie jest już zarejestrowany. u2f_error_5=Osiągnięto limit czasu zanim Twój klucz mógł zostać zweryfikowany. Odśwież, aby ponowić próbę. u2f_reload=Odśwież @@ -51,7 +52,7 @@ new_mirror=Nowa kopia lustrzana new_fork=Nowy fork repozytorium new_org=Nowa organizacja manage_org=Zarządzaj organizacjami -admin_panel=Administracja stron +admin_panel=Administracja witryny account_settings=Ustawienia konta settings=Ustawienia your_profile=Profil @@ -66,7 +67,7 @@ forks=Forki activities=Aktywności pull_requests=Oczekujące zmiany -issues=Problemy +issues=Zgłoszenia cancel=Anuluj @@ -78,16 +79,25 @@ loading=Ładowanie… install=Instalacja title=Wstępna konfiguracja docker_helper=Jeśli używasz Gitea za pomocą Docker'a, przeczytaj dokumentację przed wprowadzeniem jakichkolwiek zmian. +requite_db_desc=Gitea wymaga MySQL, PostgreSQL, MSSQL lub SQLite3. db_title=Ustawienia bazy danych db_type=Typ bazy danych host=Serwer user=Nazwa użytkownika password=Hasło db_name=Nazwa bazy danych +db_helper=Informacja dla użytkowników MySQL: użyj systemu przechowywania InnoDB, a jeżeli używasz "utf8mb4", wersja InnoDB powinna być wyższa niż 5.6. ssl_mode=SSL +charset=Zestaw znaków path=Ścieżka +sqlite_helper=Ścieżka pliku dla bazy danych SQLite3.
    Wpisz ścieżkę bezwzględną, jeśli Gitea jest uruchomiona jako usługa. +err_empty_db_path=Ścieżka do bazy danych SQLite3 nie może być pusta. no_admin_and_disable_registration=Nie możesz wyłączyć możliwości samodzielnej rejestracji kont użytkowników bez stworzenia konta administratora. err_empty_admin_password=Hasło administratora nie może być puste. +err_empty_admin_email=Pole adresu e-mail administratora nie może być puste. +err_admin_name_is_reserved=Nazwa użytkownika administratora jest nieprawidłowa, pseudonim jest zastrzeżony +err_admin_name_pattern_not_allowed=Nazwa użytkownika administratora jest nieprawidłowa, wzór nazwy użytkownika nie jest właściwy +err_admin_name_is_invalid=Nazwa użytkownika administratora jest nieprawidłowa general_title=Ustawienia ogólne app_name=Tytuł witryny @@ -179,7 +189,7 @@ issues.in_your_repos=W Twoich repozytoriach repos=Repozytoria users=Użytkownicy organizations=Organizacje -search=Wyszukiwanie +search=Szukaj code=Kod repo_no_results=Nie znaleziono pasujących repozytoriów. user_no_results=Nie znaleziono pasującego użytkowników. @@ -198,7 +208,10 @@ forgot_password_title=Zapomniałem hasła forgot_password=Zapomniałeś hasła? sign_up_now=Potrzebujesz konta? Zarejestruj się teraz. sign_up_successful=Konto zostało stworzone pomyślnie. -confirmation_mail_sent_prompt=Nowy email aktywacyjny został wysłany na adres %s. Sprawdź swoją skrzynkę odbiorczą w ciągu %s aby zakończyć proces rejestracji. +confirmation_mail_sent_prompt=Nowy email aktywacyjny został wysłany na adres %s. Sprawdź swoją skrzynkę odbiorczą w ciągu %s aby dokończyć proces rejestracji. +must_change_password=Zaktualizuj swoje hasło +allow_password_change=Użytkownik musi zmienić hasło (zalecane) +reset_password_mail_sent_prompt=E-mail potwierdzający został wysłany na adres %s. Sprawdź swoją skrzynkę odbiorczą w przeciągu %s, aby ukończyć proces odzyskiwania konta. active_your_account=Aktywuj swoje konto account_activated=Konto zostało aktywowane prohibit_login=Logowanie zabronione @@ -207,7 +220,11 @@ resent_limit_prompt=Zażądano już wiadomości aktywacyjnej. Zaczekaj 3 minuty has_unconfirmed_mail=Witaj, %s, masz niepotwierdzony adres e-mail (%s). Jeśli nie otrzymałeś wiadomości e-mail z potwierdzeniem lub potrzebujesz wysłać nową, kliknij na poniższy przycisk. resend_mail=Kliknij tutaj, aby wysłać e-mail aktywacyjny email_not_associate=Adres e-mail nie jest powiązany z żadnym kontem. +send_reset_mail=Wyślij e-mail odzyskujący +reset_password=Odzyskiwanie konta invalid_code=Twój kod potwierdzający jest nieprawidłowy lub wygasł. +reset_password_helper=Odzyskaj konto +reset_password_wrong_user=Zalogowano jako %s, ale link odzyskiwania konta jest przeznaczony dla %s password_too_short=Długość hasła nie może być mniejsza niż %d znaków. non_local_account=Konta niebędące lokalnymi nie mogą zmienić swojego hasła poprzez interfejs przeglądarkowy Gitea. verify=Potwierdź @@ -218,16 +235,32 @@ twofa_passcode_incorrect=Twój kod autoryzacji jest niepoprawny. Jeśli zapodzia twofa_scratch_token_incorrect=Twój kod jednorazowy jest niepoprawny. login_userpass=Zaloguj się login_openid=OpenID +oauth_signup_tab=Utwórz nowe konto +oauth_signup_title=Dodaj adres e-mail oraz hasło (umożliwia odzyskiwanie konta) +oauth_signup_submit=Utwórz konto +oauth_signin_tab=Połącz z istniejącym kontem +oauth_signin_title=Zaloguj się, aby autoryzować połączone konto +oauth_signin_submit=Połącz konto openid_connect_submit=Połącz openid_connect_title=Połącz z istniejącym kontem openid_connect_desc=Wybrany URI OpenID jest nieznany. Powiąż go z nowym kontem w tym miejscu. openid_register_title=Stwórz nowe konto openid_register_desc=Wybrany URI OpenID jest nieznany. Powiąż go z nowym kontem w tym miejscu. openid_signin_desc=Wpisz swój URI OpenID. Na przykład: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. +disable_forgot_password_mail=Odzyskiwanie konta jest wyłączone. Skontaktuj się z administratorem strony. +email_domain_blacklisted=Nie możesz zarejestrować się za pomocą tego adresu e-mail. +authorize_application=Autoryzuj aplikację +authroize_redirect_notice=Zostaniesz przekierowany(-a) do %s, jeśli autoryzujesz tę aplikację. +authorize_application_created_by=Ta aplikacja została stworzona przez %s. +authorize_application_description=Jeżeli udzielisz dostępu, aplikacja uzyska dostęp z zapisem do wszystkich informacji o Twoim koncie, wraz z prywatnymi repozytoriami i organizacjami. +authorize_title=Zezwolić "%s" na dostęp do Twojego konta? +authorization_failed=Autoryzacja nie powiodła się +authorization_failed_desc=Autoryzacja nie powiodła się ze względu na niewłaściwe żądanie. Skontaktuj się z osobami utrzymującymi aplikację, którą próbowano autoryzować. [mail] -activate_account=Prosimy aktywować swoje konto +activate_account=Aktywuj swoje konto activate_email=Potwierdź swój adres e-mail +reset_password=Odzyskaj swoje konto register_success=Rejestracja powiodła się register_notify=Witamy w Gitea @@ -272,6 +305,8 @@ password_not_match=Hasła nie są identyczne. username_been_taken=Ta nazwa użytkownika jest już zajęta. repo_name_been_taken=Nazwa repozytorium jest już zajęta. +visit_rate_limit=Zdalny punkt końcowy przesłał informację o ograniczeniu ilości żądań. +2fa_auth_required=Zdalny punkt końcowy zażądał weryfikacji dwuskładnikowej. org_name_been_taken=Nazwa organizacji jest już zajęta. team_name_been_taken=Nazwa zespołu jest już zajęta. team_no_units_error=Zezwól na dostęp do co najmniej jednej sekcji repozytorium. @@ -306,6 +341,8 @@ starred=Polubione repozytoria following=Obserwowani follow=Obserwuj unfollow=Przestań obserwować +heatmap.loading=Ładowanie mapy cieplnej… +user_bio=Biografia form.name_reserved=Nazwa użytkownika '%s' jest zarezerwowana. form.name_pattern_not_allowed=Wzór "%s" nie jest dozwolony dla nazwy użytkownika. @@ -334,6 +371,7 @@ password_username_disabled=Użytkownicy nielokalni nie mogą zmieniać swoich na full_name=Imię i nazwisko website=Strona location=Lokalizacja +update_theme=Zaktualizuj motyw update_profile=Zaktualizuj profil update_profile_success=Twój profil został zaktualizowany. change_username=Twój nick został zmieniony. @@ -341,6 +379,7 @@ change_username_prompt=Informacja: zmiana nazwy użytkownika zmienia również a continue=Kontynuuj cancel=Anuluj language=Język +ui=Motyw lookup_avatar_by_mail=Znajdź awatar po adresie e-mail federated_avatar_lookup=Wyszukiwanie zewnętrznych awatarów @@ -349,6 +388,7 @@ choose_new_avatar=Wybierz nowy avatar update_avatar=Aktualizuj awatar delete_current_avatar=Usuń obecny Avatar uploaded_avatar_not_a_image=Załadowany plik nie jest obrazem. +uploaded_avatar_is_too_big=Przesłany plik przekroczył maksymalny rozmiar. update_avatar_success=Twój awatar został zmieniony. change_password=Aktualizuj hasło @@ -361,14 +401,18 @@ password_change_disabled=Konta niebędące lokalnymi nie mogą zmienić swojego emails=Adresy e-mail manage_emails=Zarządzaj adresami e-mail +manage_themes=Wybierz motyw domyślny manage_openid=Zarządzanie adresami OpenID email_desc=Twój podstawowy adres e-mail będzie używany do powiadomień i innych działań. +theme_desc=Będzie to domyślny motyw na całej stronie. primary=Podstawowy primary_email=Ustaw jako podstawowy delete_email=Usuń email_deletion=Usuń adres email email_deletion_desc=Adres e-mail i powiązane informacje zostaną usunięte z Twojego konta. Commity za pomocą tego adresu e-mail pozostaną niezmienione. Kontynuować? email_deletion_success=Adres e-mail został usunięty. +theme_update_success=Twój motyw został zaktualizowany. +theme_update_error=Wybrany motyw nie istnieje. openid_deletion=Usuń adres OpenID openid_deletion_desc=Usunięcie tego adresu OpenID z Twojego konta uniemożliwi Ci logowanie się za jego pomocą. Kontynuować? openid_deletion_success=Adres OpenID został usunięty. @@ -392,6 +436,7 @@ ssh_helper=Potrzebujesz pomocy? Sprawdź na GitHubie przewodnik gpg_helper=Potrzebujesz pomocy? Przeczytaj na GitHubie poradnik na temat GPG. add_new_key=Dodaj klucz SSH add_new_gpg_key=Dodaj klucz GPG +ssh_key_been_used=Ten klucz SSH został już dodany do tego serwera. ssh_key_name_used=Klucz SSH z tą nazwą został już dodany do Twojego konta. gpg_key_id_used=Publiczny klucz GPG z tym ID już istnieje. gpg_no_key_email_found=Tego klucza GPG nie można używać z żadnym adresem e-mail powiązanym z Twoim kontem. @@ -438,7 +483,37 @@ access_token_deletion=Usuń token dostępu access_token_deletion_desc=Usunięcie tokenu unieważni dostęp do Twojego konta aplikacjom, które z niego korzystały. Kontynuować? delete_token_success=Token został usunięty. Aplikacje używające go nie będą miały już dostępu do Twojego konta. +manage_oauth2_applications=Zarządzaj aplikacjami OAuth2 +edit_oauth2_application=Edytuj aplikację OAuth2 +oauth2_applications_desc=Aplikacje OAuth2 pozwalają Twojej aplikacji zewnętrznej na bezpiecznie uwierzytelnianie użytkowników w tej instancji Gitea. +remove_oauth2_application=Usuń aplikację OAuth2 +remove_oauth2_application_desc=Usuwając aplikację OAuth2 odwołasz jej dostęp do wszystkich podpisanych tokenów dostępu. Kontynuować? +remove_oauth2_application_success=Aplikacja została usunięta. +create_oauth2_application=Stwórz nową aplikację OAuth2 +create_oauth2_application_button=Stwórz aplikację +create_oauth2_application_success=Udało Ci się stworzyć nową aplikację OAuth2. +update_oauth2_application_success=Udało Ci się zaktualizować aplikację OAuth2. +oauth2_application_name=Nazwa aplikacji +oauth2_select_type=Który typ aplikacji jest dla niej właściwy? +oauth2_type_web=Webowa (np. Node.JS, Tomcat, Go) +oauth2_type_native=Natywna (np. mobilna, pulpitowa, przeglądarkowa) +oauth2_redirect_uri=URI przekierowania +save_application=Zapisz +oauth2_client_id=ID klienta +oauth2_client_secret=Sekret klienta +oauth2_regenerate_secret=Ponownie wygeneruj sekretny klucz +oauth2_regenerate_secret_hint=Utraciłeś sekretny klucz? +oauth2_client_secret_hint=Sekret nie będzie więcej widoczny po opuszczeniu tej strony. Zapisz swój sekret. +oauth2_application_edit=Zmień +oauth2_application_create_description=Aplikacje OAuth2 umożliwiają Twojej aplikacji dostęp do kont użytkowników na tej instancji. +oauth2_application_remove_description=Usunięcie aplikacji OAuth2 uniemożliwi jej dostępu do autoryzowanych kont użytkowników na tej instancji. Kontynuować? +authorized_oauth2_applications=Autoryzowane aplikacje OAuth2 +authorized_oauth2_applications_description=Udzielono dostępu do swojego konta Gitea następującym aplikacjom. Odwołaj dostęp dla aplikacji, których już nie używasz. +revoke_key=Odwołaj +revoke_oauth2_grant=Odwołaj dostęp +revoke_oauth2_grant_description=Odwołanie dostępu dla tej aplikacji uniemożliwi jej korzystanie z Twoich danych. Czy jesteś pewny(-a)? +revoke_oauth2_grant_success=Pomyślnie odwołano dostępu. twofa_desc=Weryfikacja dwuskładnikowa zwiększa bezpieczeństwo Twojego konta. twofa_is_enrolled=Twoje konto ma obecnie włączoną autoryzację dwuetapową. @@ -457,6 +532,7 @@ then_enter_passcode=Oraz wpisz kod dostępu pokazany w aplikacji: passcode_invalid=Kod dostępu jest nieprawidłowy. Spróbuj ponownie. twofa_enrolled=Na Twoim koncie została uruchomiona weryfikacja dwuetapowa. Przechowuj swój kod jednorazowy (%s) w bezpiecznym miejscu, gdyż jest widoczny tylko raz! +u2f_desc=Klucze bezpieczeństwa to urządzenia zawierające klucze kryptograficzne. Mogą być używane do weryfikacji dwuskładnikowej. Klucz bezpieczeństwa musi być wspierany przez standard FIDO U2F. u2f_require_twofa=Twoje konto musi mieć włączoną autoryzację dwuetapową, żeby korzystać z kluczy bezpieczeństwa. u2f_register_key=Dodaj klucz bezpieczeństwa u2f_nickname=Nazwa @@ -480,16 +556,22 @@ confirm_delete_account=Potwierdź usunięcie delete_account_title=Usuń swoje konto delete_account_desc=Czy na pewno chcesz permanentnie usunąć to konto użytkownika? +email_notifications.enable=Włącz powiadomienia e-mail +email_notifications.onmention=Wyślij wiadomość e-mail wyłącznie przy wzmiankach +email_notifications.disable=Wyłącz powiadomienia e-mail +email_notifications.submit=Ustaw preferencje wiadomości e-mail + [repo] owner=Właściciel repo_name=Nazwa repozytorium repo_name_helper=Dobra nazwa repozytorium jest utworzona z krótkich, łatwych do zapamiętania i unikalnych słów kluczowych. visibility=Widoczność +visibility_description=Tylko właściciel lub członkowie organizacji, jeśli mają odpowiednie uprawnienia, będą mogli to zobaczyć. visibility_helper=Przekształć repozytorium na prywatne visibility_helper_forced=Administrator strony wymaga, aby nowe repozytoria były prywatne. visibility_fork_helper=(Zmiana tej wartości wpłynie na wszystkie forki.) clone_helper=Potrzebujesz pomocy z klonowaniem? Odwiedź pomoc. -fork_repo=Sforkowane +fork_repo=Forkuj repozytorium fork_from=Forkuj z fork_visibility_helper=Widoczność sforkowanego repozytorium nie może być zmieniona. repo_desc=Opis @@ -504,8 +586,12 @@ create_repo=Utwórz repozytorium default_branch=Domyślna gałąź mirror_prune=Wyczyść mirror_prune_desc=Usuń przestarzałe odwołania do zdalnych śledzeń +mirror_interval=Przedział czasowy dla tworzenia kopii lustrzanej (prawidłowe jednostki czasu to 'h' (godziny), 'm', 's'). 0, aby wyłączyć automatyczną synchronizację. mirror_interval_invalid=Interwał lustrzanej kopii jest niepoprawny. mirror_address=Sklonuj z adresu URL +mirror_address_desc=Wpisz wymagane dane uwierzytelnienia w sekcji Autoryzacja klonowania. +mirror_address_url_invalid=Podany adres URL jest niewłaściwy. Musisz poprawnie escape'ować wszystkie jego elementy. +mirror_address_protocol_invalid=Podany adres URL jest niewłaściwy. Tylko z http(s):// lub git:// można utworzyć kopie lustrzane. mirror_last_synced=Ostatnio zsynchronizowano watchers=Obserwujący stargazers=Polubienia @@ -513,14 +599,24 @@ forks=Forki pick_reaction=Wybierz swoją reakcję reactions_more=i %d więcej +archive.title=To repozytorium jest zarchiwizowane. Możesz wyświetlać pliki i je sklonować, ale nie możesz do niego przepychać zmian lub otwierać zgłoszeń/Pull Requestów. +archive.issue.nocomment=To repozytorium jest zarchiwizowane. Nie możesz komentować zgłoszeń. +archive.pull.nocomment=To repozytorium jest zarchiwizowane. Nie możesz komentować Pull Requestów. form.reach_limit_of_creation=Osiągnąłeś limit %d repozytoriów. form.name_reserved=Nazwa repozytorium „%s” jest zarezerwowana. form.name_pattern_not_allowed=Wzór "%s" nie jest dozwolony w nazwie repozytorium. -need_auth=Klonowanie autoryzacji +need_auth=Autoryzacja klonowania migrate_type=Typ migracji migrate_type_helper=To repozytorium będzie kopią lustrzaną +migrate_items=Składniki migracji +migrate_items_wiki=Wiki +migrate_items_milestones=Kamienie milowe +migrate_items_labels=Etykiety +migrate_items_issues=Zgłoszenia +migrate_items_pullrequests=Pull Requesty +migrate_items_releases=Wydania migrate_repo=Przenieś repozytorium migrate.clone_address=Migruj/klonuj z adresu URL migrate.clone_address_desc=Adres HTTP(S) lub "klona" Gita istniejącego repozytorium @@ -528,18 +624,23 @@ migrate.clone_local_path=lub ścieżka lokalnego serwera migrate.permission_denied=Nie możesz importować lokalnych repozytoriów. migrate.invalid_local_path=Lokalna ścieżka jest niepoprawna - nie istnieje lub nie jest katalogiem. migrate.failed=Migracja nie powiodła się: %v +migrate.lfs_mirror_unsupported=Tworzenie kopii lustrzanych elementów LFS nie jest wspierane - użyj zamiast tego 'git lfs fetch --all' i 'git lfs push --all'. +migrate.migrate_items_options=Przy migracji z GitHub'a, wpisz nazwę użytkownika, a opcje migracji zostaną wyświetlone. +migrated_from=Zmigrowane z %[2]s +migrated_from_fake=Zmigrowane z %[1]s mirror_from=kopia lustrzana -forked_from=sklonowany z -fork_from_self=Nie możesz forkować swojego własnego repozytorium. +forked_from=sforkowany z +fork_from_self=Nie możesz sforkować swojego własnego repozytorium. +fork_guest_user=Zaloguj się, aby sforkować to repozytorium. copy_link=Kopiuj copy_link_success=Link został skopiowany copy_link_error=Naciśnij klawisze ⌘-C lub Ctrl-C, aby skopiować copied=Skopiowano unwatch=Przestań obserwować watch=Obserwuj -unstar=Usuń gwiazdkę -star=Gwiazdka +unstar=Usuń polubienie +star=Polub fork=Forkuj download_archive=Pobierz repozytorium @@ -548,15 +649,17 @@ quick_guide=Skrócona instrukcja clone_this_repo=Klonuj repozytorium create_new_repo_command=Tworzenie nowego repozytorium z linii poleceń push_exist_repo=Wypychanie istniejącego repozytorium z linii poleceń +empty_message=To repozytorium nie zawiera żadnej zawartości. code=Kod +code.desc=Uzyskaj dostęp do kodu źródłowego, plików, commitów i gałęzi. branch=Gałąź tree=Drzewo filter_branch_and_tag=Filtruj gałąź lub tag branches=Gałęzie tags=Tagi -issues=Problemy -pulls=Pull Requests +issues=Zgłoszenia +pulls=Oczekujące zmiany labels=Etykiety milestones=Kamienie milowe commits=Commity @@ -566,40 +669,64 @@ file_raw=Czysty file_history=Historia file_view_raw=Zobacz czysty file_permalink=Bezpośredni odnośnik +file_too_large=Ten plik jest zbyt duży, aby go wyświetlić. video_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "video". +audio_not_supported_in_browser=Twoja przeglądarka nie obsługuje znacznika HTML5 "audio". stored_lfs=Przechowane za pomocą Git LFS commit_graph=Wykres commitów +blame=Wina +normal_view=Zwykły widok editor.new_file=Nowy plik editor.upload_file=Wyślik plik editor.edit_file=Edytuj plik editor.preview_changes=Podgląd zmian +editor.cannot_edit_lfs_files=Pliki LFS nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.cannot_edit_non_text_files=Pliki binarne nie mogą być edytowane poprzez interfejs przeglądarkowy. editor.edit_this_file=Edytuj plik +editor.must_be_on_a_branch=Musisz znajdować się na gałęzi, aby nanieść lub zaproponować zmiany tego pliku. +editor.fork_before_edit=Musisz sforkować to repozytorium, aby nanieść lub zaproponować zmiany tego pliku. editor.delete_this_file=Usuń plik +editor.must_have_write_access=Musisz mieć uprawnienia do zapisu, aby nanieść lub zaproponować zmiany tego pliku. editor.file_delete_success=Plik %s został usunięty. editor.name_your_file=Nazwij plik… +editor.filename_help=Utwórz katalog, poprzez wpisanie jego nazwy i dodanie ukośnika ('/'). Usuń katalog, wciskając klawisz Backspace na początku pola tekstowego. editor.or=lub editor.cancel_lower=Anuluj editor.commit_changes=Zatwierdź zmiany -editor.add=Dodaj '%s' +editor.add_tmpl=Dodanie '' +editor.add=Dodanie '%s' editor.update=Zaktualizuj '%s' editor.delete=Usuń '%s' editor.commit_message_desc=Dodaj dodatkowy rozszerzony opis… editor.commit_directly_to_this_branch=Zmieniaj bezpośrednio gałąź %s. -editor.create_new_branch=Stwórz nową gałąź dla tego commita i rozpocznij pull request. +editor.create_new_branch=Stwórz nową gałąź dla tego commita i rozpocznij Pull Request. +editor.propose_file_change=Zaproponuj zmiany w pliku editor.new_branch_name_desc=Nazwa nowej gałęzi… editor.cancel=Anuluj editor.filename_cannot_be_empty=Nazwa pliku nie może być pusta. +editor.filename_is_invalid=Plik ma nieprawidłową nazwę: '%s'. +editor.branch_does_not_exist=Gałąź '%s' nie istnieje w tym repozytorium. editor.branch_already_exists=Gałąź '%s' już istnieje w tym repozytorium. +editor.directory_is_a_file=Nazwa katalogu '%s' jest już używana jako nazwa pliku w tym repozytorium. +editor.file_is_a_symlink='%s' jest dowiązaniem symbolicznym. Dowiązania symboliczne nie mogą być edytowane w edytorze przeglądarkowym +editor.filename_is_a_directory=Nazwa pliku '%s' jest już używana jako nazwa folderu w tym repozytorium. +editor.file_editing_no_longer_exists=Edytowany plik '%s' już nie istnieje w tym repozytorium. +editor.file_deleting_no_longer_exists=Usuwany plik '%s' już nie istnieje w tym repozytorium. +editor.file_changed_while_editing=Zawartość pliku zmieniła się, odkąd rozpoczęto jego edycję. Kliknij tutaj, aby zobaczyć zmiany, lub ponownie Zatwierdź zmiany, aby je nadpisać. +editor.file_already_exists=Plik o nazwie '%s' już istnieje w tym repozytorium. editor.no_changes_to_show=Brak zmian do pokazania. editor.fail_to_update_file=Tworzenie/aktualizacja pliku '%s' nie powiodła się z błędem: %v editor.add_subdir=Dodaj katalog… editor.unable_to_upload_files=Wysyłanie plików do '%s' nie powiodło się z błędem: %v editor.upload_files_to_dir=Prześlij pliki do '%s' +editor.cannot_commit_to_protected_branch=Nie można commitować do chronionej gałęzi '%s'. +commits.desc=Przeglądaj historię zmian kodu źródłowego. commits.commits=Commity +commits.no_commits=Brak pokrewnych commitów. '%s' i '%s' mają zupełnie odrębne historie. commits.search=Przeszukaj commity… +commits.search.tooltip=Możesz prefiksować słowa kluczowe za pomocą "author:", "committer:", "after:", lub "before:", np. "revert author:Alicja before:2019-04-01". commits.find=Szukaj commits.search_all=Wszystkie gałęzie commits.author=Autor @@ -610,37 +737,43 @@ commits.newer=Nowsze commits.signed_by=Podpisane przez commits.gpg_key_id=ID klucza GPG +ext_issues=Zgłoszenia zewn. +ext_issues.desc=Link do zewnętrznego systemu śledzenia zgłoszeń. -issues.new=Nowy problem +issues.desc=Organizuj zgłoszenia o błędach, zadania i cele. +issues.new=Nowe zgłoszenie +issues.new.title_empty=Tytuł nie może być pusty issues.new.labels=Etykiety issues.new.no_label=Brak etykiety issues.new.clear_labels=Wyczyść etykiety issues.new.milestone=Kamień milowy issues.new.no_milestone=Brak kamienia milowego issues.new.clear_milestone=Wyczyść kamień milowy -issues.new.open_milestone=Otwórz kamienie milowe +issues.new.open_milestone=Otwarte kamienie milowe issues.new.closed_milestone=Zamknięte kamienie milowe issues.new.assignees=Przypisani issues.new.clear_assignees=Usuń przypisanych issues.new.no_assignees=Brak przypisanych issues.no_ref=Nie określono gałęzi/etykiety -issues.create=Utwórz problem +issues.create=Utwórz zgłoszenie issues.new_label=Nowa etykieta issues.new_label_placeholder=Nazwa etykiety issues.new_label_desc_placeholder=Opis issues.create_label=Utwórz etykietę issues.label_templates.title=Załaduj wstępnie przygotowany zestaw etykiet +issues.label_templates.info=Nie istnieją żadne etykiety. Stwórz etykietę poprzez "Nowa etykieta", lub użyj predefiniowanego zestawu etykiet: issues.label_templates.helper=Wybierz zestaw etykiet -issues.label_templates.use=Użyj zbioru etykiet +issues.label_templates.use=Użyj zestawu etykiet issues.label_templates.fail_to_load_file=Ładowanie pliku szablonu etykiety '%s' nie powiodło się: %v -issues.add_milestone_at=`dodano do %s kamień milowy %s` -issues.change_milestone_at=`zmodyfikował kamień milowy z %s na %s %s` -issues.remove_milestone_at=`usunięto z %s kamień milowy %s` +issues.add_label_at=dodano etykietę
    %s
    %s +issues.remove_label_at=usunięto etykietę
    %s
    %s +issues.add_milestone_at=`dodaje to do kamienia milowego %s %s` +issues.change_milestone_at=`zmienia kamień milowy z %s na %s %s` +issues.remove_milestone_at=`usuwa to z kamienia milowego %s %s` issues.deleted_milestone=`(usunięto)` -issues.self_assign_at=`samo przypisany do %s` -issues.add_assignee_at=`został przypisany przez %s %s` -issues.change_title_at=`zmieniono tytuł z %s na %s %s` -issues.delete_branch_at=`usunął gałąź %s %s` +issues.remove_assignee_at=`usunięto przypisanie przez %s %s` +issues.change_title_at=`zmieniono tytuł z %s na %s %s` +issues.delete_branch_at=`usuwa gałąź %s %s` issues.open_tab=Otwarte %d issues.close_tab=Zamknięte %d issues.filter_label=Etykieta @@ -650,7 +783,7 @@ issues.filter_milestone_no_select=Wszystkie kamienie milowe issues.filter_assignee=Przypisany issues.filter_assginee_no_select=Wszyscy przypisani issues.filter_type=Typ -issues.filter_type.all_issues=Wszystkie problemy +issues.filter_type.all_issues=Wszystkie zgłoszenia issues.filter_type.assigned_to_you=Przypisane do Ciebie issues.filter_type.created_by_you=Utworzone przez Ciebie issues.filter_type.mentioning_you=Wspominające Ciebie @@ -660,12 +793,14 @@ issues.filter_sort.oldest=Najstarsze issues.filter_sort.recentupdate=Ostatnio aktualizowane issues.filter_sort.leastupdate=Najdawniej aktualizowane issues.filter_sort.mostcomment=Najczęściej komentowane -issues.filter_sort.leastcomment=Najmniej komentowane +issues.filter_sort.leastcomment=Najrzadziej komentowane +issues.filter_sort.nearduedate=Najbliższa data realizacji +issues.filter_sort.farduedate=Najdalsza data realizacji issues.filter_sort.moststars=Najwięcej gwiazdek issues.filter_sort.feweststars=Najmniej gwiazdek issues.filter_sort.mostforks=Najwięcej forków issues.filter_sort.fewestforks=Najmniej forków -issues.action_open=Otwarte +issues.action_open=Otwórz issues.action_close=Zamknij issues.action_label=Etykieta issues.action_milestone=Kamień milowy @@ -673,23 +808,27 @@ issues.action_milestone_no_select=Brak kamieni milowych issues.action_assignee=Przypisany issues.action_assignee_no_select=Brak przypisania issues.opened_by=otworzone %[1]s przez %[3]s +pulls.merged_by=scalono %[1]s przez %[3]s +pulls.merged_by_fake=scalono %[1]s przez %[2]s +issues.closed_by=zamknięte %[1]s przez %[3]s issues.opened_by_fake=otworzone %[1]s przez %[2]s +issues.closed_by_fake=zamknięte %[1]s przez %[2]s issues.previous=Poprzedni issues.next=Następny issues.open_title=Otwarty issues.closed_title=Zamknięty issues.num_comments=%d komentarzy -issues.commented_at=`skomentował %s` +issues.commented_at=`skomentował(-a) %s` issues.delete_comment_confirm=Czy na pewno chcesz usunąć ten komentarz? issues.no_content=Nie ma jeszcze treści. issues.close_issue=Zamknij issues.close_comment_issue=Skomentuj i zamknij issues.reopen_issue=Otwórz ponownie issues.reopen_comment_issue=Skomentuj i otwórz ponownie -issues.create_comment=Komentuj -issues.closed_at=`zamyka %[2]s` -issues.reopened_at=`otwiera ponownie %[2]s` -issues.commit_ref_at=`odnosi się do tej sprawy w zmianie %[2]s` +issues.create_comment=Skomentuj +issues.closed_at=`zamknął(-ęła) %[2]s` +issues.reopened_at=`otworzył(-a) ponownie %[2]s` +issues.commit_ref_at=`wspomniał(-a) to zgłoszenie z commita %[2]s` issues.poster=Autor issues.collaborator=Współpracownik issues.owner=Właściciel @@ -700,61 +839,155 @@ issues.save=Zapisz issues.label_title=Nazwa etykiety issues.label_description=Opis etykiety issues.label_color=Kolor etykiety -issues.label_count=Etykiety %d -issues.label_open_issues=Otwarte problemy %d +issues.label_count=%d Etykiety +issues.label_open_issues=Otwarte zgłoszenia %d issues.label_edit=Edytuj issues.label_delete=Usuń issues.label_modify=Edytuj etykietę issues.label_deletion=Usuń etykietę -issues.label_deletion_desc=Skasowanie etykiety usunie ją ze wszystkich spraw. Kontynuować? +issues.label_deletion_desc=Skasowanie etykiety usunie ją ze wszystkich zgłoszeń. Kontynuować? issues.label_deletion_success=Etykieta została usunięta. issues.label.filter_sort.alphabetically=Alfabetycznie issues.label.filter_sort.reverse_alphabetically=Alfabetycznie odwrotnie issues.label.filter_sort.by_size=Rozmiar issues.label.filter_sort.reverse_by_size=Rozmiar odwrotnie -issues.num_participants=%d uczestników -issues.attachment.open_tab=`Kliknij, aby zobaczyć „%s” w nowej karcie` -issues.attachment.download=`Kliknij, aby pobrać „%s”` +issues.num_participants=Uczestnicy %d +issues.attachment.open_tab=`Kliknij, aby zobaczyć "%s" w nowej karcie` +issues.attachment.download=`Kliknij, aby pobrać "%s"` issues.subscribe=Subskrybuj issues.unsubscribe=Anuluj subskrypcję +issues.lock=Zablokuj konwersację +issues.unlock=Odblokuj konwersację +issues.lock.unknown_reason=Nie można zablokować zagadnienia bez żadnego powodu. +issues.lock_duplicate=Zagadnienie nie może być zablokowane ponownie. +issues.unlock_error=Nie można odblokować zagadnienia, które nie jest zablokowane. +issues.lock_with_reason=zablokowano jako %s i ograniczono konwersację do współtwórców %s +issues.lock_no_reason=zablokowano i ograniczono konwersację do współtwórców %s +issues.unlock_comment=odblokowano tę konwersację %s +issues.lock_confirm=Zablokuj +issues.unlock_confirm=Odblokuj +issues.lock.notice_1=- Inni użytkownicy nie mogą dodawać nowych komentarzy do tego zagadnienia. +issues.lock.notice_2=- Ty i inni współtwórcy z dostępem do tego repozytorium możecie dalej pozostawiać komentarze dla innych. +issues.lock.notice_3=- Możesz zawsze odblokować to zagadnienie w przyszłości. +issues.unlock.notice_1=- Wszyscy będą mogli ponownie umieszczać komentarze w tym zagadnieniu. +issues.unlock.notice_2=- Możesz zawsze ponownie zablokować to zagadnienie w przyszłości. +issues.lock.reason=Powód blokady +issues.lock.title=Zablokuj konwersację w tym zgłoszeniu. +issues.unlock.title=Odblokuj konwersację w tym zgłoszeniu. +issues.comment_on_locked=Nie możesz umieszczać komentarzy pod zablokowanym zgłoszeniem. issues.tracker=Śledzenie czasu issues.start_tracking_short=Rozpocznij issues.start_tracking=Rozpocznij śledzenie czasu -issues.start_tracking_history=`rozpoczął pracę nad %s` -issues.tracking_already_started=`Już śledzisz czas pracy nad tą sprawą!` +issues.start_tracking_history=`rozpoczął(-ęła) pracę nad %s` +issues.tracker_auto_close=Licznik czasu zostanie automatycznie zatrzymany w momencie zamknięcia tego zgłoszenia +issues.tracking_already_started=`Już śledzisz czas pracy nad tym zgłoszeniem!` issues.stop_tracking=Zatrzymaj -issues.stop_tracking_history=`zakończył pracę nad %s` +issues.stop_tracking_history=`zakończył(-a) pracę nad %s` issues.add_time=Dodaj czas ręcznie issues.add_time_short=Dodaj czas issues.add_time_cancel=Anuluj -issues.add_time_history=`dodano spędzony czas %s` +issues.add_time_history=`dodał(-a) spędzony czas %s` issues.add_time_hours=Godziny issues.add_time_minutes=Minuty issues.add_time_sum_to_small=Czas nie został wprowadzony. issues.cancel_tracking=Anuluj -issues.cancel_tracking_history=`anulowanie śledzenie czasu %s` +issues.cancel_tracking_history=`anulował(-a) śledzenie czasu %s` issues.time_spent_total=Całkowity spędzony czas issues.time_spent_from_all_authors=`Całkowity spędzony czas: %s` +issues.due_date=Termin realizacji +issues.invalid_due_date_format=Format terminu realizacji musi mieć wartość 'rrrr-mm-dd'. +issues.error_modifying_due_date=Nie udało się zmodyfikować terminu realizacji. +issues.error_removing_due_date=Nie udało się usunąć terminu realizacji. issues.due_date_form=yyyy-mm-dd +issues.due_date_form_add=Dodaj termin realizacji +issues.due_date_form_edit=Edytuj +issues.due_date_form_remove=Usuń +issues.due_date_not_writer=Potrzebujesz uprawnień zapisu w tym repozytorium, aby zaktualizować termin realizacji zgłoszenia. +issues.due_date_not_set=Brak ustawionego terminu realizacji. +issues.due_date_added=dodaje termin realizacji %s %s +issues.due_date_modified=zmienia termin realizacji na %s z %s %s +issues.due_date_remove=usuwa termin realizacji %s %s +issues.due_date_overdue=Zaległe +issues.due_date_invalid=Data realizacji jest niewłaściwa lub spoza zakresu. Użyj formatu 'yyyy-mm-dd'. issues.dependency.title=Zależności +issues.dependency.issue_no_dependencies=To zgłoszenie nie ma w tej chwili żadnych zależności. +issues.dependency.pr_no_dependencies=Ten Pull Request nie zawiera w tej chwili żadnych zależności. +issues.dependency.add=Dodaj zależność… issues.dependency.cancel=Anuluj issues.dependency.remove=Usuń +issues.dependency.remove_info=Usuń tę zależność +issues.dependency.added_dependency=`%[2]s dodaje nową zależność %[3]s` +issues.dependency.removed_dependency=`%[2]s usuwa zależność %[3]s` +issues.dependency.issue_closing_blockedby=Zamknięcie tego Pull Requesta jest blokowane przez następujące zgłoszenia +issues.dependency.pr_closing_blockedby=Zamknięcie tego zgłoszenia jest blokowane przez następujące zgłoszenia +issues.dependency.issue_close_blocks=To zgłoszenie blokuje zamknięcie następujących zgłoszeń +issues.dependency.pr_close_blocks=Ten Pull Request blokuje zamknięcie następujących zgłoszeń +issues.dependency.issue_close_blocked=Musisz zamknąć wszystkie zgłoszenia blokujące to zgłoszenie zanim je zamkniesz. +issues.dependency.pr_close_blocked=Musisz zamknąć wszystkie zgłoszenia blokujące ten Pull Request zanim go scalisz. +issues.dependency.blocks_short=Blokuje issues.dependency.blocked_by_short=Zależy od -issues.dependency.remove_header=Usuń Zależność +issues.dependency.remove_header=Usuń zależność +issues.dependency.issue_remove_text=Usunie to zależność z tego zgłoszenia. Kontynuować? +issues.dependency.pr_remove_text=Usunie to tę zależność z tego Pull Requesta. Kontynuować? +issues.dependency.setting=Włącz zależności dla zgłoszeń i Pull Requestów +issues.dependency.add_error_same_issue=Zgłoszenie nie może być zależne od siebie samego. +issues.dependency.add_error_dep_issue_not_exist=Zgłoszenie zależne nie istnieje. +issues.dependency.add_error_dep_not_exist=Zależność nie istnieje. +issues.dependency.add_error_dep_exists=Zależność już istnieje. +issues.dependency.add_error_cannot_create_circular=Nie możesz stworzyć zależności z dwoma zgłoszeniami blokującymi siebie wzajemnie. +issues.dependency.add_error_dep_not_same_repo=Oba zgłoszenia muszą być w tym samym repozytorium. +issues.review.self.approval=Nie możesz zatwierdzić swojego własnego Pull Requesta. +issues.review.self.rejection=Nie możesz zażądać zmian w swoim własnym Pull Requeście. +issues.review.approve=zatwierdza te zmiany %s +issues.review.comment=zrecenzowano %s +issues.review.content.empty=Musisz pozostawić komentarz o pożądanej zmianie/zmianach. +issues.review.review=Recenzuj +issues.review.reviewers=Recenzenci +issues.review.show_outdated=Pokaż przedawnione +issues.review.hide_outdated=Ukryj przedawnione -pulls.new=Nowy pull request +pulls.desc=Włącz Pull Requesty i recenzjonowanie kodu. +pulls.new=Nowy Pull Request +pulls.compare_changes=Nowy Pull Request +pulls.compare_changes_desc=Wybierz gałąź, do której chcesz scalić oraz gałąź, z której pobrać zmiany. +pulls.compare_base=scal do +pulls.compare_compare=ściągnij z pulls.filter_branch=Filtruj branch pulls.no_results=Nie znaleziono wyników. +pulls.nothing_to_compare=Te gałęzie są sobie równe. Nie ma potrzeby tworzyć Pull Requesta. +pulls.has_pull_request=`Pull Request pomiędzy tymi gałęziami już istnieje %[2]s#%[3]d` pulls.create=Utwórz Pull Request pulls.title_desc=chce scalić %[1]d commity/ów z %[2]s do %[3]s pulls.merged_title_desc=scala %[1]d commity/ów z %[2]s do %[3]s %[4]s pulls.tab_conversation=Dyskusja pulls.tab_commits=Commity -pulls.reopen_to_merge=Proszę otworzyć ponownie pull request aby wykonać scalenie. +pulls.tab_files=Zmodyfikowane pliki +pulls.reopen_to_merge=Otwórz ponownie ten Pull Request, aby wykonać scalenie. +pulls.cant_reopen_deleted_branch=Ten Pull Request nie może być ponownie otwarty, ponieważ jedna z gałęzi została usunięta. pulls.merged=Scalone -pulls.can_auto_merge_desc=Ten pull request może być automatycznie scalony. -pulls.merge_pull_request=Scal pull request +pulls.merged_as=Pull Request został scalony jako %[2]s. +pulls.has_merged=Pull Request został scalony. +pulls.title_wip_desc=`Poprzedź tytuł przy pomocy %s, aby zapobiec przypadkowemu scaleniu tego Pull Requesta.` +pulls.cannot_merge_work_in_progress=Ten Pull Request został oznaczony jako praca w toku. Usuń prefiks %s z tytułu, kiedy będzie już gotowy. +pulls.data_broken=Ten Pull Request jest uszkodzony ze względu na brakujące informacje o forku. +pulls.files_conflicted=Ten Pull Request zawiera zmiany konfliktujące z docelową gałęzią. +pulls.is_checking=Sprawdzanie konfliktów ze scalaniem w toku. Spróbuj ponownie za chwilę. +pulls.blocked_by_approvals=Ten Pull Request nie ma jeszcze wymaganej ilości zatwierdzeń. Otrzymał %d z %d wymaganych zatwierdzeń. +pulls.can_auto_merge_desc=Ten Pull Request może być automatycznie scalony. +pulls.cannot_auto_merge_desc=Ten Pull Request nie może być automatycznie scalony z powodu konfliktów. +pulls.cannot_auto_merge_helper=Scal ręcznie, aby rozwiązać konflikty. +pulls.no_merge_desc=Ten Pull Request nie może zostać scalony, ponieważ wszystkie opcje scalania dla tego repozytorium są wyłączone. +pulls.no_merge_helper=Włącz opcje scalania w ustawieniach repozytorium, lub scal ten Pull Request ręcznie. +pulls.no_merge_wip=Ten pull request nie może być automatycznie scalony, ponieważ jest oznaczony jako praca w toku. +pulls.merge_pull_request=Scal Pull Request pulls.rebase_merge_pull_request=Zmień bazę i scal +pulls.rebase_merge_commit_pull_request=Zmień bazę i scal (--no-ff) +pulls.squash_merge_pull_request=Zmiażdż i scal +pulls.invalid_merge_option=Nie możesz użyć tej opcji scalania dla tego pull request'a. +pulls.open_unmerged_pull_exists=`Nie możesz wykonać operacji ponownego otwarcia, ponieważ jest już oczekujący pull request (#%d) z identycznymi właściwościami.` +pulls.status_checking=Niektóre etapy są w toku +pulls.status_checks_success=Wszystkie etapy powiodły się +pulls.status_checks_error=Niektóre etapy nie powiodły się milestones.new=Nowy kamień milowy milestones.open_tab=Otwarte %d @@ -763,28 +996,38 @@ milestones.closed=Zamknięto %s milestones.no_due_date=Nie ustalono terminu milestones.open=Otwórz milestones.close=Zamknij +milestones.new_subheader=Cele pozwalają na organizację zagadnień i śledzenie postępów. +milestones.completeness=%d%% Ukończono milestones.create=Utwórz kamień milowy milestones.title=Tytuł milestones.desc=Opis milestones.due_date=Termin realizacji (opcjonalnie) milestones.clear=Wyczyść +milestones.invalid_due_date_format=Format daty realizacji musi mieć wartość 'rrrr-mm-dd'. +milestones.create_success=Cel '%s' został stworzony. milestones.edit=Edytuj kamień milowy +milestones.edit_subheader=Cele pozwalają zorganizować zagadnienia i śledzić postępy. milestones.cancel=Anuluj +milestones.modify=Zaktualizuj cel +milestones.edit_success=Cel '%s' został zaktualizowany. milestones.deletion=Usuń kamień milowy +milestones.deletion_desc=Usunięcie celu usuwa go z wszystkich pozostałych zagadnień. Kontynuować? +milestones.deletion_success=Cel został usunięty. milestones.filter_sort.closest_due_date=Najbliżej daty realizacji milestones.filter_sort.furthest_due_date=Najdalej daty realizacji milestones.filter_sort.least_complete=Najmniej kompletne milestones.filter_sort.most_complete=Najbardziej kompletne -milestones.filter_sort.most_issues=Najwięcej problemów -milestones.filter_sort.least_issues=Najmniej problemów +milestones.filter_sort.most_issues=Najwięcej zgłoszeń +milestones.filter_sort.least_issues=Najmniej zgłoszeń ext_wiki=Zewn. wiki ext_wiki.desc=Link do zewnętrznego wiki. wiki=Wiki wiki.welcome=Witaj na wiki! -wiki.welcome_desc=Wiki pozwala Ci pisać i współdzielić dokumentację ze współpracownikami. +wiki.welcome_desc=Wiki pozwala Ci na tworzenie i współdzielenie dokumentacji ze współpracownikami. wiki.desc=Pisz i współdziel dokumentację ze współpracownikami. +wiki.create_first_page=Stwórz pierwszą stronę wiki.page=Strona wiki.filter_page=Filtruj stronę wiki.new_page=Strona @@ -793,8 +1036,13 @@ wiki.save_page=Zapisz stronę wiki.last_commit_info=%s edytuje tę stronę %s wiki.edit_page_button=Edytuj wiki.new_page_button=Nowa strona +wiki.file_revision=Wersja strony +wiki.wiki_page_revisions=Wersje stron wiki +wiki.back_to_wiki=Powrót do strony wiki wiki.delete_page_button=Usuń stronę +wiki.delete_page_notice_1=Usunięcie strony wiki '%s' nie może zostać cofnięte. Kontynuować? wiki.page_already_exists=Strona Wiki o tej samej nazwie już istnieje. +wiki.reserved_page=Nazwa strony wiki '%s' jest zastrzeżona. wiki.pages=Strony wiki.last_updated=Ostatnia aktualizacja %s @@ -805,40 +1053,64 @@ activity.period.halfweekly=3 dni activity.period.weekly=1 tydzień activity.period.monthly=1 miesiąc activity.overview=Przegląd -activity.active_prs_count_1=%d aktywny pull request -activity.active_prs_count_n=Aktywnych pull requestów: %d -activity.merged_prs_count_1=Scalono pull request -activity.merged_prs_count_n=Scalono pull requesty -activity.opened_prs_count_1=Proponowany pull request -activity.opened_prs_count_n=Proponowane pull requesty +activity.active_prs_count_1=%d aktywny Pull Request +activity.active_prs_count_n=%d aktywne Pull Requesty +activity.merged_prs_count_1=Scalono Pull Request +activity.merged_prs_count_n=Scalone Pull Requesty +activity.opened_prs_count_1=Proponowany Pull Request +activity.opened_prs_count_n=Proponowane Pull Requesty activity.title.user_1=%d użytkownik activity.title.user_n=%d użytkowników -activity.title.prs_1=%d pull request -activity.title.prs_n=Pull requestów: %d +activity.title.prs_1=%d Pull Request +activity.title.prs_n=%d Pull Requesty activity.title.prs_merged_by=%s zmergowane przez %s activity.title.prs_opened_by=%s zaproponowane przez %s activity.merged_prs_label=Scalone activity.opened_prs_label=Proponowane -activity.active_issues_count_1=%d aktywnych zagadnień -activity.active_issues_count_n=%d Aktywnych zagadnień -activity.closed_issues_count_1=Zamknięty problem -activity.closed_issues_count_n=Zamknięte problemy -activity.title.issues_1=%d Zagadnienie -activity.title.issues_n=%d Zagadnienia +activity.active_issues_count_1=%d Aktywne zgłoszenia +activity.active_issues_count_n=%d Aktywnych zgłoszeń +activity.closed_issues_count_1=Zamknięte zgłoszenie +activity.closed_issues_count_n=Zamknięte zgłoszenia +activity.title.issues_1=%d Zgłoszenie +activity.title.issues_n=%d Zgłoszenia activity.title.issues_closed_by=%s zamknięte przez %s activity.title.issues_created_by=%s utworzone przez %s activity.closed_issue_label=Zamknięty -activity.new_issues_count_1=Nowy problem -activity.new_issues_count_n=Nowe problemy +activity.new_issues_count_1=Nowe zgłoszenie +activity.new_issues_count_n=Nowe zgłoszenia activity.new_issue_label=Otwarte +activity.title.unresolved_conv_1=%d Nierozstrzygnięta dyskusja +activity.title.unresolved_conv_n=%d Nierozstrzygniętych dyskusji +activity.unresolved_conv_desc=Te niedawno zmienione zagadnienia i Pull Requesty nie zostały jeszcze rozwiązane. activity.unresolved_conv_label=Otwarte activity.title.releases_1=%d Wydanie activity.title.releases_n=%d Wydań activity.title.releases_published_by=%s opublikowane przez %s activity.published_release_label=Opublikowane +activity.no_git_activity=Nie było żadnej aktywności w tym okresie czasu. +activity.git_stats_exclude_merges=Wykluczając scalenia, +activity.git_stats_author_1=%d autor +activity.git_stats_author_n=%d autorzy +activity.git_stats_pushed_1=przepchnął(-ęła) +activity.git_stats_pushed_n=przepchnęli(-ęły) +activity.git_stats_commit_1=%d commit +activity.git_stats_commit_n=%d commity +activity.git_stats_push_to_branch=do %s i +activity.git_stats_push_to_all_branches=do wszystkich gałęzi. +activity.git_stats_on_default_branch=Na %s, +activity.git_stats_file_1=%d plik +activity.git_stats_file_n=%d pliki +activity.git_stats_files_changed_1=zmodyfikował(-a) +activity.git_stats_files_changed_n=zmodyfikowali(-ły) +activity.git_stats_additions=w wyniku czego powstały +activity.git_stats_addition_1=%d dodanie +activity.git_stats_addition_n=% dodań +activity.git_stats_and_deletions=i +activity.git_stats_deletion_1=%d usunięcie +activity.git_stats_deletion_n=%d usunięć search=Szukaj -search.search_repo=Szukaj repozytorium +search.search_repo=Przeszukaj repozytorium search.results=Wyniki wyszukiwania dla "%s" w %s settings=Ustawienia @@ -854,52 +1126,151 @@ settings.githooks=Hooki Git settings.basic_settings=Ustawienia podstawowe settings.mirror_settings=Kopia lustrzana ustawień settings.sync_mirror=Synchronizuj teraz +settings.mirror_sync_in_progress=Synchronizacja kopii lustrzanych jest w toku. Sprawdź ponownie za minutę. +settings.email_notifications.enable=Włącz powiadomienia e-mail +settings.email_notifications.onmention=Wyślij wiadomość e-mail wyłącznie przy wzmiankach +settings.email_notifications.disable=Wyłącz powiadomienia e-mail +settings.email_notifications.submit=Ustaw preferencje wiadomości e-mail settings.site=Strona settings.update_settings=Aktualizuj ustawienia settings.advanced_settings=Ustawienia zaawansowane +settings.wiki_desc=Włącz wiki repozytorium +settings.use_internal_wiki=Użyj wbudowanego wiki +settings.use_external_wiki=Użyj zewnętrznego wiki settings.external_wiki_url=Adres URL zewnętrznego Wiki -settings.external_tracker_url=URL zewnętrznego trackera zagadnień -settings.tracker_url_format=Format dla adresu URL zewnętrznego systemu +settings.external_wiki_url_error=URL zewnętrznego wiki nie jest prawidłowym adresem URL. +settings.external_wiki_url_desc=Odwiedzający są przekierowywani do zewnętrznego adresu URL wiki po kliknięciu w zakładkę wiki. +settings.issues_desc=Włącz śledzenie zgłoszeń w repozytorium +settings.use_internal_issue_tracker=Użyj wbudowanego śledzenia zgłoszeń +settings.use_external_issue_tracker=Użyj zewnętrznego śledzenia zgłoszeń +settings.external_tracker_url=URL zewnętrznego systemu śledzenia zgłoszeń +settings.external_tracker_url_error=Adres URL zewnętrznego śledzenia zgłoszeń nie jest poprawnym adresem URL. +settings.external_tracker_url_desc=Odwiedzający są przekierowywani do adresu URL zewnętrznego systemu śledzenia zgłoszeń po kliknięciu w zakładkę zgłoszeń. +settings.tracker_url_format=Format adresu URL zewnętrznego systemu śledzenia zgłoszeń +settings.tracker_url_format_error=Adres URL zewnętrznego systemu śledzenia zgłoszeń nie jest poprawnym adresem URL. +settings.tracker_issue_style=Format numerowania dla zewnętrznego systemu śledzenia zgłoszeń settings.tracker_issue_style.numeric=Numeryczny settings.tracker_issue_style.alphanumeric=Alfanumeryczne +settings.tracker_url_format_desc=Użyj zamienników {user}, {repo} i {index} dla nazwy użytkownika, nazwy repozytorium i numeru porządkowego zgłoszenia. +settings.enable_timetracker=Włącz śledzenie czasu +settings.allow_only_contributors_to_track_time=Zezwalaj wyłącznie współpracownikom na śledzenie czasu +settings.pulls_desc=Włącz Pull Requesty w repozytorium +settings.pulls.ignore_whitespace=Ignoruj znaki białe w konfliktach +settings.pulls.allow_merge_commits=Włącz scalanie poprzez commity +settings.pulls.allow_rebase_merge=Włącz zmianę bazy do scalania commitów +settings.pulls.allow_rebase_merge_commit=Włącz zmianę bazy ze stworzeniem commita ze scaleniem (--no-ff) +settings.pulls.allow_squash_commits=Włącz miażdżenie do scalania commitów +settings.admin_settings=Ustawienia administratora +settings.admin_enable_health_check=Włącz sprawdzanie stanu zdrowia repozytoriów (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch=Zamknij zgłoszenie poprzez commit wprowadzony do nie-domyślnej gałęzi settings.danger_zone=Strefa niebezpieczeństwa settings.new_owner_has_same_repo=Nowy właściciel już posiada repozytorium o tej samej nazwie. +settings.convert=Konwertuj na zwykłe repozytorium +settings.convert_desc=Możesz przekonwertować tę kopię lustrzaną na zwykłe repozytorium. Ta czynność jest nieodwracalna. +settings.convert_notices_1=Ta operacja przekonwertuje kopię lustrzaną w zwykłe repozytorium i nie może być cofnięta. +settings.convert_confirm=Konwertuj repozytorium +settings.convert_succeed=Kopia lustrzana została przekonwertowana w zwykłe repozytorium. settings.transfer=Przeniesienie własności +settings.transfer_desc=Przenieś to repozytorium do innego użytkownika lub organizacji, w której posiadasz uprawnienia administratora. +settings.transfer_notices_1=- Stracisz dostęp do tego repozytorium, jeśli przeniesiesz je do innego użytkownika. +settings.transfer_notices_2=- Utrzymasz dostęp do tego repozytorium, jeśli przeniesiesz je do organizacji, której jesteś (współ-)właścicielem. +settings.transfer_form_title=Wpisz nazwę repozytorium w celu potwierdzenia: +settings.wiki_delete=Usuń dane Wiki +settings.wiki_delete_desc=Usunięcie danych wiki jest nieodwracalne. +settings.wiki_delete_notices_1=- Ta operacja usunie i wyłączy wiki repozytorium %s. +settings.confirm_wiki_delete=Usuń dane Wiki +settings.wiki_deletion_success=Dane wiki repozytorium zostały usunięte. settings.delete=Usuń to repozytorium +settings.delete_desc=Usunięcie repozytorium jest trwałe i nieodwracalne. settings.delete_notices_1=- Ta operacja NIE MOŻE zostać cofnięta. +settings.delete_notices_2=- Ta operacja trwale usunie repozytorium %s, w tym kod źródłowy, zgłoszenia, komentarze, dane wiki i dostęp dla współpracowników. +settings.delete_notices_fork_1=- Forki tego repozytorium będą niezależne po jego usunięciu. +settings.deletion_success=Repozytorium zostało usunięte. +settings.update_settings_success=Ustawienia repozytorium zostały zaktualizowane. settings.transfer_owner=Nowy właściciel +settings.make_transfer=Wykonaj transfer +settings.transfer_succeed=Repozytorium zostało przeniesione. settings.confirm_delete=Usuń repozytorium +settings.add_collaborator=Dodaj +settings.add_collaborator_success=Dodano użytkownika. +settings.add_collaborator_inactive_user=Nie możesz dodać nieaktywnego użytkownika jako współpracownika. +settings.add_collaborator_duplicate=Współpracownik został już dodany do tego repozytorium. settings.delete_collaborator=Usuń +settings.collaborator_deletion=Usuń współpracownika +settings.collaborator_deletion_desc=Usunięcie współpracownika odbierze mu dostęp do tego repozytorium. Kontynuować? +settings.remove_collaborator_success=Usunięto użytkownika. settings.search_user_placeholder=Szukaj użytkownika… +settings.org_not_allowed_to_be_collaborator=Organizacji nie można dodać jako współpracownika. settings.add_webhook=Dodaj webhooka +settings.add_webhook.invalid_channel_name=Nazwa kanału Webhooka nie może być pusta i nie może zawierać jedynie znaku #. +settings.hooks_desc=Webhooki automatycznie tworzą zapytania HTTP POST do serwera, kiedy następują pewne zdarzenia w Gitea. Przeczytaj o tym więcej w przewodniku o Webhookach. +settings.webhook_deletion=Usuń Webhooka +settings.webhook_deletion_desc=Usunięcie Webhooka wykasuje jego ustawienia i historię dostaw. Kontynuować? +settings.webhook_deletion_success=Webhook został usunięty. settings.webhook.test_delivery=Testuj dostawę +settings.webhook.test_delivery_desc=Sprawdź tego Webhooka przy pomocy testowego zdarzenia. +settings.webhook.test_delivery_success=Testowe zdarzenie zostało dodane do kolejki dostaw. Może zająć kilka sekund, zanim pojawi się w historii dostaw. settings.webhook.request=Żądanie settings.webhook.response=Odpowiedź settings.webhook.headers=Nagłówki settings.webhook.payload=Zawartość settings.webhook.body=Treść +settings.githooks_desc=Hooki Gita są obsługiwane bezpośrednio przez Gita. Możesz edytować pliki hook'ów poniżej, aby wykonywać niestandardowe operacje. settings.githook_edit_desc=Jeśli hook jest nieaktywny, zaprezentowana zostanie przykładowa treść. Pozostawienie pustej wartości wyłączy ten hook. settings.githook_name=Nazwa hooka settings.githook_content=Treść hooka settings.update_githook=Zaktualizuj hook +settings.add_webhook_desc=Gitea wyśle żądanie POST z określonym typem zawartości do docelowego adresu URL. Przeczytaj o tym więcej w przewodniku o Webhookach. +settings.payload_url=Adres docelowy URL +settings.http_method=Metoda HTTP +settings.content_type=Typ zawartości POST settings.secret=Sekret settings.slack_username=Użytkownik settings.slack_icon_url=Adres URL ikony settings.discord_username=Nazwa użytkownika settings.discord_icon_url=Adres URL ikony settings.slack_color=Kolor +settings.event_desc=Wywołaj przy: +settings.event_push_only=Wydarzeniach przepchnięcia +settings.event_send_everything=Wszystkich wydarzeniach +settings.event_choose=Niestandardowych wydarzeniach… settings.event_create=Utwórz +settings.event_create_desc=Utworzono gałąź lub tag. +settings.event_delete=Usuń +settings.event_delete_desc=Usunięto gałąź lub tag +settings.event_fork=Fork +settings.event_fork_desc=Repozytorium sforkowane +settings.event_issues=Zgłoszenia +settings.event_issues_desc=Zgłoszenie stworzone, zamknięte, ponownie otworzone, edytowane, przypisane, usunięto przypisanie, etykieta zaktualizowana, etykieta wyczyszczona, ustawiono cel lub usunięto cel. +settings.event_issue_comment=Komentarz w zgłoszeniu +settings.event_issue_comment_desc=Komentarz w zgłoszeniu stworzony, edytowany lub usunięty. +settings.event_release=Wydanie +settings.event_release_desc=Wydanie opublikowane, zaktualizowane lub usunięte z repozytorium. settings.event_pull_request=Pull Request +settings.event_pull_request_desc=Pull Request otwarty, zamknięty, ponownie otwarty, zaakceptowany, odrzucony, komentarz oceniający, przypisany, nieprzypisany, etykieta zaktualizowana, etykieta wyczyszczona lub zsynchronizowany. settings.event_push=Wypchnięcie +settings.event_push_desc=Wypchnięcie git do repozytorium. settings.event_repository=Repozytorium +settings.event_repository_desc=Repozytorium stworzone lub usunięte. +settings.active=Aktywne +settings.active_helper=Informacja o wywołanych wydarzeniach będzie przesłana do tego adresu URL Webhooka. +settings.add_hook_success=Webhook został dodany. settings.update_webhook=Zaktualizuj webhook +settings.update_hook_success=Webhook został zaktualizowany. +settings.delete_webhook=Usuń Webhooka settings.recent_deliveries=Ostatnie wywołania settings.hook_type=Typ hooka +settings.add_slack_hook_desc=Zintegruj Slacka ze swoim repozytorium. settings.slack_token=Token settings.slack_domain=Domena settings.slack_channel=Kanał +settings.add_discord_hook_desc=Zintegruj Discorda ze swoim repozytorium. +settings.add_dingtalk_hook_desc=Zintegruj Dingtalka ze swoim repozytorium. +settings.add_telegram_hook_desc=Zintegruj Telegrama ze swoim repozytorium. +settings.add_msteams_hook_desc=Zintegruj Microsoft Teams ze swoim repozytorium. settings.deploy_keys=Klucze wdrożeniowe -settings.add_deploy_key=Dodaj klucz wdrożenia +settings.add_deploy_key=Dodaj klucz wdrożeniowy +settings.is_writable=Włącz dostęp do zapisu settings.title=Tytuł settings.deploy_key_content=Treść settings.branches=Gałęzie @@ -909,6 +1280,8 @@ settings.protected_branch_can_push_yes=Możesz wysyłać settings.protected_branch_can_push_no=Nie możesz wysyłać settings.branch_protection=Ochrona gałęzi dla "%s settings.protect_this_branch=Włącz ochronę gałęzi +settings.protect_whitelist_search_users=Szukaj użytkowników… +settings.protect_whitelist_search_teams=Szukaj zespołów… settings.add_protected_branch=Włącz ochronę settings.delete_protected_branch=Wyłącz ochronę settings.update_protect_branch_success=Ochrona gałęzi dla gałęzi "%s" została zaktualizowana. @@ -916,20 +1289,47 @@ settings.remove_protected_branch_success=Ochrona gałęzi dla gałęzi "%s" zost settings.protected_branch_deletion=Wyłącz ochronę gałęzi settings.protected_branch_deletion_desc=Wyłączenie ochrony gałęzi pozwoli użytkownikom z uprawnieniami zapisu do przekazywania zmian do gałęzi. Kontynuować? settings.choose_branch=Wybierz gałąź… +settings.edit_protected_branch=Zmień +settings.protected_branch_required_approvals_min=Wymagane zatwierdzenia nie mogą mieć wartości ujemnej. +settings.bot_token=Token bota +settings.chat_id=ID czatu +settings.archive.button=Zarchiwizuj repozytorium +settings.archive.header=Zarchiwizuj to repozytorium +settings.archive.text=Zarchiwizowanie repozytorium sprawi, że będzie ono "tylko do odczytu". Zostanie ukryte z pulpitu, nie będzie możliwe commitowanie, otwieranie zgłoszeń, czy tworzenie Pull Requestów. +settings.archive.success=Repozytorium zostało pomyślnie zarchiwizowane. diff.browse_source=Przeglądaj źródła diff.parent=rodzic diff.commit=commit +diff.git-notes=Notatki diff.data_not_available=Informacje nt. zmian nie są dostępne diff.show_diff_stats=Pokaż statystyki zmian diff.show_split_view=Widok podzielony diff.show_unified_view=Zunifikowany widok +diff.whitespace_button=Znaki białe +diff.whitespace_show_everything=Pokaż wszystkie zmiany +diff.whitespace_ignore_all_whitespace=Ignoruj znaki białe przy porównywaniu linii +diff.whitespace_ignore_amount_changes=Ignoruj zmiany w ilości znaków białych +diff.whitespace_ignore_at_eol=Ignoruj zmiany w znakach białych przy EOL diff.stats_desc=%d zmienionych plików z %d dodań i %d usunięć diff.bin=BIN diff.view_file=Wyświetl plik diff.file_suppressed=Plik diff jest za duży diff.too_many_files=Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików +diff.comment.placeholder=Zostaw komentarz +diff.comment.markdown_info=Formatowanie przy użyciu Markdown jest wspierane. +diff.comment.add_single_comment=Dodaj jeden komentarz +diff.comment.add_review_comment=Dodaj komentarz +diff.comment.start_review=Rozpocznij recenzję +diff.comment.reply=Odpowiedz +diff.review=Recenzuj +diff.review.header=Dodaj recenzję +diff.review.placeholder=Komentarz recenzji +diff.review.comment=Skomentuj +diff.review.approve=Zatwierdź +diff.review.reject=Zażądaj zmian +releases.desc=Śledź wersje projektu i pobrania. release.releases=Wydania release.new_release=Nowe wydanie release.draft=Szkic @@ -938,29 +1338,59 @@ release.stable=Stabilny release.edit=edytuj release.ahead=%d commitów w %s od tego wydania release.source_code=Kod źródłowy +release.new_subheader=Wydania pozwalają na zorganizowanie wersji projektu. +release.edit_subheader=Wydania pozwalają na zorganizowanie wersji projektu. release.tag_name=Nazwa tagu release.target=Cel +release.tag_helper=Wybierz istniejący tag lub stwórz nowy. release.title=Tytuł release.content=Treść +release.prerelease_desc=Oznacz jako wczesne wydanie +release.prerelease_helper=Oznacz to wydanie jako nieprzeznaczone na użytek produkcyjny. release.cancel=Anuluj release.publish=Publikuj wersję release.save_draft=Zapisz szkic +release.edit_release=Zaktualizuj wydanie +release.delete_release=Usuń wydanie +release.deletion=Usuń wydanie +release.deletion_desc=Usunięcie wydania wykasuje jego tag Gita z repozytorium. Zawartość repozytorium i historia pozostaje niezmieniona. Kontynuować? release.deletion_success=Wydanie zostało usunięte. +release.tag_name_already_exist=Wydanie z tą nazwą tagu już istnieje. +release.tag_name_invalid=Nazwa tagu jest niewłaściwa. release.downloads=Pliki do pobrania +branch.name=Nazwa gałęzi branch.search=Szukaj gałęzi +branch.already_exists=Gałąź z nazwą '%s' już istnieje. branch.delete_head=Usuń +branch.delete=Usuń branch "%s branch.delete_html=Usuń gałąź +branch.delete_desc=Usunięcie gałęzi jest trwałe i NIE MOŻE zostać cofnięte. Kontynuować? +branch.deletion_success=Gałąź '%s' została usunięta. +branch.deletion_failed=Nie udało się usunąć gałęzi '%s'. +branch.delete_branch_has_new_commits=Gałąź '%s' nie może zostać usunięta, ponieważ zostały dodane do niej nowe commity po scaleniu. branch.create_branch=Utwórz gałąź %s branch.create_from=z '%s' +branch.create_success=Gałąź '%s' została utworzona. branch.branch_already_exists=Gałąź '%s' już istnieje w tym repozytorium. +branch.branch_name_conflict=Nazwa gałęzi '%s' koliduje z już istniejącą gałęzią '%s'. +branch.tag_collision=Gałąź '%s' nie może zostać utworzona, ponieważ w tym repozytorium istnieje już tag o takiej samej nazwie. branch.deleted_by=Usunięta przez %s +branch.restore_success=Gałąź '%s' została przywrócona. +branch.restore_failed=Nie udało się przywrócić gałęzi '%s'. +branch.protected_deletion_failed=Gałąź '%s' jest chroniona. Nie można jej usunąć. +branch.restore=Przywróć gałąź '%s' +branch.download=Pobierz gałąź '%s' +topic.manage_topics=Zarządzaj tematami topic.done=Gotowe +topic.count_prompt=Nie możesz wybrać więcej, niż 25 tematów +topic.format_prompt=Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków. [org] org_name_holder=Nazwa organizacji org_full_name_holder=Pełna nazwa organizacji +org_name_helper=Nazwa organizacji powinna być krótka i łatwa do zapamiętania. create_org=Utwórz organizację repo_updated=Zaktualizowano people=Ludzie @@ -972,26 +1402,42 @@ create_team=Utwórz zespół org_desc=Opis team_name=Nazwa zespołu team_desc=Opis +team_name_helper=Nazwy zespołów powinny być krótkie i łatwe do zapamiętania. +team_desc_helper=Opisz cel lub rolę zespołu. team_permission_desc=Uprawnienie +team_unit_desc=Zezwól na dostęp do sekcji repozytoriów +form.name_reserved=Nazwa organizacji '%s' jest zastrzeżona. +form.name_pattern_not_allowed=Wzór '%s' nie jest dozwolony w nazwie organizacji. +form.create_org_not_allowed=Nie masz uprawnień do stworzenia organizacji. settings=Ustawienia settings.options=Organizacja settings.full_name=Imię i nazwisko settings.website=Strona settings.location=Lokalizacja +settings.visibility=Widoczność +settings.visibility.public=Publiczne +settings.visibility.limited=Ukryte (widoczność tylko dla zalogowanych użytkowników) +settings.visibility.private=Prywatne (widoczne tylko dla użytkowników organizacji) settings.update_settings=Aktualizuj ustawienia settings.update_setting_success=Ustawienia organizacji zostały zaktualizowane. +settings.change_orgname_prompt=Uwaga: zmiana nazwy organizacji zmieni również adres URL tej organizacji. +settings.update_avatar_success=Awatar organizacji został zaktualizowany. settings.delete=Usuń organizację settings.delete_account=Usuń tą organizację +settings.delete_prompt=Organizacja zostanie trwale usunięta. Tej akcji NIE MOŻNA cofnąć! settings.confirm_delete_account=Potwierdź usunięcie settings.delete_org_title=Usuń organizację +settings.delete_org_desc=Ta organizacja zostanie trwale usunięta. Kontynuować? settings.hooks_desc=Dodaj webhooki, uruchamiane dla wszystkich repozytoriów w tej organizacji. members.membership_visibility=Widoczność członkostwa: members.public=Widoczny +members.public_helper=ukryj members.private=Ukryty +members.private_helper=pokaż members.member_role=Rola: members.owner=Właściciel members.member=Członek @@ -1003,26 +1449,40 @@ members.invite_now=Zaproś teraz teams.join=Dołącz teams.leave=Opuść teams.read_access=Dostęp do odczytu +teams.read_access_helper=Członkowie mogą wyświetlać i klonować repozytoria zespołów. teams.write_access=Dostęp do zapisu +teams.write_access_helper=Członkowie mają uprawnienia odczytu i wypychania do repozytoriów zespołu. +teams.admin_access=Dostęp administratora +teams.admin_access_helper=Członkowie mogą ściągać i wypychać zmiany do repozytoriów zespołu oraz dodawać do niego współpracowników. teams.no_desc=Ten zespół nie ma opisu teams.settings=Ustawienia +teams.owners_permission_desc=Właściciele mają pełny dostęp do wszystkich repozytoriów i mają prawa administratora w całej organizacji. teams.members=Członkowie zespołu teams.update_settings=Aktualizuj ustawienia teams.delete_team=Usuń zespół teams.add_team_member=Dodaj członka zespołu teams.delete_team_title=Usuń zespół +teams.delete_team_desc=Usunięcie zespołu wycofa dostęp do repozytorium jego członkom. Kontynuować? teams.delete_team_success=Zespół został usunięty. +teams.read_permission_desc=Ten zespół udziela dostępu z odczytem: członkowie mogą wyświetlać i klonować repozytoria zespołu. +teams.write_permission_desc=Ten zespół udziela dostępu z zapisem: członkowie mogą wyświetlać i wypychać zmiany do repozytoriów zespołu. +teams.admin_permission_desc=Ten zespół udziela dostępu administratora: członkowie mogą wyświetlać i wypychać zmiany oraz dodawać współpracowników do repozytoriów zespołu. teams.repositories=Repozytoria zespołu teams.search_repo_placeholder=Szukaj repozytorium… teams.add_team_repository=Dodaj repozytorium zespołu teams.remove_repo=Usuń teams.add_nonexistent_repo=Repozytorium, które próbujesz dodać, nie istnieje. Proszę je najpierw utworzyć. +teams.add_duplicate_users=Użytkownik jest już członkiem zespołu. +teams.repos.none=Ten zespół nie ma dostępu do żadnego repozytorium. +teams.members.none=Ten zespół nie ma żadnych członków. [admin] dashboard=Pulpit users=Konta użytkownika organizations=Organizacje repositories=Repozytoria +hooks=Domyślne Webhooki +authentication=Źródła uwierzytelniania config=Konfiguracja notices=Powiadomienia systemu monitor=Monitorowanie @@ -1031,21 +1491,42 @@ last_page=Ostatnia total=Ogółem: %d dashboard.statistic=Podsumowanie +dashboard.operations=Operacje konserwacji dashboard.system_status=Status strony +dashboard.statistic_info=Baza danych Gitea zawiera %d użytkowników, %d organizacji, %d kluczy publicznych, %d repozytoriów, %d obserwujących repozytoria, %d polubionych repozytoriów, %d akcji, %d tokenów, %d zgłoszeń, %d komenatrzy, %d kont społecznościowych, %d obserwujących użytkowników, %d kopii lustrzanych, %d wydań, %d źródeł uwierzytelniania, %d Webhooków, %d kamieni milowych, %d etykiet, %d zadań hook'ów, %d zespołów, %d zadań aktualizacji, %d załączników. dashboard.operation_name=Nazwa operacji dashboard.operation_switch=Przełącz dashboard.operation_run=Uruchom dashboard.clean_unbind_oauth=Usuń wychodzące połączenia OAuth dashboard.clean_unbind_oauth_success=Wszystkie połączenia wychodzące OAuth zostały usunięte. +dashboard.delete_inactivate_accounts=Usuń wszystkie nieaktywne konta +dashboard.delete_inactivate_accounts_success=Wszystkie nieaktywne konta zostały usunięte. +dashboard.delete_repo_archives=Usuń wszystkie archiwa repozytoriów +dashboard.delete_repo_archives_success=Wszystkie archiwa repozytoriów zostały usunięte. +dashboard.delete_missing_repos=Usuń wszystkie repozytoria, które nie mają plików Gita +dashboard.delete_missing_repos_success=Wszystkie repozytoria z brakującymi plikami Gita zostały usunięte. +dashboard.delete_generated_repository_avatars=Usuń wygenerowane awatary repozytoriów +dashboard.delete_generated_repository_avatars_success=Wygenerowane awatary repozytoriów zostały usunięte. +dashboard.git_gc_repos=Wykonaj zbieranie śmieci ze wszystkich repozytoriów +dashboard.git_gc_repos_success=Śmieci ze wszystkich repozytoriów zostały zebrane. +dashboard.resync_all_sshkeys=Zaktualizuj plik '.ssh/authorized_keys' z kluczami SSH Gitea. (Nie jest wymagane w przypadku wbudowanego serwera SSH.) +dashboard.resync_all_sshkeys_success=Wszystkie klucze publiczne SSH zarządzane przez Gitea zostały zaktualizowane. +dashboard.resync_all_hooks=Ponownie synchronizuj hooki pre-receive, update i post-receive we wszystkich repozytoriach. +dashboard.resync_all_hooks_success=Wszystkie hooki pre-receive, update i post-receive w repozytoriach zostały ponownie zsynchronizowane. dashboard.reinit_missing_repos=Ponownie zainicjalizuj wszystkie brakujące repozytoria Git, dla których istnieją rekordy dashboard.reinit_missing_repos_success=Wszystkie brakujące repozytoria Git, dla których istnieją rekordy, zostały zainicjalizowane. dashboard.sync_external_users=Synchronizuj zewnętrzne dane użytkownika +dashboard.sync_external_users_started=Synchronizacja zewnętrznych danych użytkowników została rozpoczęta. +dashboard.git_fsck=Wykonaj badanie stanu zdrowia we wszystkich repozytoriach +dashboard.git_fsck_started=Badanie stanu zdrowia repozytoriów zostało rozpoczęte. dashboard.server_uptime=Uptime serwera dashboard.current_goroutine=Bieżące Goroutines dashboard.current_memory_usage=Bieżące użycie pamięci dashboard.total_memory_allocated=Całkowita przydzielona pamięć dashboard.memory_obtained=Pamięć uzyskana dashboard.pointer_lookup_times=Czas określania wskaźników +dashboard.memory_allocate_times=Alokacje pamięci +dashboard.memory_free_times=Zwolnienie pamięci dashboard.current_heap_usage=Bieżące użycie sterty dashboard.heap_memory_obtained=Uzyskana pamięć sterty dashboard.heap_memory_idle=Bezczynna pamięć sterty @@ -1068,16 +1549,37 @@ dashboard.total_gc_pause=Sumaryczny czas wstrzymania przez GC dashboard.last_gc_pause=Ostatnie wstrzymanie przez GC dashboard.gc_times=Ilość wywołań GC +users.user_manage_panel=Zarządzanie kontami użytkowników +users.new_account=Nowy użytkownik users.name=Nazwa użytkownika -users.activated=Aktywowany +users.activated=Aktywny users.admin=Administrator users.repos=Repozytoria users.created=Utworzony +users.last_login=Ostatnie logowanie +users.never_login=Nigdy nie zalogował(-a) się +users.send_register_notify=Wyślij użytkownikowi powiadomienie o rejestracji +users.new_success=Konto użytkownika '%s' zostało utworzone. users.edit=Edytuj users.auth_source=Źródło uwierzytelniania users.local=Lokalny +users.auth_login_name=Nazwa logowania uwierzytelnienia +users.password_helper=Pozostaw hasło puste, aby go nie zmieniać. +users.update_profile_success=Konto użytkownika zostało zaktualizowane. +users.edit_account=Edytuj konto użytkownika +users.max_repo_creation=Maksymalna liczba repozytoriów +users.max_repo_creation_desc=(Wpisz -1, aby użyć domyślnego globalnego limitu.) +users.is_activated=Konto użytkownika jest aktywne +users.prohibit_login=Wyłącz logowanie +users.is_admin=Jest administratorem +users.allow_git_hook=Może tworzyć hooki Gita +users.allow_import_local=Może importować lokalne repozytoria +users.allow_create_organization=Może tworzyć organizacje users.update_profile=Zaktualizuj konto użytkownika users.delete_account=Usuń konto użytkownika +users.still_own_repo=Ten użytkownik jest właścicielem jednego lub większej ilości repozytoriów. Musisz je najpierw usunąć lub przenieść. +users.still_has_org=Ten użytkownik jest członkiem organizacji. Musisz go najpierw usunąć ze wszystkich organizacji. +users.deletion_success=Konto użytkownika zostało usunięte. orgs.org_manage_panel=Zarządzanie organizacją orgs.name=Nazwa @@ -1091,13 +1593,20 @@ repos.name=Nazwa repos.private=Prywatne repos.watches=Obserwujących repos.stars=Polubienia -repos.issues=Sprawy +repos.forks=Forki +repos.issues=Zgłoszenia repos.size=Rozmiar +hooks.desc=Webhooki automatycznie tworzą zapytania HTTP POST do serwera, kiedy następują pewne zdarzenia w Gitea. Webhooki zdefiniowane w tym miejscu będą domyślnie kopiowane do wszystkich nowych repozytoriów. Przeczytaj o tym więcej w przewodniku o Webhookach. +hooks.add_webhook=Dodaj domyślny Webhook +hooks.update_webhook=Zaktualizuj domyślny Webhook +auths.auth_manage_panel=Zarządzanie źródłami uwierzytelniania +auths.new=Dodaj źródło uwierzytelniania auths.name=Nazwa auths.type=Typ auths.enabled=Włączone +auths.syncenabled=Włącz synchronizację użytkowników auths.updated=Zaktualizowano auths.auth_type=Typ uwierzytelniania auths.auth_name=Nazwa uwierzytelnienia @@ -1107,8 +1616,17 @@ auths.host=Serwer auths.port=Port auths.bind_dn=DN powiązania auths.bind_password=Hasło Bind +auths.bind_password_helper=Uwaga: To hasło będzie przechowywane w czystym tekście. Użyj konta "tylko do odczytu", jeśli to możliwe. auths.user_base=Baza wyszukiwania auths.user_dn=DN użytkownika +auths.attribute_username=Atrybut nazwy użytkownika +auths.attribute_username_placeholder=Pozostaw puste, aby użyć nazwy użytkownika wprowadzonej w Gitea. +auths.attribute_name=Atrybut imienia +auths.attribute_surname=Atrybut nazwiska +auths.attribute_mail=Atrybut adresu e-mail +auths.attribute_ssh_public_key=Atrybut publicznego klucza SSH +auths.attributes_in_bind=Pobierz atrybuty w kontekście Bind DN +auths.use_paged_search=Użyj wyszukiwania paginowanego auths.search_page_size=Rozmiar strony auths.filter=Filtr użytkownika auths.admin_filter=Filtr administratora @@ -1117,6 +1635,7 @@ auths.smtp_auth=Typ uwierzytelnienia SMTP auths.smtphost=Serwer SMTP auths.smtpport=Port SMTP auths.allowed_domains=Dozwolone domeny +auths.allowed_domains_helper=Pozostaw puste, aby zezwolić na wszystkie domeny. Rozdziel kolejne domeny przecinkiem (','). auths.enable_tls=Włącz szyfrowanie TLS auths.skip_tls_verify=Pomiń weryfikację protokołu TLS auths.pam_service_name=Nazwa usługi PAM @@ -1124,6 +1643,7 @@ auths.oauth2_provider=Dostawca OAuth2 auths.oauth2_clientID=ID klienta (klucz) auths.oauth2_clientSecret=Sekretny Token auths.openIdConnectAutoDiscoveryURL=OpenID Connect Auto Discovery URL +auths.oauth2_use_custom_url=Użyj niestandardowych adresów URL, zamiast domyślnych adresów URL auths.oauth2_tokenURL=Adres URL tokena auths.oauth2_authURL=URL autoryzacji auths.oauth2_profileURL=URL profilu @@ -1133,12 +1653,23 @@ auths.tips=Wskazówki auths.tips.oauth2.general=Uwierzytelnianie OAuth2 auths.tips.oauth2.general.tip=Przy rejestracji nowego uwierzytelnienia OAuth2, URL zwrotny/przekierowań powinien mieć postać /user/oauth2//callback auths.tip.oauth2_provider=Dostawca OAuth2 +auths.tip.bitbucket=Zarejestruj nowego konsumenta OAuth na https://bitbucket.org/account/user//oauth-consumers/new i dodaj uprawnienie "Account" - "Read auths.tip.dropbox=Stwórz nową aplikację na https://www.dropbox.com/developers/apps auths.tip.facebook=Zarejestruj nową aplikację na https://developers.facebook.com/apps i dodaj produkt "Facebook Login auths.tip.github=Zarejestruj nową aplikację OAuth na https://github.com/settings/applications/new auths.tip.gitlab=Zarejestruj nową aplikację na https://gitlab.com/profile/applications +auths.tip.google_plus=Uzyskaj dane uwierzytelniające klienta OAuth2 z konsoli Google API na https://console.developers.google.com/ +auths.tip.openid_connect=Użyj adresu URL OpenID Connect Discovery (/.well-known/openid-configuration), aby określić punkty końcowe +auths.tip.twitter=Przejdź na https://dev.twitter.com/apps, stwórz aplikację i upewnij się, że opcja “Allow this application to be used to Sign in with Twitter” jest włączona +auths.tip.discord=Zarejestruj nową aplikację na https://discordapp.com/developers/applications/me +auths.edit=Edytuj źródło uwierzytelniania +auths.activated=To źródło uwierzytelniania jest aktywne auths.new_success=Uwierzytelnienie '%s' zostało dodane. +auths.update_success=Źródło uwierzytelniania zostało zaktualizowane. +auths.update=Zaktualizuj źródło uwierzytelniania +auths.delete=Usuń źródło uwierzytelniania auths.delete_auth_title=Usuń źródło uwierzytelniania +auths.delete_auth_desc=Usunięcie źródła uwierzytelniania uniemożliwi użytkownikom używania go do zalogowania się. Kontynuować? config.server_config=Konfiguracja serwera config.app_name=Tytuł strony @@ -1170,6 +1701,7 @@ config.db_config=Konfiguracja bazy danych config.db_type=Typ config.db_host=Serwer config.db_name=Nazwa +config.db_user=Nazwa użytkownika config.db_ssl_mode=SSL config.db_path=Ścieżka @@ -1191,6 +1723,7 @@ config.mailer_host=Serwer config.mailer_user=Użytkownik config.mailer_use_sendmail=Używaj Sendmail config.mailer_sendmail_path=Ścieżka Sendmail +config.test_mail_sent=Testowa wiadomość e-mail została wysłana do '%s'. config.oauth_config=Konfiguracja OAuth config.oauth_enabled=Włączone @@ -1228,12 +1761,20 @@ config.git_gc_timeout=Limit czasu usuwania śmieci config.log_config=Konfiguracja dziennika config.log_mode=Tryb dziennika +config.go_log=Używa dziennika Go (domyślne przekierowanie) +config.router_log_mode=Tryb dziennika routera +config.disabled_logger=Wyłączone +config.access_log_mode=Tryb dziennika dostępu +config.access_log_template=Szablon +config.xorm_log_mode=Tryb dziennika XORM +config.xorm_log_sql=Dziennik SQL monitor.cron=Zadania cron monitor.name=Nazwa monitor.schedule=Harmonogram monitor.next=Następny czas monitor.previous=Poprzedni czas +monitor.execute_times=Wykonania monitor.process=Uruchomione procesy monitor.desc=Opis monitor.start=Czas rozpoczęcia @@ -1256,18 +1797,24 @@ notices.delete_success=Powiadomienia systemu zostały usunięte. [action] create_repo=tworzy repozytorium %s rename_repo=zmienia nazwę repozytorium %[1]s na %[3]s -create_issue=`zgłasza problem %s#%[2]s` -close_issue=`zamknięcie problemu %s#%[2]s` -reopen_issue=`ponowne otwarcie problemu %s#%[2]s` -create_pull_request=`tworzy pull request %s#%[2]s` -close_pull_request=`zamknięcie pull request %s#%[2]s` -reopen_pull_request=`ponowne otwarcie pull request %s#%[2]s` -comment_issue=`komentuje problem %s#%[2]s` -merge_pull_request=`scala pull request %s#%[2]s` +commit_repo=wypycha do %[3]s w[4]s +create_issue=`otwiera zgłoszenie %s#%[2]s` +close_issue=`zamyka zgłoszenie %s#%[2]s` +reopen_issue=`ponownie otwiera zgłoszenie %s#%[2]s` +create_pull_request=`tworzy Pull Request %s#%[2]s` +close_pull_request=`zamyka Pull Request %s#%[2]s` +reopen_pull_request=`ponownie otwiera Pull Request %s#%[2]s` +comment_issue=`dodaje komentarz w zgłoszeniu %s#%[2]s` +merge_pull_request=`scala Pull Request %s#%[2]s` transfer_repo=przenosi repozytorium %s do %s -delete_tag=usunięto tag %[2]s z %[3]s -delete_branch=usunięto gałąź %[2]s z %[3]s +push_tag=wypycha tag %[2]s do %[3]s +delete_tag=usuwa tag %[2]s z %[3]s +delete_branch=usuwa gałąź %[2]s z %[3]s compare_commits=Porównaj %d commitów +compare_commits_general=Porównaj commity +mirror_sync_push=synchronizuje commity do %[3]s w %[4]s z kopii lustrzanej +mirror_sync_create=synchronizuje nowe odwołanie %[2]s do %[3]s z kopii lustrzanej +mirror_sync_delete=synchronizuje i usuwa odwołanie %[2]s w %[3]s z kopii lustrzanej [tool] ago=%s temu @@ -1292,6 +1839,8 @@ raw_seconds=sekund raw_minutes=minut [dropzone] +default_message=Upuść pliki tutaj lub kliknij, aby przesłać. +invalid_input_type=Nie można przesłać plików tego typu. file_too_big=Rozmiar pliku ({{filesize}} MB) przekracza maksymalny rozmiar ({{maxFilesize}} MB). remove_file=Usuń plik @@ -1299,6 +1848,8 @@ remove_file=Usuń plik notifications=Powiadomienia unread=Nieprzeczytane read=Przeczytane +no_unread=Brak nieprzeczytanych powiadomień. +no_read=Brak przeczytanych powiadomień. pin=Przypnij powiadomienie mark_as_read=Oznacz jako przeczytane mark_as_unread=Oznacz jak nieprzeczytane @@ -1307,8 +1858,12 @@ mark_all_as_read=Oznacz wszystkie jako przeczytane [gpg] error.extract_sign=Nie udało się wyłuskać podpisu error.generate_hash=Nie udało się wygenerować skrótu dla commitu +error.no_committer_account=Brak konta powiązanego z adresem e-mail autora error.no_gpg_keys_found=Nie znaleziono w bazie danych klucza dla tego podpisu error.not_signed_commit=Commit nie podpisany +error.failed_retrieval_gpg_keys=Nie udało się odzyskać żadnego klucza powiązanego z kontem autora [units] +error.no_unit_allowed_repo=Nie masz uprawnień do żadnej sekcji tego repozytorium. +error.unit_not_allowed=Nie masz uprawnień do tej sekcji repozytorium. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 605c13f69b..66c2d9fd4c 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -299,6 +299,7 @@ max_size_error=` deve conter no máximo %s caracteres.` email_error=` não é um endereço de e-mail válido.` url_error=`não é uma URL válida.` include_error=` deve conter '%s'.` +glob_pattern_error=` padrão glob é inválido: %s.` unknown_error=Erro desconhecido: captcha_incorrect=O código CAPTCHA está incorreto. password_not_match=As senhas não coincidem. @@ -317,6 +318,7 @@ enterred_invalid_repo_name=O nome do repositório que você digitou está incorr enterred_invalid_owner_name=O nome do novo proprietário não é válido. enterred_invalid_password=A senha que você digitou está incorreta. user_not_exist=O usuário não existe. +team_not_exist=A equipe não existe. last_org_owner=Você não pode remover o último usuário do time 'proprietários'. Deve haver pelo menos um proprietário em qualquer time. cannot_add_org_to_team=Uma organização não pode ser adicionada como membro de um time. @@ -556,11 +558,17 @@ confirm_delete_account=Confirmar exclusão delete_account_title=Excluir conta de usuário delete_account_desc=Tem certeza que deseja apagar sua conta de usuário permanentemente? +email_notifications.enable=Habilitar notificações de e-mail +email_notifications.onmention=Somente e-mail com menção +email_notifications.disable=Desabilitar notificações de e-mail +email_notifications.submit=Atualizar preferências de e-mail + [repo] owner=Proprietário repo_name=Nome do repositório repo_name_helper=Um bom nome de repositório é composto por palavras curtas, memorizáveis e únicas. visibility=Visibilidade +visibility_description=Somente o proprietário ou os membros da organização, se tiverem direitos, poderão vê-lo. visibility_helper=Tornar este repositório privado visibility_helper_forced=O administrador do site força novos repositórios a serem privados. visibility_fork_helper=(Esta alteração irá afetar todos os forks.) @@ -571,6 +579,8 @@ fork_visibility_helper=A visibilidade do fork de um repositório não pode ser a repo_desc=Descrição repo_lang=Linguagem repo_gitignore_helper=Selecione modelos do .gitignore. +issue_labels=Etiquetas de issue +issue_labels_helper=Selecione um conjunto de etiquetas de issue. license=Licença license_helper=Selecione um arquivo de licença. readme=LEIA-ME @@ -583,7 +593,7 @@ mirror_prune_desc=Remover referências obsoletas de controle remoto mirror_interval=Intervalo de espelhamento (as unidades de tempo válidas são 'h', 'm', 's'). 0 para desativar a sincronização automática. mirror_interval_invalid=O intervalo do espelhamento não é válido. mirror_address=Clonar de URL -mirror_address_desc=Inclua qualquer credencial de autorização necessária na URL. Esta deve ser uma url escapada corretamente +mirror_address_desc=Coloque qualquer credencial necessária na seção de Autorização de Clone. mirror_address_url_invalid=A url fornecida é inválida. Você deve escapar todos os componentes da url corretamente. mirror_address_protocol_invalid=A url fornecida é inválida. Apenas http(s):// ou git:// podem ser espelhados. mirror_last_synced=Última sincronização @@ -695,6 +705,7 @@ editor.delete=Excluir '%s' editor.commit_message_desc=Adicione uma descrição detalhada (opcional)... editor.commit_directly_to_this_branch=Commit diretamente no branch %s. editor.create_new_branch=Crie um novo branch para este commit e crie um pull request. +editor.propose_file_change=Propor alteração de arquivo editor.new_branch_name_desc=Novo nome do branch... editor.cancel=Cancelar editor.filename_cannot_be_empty=Nome do arquivo não pode ser em branco. @@ -768,7 +779,7 @@ issues.self_assign_at=`se auto atribuiu para esta issue %s` issues.add_assignee_at=`foi atribuído por %s %s` issues.remove_assignee_at=`teve sua atribuição removida por %s %s` issues.remove_self_assignment=`removeu sua atribuição %s` -issues.change_title_at=`mudou título de %s para %s %s` +issues.change_title_at=`alterou o título de %s para %s %s` issues.delete_branch_at=`excluiu branch %s %s` issues.open_tab=%d aberto issues.close_tab=%d fechado @@ -825,6 +836,10 @@ issues.create_comment=Comentar issues.closed_at=`fechou %[2]s` issues.reopened_at=`reaberto %[2]s` issues.commit_ref_at=`citou esta issue em um commit %[2]s` +issues.ref_issue_at=`referenciou esta issue %[1]s` +issues.ref_pull_at=`referenciou este pull request %[1]s` +issues.ref_issue_ext_at=`referenciou esta issue de %[1]s %[2]s` +issues.ref_pull_ext_at=`referenciou este pull request de %[1]s %[2]s` issues.poster=Autor issues.collaborator=Colaborador issues.owner=Proprietário @@ -944,7 +959,7 @@ issues.review.reviewers=Revisões issues.review.show_outdated=Mostrar desatualizado issues.review.hide_outdated=Ocultar desatualizado -pulls.desc=Habilitar solicitações de merge e revisões de código. +pulls.desc=Habilitar pull requests e revisões de código. pulls.new=Novo pull request pulls.compare_changes=Novo pull request pulls.compare_changes_desc=Selecione a branch de destino (push) e a branch de origem (pull) para o merge. @@ -963,12 +978,15 @@ pulls.tab_files=Arquivos alterados pulls.reopen_to_merge=Por favor reabra este pull request para aplicar o merge. pulls.cant_reopen_deleted_branch=Este pull request não pode ser reaberto porque o branch foi excluído. pulls.merged=Merge aplicado +pulls.merged_as=O pull request teve merge aplicado como %[2]s. pulls.has_merged=O merge deste pull request foi aplicado. pulls.title_wip_desc=`Inicie o título com o prefixo %s para prevenir o merge do pull request até que o mesmo esteja pronto.` pulls.cannot_merge_work_in_progress=Este pull request está marcado como um trabalho em andamento. Remova o prefixo %s do título quando estiver pronto pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork. pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino. pulls.is_checking=Verificação de conflitos do merge está em andamento. Tente novamente em alguns momentos. +pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas. +pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request. pulls.blocked_by_approvals=Este pull request ainda não possui aprovações suficientes. %d de %d aprovações concedidas. pulls.can_auto_merge_desc=O merge deste pull request pode ser aplicado automaticamente. pulls.cannot_auto_merge_desc=O merge deste pull request não pode ser aplicado automaticamente pois há conflitos. @@ -976,6 +994,7 @@ pulls.cannot_auto_merge_helper=Faça o merge manualmente para resolver os confli pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas. pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente. pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento. +pulls.no_merge_status_check=Este pull request não pode ter seu merge aplicado porque nem todas as verificações de status necessárias foram bem sucedidas. pulls.merge_pull_request=Aplicar merge do pull request pulls.rebase_merge_pull_request=Aplicar Rebase e Merge pulls.rebase_merge_commit_pull_request=Aplicar Rebase e Merge (--no-ff) @@ -1117,6 +1136,7 @@ settings.collaboration=Colaboradores settings.collaboration.admin=Administrador settings.collaboration.write=Escrita settings.collaboration.read=Leitura +settings.collaboration.owner=Proprietário settings.collaboration.undefined=Indefinido settings.hooks=Webhooks settings.githooks=Hooks do Git @@ -1124,6 +1144,10 @@ settings.basic_settings=Configurações básicas settings.mirror_settings=Opções de espelhamento settings.sync_mirror=Sincronizar agora settings.mirror_sync_in_progress=Sincronização do espelhamento está em andamento. Verifique novamente em um minuto. +settings.email_notifications.enable=Habilitar notificações de e-mail +settings.email_notifications.onmention=Somente e-mail com menção +settings.email_notifications.disable=Desabilitar notificações de e-mail +settings.email_notifications.submit=Atualizar preferências de e-mail settings.site=Site settings.update_settings=Atualizar configurações settings.advanced_settings=Configurações avançadas @@ -1194,6 +1218,11 @@ settings.collaborator_deletion_desc=A exclusão de um colaborador irá revogar o settings.remove_collaborator_success=O colaborador foi removido. settings.search_user_placeholder=Pesquisar usuário... settings.org_not_allowed_to_be_collaborator=Organizações não podem ser adicionadas como um colaborador. +settings.change_team_access_not_allowed=Alteração do acesso da equipe para o repositório está restrito ao proprietário da organização +settings.team_not_in_organization=A equipe não está na mesma organização que o repositório +settings.add_team_duplicate=A equipe já tem o repositório +settings.add_team_success=A equipe agora tem acesso ao repositório. +settings.remove_team_success=O acesso da equipe ao repositório foi removido. settings.add_webhook=Adicionar webhook settings.add_webhook.invalid_channel_name=Nome do canal no webhook não pode estar em branco e não pode conter somente o caractere #. settings.hooks_desc=Webhooks automaticamente fazem requisições de HTTP POST para um servidor quando acionados determinados eventos de Gitea. Leia mais no guia de webhooks. @@ -1243,6 +1272,8 @@ settings.event_pull_request=Pull request settings.event_pull_request_desc=Pull request aberto, fechado, reaberto, editado, aprovado, rejeitado, com comentário de revisão, atribuído, não atribuído, etiqueta atualizada, etiqueta limpa ou sincronizada. settings.event_push=Push settings.event_push_desc=Git push para o repositório. +settings.branch_filter=Filtro de branch +settings.branch_filter_desc=Controle de permissão de push, eventos de criação e exclusão de branch, especificados como padrão glob. Se vazio ou *, eventos para todos os branches serão relatados. Veja github.com/gobwas/glob documentação para sintaxe. Exemplos: master, {master,release*}. settings.event_repository=Repositório settings.event_repository_desc=Repositório criado ou excluído. settings.active=Ativo @@ -1293,6 +1324,9 @@ settings.protect_merge_whitelist_committers=Habilitar controle de permissão de settings.protect_merge_whitelist_committers_desc=Permitir que determinados usuários ou equipes possam aplicar merge de pull requests neste branch. settings.protect_merge_whitelist_users=Usuários com permissão para aplicar merge: settings.protect_merge_whitelist_teams=Equipes com permissão para aplicar merge: +settings.protect_check_status_contexts=Habilitar verificação de status +settings.protect_check_status_contexts_desc=Exigir que as verificações de status sejam aprovadas antes de aplicar o merge. Escolha quais verificações de status devem passar antes que os branches possam ter o merge aplicado em um branch que corresponda a esta regra. Quando habilitadas, os commits devem primeiro ser aplicados via push para outro branch, depois aplicado merge ou realizado o push diretamente em um branch que corresponda a esta regra após a verificação de status. Se nenhum contexto for selecionado, o último commit deverá ser bem sucedido, independentemente do contexto. +settings.protect_check_status_contexts_list=Verificações de status encontradas na última semana para este repositório settings.protect_required_approvals=Aprovações necessárias: settings.protect_required_approvals_desc=Permitir apenas merge de pull request com aprovações suficientes de usuários ou equipes com permissão de revisão. settings.protect_approvals_whitelist_users=Usuários com permissão de revisão: @@ -1340,6 +1374,11 @@ diff.whitespace_ignore_at_eol=Ignorar alterações com espaço em branco no fina diff.stats_desc= %d arquivos alterados com %d adições e %d exclusões diff.bin=BIN diff.view_file=Ver arquivo +diff.file_before=Antes +diff.file_after=Depois +diff.file_image_width=Largura +diff.file_image_height=Altura +diff.file_byte_size=Tamanho diff.file_suppressed=Diferenças do arquivo suprimidas por serem muito extensas diff.too_many_files=Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff diff.comment.placeholder=Deixe um comentário @@ -1405,6 +1444,8 @@ branch.deleted_by=Excluído por %s branch.restore_success=A branch '%s' foi restaurada. branch.restore_failed=Falha ao restaurar a branch %s. branch.protected_deletion_failed=A branch '%s' está protegida. Ela não pode ser excluída. +branch.restore=Restaurar branch '%s' +branch.download=Baixar branch '%s' topic.manage_topics=Gerenciar Tópicos topic.done=Feito @@ -1440,6 +1481,8 @@ settings.options=Organização settings.full_name=Nome completo settings.website=Site settings.location=Localização +settings.permission=Permissões +settings.repoadminchangeteam=O administrador do repositório pode adicionar e remover o acesso para equipes settings.visibility=Visibilidade settings.visibility.public=Pública settings.visibility.limited=Limitada (Visível apenas para usuários registrados) @@ -1686,6 +1729,7 @@ auths.tip.google_plus=Obter credenciais de cliente OAuth2 do console de API do G auths.tip.openid_connect=Use o OpenID Connect Discovery URL (/.well-known/openid-configuration) para especificar os endpoints auths.tip.twitter=Vá em https://dev.twitter.com/apps, crie um aplicativo e certifique-se de que está habilitada a opção “Allow this application to be used to Sign in with Twitter“ auths.tip.discord=Cadastrar um novo aplicativo em https://discordapp.com/developers/applications/me +auths.tip.gitea=Cadastrar um novo aplicativo OAuth2. Guia pode ser encontrado em https://docs.gitea.io/en-us/oauth2-provider/ auths.edit=Editar fonte de autenticação auths.activated=Esta fonte de autenticação está ativada auths.new_success=A autenticação '%s' foi adicionada. diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 4c84dd1201..475129ce36 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -541,6 +541,7 @@ confirm_delete_account=Подтвердите удаление delete_account_title=Удалить аккаунт delete_account_desc=Вы уверены, что хотите навсегда удалить этот аккаунт? + [repo] owner=Владелец repo_name=Имя репозитория @@ -740,7 +741,6 @@ issues.deleted_milestone=`(удалено)` issues.self_assign_at=`самоназначился %s` issues.add_assignee_at=`был назначен %s %s` issues.remove_assignee_at=`был снят с назначения %s %s` -issues.change_title_at=`поменял заголовок задачи с %s на %s %s` issues.delete_branch_at=`удалена ветка %s %s` issues.open_tab=%d открыто(ы) issues.close_tab=%d закрыто(ы) @@ -913,7 +913,6 @@ issues.review.reviewers=Рецензенты issues.review.show_outdated=Показать устаревшие issues.review.hide_outdated=Скрыть устаревшие -pulls.desc=Включить Pull Request'ы и интерфейс согласования правок. pulls.new=Новый Pull Request pulls.compare_changes=Новый Pull Request pulls.compare_changes_desc=Сравнить две ветки и создать запрос на слияние для изменений. diff --git a/options/locale/locale_sr-SP.ini b/options/locale/locale_sr-SP.ini index 18ea2f0a45..8b0d5743fb 100644 --- a/options/locale/locale_sr-SP.ini +++ b/options/locale/locale_sr-SP.ini @@ -183,6 +183,7 @@ delete_token=Уклони delete_account=Уклоните ваш налог confirm_delete_account=Потврдите брисање + [repo] owner=Власник repo_name=Име спремишта diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 001bcfbaac..a954fd2a9a 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -476,6 +476,7 @@ confirm_delete_account=Bekräfta Borttagelsen delete_account_title=Ta Bort Användarkonto delete_account_desc=Är du säker på att du vill ta bort ditt konto permanent? + [repo] owner=Ägare repo_name=Utvecklingskatalogens namn @@ -653,7 +654,6 @@ issues.remove_milestone_at='tog bort denna från milstolpen %s %s' issues.deleted_milestone=`(raderad)` issues.self_assign_at=`tilldelade denna till sig själv %s` issues.add_assignee_at=`blev tilldelad denna av %s %s` -issues.change_title_at='ändrade titeln från %s till %s %s' issues.delete_branch_at='tog bort grenen %s %s' issues.open_tab=%d Öppna issues.close_tab=%d Stängda @@ -772,7 +772,6 @@ issues.review.self.approval=Du kan inte godkänna din egen pull-begäran. issues.review.approve=godkände dessa ändringar %s issues.review.hide_outdated=Dölj föråldrade -pulls.desc=Aktivera merge-requests och kodgranskning. pulls.new=Ny Pull-Förfrågan pulls.compare_changes=Ny Pull-Request pulls.compare_changes_desc=Välj branchen att merga in i, och ifrån. diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index b8e8427287..9fa1b7d42b 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1,10 +1,13 @@ +app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git hizmeti home=Ana Sayfa dashboard=Pano explore=Keşfet help=Yardım sign_in=Giriş Yap +sign_in_with=Şununla giriş yap sign_out=Çıkış Yap +sign_up=Kaydol link_account=Bağlantı hesabı register=Üye Ol website=Web sitesi @@ -13,12 +16,31 @@ page=Sayfa template=Şablon language=Dil notifications=Bildirimler +create_new=Oluştur… +user_profile_and_more=Profil ve Ayarlar… signed_in_as=Giriş yapan: +enable_javascript=Bu web sitesi JavaScript ile daha iyi çalışır. username=Kullanıcı Adı +email=E-posta Adresi password=Parola +re_type=Parolayı yeniden yazın +captcha=CAPTCHA +twofa=İki Aşamalı Doğrulama passcode=Şifre +u2f_insert_key=Güvenlik anahtarınızı girin +u2f_sign_in=Güvenlik anahtarınızdaki düğmeye basın. Eğer düğme yoksa güvenlik anahtarınızı tekrar girin. +u2f_press_button=Lütfen güvenlik anahtarınızın düğmesine basın… +u2f_use_twofa=Telefonunuzdan iki faktörlü doğrulama kodu kullanın +u2f_error=Güvenlik anahtarınız okunamıyor. +u2f_unsupported_browser=Tarayıcınız iki faktörlü doğrulama güvenlik anahtarlarını desteklemiyor. +u2f_error_1=Bilinmeyen bir hata oluştu. Lütfen tekrar deneyin. +u2f_error_2=Lütfen doğru, şifrelenmiş (https://) URL kullandığınızdan emin olun. +u2f_error_3=Sunucu isteğinizi işleyemedi. +u2f_error_4=Bu istek için güvenlik anahtarının izni yok. Anahtarın halihazırda kayıtlı olmadığından emin olun. +u2f_error_5=Anahtarınız okunamadan önce zaman aşımı oldu. Lütfen sayfayı yenileyin ve tekrar deneyin. +u2f_reload=Yeniden yükle repository=Depo organization=Organizasyon @@ -29,8 +51,12 @@ new_mirror=Yeni Yansıma new_fork=Yeni Depo Bölünmesi new_org=Yeni Organizasyon manage_org=Organizasyonları Yönet +admin_panel=Site Yönetimi account_settings=Hesap Ayarları settings=Ayarlar +your_profile=Profil +your_starred=Yıldızlı +your_settings=Ayarlar all=Tümü sources=Kaynaklar @@ -44,37 +70,107 @@ issues=Sorunlar cancel=İptal +write=Yaz +preview=Önizleme +loading=Yükleniyor… [install] install=Kurulum +title=Başlangıç Yapılandırması +docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce belgeleri okuyun. +requite_db_desc=Gitea MySQL, PostgreSQL, MSSQL, veya SQLite3'e ihtiyaç duyar. db_title=Veritabanı Ayarları db_type=Veritabanı Türü host=Sunucu +user=Kullanıcı adı password=Parola db_name=Veritabanı Adı +db_helper=MySQL kullanıcıları için not: lütfen InnoDB depolama motorunu kullanın ve "utf8mb4" kullanıyorsanız, InnoDB sürümünüz 5.6'dan büyük olmalıdır. +ssl_mode=SSL +charset=Karakter kümesi path=Yol +sqlite_helper=SQLite3 veritabanı dosya yolu.
    Gitea'yı servis olarak çalıştırıyorsanız tam yol adını girin. +err_empty_db_path=SQLite3 veritabanı dosya yolu boş olamaz. +no_admin_and_disable_registration=Bir yönetici hesabı oluşturmadan kullanıcı kaydını kapatamazsınız. +err_empty_admin_password=Yönetici parolası boş olamaz. +err_empty_admin_email=Yönetici e-postası boş olamaz. +err_admin_name_is_reserved=Yönetici Kullanıcı Adı geçersiz, bu kullanıcı adı rezerv edilen bir kelimedir +err_admin_name_pattern_not_allowed=Yönetici Kullanıcı Adı geçersiz, bu kullanıcı adı desenine izin verilmiyor +err_admin_name_is_invalid=Yönetici Kullanıcı Adı geçersiz +general_title=Genel Ayarlar +app_name=Site Başlığı +app_name_helper=Şirket adınızı buraya girebilirsiniz. repo_path=Depo Kök Dizini +repo_path_helper=Tüm uzak Git depoları bu dizine kaydedilecektir. +lfs_path=Git LFS Kök Yolu +lfs_path_helper=Git LFS tarafından izlenen dosyalar bu dizinde saklanacaktır. LFS'yi devre dışı bırakmak için boş bırakın. +run_user=Şu Kullanıcı Olarak Çalıştır +run_user_helper=Gitea'nin çalışacağı, işletim sistemi kullanıcı adını giriniz. Bu kullanıcının depo kök yoluna erişiminin olması gerektiğini unutmayın. +domain=SSH Sunucusu Alan Adı +domain_helper=SSH kopyalama URL'leri için alan adı veya sunucu adresi. +ssh_port=SSH Sunucu Portu +http_port=Gitea HTTP Dinleme Portu +http_port_helper=Gitea'nın web sunucusunun dinleyeceği port numarası. log_root_path=Günlük Dosyaları Yolu +log_root_path_helper=Günlük dosyaları bu dizine kaydedilecektir. optional_title=İsteğe Bağlı Ayarlar +email_title=E-posta Ayarları smtp_host=SMTP Sunucusu +smtp_from=E-posta Gönderen +smtp_from_helper=Gitea'nın kullanacağı e-posta adresi. Yalın bir e-posta adresi girin veya "İsim" biçimini kullanın. +mailer_user=SMTP Kullanıcı Adı +mailer_password=SMTP Parolası +register_confirm=Kayıt için E-posta Doğrulaması Gereksin +mail_notify=E-Posta Bildirimlerini Etkinleştir +offline_mode=Yerel Kipi Etkinleştir +disable_gravatar=Gravatar'ı Devre Dışı Bırak federated_avatar_lookup_popup=Enable federated avatars lookup to use federated open source service based on libravatar. +disable_registration=Kendi Kendine Kaydolmayı Devre Dışı Bırak +disable_registration_popup=Kullanıcının kendi kendine kaydolmasını devre dışı bırak. Yalnızca yöneticiler yeni hesaplar oluşturabilecek. +allow_only_external_registration_popup=Sadece dış hizmetler aracılığıyla kullanıcı kaydına izin ver openid_signin=OpenID Oturum Açmayı Etkinleştiriniz +openid_signin_popup=OpenID ile kullanıcı girişini etkinleştir. +openid_signup=OpenID ile Kendi Kendine Kaydı Etkinleştir +openid_signup_popup=OpenID Tabanlı Kendi Kendi Kullanıcı Kaydını Etkinleştir. +enable_captcha=CAPTCHA'yı Etkinleştir enable_captcha_popup=Kullanıcının kendi kendine kaydolması için captcha doğrulaması gereksin. +require_sign_in_view=Sayfaları Görüntülemek için Giriş Yapmak Gereksin +require_sign_in_view_popup=Sayfa erişimini giriş yapmış kullanıcılarla sınırlandır. Ziyaretçiler sadece 'oturum açma' ve kayıt sayfalarını görecektir. +admin_setting_desc=Bir yönetici hesabı açmak isteğe bağlıdır. İlk kayıtlı kullanıcı kendiliğinden yönetici olmaktadır. +admin_title=Yönetici Hesabı Ayarları +admin_name=Yönetici Kullanıcı Adı admin_password=Parola confirm_password=Parolayı Doğrula +admin_email=E-posta Adresi install_btn_confirm=Gitea'u Kur test_git_failed='git' komut testi başarısız: %v +sqlite3_not_available=Bu Gieta sürümü SQLite3 desteklemiyor. Lütfen %s adresinden resmi çalışır sürümü ('gobuild' sürümünü değil) indirin. +invalid_db_setting=Veritabanı ayarları geçersiz: %v +invalid_repo_path=Depo kök dizini geçersiz: %v save_config_failed=%v Yapılandırması kaydedilirken hata oluştu +invalid_admin_setting=Yönetici hesap ayarları geçersiz: %v +install_success=Hoşgeldiniz! Gitea'yı seçtiğiniz için teşekkür ederiz. Eğlenin ve kendinize iyi bakın! +invalid_log_root_path=Log dosya yolu geçersiz: %v +default_keep_email_private=E-posta adreslerini varsayılan olarak gizle +default_keep_email_private_popup=Yeni kullanıcı hesaplarının e-posta adreslerini varsayılan olarak gizle. +default_allow_create_organization=Varsayılan Olarak Organizasyon Oluşturmaya İzin Ver +default_allow_create_organization_popup=Varsayılan olarak yeni kullanıcı hesaplarının organizasyon oluşturmasına izin ver. +no_reply_address=Gizlenecek E-Posta Alan Adı +no_reply_address_helper=Gizlenmiş e-posta adresine sahip kullanıcılar için alan adı. Örneğin 'ali' kullanıcı adı, gizlenmiş e-postalar için alan adı 'yanityok.ornek.org' olarak ayarlandığında Git günlüğüne 'ali@yanityok.ornek.org' olarak kaydedilecektir. [home] +uname_holder=Kullanıcı Adı veya E-Posta Adresi password_holder=Parola switch_dashboard_context=Panoya Geçiş Yap +my_repos=Depolar +show_more_repos=Daha fazla depo göster… collaborative_repos=Katkıya Açık Depolar my_orgs=Organizasyonlarım my_mirrors=Yansılarım view_home=%s Görüntüle +search_repos=Depo bul… issues.in_your_repos=Depolarınızda @@ -83,42 +179,87 @@ repos=Depolar users=Kullanıcılar organizations=Organizasyonlar search=Ara +code=Kod +repo_no_results=Eşleşen bir depo bulunamadı. +user_no_results=Eşleşen kullanıcı bulunamadı. +org_no_results=Eşleşen organizasyon bulunamadı. +code_no_results=Aranan terimlerle eşleşen bir kaynak kod bulunamadı. +code_search_results='%s' için arama sonuçları [auth] +create_new_account=Hesap Oluştur register_helper_msg=Bir hesabınız var mı? Şimdi giriş yapın! +social_register_helper_msg=Hesabınız var mı? Hemen bağlayın! +disable_register_prompt=Kayıt işlemi devre dışıdır. Lütfen site yöneticisiyle iletişim kurun. +disable_register_mail=Kayıt için e-posta doğrulama devre dışıdır. remember_me=Beni Hatırla forgot_password_title=Şifremi unuttum forgot_password=Şifrenizi mi unuttunuz? +sign_up_now=Bir hesaba mı ihtiyacınız var? Hemen kaydolun. +sign_up_successful=Hesap başarılı bir şekilde oluşturuldu. confirmation_mail_sent_prompt=Yeni onay e-postası %s adresine gönderildi. Lütfen gelen kutunuzu bir sonraki %s e kadar kontrol edip kayıt işlemini tamamlayın. +must_change_password=Parolanızı güncelleyin +allow_password_change=Kullanıcıyı parola değiştirmeye zorla (önerilen) +reset_password_mail_sent_prompt=%s adresine bir onay e-postası gönderildi. Hesap kurtarma işlemini tamamlamak için lütfen gelen kutunuzu sonraki %s içinde kontrol edin. active_your_account=Hesabınızı Aktifleştirin +account_activated=Hesap etkinleştirildi +resent_limit_prompt=Zaten bir doğrulama e-postası talep ettiniz. Lütfen 3 dakika bekleyip tekrar deneyin. has_unconfirmed_mail=Merhaba %s, doğrulanmamış bir e-posta adresin var (%s). Bir doğrulama e-postası almadıysanız ya da yenisine ihtiyacınız varsa lütfen aşağıdaki düğmeye tıklayın. resend_mail=Doğrulama e-postasını tekrar almak için buraya tıklayın email_not_associate=Bu e-posta adresi hiçbir hesap ile ilişkilendirilmemiştir. +send_reset_mail=Hesap Kurtarma E-postası Gönder +reset_password=Hesap Kurtarma +invalid_code=Doğrulama kodunuz geçersiz veya süresi dolmuş. +reset_password_helper=Hesabı Kurtar +reset_password_wrong_user=%s olarak oturum açtınız, ancak hesap kurtarma bağlantısı %s için +password_too_short=Parolanız en az %d karakter uzunluğunda olmalıdır. +non_local_account=Yerel olmayan kullanıcılar parolalarını Gitea web arayüzünden güncelleyemezler. verify=Doğrula scratch_code=Çizgi kodu use_scratch_code=Bir çizgi kodu kullanınız twofa_scratch_used=Çizgi kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada cihaz kaydınızı kaldırabilir veya yeni bir çizgi kodu oluşturabilirsiniz. twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir. +login_userpass=Oturum Aç login_openid=Açık Kimlik +oauth_signup_tab=Hesap oluştur +oauth_signup_title=E-posta ve Şifre Ekle (Hesap Kurtarma için) +oauth_signup_submit=Hesabı Tamamla +oauth_signin_tab=Mevcut Hesaba Bağla +oauth_signin_title=Bağlantılı Hesabı Yetkilendirmek için Giriş Yapın +oauth_signin_submit=Hesabı Bağla openid_connect_submit=Bağlan openid_connect_title=Mevcut olan bir hesaba bağlan +openid_connect_desc=Seçilen OpenID URI'si bilinmiyor. Burada yeni bir hesapla ilişkilendir. openid_register_title=Yeni hesap oluştur +openid_register_desc=Seçilen OpenID URI'si bilinmiyor. Burada yeni bir hesapla ilişkilendir. +disable_forgot_password_mail=Hesap kurtarma devre dışı. Lütfen site yöneticinizle iletişime geçin. +email_domain_blacklisted=Bu e-posta adresinizle kayıt olamazsınız. +authorize_application=Uygulamayı Yetkilendir +authroize_redirect_notice=Bu uygulamayı yetkilendirirseniz %s adresine yönlendirileceksiniz. +authorize_application_created_by=Bu uygulama %s tarafından oluşturuldu. +authorize_application_description=Erişime izin verirseniz, özel depolar ve organizasyonlar da dahil olmak üzere tüm hesap bilgilerinize erişebilir ve yazabilir. +authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi? +authorization_failed=Yetkilendirme başarısız oldu +authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun. [mail] activate_account=Lütfen hesabınızı aktifleştirin activate_email=E-posta adresinizi doğrulayın +reset_password=Hesabınızı kurtarın register_success=Kayıt başarılı register_notify=Gitea'ya Hoş Geldiniz [modal] yes=Evet no=Hayır +modify=Güncelle [form] UserName=Kullanıcı Adı RepoName=Depo adı Email=E-posta adresi Password=Parola +Retype=Parolayı yeniden yazın SSHTitle=SSH anahtarının adı HttpsUrl=HTTPS Bağlantısı PayloadUrl=Yük Bağlantısı @@ -134,155 +275,342 @@ TreeName=Dosya yolu Content=İçerik require_error=` boş olamaz.` +alpha_dash_error=` yalnızca alfasayısal, çizgi ('-') ve alt çizgi ('_') karakterlerini içermelidir. ` +alpha_dash_dot_error=` yalnızca alfasayısal, çizgi ('-'), alt çizgi ('_') ve nokta ('.') karakterlerini içermelidir. ` size_error=` uzunluk en fazla %s olmalıdır.` min_size_error=` en az %s karakter içermelidir.` max_size_error=` en fazla %s karakter içermelidir.` email_error=' geçerli bir e-posta adresi değil.' url_error=` geçerli bir bağlantı değil.` include_error=` '%s' içermelidir.` +glob_pattern_error=` glob deseni geçersiz: %s.` unknown_error=Bilinmeyen hata: +captcha_incorrect=CAPTCHA eşleşmedi. +password_not_match=Parolalar uyuşmuyor. +username_been_taken=Bu kullanıcı adı daha önce alınmış. +repo_name_been_taken=Depo adı zaten kullanılıyor. +org_name_been_taken=Organizasyon adı zaten kullanılıyor. +team_name_been_taken=Takım adı zaten alınmış. +team_no_units_error=En az bir depo bölümüne erişimine izin ver. +email_been_used=E-posta adresi zaten kullanılıyor. +openid_been_used=OpenID adresi '%s' zaten kullanılıyor. +username_password_incorrect=Kullanıcı adı veya parola hatalı. +enterred_invalid_repo_name=Girdiğiniz depo adı hatalı. +enterred_invalid_owner_name=Yeni sahip ismi hatalı. +enterred_invalid_password=Girdiğiniz parola hatalı. user_not_exist=Böyle bir kullanıcı yok. +team_not_exist=Böyle bir takım bulunmuyor. +last_org_owner=Son kullanıcıyı 'sahipler' takımından kaldıramazsınız. Herhangi bir takımda en az bir sahip olması gerekir. +cannot_add_org_to_team=Organizasyon, takım üyesi olarak eklenemez. +invalid_ssh_key=SSH anahtarınız doğrulanamıyor: %s +invalid_gpg_key=GPG anahtarınız doğrulanamıyor: %s +unable_verify_ssh_key=SSH anahtarı doğrulanamıyor; hatalar için lütfen tekrar kontrol edin. auth_failed=Kimlik doğrulaması başarısız oldu: %v +still_own_repo=Hesabınız bir veya daha fazla depoya sahip; önce onları silin veya transfer edin. +still_has_org=Hesabınız bir veya daha fazla organizasyonun üyesi; öncelikle onlardan ayrılın. +org_still_own_repo=Bu organizasyon hala bir veya daha fazla depoya sahip; önce onları silin veya transfer edin. target_branch_not_exist=Hedef dal mevcut değil. [user] +change_avatar=Profil resmini değiştir… join_on=Şuna katıldınız repositories=Depolar activity=Genel Aktivite followers=Takipçiler +starred=Yıldızlanmış depolar following=Takip Edilenler follow=Takip Et unfollow=Takibi Bırak +heatmap.loading=Heatmap yükleniyor… +user_bio=Biyografi form.name_reserved=%s Kullanıcı adı rezerve edilmiş. +form.name_pattern_not_allowed=Kullanıcı adında '%s' deseni kullanılamaz. [settings] profile=Profil +account=Hesap password=Parola security=Güvenlik avatar=Avatar ssh_gpg_keys=SSH / GPG Anahtarları social=Sosyal Medya Hesapları +applications=Uygulamalar +orgs=Organizasyonları Yönet repos=Depolar delete=Hesabı Sil twofa=İki Aşamalı Doğrulama +account_link=Bağlı Hesaplar +organization=Organizasyonlar uid=Tekil ID +u2f=Güvenlik Anahtarları public_profile=Herkese Açık Profil +profile_desc=E-posta adresiniz bilgilendirmeler ve diğer işlemler için kullanılacaktır. +password_username_disabled=Yerel olmayan kullanıcılara kullanıcı adlarını değiştirme izni verilmemiştir. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz. full_name=Ad Soyad website=Web Sitesi location=Konum +update_theme=Temayı Güncelle update_profile=Profili Güncelle update_profile_success=Profil resminiz güncellendi. +change_username=Kullanıcı adınız değiştirildi. +change_username_prompt=Not: Kullanıcı adı değişiklikleri hesap URL'nizi de değiştirir. continue=Devam Et cancel=İptal +language=Dil +ui=Tema federated_avatar_lookup=Birleşmiş Avatar Araması enable_custom_avatar=Özel Avatarı Etkinleştir choose_new_avatar=Yeni Avatar Seç +update_avatar=Profil Resmini Güncelle delete_current_avatar=Güncel Avatarı Sil +uploaded_avatar_not_a_image=Yüklenen dosya bir resim dosyası değil. +uploaded_avatar_is_too_big=Yüklenen dosya maksimum boyutu aştı. +update_avatar_success=Profil resminiz değiştirildi. +change_password=Parolayı Güncelle old_password=Mevcut Parola new_password=Yeni Parola +retype_new_password=Yeni parolayı tekrar yazın +password_incorrect=Mevcut parola hatalı. +change_password_success=Parolanız güncelleştirildi. Şu andan itibaren yeni parolanızı kullanarak oturum açın. +password_change_disabled=Yerel olmayan kullanıcılar parolalarını Gitea web arayüzünden güncelleyemezler. emails=E-Posta Adresleri +manage_emails=E-posta Adreslerini Yönet +manage_themes=Varsayılan temayı seç +manage_openid=OpenID Adreslerini Yönet email_desc=Birincil e-posta adresiniz bilgilendirmeler ve diğer işlemler için kullanılacaktır. +theme_desc=Bu, sitedeki varsayılan temanız olacak. primary=Birincil +primary_email=Birincil Yap +delete_email=Kaldır +email_deletion=E-posta Adresini Kaldır +email_deletion_desc=E-posta adresi ve ilgili bilgiler hesabınızdan kaldırılacak. Bu e-posta adresi tarafından yapılan işlemeler değişmeden kalacaktır. Devam edilsin mi? +email_deletion_success=E-posta adresi kaldırıldı. +theme_update_success=Temanız güncellendi. +theme_update_error=Seçilen tema mevcut değil. +openid_deletion=OpenID Adresini Kaldır +openid_deletion_desc=Hesabınızdan bu OpenID adresini silmek, onunla giriş yapmanızı engeller. Devam? +openid_deletion_success=OpenID adresi kaldırıldı. +add_new_email=Yeni E-posta Adresi Ekle +add_new_openid=Yeni OpenID URI'si Ekle +add_email=E-posta Adresi Ekle add_openid=Açık Kimlik URI 'si ekle +add_email_confirmation_sent='%s' adresine yeni bir doğrulama e-postası gönderildi. E-postanızı doğrulamak için %s içinde gelen kutunuzu kontrol ediniz. +add_email_success=Yeni e-posta adresi eklendi. +add_openid_success=Yeni OpenID adresi eklendi. +keep_email_private=E-posta Adresini Gizle +keep_email_private_popup=E-posta adresiniz diğer kullanıcılardan gizlenir. +openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar. manage_ssh_keys=SSH Anahtarlarını Yönet manage_gpg_keys=GPH Anahtarlarını Yönet add_key=Anahtar Ekle +ssh_desc=Bu genel SSH anahtarları hesabınızla ilişkilendirildi. İlgili özel anahtarlar, depolarınıza tam erişim sağlar. +gpg_desc=Bu açık GPG anahtarları hesabınızla ilişkilendirildi. İşlemelerin doğrulanmasına izin verdiği için özel anahtarlarınızı güvende tutun. ssh_helper=Yardıma ihtiyacınız mı var? Github klavuzundaki Kendi SSH anahtarınızı oluşturun bölümüne göz atın veya SSH'ı kullanırken karşılaşabileceğinizortak problemler'i çözün. gpg_helper=Yardıma ihtiyacınız mı var?Github klavuzundaki GPG hakkında bölümüne göz atınız. add_new_key=SSH Anahtarı Ekle add_new_gpg_key=GPG Anahtarı Ekle +ssh_key_been_used=Bu SSH anahtarı, sunucuya zaten eklenmiş. +ssh_key_name_used=Aynı isimde bir SSH anahtarı zaten hesabınıza eklenmiş. +gpg_key_id_used=Aynı kimliğe sahip bir açık GPG anahtarı zaten var. +gpg_no_key_email_found=Bu GPG anahtarı, hesabınızla ilişkili hiçbir e-posta adresiyle kullanılamaz. subkeys=Alt anahtarlar key_id=Anahtar Kimliği key_name=Anahtar İsmi key_content=İçerik +add_key_success=SSH anahtarı '%s' eklendi. +add_gpg_key_success=GPG anahtarı '%s' eklendi. +delete_key=Sil +ssh_key_deletion=SSH Anahtarını Sil +gpg_key_deletion=GPG Anahtarını Sil +ssh_key_deletion_desc=Bir SSH anahtarını kaldırmak, hesabınıza erişimi iptal eder. Devam edilsin mi? +gpg_key_deletion_desc=Bir GPG anahtarını kaldırmak, onun tarafından imzalanan işlemelerin doğrulamasını iptal eder. Devam edilsin mi? +ssh_key_deletion_success=SSH anahtarı silindi. +gpg_key_deletion_success=GPG anahtarı silindi. add_on=Eklendiği tarih valid_until=-E kadar geçerli valid_forever=Sürekli geçerlidir last_used=Son kullanım no_activity=Yeni aktivite yok +can_read_info=Oku +can_write_info=Yaz key_state_desc=Bu anahtar son 7 gün içinde kullanılmıştır token_state_desc=Bu token son 7 gün içinde kullanılmıştır show_openid=Profilde göster hide_openid=Profilden gizle +ssh_disabled=SSH devre dışı bırakıldı manage_social=Bağlanmış Sosyal Hesapları Yönet +social_desc=Bu sosyal hesaplar Gitea hesabınızla bağlantılı. Hepsini Gitea hesabınıza giriş yapmak için kullanılabildiğinden emin olun. +unbind=Bağlantıyı Kaldır +unbind_success=Sosyal hesabın bağlantısı Gitea hesabınızdan kaldırılmıştır. +manage_access_token=Erişim Jetonlarını Yönet generate_new_token=Yeni Erişim Anahtarı Üret +tokens_desc=Bu jetonlar Gitea API'sini kullanarak hesabınıza erişim sağlar. +new_token_desc=Jeton kullanan uygulamalar hesabınıza tam erişime sahiptir. token_name=Erişim Anahtarı İsmi generate_token=Erişim Anahtarı Üret +generate_token_success=Yeni bir jeton oluşturuldu. Tekrar gösterilmeyeceği için şimdi kopyalayın. delete_token=Sil +access_token_deletion=Erişim Jetonunu Sil +access_token_deletion_desc=Bir jetonu silmek, onu kullanan uygulamalar için hesabınıza erişimi iptal eder. Devam edilsin mi? +delete_token_success=Jeton silindi. Onu kullanan uygulamalar artık hesabınıza erişemez. +manage_oauth2_applications=OAuth2 Uygulamalarını Yönet +edit_oauth2_application=OAuth2 Uygulamalarını Düzenle +oauth2_applications_desc=OAuth2 uygulamaları, üçüncü parti uygulamanızın bu Gitea örneğinde kullanıcıları güvenli bir şekilde doğrulamasını sağlar. +remove_oauth2_application=OAuth2 Uygulamasını Kaldır +remove_oauth2_application_desc=Bir OAuth2 uygulamasının kaldırılması, imzalı tüm erişim jetonlarına erişimi iptal eder. Devam edilsin mi? +remove_oauth2_application_success=Uygulama silindi. +create_oauth2_application=Yeni bir OAuth2 Uygulaması Oluştur +create_oauth2_application_button=Uygulama Oluştur +create_oauth2_application_success=Başarıyla yeni bir OAuth2 uygulaması oluşturdunuz. +update_oauth2_application_success=OAuth2 uygulamasını başarıyla güncellediniz. +oauth2_application_name=Uygulama Adı +oauth2_select_type=Hangi uygulama türü uyuyor? +oauth2_type_web=Web (ör. Node.JS, Tomcat, Go) +oauth2_type_native=Yerel (ör. Mobil, Masaüstü, Tarayıcı) +oauth2_redirect_uri=Yönlendirme URI'si +save_application=Kaydet +oauth2_client_id=İstemci Kimliği +oauth2_application_edit=Düzenle +revoke_key=İptal Et +revoke_oauth2_grant=Erişimi İptal Et +revoke_oauth2_grant_success=Erişimi başarıyla iptal ettiniz. twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmiş. twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş. twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı. scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın: or_enter_secret=Veya gizli şeyi girin: %s +then_enter_passcode=Ve uygulamada gösterilen şifreyi girin: +passcode_invalid=Şifre geçersiz. Tekrar deneyin. +u2f_register_key=Güvenlik Anahtarı Ekle +u2f_nickname=Takma Ad +u2f_delete_key=Güvenlik Anahtarını Sil +manage_account_links=Bağlı Hesapları Yönet +manage_account_links_desc=Bu harici hesaplar Gitea hesabınızla bağlantılı. +account_links_not_available=Şu anda Gitea hesabınıza bağlı harici bir hesap yok. +remove_account_link=Bağlantılı Hesabı Kaldır +remove_account_link_desc=Bağlantılı bir hesabı kaldırmak, onunla Gitea hesabınıza erişimi iptal edecektir. Devam edilsin mi? +remove_account_link_success=Bağlantılı hesap kaldırıldı. orgs_none=Herhangi bir organizasyonun bir üyesi değilsiniz. repos_none=Herhangi bir depoya sahip değilsiniz delete_account=Hesabınızı Silin +delete_prompt=Bu işlem kullanıcı hesabınızı kalıcı olarak siler. Bu işlem GERİ ALINAMAZ. confirm_delete_account=Silmeyi Onayla +delete_account_title=Kullanıcı Hesabını Silin +delete_account_desc=Bu kullanıcı hesabını kalıcı olarak silmek istediğinizden emin misiniz? + +email_notifications.enable=E-posta Bildirimlerini Etkinleştir +email_notifications.onmention=Sadece Bahsedilen E-posta +email_notifications.disable=E-posta Bildirimlerini Devre Dışı Bırak +email_notifications.submit=E-posta Tercihlerini Ayarla [repo] owner=Sahibi repo_name=Depo İsmi +repo_name_helper=İyi bir depo ismi kısa, akılda kalıcı ve özgün anahtar kelimelerden oluşur. visibility=Görünürlük +visibility_description=Yalnızca sahibi veya haklara sahip organizasyon üyeleri onu görebilecek. +visibility_helper=Depoyu Gizli Yap +visibility_helper_forced=Site yöneticiniz, yeni depoları gizli olmaya zorluyor. +visibility_fork_helper=(Bunu değiştirmek tüm çatallamaları etkileyecektir.) +clone_helper=Klonlama konusunda yardıma mı ihtiyacınız var? Yardım adresini ziyaret edin. fork_repo=Depoyu Çatalla fork_from=Buradan Çatalla +fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez. repo_desc=Açıklama repo_lang=Dil +repo_gitignore_helper=.gitignore şablonlarını seç. +issue_labels=Konu Etiketleri +issue_labels_helper=Bir konu etiket seti seçin. license=Lisans +license_helper=Bir lisans dosyası seçin. +readme=README +readme_helper=Bir README dosyası şablonu seçin. +auto_init=Depoyu başlat (.gitignore, Lisans ve README dosyalarını ekler) create_repo=Depo Oluştur default_branch=Varsayılan Dal mirror_prune=Buda +mirror_interval_invalid=Yansı süre aralığı geçerli değil. +mirror_address=URL'den Klonla +mirror_address_protocol_invalid=Sağlanan url geçersiz. Yalnızca http(s):// veya git:// konumları yansıtılabilir. +mirror_last_synced=Son Senkronize Edilen watchers=İzleyenler stargazers=Yıldızlayanlar forks=Çatallamalar pick_reaction=Reaksiyonunu seç reactions_more=ve %d daha fazla +archive.title=Bu depo arşivlendi. Dosyaları görüntüleyebilir ve klonlayabilirsiniz ama işlem gönderemez ve konu/değişiklik isteği açamazsınız. +archive.issue.nocomment=Bu depo arşivlendi. Konular bölümünde yorum yapamazsınız. +archive.pull.nocomment=Bu depo arşivlendi. Değişiklik istekleri bölümünde yorum yapamazsınız. form.reach_limit_of_creation=Zaten %d depo limitinize ulaştınız. form.name_reserved=Depo ismi '%s' rezerve edildi. +form.name_pattern_not_allowed='%s' deseni, depo adı için geçerli değildir. +need_auth=Yetkilendirmeyi Klonla migrate_type=Göç Türü migrate_type_helper=Bu depo bir yansı olacaktır +migrate_items=Göç Öğeleri +migrate_items_wiki=Wiki +migrate_items_milestones=Kilometre Taşları +migrate_items_labels=Etiketler +migrate_items_issues=Konular +migrate_items_pullrequests=Değişiklik İstekleri +migrate_items_releases=Sürümler migrate_repo=Depoyu Göç Ettir +migrate.clone_address=URL'den Taşı / Klonla +migrate.clone_address_desc=Varolan bir deponun HTTP(S) veya Git 'klonlama' URL'si +migrate.clone_local_path=veya bir yerel sunucu yolu migrate.permission_denied=Yerel depoları içeri aktarma izniniz yok. +migrate.invalid_local_path=Yerel yol geçersiz. Mevcut değil veya bir dizin değil. migrate.failed=Göç başarısız: %v +migrate.lfs_mirror_unsupported=LFS nesnelerini yansılama desteklenmiyor - yerine 'git lfs fetch --all' ve 'git lfs push --all' kullanın. +migrate.migrate_items_options=Github'dan göç yaparken, bir kullanıcı adı girin ve göç seçenekleri görüntülenecektir. +migrated_from_fake=%[1]s Konumundan Taşındı mirror_from=şunun yansıması forked_from=şundan çatallanmış +fork_from_self=Sahibi olduğunuz bir depoyu çatallayamazsınız. +fork_guest_user=Bu depoyu çatallamak için giriş yap. copy_link=Kopyala +copy_link_success=Bağlantı kopyalandı +copy_link_error=Kopyalamak için ⌘C veya Ctrl-C kullanın copied=Kopyalama Tamam unwatch=İzlemeyi Bırak watch=İzle unstar=Yıldızı Kaldır star=Yıldızla fork=Çatalla +download_archive=Depoyu İndir no_desc=Açıklama Yok quick_guide=Hızlı Başlangıç Kılavuzu clone_this_repo=Bu depoyu klonla create_new_repo_command=Komut satırında yeni bir depo oluşturuluyor push_exist_repo=Komut satırından mevcut bir depo itiliyor +empty_message=Bu depoda herhangi bir içerik yok. code=Kod +code.desc=Kaynak koda, dosyalara, işlemelere ve dallara eriş. branch=Dal tree=Ağaç filter_branch_and_tag=Dal veya biçim imini filtrele @@ -299,34 +627,77 @@ file_raw=Ham file_history=Geçmiş file_view_raw=Ham Görünüm file_permalink=Kalıcı Bağlantı +file_too_large=Bu dosya görüntülemek için çok büyük. +video_not_supported_in_browser=Tarayıcınız HTML5 'video' etiketini desteklemiyor. +audio_not_supported_in_browser=Tarayıcınız HTML5 'audio' etiketini desteklemiyor. stored_lfs=Git LFS ile depolandı +commit_graph=İşleme Grafiği +blame=Bahset +normal_view=Normal Görünüm +editor.new_file=Yeni dosya +editor.upload_file=Dosya Yükle +editor.edit_file=Dosyayı Düzenle editor.preview_changes=Değişiklikleri Önizle +editor.cannot_edit_non_text_files=Bu tür dosyalar web arayüzünden düzenlenemez. +editor.edit_this_file=Dosyayı Düzenle +editor.must_be_on_a_branch=Bu dosyada değişiklik yapmak veya önermek için bir dalda olmalısınız. +editor.fork_before_edit=Bu dosyada değişiklik yapmak veya önermek için depoyu çatallamalısınız. +editor.delete_this_file=Dosyayı Sil +editor.must_have_write_access=Bu dosyada değişiklikler yapmak veya önermek için yazma erişiminizin olması gerekir. +editor.file_delete_success='%s' dosyası silindi. +editor.name_your_file=Dosyanızı isimlendirin… editor.or=veya +editor.cancel_lower=İptal editor.commit_changes=Değişiklikleri Uygula +editor.add_tmpl='' eklendi editor.add='%s' ekle editor.update='%s' güncelle editor.delete='%s' sil +editor.commit_message_desc=İsteğe bağlı uzun bir açıklama ekleyin… editor.commit_directly_to_this_branch=Doğrudan %s bölümüne uygula. editor.create_new_branch=Bu işlem için bir yeni branş oluşturun ve bir çekme istediği başlatın. +editor.propose_file_change=Dosya değişikliği öner +editor.new_branch_name_desc=Yeni dal ismi… editor.cancel=İptal +editor.filename_cannot_be_empty=Dosya adı boş olamaz. +editor.filename_is_invalid=Dosya adı geçersiz: '%s'. +editor.branch_does_not_exist=Bu depoda '%s' dalı yok. editor.branch_already_exists='%s' bölümü bu depoda zaten mevcut. +editor.directory_is_a_file=Dizin adı '%s' zaten bu depoda bir dosya adı olarak kullanılmaktadır. +editor.file_is_a_symlink='%s' sembolik bir bağlantıdır. Sembolik bağlantılar web düzenleyicisinde düzenlenemez +editor.filename_is_a_directory=Dosya adı '%s' zaten bu depoda bir dizin adı olarak kullanılmaktadır. +editor.file_editing_no_longer_exists=Düzenlenmekte olan '%s' dosyası artık bu depoda yer almıyor. +editor.file_deleting_no_longer_exists=Silinen '%s' dosyası bu depoda artık yer almıyor değil. +editor.file_changed_while_editing=Düzenlemeye başladığınızdan beri dosya içeriği değişti. Görmek için burayı tıklayın veya üzerine yazmak için değişiklikleri yine de işleyin. +editor.file_already_exists=Bu depoda '%s' isimli bir dosya zaten mevcut. editor.no_changes_to_show=Gösterilecek değişiklik yok. editor.fail_to_update_file=Şu hata ile '%s' dosyasını güncelleme/oluşturma başarısız oldu: %v +editor.add_subdir=Bir dizin ekle… editor.unable_to_upload_files=Şu hata ile dosyalar '%s' 'a yüklenemedi: %v editor.upload_files_to_dir=Dosyaları '%s' 'a yükle +editor.cannot_commit_to_protected_branch=Korunan '%s' dalına işleme yapılamıyor. +commits.desc=Kaynak kodu değişiklik geçmişine göz atın. commits.commits=İşlemeler +commits.no_commits=Ortak bir işleme yok. '%s' ve '%s' tamamen farklı geçmişlere sahip. +commits.search=İşlemeleri ara… commits.find=Ara +commits.search_all=Tüm Dallar commits.author=Yazar commits.message=Mesaj commits.date=Tarih commits.older=Daha Eski commits.newer=Daha yeni commits.signed_by=İmzalayan +commits.gpg_key_id=GPC Anahtar Kimliği +ext_issues=Dışsal Konular +ext_issues.desc=Dışsal konu takip sistemine bağla. +issues.desc=Hata raporlarını, görevleri ve kilometre taşlarını yönetmenizi sağlar. issues.new=Yeni Sorun +issues.new.title_empty=Başlık boş olamaz issues.new.labels=Etiketler issues.new.no_label=Etiket Yok issues.new.clear_labels=Etiketleri temizle @@ -335,26 +706,40 @@ issues.new.no_milestone=Kilometre Taşı Yok issues.new.clear_milestone=Kilometre Taşlarını Temizle issues.new.open_milestone=Kilometre Taşlarını Aç issues.new.closed_milestone=Kapanmış Kilometre Taşları +issues.new.assignees=Atananlar +issues.new.clear_assignees=Atamaları Temizle +issues.new.no_assignees=Atanan Kişi Yok issues.no_ref=Bölüm/Etiket Belirtilmedi issues.create=Sorun Oluştur issues.new_label=Yeni Etiket +issues.new_label_placeholder=Etiket adı +issues.new_label_desc_placeholder=Açıklama issues.create_label=Etiket Oluştur issues.label_templates.title=Önceden tanımlanmış bir etiket seti yükle +issues.label_templates.info=Henüz bir etiket yok. 'Yeni Etiket' ile bir etiket oluşturun veya önceden tanımlanmış bir etiket seti kullanın: issues.label_templates.helper=Bir etiket seti seçin +issues.label_templates.use=Etiket Kümesi Kullan issues.label_templates.fail_to_load_file=Etiket şablon dosyası yüklemesi başarısız oldu '%s':%v +issues.add_label_at=
    %s
    etiketi eklendi %s +issues.remove_label_at=
    %s
    etiketi silindi %s issues.add_milestone_at=`bu %s yol taşına eklendi %s` issues.change_milestone_at=` %sden%sye yol taşı düzenlendi %s` issues.remove_milestone_at=`bu dosya %s yol taşından kaldırıldı %s` issues.deleted_milestone=`(silindi)` issues.self_assign_at=`kendiliğinden atanmış bu %s` issues.add_assignee_at=`%s tarafından atandı %s` -issues.change_title_at=`başlık %sden %se değiştirildi %s` +issues.remove_assignee_at=`%s tarafından atama kaldırıldı %s` +issues.remove_self_assignment=`atamalarını kaldırdı %s` +issues.change_title_at=`başlık %s iken %s olarak değiştirildi %s` issues.delete_branch_at=`branş %s silindi %s` issues.open_tab=%d açık issues.close_tab=%d kapanmış issues.filter_label=Etiket -issues.filter_milestone=Kilometre taşı +issues.filter_label_no_select=Tüm etiketler +issues.filter_milestone=Kilometre Taşı +issues.filter_milestone_no_select=Tüm kilometre taşları issues.filter_assignee=Atanan +issues.filter_assginee_no_select=Tüm atananlar issues.filter_type=Tür issues.filter_type.all_issues=Tüm Sorunlar issues.filter_type.assigned_to_you=Size atanan @@ -367,15 +752,23 @@ issues.filter_sort.recentupdate=Yakın zamanda güncellenmiş issues.filter_sort.leastupdate=Yakın olmayan zamanda güncellenmiş issues.filter_sort.mostcomment=En çok yorum yapılan issues.filter_sort.leastcomment=En az yorum yapılan +issues.filter_sort.nearduedate=Bitiş tarihi en yakın +issues.filter_sort.farduedate=Bitiş tarihi en uzak +issues.filter_sort.moststars=En çok yıldızlılar +issues.filter_sort.feweststars=En az yıldızlılar +issues.filter_sort.mostforks=En çok çatallananlar +issues.filter_sort.fewestforks=En az çatallananlar issues.action_open=Açık issues.action_close=Kapalı issues.action_label=Etiket -issues.action_milestone=Katedilen Yol -issues.action_milestone_no_select=Katedilen Yol Yok +issues.action_milestone=Kilometre Taşı +issues.action_milestone_no_select=Kilometre Taşı Yok issues.action_assignee=Vekil issues.action_assignee_no_select=Vekil yok -issues.opened_by=%[3]s tarafından %[1]s kere açıldı -issues.opened_by_fake=%[2]s tarafından %[1]s kere açıldı +issues.opened_by=%[3]s tarafından %[1]s açıldı +issues.closed_by=%[3]s tarafından %[1]s kapatıldı +issues.opened_by_fake=%[2]s tarafından %[1]s açıldı +issues.closed_by_fake=%[2]s tarafından %[1]s kapatıldı issues.previous=Önceki issues.next=Sonraki issues.open_title=Açık @@ -385,11 +778,17 @@ issues.commented_at=`%s olarak yorumlandı` issues.delete_comment_confirm=Bu yorumu silmek istediğinizden emin misiniz? issues.no_content=Henüz bir içerik yok. issues.close_issue=Kapat +issues.close_comment_issue=Yorum Yap ve Kapat issues.reopen_issue=Yeniden aç +issues.reopen_comment_issue=Yorum Yap ve Yeniden Aç issues.create_comment=Yorum yap issues.closed_at=`%[2]s kapattı` issues.reopened_at=`%[2]s yeniden açtı` issues.commit_ref_at=`%[2]s işlemesinde bu sorunu işaret etti` +issues.ref_issue_at=`bu konudan bahsetti %[1]s` +issues.ref_pull_at=`bu değişiklik isteğinden bahsetti %[1]s` +issues.ref_issue_ext_at=`%[1]s'den bu konuya değinildi %[2]s` +issues.ref_pull_ext_at=`%[1]s'den bu değişiklik isteğine değinildi %[2]s` issues.poster=Poster issues.collaborator=Katkıcı issues.owner=Sahibi @@ -398,11 +797,16 @@ issues.edit=Düzenle issues.cancel=İptal issues.save=Kaydet issues.label_title=Etiket adı +issues.label_description=Etiket açıklaması issues.label_color=Etiket rengi issues.label_count=%d etiket issues.label_open_issues=%d açık sorun issues.label_edit=Düzenle issues.label_delete=Sil +issues.label_modify=Etiketi Düzenle +issues.label_deletion=Etiketi Sil +issues.label_deletion_desc=Bir etiketi silmek onu bütün konulardan kaldırır. Devam edilsin mi? +issues.label_deletion_success=Etiket silindi. issues.label.filter_sort.alphabetically=Alfabetik issues.label.filter_sort.reverse_alphabetically=Ters alfabetik issues.label.filter_sort.by_size=Boyut @@ -412,30 +816,127 @@ issues.attachment.open_tab=`Yeni bir sekmede "%s" görmek için tıkla` issues.attachment.download=`"%s" indirmek için tıkla` issues.subscribe=Abone Ol issues.unsubscribe=Abonelikten Çık +issues.lock=Konuşmayı kilitle +issues.unlock=Konuşmanın kilidini aç +issues.lock.unknown_reason=Sebep belirtmeden konuyu kilitleyemezsiniz. +issues.lock_duplicate=Bir konu iki kez kilitlenemez. +issues.unlock_error=Kilitlenmemiş bir konunun kilidini açamazsınız. +issues.lock_with_reason=%s olarak kilitlendi ve katkıcılar için sınırlandırıldı %s +issues.lock_no_reason=konuşma kilitlendi ve katkıcılar için sınırlandırıldı %s +issues.unlock_comment=bu konuşmanın kilidini açtı %s +issues.lock_confirm=Kilitle +issues.unlock_confirm=Kilidi Aç +issues.lock.notice_1=- Diğer kullanıcılar bu konuya yeni yorum ekleyemez. +issues.lock.notice_2=- Siz ve bu depoya erişimi olan diğer katkıcılar, başkalarının görebileceği yorumlar bırakabilir. +issues.lock.notice_3=- Her zaman bu konunun kilidini açabilirsiniz. +issues.unlock.notice_1=- Herkes bu konuda bir kez daha yorum yapabilir. +issues.unlock.notice_2=- İlerde her zaman bu konuyu kilitleyebilirsiniz. +issues.lock.reason=Kilitleme nedeni +issues.lock.title=Konuşmayı kilitle. +issues.unlock.title=Konuşmanın kilidini aç. +issues.comment_on_locked=Kilitli bir konuya yorum yapamazsınız. issues.start_tracking_short=Başlat issues.start_tracking_history=`%s çalışması başlatıldı` issues.tracking_already_started=`Bu konuda zaten zaman izleyicisini başlattınız!` issues.stop_tracking=Durdur issues.stop_tracking_history=`%s çalışması durduruldu` +issues.add_time_short=Zaman Ekle issues.add_time_cancel=İptal issues.add_time_history=`%s harcanan zaman eklendi` issues.add_time_hours=Saat issues.add_time_minutes=Dakika +issues.add_time_sum_to_small=Zaman girilmedi. issues.cancel_tracking=İptal issues.cancel_tracking_history=` %s zaman izleyicisi iptal edildi ` +issues.time_spent_total=Toplam Harcanan Zaman +issues.time_spent_from_all_authors=`Toplam Harcanan Zaman: %s` +issues.due_date=Bitiş Tarihi +issues.invalid_due_date_format=Bitiş tarihinin biçimi 'yyyy-aa-gg' olmalıdır. +issues.error_modifying_due_date=Bitiş tarihi değiştirilemedi. +issues.error_removing_due_date=Bitiş tarihi silinemedi. +issues.due_date_form=yyyy-aa-gg +issues.due_date_form_add=Bitiş tarihi ekle +issues.due_date_form_edit=Düzenle +issues.due_date_form_remove=Kaldır +issues.due_date_not_writer=Bir konunun bitiş tarihini değiştirmek için depoda yazma hakkınız olmalıdır. +issues.due_date_not_set=Bitiş tarihi atanmadı. +issues.dependency.title=Bağımlılıklar +issues.dependency.issue_no_dependencies=Bu konu henüz bir bağımlılık içermiyor. +issues.dependency.pr_no_dependencies=Bu çekme isteği henüz bir bağımlılık içermiyor. +issues.dependency.add=Bağımlılık ekle… +issues.dependency.cancel=İptal +issues.dependency.remove=Kaldır +issues.dependency.remove_info=Bu bağımlılığı kaldır +issues.dependency.added_dependency=`%[2]s yeni bir bağımlık ekledi %[3]s` +issues.dependency.removed_dependency=`%[2]s bir bağımlılığı kaldırdı %[3]s` +issues.dependency.issue_closing_blockedby=Bu değişiklik isteğinin kapatılması aşağıdaki sorunlar nedeniyle engelleniyor +issues.dependency.pr_closing_blockedby=Bu sorunun kapatılması aşağıdaki sorunlar nedeniyle engelleniyor +issues.dependency.issue_close_blocks=Bu sorun aşağıdaki sorunların kapatılmasını engelliyor +issues.dependency.pr_close_blocks=Bu değişiklik isteği aşağıdaki sorunların kapatılmasını engelliyor +issues.dependency.issue_close_blocked=Kapatmadan önce bu sorunu engelleyen tüm sorunları kapatmanız gerekir. +issues.dependency.pr_close_blocked=Birleştirme işleminden önce, bu değişiklik isteğini engelleyen tüm sorunları kapatmanız gerekir. +issues.dependency.blocks_short=Engeller +issues.dependency.blocked_by_short=Bağımlılıklar +issues.dependency.remove_header=Bağımlılığı Kaldır +issues.dependency.issue_remove_text=Bu işlem, bu konudaki bağımlılığı kaldıracaktır. Devam edilsin mi? +issues.dependency.pr_remove_text=Bu işlem, bu değişiklik isteğindeki bağımlılığı kaldıracaktır. Devam edilsin mi? +issues.dependency.setting=Konular ve Değişiklik İstekleri İçin Bağımlılıkları Etkinleştir +issues.dependency.add_error_same_issue=Bir sorunu kendine bağımlı yapamazsınız. +issues.dependency.add_error_dep_issue_not_exist=Bağımlı sorun mevcut değil. +issues.dependency.add_error_dep_not_exist=Bağımlılık mevcut değil. +issues.dependency.add_error_dep_exists=Bağımlılık zaten var. +issues.dependency.add_error_cannot_create_circular=Birbirini engelleyen iki sorunla bir bağımlılık oluşturamazsınız. +issues.dependency.add_error_dep_not_same_repo=Her iki konu da aynı depoda olmalıdır. +issues.review.self.approval=Kendi değişiklik isteğinizi onaylayamazsınız. +issues.review.self.rejection=Kendi değişiklik isteğinizde değişiklik isteyemezsiniz. +issues.review.content.empty=İstenen değişiklik(ler)i belirten bir yorum bırakmanız gerekir. +issues.review.pending=Beklemede +issues.review.review=Gözden Geçir +issues.review.reviewers=Gözden Geçirenler +pulls.desc=Değişiklik isteklerini ve kod incelemelerini etkinleştir. pulls.new=Yeni Değişiklik İsteği +pulls.compare_changes=Yeni Değişiklik İsteği +pulls.compare_compare=şuradan çek pulls.filter_branch=Dal filtrele pulls.no_results=Sonuç bulunamadı. +pulls.nothing_to_compare=Bu dallar eşit. Değişiklik isteği oluşturmaya gerek yok. +pulls.has_pull_request=`Bu dallar arasında bir değişiklik isteği zaten var: %[2]s#%[3]d` pulls.create=Değişiklik İsteği Oluştur pulls.title_desc=%[3]s içindeki %[2]s işlemelerini %[1]d ile birleştirmek istiyor pulls.merged_title_desc=%[3]s %[4]s içindeki %[2]s işlemelerini %[1]d ile birleştirdi pulls.tab_conversation=Sohbet pulls.tab_commits=İşlemeler +pulls.tab_files=Değiştirilen Dosyalar pulls.reopen_to_merge=Bir birleşim uygulamak için lütfen bu çekme isteğini yeniden açın. +pulls.cant_reopen_deleted_branch=Dal silindiğinden bu değişiklik isteği yeniden açılamaz. pulls.merged=Birleştirildi +pulls.merged_as=Değişiklik isteği %[2]s olarak birleştirildi. +pulls.has_merged=Değişiklik isteği birleştirildi. +pulls.title_wip_desc=`Değişiklik isteğinin yanlışlıkla birleştirilmesini önlemek için, başlığı %s ile başlatın` +pulls.cannot_merge_work_in_progress=Bu değişiklik isteği devam eden bir çalışma olarak işaretlendi. Hazır olduğunda %s ön ekini başlıktan kaldırın +pulls.data_broken=Bu değişiklik isteği, çatallama bilgilerinin eksik olması nedeniyle bozuldu. +pulls.files_conflicted=Bu değişiklik isteğinde, hedef dalla çakışan değişiklikler var. +pulls.is_checking=Birleştirme çakışması denetimi devam ediyor. Birkaç dakika sonra tekrar deneyin. +pulls.required_status_check_failed=Bazı gerekli denetimler başarılı olmadı. +pulls.required_status_check_administrator=Yönetici olarak, bu değişiklik isteğini yine de birleştirebilirsiniz. +pulls.blocked_by_approvals=Bu değişiklik isteği henüz onaylanmadı. %[2]d isteğin %[1]d onayı verildi. pulls.can_auto_merge_desc=Bu değişiklik isteği otomatik olarak birleştirilebilir. +pulls.cannot_auto_merge_desc=Bu değişiklik isteği, çakışmalar nedeniyle otomatik olarak birleştirilemiyor. +pulls.cannot_auto_merge_helper=Çakışmaları çözmek için el ile birleştirin. +pulls.no_merge_desc=Tüm depo birleştirme seçenekleri devre dışı bırakıldığından, bu değişiklik isteği birleştirilemez. +pulls.no_merge_helper=Depo ayarlarındaki birleştirme seçeneklerini etkinleştirin veya değişiklik isteğini el ile birleştirin. +pulls.no_merge_wip=Bu değişiklik isteği birleştirilemez çünkü devam eden bir çalışma olarak işaretlendi. +pulls.no_merge_status_check=Gerekli olan tüm durum denetimleri başarılı olmadığından bu değişiklik isteği birleştirilemez. pulls.merge_pull_request=Değişiklik İsteğini Birleştir +pulls.rebase_merge_pull_request=Rebase ve Merge +pulls.rebase_merge_commit_pull_request=Rebase ve Merge (--no-ff) +pulls.squash_merge_pull_request=Squash ve Merge +pulls.invalid_merge_option=Bu değişiklik isteği için bu birleştirme seçeneğini kullanamazsınız. +pulls.open_unmerged_pull_exists=`Aynı özelliklere sahip bekleyen bir çekme isteği (#%d) olduğundan yeniden açma işlemini gerçekleştiremezsiniz.` +pulls.status_checking=Bazı denetlemeler beklemede +pulls.status_checks_success=Tüm denetlemeler başarılı oldu +pulls.status_checks_error=Bazı denetlemeler başarısız oldu milestones.new=Yeni Kilometre Taşı milestones.open_tab=%d Açık @@ -444,13 +945,23 @@ milestones.closed=Kapalı %s milestones.no_due_date=Bitiş tarihi yok milestones.open=Aç milestones.close=Kapat +milestones.new_subheader=Kilometre taşları konuları yönetir ve gelişmeleri takip eder. +milestones.completeness=%d%% Tamamlandı milestones.create=Kilometre Taşı Oluştur milestones.title=Başlık milestones.desc=Açıklama milestones.due_date=Bitiş Tarihi (isteğe bağlı) milestones.clear=Temizle +milestones.invalid_due_date_format=Bitiş tarihinin biçimi 'yyyy-aa-gg' olmalıdır. +milestones.create_success='%s' kilometre taşı oluşturuldu. milestones.edit=Kilometre Taşını Düzenle +milestones.edit_subheader=Kilometre taşları konuları yönetir ve gelişmeleri takip eder. milestones.cancel=İptal +milestones.modify=Kilometre Taşını Güncelle +milestones.edit_success=`%s` kilometre taşı güncellendi. +milestones.deletion=Kilometre Taşını Sil +milestones.deletion_desc=Bir kilometre taşını silmek, onu ilgili tüm sorunlardan kaldırır. Devam edilsin mi? +milestones.deletion_success=Kilometre taşı silindi. milestones.filter_sort.closest_due_date=En yakın zamanı gelmiş tarih milestones.filter_sort.furthest_due_date=En uzak zamanı gelmiş tarih milestones.filter_sort.least_complete=En az tamamlama @@ -458,17 +969,26 @@ milestones.filter_sort.most_complete=En çok tamamlama milestones.filter_sort.most_issues=En çok sorun milestones.filter_sort.least_issues=En az sorun +ext_wiki=Harici Wiki +ext_wiki.desc=Harici bir wiki'ye bağlantı. wiki=Wiki +wiki.welcome=Wiki'ye Hoşgeldiniz. +wiki.welcome_desc=Wiki, katkıcılarla belge yazmanıza ve paylaşmanıza olanak tanır. +wiki.desc=Katkıcılarla belgeler yazın ve paylaşın. +wiki.create_first_page=İlk sayfayı oluştur wiki.page=Sayfa wiki.filter_page=Sayfa filtrele +wiki.new_page=Sayfa wiki.default_commit_message=Bu sayfa güncellemesi hakkında bir not yaz (isteğe bağlı). wiki.save_page=Sayfayı Kaydet wiki.last_commit_info=%s bu sayfayı düzenledi %s wiki.edit_page_button=Düzenle wiki.new_page_button=Yeni Sayfa +wiki.back_to_wiki=Wiki sayfasına geri dön wiki.delete_page_button=Sayfayı Sil wiki.page_already_exists=Aynı isimde bir Wiki sayfası zaten var. +wiki.reserved_page='%s' wiki sayfa adı rezerve edilmiştir. wiki.pages=Sayfalar wiki.last_updated=Son güncelleme %s @@ -480,7 +1000,7 @@ activity.period.weekly=1 hafta activity.period.monthly=1 ay activity.overview=Genel Bakış activity.active_prs_count_1=%d Aktif Çekme İsteği -activity.active_prs_count_n=%d Aktif Çekme İstekleri +activity.active_prs_count_n=%d Aktif Çekme İsteği activity.merged_prs_count_1=Birleştirilmiş Çekme İsteği activity.merged_prs_count_n=Birleştirilmiş Çekme İstekleri activity.opened_prs_count_1=Önerilen Çekme İsteği @@ -494,22 +1014,44 @@ activity.title.prs_opened_by=%s tarafından %s önerildi activity.merged_prs_label=Birleştirilen activity.opened_prs_label=Önerilen activity.active_issues_count_1=%d Aktif Konu -activity.active_issues_count_n=%d Aktif Konular +activity.active_issues_count_n=%d Aktif Konu activity.closed_issues_count_1=Kapalı Konu activity.closed_issues_count_n=Kapalı Konular activity.title.issues_1=%d Konu -activity.title.issues_n=%d Konular -activity.title.issues_closed_by=%s tarafından %s kapandı -activity.title.issues_created_by=%s tarafından %s oluşturuldu +activity.title.issues_n=%d Konu +activity.title.issues_closed_by=%s %s tarafından kapatıldı +activity.title.issues_created_by=%s %s tarafından oluşturuldu activity.closed_issue_label=Kapalı activity.new_issues_count_1=Yeni Konu activity.new_issues_count_n=Yeni Konular activity.new_issue_label=Açıldı +activity.title.unresolved_conv_1=%d Çözümlenmemiş Konuşma +activity.title.unresolved_conv_n=%d Çözümlenmemiş Konuşma activity.unresolved_conv_label=Açık activity.title.releases_1=%d Serbest bırak activity.title.releases_n=%d Serbest bırakmalar activity.title.releases_published_by=%s tarafından %s yayınlandı activity.published_release_label=Yayınlandı +activity.no_git_activity=Bu dönemde herhangi bir işleme yapılmamıştır. +activity.git_stats_exclude_merges=Birleştirmeler hariç, +activity.git_stats_author_1=%d yazar +activity.git_stats_author_n=%d yazar +activity.git_stats_pushed_1=itti +activity.git_stats_pushed_n=itti +activity.git_stats_commit_1=%d işleme +activity.git_stats_commit_n=%d işleme +activity.git_stats_push_to_branch=%s 'e ve +activity.git_stats_push_to_all_branches=tüm dallara. +activity.git_stats_on_default_branch=%s üzerinde, +activity.git_stats_file_1=%d dosya +activity.git_stats_file_n=%d dosya +activity.git_stats_files_changed_1=değişti +activity.git_stats_files_changed_n=değişti +activity.git_stats_addition_1=%d ekleme +activity.git_stats_addition_n=%d ekleme +activity.git_stats_and_deletions=ve +activity.git_stats_deletion_1=%d silme +activity.git_stats_deletion_n=%d silme search=Ara search.search_repo=Depo ara @@ -517,76 +1059,249 @@ search.results="%s" için %s içinde sonuçları ara settings=Ayarlar settings.desc=Ayarlar, depo için ayarları yönetebileceğiniz yerdir +settings.options=Depo +settings.collaboration=Katkıcılar +settings.collaboration.admin=Yönetici settings.collaboration.write=Yaz settings.collaboration.read=Oku +settings.collaboration.owner=Sahibi settings.collaboration.undefined=Belirsiz settings.hooks=Web İstekleri settings.githooks=Git İstekleri settings.basic_settings=Temel Ayarlar settings.mirror_settings=Yansıma Ayarları +settings.sync_mirror=Şimdi Eşitle +settings.mirror_sync_in_progress=Yansı senkronizasyonu devam ediyor. Bir dakika sonra tekrar kontrol edin. +settings.email_notifications.enable=E-posta Bildirimlerini Etkinleştir +settings.email_notifications.onmention=Sadece Bahsedilen E-posta +settings.email_notifications.disable=E-posta Bildirimlerini Devre Dışı Bırak +settings.email_notifications.submit=E-posta Tercihlerini Ayarla +settings.site=Web Sitesi settings.update_settings=Ayarları Güncelle settings.advanced_settings=Gelişmiş Ayarlar +settings.wiki_desc=Depo Wiki'sini Etkinkleştir +settings.use_internal_wiki=Dahili Wiki Kullan +settings.use_external_wiki=Harici Wiki Kullan settings.external_wiki_url=Harici Wiki bağlantısı +settings.external_wiki_url_error=Harici wiki URL'si geçerli bir URL değil. +settings.external_wiki_url_desc=Ziyaretçiler, wiki sekmesine tıklandığında harici wiki URL'sine yönlendirilir. settings.external_tracker_url=Harici Konu İzleyici URLsi +settings.external_tracker_url_desc=Ziyaretçiler, konular sekmesine tıklandığında harici konu izleyici URL'sine yönlendirilir. settings.tracker_url_format=Harici Sorun Takipçisi Bağlantı Formatı +settings.tracker_url_format_error=Harici konu izleyici URL biçimi geçerli bir URL değil. +settings.tracker_issue_style=Harici Konu İzleyici Numara Biçimi settings.tracker_issue_style.numeric=Sayısal settings.tracker_issue_style.alphanumeric=Alfanumerik +settings.enable_timetracker=Zaman Takibini Etkinleştir +settings.allow_only_contributors_to_track_time=Sadece Katkıcılar İçin Zaman Takibine İzin Ver +settings.pulls_desc=Değişiklik İsteklerini Etkinleştir +settings.pulls.ignore_whitespace=Çakışmalar için Boşlukları Gözardı Et +settings.pulls.allow_merge_commits=İşleme Birleştirmeyi Etkinleştir +settings.admin_settings=Yönetici Ayarları +settings.admin_enable_health_check=Depo Sağlık Kontrollerini Etkinleştir (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch=Varsayılan olmayan bir dalda yapılan bir işlemeyle konuyu kapat settings.danger_zone=Tehlike Alanı settings.new_owner_has_same_repo=Yeni sahibin aynı isimde başka bir deposu var. Lütfen farklı bir isim seçin. +settings.convert=Düzenli Depoya Dönüştür +settings.convert_desc=Bu yansıyı normal bir depoya dönüştürebilirsiniz. Bu işlem geri alınamaz. +settings.convert_notices_1=Bu işlem yansıyı normal bir depoya dönüştürür ve geri alınamaz. +settings.convert_confirm=Depoyu Dönüştür +settings.convert_succeed=Yansı normal bir depoya dönüştürüldü. settings.transfer=Sahipliği Aktar +settings.transfer_desc=Bu depoyu bir kullanıcıya veya yönetici haklarına sahip olduğunuz bir organizasyona aktarın. +settings.transfer_notices_1=- Bireysel bir kullanıcıya aktarırsanız depoya erişiminizi kaybedersiniz. +settings.transfer_form_title=Onaylamak için depo adını girin: +settings.wiki_delete=Wiki Verisini Sil +settings.wiki_delete_desc=Depo wiki verilerini silmek kalıcıdır ve geri alınamaz. +settings.confirm_wiki_delete=Wiki Verisini Sil +settings.wiki_deletion_success=Depo wiki verisi silindi. settings.delete=Bu Depoyu Sil settings.delete_notices_1=- Bu işlem geri ALINAMAZ. +settings.deletion_success=Depo silindi. +settings.update_settings_success=Depo ayarları güncellendi. settings.transfer_owner=Yeni Sahip +settings.make_transfer=Transfer Et +settings.transfer_succeed=Depo transfer edildi. +settings.confirm_delete=Depoyu Sil +settings.add_collaborator=Katkıcı Ekle +settings.add_collaborator_success=Katkıcı eklendi. +settings.add_collaborator_inactive_user=Etkin olmayan bir kullanıcı katkıcı olarak eklenemez. +settings.add_collaborator_duplicate=Katkıcı bu depoya zaten eklenmiş. +settings.delete_collaborator=Sil +settings.collaborator_deletion=Katkıcıyı Sil +settings.collaborator_deletion_desc=Bir katkıcıyı silmek, bu depoya erişimini iptal edecektir. Devam et? +settings.remove_collaborator_success=Katkıcı silindi. +settings.search_user_placeholder=Kullanıcı ara… +settings.org_not_allowed_to_be_collaborator=Organizasyonlar katkıcı olarak eklenemez. +settings.change_team_access_not_allowed=Depo için takım erişimini değiştirmek, organizasyon sahibiyle sınırlandırıldı +settings.team_not_in_organization=Takım, depo ile aynı organizasyonda değil +settings.add_team_duplicate=Takım zaten bu depoya sahip +settings.add_team_success=Takım artık bu depoya erişebilir. settings.add_webhook=Web İsteği Ekle +settings.hooks_desc=Web istemcileri, belirli Gitea olayları tetiklendiğinde otomatik olarak bir sunucuya HTTP POST isteği yapar. Web istekleri kılavuzundan daha fazla bilgi edinebilirsiniz. +settings.webhook_deletion=Web İsteğini Sil +settings.webhook_deletion_desc=Bir web isteğini kaldırmak, ayarlarını ve teslimat geçmişini siler. Devam edilsin mi? +settings.webhook_deletion_success=Web isteği silindi. settings.webhook.test_delivery=Test Dağıtımı +settings.webhook.test_delivery_desc=Bu web isteğini sahte bir olayla test edin. +settings.webhook.test_delivery_success=Teslim sırasına sahte bir olay eklendi. Teslim geçmişinde görünmesi birkaç saniye sürebilir. settings.webhook.request=İstekler settings.webhook.response=Cevaplar settings.webhook.headers=Başlıklar +settings.webhook.payload=İçerik settings.webhook.body=Gövde +settings.githooks_desc=Git istekleri Git'in kendisi tarafından desteklenmektedir. Özel işlemler ayarlamak için aşağıdaki istek dosyalarını düzenleyebilirsiniz. settings.githook_edit_desc=İstek aktif değilse örnek içerik sunulacaktır. İçeriği boş bırakmak, isteği devre dışı bırakmayı beraberinde getirecektir. settings.githook_name=İstek İsmi settings.githook_content=İstek İçeriği settings.update_githook=İsteği Güncelle +settings.add_webhook_desc=Gitea, belirtilen içerik türüne sahip POST isteğini hedef URL’ye gönderecektir. Web istekleri kılavuzundan daha fazla bilgi edinebilirsiniz. +settings.payload_url=Hedef URL +settings.http_method=HTTP Yöntemi +settings.content_type=POST İçerik Türü settings.secret=Gizli settings.slack_username=Kullanıcı Adı settings.slack_icon_url=Simge Bağlantısı settings.discord_username=Kullanıcı adı settings.discord_icon_url=Simge URL'si settings.slack_color=Renk +settings.event_desc=Tetikleyici Açık: +settings.event_push_only=İtme Olayları +settings.event_send_everything=Tüm Olaylar +settings.event_choose=Özel Olaylar… settings.event_create=Oluştur +settings.event_create_desc=Dal veya etiket oluşturulduğunda. +settings.event_delete=Sil +settings.event_delete_desc=Dal veya etiket silindiğinde. +settings.event_fork=Çatalla +settings.event_fork_desc=Depo çatallandığında. +settings.event_issues=Konular +settings.event_issues_desc=Konu açıldığında, kapatıldığında, yeniden açıldığında, düzenlendiğinde, atandığında, atama kaldırıldığında, etiket güncellendiğinde, etiket silindiğinde, kilometre taşına eklendiğinde ya da kilometre taşından silindiğinde. +settings.event_issue_comment=Konu Yorumu +settings.event_issue_comment_desc=Konu yorumu eklendiğinde, düzenlendiğinde veya silindiğinde. +settings.event_release=Sürüm +settings.event_release_desc=Sürüm yayınlandığında, güncellendiğinde veya silindiğinde. settings.event_pull_request=İstek Çek +settings.event_pull_request_desc=Değişiklik isteği açıldığında, kapatıldığında, yeniden açıldığında, düzenlendiğinde, onaylandığında, reddedildiğinde, yorum yazdığında, atama yapıldığında, atama kaldırıldığında, etiket güncellendiğinde, etiketler temizlendiğinde veya senkronize edildiğinde. settings.event_push=Çek +settings.event_push_desc=Depo ittirildiğinde. +settings.branch_filter=Dal filtresi settings.event_repository=Depo +settings.event_repository_desc=Depo oluşturuldu veya silindi. +settings.active=Etkin +settings.active_helper=Tetiklenen olaylar hakkındaki bilgiler bu web isteği URL'sine gönderilir. +settings.add_hook_success=Web isteği eklendi. settings.update_webhook=Web İsteğini Düzenle +settings.update_hook_success=Web isteği güncellendi. +settings.delete_webhook=Web İsteğini Sil settings.recent_deliveries=Son Dağıtımlar settings.hook_type=İstek Türü +settings.add_slack_hook_desc=Deponuza Slack entegre edin. settings.slack_token=Erişim Anahtarı settings.slack_domain=Alan Adı settings.slack_channel=Kanal +settings.add_discord_hook_desc=Deponuza Discord entegre edin. +settings.add_dingtalk_hook_desc=Deponuza Dingtalk entegre edin. +settings.add_telegram_hook_desc=Deponuza Telegram entegre edin. +settings.add_msteams_hook_desc=Deponuza Microsoft Teams entegre edin. settings.deploy_keys=Dağıtım Anahtarları settings.add_deploy_key=Dağıtım Anahtarı Ekle +settings.deploy_key_desc=Dağıtım anahtarları, depoyu salt okunur çekme yetkisine sahip. +settings.is_writable=Yazma Erişimini Etkinleştir +settings.is_writable_info=Bu dağıtım anahtarının depoyu itmesine izin ver. +settings.no_deploy_keys=Henüz dağıtım anahtarı yok. settings.title=Başlık settings.deploy_key_content=İçerik +settings.key_been_used=Aynı içeriğe sahip bir dağıtım anahtarı zaten kullanılıyor. +settings.key_name_used=Aynı ada sahip bir dağıtım anahtarı zaten var. +settings.add_key_success=Dağıtım anahtarı '%s' eklenmiştir. +settings.deploy_key_deletion=Dağıtım Anahtarını Sil +settings.deploy_key_deletion_desc=Dağıtım anahtarının silinmesi, bu depoya olan erişimini iptal eder. Devam edilsin mi? +settings.deploy_key_deletion_success=Dağıtım anahtarı silindi. settings.branches=Branşlar settings.protected_branch=Branş Koruması settings.protected_branch_can_push=İtmeye izin verilsin mi? settings.protected_branch_can_push_yes=İtebilirsiniz settings.protected_branch_can_push_no=İtemezsiniz +settings.branch_protection=%s dalı için Dal Koruması +settings.protect_this_branch=Dal Korumayı Etkinleştir +settings.protect_this_branch_desc=Silmeyi önleyin ve dala herhangi bir Git itmesini devre dışı bırakın. +settings.protect_whitelist_committers=İtme Beyaz Listesini Etkinleştir +settings.protect_whitelist_committers_desc=Beyaz listedeki kullanıcıların veya takımların bu dalı itmesine izin verin (ancak zorla itmeye değil). +settings.protect_whitelist_users=İtme için beyaz listedeki kullanıcılar: +settings.protect_whitelist_search_users=Kullanıcı ara… +settings.protect_whitelist_teams=İtme için beyaz listedeki takımlar: +settings.protect_whitelist_search_teams=Takımları ara… +settings.protect_merge_whitelist_committers=Birleştirme Beyaz Listesini Etkinleştir +settings.protect_merge_whitelist_committers_desc=Yalnızca beyaz listedeki kullanıcıların veya takımların bu daldaki değişiklik isteklerini birleştirmesine izin ver. +settings.protect_merge_whitelist_users=Birleştirme için beyaz listedeki kullanıcılar: +settings.protect_merge_whitelist_teams=Birleştirme için beyaz listedeki takımlar: +settings.protect_check_status_contexts=Durum Denetimini Etkinleştir +settings.protect_check_status_contexts_list=Bu depo için geçen haftadaki durum denetimleri +settings.protect_required_approvals=Gerekli onaylar: +settings.protect_required_approvals_desc=Yalnızca beyaz listedeki kullanıcıların veya takımların yeterince olumlu incelemelerini içeren değişiklik isteğini birleştirmeye izin ver. +settings.protect_approvals_whitelist_users=Beyaz listedeki incelemeciler: +settings.protect_approvals_whitelist_teams=Gözden geçirme için beyaz listedeki takımlar: settings.add_protected_branch=Korumayı etkinleştir settings.delete_protected_branch=Korumayı devre dışı bırak +settings.update_protect_branch_success='%s' dalı için dal koruması güncellendi. +settings.remove_protected_branch_success='%s' dalı için dal koruması devre dışı bırakıldı. +settings.protected_branch_deletion=Dal Korumasını Devre Dışı Bırak +settings.protected_branch_deletion_desc=Dal korumasını devre dışı bırakmak, kullanıcıların dalı itmek için yazma izni olmasını sağlar. Devam edilsin mi? +settings.default_branch_desc=Değişiklik istekleri ve kod işlemeleri için varsayılan bir depo dalı seçin: +settings.choose_branch=Bir dal seç… +settings.no_protected_branch=Korumalı dal yok. +settings.edit_protected_branch=Düzenle +settings.protected_branch_required_approvals_min=Gerekli onaylar negatif olamaz. +settings.bot_token=Bot Jetonu +settings.chat_id=Sohbet Kimliği +settings.archive.button=Depoyu Arşivle +settings.archive.header=Bu Depoyu Arşivle +settings.archive.text=Depoyu arşivlemek tamamen salt okunur hale getirecek. Gösterge panelinden gizlenir, işleme yapılamaz ve hiçbir konu ya da değişiklik isteği oluşturulamaz. +settings.archive.success=Depo başarıyla arşivlendi. +settings.archive.error=Depoyu arşivlemeye çalışırken bir hata oluştu. Daha fazla ayrıntı için günlüğe bakın. +settings.archive.error_ismirror=Yansılanmış bir depoyu arşivleyemezsiniz. +settings.archive.branchsettings_unavailable=Depo arşivlenirse dal ayarları kullanılamaz. +settings.unarchive.button=Depoyu Arşivden Çıkar +settings.unarchive.header=Bu Depoyu Arşivden Çıkar +settings.unarchive.text=Depoyu arşivden çıkarmak, yeni sorunların ve değişiklik isteklerinin yanı sıra işleme ve itme yeteneğini de geri kazandıracaktır. +settings.unarchive.success=Depo başarıyla arşivden çıkarıldı. +settings.unarchive.error=Depoyu arşivden çıkarmaya çalışırken bir hata oluştu. Daha fazla ayrıntı için günlüğe bakın. +settings.update_avatar_success=Depo resmi güncellendi. diff.browse_source=Kaynağa Gözat diff.parent=ebeveyn diff.commit=işleme +diff.git-notes=Notlar diff.data_not_available=Farklı İçerik Mevut Değil diff.show_diff_stats=Farklılık Durumunu Göster diff.show_split_view=Görünümü Böl diff.show_unified_view=Birleşik Görünüm +diff.whitespace_button=Boşluk +diff.whitespace_show_everything=Tüm değişiklikleri göster +diff.whitespace_ignore_all_whitespace=Satırları karşılaştırırken boşluğu yoksay +diff.whitespace_ignore_amount_changes=Boşluk miktarındaki değişiklikleri yoksay +diff.whitespace_ignore_at_eol=Satır sonundaki boşluk değişiklikleri yoksay diff.stats_desc= %d değiştirilmiş dosya ile %d ekleme ve %d silme diff.bin=BIN diff.view_file=Dosyayı Görüntüle +diff.file_before=Önce +diff.file_after=Sonra +diff.file_image_width=Genişlik +diff.file_image_height=Yükseklik +diff.file_byte_size=Boyut diff.file_suppressed=Dosya farkı çok büyük olduğundan ihmal edildi diff.too_many_files=Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor +diff.comment.placeholder=Yorum Yap +diff.comment.markdown_info=Markdown ile şekillendirme desteklenir. +diff.comment.add_single_comment=Bir yorum ekle +diff.comment.add_review_comment=Yorum ekle +diff.comment.start_review=İncelemeye başla +diff.comment.reply=Yanıtla +diff.review=İncele +diff.review.comment=Yorum Yap +diff.review.approve=Onayla +diff.review.reject=Değişiklik iste release.releases=Sürümler release.new_release=Yeni Sürüm @@ -596,52 +1311,105 @@ release.stable=Kararlı release.edit=düzenle release.ahead=%s son sürümden beri %d işleme release.source_code=Kaynak Kodu +release.new_subheader=Sürümler proje versiyonlarını yönetmenizi sağlar. +release.edit_subheader=Sürümler proje versiyonlarını yönetmenizi sağlar. release.tag_name=Biçim imi adı release.target=Hedef +release.tag_helper=Mevcut bir etiket seçin veya yeni bir etiket oluşturun. release.title=Başlık release.content=İçerik +release.prerelease_desc=Sürüm Öncesi Olarak İşaretle +release.prerelease_helper=Bu sürümü, gerçek kullanım için uygun değildir olarak işaretleyin. release.cancel=İptal release.publish=Sürümü Yayınla release.save_draft=Taslağı Kaydet +release.edit_release=Sürümü Güncelle +release.delete_release=Sürümü Sil +release.deletion=Sürümü Sil +release.deletion_desc=Bir sürümün silinmesi Git etiketini depodan kaldırır. Depo içerikleri ve tarihçeleri değişmeden kalır. Devam edilsin mi? release.deletion_success=Sürüm silindi. +release.tag_name_already_exist=Bu etiket adına sahip bir sürüm zaten var. +release.tag_name_invalid=Etiket adı geçerli değil. release.downloads=İndirmeler +branch.name=Dal Adı branch.search=Bölümleri ara +branch.already_exists='%s' adında bir dal zaten var. branch.delete_head=Sil +branch.delete='%s' Dalını Sil branch.delete_html=Bölüm Sil +branch.delete_desc=Bir dalı silmek kalıcıdır. GERİ ALINAMAZ. Devam edilsin mi? +branch.deletion_success='%s' dalı silindi. +branch.deletion_failed='%s' dalı silinemedi. +branch.delete_branch_has_new_commits='%s' dalı silinemedi çünkü birleştirme sonrasında yeni işlemeler eklendi. branch.create_branch=Branş %s oluştur branch.create_from='%s'den +branch.create_success='%s' dalı oluşturuldu. branch.branch_already_exists=Branş '%s' zaten bu depoda bulunuyor. +branch.branch_name_conflict='%s' dal adı zaten mevcut olan '%s' dalıyla çakışıyor. +branch.tag_collision='%s' dalı, depoda aynı ada sahip bir etiket olduğundan oluşturulamıyor. branch.deleted_by=%s tarafından silindi +branch.restore_success='%s' dalı geri yüklendi. +branch.restore_failed='%s' dalı geri yüklenemedi. +branch.protected_deletion_failed='%s' dalı korunuyor. Silinemez. +branch.restore='%s' Dalını Geri Yükle +branch.download='%s' Dalını İndir +topic.done=Bitti [org] org_name_holder=Organizasyon Adı org_full_name_holder=Organizasyon Tam Adı +org_name_helper=Organizasyon adları kısa ve hatırlanabilir olmalıdır. create_org=Organizasyon Oluştur repo_updated=Güncellendi people=İnsanlar teams=Ekipler lower_members=üyeler lower_repositories=depolar +create_new_team=Yeni Takım +create_team=Takım Oluştur org_desc=Açıklama team_name=Ekip Adı team_desc=Açıklama +team_name_helper=Takım adları kısa ve hatırlanabilir olmalıdır. +team_desc_helper=Takımın amacını veya rolünü açıklayın. +team_permission_desc=İzin +team_unit_desc=Depo Bölümlerine Erişime İzin Ver +form.name_reserved='%s' organizasyon adı rezerve edilmiştir. +form.name_pattern_not_allowed=Organizasyon adında '%s' desenine izin verilmiyor. +form.create_org_not_allowed=Organizasyon oluşturmanıza izin verilmiyor. settings=Ayarlar +settings.options=Organizasyon settings.full_name=Tam İsim settings.website=Web Sitesi settings.location=Lokasyon +settings.permission=İzinler +settings.repoadminchangeteam=Depo yöneticisi takımlar için erişim ekleyebilir ve kaldırabilir +settings.visibility=Görünürlük +settings.visibility.public=Genel +settings.visibility.limited=Sınırlı (Sadece oturum açan kullanıcılar görebilir) +settings.visibility.private=Özel (Sadece organizasyon üyeleri görebilir) settings.update_settings=Ayarları Güncelle settings.update_setting_success=Organizasyon ayarları güncellendi. +settings.change_orgname_prompt=Not: Organizasyon adını değiştirmek, kuruluşun URL’sini de değiştirir. +settings.update_avatar_success=Organizasyonun resmi güncellendi. settings.delete=Organizasyonu Sil settings.delete_account=Bu Organizasyonu Sil +settings.delete_prompt=Organizasyon kalıcı olarak kaldırılacaktır. Bu işlem GERİ ALINAMAZ! settings.confirm_delete_account=Silmeyi Onaylıyorum +settings.delete_org_title=Organizasyonu Sil +settings.delete_org_desc=Bu organizasyon kalıcı olarak silinecektir. Devam edilsin mi? settings.hooks_desc=Bu organizasyon altındaki tüm depolar için tetiklenecek webhook'lar ekle. members.membership_visibility=Üyelik Görünürlüğü: +members.public=Görünür +members.public_helper=gizle +members.private=Gizlenmiş +members.private_helper=görünür yap members.member_role=Üye Rolü: members.owner=Sahibi members.member=Üye @@ -653,22 +1421,39 @@ members.invite_now=Şimdi Davet Et teams.join=Katıl teams.leave=Ayrıl teams.read_access=Okuma Erişimi +teams.read_access_helper=Üyeler, takım depolarını görüntüleyebilir ve klonlayabilir. teams.write_access=Yazma Erişimi +teams.write_access_helper=Üyeler takım depolarını okuyabilir ve itme yapabilir. +teams.admin_access=Yönetici Erişimi +teams.admin_access_helper=Üyeler takım depolarını çekip itebilir ve katkıcı ekleyebilir. teams.no_desc=Herhangi bir takım açıklaması yok teams.settings=Ayarlar +teams.owners_permission_desc=Sahipler tüm depolara tam erişime sahiptir ve organizasyona yönetici erişimine sahiptir. teams.members=Ekip Üyeleri teams.update_settings=Ayarları Güncelle +teams.delete_team=Takımı Sil teams.add_team_member=Ekip Üyesi Ekle +teams.delete_team_title=Takımı Sil +teams.delete_team_desc=Bir takımı silmek, üyelerinden depo erişimini iptal eder. Devam edilsin mi? teams.delete_team_success=Takım silindi. +teams.read_permission_desc=Bu takım Okuma erişimi veriyor. Üyeler takım depolarını görüntüleyebilir ve klonlayabilir. +teams.write_permission_desc=Bu takım Yazma erişimi veriyor. Üyeler takım depolarından okuyabilir ve takım depolarına itme yapabilir. +teams.admin_permission_desc=Bu takım Yönetici erişimi veriyor. Üyeler takım depolarını okuyabilir, itebilir ve katkıcı ekleyebilir. teams.repositories=Ekip Depoları +teams.search_repo_placeholder=Depo ara… teams.add_team_repository=Ekip Deposu Ekle teams.remove_repo=Kaldır teams.add_nonexistent_repo=Eklemeye çalıştığınz depo mevcut değil. Lütfen önce oluşturun. +teams.add_duplicate_users=Kullanıcı zaten takımın üyesi. +teams.repos.none=Bu takım tarafından hiçbir depoya erişilemedi. +teams.members.none=Bu takımda üye yok. [admin] -dashboard=Başlangıç +dashboard=Pano +users=Kullanıcı Hesapları organizations=Organizasyonlar repositories=Depolar +hooks=Varsayılan Web İstemcileri config=Yapılandırma notices=Sistem Bildirimler monitor=İzleme @@ -676,20 +1461,40 @@ first_page=İlk last_page=Son total=Toplam: %d +dashboard.statistic=Özet +dashboard.operations=Bakım İşlemleri +dashboard.system_status=Sistem Durumu +dashboard.statistic_info=Gitea veritabanında %d kullanıcı, %d organizasyon, %d açık anahtar, %d depo, %d izleme, %d yıldız, %d eylem, %d erişim, %d konu, %d yorum, %d sosyal hesap, %d takip, %d yansı, %d sürüm, %d kimlik doğrulama kaynağı, %d web istemcisi, %d kilometre taşı, %d etiket, %d istemci görevi, %d takım, %d güncelleme görevi, %d ek bulunuyor. dashboard.operation_name=İşlem Adı dashboard.operation_switch=Geç dashboard.operation_run=Çalıştır dashboard.clean_unbind_oauth=Bağsız OAuth bağlantılarını temizle dashboard.clean_unbind_oauth_success=Tüm bağsız OAuth bağlantıları silindi. +dashboard.delete_inactivate_accounts=Etkinleştirilmemiş tüm hesapları sil +dashboard.delete_inactivate_accounts_success=Etkinleştirilmemiş tüm hesaplar silindi. +dashboard.delete_repo_archives=Tüm arşiv depoları sil +dashboard.delete_repo_archives_success=Tüm arşiv depolar silindi. +dashboard.delete_missing_repos=Git dosyaları eksik olan tüm depoları sil +dashboard.delete_missing_repos_success=Git dosyaları eksik olan tüm depolar silindi. +dashboard.delete_generated_repository_avatars=Oluşturulan depo resimlerini sil +dashboard.delete_generated_repository_avatars_success=Oluşturulan depo resimleri silindi. +dashboard.git_gc_repos=Depolardaki çöpleri topla +dashboard.git_gc_repos_success=Tüm depolardaki çöp toplama işlemi bitti. +dashboard.resync_all_sshkeys_success=Gitea tarafından kontrol edilen açık SSH anahtarları güncellendi. dashboard.reinit_missing_repos=Kayıtları bulunanlar için tüm eksik Git depolarını yeniden başlat dashboard.reinit_missing_repos_success=Kayıtları bulunanlar için tüm eksik Git depoları yeniden başlatıldı. dashboard.sync_external_users=Harici kullanıcı verisini senkronize et +dashboard.sync_external_users_started=Harici kullanıcı veri senkronizasyonu başladı. +dashboard.git_fsck=Tüm depolarda sağlık kontrolü yap +dashboard.git_fsck_started=Depo sağlık kontrolleri başladı. dashboard.server_uptime=Sunucunun Ayakta Kalma Süresi dashboard.current_goroutine=Güncel Goroutine'ler dashboard.current_memory_usage=Güncel Bellek Kullanımı dashboard.total_memory_allocated=Bellekte Ayrılan Toplam Yer dashboard.memory_obtained=Elde Edilen Bellek dashboard.pointer_lookup_times=İşaret Arama Zamanları +dashboard.memory_allocate_times=Bellek Ayırmaları +dashboard.memory_free_times=Bellek Boşaltmaları dashboard.current_heap_usage=Güncel Yığın Kullanımı dashboard.heap_memory_obtained=Elde Edilen Yığın Belleği dashboard.heap_memory_idle=Boştaki Yığın Belleği @@ -712,18 +1517,41 @@ dashboard.total_gc_pause=Toplam GC Durması dashboard.last_gc_pause=Son GC Durması dashboard.gc_times=GC Zamanları +users.user_manage_panel=Kullanıcı Hesap Yönetimi +users.new_account=Yeni Kullanıcı Hesabı +users.name=Kullanıcı Adı users.activated=Aktifleştirilmiş users.admin=Yönetici users.repos=Depolar users.created=Oluşturuldu +users.last_login=Son Oturum Açma +users.never_login=Hiç Oturum Açılmadı +users.send_register_notify=Kullanıcı Kayıt Bildirimi Gönder +users.new_success='%s' kullanıcı hesabı oluşturuldu. users.edit=Düzenle users.auth_source=Yetkilendirme Kaynağı users.local=Yerel +users.password_helper=Şifreyi değiştirmemek için boş bırakın. +users.update_profile_success=Kullanıcı hesabı güncellendi. +users.edit_account=Kullanıcı Hesabını Düzenle +users.max_repo_creation=Maksimum Depo Sayısı +users.max_repo_creation_desc=(Genel varsayılan sınırı kullanmak için -1 girin.) +users.is_activated=Kullanıcı Hesabı Etkinleştirildi +users.prohibit_login=Oturum Açmayı Devre Dışı Bırak +users.is_admin=Yöneticidir +users.allow_git_hook=Git İstemcileri Oluşturabilir +users.allow_create_organization=Organizasyon Oluşturabilir +users.update_profile=Kullanıcı Hesabını Güncelle +users.delete_account=Kullanıcı Hesabını Sil +users.still_own_repo=Bu kullanıcı hala bir veya daha fazla depoya sahip. Önce bu depoları silin veya transfer edin. +users.still_has_org=Bu kullanıcı bir organizasyonun üyesidir. Önce kullanıcıyı tüm organizasyonlardan çıkarın. +users.deletion_success=Kullanıcı hesabı silindi. orgs.org_manage_panel=Organizasyon Yönetimi orgs.name=İsim orgs.teams=Ekipler orgs.members=Üyeler +orgs.new_orga=Yeni Organizasyon repos.repo_manage_panel=Depo Yönetimi repos.owner=Sahibi @@ -731,13 +1559,19 @@ repos.name=İsim repos.private=Özel repos.watches=İzlemeler repos.stars=Yıldızlar +repos.forks=Çatallar repos.issues=Sorunlar repos.size=Boyut +hooks.add_webhook=Varsayılan Web İstemcisi Ekle +hooks.update_webhook=Varsayılan Web İstemcisini Güncelle +auths.auth_manage_panel=Kimlik Doğrulama Kaynak Yönetimi +auths.new=Kimlik Doğrulama Kaynağı Ekle auths.name=İsim auths.type=Tür auths.enabled=Aktifleştirilmiş +auths.syncenabled=Kullanıcı Senkronizasyonunu Etkinleştir auths.updated=Güncellendi auths.auth_type=Yetki Türü auths.auth_name=Yetki İsmi @@ -747,8 +1581,16 @@ auths.host=Sunucu auths.port=Bağlantı Noktası auths.bind_dn=Bağlama DN'i auths.bind_password=Bağlama Parolası +auths.bind_password_helper=Uyarı: Bu parola düz metin olarak saklanır. Mümkünse salt okunur bir hesap kullanın. auths.user_base=Kullanıcı Arama Tabanı auths.user_dn=Kullanıcı DN'i +auths.attribute_username_placeholder=Gitea'da girilen kullanıcı adını kullanmak için boş bırakın. +auths.attribute_name=Ad Özelliği +auths.attribute_surname=Soyad Özelliği +auths.attribute_mail=E-posta Özelliği +auths.attribute_ssh_public_key=Açık SSH Anahtarı Özelliği +auths.use_paged_search=Sayfalı Aramayı Kullan +auths.search_page_size=Sayfa Boyutu auths.filter=Kullanıcı Filtresi auths.admin_filter=Yönetici Filtresi auths.ms_ad_sa=MS AD Arama Nitelikleri @@ -756,31 +1598,52 @@ auths.smtp_auth=SMTP Yetkilendirme Türü auths.smtphost=SMTP Sunucusu auths.smtpport=SMTP Portu auths.allowed_domains=İzin Verilen Alan Adları +auths.allowed_domains_helper=Tüm alanlara izin vermek için boş bırakın. Birden çok alan adını virgülle (',') ayırın. auths.enable_tls=TLS Şifrelemeyi Aktifleştir auths.skip_tls_verify=TLS Doğrulamasını Atla auths.pam_service_name=PAM Servis Adı +auths.oauth2_provider=OAuth2 Sağlayıcısı auths.oauth2_clientID=İstemci Kimliği (Anahtar) +auths.oauth2_tokenURL=Jeton URL +auths.oauth2_authURL=Yetkilendirme URL'si +auths.oauth2_profileURL=Profil URL’si +auths.oauth2_emailURL=E-posta URL'si auths.enable_auto_register=Otomatik Kaydolmayı Aktifleştir auths.tips=İpuçları +auths.tips.oauth2.general=OAuth2 Kimlik Doğrulama +auths.tip.oauth2_provider=OAuth2 Sağlayıcısı +auths.tip.bitbucket=https://bitbucket.org/account/user//oauth-consumers/new adında yeni bir OAuth tüketicisi kaydedin ve 'Hesap' - 'Oku' iznini ekleyin auths.tip.dropbox=https://www.dropbox.com/developers/apps adresinde yeni bir uygulama oluştur auths.tip.facebook=https://developers.facebook.com/apps adresinde yeni bir uygulama kaydedin ve "Facebook Giriş" ürününü ekleyin auths.tip.github=https://github.com/settings/applications/new adresinde yeni bir OAuth uygulaması kaydedin auths.tip.gitlab=https://gitlab.com/profile/applications adresinde yeni bir uygulama kaydedin +auths.tip.twitter=https://dev.twitter.com/apps adresine gidin, bir uygulama oluşturun ve “Bu uygulamanın Twitter ile oturum açmak için kullanılmasına izin ver” seçeneğinin etkin olduğundan emin olun +auths.tip.discord=https://discordapp.com/developers/applications/me adresinde yeni bir uygulama kaydedin +auths.tip.gitea=Yeni bir OAuth2 uygulaması kaydedin. Rehber https://docs.gitea.io/en-us/oauth2-provider/ adresinde bulunabilir +auths.edit=Kimlik Doğrulama Kaynağı Düzenle +auths.activated=Bu Kimlik Doğrulama Kaynağı Etkinleştirildi auths.new_success=Kimlik doğrulama '%s' eklendi. auths.delete_auth_title=Kimlik Doğrulama Kaynağını Sil config.server_config=Sunucu Yapılandırması +config.app_name=Site Başlığı +config.app_ver=Gitea Sürümü config.custom_conf=Yapılandırma Dosyası Yolu +config.offline_mode=Yerel Kip config.disable_router_log=Yönlendirici Log'larını Devre Dışı Bırak +config.run_user=Şu Kullanıcı Olarak Çalıştır config.run_mode=Çalıştırma Modu config.git_version=Git Sürüm config.repo_root_path=Depo Kök Yolu +config.lfs_root_path=LFS Kök Dizini config.static_file_root_path=Sabit Dosya Kök Yolu +config.log_file_root_path=Günlük Dosyası Yolu config.script_type=Betik Türü config.reverse_auth_user=Tersine Yetkilendirme Kullanıcısı config.ssh_config=SSH Yapılandırması config.ssh_enabled=Aktif +config.ssh_domain=Sunucu Alan Adı config.ssh_port=Bağlantı Noktası config.ssh_listen_port=Port'u Dinle config.ssh_root_path=Kök Yol @@ -789,17 +1652,29 @@ config.ssh_keygen_path=Keygen ('ssh-keygen') Yolu config.ssh_minimum_key_size_check=Minimum Anahtar Uzunluğu Kontrolü config.ssh_minimum_key_sizes=Minimum Anahtar Uzunlukları +config.lfs_config=LFS Yapılandırması +config.lfs_enabled=Etkin +config.lfs_content_path=LFS İçerik Yolu config.db_config=Veritabanı Yapılandırması config.db_type=Türü config.db_host=Sunucu config.db_name=İsim +config.db_user=Kullanıcı adı +config.db_ssl_mode=SSL config.db_path=Yol config.service_config=Servis Yapılandırması +config.register_email_confirm=Kayıt Olmak İçin E-posta Onayı Gereksin +config.disable_register=Kullanıcı Kaydını Devre Dışı Bırak +config.allow_only_external_registration=Sadece Dış Hizmetler Aracılığıyla Kullanıcı Kaydına İzin Ver config.show_registration_button=Kaydolma Tuşunu Göster +config.require_sign_in_view=Sayfaları Görüntülemek için Giriş Yapmaya Zorla +config.mail_notify=E-Posta Bildirimlerini Etkinleştir config.disable_key_size_check=Minimum Anahtar Uzunluğu Kontrolünü Devre Dışı Bırak +config.enable_captcha=CAPTCHA'yı Etkinleştir config.active_code_lives=Kod Yaşamlarını Aktifleştir +config.default_keep_email_private=E-posta Adreslerini Varsayılan Olarak Gizle config.webhook_config=Web İstekleri Yapılandırması config.queue_length=Kuyruk Uzunluğu @@ -811,6 +1686,9 @@ config.mailer_disable_helo=HELO'yu Devre Dışı Bırak config.mailer_name=İsim config.mailer_host=Sunucu config.mailer_user=Kullanıcı +config.send_test_mail=Test E-postası Gönder +config.test_mail_failed='%s' adresine test e-postası gönderilemedi: %v +config.test_mail_sent='%s' adresine bir test e-postası gönderildi. config.oauth_config=OAuth Yapılandırması config.oauth_enabled=Aktif @@ -834,21 +1712,41 @@ config.picture_service=Resim Servisi config.disable_gravatar=Gravatar Hizmet Dışı config.git_config=Git Yapılandırması +config.git_disable_diff_highlight=Değişiklik Sözdizimi Vurgusunu Devre Dışı Bırak +config.git_max_diff_lines=Maksimum Değişiklik Satırı (tek bir dosya için) +config.git_max_diff_line_characters=Maksimum Değişiklik Karakteri (tek bir satır için) +config.git_max_diff_files=Maksimum Değişiklik Dosyaları (gösterilecek) +config.git_gc_args=GC Argümanları +config.git_migrate_timeout=Göç İşlemi Zaman Aşımı +config.git_mirror_timeout=Yansı Güncelleme Zaman Aşımı +config.git_clone_timeout=Klonlama İşlemi Zaman Aşımı +config.git_pull_timeout=Çekme İşlemi Zaman Aşımı +config.git_gc_timeout=GC İşlemi Zaman Aşımı config.log_config=Log Yapılandırması config.log_mode=Log Modu +config.macaron_log_mode=Macaron Günlük Kipi +config.own_named_logger=Adlandırılmış Günlük Kaydedicisi +config.router_log_mode=Yönlendirici Günlük Kipi +config.disabled_logger=Devre Dışı +config.access_log_mode=Erişim Günlüğü Kipi +config.access_log_template=Şablon +config.xorm_log_mode=XORM Günlük Kipi +config.xorm_log_sql=SQL Günlüğü monitor.cron=Cron Görevleri monitor.name=İsim monitor.schedule=Program monitor.next=Sonraki Zaman monitor.previous=Önceki Zaman +monitor.execute_times=Çalıştırma monitor.process=Çalışan Süreçler monitor.desc=Açıklama monitor.start=Başlangıç Zamanı monitor.execute_time=Çalıştırma Zamanı notices.system_notice_list=Sistem Bildirimleri +notices.view_detail_header=Bildirim Ayrıntılarını Görüntüle notices.actions=İşlemler notices.select_all=Tümünü Seç notices.deselect_all=Tümünü Seçmeyi Bırak @@ -859,10 +1757,12 @@ notices.type=Tip notices.type_1=Depo notices.desc=Açıklama notices.op=İşlem +notices.delete_success=Sistem bildirimleri silindi. [action] create_repo=depo %s oluşturuldu rename_repo=%[1]s olan depo adını %[3]s buna çevirdi +commit_repo=%[4]s deposunda %[3]s dalını itti create_issue=`%s#%[2]s sorununu açtı` close_issue=`%s#%[2]s sorununu kapattı` reopen_issue=`%s#%[2]s sorununu tekrar açtı` @@ -872,11 +1772,18 @@ reopen_pull_request=`%s#%[2]s değişiklik isteğini t comment_issue=`%s#%[2]s sorununa yorum yazdı` merge_pull_request=`%s#%[2]s değişim isteğini birleştirdi` transfer_repo=depo %s %s'a aktarıldı +push_tag=%[3]s deposuna %[2]s etiketi itildi +delete_tag=%[2]s etiketi %[3]s deposundan silindi +delete_branch=%[3]s deposundan %[2]s dalı silindi +compare_commits=%d işlemeyi karşılaştır +compare_commits_general=İşlemeleri karşılaştır +mirror_sync_push=işlemeler yansıdan %[4]s deposundaki %[3]s dalına eşitlendi [tool] ago=%s önce from_now=%s şu andan now=şimdi +future=gelecek 1s=1 saniye 1m=1 dakika 1h=1 saat @@ -895,16 +1802,31 @@ raw_seconds=saniyeler raw_minutes=dakikalar [dropzone] +default_message=Dosyaları buraya bırakın veya yüklemek için tıklayın. +invalid_input_type=Bu tür dosyaları yükleyemezsiniz. +file_too_big=Dosya boyutu ({{filesize}} MB) maksimum boyutu ({{maxFilesize}} MB) aşıyor. remove_file=Dosya Kaldır [notification] +notifications=Bildirimler +unread=Okunmamış +read=Okunmuş +no_unread=Okunmamış bildirim yok. +no_read=Okunmuş bildirim yok. pin=Pin bildirimi mark_as_read=Okundu olarak işaretle mark_as_unread=Okunmadı olarak işaretle mark_all_as_read=Tümünü okundu olarak işaretle [gpg] +error.extract_sign=İmza çıkarılamadı +error.generate_hash=İşlemenin sağlama kodu oluşturulamadı +error.no_committer_account=İşleme yapanın e-posta adresine bağlı hesap yok error.no_gpg_keys_found=Veri tabanında bu imza için bilinen anahtar bulunamadı +error.not_signed_commit=İmzalı bir işleme değil +error.failed_retrieval_gpg_keys=İşleme yapanın hesabına bağlı herhangi bir anahtar alınamadı [units] +error.no_unit_allowed_repo=Bu deponun hiçbir bölümüne erişme izniniz yok. +error.unit_not_allowed=Bu depo bölümüne erişme izniniz yok. diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 5368d13a96..22d30705db 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -487,6 +487,7 @@ confirm_delete_account=Підтвердження видалення delete_account_title=Видалити цей обліковий запис delete_account_desc=Ви впевнені, що хочете остаточно видалити цей обліковий запис? + [repo] owner=Власник repo_name=Назва репозиторію @@ -665,7 +666,6 @@ issues.remove_milestone_at=`видалено з етапу%s %s` issues.deleted_milestone=`(видалено)` issues.self_assign_at=`самостійно призначений %s` issues.add_assignee_at=`був призначений %s %s` -issues.change_title_at=`змінив(ла) заголовок з %s на %s %s` issues.delete_branch_at=`видалена гілка %s %s` issues.open_tab=%d відкрито issues.close_tab=%d закрито @@ -813,7 +813,6 @@ issues.review.review=Рецензії issues.review.show_outdated=Показати застарілі issues.review.hide_outdated=Приховати застарілі -pulls.desc=Увімкнути запити на злиття та інтерфейс узгодження правок. pulls.new=Новий запит на злиття pulls.compare_changes=Новий запит на злиття pulls.compare_changes_desc=Порівняти дві гілки і створити запит на злиття для змін. diff --git a/options/locale/locale_vi-VN.ini b/options/locale/locale_vi-VN.ini index 8efc2a4d50..f2571ce9c0 100644 --- a/options/locale/locale_vi-VN.ini +++ b/options/locale/locale_vi-VN.ini @@ -49,6 +49,7 @@ + [repo] diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 1eb82ef403..7667609793 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -299,6 +299,7 @@ max_size_error=长度最大为 %s 个字符。 email_error=不是一个有效的邮箱地址。 url_error=不是一个有效的 URL。 include_error=必须包含子字符串 '%s'。 +glob_pattern_error=`匹配模式无效:%s.` unknown_error=未知错误: captcha_incorrect=验证码不正确。 password_not_match=密码不匹配。 @@ -317,6 +318,7 @@ enterred_invalid_repo_name=输入的仓库名称不正确 enterred_invalid_owner_name=新的所有者名称无效。 enterred_invalid_password=输入的密码不正确 user_not_exist=该用户名不存在 +team_not_exist=团队不存在 last_org_owner=您不能从 "所有者" 团队中删除最后一个用户。在任何给定的团队中必须至少有一个所有者。 cannot_add_org_to_team=组织不能被加入到团队中。 @@ -556,11 +558,17 @@ confirm_delete_account=确认删除帐户 delete_account_title=删除当前帐户 delete_account_desc=确实要永久删除此用户帐户吗? +email_notifications.enable=启用邮件通知 +email_notifications.onmention=只在被提到时邮件通知 +email_notifications.disable=停用邮件通知 +email_notifications.submit=邮件通知设置 + [repo] owner=拥有者 repo_name=仓库名称 repo_name_helper=好的存储库名称使用简短、深刻和独特的关键字。 visibility=可见性 +visibility_description=只有组织所有人或拥有权利的组织成员才能看到。 visibility_helper=将仓库设为私有 visibility_helper_forced=站点管理员强制要求新仓库为私有。 visibility_fork_helper=(修改该值将会影响到所有派生仓库) @@ -571,6 +579,8 @@ fork_visibility_helper=无法更改派生仓库的可见性。 repo_desc=仓库描述 repo_lang=仓库语言 repo_gitignore_helper=选择 .gitignore 模板。 +issue_labels=工单标签 +issue_labels_helper=选择一个工单标签集 license=授权许可 license_helper=选择授权许可文件。 readme=自述 @@ -583,7 +593,7 @@ mirror_prune_desc=删除过时的远程跟踪引用 mirror_interval=镜像间隔 (有效时间单位为 "h"、"m"、"s")。0将禁用自动同步。 mirror_interval_invalid=镜像间隔无效。 mirror_address=从URL克隆 -mirror_address_desc=在 URL 中包括任何必需的授权凭据。这些 URL 必须进行编码。 +mirror_address_desc=在 Clone 认证部分里输入必要的信息。 mirror_address_url_invalid=URL无效。请检查您所输入的URL是否正确。 mirror_address_protocol_invalid=提供的 url 无效。只能从 http(s):// 或 git:// 位置进行镜像。 mirror_last_synced=上次同步 @@ -695,6 +705,7 @@ editor.delete=删除 '%s' editor.commit_message_desc=添加一个可选的扩展描述... editor.commit_directly_to_this_branch=直接提交至 %s 分支。 editor.create_new_branch=为此提交创建一个 新的分支 并发起合并请求。 +editor.propose_file_change=提议文件更改 editor.new_branch_name_desc=新的分支名称... editor.cancel=取消 editor.filename_cannot_be_empty=文件名不能为空。 @@ -768,7 +779,7 @@ issues.self_assign_at=`于 %s 指派给自己` issues.add_assignee_at=`于 %[2]s 被 %[1]s 指派` issues.remove_assignee_at=`%s 取消了指派在 %s` issues.remove_self_assignment=`于 %s 取消了指派` -issues.change_title_at=`于 %[3]s 修改标题 %[1]s%[2]s` +issues.change_title_at=`于 %[3]s 修改标题 %[1]s%[2]s` issues.delete_branch_at=`于 %[2]s 删除了分支 %[1]s` issues.open_tab=%d 个开启中 issues.close_tab=%d 个已关闭 @@ -825,6 +836,10 @@ issues.create_comment=评论 issues.closed_at=`于 %[2]s 关闭` issues.reopened_at=`于 %[2]s 重新开启` issues.commit_ref_at=`在代码提交 %[2]s 中引用了该工单` +issues.ref_issue_at=`引用工单 %[1]s` +issues.ref_pull_at=`引用合并请求 %[1]s` +issues.ref_issue_ext_at=`引用工单 %[1]s %[2]s` +issues.ref_pull_ext_at=`引用合并请求 %[1]s %[2]s` issues.poster=发布者 issues.collaborator=协作者 issues.owner=所有者 @@ -944,7 +959,7 @@ issues.review.reviewers=评审人 issues.review.show_outdated=显示过时的 issues.review.hide_outdated=隐藏过时的 -pulls.desc=启用合并请求和代码审阅。 +pulls.desc=启用合并请求和代码评审。 pulls.new=创建合并请求 pulls.compare_changes=创建合并请求 pulls.compare_changes_desc=选择合并的目标分支和源分支。 @@ -963,12 +978,15 @@ pulls.tab_files=文件变动 pulls.reopen_to_merge=请重新打开此拉请求执行合并。 pulls.cant_reopen_deleted_branch=无法重新打开此合并请求,因为分支已删除。 pulls.merged=已合并 +pulls.merged_as=该合并请求已作为 %[2]s 被合并。 pulls.has_merged=请求已合并。 pulls.title_wip_desc=`标题以 %s 开头以免合并请求意外合并。` pulls.cannot_merge_work_in_progress=这个合并请求被标记为尚未完成的工作。完成后请从标题中移除%s前缀。 pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。 pulls.files_conflicted=此合并请求有变更与目标分支冲突。 pulls.is_checking=正在进行合并冲突检测,请稍后再试。 +pulls.required_status_check_failed=一些必要的检查没有成功。 +pulls.required_status_check_administrator=作为管理员,您仍可合并此合并请求。 pulls.blocked_by_approvals=此合并请求没有通过审批。已获取审批数%d个,共需要审批数%d个。 pulls.can_auto_merge_desc=该合并请求可以进行自动合并操作。 pulls.cannot_auto_merge_desc=该合并请求存在冲突,无法进行自动合并操作。 @@ -976,6 +994,7 @@ pulls.cannot_auto_merge_helper=手动合并解决此冲突 pulls.no_merge_desc=由于未启用合并选项,此合并请求无法被合并。 pulls.no_merge_helper=在仓库设置中启用合并选项或者手工合并请求。 pulls.no_merge_wip=这个合并请求无法合并,因为被标记为尚未完成的工作。 +pulls.no_merge_status_check=此合并请求不能合并,因为不是所有的状态检查都是成功的。 pulls.merge_pull_request=合并请求 pulls.rebase_merge_pull_request=变基并合并 pulls.rebase_merge_commit_pull_request=变基合并 (--no-ff) @@ -1117,6 +1136,7 @@ settings.collaboration=协作者 settings.collaboration.admin=管理员 settings.collaboration.write=可写权限 settings.collaboration.read=可读权限 +settings.collaboration.owner=所有者 settings.collaboration.undefined=未定义 settings.hooks=管理 Web 钩子 settings.githooks=管理 Git 钩子 @@ -1124,6 +1144,10 @@ settings.basic_settings=基本设置 settings.mirror_settings=镜像设置 settings.sync_mirror=同步 settings.mirror_sync_in_progress=镜像同步正在进行中,请稍后后再试。 +settings.email_notifications.enable=启用邮件通知 +settings.email_notifications.onmention=只在被提到时邮件通知 +settings.email_notifications.disable=停用邮件通知 +settings.email_notifications.submit=邮件通知设置 settings.site=网站 settings.update_settings=更新仓库设置 settings.advanced_settings=高级设置 @@ -1194,6 +1218,11 @@ settings.collaborator_deletion_desc=删除协作者后他将无法再对此仓 settings.remove_collaborator_success=协作者删除成功! settings.search_user_placeholder=搜索用户... settings.org_not_allowed_to_be_collaborator=组织不允许被添加为仓库协作者! +settings.change_team_access_not_allowed=更改仓库的团队访问权限仅限于组织所有者 +settings.team_not_in_organization=团队不在与仓库相同的组织中 +settings.add_team_duplicate=团队已经拥有仓库 +settings.add_team_success=团队现在可以访问仓库。 +settings.remove_team_success=团队访问仓库的权限已被删除。 settings.add_webhook=添加 Web 钩子 settings.add_webhook.invalid_channel_name=Webhook 通道名称不能为空且不能仅包含一个 # 字符。 settings.hooks_desc=当Gitea事件发生时,Web钩子自动发出HTTP POST请求。在 指南 中阅读更多内容。 @@ -1243,6 +1272,8 @@ settings.event_pull_request=合并请求 settings.event_pull_request_desc=开启、关闭、重新开启、编辑、同意、拒绝、审查回复、指派、取消指派、更新标签、清除标签或同步合并请求 settings.event_push=推送 settings.event_push_desc=Git 仓库推送 +settings.branch_filter=分支过滤 +settings.branch_filter_desc=推送、创建,删除分支事件白名单,支持匹配符。如果为空或者 *,所有分支的事件均被触发。语法参见 github.com/gobwas/glob 。示例: Master, {master,release*}。 settings.event_repository=仓库 settings.event_repository_desc=创建或删除仓库 settings.active=激活 @@ -1293,6 +1324,9 @@ settings.protect_merge_whitelist_committers=启用合并白名单 settings.protect_merge_whitelist_committers_desc=仅允许白名单用户或团队合并合并请求到此分支。 settings.protect_merge_whitelist_users=合并白名单用户: settings.protect_merge_whitelist_teams=合并白名单团队: +settings.protect_check_status_contexts=启用状态检查 +settings.protect_check_status_contexts_desc=要求状态检查通过才能合并,选择必须先通过哪些状态检查才能合并。如果启用,推送的合并请求必须先通过状态检查才能够合并到对应的分支。如果没有选择具体的状态检查上下文,则所有的状态检查都通过才能合并。 +settings.protect_check_status_contexts_list=此仓库上周进行过的状态检查 settings.protect_required_approvals=所需的批准: settings.protect_required_approvals_desc=只允许合并有足够审批的请求。 settings.protect_approvals_whitelist_users=审查者白名单: @@ -1340,6 +1374,11 @@ diff.whitespace_ignore_at_eol=忽略行末空白符号的更改 diff.stats_desc=共有 %d 个文件被更改,包括 %d 次插入%d 次删除 diff.bin=二进制 diff.view_file=查看文件 +diff.file_before=之前 +diff.file_after=之后 +diff.file_image_width=宽度 +diff.file_image_height=高度 +diff.file_byte_size=大小 diff.file_suppressed=文件差异内容过多而无法显示 diff.too_many_files=部分文件因为文件数量过多而无法显示 diff.comment.placeholder=留下评论 @@ -1405,6 +1444,8 @@ branch.deleted_by=删除人:%s branch.restore_success=分支 '%s' 已恢复。 branch.restore_failed=未能还原分支%s。 branch.protected_deletion_failed=分支 '%s' 已被保护,不可删除。 +branch.restore=恢复分支 '%s' +branch.download=下载分支 '%s' topic.manage_topics=管理主题 topic.done=保存 @@ -1440,6 +1481,8 @@ settings.options=组织 settings.full_name=组织全名 settings.website=官方网站 settings.location=所在地区 +settings.permission=权限 +settings.repoadminchangeteam=仓库管理员可以添加或移除团队的访问权限 settings.visibility=可见性 settings.visibility.public=公开 settings.visibility.limited=受限 (仅对登录用户可见) @@ -1686,6 +1729,7 @@ auths.tip.google_plus=从谷歌 API 控制台 (https://console.developers.google auths.tip.openid_connect=使用 OpenID 连接发现 URL (/.well-known/openid-configuration) 来指定终点 auths.tip.twitter=访问 https://dev.twitter.com/apps,创建应用并确保启用了"允许此应用程序用于登录 Twitter"的选项。 auths.tip.discord=在 https://discordapp.com/developers/applications/me 上注册新应用程序 +auths.tip.gitea=注册一个新的 OAuth2 应用程序。可以访问 https://docs.gitea.io/en-us/oauth 2-product/ 查看帮助 。 auths.edit=修改认证源 auths.activated=该认证源已经启用 auths.new_success=已添加身份验证 '%s'。 @@ -1836,7 +1880,7 @@ monitor.name=任务名称 monitor.schedule=任务安排 monitor.next=下次执行时间 monitor.previous=上次执行时间 -monitor.execute_times=执行时长 +monitor.execute_times=执行次数 monitor.process=运行中进程 monitor.desc=进程描述 monitor.start=开始时间 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 8f3f392dc3..ab97fccb6b 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -232,6 +232,7 @@ repos_none=您不擁有任何存儲庫 delete_account=刪除當前帳戶 confirm_delete_account=確認刪除帳戶 + [repo] owner=擁有者 repo_name=儲存庫名稱 @@ -338,7 +339,6 @@ issues.remove_milestone_at=`從里程碑 %[2]s 刪除 %[1]s` issues.deleted_milestone=`(已刪除)` issues.self_assign_at=將 %s 指派給自己 issues.add_assignee_at=`被%s %s指派` -issues.change_title_at=`修改標題從 %s%s %s` issues.delete_branch_at=`刪除分支 %s %s` issues.open_tab=%d 個開啓中 issues.close_tab=%d 個已關閉 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 42719c88c8..796906312b 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -436,6 +436,7 @@ confirm_delete_account=確認刪除帳戶 delete_account_title=刪除使用者帳號 delete_account_desc=您是否確定要永久刪除此帳號? + [repo] owner=擁有者 repo_name=儲存庫名稱 @@ -587,7 +588,6 @@ issues.remove_milestone_at=`從里程碑 %[2]s 刪除 %[1]s` issues.deleted_milestone=`(已刪除)` issues.self_assign_at=將 %s 指派給自己 issues.add_assignee_at=`被%s %s指派` -issues.change_title_at=`修改標題從 %s%s %s` issues.delete_branch_at=`刪除分支 %s %s` issues.open_tab=%d 個開啓中 issues.close_tab=%d 個已關閉 @@ -696,7 +696,6 @@ issues.due_date_modified=已將截止日期修改為 %s ,原截止日期: %s issues.due_date_remove=已移除截止日期 %s %s issues.due_date_overdue=逾期 -pulls.desc=啟用合併請求及程式碼審核 pulls.new=建立合併請求 pulls.compare_changes=建立合併請求 pulls.compare_base=合併到 diff --git a/package-lock.json b/package-lock.json index 57e4af164c..d2aa604203 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,31 +3,31 @@ "lockfileVersion": 1, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" } }, "@babel/core": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", - "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", + "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", - "@babel/helpers": "^7.4.4", - "@babel/parser": "^7.4.5", + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helpers": "^7.5.5", + "@babel/parser": "^7.5.5", "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.5", - "@babel/types": "^7.4.4", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -42,14 +42,14 @@ } }, "@babel/generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", - "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", "dev": true, "requires": { - "@babel/types": "^7.4.4", + "@babel/types": "^7.5.5", "jsesc": "^2.5.1", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "source-map": "^0.5.0", "trim-right": "^1.0.1" }, @@ -92,20 +92,20 @@ } }, "@babel/helpers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", - "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.5.tgz", + "integrity": "sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==", "dev": true, "requires": { "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -114,9 +114,9 @@ } }, "@babel/parser": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", - "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", "dev": true }, "@babel/template": { @@ -131,30 +131,30 @@ } }, "@babel/traverse": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", - "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.4.5", - "@babel/types": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.11" + "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, @@ -198,9 +198,9 @@ "dev": true }, "@types/node": { - "version": "12.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.10.tgz", - "integrity": "sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ==", + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.3.tgz", + "integrity": "sha512-3SiLAIBkDWDg6vFo0+5YJyHPWU9uwu40Qe+v+0MH8wRKYBimHvvAOyk3EzMrD/TrIlLYfXrqDqrg913PynrMJQ==", "dev": true }, "@types/unist": { @@ -231,15 +231,15 @@ } }, "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", + "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", "dev": true }, "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", + "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", "dev": true }, "agent-base": { @@ -261,9 +261,9 @@ } }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -442,18 +442,18 @@ "dev": true }, "autoprefixer": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz", - "integrity": "sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz", + "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==", "dev": true, "requires": { - "browserslist": "^4.6.1", - "caniuse-lite": "^1.0.30000971", + "browserslist": "^4.6.3", + "caniuse-lite": "^1.0.30000980", "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.16", - "postcss-value-parser": "^3.3.1" + "postcss": "^7.0.17", + "postcss-value-parser": "^4.0.0" } }, "aws-sign2": { @@ -599,14 +599,14 @@ } }, "browserslist": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.3.tgz", - "integrity": "sha512-CNBqTCq22RKM8wKJNowcqihHJ4SkI8CGeK7KOR9tPboXUuS5Zk5lQgzzTbs4oxD8x+6HUshZUa2OyNI9lR93bQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", + "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000975", - "electron-to-chromium": "^1.3.164", - "node-releases": "^1.1.23" + "caniuse-lite": "^1.0.30000989", + "electron-to-chromium": "^1.3.247", + "node-releases": "^1.1.29" } }, "buffer-from": { @@ -616,9 +616,9 @@ "dev": true }, "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { "bluebird": "^3.5.5", @@ -626,6 +626,7 @@ "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", @@ -635,17 +636,6 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - } } }, "cache-base": { @@ -729,9 +719,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000979", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000979.tgz", - "integrity": "sha512-gcu45yfq3B7Y+WB05fOMfr0EiSlq+1u+m6rPHyJli/Wy3PVQNGaU7VA4bZE5qw+AU2UVOBR/N5g1bzADUqdvFw==", + "version": "1.0.30000989", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz", + "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==", "dev": true }, "caseless": { @@ -789,9 +779,9 @@ "dev": true }, "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -806,12 +796,35 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } } }, "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "class-utils": { @@ -882,6 +895,17 @@ "string-width": "^2.1.1", "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "clone": { @@ -986,6 +1010,14 @@ "dev": true, "requires": { "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "copy-concurrently": { @@ -1206,13 +1238,27 @@ } }, "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", "dev": true, "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + } } }, "domelementtype": { @@ -1273,9 +1319,9 @@ } }, "electron-to-chromium": { - "version": "1.3.183", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.183.tgz", - "integrity": "sha512-WbKCYs7yAFOfpuoa2pK5kbOngriUtlPC+8mcQW5L/686wv04w7hYXfw5ScDrsl9kixFw1SPsALEob5V/gtlDxw==", + "version": "1.3.249", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.249.tgz", + "integrity": "sha512-BSNKVkF67cfgwCOJD3/eyIFi001+8mRoazPJYZRpxxtabToCDCef1vhZMDjA6CPfAdgOI0QMOiGLELgJVYP76Q==", "dev": true }, "emoji-regex": { @@ -1355,9 +1401,9 @@ "dev": true }, "eslint": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz", - "integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.3.0.tgz", + "integrity": "sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1366,42 +1412,51 @@ "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^6.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^3.1.0", + "glob-parent": "^5.0.0", "globals": "^11.7.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", + "inquirer": "^6.4.1", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", "progress": "^2.0.0", "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", - "text-table": "^0.2.0" + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -1409,26 +1464,29 @@ } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", "dev": true }, "espree": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz", - "integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz", + "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==", "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "acorn": "^7.0.0", + "acorn-jsx": "^5.0.2", + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -1456,15 +1514,15 @@ } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "execa": { @@ -1569,9 +1627,9 @@ } }, "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { "chardet": "^0.7.0", @@ -1669,6 +1727,29 @@ "is-glob": "^4.0.0", "merge2": "^1.2.3", "micromatch": "^3.1.10" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } } }, "fast-json-stable-stringify": { @@ -2445,24 +2526,12 @@ } }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, "glob-to-regexp": { @@ -2537,9 +2606,9 @@ } }, "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, "graceful-readlink": { @@ -2605,15 +2674,15 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", "dev": true }, "html-tags": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.0.0.tgz", - "integrity": "sha512-xiXEBjihaNI+VZ2mKEoI5ZPxqUsevTKM+aeeJ/W4KAg2deGE35minmCJMn51BvwJZmiHaeAxrb2LAS0yZJxuuA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", "dev": true }, "htmlparser2": { @@ -2689,12 +2758,12 @@ } }, "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", + "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", "dev": true, "requires": { - "agent-base": "^4.1.0", + "agent-base": "^4.3.0", "debug": "^3.1.0" }, "dependencies": { @@ -2806,6 +2875,12 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2829,9 +2904,9 @@ "dev": true }, "inquirer": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.4.1.tgz", - "integrity": "sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, "requires": { "ansi-escapes": "^3.2.0", @@ -2840,30 +2915,13 @@ "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.12", "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", "string-width": "^2.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } } }, "invert-kv": { @@ -3256,9 +3314,9 @@ } }, "less": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", - "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", + "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", "dev": true, "requires": { "clone": "^2.1.2", @@ -3328,9 +3386,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "log-symbols": { @@ -3359,35 +3417,26 @@ } }, "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - }, - "dependencies": { - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } + "yallist": "^3.0.2" } }, "make-fetch-happen": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", - "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz", + "integrity": "sha512-nFr/vpL1Jc60etMVKeaLOqfGjMMb3tAHFVJWxHOFCFS04Zmd7kGlMxo0l1tzfhoQje0/UPnd0X8OeGUiXXnfPA==", "dev": true, "requires": { "agentkeepalive": "^3.4.1", - "cacache": "^11.0.1", + "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.1", - "lru-cache": "^4.1.2", + "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", "promise-retry": "^1.1.1", @@ -3506,9 +3555,9 @@ } }, "merge2": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", - "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.4.tgz", + "integrity": "sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==", "dev": true }, "micromatch": { @@ -3711,9 +3760,9 @@ } }, "node-releases": { - "version": "1.1.24", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.24.tgz", - "integrity": "sha512-wym2jptfuKowMmkZsfCSTsn8qAVo8zm+UiQA6l5dNqUcpfChZSnS/vbbpOeXczf+VdPhutxh+99lWHhdd6xKzg==", + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.29.tgz", + "integrity": "sha512-R5bDhzh6I+tpi/9i2hrrvGJ3yKPYzlVOORDkXhnZuwi5D3q1I5w4vYy24PJXTcLk9Q0kws9TO77T75bcK8/ysQ==", "dev": true, "requires": { "semver": "^5.3.0" @@ -3894,9 +3943,9 @@ "dev": true }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -4062,9 +4111,9 @@ } }, "postcss-cli": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-6.1.2.tgz", - "integrity": "sha512-jIWfIkqt8cTThSpH8DBaNxHlBf99OKSem2RseRpfVPqWayxHKQB0IWdS/IF5XSGeFU5QslSDTdVHnw6qggXGkA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-6.1.3.tgz", + "integrity": "sha512-eieqJU+OR1OFc/lQqMsDmROTJpoMZFvoAQ+82utBQ8/8qGMTfH9bBSPsTdsagYA8uvNzxHw2I2cNSSJkLAGhvw==", "dev": true, "requires": { "chalk": "^2.1.0", @@ -4091,9 +4140,9 @@ } }, "postcss-jsx": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.1.tgz", - "integrity": "sha512-xaZpy01YR7ijsFUtu5rViYCFHurFIPHir+faiOQp8g/NfTfWqZCKDhKrydQZ4d8WlSAmVdXGwLjpFbsNUI26Sw==", + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.3.tgz", + "integrity": "sha512-yV8Ndo6KzU8eho5mCn7LoLUGPkXrRXRjhMpX4AaYJ9wLJPv099xbtpbRQ8FrPnzVxb/cuMebbPR7LweSt+hTfA==", "dev": true, "requires": { "@babel/core": ">=7.2.2" @@ -4198,9 +4247,9 @@ "dev": true }, "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", "dev": true }, "prelude-ls": { @@ -4260,16 +4309,10 @@ "dev": true, "optional": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", "dev": true, "optional": true }, @@ -4342,6 +4385,12 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true } } }, @@ -4441,6 +4490,14 @@ "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "readdirp": { @@ -4612,9 +4669,9 @@ "dev": true }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -4691,9 +4748,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", "dev": true }, "safe-regex": { @@ -4712,9 +4769,9 @@ "dev": true }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "set-blocking": { @@ -5001,9 +5058,9 @@ } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, "specificity": { @@ -5105,6 +5162,17 @@ "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "string_decoder": { @@ -5114,6 +5182,14 @@ "dev": true, "requires": { "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "stringify-entities": { @@ -5129,12 +5205,20 @@ } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } }, "strip-bom": { @@ -5156,9 +5240,9 @@ "dev": true }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, "style-search": { @@ -5223,12 +5307,6 @@ "table": "^5.2.3" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -5260,9 +5338,9 @@ "dev": true }, "ignore": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.2.tgz", - "integrity": "sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", "dev": true }, "is-fullwidth-code-point": { @@ -5296,6 +5374,12 @@ "picomatch": "^2.0.5" } }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -5319,15 +5403,6 @@ "strip-ansi": "^5.2.0" } }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5379,23 +5454,17 @@ "dev": true }, "table": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.1.tgz", - "integrity": "sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", + "ajv": "^6.10.2", + "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -5406,15 +5475,6 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -5748,29 +5808,23 @@ "dev": true }, "updates": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/updates/-/updates-8.2.0.tgz", - "integrity": "sha512-HgTxeZwpx40My9OLzvXD7MpBHPKAAQM2ergvVjMDgmsLRH1DQXx5hrlQLuDhvO7HcGgBH+/P/y2MPnovpW2hAw==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/updates/-/updates-8.5.3.tgz", + "integrity": "sha512-bREdpucNEtSULXu2PLfYmKnRD6E0lM16vbZNsgR39Ou7FqiUEyasA0o2Lrb3uHwZN3L2WhOjf+EjQl7NiOHhug==", "dev": true, "requires": { "chalk": "2.4.2", "find-up": "4.1.0", - "hosted-git-info": "2.7.1", - "make-fetch-happen": "4.0.1", + "hosted-git-info": "3.0.0", + "make-fetch-happen": "5.0.0", "minimist": "1.2.0", "rc": "1.2.8", "registry-auth-token": "4.0.0", - "semver": "6.1.2", + "semver": "6.3.0", "string-width": "4.1.0", "text-table": "0.2.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -5787,6 +5841,15 @@ "path-exists": "^4.0.0" } }, + "hosted-git-info": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.0.tgz", + "integrity": "sha512-zYSx1cP4MLsvKtTg8DF/PI6e6FHZ3wcawcTGsrLU2TM+UfD4jmSrn2wdQT16TFbH3lO4PIdjLG0E+cuYDgFD9g==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -5824,9 +5887,9 @@ "dev": true }, "semver": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", - "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "string-width": { @@ -5839,15 +5902,6 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^5.2.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -5879,12 +5933,18 @@ "dev": true }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", "dev": true, "optional": true }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -6032,9 +6092,9 @@ "dev": true }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, "y18n": { diff --git a/package.json b/package.json index 059bbdefb3..376ae89b67 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,14 @@ "node": ">=8" }, "devDependencies": { - "autoprefixer": "9.6.0", - "eslint": "6.0.1", - "less": "3.9.0", + "autoprefixer": "9.6.1", + "eslint": "6.3.0", + "less": "3.10.3", "less-plugin-clean-css": "1.5.1", - "postcss-cli": "6.1.2", + "postcss-cli": "6.1.3", "stylelint": "10.1.0", "stylelint-config-standard": "18.3.0", - "updates": "8.2.0" + "updates": "8.5.3" }, "browserslist": [ "> 1%", diff --git a/public/css/index.css b/public/css/index.css index b948766b41..496194decc 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -81,7 +81,7 @@ a{cursor:pointer} .ui.menu,.ui.segment,.ui.vertical.menu{box-shadow:none} .ui .menu:not(.vertical) .item>.button.compact{padding:.58928571em 1.125em} .ui .menu:not(.vertical) .item>.button.small{font-size:.92857143rem} -.ui.menu .ui.dropdown.item .menu .item{margin-right:auto} +.ui.menu .ui.dropdown.item .menu .item{width:100%} .ui.dropdown .menu>.item>.floating.label{z-index:11} .ui.dropdown .menu .menu>.item>.floating.label{z-index:21} .ui .text.red{color:#d95c5c!important} @@ -140,6 +140,16 @@ a{cursor:pointer} .ui .migrate{color:#888!important;opacity:.5} .ui .migrate a{color:#444!important} .ui .migrate a:hover{color:#000!important} +.ui .border{border:1px solid} +.ui .border.red{border-color:#d95c5c!important} +.ui .border.blue{border-color:#428bca!important} +.ui .border.black{border-color:#444} +.ui .border.grey{border-color:#767676!important} +.ui .border.light.grey{border-color:#888!important} +.ui .border.green{border-color:#6cc644!important} +.ui .border.purple{border-color:#6e5494!important} +.ui .border.yellow{border-color:#fbbd08!important} +.ui .border.gold{border-color:#a1882b!important} .ui .branch-tag-choice{line-height:20px} @media only screen and (max-width:767px){.ui.pagination.menu .item.navigation span.navigation_label,.ui.pagination.menu .item:not(.active):not(.navigation){display:none} } @@ -200,6 +210,7 @@ footer .ui.left,footer .ui.right{line-height:40px} #user-heatmap svg:not(:root){overflow:inherit;padding:0!important} @media only screen and (max-width:1200px){#user-heatmap{display:none} } +#user-heatmap .total-contributions{text-align:left;font-weight:500;margin-top:0} .heatmap-color-0{background-color:#f4f4f4} .heatmap-color-1{background-color:#d7e5db} .heatmap-color-2{background-color:#adc7ab} @@ -211,6 +222,26 @@ footer .ui.left,footer .ui.right{line-height:40px} .ui.tabular.menu .item{color:rgba(0,0,0,.5)} .ui.tabular.menu .item:hover{color:rgba(0,0,0,.8)} .ui.tabular.menu .item.active{color:rgba(0,0,0,.9)} +.inline-grouped-list{display:inline-block;vertical-align:top} +.inline-grouped-list>.ui{display:block;margin-top:5px;margin-bottom:10px} +.inline-grouped-list>.ui:first-child{margin-top:1px} +.lines-num{vertical-align:top;text-align:right!important;color:#999;background:#f5f5f5;width:1%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +.lines-num span:before{content:attr(data-line-number);line-height:20px!important;padding:0 10px;cursor:pointer;display:block} +.lines-code,.lines-num{padding:0!important} +.lines-code .hljs,.lines-code ol,.lines-code pre,.lines-num .hljs,.lines-num ol,.lines-num pre{background-color:#fff;margin:0;padding:0!important} +.lines-code .hljs li,.lines-code ol li,.lines-code pre li,.lines-num .hljs li,.lines-num ol li,.lines-num pre li{display:block;width:100%} +.lines-code .hljs li:before,.lines-code ol li:before,.lines-code pre li:before,.lines-num .hljs li:before,.lines-num ol li:before,.lines-num pre li:before{content:' '} +.lines-commit{vertical-align:top;color:#999;padding:0!important;background:#f5f5f5;width:1%;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none} +.lines-commit .blame-info{width:350px;max-width:350px;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:0 0 0 10px} +.lines-commit .blame-info .blame-data{display:flex;font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial} +.lines-commit .blame-info .blame-data .blame-message{flex-grow:2;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:20px} +.lines-commit .blame-info .blame-data .blame-avatar,.lines-commit .blame-info .blame-data .blame-time{flex-shrink:0} +.lines-commit .ui.avatar.image{height:18px;width:18px} +.lines-code .bottom-line,.lines-commit .bottom-line,.lines-num .bottom-line{border-bottom:1px solid #eaecef} +.code-view{overflow:auto;overflow-x:auto;overflow-y:hidden} +.code-view *{font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;line-height:20px} +.code-view table{width:100%} +.code-view .active{background:#fff866} .markdown:not(code){overflow:hidden;font-size:16px;line-height:1.6!important;word-wrap:break-word} .markdown:not(code).ui.segment{padding:3em} .markdown:not(code).file-view{padding:2em 2em 2em!important} @@ -292,7 +323,7 @@ footer .ui.left,footer .ui.right{line-height:40px} .markdown:not(code) .ui.list .list,.markdown:not(code) ol.ui.list ol,.markdown:not(code) ul.ui.list ul{padding-left:2em} .repository.wiki.revisions .ui.container>.ui.stackable.grid{flex-direction:row-reverse} .repository.wiki.revisions .ui.container>.ui.stackable.grid>.header{margin-top:0} -.repository.wiki.revisions .ui.container>.ui.stackable.grid>.header .sub.header{padding-left:52px} +.repository.wiki.revisions .ui.container>.ui.stackable.grid>.header .sub.header{padding-left:52px;word-break:break-word} .file-revisions-btn{display:block;float:left;margin-bottom:2px!important;padding:11px!important;margin-right:10px!important} .file-revisions-btn i{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} .home .logo{max-width:220px} @@ -396,6 +427,8 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository .repo-header .ui.header{margin-top:0} .repository .repo-header .mega-octicon{width:30px;font-size:30px} .repository .repo-header .ui.huge.breadcrumb{font-weight:400;font-size:1.5rem} +.repository .repo-header .ui.huge.breadcrumb i.mega-octicon{position:relative;top:5px} +.repository .repo-header .ui.huge.breadcrumb i.octicon-lock{margin-left:5px} .repository .repo-header .fork-flag{margin-left:36px;margin-top:3px;display:block;font-size:12px;white-space:nowrap} .repository .repo-header .octicon.octicon-repo-forked{margin-top:-1px;font-size:15px} .repository .repo-header .button{margin-top:2px;margin-bottom:2px} @@ -445,6 +478,10 @@ footer .ui.left,footer .ui.right{line-height:40px} } .repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400} .repository.file.list #repo-files-table thead .ui.avatar{margin-bottom:5px} +.repository.file.list #repo-files-table thead .commit-summary a{text-decoration:underline;-webkit-text-decoration-style:dashed;text-decoration-style:dashed} +.repository.file.list #repo-files-table thead .commit-summary a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid} +.repository.file.list #repo-files-table thead .commit-summary a.default-link{text-decoration:none} +.repository.file.list #repo-files-table thead .commit-summary a.default-link:hover{text-decoration:underline;-webkit-text-decoration-style:solid;text-decoration-style:solid} .repository.file.list #repo-files-table tbody .octicon{margin-left:3px;margin-right:5px;color:#777} .repository.file.list #repo-files-table tbody .octicon.octicon-mail-reply{margin-right:10px} .repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule,.repository.file.list #repo-files-table tbody .octicon.octicon-file-symlink-directory{color:#1e70bf} @@ -468,24 +505,8 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository.file.list .non-diff-file-content .view-raw img{padding:5px 5px 0 5px} .repository.file.list .non-diff-file-content .plain-text{padding:1em 2em 1em 2em} .repository.file.list .non-diff-file-content .plain-text pre{word-break:break-word;white-space:pre-wrap} +.repository.file.list .non-diff-file-content .csv{overflow-x:auto;padding:0!important} .repository.file.list .non-diff-file-content pre{overflow:auto} -.repository.file.list .non-diff-file-content .code-view *{font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;line-height:20px} -.repository.file.list .non-diff-file-content .code-view table{width:100%} -.repository.file.list .non-diff-file-content .code-view .lines-num{vertical-align:top;text-align:right;color:#999;background:#f5f5f5;width:1%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -.repository.file.list .non-diff-file-content .code-view .lines-num span{line-height:20px;padding:0 10px;cursor:pointer;display:block} -.repository.file.list .non-diff-file-content .code-view .lines-code,.repository.file.list .non-diff-file-content .code-view .lines-num{padding:0} -.repository.file.list .non-diff-file-content .code-view .lines-code .hljs,.repository.file.list .non-diff-file-content .code-view .lines-code ol,.repository.file.list .non-diff-file-content .code-view .lines-code pre,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs,.repository.file.list .non-diff-file-content .code-view .lines-num ol,.repository.file.list .non-diff-file-content .code-view .lines-num pre{background-color:#fff;margin:0;padding:0!important} -.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li,.repository.file.list .non-diff-file-content .code-view .lines-code ol li,.repository.file.list .non-diff-file-content .code-view .lines-code pre li,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li,.repository.file.list .non-diff-file-content .code-view .lines-num ol li,.repository.file.list .non-diff-file-content .code-view .lines-num pre li{display:block;width:100%} -.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li.active,.repository.file.list .non-diff-file-content .code-view .lines-code ol li.active,.repository.file.list .non-diff-file-content .code-view .lines-code pre li.active,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li.active,.repository.file.list .non-diff-file-content .code-view .lines-num ol li.active,.repository.file.list .non-diff-file-content .code-view .lines-num pre li.active{background:#ffd} -.repository.file.list .non-diff-file-content .code-view .lines-code .hljs li:before,.repository.file.list .non-diff-file-content .code-view .lines-code ol li:before,.repository.file.list .non-diff-file-content .code-view .lines-code pre li:before,.repository.file.list .non-diff-file-content .code-view .lines-num .hljs li:before,.repository.file.list .non-diff-file-content .code-view .lines-num ol li:before,.repository.file.list .non-diff-file-content .code-view .lines-num pre li:before{content:' '} -.repository.file.list .non-diff-file-content .code-view .lines-commit{vertical-align:top;color:#999;padding:0;background:#f5f5f5;width:1%;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none} -.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info{width:350px;max-width:350px;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:0 0 0 10px} -.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data{display:flex;font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial} -.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-message{flex-grow:2;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:20px} -.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-avatar,.repository.file.list .non-diff-file-content .code-view .lines-commit .blame-info .blame-data .blame-time{flex-shrink:0} -.repository.file.list .non-diff-file-content .code-view .lines-commit .ui.avatar.image{height:18px;width:18px} -.repository.file.list .non-diff-file-content .code-view .lines-code .bottom-line,.repository.file.list .non-diff-file-content .code-view .lines-commit .bottom-line,.repository.file.list .non-diff-file-content .code-view .lines-num .bottom-line{border-bottom:1px solid #eaecef} -.repository.file.list .non-diff-file-content .code-view .active{background:#ffd} .repository.file.list .sidebar{padding-left:0} .repository.file.list .sidebar .octicon{width:16px} .repository.file.editor .treepath{width:100%} @@ -505,6 +526,7 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository.options #interval{width:100px!important;min-width:100px} .repository.options .danger .item{padding:20px 15px} .repository.options .danger .ui.divider{margin:0} +.repository .comment textarea{max-height:none!important} .repository.new.issue .comment.form .comment .avatar{width:3em} .repository.new.issue .comment.form .content{margin-left:4em} .repository.new.issue .comment.form .content:after,.repository.new.issue .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} @@ -628,11 +650,13 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository #commits-table thead .sha{width:140px} .repository #commits-table thead .shatd{text-align:center} .repository #commits-table td.sha .sha.label{margin:0} +.repository #commits-table td.message{text-overflow:unset} .repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n){background-color:rgba(0,0,0,.02)!important} -.repository #commits-table td.sha .sha.label.isSigned,.repository #repo-files-table .sha.label.isSigned{border:1px solid #bbb} -.repository #commits-table td.sha .sha.label.isSigned .detail.icon,.repository #repo-files-table .sha.label.isSigned .detail.icon{background:#fafafa;margin:-6px -10px -4px 0;padding:5px 3px 5px 6px;border-left:1px solid #bbb;border-top-left-radius:0;border-bottom-left-radius:0} +.repository #commits-table td.sha .sha.label,.repository #repo-files-table .sha.label{border:1px solid #bbb} +.repository #commits-table td.sha .sha.label .detail.icon,.repository #repo-files-table .sha.label .detail.icon{background:#fafafa;margin:-6px -10px -4px 0;padding:5px 3px 5px 6px;border-left:1px solid #bbb;border-top-left-radius:0;border-bottom-left-radius:0} .repository #commits-table td.sha .sha.label.isSigned.isVerified,.repository #repo-files-table .sha.label.isSigned.isVerified{border:1px solid #21ba45;background:rgba(33,186,69,.1)} -.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon{border-left:1px solid rgba(33,186,69,.5)} +.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon{border-left:1px solid #21ba45} +.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover,.repository #repo-files-table .sha.label.isSigned.isVerified:hover{background:rgba(33,186,69,.3)!important} .repository .diff-detail-box{padding:7px 0;background:#fff;line-height:30px} .repository .diff-detail-box>div:after{clear:both;content:"";display:block} .repository .diff-detail-box ol{clear:both;padding-left:0;margin-top:5px;margin-bottom:28px} @@ -654,10 +678,10 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository .diff-file-box .file-body.file-code .lines-num span.fold{display:block;text-align:center} .repository .diff-file-box .file-body.file-code .lines-num-old{border-right:1px solid #ddd} .repository .diff-file-box .code-diff{font-size:12px} -.repository .diff-file-box .code-diff td{padding:0 0 0 10px;border-top:0} -.repository .diff-file-box .code-diff .lines-num{border-color:#d4d4d5;border-right-width:1px;border-right-style:solid;padding:0 5px} +.repository .diff-file-box .code-diff td{padding:0 0 0 10px!important;border-top:0} +.repository .diff-file-box .code-diff .lines-num{border-color:#d4d4d5;border-right-width:1px;border-right-style:solid;padding:0 5px!important} .repository .diff-file-box .code-diff tbody tr td.halfwidth{width:49%} -.repository .diff-file-box .code-diff tbody tr td.tag-code,.repository .diff-file-box .code-diff tbody tr.tag-code td{background-color:#f0f0f0!important;border-color:#d3cfcf!important;padding-top:8px;padding-bottom:8px} +.repository .diff-file-box .code-diff tbody tr td.center{text-align:center} .repository .diff-file-box .code-diff tbody tr .removed-code{background-color:#f99} .repository .diff-file-box .code-diff tbody tr .added-code{background-color:#9f9} .repository .diff-file-box .code-diff tbody tr [data-line-num]::before{content:attr(data-line-num);text-align:right} @@ -672,7 +696,7 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository .diff-file-box .code-diff-split tbody tr td:nth-child(4){border-left-width:1px;border-left-style:solid} .repository .diff-file-box.file-content{clear:right} .repository .diff-file-box.file-content img{max-width:100%;padding:5px 5px 0 5px} -.repository .code-view{overflow:auto;overflow-x:auto;overflow-y:hidden} +.repository .diff-file-box.file-content img.emoji{padding:0} .repository .repo-search-result{padding-top:10px;padding-bottom:10px} .repository .repo-search-result .lines-num a{color:inherit} .repository.quickstart .guide .item{padding:1em} @@ -723,6 +747,8 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #ddd} .repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px} .repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px} +.repository.settings.collaboration #repo-collab-team-form #search-team-box .results{left:7px} +.repository.settings.collaboration #repo-collab-team-form .ui.button{margin-left:5px;margin-top:-3px} .repository.settings.branches .protected-branches .selection.dropdown{width:300px} .repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px} .repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0} @@ -752,13 +778,14 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository .segment.reactions .select-reaction{float:none} .repository .segment.reactions .select-reaction:not(.active) a{display:none} .repository .segment.reactions:hover .select-reaction a{display:block} -.user-cards .list{padding:0} +.user-cards .list{padding:0;display:flex;flex-wrap:wrap} .user-cards .list .item{list-style:none;width:32%;margin:10px 10px 10px 0;padding-bottom:14px;float:left} .user-cards .list .item .avatar{width:48px;height:48px;float:left;display:block;margin-right:10px} .user-cards .list .item .name{margin-top:0;margin-bottom:0;font-weight:400} .user-cards .list .item .meta{margin-top:5px} #search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em} #search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0} +#search-team-box .results .result .content{margin:6px 0} #issue-filters.hide{display:none} #issue-actions{margin-top:-1rem!important} #issue-actions.hide{display:none} @@ -772,8 +799,11 @@ footer .ui.left,footer .ui.right{line-height:40px} .issue.list>.item .desc .checklist{padding-left:5px} .issue.list>.item .desc .checklist .progress-bar{margin-left:2px;width:80px;height:6px;display:inline-block;background-color:#eee;overflow:hidden;border-radius:3px;vertical-align:2px!important} .issue.list>.item .desc .checklist .progress-bar .progress{background-color:#ccc;display:block;height:100%} -.issue.list>.item .desc a.milestone{padding-left:5px;color:#999!important} +.issue.list>.item .desc a.milestone{margin-left:5px;color:#999!important} .issue.list>.item .desc a.milestone:hover{color:#000!important} +.issue.list>.item .desc a.ref{margin-left:8px;color:#999!important} +.issue.list>.item .desc a.ref:hover{color:#000!important} +.issue.list>.item .desc a.ref span{margin-right:-4px} .issue.list>.item .desc .assignee{margin-top:-5px;margin-right:5px} .issue.list>.item .desc .overdue{color:red} .page.buttons{padding-top:15px} @@ -781,7 +811,7 @@ footer .ui.left,footer .ui.right{line-height:40px} .ui.form .dropzone .dz-error-message{top:140px} .settings .content{margin-top:2px} .settings .content .segment,.settings .content>.header{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)} -.settings .list>.item .green{color:#21ba45} +.settings .list>.item .green:not(.ui.button){color:#21ba45} .settings .list>.item:not(:first-child){border-top:1px solid #eaeaea;padding:1rem;margin:15px -1rem -1rem -1rem} .settings .list>.item>.mega-octicon{display:table-cell} .settings .list>.item>.mega-octicon+.content{display:table-cell;padding:0 0 0 .5em;vertical-align:top} @@ -822,8 +852,12 @@ footer .ui.left,footer .ui.right{line-height:40px} .stats-table .table-cell{display:table-cell} .stats-table .table-cell.tiny{height:.5em} tbody.commit-list{vertical-align:baseline} -.commit-list .message-wrapper{overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - 24px);display:inline-block;vertical-align:middle} -.commit-list .message-wrapper .commit-status-link{display:inline-block;vertical-align:middle} +.commit-list .message-wrapper{overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - 50px);display:inline-block;vertical-align:middle} +.commit-list .commit-summary a{text-decoration:underline;-webkit-text-decoration-style:dashed;text-decoration-style:dashed} +.commit-list .commit-summary a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid} +.commit-list .commit-summary a.default-link{text-decoration:none} +.commit-list .commit-summary a.default-link:hover{text-decoration:underline;-webkit-text-decoration-style:solid;text-decoration-style:solid} +.commit-list .commit-status-link{display:inline-block;vertical-align:middle} .commit-body{white-space:pre-wrap} .git-notes.top{text-align:left} .git-notes .commit-body{margin:0} @@ -843,6 +877,7 @@ tbody.commit-list{vertical-align:baseline} .repo-buttons .disabled-repo-button a.button{opacity:.5;cursor:not-allowed} .repo-buttons .disabled-repo-button a.button:hover{background:0 0!important;color:rgba(0,0,0,.6)!important;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset!important} .repo-buttons .ui.labeled.button>.label{border-left:0!important;margin:0!important} +.tag-code,.tag-code td{background-color:#f0f0f0!important;border-color:#d3cfcf!important;padding-top:8px;padding-bottom:8px} .CodeMirror{font:14px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} .CodeMirror.cm-s-default{border-radius:3px;padding:0!important} .CodeMirror .cm-comment{background:inherit!important} @@ -920,6 +955,7 @@ tbody.commit-list{vertical-align:baseline} .dashboard{padding-top:15px} .dashboard.feeds .context.user.menu,.dashboard.issues .context.user.menu{z-index:101;min-width:200px} .dashboard.feeds .context.user.menu .ui.header,.dashboard.issues .context.user.menu .ui.header{font-size:1rem;text-transform:none} +.dashboard.feeds .filter.menu,.dashboard.issues .filter.menu{width:initial} .dashboard.feeds .filter.menu .item,.dashboard.issues .filter.menu .item{text-align:left} .dashboard.feeds .filter.menu .item .text,.dashboard.issues .filter.menu .item .text{height:16px;vertical-align:middle} .dashboard.feeds .filter.menu .item .text.truncate,.dashboard.issues .filter.menu .item .text.truncate{width:85%} @@ -979,7 +1015,9 @@ tbody.commit-list{vertical-align:baseline} .ui.repository.list .item .time{font-size:12px;color:grey} .ui.repository.list .item .ui.tags{margin-bottom:1em} .ui.repository.list .item .ui.avatar.image{width:24px;height:24px} -.ui.repository.branches .time{font-size:12px;color:grey} +.ui.repository.branches .info{font-size:12px;color:grey;display:flex;white-space:pre} +.ui.repository.branches .info .commit-message{max-width:72em;overflow:hidden;text-overflow:ellipsis} +.ui.repository.branches .overflow-visible{overflow:visible} .ui.user.list .item{padding-bottom:25px} .ui.user.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px} .ui.user.list .item .ui.avatar.image{width:40px;height:40px} diff --git a/public/css/theme-arc-green.css b/public/css/theme-arc-green.css index a3a7204009..28a127a3dd 100644 --- a/public/css/theme-arc-green.css +++ b/public/css/theme-arc-green.css @@ -1,5 +1,4 @@ .hljs{display:block;overflow-x:auto;padding:.5em;color:#bababa} -.repository.file.list .non-diff-file-content .code-view .lines-code ol,.repository.file.list .non-diff-file-content .code-view .lines-num{background-color:#2b2b2b!important} .hljs-emphasis,.hljs-strong{color:#a8a8a2} .hljs-bullet,.hljs-link,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:#6896ba} .hljs-code,.hljs-selector-class{color:#a6e22e} @@ -43,7 +42,7 @@ a:hover{color:#a0cc75} .ui.attached.table{border:1px solid #304251;background:#304251} .feeds .list ul li:not(:last-child){border-bottom:1px solid #333640} .feeds .list ul li.private{background:#353945;border:1px solid #333640} -.ui.secondary.menu .dropdown.item:hover,.ui.secondary.menu .link.item:hover,.ui.secondary.menu a.item:hover{color:#fff} +.ui.secondary.menu .active.item:hover,.ui.secondary.menu .dropdown.item:hover,.ui.secondary.menu .link.item:hover,.ui.secondary.menu a.item:hover{color:#fff} .ui.menu .ui.dropdown .menu>.item{background:#2c303a!important;color:#9e9e9e!important} .ui.secondary.menu .dropdown.item>.menu,.ui.text.menu .dropdown.item>.menu{border:1px solid #434444} footer{background:#2e323e;border-top:1px solid #313131} @@ -90,8 +89,8 @@ footer{background:#2e323e;border-top:1px solid #313131} .ui.button:hover{background-color:#404552;color:#dbdbdb} .ui.table thead th{background:#404552;color:#dbdbdb} .repository.file.list #repo-files-table tr:hover{background-color:#393d4a} -.ui.table{color:#a5a5a5!important;border:1px solid #4c505c;background:#353945} -.ui.table tbody tr{border-bottom:1px solid #333640;background:#2a2e3a} +.ui.table{color:#a5a5a5!important;border-color:#4c505c;background:#353945} +.ui.table tbody tr{border-color:#333640;background:#2a2e3a} .ui .text.grey{color:#808084!important} .ui.attached.table.segment{background:#353945;color:#dbdbdb!important} .markdown:not(code) h2{border-bottom:1px solid #304251} @@ -137,13 +136,10 @@ footer{background:#2e323e;border-top:1px solid #313131} .repository .diff-file-box .code-diff-unified tbody tr.del-code td{background-color:#3c2626!important;border-color:#634343!important} .repository .diff-file-box .code-diff-unified tbody tr.add-code td{background-color:#283e2d!important;border-color:#314a37!important} .repository .diff-file-box .code-diff tbody tr .added-code{background-color:#3a523a} -.repository .diff-file-box .code-diff .lines-num{border-right:1px solid #2d2d2d} -.repository .diff-file-box .file-body.file-code .lines-num{color:#9e9e9e;background:#2e323e} -.repository .diff-file-box .file-body.file-code .lines-num-old{border-right:1px solid #2d2d2d} .hljs-section,.hljs-selector-id,.hljs-title{color:#986c88} .hljs-doctag,.hljs-string{color:#8ab398} .repository .diff-file-box .code-diff tbody tr .removed-code{background-color:#5f3737} -.repository .diff-file-box .code-diff tbody tr td.tag-code,.repository .diff-file-box .code-diff tbody tr.tag-code td{background-color:#292727!important} +.tag-code,.tag-code td{background:#242637!important;border-color:transparent!important} .ui.vertical.menu .active.item{background:#4b5162} .ui.vertical.menu .item{background:#353945} .ui.vertical.menu .header.item{background:#404552} @@ -205,17 +201,21 @@ input{background:#2e323e} .ui.list .list>.item .header,.ui.list>.item .header{color:#dedede} .ui.list .list>.item .description,.ui.list>.item .description{color:#9e9e9e} .ui.user.list .item .description a{color:#668cb1} -.repository.file.list #file-content .code-view .lines-num{background:#2e323e} .repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule{color:#7c9b5e} .ui.blue.button:focus,.ui.blue.buttons .button:focus{background-color:#a27558} .ui.basic.blue.button:hover,.ui.basic.blue.buttons .button:hover{box-shadow:0 0 0 1px #87ab63 inset!important;color:#87ab63!important} .ui.basic.blue.button:focus,.ui.basic.blue.buttons .button:focus{box-shadow:0 0 0 1px #87ab63 inset!important;color:#87ab63!important} -.repository.file.list #file-content .code-view .lines-code .hljs,.repository.file.list #file-content .code-view .lines-code ol,.repository.file.list #file-content .code-view .lines-code pre,.repository.file.list #file-content .code-view .lines-num .hljs,.repository.file.list #file-content .code-view .lines-num ol,.repository.file.list #file-content .code-view .lines-num pre{background-color:#2a2e3a} -a.ui.label:hover,a.ui.labels .label:hover{background-color:#505667;color:#dbdbdb} +.lines-commit{background:#2e323e!important} +.bottom-line{border-color:#4e525e!important} +.lines-num{background:#2e323e!important;color:#9e9e9e!important;border-color:#2d2d2d!important} +.lines-code .hljs,.lines-code ol,.lines-code pre,.lines-num .hljs,.lines-num ol,.lines-num pre{background-color:#2a2e3a!important} +.code-view .active{background:#554a00} +a.ui.label:hover,a.ui.labels .label:hover{background-color:#505667!important;color:#dbdbdb!important} +.repository #commits-table td.sha .sha.label,.repository #repo-files-table .sha.label{border-color:#888} +.repository #commits-table td.sha .sha.label.isSigned .detail.icon,.repository #repo-files-table .sha.label.isSigned .detail.icon{background:0 0;border-left-color:#888} .repository .label.list .item{border-bottom:1px dashed #4c505c} .ui.basic.blue.button,.ui.basic.blue.buttons .button{box-shadow:0 0 0 1px #87ab63 inset!important;color:#87ab63!important} -.repository.file.list #file-content .code-view .hljs,.repository.file.list #file-content .code-view .lines-code ol,.repository.file.list #file-content .code-view .lines-code pre,.repository.file.list #file-content .code-view .lines-num .hljs,.repository.file.list #file-content .code-view .lines-num ol,.repository.file.list #file-content .code-view .lines-num pre{background-color:#2a2e3a} -.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(4),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(5),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(6){background-color:#2a2e3a} +.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(4),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(5),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(6){background-color:#2a2e3a} .repository .diff-file-box .code-diff-split tbody tr td.add-code,.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(4),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(5),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(6){background-color:#283e2d!important;border-color:#314a37!important} .repository .diff-file-box .code-diff-split tbody tr td.del-code,.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(3){background-color:#3c2626!important;border-color:#634343!important} .ui.blue.button:active,.ui.blue.buttons .button:active{background-color:#a27558} diff --git a/public/img/auth/gitea.png b/public/img/auth/gitea.png new file mode 100644 index 0000000000..30d3313594 Binary files /dev/null and b/public/img/auth/gitea.png differ diff --git a/public/img/auth/google.png b/public/img/auth/google.png new file mode 100644 index 0000000000..389c1cd54c Binary files /dev/null and b/public/img/auth/google.png differ diff --git a/public/img/auth/google_plus.png b/public/img/auth/google_plus.png deleted file mode 100644 index 21251288d2..0000000000 Binary files a/public/img/auth/google_plus.png and /dev/null differ diff --git a/public/js/draw.js b/public/js/draw.js index 518d1ec545..643ff233c0 100644 --- a/public/js/draw.js +++ b/public/js/draw.js @@ -1,7 +1,7 @@ /* globals gitGraph */ $(document).ready(function () { - var graphList = []; + const graphList = []; if (!document.getElementById('graph-canvas')) { return; diff --git a/public/js/index.js b/public/js/index.js index b4ce8c78b6..8a85ad9157 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -7,35 +7,43 @@ function htmlEncode(text) { return jQuery('
    ').text(text).html() } -var csrf; -var suburl; +let csrf; +let suburl; +let previewFileModes; +let simpleMDEditor; +let codeMirrorEditor; + +// Disable Dropzone auto-discover because it's manually initialized +if (typeof(Dropzone) !== "undefined") { + Dropzone.autoDiscover = false; +} // Polyfill for IE9+ support (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) if (!Array.from) { Array.from = (function () { - var toStr = Object.prototype.toString; - var isCallable = function (fn) { + const toStr = Object.prototype.toString; + const isCallable = function (fn) { return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; }; - var toInteger = function (value) { - var number = Number(value); + const toInteger = function (value) { + const number = Number(value); if (isNaN(number)) { return 0; } if (number === 0 || !isFinite(number)) { return number; } return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); }; - var maxSafeInteger = Math.pow(2, 53) - 1; - var toLength = function (value) { - var len = toInteger(value); + const maxSafeInteger = Math.pow(2, 53) - 1; + const toLength = function (value) { + const len = toInteger(value); return Math.min(Math.max(len, 0), maxSafeInteger); }; // The length property of the from method is 1. return function from(arrayLike/*, mapFn, thisArg */) { // 1. Let C be the this value. - var C = this; + const C = this; // 2. Let items be ToObject(arrayLike). - var items = Object(arrayLike); + const items = Object(arrayLike); // 3. ReturnIfAbrupt(items). if (arrayLike == null) { @@ -43,8 +51,8 @@ if (!Array.from) { } // 4. If mapfn is undefined, then let mapping be false. - var mapFn = arguments.length > 1 ? arguments[1] : void undefined; - var T; + const mapFn = arguments.length > 1 ? arguments[1] : void undefined; + let T; if (typeof mapFn !== 'undefined') { // 5. else // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. @@ -60,17 +68,17 @@ if (!Array.from) { // 10. Let lenValue be Get(items, "length"). // 11. Let len be ToLength(lenValue). - var len = toLength(items.length); + const len = toLength(items.length); // 13. If IsConstructor(C) is true, then // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len. // 14. a. Else, Let A be ArrayCreate(len). - var A = isCallable(C) ? Object(new C(len)) : new Array(len); + const A = isCallable(C) ? Object(new C(len)) : new Array(len); // 16. Let k be 0. - var k = 0; + let k = 0; // 17. Repeat, while k < len… (also steps a - h) - var kValue; + let kValue; while (k < len) { kValue = items[k]; if (mapFn) { @@ -98,13 +106,13 @@ if (typeof Object.assign != 'function') { throw new TypeError('Cannot convert undefined or null to object'); } - var to = Object(target); + const to = Object(target); - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; + for (let index = 1; index < arguments.length; index++) { + const nextSource = arguments[index]; if (nextSource != null) { // Skip over if undefined or null - for (var nextKey in nextSource) { + for (const nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; @@ -120,10 +128,10 @@ if (typeof Object.assign != 'function') { } function initCommentPreviewTab($form) { - var $tabMenu = $form.find('.tabular.menu'); + const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); $tabMenu.find('.item[data-tab="' + $tabMenu.data('preview') + '"]').click(function () { - var $this = $(this); + const $this = $(this); $.post($this.data('url'), { "_csrf": csrf, "mode": "gfm", @@ -131,7 +139,7 @@ function initCommentPreviewTab($form) { "text": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() }, function (data) { - var $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); + const $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); $previewPanel.html(data); emojify.run($previewPanel[0]); $('pre code', $previewPanel[0]).each(function () { @@ -144,16 +152,14 @@ function initCommentPreviewTab($form) { buttonsClickOnEnter(); } -var previewFileModes; - function initEditPreviewTab($form) { - var $tabMenu = $form.find('.tabular.menu'); + const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); - var $previewTab = $tabMenu.find('.item[data-tab="' + $tabMenu.data('preview') + '"]'); + const $previewTab = $tabMenu.find('.item[data-tab="' + $tabMenu.data('preview') + '"]'); if ($previewTab.length) { previewFileModes = $previewTab.data('preview-file-modes').split(','); $previewTab.click(function () { - var $this = $(this); + const $this = $(this); $.post($this.data('url'), { "_csrf": csrf, "mode": "gfm", @@ -161,7 +167,7 @@ function initEditPreviewTab($form) { "text": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() }, function (data) { - var $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); + const $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); $previewPanel.html(data); emojify.run($previewPanel[0]); $('pre code', $previewPanel[0]).each(function () { @@ -174,17 +180,17 @@ function initEditPreviewTab($form) { } function initEditDiffTab($form) { - var $tabMenu = $form.find('.tabular.menu'); + const $tabMenu = $form.find('.tabular.menu'); $tabMenu.find('.item').tab(); $tabMenu.find('.item[data-tab="' + $tabMenu.data('diff') + '"]').click(function () { - var $this = $(this); + const $this = $(this); $.post($this.data('url'), { "_csrf": csrf, "context": $this.data('context'), "content": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() }, function (data) { - var $diffPreviewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('diff') + '"]'); + const $diffPreviewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('diff') + '"]'); $diffPreviewPanel.html(data); emojify.run($diffPreviewPanel[0]); } @@ -203,10 +209,10 @@ function initEditForm() { } function initBranchSelector() { - var $selectBranch = $('.ui.select-branch') - var $branchMenu = $selectBranch.find('.reference-list-menu'); + const $selectBranch = $('.ui.select-branch') + const $branchMenu = $selectBranch.find('.reference-list-menu'); $branchMenu.find('.item:not(.no-select)').click(function () { - var selectedValue = $(this).data('id'); + const selectedValue = $(this).data('id'); $($(this).data('id-selector')).val(selectedValue); $selectBranch.find('.ui .branch-name').text(selectedValue); }); @@ -236,7 +242,7 @@ function updateIssuesMeta(url, action, issueIds, elementId) { } function initReactionSelector(parent) { - var reactions = ''; + let reactions = ''; if (!parent) { parent = $(document); reactions = '.reactions > '; @@ -245,15 +251,15 @@ function initReactionSelector(parent) { parent.find(reactions + 'a.label').popup({'position': 'bottom left', 'metadata': {'content': 'title', 'title': 'none'}}); parent.find('.select-reaction > .menu > .item, ' + reactions + 'a.label').on('click', function(e){ - var vm = this; + const vm = this; e.preventDefault(); if ($(this).hasClass('disabled')) return; - var actionURL = $(this).hasClass('item') ? + const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url'); - var url = actionURL + '/' + ($(this).hasClass('blue') ? 'unreact' : 'react'); + const url = actionURL + '/' + ($(this).hasClass('blue') ? 'unreact' : 'react'); $.ajax({ type: 'POST', url: url, @@ -263,22 +269,22 @@ function initReactionSelector(parent) { } }).done(function(resp) { if (resp && (resp.html || resp.empty)) { - var content = $(vm).closest('.content'); - var react = content.find('.segment.reactions'); + const content = $(vm).closest('.content'); + let react = content.find('.segment.reactions'); if (react.length > 0) { react.remove(); } if (!resp.empty) { react = $('
    '); - var attachments = content.find('.segment.bottom:first'); + const attachments = content.find('.segment.bottom:first'); if (attachments.length > 0) { react.insertBefore(attachments); } else { react.appendTo(content); } react.html(resp.html); - var hasEmoji = react.find('.has-emoji'); - for (var i = 0; i < hasEmoji.length; i++) { + const hasEmoji = react.find('.has-emoji'); + for (let i = 0; i < hasEmoji.length; i++) { emojify.run(hasEmoji.get(i)); } react.find('.dropdown').dropdown(); @@ -291,8 +297,8 @@ function initReactionSelector(parent) { function insertAtCursor(field, value) { if (field.selectionStart || field.selectionStart === 0) { - var startPos = field.selectionStart; - var endPos = field.selectionEnd; + const startPos = field.selectionStart; + const endPos = field.selectionEnd; field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length); @@ -305,8 +311,8 @@ function insertAtCursor(field, value) { function replaceAndKeepCursor(field, oldval, newval) { if (field.selectionStart || field.selectionStart === 0) { - var startPos = field.selectionStart; - var endPos = field.selectionEnd; + const startPos = field.selectionStart; + const endPos = field.selectionEnd; field.value = field.value.replace(oldval, newval); field.selectionStart = startPos + newval.length - oldval.length; field.selectionEnd = endPos + newval.length - oldval.length; @@ -320,14 +326,14 @@ function retrieveImageFromClipboardAsBlob(pasteEvent, callback){ return; } - var items = pasteEvent.clipboardData.items; + const items = pasteEvent.clipboardData.items; if (typeof(items) === "undefined") { return; } - for (var i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { if (items[i].type.indexOf("image") === -1) continue; - var blob = items[i].getAsFile(); + const blob = items[i].getAsFile(); if (typeof(callback) === "function") { pasteEvent.preventDefault(); @@ -338,7 +344,7 @@ function retrieveImageFromClipboardAsBlob(pasteEvent, callback){ } function uploadFile(file, callback) { - var xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.status == 200) { @@ -348,7 +354,7 @@ function uploadFile(file, callback) { xhr.open("post", suburl + "/attachments", true); xhr.setRequestHeader("X-Csrf-Token", csrf); - var formData = new FormData(); + const formData = new FormData(); formData.append('file', file, file.name); xhr.send(formData); } @@ -359,15 +365,15 @@ function reload() { function initImagePaste(target) { target.each(function() { - var field = this; + const field = this; field.addEventListener('paste', function(event){ retrieveImageFromClipboardAsBlob(event, function(img) { - var name = img.name.substr(0, img.name.lastIndexOf('.')); + const name = img.name.substr(0, img.name.lastIndexOf('.')); insertAtCursor(field, '![' + name + ']()'); uploadFile(img, function(res) { - var data = JSON.parse(res); + const data = JSON.parse(res); replaceAndKeepCursor(field, '![' + name + ']()', '![' + name + '](' + suburl + '/attachments/' + data.uuid + ')'); - var input = $('').val(data.uuid); + const input = $('').val(data.uuid); $('.files').append(input); }); }); @@ -386,19 +392,19 @@ function initCommentForm() { // Listsubmit function initListSubmits(selector, outerSelector) { - var $list = $('.ui.' + outerSelector + '.list'); - var $noSelect = $list.find('.no-select'); - var $listMenu = $('.' + selector + ' .menu'); - var hasLabelUpdateAction = $listMenu.data('action') == 'update'; - var labels = {}; + const $list = $('.ui.' + outerSelector + '.list'); + const $noSelect = $list.find('.no-select'); + const $listMenu = $('.' + selector + ' .menu'); + let hasLabelUpdateAction = $listMenu.data('action') == 'update'; + const labels = {}; $('.' + selector).dropdown('setting', 'onHide', function(){ hasLabelUpdateAction = $listMenu.data('action') == 'update'; // Update the var if (hasLabelUpdateAction) { - var promises = []; + const promises = []; Object.keys(labels).forEach(function(elementId) { - var label = labels[elementId]; - var promise = updateIssuesMeta( + const label = labels[elementId]; + const promise = updateIssuesMeta( label["update-url"], label["action"], label["issue-id"], @@ -465,7 +471,7 @@ function initCommentForm() { } } - var listIds = []; + const listIds = []; $(this).parent().find('.item').each(function () { if ($(this).hasClass('checked')) { listIds.push($(this).data('id')); @@ -512,9 +518,9 @@ function initCommentForm() { initListSubmits('select-assignees-modify', 'assignees'); function selectItem(select_id, input_id) { - var $menu = $(select_id + ' .menu'); - var $list = $('.ui' + select_id + '.list'); - var hasUpdateAction = $menu.data('action') == 'update'; + const $menu = $(select_id + ' .menu'); + const $list = $('.ui' + select_id + '.list'); + const hasUpdateAction = $menu.data('action') == 'update'; $menu.find('.item:not(.no-select)').click(function () { $(this).parent().find('.item').each(function () { @@ -581,10 +587,10 @@ function initInstall() { // Database type change detection. $("#db_type").change(function () { - var sqliteDefault = 'data/gitea.db'; - var tidbDefault = 'data/gitea_tidb'; + const sqliteDefault = 'data/gitea.db'; + const tidbDefault = 'data/gitea_tidb'; - var dbType = $(this).val(); + const dbType = $(this).val(); if (dbType === "SQLite3") { $('#sql_settings').hide(); $('#pgsql_settings').hide(); @@ -597,7 +603,7 @@ function initInstall() { return; } - var dbDefaults = { + const dbDefaults = { "MySQL": "127.0.0.1:3306", "PostgreSQL": "127.0.0.1:5432", "MSSQL": "127.0.0.1:1433" @@ -666,7 +672,7 @@ function initRepository() { } function initFilterSearchDropdown(selector) { - var $dropdown = $(selector); + const $dropdown = $(selector); $dropdown.dropdown({ fullTextSearch: true, selectOnKeydown: false, @@ -693,7 +699,7 @@ function initRepository() { // Options if ($('.repository.settings.options').length > 0) { $('#repo_name').keyup(function () { - var $prompt = $('#repo-name-change-prompt'); + const $prompt = $('#repo-name-change-prompt'); if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) { $prompt.show(); } else { @@ -725,7 +731,7 @@ function initRepository() { // Labels if ($('.repository.labels').length > 0) { // Create label - var $newLabelPanel = $('.new-label.segment'); + const $newLabelPanel = $('.new-label.segment'); $('.new-label.button').click(function () { $newLabelPanel.show(); }); @@ -737,7 +743,7 @@ function initRepository() { $(this).minicolors(); }); $('.precolors .color').click(function () { - var color_hex = $(this).data('color-hex'); + const color_hex = $(this).data('color-hex'); $('.color-picker').val(color_hex); $('.minicolors-swatch-color').css("background-color", color_hex); }); @@ -758,7 +764,7 @@ function initRepository() { // Milestones if ($('.repository.new.milestone').length > 0) { - var $datepicker = $('.milestone.datepicker'); + const $datepicker = $('.milestone.datepicker'); $datepicker.datetimepicker({ lang: $datepicker.data('lang'), inline: true, @@ -778,9 +784,9 @@ function initRepository() { // Issues if ($('.repository.view.issue').length > 0) { // Edit issue title - var $issueTitle = $('#issue-title'); - var $editInput = $('#edit-title-input input'); - var editTitleToggle = function () { + const $issueTitle = $('#issue-title'); + const $editInput = $('#edit-title-input input'); + const editTitleToggle = function () { $issueTitle.toggle(); $('.not-in-edit').toggle(); $('#edit-title-input').toggle(); @@ -811,11 +817,11 @@ function initRepository() { // Edit issue or comment content $('.edit-content').click(function () { - var $segment = $(this).parent().parent().parent().next(); - var $editContentZone = $segment.find('.edit-content-zone'); - var $renderContent = $segment.find('.render-content'); - var $rawContent = $segment.find('.raw-content'); - var $textarea; + const $segment = $(this).parent().parent().parent().next(); + const $editContentZone = $segment.find('.edit-content-zone'); + const $renderContent = $segment.find('.render-content'); + const $rawContent = $segment.find('.raw-content'); + let $textarea; // Setup new form if ($editContentZone.html().length == 0) { @@ -825,8 +831,8 @@ function initRepository() { emojiTribute.attach($textarea.get()); // Give new write/preview data-tab name to distinguish from others - var $editContentForm = $editContentZone.find('.ui.comment.form'); - var $tabMenu = $editContentForm.find('.tabular.menu'); + const $editContentForm = $editContentZone.find('.ui.comment.form'); + const $tabMenu = $editContentForm.find('.tabular.menu'); $tabMenu.attr('data-write', $editContentZone.data('write')); $tabMenu.attr('data-preview', $editContentZone.data('preview')); $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write')); @@ -877,7 +883,7 @@ function initRepository() { // Delete comment $('.delete-comment').click(function () { - var $this = $(this); + const $this = $(this); if (confirm($this.data('locale'))) { $.post($this.data('url'), { "_csrf": csrf @@ -889,7 +895,7 @@ function initRepository() { }); // Change status - var $statusButton = $('#status-button'); + const $statusButton = $('#status-button'); $('#comment-form .edit_area').keyup(function () { if ($(this).val().length == 0) { $statusButton.text($statusButton.data('status')) @@ -903,7 +909,7 @@ function initRepository() { }); // Pull Request merge button - var $mergeButton = $('.merge-button > button'); + const $mergeButton = $('.merge-button > button'); $mergeButton.on('click', function(e) { e.preventDefault(); $('.' + $(this).data('do') + '-fields').show(); @@ -929,10 +935,10 @@ function initRepository() { // Diff if ($('.repository.diff').length > 0) { $('.diff-counter').each(function () { - var $item = $(this); - var addLine = $item.find('span[data-line].add').data("line"); - var delLine = $item.find('span[data-line].del').data("line"); - var addPercent = parseFloat(addLine) / (parseFloat(addLine) + parseFloat(delLine)) * 100; + const $item = $(this); + const addLine = $item.find('span[data-line].add').data("line"); + const delLine = $item.find('span[data-line].del').data("line"); + const addPercent = parseFloat(addLine) / (parseFloat(addLine) + parseFloat(delLine)) * 100; $item.find(".bar .add").css("width", addPercent + "%"); }); } @@ -957,7 +963,7 @@ function initRepository() { }); // Pull request - var $repoComparePull = $('.repository.compare.pull'); + const $repoComparePull = $('.repository.compare.pull'); if ($repoComparePull.length > 0) { initFilterSearchDropdown('.choose.branch .dropdown'); // show pull request form @@ -981,18 +987,18 @@ function initRepository() { } } -var toggleMigrations = function(){ - var authUserName = $('#auth_username').val(); - var cloneAddr = $('#clone_addr').val(); - if (!$('#mirror').is(":checked") && (authUserName!=undefined && authUserName.length > 0) - && (cloneAddr!=undefined && (cloneAddr.startsWith("https://github.com") || cloneAddr.startsWith("http://github.com")))) { - $('#migrate_items').show(); - } else { - $('#migrate_items').hide(); - } -} - function initMigration() { + const toggleMigrations = function() { + const authUserName = $('#auth_username').val(); + const cloneAddr = $('#clone_addr').val(); + if (!$('#mirror').is(":checked") && (authUserName!=undefined && authUserName.length > 0) + && (cloneAddr!=undefined && (cloneAddr.startsWith("https://github.com") || cloneAddr.startsWith("http://github.com")))) { + $('#migrate_items').show(); + } else { + $('#migrate_items').hide(); + } + } + toggleMigrations(); $('#clone_addr').on('input', toggleMigrations) @@ -1003,7 +1009,7 @@ function initMigration() { function initPullRequestReview() { $('.show-outdated').on('click', function (e) { e.preventDefault(); - var id = $(this).data('comment'); + const id = $(this).data('comment'); $(this).addClass("hide"); $("#code-comments-" + id).removeClass('hide'); $("#code-preview-" + id).removeClass('hide'); @@ -1012,7 +1018,7 @@ function initPullRequestReview() { $('.hide-outdated').on('click', function (e) { e.preventDefault(); - var id = $(this).data('comment'); + const id = $(this).data('comment'); $(this).addClass("hide"); $("#code-comments-" + id).addClass('hide'); $("#code-preview-" + id).addClass('hide'); @@ -1022,7 +1028,7 @@ function initPullRequestReview() { $('button.comment-form-reply').on('click', function (e) { e.preventDefault(); $(this).hide(); - var form = $(this).parent().find('.comment-form') + const form = $(this).parent().find('.comment-form') form.removeClass('hide'); assingMenuAttributes(form.find('.menu')); }); @@ -1043,7 +1049,7 @@ function initPullRequestReview() { $('.code-view .lines-code,.code-view .lines-num') .on('mouseenter', function() { - var parent = $(this).closest('td'); + const parent = $(this).closest('td'); $(this).closest('tr').addClass( parent.hasClass('lines-num-old') || parent.hasClass('lines-code-old') ? 'focus-lines-old' : 'focus-lines-new' @@ -1058,13 +1064,13 @@ function initPullRequestReview() { return; } e.preventDefault(); - var isSplit = $(this).closest('.code-diff').hasClass('code-diff-split'); - var side = $(this).data('side'); - var idx = $(this).data('idx'); - var path = $(this).data('path'); - var form = $('#pull_review_add_comment').html(); - var tr = $(this).closest('tr'); - var ntr = tr.next(); + const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split'); + const side = $(this).data('side'); + const idx = $(this).data('idx'); + const path = $(this).data('path'); + const form = $('#pull_review_add_comment').html(); + const tr = $(this).closest('tr'); + let ntr = tr.next(); if (!ntr.hasClass('add-comment')) { ntr = $('' + (isSplit ? '' @@ -1072,8 +1078,8 @@ function initPullRequestReview() { + ''); tr.after(ntr); } - var td = ntr.find('.add-comment-' + side); - var commentCloud = td.find('.comment-code-cloud'); + const td = ntr.find('.add-comment-' + side); + let commentCloud = td.find('.comment-code-cloud'); if (commentCloud.length === 0) { td.html(form); commentCloud = td.find('.comment-code-cloud'); @@ -1088,11 +1094,11 @@ function initPullRequestReview() { } function assingMenuAttributes(menu) { - var id = Math.floor(Math.random() * Math.floor(1000000)); + const id = Math.floor(Math.random() * Math.floor(1000000)); menu.attr('data-write', menu.attr('data-write') + id); menu.attr('data-preview', menu.attr('data-preview') + id); menu.find('.item').each(function() { - var tab = $(this).attr('data-tab') + id; + const tab = $(this).attr('data-tab') + id; $(this).attr('data-tab', tab); }); menu.parent().find("*[data-tab='write']").attr('data-tab', 'write' + id); @@ -1104,7 +1110,7 @@ function assingMenuAttributes(menu) { function initRepositoryCollaboration() { // Change collaborator access mode $('.access-mode.menu .item').click(function () { - var $menu = $(this).parent(); + const $menu = $(this).parent(); $.post($menu.data('url'), { "_csrf": csrf, "uid": $menu.data('uid'), @@ -1116,7 +1122,7 @@ function initRepositoryCollaboration() { function initTeamSettings() { // Change team access mode $('.organization.new.team input[name=permission]').change(function () { - var val = $('input[name=permission]:checked', '.organization.new.team').val() + const val = $('input[name=permission]:checked', '.organization.new.team').val() if (val === 'admin') { $('.organization.new.team .team-units').hide(); } else { @@ -1126,9 +1132,9 @@ function initTeamSettings() { } function initWikiForm() { - var $editArea = $('.repository.wiki textarea#edit_area'); + const $editArea = $('.repository.wiki textarea#edit_area'); if ($editArea.length > 0) { - var simplemde = new SimpleMDE({ + const simplemde = new SimpleMDE({ autoDownloadFontAwesome: false, element: $editArea[0], forceSync: true, @@ -1161,11 +1167,11 @@ function initWikiForm() { { name: "code-inline", action: function(e){ - let cm = e.codemirror; - let selection = cm.getSelection(); + const cm = e.codemirror; + const selection = cm.getSelection(); cm.replaceSelection("`" + selection + "`"); if (!selection) { - let cursorPos = cm.getCursor(); + const cursorPos = cm.getCursor(); cm.setCursor(cursorPos.line, cursorPos.ch - 1); } cm.focus(); @@ -1175,7 +1181,7 @@ function initWikiForm() { },"code", "quote", "|", { name: "checkbox-empty", action: function(e){ - let cm = e.codemirror; + const cm = e.codemirror; cm.replaceSelection("\n- [ ] " + cm.getSelection()); cm.focus(); }, @@ -1185,7 +1191,7 @@ function initWikiForm() { { name: "checkbox-checked", action: function(e){ - let cm = e.codemirror; + const cm = e.codemirror; cm.replaceSelection("\n- [x] " + cm.getSelection()); cm.focus(); }, @@ -1200,25 +1206,22 @@ function initWikiForm() { } } -var simpleMDEditor; -var codeMirrorEditor; - // For IE String.prototype.endsWith = function (pattern) { - var d = this.length - pattern.length; + const d = this.length - pattern.length; return d >= 0 && this.lastIndexOf(pattern) === d; }; // Adding function to get the cursor position in a text field to jQuery object. $.fn.getCursorPosition = function () { - var el = $(this).get(0); - var pos = 0; + const el = $(this).get(0); + let pos = 0; if ('selectionStart' in el) { pos = el.selectionStart; } else if ('selection' in document) { el.focus(); - var Sel = document.selection.createRange(); - var SelLength = document.selection.createRange().text.length; + const Sel = document.selection.createRange(); + const SelLength = document.selection.createRange().text.length; Sel.moveStart('character', -el.value.length); pos = Sel.text.length - SelLength; } @@ -1303,14 +1306,15 @@ function initEditor() { $('.quick-pull-branch-name').hide(); $('.quick-pull-branch-name input').prop('required',false); } + $('#commit-button').text($(this).attr('button_text')); }); - var $editFilename = $("#file-name"); + const $editFilename = $("#file-name"); $editFilename.keyup(function (e) { - var $section = $('.breadcrumb span.section'); - var $divider = $('.breadcrumb div.divider'); - var value; - var parts; + const $section = $('.breadcrumb span.section'); + const $divider = $('.breadcrumb div.divider'); + let value; + let parts; if (e.keyCode == 8) { if ($(this).getCursorPosition() == 0) { @@ -1325,7 +1329,7 @@ function initEditor() { } if (e.keyCode == 191) { parts = $(this).val().split('/'); - for (var i = 0; i < parts.length; ++i) { + for (let i = 0; i < parts.length; ++i) { value = parts[i]; if (i < parts.length - 1) { if (value.length) { @@ -1341,7 +1345,7 @@ function initEditor() { } parts = []; $('.breadcrumb span.section').each(function () { - var element = $(this); + const element = $(this); if (element.find('a').length) { parts.push(element.find('a').text()); } else { @@ -1353,24 +1357,26 @@ function initEditor() { $('#tree_path').val(parts.join('/')); }).trigger('keyup'); - var $editArea = $('.repository.editor textarea#edit_area'); + const $editArea = $('.repository.editor textarea#edit_area'); if (!$editArea.length) return; - var markdownFileExts = $editArea.data("markdown-file-exts").split(","); - var lineWrapExtensions = $editArea.data("line-wrap-extensions").split(","); + const markdownFileExts = $editArea.data("markdown-file-exts").split(","); + const lineWrapExtensions = $editArea.data("line-wrap-extensions").split(","); $editFilename.on("keyup", function () { - var val = $editFilename.val(), m, mode, spec, extension, extWithDot, previewLink, dataUrl, apiCall; + const val = $editFilename.val(); + let mode, spec, extension, extWithDot, dataUrl, apiCall; + extension = extWithDot = ""; - m = /.+\.([^.]+)$/.exec(val); + const m = /.+\.([^.]+)$/.exec(val); if (m) { extension = m[1]; extWithDot = "." + extension; } - var info = CodeMirror.findModeByExtension(extension); - previewLink = $('a[data-tab=preview]'); + const info = CodeMirror.findModeByExtension(extension); + const previewLink = $('a[data-tab=preview]'); if (info) { mode = info.mode; spec = info.mime; @@ -1414,7 +1420,7 @@ function initEditor() { } // get the filename without any folder - var value = $editFilename.val(); + let value = $editFilename.val(); if (value.length === 0) { return; } @@ -1432,7 +1438,7 @@ function initEditor() { // - https://codemirror.net/doc/manual.html#keymaps codeMirrorEditor.setOption('extraKeys', { Tab: function(cm) { - var spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" "); + const spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" "); cm.replaceSelection(spaces); } }); @@ -1451,7 +1457,7 @@ function initOrganization() { // Options if ($('.organization.settings.options').length > 0) { $('#org_name').keyup(function () { - var $prompt = $('#org-name-change-prompt'); + const $prompt = $('#org-name-change-prompt'); if ($(this).val().toString().toLowerCase() != $(this).data('org-name').toString().toLowerCase()) { $prompt.show(); } else { @@ -1465,7 +1471,7 @@ function initUserSettings() { // Options if ($('.user.settings.profile').length > 0) { $('#username').keyup(function () { - var $prompt = $('#name-change-prompt'); + const $prompt = $('#name-change-prompt'); if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) { $prompt.show(); } else { @@ -1491,8 +1497,8 @@ function initWebhook() { } }); - var updateContentType = function () { - var visible = $('#http_method').val() === 'POST'; + const updateContentType = function () { + const visible = $('#http_method').val() === 'POST'; $('#content_type').parent().parent()[visible ? 'show' : 'hide'](); }; updateContentType(); @@ -1502,7 +1508,7 @@ function initWebhook() { // Test delivery $('#test-delivery').click(function () { - var $this = $(this); + const $this = $(this); $this.addClass('loading disabled'); $.post($this.data('link'), { "_csrf": csrf @@ -1566,10 +1572,11 @@ function initAdmin() { $('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url').hide(); $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required'); - var provider = $('#oauth2_provider').val(); + const provider = $('#oauth2_provider').val(); switch (provider) { case 'github': case 'gitlab': + case 'gitea': $('.oauth2_use_custom_url').show(); break; case 'openidConnect': @@ -1581,7 +1588,7 @@ function initAdmin() { } function onOAuth2UseCustomURLChange() { - var provider = $('#oauth2_provider').val(); + const provider = $('#oauth2_provider').val(); $('.oauth2_use_custom_url_field').hide(); $('.oauth2_use_custom_url_field input[required]').removeAttr('required'); @@ -1603,6 +1610,7 @@ function initAdmin() { $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input').attr('required', 'required'); $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url').show(); break; + case 'gitea': case 'gitlab': $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input').attr('required', 'required'); $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url').show(); @@ -1620,7 +1628,7 @@ function initAdmin() { $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required]').removeAttr('required'); $('.binddnrequired').removeClass("required"); - var authType = $(this).val(); + const authType = $(this).val(); switch (authType) { case '2': // LDAP $('.ldap').show(); @@ -1661,7 +1669,7 @@ function initAdmin() { } // Edit authentication if ($('.admin.edit.authentication').length > 0) { - var authType = $('#auth_type').val(); + const authType = $('#auth_type').val(); if (authType == '2' || authType == '5') { $('#security_protocol').change(onSecurityProtocolChange); if (authType == '2') { @@ -1676,7 +1684,7 @@ function initAdmin() { // Notice if ($('.admin.notice')) { - var $detailModal = $('#detail-modal'); + const $detailModal = $('#detail-modal'); // Attach view detail modals $('.view-detail').click(function () { @@ -1686,7 +1694,7 @@ function initAdmin() { }); // Select actions - var $checkboxes = $('.select.table .ui.checkbox'); + const $checkboxes = $('.select.table .ui.checkbox'); $('.select.action').click(function () { switch ($(this).data('action')) { case 'select-all': @@ -1701,9 +1709,9 @@ function initAdmin() { } }); $('#delete-selection').click(function () { - var $this = $(this); + const $this = $(this); $this.addClass("loading disabled"); - var ids = []; + const ids = []; $checkboxes.each(function () { if ($(this).checkbox('is checked')) { ids.push($(this).data('id')); @@ -1727,15 +1735,15 @@ function buttonsClickOnEnter() { } function searchUsers() { - var $searchUserBox = $('#search-user-box'); + const $searchUserBox = $('#search-user-box'); $searchUserBox.search({ minCharacters: 2, apiSettings: { url: suburl + '/api/v1/users/search?q={query}', onResponse: function(response) { - var items = []; + const items = []; $.each(response.data, function (_i, item) { - var title = item.login; + let title = item.login; if (item.full_name && item.full_name.length > 0) { title += ' (' + htmlEncode(item.full_name) + ')'; } @@ -1753,14 +1761,38 @@ function searchUsers() { }); } +function searchTeams() { + const $searchTeamBox = $('#search-team-box'); + $searchTeamBox.search({ + minCharacters: 2, + apiSettings: { + url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams/search?q={query}', + headers: {"X-Csrf-Token": csrf}, + onResponse: function(response) { + const items = []; + $.each(response.data, function (_i, item) { + const title = item.name + ' (' + item.permission + ' access)'; + items.push({ + title: title, + }) + }); + + return { results: items } + } + }, + searchFields: ['name', 'description'], + showNoResults: false + }); +} + function searchRepositories() { - var $searchRepoBox = $('#search-repo-box'); + const $searchRepoBox = $('#search-repo-box'); $searchRepoBox.search({ minCharacters: 2, apiSettings: { url: suburl + '/api/v1/repos/search?q={query}&uid=' + $searchRepoBox.data('uid'), onResponse: function(response) { - var items = []; + const items = []; $.each(response.data, function (_i, item) { items.push({ title: item.full_name.split("/")[1], @@ -1779,16 +1811,16 @@ function searchRepositories() { function initCodeView() { if ($('.code-view .linenums').length > 0) { $(document).on('click', '.lines-num span', function (e) { - var $select = $(this); - var $list = $select.parent().siblings('.lines-code').find('ol.linenums > li'); + const $select = $(this); + const $list = $select.parent().siblings('.lines-code').find('ol.linenums > li'); selectRange($list, $list.filter('[rel=' + $select.attr('id') + ']'), (e.shiftKey ? $list.filter('.active').eq(0) : null)); deSelect(); }); $(window).on('hashchange', function () { - var m = window.location.hash.match(/^#(L\d+)-(L\d+)$/); - var $list = $('.code-view ol.linenums > li'); - var $first; + let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/); + const $list = $('.code-view ol.linenums > li'); + let $first; if (m) { $first = $list.filter('.' + m[1]); selectRange($list, $first, $list.filter('.' + m[2])); @@ -1873,7 +1905,7 @@ function checkError(resp) { function u2fError(errorType) { - var u2fErrors = { + const u2fErrors = { 'browser': $('#unsupported-browser'), 1: $('#u2f-error-1'), 2: $('#u2f-error-2'), @@ -1882,7 +1914,7 @@ function u2fError(errorType) { 5: $('.u2f-error-5') }; u2fErrors[errorType].removeClass('hide'); - for(var type in u2fErrors){ + for(const type in u2fErrors){ if(type != errorType){ u2fErrors[type].addClass('hide'); } @@ -1933,11 +1965,11 @@ function initWipTitle() { $(".title_wip_desc > a").click(function (e) { e.preventDefault(); - var $issueTitle = $("#issue_title"); + const $issueTitle = $("#issue_title"); $issueTitle.focus(); - var value = $issueTitle.val().trim().toUpperCase(); + const value = $issueTitle.val().trim().toUpperCase(); - for (var i in wipPrefixes) { + for (const i in wipPrefixes) { if (value.startsWith(wipPrefixes[i].toUpperCase())) { return; } @@ -1953,10 +1985,6 @@ $(document).ready(function () { // Show exact time $('.time-since').each(function () { - var time = new Date($(this).attr('title')) - if (!isNaN(time)){ - $(this).attr('title', time.toLocaleString()) - } $(this).addClass('poping up').attr('data-content', $(this).attr('title')).attr('data-variation', 'inverted tiny').attr('title', ''); }); @@ -2001,17 +2029,18 @@ $(document).ready(function () { // Highlight JS if (typeof hljs != 'undefined') { - hljs.initHighlightingOnLoad(); + const nodes = [].slice.call(document.querySelectorAll('pre code') || []); + for (let i = 0; i < nodes.length; i++) { + hljs.highlightBlock(nodes[i]); + } } // Dropzone - var $dropzone = $('#dropzone'); + const $dropzone = $('#dropzone'); if ($dropzone.length > 0) { - // Disable auto discover for all elements: - Dropzone.autoDiscover = false; + const filenameDict = {}; - var filenameDict = {}; - $dropzone.dropzone({ + new Dropzone("#dropzone", { url: $dropzone.data('upload-url'), headers: {"X-Csrf-Token": csrf}, maxFiles: $dropzone.data('max-file'), @@ -2025,7 +2054,7 @@ $(document).ready(function () { init: function () { this.on("success", function (file, data) { filenameDict[file.name] = data.uuid; - var input = $('').val(data.uuid); + const input = $('').val(data.uuid); $('.files').append(input); }); this.on("removedfile", function (file) { @@ -2039,7 +2068,7 @@ $(document).ready(function () { }); } }) - } + }, }); } @@ -2048,10 +2077,10 @@ $(document).ready(function () { img_dir: suburl + '/vendor/plugins/emojify/images', ignore_emoticons: true }); - var hasEmoji = document.getElementsByClassName('has-emoji'); - for (var i = 0; i < hasEmoji.length; i++) { + const hasEmoji = document.getElementsByClassName('has-emoji'); + for (let i = 0; i < hasEmoji.length; i++) { emojify.run(hasEmoji[i]); - for (var j = 0; j < hasEmoji[i].childNodes.length; j++) { + for (let j = 0; j < hasEmoji[i].childNodes.length; j++) { if (hasEmoji[i].childNodes[j].nodeName === "A") { emojify.run(hasEmoji[i].childNodes[j]) } @@ -2059,7 +2088,7 @@ $(document).ready(function () { } // Clipboard JS - var clipboard = new Clipboard('.clipboard'); + const clipboard = new Clipboard('.clipboard'); clipboard.on('success', function (e) { e.clearSelection(); @@ -2082,7 +2111,7 @@ $(document).ready(function () { $('.delete-branch-button').click(showDeletePopup); $('.undo-button').click(function() { - var $this = $(this); + const $this = $(this); $.post($this.data('url'), { "_csrf": csrf, "id": $this.data("id") @@ -2097,7 +2126,7 @@ $(document).ready(function () { $($(this).data('modal')).modal('show'); }); $('.delete-post.button').click(function () { - var $this = $(this); + const $this = $(this); $.post($this.data('request-url'), { "_csrf": csrf }).done(function () { @@ -2107,11 +2136,11 @@ $(document).ready(function () { // Set anchor. $('.markdown').each(function () { - var headers = {}; + const headers = {}; $(this).find('h1, h2, h3, h4, h5, h6').each(function () { - var node = $(this); - var val = encodeURIComponent(node.text().toLowerCase().replace(/[^\u00C0-\u1FFF\u2C00-\uD7FF\w\- ]/g, '').replace(/[ ]/g, '-')); - var name = val; + let node = $(this); + const val = encodeURIComponent(node.text().toLowerCase().replace(/[^\u00C0-\u1FFF\u2C00-\uD7FF\w\- ]/g, '').replace(/[ ]/g, '-')); + let name = val; if (headers[val] > 0) { name = val + '-' + headers[val]; } @@ -2126,7 +2155,7 @@ $(document).ready(function () { }); $('.issue-checkbox').click(function() { - var numChecked = $('.issue-checkbox').children('input:checked').length; + const numChecked = $('.issue-checkbox').children('input:checked').length; if (numChecked > 0) { $('#issue-filters').addClass("hide"); $('#issue-actions').removeClass("hide"); @@ -2139,19 +2168,34 @@ $(document).ready(function () { $('.issue-action').click(function () { let action = this.dataset.action; let elementId = this.dataset.elementId; - let issueIDs = $('.issue-checkbox').children('input:checked').map(function() { + const issueIDs = $('.issue-checkbox').children('input:checked').map(function() { return this.dataset.issueId; }).get().join(); - let url = this.dataset.url; + const url = this.dataset.url; if (elementId === '0' && url.substr(-9) === '/assignee'){ elementId = ''; action = 'clear'; } - updateIssuesMeta(url, action, issueIDs, elementId).then(reload); + updateIssuesMeta(url, action, issueIDs, elementId).then(function() { + // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload + if (action === "close" || action === "open" ){ + //uncheck all checkboxes + $('.issue-checkbox input[type="checkbox"]').each(function(_,e){ e.checked = false; }); + } + reload(); + }); + }); + + // NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload + // trigger ckecked event, if checkboxes are checked on load + $('.issue-checkbox input[type="checkbox"]:checked').first().each(function(_,e) { + e.checked = false; + $(e).click(); }); buttonsClickOnEnter(); searchUsers(); + searchTeams(); searchRepositories(); initCommentForm(); @@ -2190,12 +2234,12 @@ $(document).ready(function () { } } - var routes = { + const routes = { 'div.user.settings': initUserSettings, 'div.repository.settings.collaboration': initRepositoryCollaboration }; - var selector; + let selector; for (selector in routes) { if ($(selector).length > 0) { routes[selector](); @@ -2203,9 +2247,9 @@ $(document).ready(function () { } } - var $cloneAddr = $('#clone_addr'); + const $cloneAddr = $('#clone_addr'); $cloneAddr.change(function() { - var $repoName = $('#repo_name'); + const $repoName = $('#repo_name'); if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]); } @@ -2232,17 +2276,17 @@ function deSelect() { function selectRange($list, $select, $from) { $list.removeClass('active'); if ($from) { - var a = parseInt($select.attr('rel').substr(1)); - var b = parseInt($from.attr('rel').substr(1)); - var c; + let a = parseInt($select.attr('rel').substr(1)); + let b = parseInt($from.attr('rel').substr(1)); + let c; if (a != b) { if (a > b) { c = a; a = b; b = c; } - var classes = []; - for (var i = a; i <= b; i++) { + const classes = []; + for (let i = a; i <= b; i++) { classes.push('.L' + i); } $list.filter(classes.join(',')).addClass('active'); @@ -2263,8 +2307,8 @@ $(function () { // Parse SSH Key $("#ssh-key-content").on('change paste keyup',function(){ - var arrays = $(this).val().split(" "); - var $title = $("#ssh-key-title") + const arrays = $(this).val().split(" "); + const $title = $("#ssh-key-title") if ($title.val() === "" && arrays.length === 3 && arrays[2] !== "") { $title.val(arrays[2]); } @@ -2272,13 +2316,13 @@ $(function () { }); function showDeletePopup() { - var $this = $(this); - var filter = ""; + const $this = $(this); + let filter = ""; if ($this.attr("id")) { filter += "#" + $this.attr("id") } - var dialog = $('.delete.modal' + filter); + const dialog = $('.delete.modal' + filter); dialog.find('.name').text($this.data('name')); dialog.modal({ @@ -2301,7 +2345,7 @@ function showDeletePopup() { } function initVueComponents(){ - var vueDelimeters = ['${', '}']; + const vueDelimeters = ['${', '}']; Vue.component('repo-search', { delimiters: vueDelimeters, @@ -2391,7 +2435,7 @@ function initVueComponents(){ mounted: function() { this.searchRepos(this.reposFilter); - var self = this; + const self = this; Vue.nextTick(function() { self.$refs.search.focus(); }); @@ -2425,18 +2469,18 @@ function initVueComponents(){ }, searchRepos: function(reposFilter) { - var self = this; + const self = this; this.isLoading = true; - var searchedMode = this.repoTypes[reposFilter].searchMode; - var searchedURL = this.searchURL; - var searchedQuery = this.searchQuery; + const searchedMode = this.repoTypes[reposFilter].searchMode; + const searchedURL = this.searchURL; + const searchedQuery = this.searchQuery; $.getJSON(searchedURL, function(result, _textStatus, request) { if (searchedURL == self.searchURL) { self.repos = result.data; - var count = request.getResponseHeader('X-Total-Count'); + const count = request.getResponseHeader('X-Total-Count'); if (searchedQuery === '' && searchedMode === '') { self.reposTotalCount = count; } @@ -2473,7 +2517,7 @@ function initCtrlEnterSubmit() { } function initVueApp() { - var el = document.getElementById('app'); + const el = document.getElementById('app'); if (!el) { return; } @@ -2511,7 +2555,7 @@ function cancelStopwatch() { } function initHeatmap(appElementId, heatmapUser, locale) { - var el = document.getElementById(appElementId); + const el = document.getElementById(appElementId); if (!el) { return; } @@ -2521,7 +2565,7 @@ function initHeatmap(appElementId, heatmapUser, locale) { locale.contributions = locale.contributions || 'contributions'; locale.no_contributions = locale.no_contributions || 'No contributions'; - var vueDelimeters = ['${', '}']; + const vueDelimeters = ['${', '}']; Vue.component('activity-heatmap', { delimiters: vueDelimeters, @@ -2546,7 +2590,8 @@ function initHeatmap(appElementId, heatmapUser, locale) { isLoading: true, colorRange: [], endDate: null, - values: [] + values: [], + totalContributions: 0, }; }, @@ -2565,10 +2610,11 @@ function initHeatmap(appElementId, heatmapUser, locale) { methods: { loadHeatmap: function(userName) { - var self = this; + const self = this; $.get(this.suburl + '/api/v1/users/' + userName + '/heatmap', function(chartRawData) { - var chartData = []; - for (var i = 0; i < chartRawData.length; i++) { + const chartData = []; + for (let i = 0; i < chartRawData.length; i++) { + self.totalContributions += chartRawData[i].contributions; chartData[i] = { date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions }; } self.values = chartData; @@ -2577,11 +2623,11 @@ function initHeatmap(appElementId, heatmapUser, locale) { }, getColor: function(idx) { - var el = document.createElement('div'); + const el = document.createElement('div'); el.className = 'heatmap-color-' + idx; document.body.appendChild(el); - var color = getComputedStyle(el).backgroundColor; + const color = getComputedStyle(el).backgroundColor; document.body.removeChild(el); @@ -2589,7 +2635,7 @@ function initHeatmap(appElementId, heatmapUser, locale) { } }, - template: '
    ' + template: '

    total contributions in the last 12 months

    ' }); new Vue({ @@ -2606,9 +2652,9 @@ function initHeatmap(appElementId, heatmapUser, locale) { function initFilterBranchTagDropdown(selector) { $(selector).each(function() { - var $dropdown = $(this); - var $data = $dropdown.find('.data'); - var data = { + const $dropdown = $(this); + const $data = $dropdown.find('.data'); + const data = { items: [], mode: $data.data('mode'), searchTerm: '', @@ -2633,7 +2679,7 @@ function initFilterBranchTagDropdown(selector) { data: data, beforeMount: function () { - var vm = this; + const vm = this; this.noResults = vm.$el.getAttribute('data-no-results'); this.canCreateBranch = vm.$el.getAttribute('data-can-create-branch') === 'true'; @@ -2658,9 +2704,9 @@ function initFilterBranchTagDropdown(selector) { computed: { filteredItems: function() { - var vm = this; + const vm = this; - var items = vm.items.filter(function (item) { + const items = vm.items.filter(function (item) { return ((vm.mode === 'branches' && item.branch) || (vm.mode === 'tags' && item.tag)) && (!vm.searchTerm @@ -2676,7 +2722,7 @@ function initFilterBranchTagDropdown(selector) { && !this.showCreateNewBranch; }, showCreateNewBranch: function() { - var vm = this; + const vm = this; if (!this.canCreateBranch || !vm.searchTerm || vm.mode === 'tags') { return false; } @@ -2689,7 +2735,7 @@ function initFilterBranchTagDropdown(selector) { methods: { selectItem: function(item) { - var prev = this.getSelected(); + const prev = this.getSelected(); if (prev !== null) { prev.selected = false; } @@ -2703,27 +2749,27 @@ function initFilterBranchTagDropdown(selector) { this.$refs.newBranchForm.submit(); }, focusSearchField: function() { - var vm = this; + const vm = this; Vue.nextTick(function() { vm.$refs.searchField.focus(); }); }, getSelected: function() { - for (var i = 0, j = this.items.length; i < j; ++i) { + for (let i = 0, j = this.items.length; i < j; ++i) { if (this.items[i].selected) return this.items[i]; } return null; }, getSelectedIndexInFiltered: function() { - for (var i = 0, j = this.filteredItems.length; i < j; ++i) { + for (let i = 0, j = this.filteredItems.length; i < j; ++i) { if (this.filteredItems[i].selected) return i; } return -1; }, scrollToActive: function() { - var el = this.$refs['listItem' + this.active]; + let el = this.$refs['listItem' + this.active]; if (!el || el.length === 0) { return; } @@ -2731,7 +2777,7 @@ function initFilterBranchTagDropdown(selector) { el = el[0]; } - var cont = this.$refs.scrollContainer; + const cont = this.$refs.scrollContainer; if (el.offsetTop < cont.scrollTop) { cont.scrollTop = el.offsetTop; @@ -2741,7 +2787,7 @@ function initFilterBranchTagDropdown(selector) { } }, keydown: function(event) { - var vm = this; + const vm = this; if (event.keyCode === 40) { // arrow down event.preventDefault(); @@ -2796,9 +2842,9 @@ $(".commit-button").click(function() { }); function initNavbarContentToggle() { - var content = $('#navbar'); - var toggle = $('#navbar-expand-toggle'); - var isExpanded = false; + const content = $('#navbar'); + const toggle = $('#navbar-expand-toggle'); + let isExpanded = false; toggle.click(function() { isExpanded = !isExpanded; if (isExpanded) { @@ -2813,13 +2859,13 @@ function initNavbarContentToggle() { } function initTopicbar() { - var mgrBtn = $("#manage_topic"), - editDiv = $("#topic_edit"), - viewDiv = $("#repo-topics"), - saveBtn = $("#save_topic"), - topicDropdown = $('#topic_edit .dropdown'), - topicForm = $('#topic_edit.ui.form'), - topicPrompts; + const mgrBtn = $("#manage_topic"); + const editDiv = $("#topic_edit"); + const viewDiv = $("#repo-topics"); + const saveBtn = $("#save_topic"); + const topicDropdown = $('#topic_edit .dropdown'); + const topicForm = $('#topic_edit.ui.form'); + const topicPrompts = getPrompts(); mgrBtn.click(function() { viewDiv.hide(); @@ -2827,7 +2873,7 @@ function initTopicbar() { }); function getPrompts() { - var hidePrompt = $("div.hide#validate_prompt"), + const hidePrompt = $("div.hide#validate_prompt"), prompts = { countPrompt: hidePrompt.children('#count_prompt').text(), formatPrompt: hidePrompt.children('#format_prompt').text() @@ -2837,7 +2883,7 @@ function initTopicbar() { } saveBtn.click(function() { - var topics = $("input[name=topics]").val(); + const topics = $("input[name=topics]").val(); $.post(saveBtn.data('link'), { "_csrf": csrf, @@ -2846,10 +2892,10 @@ function initTopicbar() { if (xhr.responseJSON.status === 'ok') { viewDiv.children(".topic").remove(); if (topics.length) { - var topicArray = topics.split(","); + const topicArray = topics.split(","); - var last = viewDiv.children("a").last(); - for (var i=0; i < topicArray.length; i++) { + const last = viewDiv.children("a").last(); + for (let i=0; i < topicArray.length; i++) { $('
    '+topicArray[i]+'
    ').insertBefore(last) } } @@ -2861,11 +2907,11 @@ function initTopicbar() { if (xhr.responseJSON.invalidTopics.length > 0) { topicPrompts.formatPrompt = xhr.responseJSON.message; - var invalidTopics = xhr.responseJSON.invalidTopics, + const invalidTopics = xhr.responseJSON.invalidTopics, topicLables = topicDropdown.children('a.ui.label'); topics.split(',').forEach(function(value, index) { - for (var i=0; i < invalidTopics.length; i++) { + for (let i=0; i < invalidTopics.length; i++) { if (invalidTopics[i] === value) { topicLables.eq(index).removeClass("green").addClass("red"); } @@ -2900,7 +2946,7 @@ function initTopicbar() { throttle: 500, cache: false, onResponse: function(res) { - let formattedResponse = { + const formattedResponse = { success: false, results: [], }; @@ -2908,23 +2954,23 @@ function initTopicbar() { return text.replace(/<[^>]*>?/gm, ""); }; - let query = stripTags(this.urlData.query.trim()); + const query = stripTags(this.urlData.query.trim()); let found_query = false; - let current_topics = []; + const current_topics = []; topicDropdown.find('div.label.visible.topic,a.label.visible').each(function(_,e){ current_topics.push(e.dataset.value); }); if (res.topics) { let found = false; for (let i=0;i < res.topics.length;i++) { // skip currently added tags - if (current_topics.indexOf(res.topics[i].Name) != -1){ + if (current_topics.indexOf(res.topics[i].topic_name) != -1){ continue; } - if (res.topics[i].Name.toLowerCase() === query.toLowerCase()){ + if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()){ found_query = true; } - formattedResponse.results.push({"description": res.topics[i].Name, "data-value": res.topics[i].Name}); + formattedResponse.results.push({"description": res.topics[i].topic_name, "data-value": res.topics[i].topic_name}); found = true; } formattedResponse.success = found; @@ -2960,7 +3006,7 @@ function initTopicbar() { }); $.fn.form.settings.rules.validateTopic = function(_values, regExp) { - var topics = topicDropdown.children('a.ui.label'), + const topics = topicDropdown.children('a.ui.label'), status = topics.length === 0 || topics.last().attr("data-value").match(regExp); if (!status) { topics.last().removeClass("green").addClass("red"); @@ -2968,7 +3014,6 @@ function initTopicbar() { return status && topicDropdown.children('a.ui.label.red').length === 0; }; - topicPrompts = getPrompts(); topicForm.form({ on: 'change', inline : true, @@ -2995,7 +3040,7 @@ function toggleDeadlineForm() { } function setDeadline() { - var deadline = $('#deadlineDate').val(); + const deadline = $('#deadlineDate').val(); updateDeadline(deadline); } @@ -3003,10 +3048,10 @@ function updateDeadline(deadlineString) { $('#deadline-err-invalid-date').hide(); $('#deadline-loader').addClass('loading'); - var realDeadline = null; + let realDeadline = null; if (deadlineString !== '') { - var newDate = Date.parse(deadlineString) + const newDate = Date.parse(deadlineString) if (isNaN(newDate)) { $('#deadline-loader').removeClass('loading'); @@ -3051,14 +3096,14 @@ function deleteDependencyModal(id, type) { } function initIssueList() { - var repolink = $('#repolink').val(); + const repolink = $('#repolink').val(); $('#new-dependency-drop-list') .dropdown({ apiSettings: { url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}', onResponse: function(response) { - var filteredResponse = {'success': true, 'results': []}; - var currIssueId = $('#new-dependency-drop-list').data('issue-id'); + const filteredResponse = {'success': true, 'results': []}; + const currIssueId = $('#new-dependency-drop-list').data('issue-id'); // Parse the response from the api to work with our dropdown $.each(response, function(_i, issue) { // Don't list current issue in the dependency list. @@ -3080,7 +3125,7 @@ function initIssueList() { ; } function cancelCodeComment(btn) { - var form = $(btn).closest("form"); + const form = $(btn).closest("form"); if(form.length > 0 && form.hasClass('comment-form')) { form.addClass('hide'); form.parent().find('button.comment-form-reply').show(); @@ -3089,8 +3134,8 @@ function cancelCodeComment(btn) { } } function onOAuthLoginClick() { - var oauthLoader = $('#oauth2-login-loader'); - var oauthNav = $('#oauth2-login-navigator'); + const oauthLoader = $('#oauth2-login-loader'); + const oauthNav = $('#oauth2-login-navigator'); oauthNav.hide(); oauthLoader.removeClass('disabled'); diff --git a/public/less/_base.less b/public/less/_base.less index aae3b97c72..e295be368d 100644 --- a/public/less/_base.less +++ b/public/less/_base.less @@ -339,7 +339,7 @@ code, } &.menu .ui.dropdown.item .menu .item { - margin-right: auto; + width: 100%; } &.dropdown .menu > .item > .floating.label { @@ -600,6 +600,45 @@ code, } } + .border { + border: 1px solid; + &.red { + border-color: #d95c5c !important; + } + + &.blue { + border-color: #428bca !important; + } + + &.black { + border-color: #444444; + } + + &.grey { + border-color: #767676 !important; + } + + &.light.grey { + border-color: #888888 !important; + } + + &.green { + border-color: #6cc644 !important; + } + + &.purple { + border-color: #6e5494 !important; + } + + &.yellow { + border-color: #fbbd08 !important; + } + + &.gold { + border-color: #a1882b !important; + } + } + .branch-tag-choice { line-height: 20px; } @@ -617,7 +656,6 @@ code, .file-comment { font: 12px @monospaced-fonts, monospace; color: rgba(0, 0, 0, 0.87); - } .ui.floating.dropdown { @@ -843,6 +881,12 @@ footer { display: none; } } + + .total-contributions { + text-align: left; + font-weight: 500; + margin-top: 0; + } } .heatmap-color-0 { @@ -889,3 +933,131 @@ footer { .ui.tabular.menu .item.active { color: rgba(0, 0, 0, 0.9); } + +/* multiple radio or checkboxes as inline element */ +.inline-grouped-list { + display: inline-block; + vertical-align: top; + + > .ui { + display: block; + margin-top: 5px; + margin-bottom: 10px; + + &:first-child { + margin-top: 1px; + } + } +} + +.lines-num { + vertical-align: top; + text-align: right !important; + color: #999999; + background: #f5f5f5; + width: 1%; + user-select: none; + + span { + &:before { + content: attr(data-line-number); + line-height: 20px !important; + padding: 0 10px; + cursor: pointer; + display: block; + } + } +} + +.lines-num, +.lines-code { + padding: 0 !important; + + pre, + ol, + .hljs { + background-color: white; + margin: 0; + padding: 0 !important; + + li { + display: block; + width: 100%; + + &:before { + content: ' '; + } + } + } +} + +.lines-commit { + vertical-align: top; + color: #999999; + padding: 0 !important; + background: #f5f5f5; + width: 1%; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; + + .blame-info { + width: 350px; + max-width: 350px; + display: block; + user-select: none; + padding: 0 0 0 10px; + + .blame-data { + display: flex; + font-family: @default-fonts; + + .blame-message { + flex-grow: 2; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + line-height: 20px; + } + + .blame-time, + .blame-avatar { + flex-shrink: 0; + } + } + } + + .ui.avatar.image { + height: 18px; + width: 18px; + } +} + +.lines-num, +.lines-code, +.lines-commit { + .bottom-line { + border-bottom: 1px solid #eaecef; + } +} + +.code-view { + overflow: auto; + overflow-x: auto; + overflow-y: hidden; + + * { + font-size: 12px; + font-family: @monospaced-fonts, monospace; + line-height: 20px; + } + + table { + width: 100%; + } + + .active { + background: #fff866; + } +} diff --git a/public/less/_dashboard.less b/public/less/_dashboard.less index 1b5d231499..8749fb8802 100644 --- a/public/less/_dashboard.less +++ b/public/less/_dashboard.less @@ -14,6 +14,8 @@ } .filter.menu { + width: initial; + .item { text-align: left; diff --git a/public/less/_explore.less b/public/less/_explore.less index c5065a35bc..ad9adc70e0 100644 --- a/public/less/_explore.less +++ b/public/less/_explore.less @@ -62,9 +62,19 @@ } .ui.repository.branches { - .time { + .info { font-size: 12px; color: #808080; + display: flex; + white-space: pre; + .commit-message { + max-width: 72em; + overflow: hidden; + text-overflow: ellipsis; + } + } + .overflow-visible { + overflow: visible; } } diff --git a/public/less/_markdown.less b/public/less/_markdown.less index 1dcc2caf94..8c7b1125c9 100644 --- a/public/less/_markdown.less +++ b/public/less/_markdown.less @@ -505,6 +505,7 @@ .sub.header { padding-left: 52px; + word-break: break-word; } } } diff --git a/public/less/_repository.less b/public/less/_repository.less index 21aeea81e7..ade3477ccc 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -20,6 +20,15 @@ .ui.huge.breadcrumb { font-weight: 400; font-size: 1.5rem; + + i.mega-octicon { + position: relative; + top: 5px; + } + + i.octicon-lock { + margin-left: 5px; + } } .fork-flag { @@ -277,6 +286,24 @@ .ui.avatar { margin-bottom: 5px; } + + .commit-summary a { + text-decoration: underline; + text-decoration-style: dashed; + + &:hover { + text-decoration-style: solid; + } + + &.default-link { + text-decoration: none; + + &:hover { + text-decoration: underline; + text-decoration-style: solid; + } + } + } } tbody { @@ -400,117 +427,13 @@ } } - pre { - overflow: auto; + .csv { + overflow-x: auto; + padding: 0 !important; } - .code-view { - * { - font-size: 12px; - font-family: @monospaced-fonts, monospace; - line-height: 20px; - } - - table { - width: 100%; - } - - .lines-num { - vertical-align: top; - text-align: right; - color: #999999; - background: #f5f5f5; - width: 1%; - user-select: none; - - span { - line-height: 20px; - padding: 0 10px; - cursor: pointer; - display: block; - } - } - - .lines-num, - .lines-code { - padding: 0; - - pre, - ol, - .hljs { - background-color: white; - margin: 0; - padding: 0 !important; - - li { - display: block; - width: 100%; - - &.active { - background: #ffffdd; - } - - &:before { - content: ' '; - } - } - } - } - - .lines-commit { - vertical-align: top; - color: #999999; - padding: 0; - background: #f5f5f5; - width: 1%; - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - user-select: none; - - .blame-info { - width: 350px; - max-width: 350px; - display: block; - user-select: none; - padding: 0 0 0 10px; - - .blame-data { - display: flex; - font-family: @default-fonts; - - .blame-message { - flex-grow: 2; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - line-height: 20px; - } - - .blame-time, - .blame-avatar { - flex-shrink: 0; - } - } - } - - .ui.avatar.image { - height: 18px; - width: 18px; - } - } - - .lines-num, - .lines-code, - .lines-commit { - .bottom-line { - border-bottom: 1px solid #eaecef; - } - } - - .active { - background: #ffffdd; - } + pre { + overflow: auto; } } @@ -614,6 +537,10 @@ @comment-avatar-width: 3em; + .comment textarea { + max-height: none !important; + } + &.new.issue { .comment.form { .comment { @@ -1265,6 +1192,10 @@ margin: 0; } + td.message { + text-overflow: unset; + } + &.ui.basic.striped.table tbody tr:nth-child(2n) { background-color: rgba(0, 0, 0, 0.02) !important; } @@ -1272,17 +1203,15 @@ #commits-table td.sha .sha.label, #repo-files-table .sha.label { - &.isSigned { - border: 1px solid #bbbbbb; + border: 1px solid #bbbbbb; - .detail.icon { - background: #fafafa; - margin: -6px -10px -4px 0; - padding: 5px 3px 5px 6px; - border-left: 1px solid #bbbbbb; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } + .detail.icon { + background: #fafafa; + margin: -6px -10px -4px 0; + padding: 5px 3px 5px 6px; + border-left: 1px solid #bbbbbb; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } &.isSigned.isVerified { @@ -1290,7 +1219,11 @@ background: fade(#21ba45, 10%); .detail.icon { - border-left: 1px solid fade(#21ba45, 50%); + border-left: 1px solid #21ba45; + } + + &:hover { + background: fade(#21ba45, 30%) !important; } } } @@ -1418,7 +1351,7 @@ font-size: 12px; td { - padding: 0 0 0 10px; + padding: 0 0 0 10px !important; border-top: 0; } @@ -1426,7 +1359,7 @@ border-color: #d4d4d5; border-right-width: 1px; border-right-style: solid; - padding: 0 5px; + padding: 0 5px !important; } tbody { @@ -1436,23 +1369,10 @@ width: 49%; } - &.tag-code td, - td.tag-code { - background-color: #f0f0f0 !important; - border-color: #d3cfcf !important; - padding-top: 8px; - padding-bottom: 8px; - // td.selected-line, td.selected-line pre { - // background-color: #ffffdd !important; - // } + td.center { + text-align: center; } - // &.same-code { - // td.selected-line, td.selected-line pre { - // background-color: #ffffdd !important; - // } - // } - .removed-code { background-color: #ff9999; } @@ -1540,17 +1460,14 @@ max-width: 100%; padding: 5px 5px 0 5px; } + img.emoji { + padding: 0; + } clear: right; } } - .code-view { - overflow: auto; - overflow-x: auto; - overflow-y: hidden; - } - .repo-search-result { padding-top: 10px; padding-bottom: 10px; @@ -1820,6 +1737,19 @@ margin-top: -3px; } } + + #repo-collab-team-form { + #search-team-box { + .results { + left: 7px; + } + } + + .ui.button { + margin-left: 5px; + margin-top: -3px; + } + } } &.branches { @@ -1971,6 +1901,8 @@ &.user-cards { .list { padding: 0; + display: flex; + flex-wrap: wrap; .item { list-style: none; @@ -2018,6 +1950,16 @@ } } +#search-team-box { + .results { + .result { + .content { + margin: 6px 0; + } + } + } +} + #issue-filters.hide { display: none; } @@ -2084,7 +2026,7 @@ } a.milestone { - padding-left: 5px; + margin-left: 5px; color: #999999 !important; &:hover { @@ -2092,6 +2034,19 @@ } } + a.ref { + margin-left: 8px; + color: #999999 !important; + + &:hover { + color: #000000 !important; + } + + span { + margin-right: -4px; + } + } + .assignee { margin-top: -5px; margin-right: 5px; @@ -2133,7 +2088,7 @@ .list { > .item { - .green { + .green:not(.ui.button) { color: #21ba45; } @@ -2300,12 +2255,30 @@ tbody.commit-list { .commit-list .message-wrapper { overflow: hidden; text-overflow: ellipsis; - max-width: calc(100% - 24px); + max-width: calc(100% - 50px); display: inline-block; vertical-align: middle; } -.commit-list .message-wrapper .commit-status-link { +.commit-list .commit-summary a { + text-decoration: underline; + text-decoration-style: dashed; + + &:hover { + text-decoration-style: solid; + } + + &.default-link { + text-decoration: none; + + &:hover { + text-decoration: underline; + text-decoration-style: solid; + } + } +} + +.commit-list .commit-status-link { display: inline-block; vertical-align: middle; } @@ -2404,3 +2377,11 @@ tbody.commit-list { border-left: 0 !important; margin: 0 !important; } + +.tag-code, +.tag-code td { + background-color: #f0f0f0 !important; + border-color: #d3cfcf !important; + padding-top: 8px; + padding-bottom: 8px; +} diff --git a/public/less/themes/arc-green.less b/public/less/themes/arc-green.less index ebe63c65fa..27c32728a2 100644 --- a/public/less/themes/arc-green.less +++ b/public/less/themes/arc-green.less @@ -7,11 +7,6 @@ color: #bababa; } -.repository.file.list .non-diff-file-content .code-view .lines-num, -.repository.file.list .non-diff-file-content .code-view .lines-code ol { - background-color: #2b2b2b !important; -} - .hljs-strong, .hljs-emphasis { color: #a8a8a2; @@ -230,6 +225,7 @@ a:hover { .ui.secondary.menu .dropdown.item:hover, .ui.secondary.menu .link.item:hover, +.ui.secondary.menu .active.item:hover, .ui.secondary.menu a.item:hover { color: #ffffff; } @@ -498,12 +494,12 @@ a.ui.basic.green.label:hover { .ui.table { color: #a5a5a5 !important; - border: 1px solid #4c505c; + border-color: #4c505c; background: #353945; } .ui.table tbody tr { - border-bottom: 1px solid #333640; + border-color: #333640; background: #2a2e3a; } @@ -714,19 +710,6 @@ a.ui.basic.green.label:hover { background-color: #3a523a; } -.repository .diff-file-box .code-diff .lines-num { - border-right: 1px solid #2d2d2d; -} - -.repository .diff-file-box .file-body.file-code .lines-num { - color: #9e9e9e; - background: #2e323e; -} - -.repository .diff-file-box .file-body.file-code .lines-num-old { - border-right: 1px solid #2d2d2d; -} - .hljs-title, .hljs-section, .hljs-selector-id { @@ -742,9 +725,10 @@ a.ui.basic.green.label:hover { background-color: #5f3737; } -.repository .diff-file-box .code-diff tbody tr.tag-code td, -.repository .diff-file-box .code-diff tbody tr td.tag-code { - background-color: #292727 !important; +.tag-code, +.tag-code td { + background: #242637 !important; + border-color: transparent !important; } .ui.vertical.menu .active.item { @@ -1054,10 +1038,6 @@ input { color: #668cb1; } -.repository.file.list #file-content .code-view .lines-num { - background: #2e323e; -} - .repository.file.list #repo-files-table tbody .octicon.octicon-file-directory, .repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule { color: #7c9b5e; @@ -1080,19 +1060,48 @@ input { color: #87ab63 !important; } -.repository.file.list #file-content .code-view .lines-num pre, -.repository.file.list #file-content .code-view .lines-code pre, -.repository.file.list #file-content .code-view .lines-num ol, -.repository.file.list #file-content .code-view .lines-code ol, -.repository.file.list #file-content .code-view .lines-num .hljs, -.repository.file.list #file-content .code-view .lines-code .hljs { - background-color: #2a2e3a; +.lines-commit { + background: #2e323e !important; +} + +.bottom-line { + border-color: #4e525e !important; +} + +.lines-num { + background: #2e323e !important; + color: #9e9e9e !important; + border-color: #2d2d2d !important; +} + +.lines-num pre, +.lines-code pre, +.lines-num ol, +.lines-code ol, +.lines-num .hljs, +.lines-code .hljs { + background-color: #2a2e3a !important; +} + +.code-view .active { + background: #554a00; } a.ui.label:hover, a.ui.labels .label:hover { - background-color: #505667; - color: #dbdbdb; + background-color: #505667 !important; + color: #dbdbdb !important; +} + +.repository #commits-table td.sha .sha.label, +.repository #repo-files-table .sha.label { + border-color: #888; +} + +.repository #commits-table td.sha .sha.label.isSigned .detail.icon, +.repository #repo-files-table .sha.label.isSigned .detail.icon { + background: none; + border-left-color: #888; } .repository .label.list .item { @@ -1105,20 +1114,9 @@ a.ui.labels .label:hover { color: #87ab63 !important; } -.repository.file.list #file-content .code-view { - .lines-num pre, - .lines-code pre, - .lines-num ol, - .lines-code ol, - .lines-num .hljs, - .hljs { - background-color: #2a2e3a; - } -} - .repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(1), .repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(2), -.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(3), +.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(3), .repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(4), .repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(5), .repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(6) { diff --git a/public/vendor/VERSIONS b/public/vendor/VERSIONS index d058710711..6c5f10424f 100644 --- a/public/vendor/VERSIONS +++ b/public/vendor/VERSIONS @@ -6,7 +6,10 @@ File(s): /vendor/plugins/jquery.areyousure/jquery.are-you-sure.js Version: 1.9.0 File(s): /vendor/plugins/jquery/jquery.min.js -Version: 1.12.4 +Version: 3.4.1 + +File(s): /vendor/plugins/jquery-migrate/jquery-migrate.min.js +Version: 3.0.1 File(s): /vendor/plugins/semantic/semantic.min.js Version: 2.3.1 @@ -45,7 +48,7 @@ File(s): /vendor/plugins/simplemde/simplemde.min.js Version: 1.10.1 File(s): /vendor/plugins/pdfjs/ -Version: 1.4.20 +Version: 2.1.266 File(s): /vendor/plugins/u2f/ Version: 1.0.8 diff --git a/public/vendor/librejs.html b/public/vendor/librejs.html index 8a1669ae02..c17c2f14e8 100644 --- a/public/vendor/librejs.html +++ b/public/vendor/librejs.html @@ -17,8 +17,13 @@ jquery.min.js - Expat - jquery-1.12.4.min.js + MIT + jquery-3.4.1.min.js + + + jquery-migrate.min.js + MIT + jquery-migrate-3.0.1.min.js semantic.min.js @@ -103,7 +108,7 @@ pdf.js Apache-2.0-only - pdf.js-v1.4.20.tar.gz + pdf.js-v2.1.266.tar.gz u2f-api diff --git a/public/vendor/plugins/jquery-migrate/jquery-migrate.min.js b/public/vendor/plugins/jquery-migrate/jquery-migrate.min.js new file mode 100644 index 0000000000..cd6d6c8532 --- /dev/null +++ b/public/vendor/plugins/jquery-migrate/jquery-migrate.min.js @@ -0,0 +1,215 @@ +/*! jQuery Migrate v3.0.1 | (c) jQuery Foundation and other contributors | jquery.org/license */ + +void 0 === jQuery.migrateMute && (jQuery.migrateMute = !0), function(e) { + "function" == typeof define && define.amd ? define([ "jquery" ], window, e) : "object" == typeof module && module.exports ? module.exports = e(require("jquery"), window) : e(jQuery, window); +}(function(e, t) { + "use strict"; + function r(r) { + var n = t.console; + o[r] || (o[r] = !0, e.migrateWarnings.push(r), n && n.warn && !e.migrateMute && (n.warn("JQMIGRATE: " + r), + e.migrateTrace && n.trace && n.trace())); + } + function n(e, t, n, a) { + Object.defineProperty(e, t, { + configurable: !0, + enumerable: !0, + get: function() { + return r(a), n; + }, + set: function(e) { + r(a), n = e; + } + }); + } + function a(e, t, n, a) { + e[t] = function() { + return r(a), n.apply(this, arguments); + }; + } + e.migrateVersion = "3.0.1", function() { + var r = /^[12]\./; + t.console && t.console.log && (e && !r.test(e.fn.jquery) || t.console.log("JQMIGRATE: jQuery 3.0.0+ REQUIRED"), + e.migrateWarnings && t.console.log("JQMIGRATE: Migrate plugin loaded multiple times"), + t.console.log("JQMIGRATE: Migrate is installed" + (e.migrateMute ? "" : " with logging active") + ", version " + e.migrateVersion)); + }(); + var o = {}; + e.migrateWarnings = [], void 0 === e.migrateTrace && (e.migrateTrace = !0), e.migrateReset = function() { + o = {}, e.migrateWarnings.length = 0; + }, "BackCompat" === t.document.compatMode && r("jQuery is not compatible with Quirks Mode"); + var i = e.fn.init, s = e.isNumeric, u = e.find, c = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/, l = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/g; + e.fn.init = function(e) { + var t = Array.prototype.slice.call(arguments); + return "string" == typeof e && "#" === e && (r("jQuery( '#' ) is not a valid selector"), + t[0] = []), i.apply(this, t); + }, e.fn.init.prototype = e.fn, e.find = function(e) { + var n = Array.prototype.slice.call(arguments); + if ("string" == typeof e && c.test(e)) try { + t.document.querySelector(e); + } catch (a) { + e = e.replace(l, function(e, t, r, n) { + return "[" + t + r + '"' + n + '"]'; + }); + try { + t.document.querySelector(e), r("Attribute selector with '#' must be quoted: " + n[0]), + n[0] = e; + } catch (e) { + r("Attribute selector with '#' was not fixed: " + n[0]); + } + } + return u.apply(this, n); + }; + var d; + for (d in u) Object.prototype.hasOwnProperty.call(u, d) && (e.find[d] = u[d]); + e.fn.size = function() { + return r("jQuery.fn.size() is deprecated and removed; use the .length property"), + this.length; + }, e.parseJSON = function() { + return r("jQuery.parseJSON is deprecated; use JSON.parse"), JSON.parse.apply(null, arguments); + }, e.isNumeric = function(t) { + var n = s(t), a = function(t) { + var r = t && t.toString(); + return !e.isArray(t) && r - parseFloat(r) + 1 >= 0; + }(t); + return n !== a && r("jQuery.isNumeric() should not be called on constructed objects"), + a; + }, a(e, "holdReady", e.holdReady, "jQuery.holdReady is deprecated"), a(e, "unique", e.uniqueSort, "jQuery.unique is deprecated; use jQuery.uniqueSort"), + n(e.expr, "filters", e.expr.pseudos, "jQuery.expr.filters is deprecated; use jQuery.expr.pseudos"), + n(e.expr, ":", e.expr.pseudos, "jQuery.expr[':'] is deprecated; use jQuery.expr.pseudos"); + var p = e.ajax; + e.ajax = function() { + var e = p.apply(this, arguments); + return e.promise && (a(e, "success", e.done, "jQXHR.success is deprecated and removed"), + a(e, "error", e.fail, "jQXHR.error is deprecated and removed"), a(e, "complete", e.always, "jQXHR.complete is deprecated and removed")), + e; + }; + var f = e.fn.removeAttr, y = e.fn.toggleClass, m = /\S+/g; + e.fn.removeAttr = function(t) { + var n = this; + return e.each(t.match(m), function(t, a) { + e.expr.match.bool.test(a) && (r("jQuery.fn.removeAttr no longer sets boolean properties: " + a), + n.prop(a, !1)); + }), f.apply(this, arguments); + }, e.fn.toggleClass = function(t) { + return void 0 !== t && "boolean" != typeof t ? y.apply(this, arguments) : (r("jQuery.fn.toggleClass( boolean ) is deprecated"), + this.each(function() { + var r = this.getAttribute && this.getAttribute("class") || ""; + r && e.data(this, "__className__", r), this.setAttribute && this.setAttribute("class", r || !1 === t ? "" : e.data(this, "__className__") || ""); + })); + }; + var h = !1; + e.swap && e.each([ "height", "width", "reliableMarginRight" ], function(t, r) { + var n = e.cssHooks[r] && e.cssHooks[r].get; + n && (e.cssHooks[r].get = function() { + var e; + return h = !0, e = n.apply(this, arguments), h = !1, e; + }); + }), e.swap = function(e, t, n, a) { + var o, i, s = {}; + h || r("jQuery.swap() is undocumented and deprecated"); + for (i in t) s[i] = e.style[i], e.style[i] = t[i]; + o = n.apply(e, a || []); + for (i in t) e.style[i] = s[i]; + return o; + }; + var g = e.data; + e.data = function(t, n, a) { + var o; + if (n && "object" == typeof n && 2 === arguments.length) { + o = e.hasData(t) && g.call(this, t); + var i = {}; + for (var s in n) s !== e.camelCase(s) ? (r("jQuery.data() always sets/gets camelCased names: " + s), + o[s] = n[s]) : i[s] = n[s]; + return g.call(this, t, i), n; + } + return n && "string" == typeof n && n !== e.camelCase(n) && (o = e.hasData(t) && g.call(this, t)) && n in o ? (r("jQuery.data() always sets/gets camelCased names: " + n), + arguments.length > 2 && (o[n] = a), o[n]) : g.apply(this, arguments); + }; + var v = e.Tween.prototype.run, j = function(e) { + return e; + }; + e.Tween.prototype.run = function() { + e.easing[this.easing].length > 1 && (r("'jQuery.easing." + this.easing.toString() + "' should use only one argument"), + e.easing[this.easing] = j), v.apply(this, arguments); + }, e.fx.interval = e.fx.interval || 13, t.requestAnimationFrame && n(e.fx, "interval", e.fx.interval, "jQuery.fx.interval is deprecated"); + var Q = e.fn.load, b = e.event.add, w = e.event.fix; + e.event.props = [], e.event.fixHooks = {}, n(e.event.props, "concat", e.event.props.concat, "jQuery.event.props.concat() is deprecated and removed"), + e.event.fix = function(t) { + var n, a = t.type, o = this.fixHooks[a], i = e.event.props; + if (i.length) for (r("jQuery.event.props are deprecated and removed: " + i.join()); i.length; ) e.event.addProp(i.pop()); + if (o && !o._migrated_ && (o._migrated_ = !0, r("jQuery.event.fixHooks are deprecated and removed: " + a), + (i = o.props) && i.length)) for (;i.length; ) e.event.addProp(i.pop()); + return n = w.call(this, t), o && o.filter ? o.filter(n, t) : n; + }, e.event.add = function(e, n) { + return e === t && "load" === n && "complete" === t.document.readyState && r("jQuery(window).on('load'...) called after load event occurred"), + b.apply(this, arguments); + }, e.each([ "load", "unload", "error" ], function(t, n) { + e.fn[n] = function() { + var e = Array.prototype.slice.call(arguments, 0); + return "load" === n && "string" == typeof e[0] ? Q.apply(this, e) : (r("jQuery.fn." + n + "() is deprecated"), + e.splice(0, 0, n), arguments.length ? this.on.apply(this, e) : (this.triggerHandler.apply(this, e), + this)); + }; + }), e.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "), function(t, n) { + e.fn[n] = function(e, t) { + return r("jQuery.fn." + n + "() event shorthand is deprecated"), arguments.length > 0 ? this.on(n, null, e, t) : this.trigger(n); + }; + }), e(function() { + e(t.document).triggerHandler("ready"); + }), e.event.special.ready = { + setup: function() { + this === t.document && r("'ready' event is deprecated"); + } + }, e.fn.extend({ + bind: function(e, t, n) { + return r("jQuery.fn.bind() is deprecated"), this.on(e, null, t, n); + }, + unbind: function(e, t) { + return r("jQuery.fn.unbind() is deprecated"), this.off(e, null, t); + }, + delegate: function(e, t, n, a) { + return r("jQuery.fn.delegate() is deprecated"), this.on(t, e, n, a); + }, + undelegate: function(e, t, n) { + return r("jQuery.fn.undelegate() is deprecated"), 1 === arguments.length ? this.off(e, "**") : this.off(t, e || "**", n); + }, + hover: function(e, t) { + return r("jQuery.fn.hover() is deprecated"), this.on("mouseenter", e).on("mouseleave", t || e); + } + }); + var x = e.fn.offset; + e.fn.offset = function() { + var n, a = this[0], o = { + top: 0, + left: 0 + }; + return a && a.nodeType ? (n = (a.ownerDocument || t.document).documentElement, e.contains(n, a) ? x.apply(this, arguments) : (r("jQuery.fn.offset() requires an element connected to a document"), + o)) : (r("jQuery.fn.offset() requires a valid DOM element"), o); + }; + var k = e.param; + e.param = function(t, n) { + var a = e.ajaxSettings && e.ajaxSettings.traditional; + return void 0 === n && a && (r("jQuery.param() no longer uses jQuery.ajaxSettings.traditional"), + n = a), k.call(this, t, n); + }; + var A = e.fn.andSelf || e.fn.addBack; + e.fn.andSelf = function() { + return r("jQuery.fn.andSelf() is deprecated and removed, use jQuery.fn.addBack()"), + A.apply(this, arguments); + }; + var S = e.Deferred, q = [ [ "resolve", "done", e.Callbacks("once memory"), e.Callbacks("once memory"), "resolved" ], [ "reject", "fail", e.Callbacks("once memory"), e.Callbacks("once memory"), "rejected" ], [ "notify", "progress", e.Callbacks("memory"), e.Callbacks("memory") ] ]; + return e.Deferred = function(t) { + var n = S(), a = n.promise(); + return n.pipe = a.pipe = function() { + var t = arguments; + return r("deferred.pipe() is deprecated"), e.Deferred(function(r) { + e.each(q, function(o, i) { + var s = e.isFunction(t[o]) && t[o]; + n[i[1]](function() { + var t = s && s.apply(this, arguments); + t && e.isFunction(t.promise) ? t.promise().done(r.resolve).fail(r.reject).progress(r.notify) : r[i[0] + "With"](this === a ? r.promise() : this, s ? [ t ] : arguments); + }); + }), t = null; + }).promise(); + }, t && t.call(n, n), n; + }, e.Deferred.exceptionHook = S.exceptionHook, e; +}); \ No newline at end of file diff --git a/public/vendor/plugins/jquery/jquery.min.js b/public/vendor/plugins/jquery/jquery.min.js index e836475870..a1c07fd803 100644 --- a/public/vendor/plugins/jquery/jquery.min.js +++ b/public/vendor/plugins/jquery/jquery.min.js @@ -1,5 +1,2 @@ -/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; -}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("