Merge remote-tracking branch 'origin/master' into rebase-merge-commit
This commit is contained in:
commit
5bfe7e4467
41
.drone.yml
41
.drone.yml
|
@ -4,7 +4,7 @@ workspace:
|
||||||
|
|
||||||
clone:
|
clone:
|
||||||
git:
|
git:
|
||||||
image: plugins/git:1
|
image: plugins/git:next
|
||||||
depth: 50
|
depth: 50
|
||||||
tags: true
|
tags: true
|
||||||
|
|
||||||
|
@ -75,6 +75,7 @@ pipeline:
|
||||||
- make lint
|
- make lint
|
||||||
- make fmt-check
|
- make fmt-check
|
||||||
- make swagger-check
|
- make swagger-check
|
||||||
|
# - make swagger-validate
|
||||||
- make misspell-check
|
- make misspell-check
|
||||||
- make test-vendor
|
- make test-vendor
|
||||||
- make build
|
- make build
|
||||||
|
@ -202,7 +203,7 @@ pipeline:
|
||||||
when:
|
when:
|
||||||
event: [ push, tag ]
|
event: [ push, tag ]
|
||||||
|
|
||||||
build_docs:
|
build-docs:
|
||||||
image: webhippie/hugo:latest
|
image: webhippie/hugo:latest
|
||||||
pull: true
|
pull: true
|
||||||
commands:
|
commands:
|
||||||
|
@ -211,26 +212,12 @@ pipeline:
|
||||||
- make clean
|
- make clean
|
||||||
- make build
|
- make build
|
||||||
|
|
||||||
docker_docs:
|
publish-docs:
|
||||||
image: plugins/docker:17.05
|
image: lucap/drone-netlify:latest
|
||||||
pull: true
|
pull: true
|
||||||
secrets: [ docker_username, docker_password ]
|
secrets: [ netlify_token ]
|
||||||
repo: gitea/docs
|
site_id: d2260bae-7861-4c02-8646-8f6440b12672
|
||||||
context: docs
|
path: docs/public/
|
||||||
dockerfile: docs/Dockerfile
|
|
||||||
tags: [ '${DRONE_BRANCH##release/v}' ]
|
|
||||||
when:
|
|
||||||
event: [ push ]
|
|
||||||
branch: [ release/* ]
|
|
||||||
|
|
||||||
docker_docs:
|
|
||||||
image: plugins/docker:17.05
|
|
||||||
pull: true
|
|
||||||
secrets: [ docker_username, docker_password ]
|
|
||||||
repo: gitea/docs
|
|
||||||
context: docs
|
|
||||||
dockerfile: docs/Dockerfile
|
|
||||||
tags: [ 'latest' ]
|
|
||||||
when:
|
when:
|
||||||
event: [ push ]
|
event: [ push ]
|
||||||
branch: [ master ]
|
branch: [ master ]
|
||||||
|
@ -254,6 +241,18 @@ pipeline:
|
||||||
when:
|
when:
|
||||||
event: [ push, tag ]
|
event: [ push, tag ]
|
||||||
|
|
||||||
|
gpg-sign:
|
||||||
|
image: plugins/gpgsign:1
|
||||||
|
pull: true
|
||||||
|
secrets: [ gpgsign_key, gpgsign_passphrase ]
|
||||||
|
detach_sign: true
|
||||||
|
files:
|
||||||
|
- dist/release/*
|
||||||
|
excludes:
|
||||||
|
- dist/release/*.sha256
|
||||||
|
when:
|
||||||
|
event: [ push, tag ]
|
||||||
|
|
||||||
release:
|
release:
|
||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
pull: true
|
pull: true
|
||||||
|
|
51
BSDmakefile
Normal file
51
BSDmakefile
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# GNU makefile proxy script for BSD make
|
||||||
|
# Written and maintained by Mahmoud Al-Qudsi <mqudsi@neosmart.net>
|
||||||
|
# Copyright NeoSmart Technologies <https://neosmart.net/> 2014-2018
|
||||||
|
# Obtain updates from <https://github.com/neosmart/gmake-proxy>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
# list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
JARG =
|
||||||
|
GMAKE = "gmake"
|
||||||
|
#When gmake is called from another make instance, -w is automatically added
|
||||||
|
#which causes extraneous messages about directory changes to be emitted.
|
||||||
|
#--no-print-directory silences these messages.
|
||||||
|
GARGS = "--no-print-directory"
|
||||||
|
|
||||||
|
.if "$(.MAKE.JOBS)" != ""
|
||||||
|
JARG = -j$(.MAKE.JOBS)
|
||||||
|
.endif
|
||||||
|
|
||||||
|
#by default bmake will cd into ./obj first
|
||||||
|
.OBJDIR: ./
|
||||||
|
|
||||||
|
.PHONY: FRC
|
||||||
|
$(.TARGETS): FRC
|
||||||
|
$(GMAKE) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
|
||||||
|
|
||||||
|
.DONE .DEFAULT: .SILENT
|
||||||
|
$(GMAKE) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
|
||||||
|
|
||||||
|
.ERROR: .SILENT
|
||||||
|
if ! which $(GMAKE) > /dev/null; then \
|
||||||
|
echo "GNU Make is required!"; \
|
||||||
|
fi
|
75
CHANGELOG.md
75
CHANGELOG.md
|
@ -4,6 +4,81 @@ 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
|
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).
|
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||||
|
|
||||||
|
## [1.5.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.5.0-rc1) - 2018-07-04
|
||||||
|
* SECURITY
|
||||||
|
* Limit uploaded avatar image-size to 4096px x 3072px by default (#4353)
|
||||||
|
* Do not allow to reuse TOTP passcode (#3878)
|
||||||
|
* FEATURE
|
||||||
|
* Add cli commands to regen hooks & keys (#3979)
|
||||||
|
* Add support for FIDO U2F (#3971)
|
||||||
|
* Added user language setting (#3875)
|
||||||
|
* LDAP Public SSH Keys synchronization (#1844)
|
||||||
|
* Add topic support (#3711)
|
||||||
|
* Multiple assignees (#3705)
|
||||||
|
* Add protected branch whitelists for merging (#3689)
|
||||||
|
* Global code search support (#3664)
|
||||||
|
* Add label descriptions (#3662)
|
||||||
|
* Add issue search via API (#3612)
|
||||||
|
* Add repository setting to enable/disable health checks (#3607)
|
||||||
|
* Emoji Autocomplete (#3433)
|
||||||
|
* Implements generator cli for secrets (#3531)
|
||||||
|
* ENHANCEMENT
|
||||||
|
* Add more webhooks support and refactor webhook templates directory (#3929)
|
||||||
|
* Add new option to allow only OAuth2/OpenID user registration (#3910)
|
||||||
|
* Add option to use paged LDAP search when synchronizing users (#3895)
|
||||||
|
* Symlink icons (#1416)
|
||||||
|
* Improve release page UI (#3693)
|
||||||
|
* Add admin dashboard option to run health checks (#3606)
|
||||||
|
* Add branch link in branch list (#3576)
|
||||||
|
* Reduce sql query times in retrieveFeeds (#3547)
|
||||||
|
* Option to enable or disable swagger endpoints (#3502)
|
||||||
|
* Add missing licenses (#3497)
|
||||||
|
* Reduce repo indexer disk usage (#3452)
|
||||||
|
* Enable caching on assets and avatars (#3376)
|
||||||
|
* Add repository search ordered by stars/forks. Forks column in admin repo list (#3969)
|
||||||
|
* Add Environment Variables to Docker template (#4012)
|
||||||
|
* LFS: make HTTP auth period configurable (#4035)
|
||||||
|
* Add config path as an optionial flag when changing pass via CLI (#4184)
|
||||||
|
* Refactor User Settings sections (#3900)
|
||||||
|
* Allow square brackets in external issue patterns (#3408)
|
||||||
|
* Add Attachment API (#3478)
|
||||||
|
* Add EnableTimetracking option to app settings (#3719)
|
||||||
|
* Add config option to enable or disable log executed SQL (#3726)
|
||||||
|
* Shows total tracked time in issue and milestone list (#3341)
|
||||||
|
* TRANSLATION
|
||||||
|
* Improve English grammar and consistency (#3614)
|
||||||
|
* DEPLOYMENT
|
||||||
|
* Allow Gitea to run as different USER in Docker (#3961)
|
||||||
|
* Provide compressed release binaries (#3991)
|
||||||
|
* Sign release binaries (#4188)
|
||||||
|
|
||||||
|
## [1.4.3](https://github.com/go-gitea/gitea/releases/tag/v1.4.3) - 2018-06-26
|
||||||
|
* SECURITY
|
||||||
|
* HTML-escape plain-text READMEs (#4192) (#4214)
|
||||||
|
* Fix open redirect vulnerability on login screen (#4312) (#4312)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix broken monitoring page when running processes are shown (#4203) (#4208)
|
||||||
|
* Fix delete comment bug (#4216) (#4228)
|
||||||
|
* Delete reactions added to issues and comments when deleting repository (#4232) (#4237)
|
||||||
|
* Fix wiki URL encoding bug (#4091) (#4254)
|
||||||
|
* Fix code tab link when viewing tags (#3908) (#4263)
|
||||||
|
* Fix webhook type conflation (#4285) (#4285)
|
||||||
|
|
||||||
|
## [1.4.2](https://github.com/go-gitea/gitea/releases/tag/v1.4.2) - 2018-06-04
|
||||||
|
* BUGFIXES
|
||||||
|
* Adjust z-index for floating labels (#3939) (#3950)
|
||||||
|
* Add missing token validation on application settings page (#3976) #3978
|
||||||
|
* Webhook and hook_task clean up (#4006)
|
||||||
|
* Fix webhook bug of response info is not displayed in UI (#4023)
|
||||||
|
* Fix writer cannot read bare repo guide (#4033) (#4039)
|
||||||
|
* Don't force due date to current time (#3830) (#4057)
|
||||||
|
* Fix wiki redirects (#3919) (#4065)
|
||||||
|
* Fix attachment ENABLED (#4064) (#4066)
|
||||||
|
* Added deletion of an empty line at the end of file (#4054) (#4074)
|
||||||
|
* Use ResolveReference instead of path.Join (#4073)
|
||||||
|
* Fix #4081 Check for leading / in base before removing it (#4083)
|
||||||
|
* Respository's home page not updated after first push (#4075)
|
||||||
|
|
||||||
## [1.4.1](https://github.com/go-gitea/gitea/releases/tag/v1.4.1) - 2018-05-03
|
## [1.4.1](https://github.com/go-gitea/gitea/releases/tag/v1.4.1) - 2018-05-03
|
||||||
* BREAKING
|
* BREAKING
|
||||||
* Add "error" as reserved username (#3882) (#3886)
|
* Add "error" as reserved username (#3882) (#3886)
|
||||||
|
|
|
@ -69,7 +69,7 @@ and keep the compatibility on upgrade. To make sure you are
|
||||||
running the test suite exactly like we do, you should install
|
running the test suite exactly like we do, you should install
|
||||||
the CLI for [Drone CI](https://github.com/drone/drone), as
|
the CLI for [Drone CI](https://github.com/drone/drone), as
|
||||||
we are using the server for continous testing, following [these
|
we are using the server for continous testing, following [these
|
||||||
instructions](http://readme.drone.io/usage/getting-started-cli). After that,
|
instructions](http://docs.drone.io/cli-installation/). After that,
|
||||||
you can simply call `drone exec --local --build-event "pull_request"` within
|
you can simply call `drone exec --local --build-event "pull_request"` within
|
||||||
your working directory and it will try to run the test suite locally.
|
your working directory and it will try to run the test suite locally.
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ pull request workflow to do that. And, we also use [LGTM](http://lgtm.co)
|
||||||
to ensure every PR is reviewed by at least 2 maintainers.
|
to ensure every PR is reviewed by at least 2 maintainers.
|
||||||
|
|
||||||
Please try to make your pull request easy to review for us. And, please read
|
Please try to make your pull request easy to review for us. And, please read
|
||||||
the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#best-practices-for-faster-reviews)* guide;
|
the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews)* guide;
|
||||||
it has lots of useful tips for any project you may want to contribute.
|
it has lots of useful tips for any project you may want to contribute.
|
||||||
Some of the key points:
|
Some of the key points:
|
||||||
|
|
||||||
|
@ -201,6 +201,10 @@ an advisor has time to code review, we will gladly welcome them back
|
||||||
to the maintainers team. If a maintainer is inactive for more than 3
|
to the maintainers team. If a maintainer is inactive for more than 3
|
||||||
months and forgets to leave the maintainers team, the owners may move
|
months and forgets to leave the maintainers team, the owners may move
|
||||||
him or her from the maintainers team to the advisors team.
|
him or her from the maintainers team to the advisors team.
|
||||||
|
For security reasons, Maintainers should use 2FA for their accounts and
|
||||||
|
if possible provide gpg signed commits.
|
||||||
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
|
https://help.github.com/articles/signing-commits-with-gpg/
|
||||||
|
|
||||||
## Owners
|
## Owners
|
||||||
|
|
||||||
|
@ -211,6 +215,9 @@ be the main owner, and the other two the assistant owners. When the new
|
||||||
owners have been elected, the old owners will give up ownership to the
|
owners have been elected, the old owners will give up ownership to the
|
||||||
newly elected owners. If an owner is unable to do so, the other owners
|
newly elected owners. If an owner is unable to do so, the other owners
|
||||||
will assist in ceding ownership to the newly elected owners.
|
will assist in ceding ownership to the newly elected owners.
|
||||||
|
For security reasons, Owners or any account with write access (like a bot)
|
||||||
|
must use 2FA.
|
||||||
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
|
|
||||||
After the election, the new owners should proactively agree
|
After the election, the new owners should proactively agree
|
||||||
with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the
|
with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the
|
||||||
|
|
|
@ -58,3 +58,4 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
|
||||||
|
|
||||||
COPY docker /
|
COPY docker /
|
||||||
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
|
||||||
|
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea
|
||||||
|
|
186
Gopkg.lock
generated
186
Gopkg.lock
generated
|
@ -11,7 +11,7 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "code.gitea.io/sdk"
|
name = "code.gitea.io/sdk"
|
||||||
packages = ["gitea"]
|
packages = ["gitea"]
|
||||||
revision = "b2308e3f700875a3642a78bd3f6e5db8ef6f974d"
|
revision = "ec80752c9512cf07fc62ddc42565118183743942"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/PuerkitoBio/goquery"
|
name = "github.com/PuerkitoBio/goquery"
|
||||||
|
@ -143,17 +143,6 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "098da33fde5f9220736531b3cb26a2dec86a8367"
|
revision = "098da33fde5f9220736531b3cb26a2dec86a8367"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/coreos/etcd"
|
|
||||||
packages = ["error"]
|
|
||||||
revision = "01c303113d0a3d5a8075864321c3aedb72035bdd"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/coreos/go-etcd"
|
|
||||||
packages = ["etcd"]
|
|
||||||
revision = "003851be7bb0694fe3cc457a49529a19388ee7cf"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/couchbase/vellum"
|
name = "github.com/couchbase/vellum"
|
||||||
|
@ -294,27 +283,24 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/go-sql-driver/mysql"
|
name = "github.com/go-sql-driver/mysql"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "ce924a41eea897745442daaa1739089b0f3f561d"
|
revision = "d523deb1b23d913de5bdada721a6071e71283618"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/go-xorm/builder"
|
name = "github.com/go-xorm/builder"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738"
|
revision = "dc8bf48f58fab2b4da338ffd25191905fd741b8f"
|
||||||
|
version = "v0.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/go-xorm/core"
|
name = "github.com/go-xorm/core"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9"
|
revision = "c10e21e7e1cec20e09398f2dfae385e58c8df555"
|
||||||
|
version = "v0.6.0"
|
||||||
[[projects]]
|
|
||||||
name = "github.com/go-xorm/tidb"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "21e49190ce47a766fa741cf7edc831a30c12c6ac"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/go-xorm/xorm"
|
name = "github.com/go-xorm/xorm"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
|
revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -370,11 +356,6 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "8fb95d837f7d6db1913fecfd7bcc5333e6499596"
|
revision = "8fb95d837f7d6db1913fecfd7bcc5333e6499596"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/juju/errors"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "b2c7a7da5b2995941048f60146e67702a292e468"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/kballard/go-shellquote"
|
name = "github.com/kballard/go-shellquote"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
@ -497,134 +478,12 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "891127d8d1b52734debe1b3c3d7e747502b6c366"
|
revision = "891127d8d1b52734debe1b3c3d7e747502b6c366"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/ngaut/deadline"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "fae8f9dfd7048de16575b9d4c255278e38c28a4f"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ngaut/go-zookeeper"
|
|
||||||
packages = ["zk"]
|
|
||||||
revision = "9c3719e318c7cfd072e41eb48cb71fcaa49d5e05"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/ngaut/log"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "d2af3a61f64d093457fb23b25d20f4ce3cd551ce"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ngaut/pools"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "b7bc8c42aac787667ba45adea78233f53f548443"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ngaut/sync2"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "7a24ed77b2efb460c1468b7dc917821c66e80e55"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ngaut/tso"
|
|
||||||
packages = [
|
|
||||||
"client",
|
|
||||||
"proto",
|
|
||||||
"util"
|
|
||||||
]
|
|
||||||
revision = "118f6c141d58f1e72577ff61f43f649bf39355ee"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/ngaut/zkhelper"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "6738bdc138d469112c6687fbfcfe049ccabd6a0a"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/petar/GoLLRB"
|
|
||||||
packages = ["llrb"]
|
|
||||||
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/philhofer/fwd"
|
name = "github.com/philhofer/fwd"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "bb6d471dc95d4fe11e432687f8b70ff496cf3136"
|
revision = "bb6d471dc95d4fe11e432687f8b70ff496cf3136"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pingcap/go-hbase"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"iohelper",
|
|
||||||
"proto"
|
|
||||||
]
|
|
||||||
revision = "7a98d1fe4e9e115de8c77ae0e158c0d08732c550"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/pingcap/go-themis"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"oracle",
|
|
||||||
"oracle/oracles"
|
|
||||||
]
|
|
||||||
revision = "dbb996606c1d1fe8571fd9ac6da2254c76d2c5c9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pingcap/tidb"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"ast",
|
|
||||||
"column",
|
|
||||||
"context",
|
|
||||||
"ddl",
|
|
||||||
"domain",
|
|
||||||
"evaluator",
|
|
||||||
"executor",
|
|
||||||
"infoschema",
|
|
||||||
"inspectkv",
|
|
||||||
"kv",
|
|
||||||
"kv/memkv",
|
|
||||||
"meta",
|
|
||||||
"meta/autoid",
|
|
||||||
"model",
|
|
||||||
"mysql",
|
|
||||||
"optimizer",
|
|
||||||
"optimizer/plan",
|
|
||||||
"parser",
|
|
||||||
"parser/opcode",
|
|
||||||
"perfschema",
|
|
||||||
"privilege",
|
|
||||||
"privilege/privileges",
|
|
||||||
"sessionctx",
|
|
||||||
"sessionctx/autocommit",
|
|
||||||
"sessionctx/db",
|
|
||||||
"sessionctx/forupdate",
|
|
||||||
"sessionctx/variable",
|
|
||||||
"store/hbase",
|
|
||||||
"store/localstore",
|
|
||||||
"store/localstore/boltdb",
|
|
||||||
"store/localstore/engine",
|
|
||||||
"store/localstore/goleveldb",
|
|
||||||
"structure",
|
|
||||||
"table",
|
|
||||||
"table/tables",
|
|
||||||
"terror",
|
|
||||||
"util",
|
|
||||||
"util/bytes",
|
|
||||||
"util/charset",
|
|
||||||
"util/codec",
|
|
||||||
"util/distinct",
|
|
||||||
"util/hack",
|
|
||||||
"util/segmentmap",
|
|
||||||
"util/sqlexec",
|
|
||||||
"util/stringutil",
|
|
||||||
"util/types"
|
|
||||||
]
|
|
||||||
revision = "33197485abe227dcb254644cf5081c9a3c281669"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pmezard/go-difflib"
|
name = "github.com/pmezard/go-difflib"
|
||||||
packages = ["difflib"]
|
packages = ["difflib"]
|
||||||
|
@ -673,24 +532,6 @@
|
||||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||||
version = "v1.2.1"
|
version = "v1.2.1"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/syndtr/goleveldb"
|
|
||||||
packages = [
|
|
||||||
"leveldb",
|
|
||||||
"leveldb/cache",
|
|
||||||
"leveldb/comparer",
|
|
||||||
"leveldb/errors",
|
|
||||||
"leveldb/filter",
|
|
||||||
"leveldb/iterator",
|
|
||||||
"leveldb/journal",
|
|
||||||
"leveldb/memdb",
|
|
||||||
"leveldb/opt",
|
|
||||||
"leveldb/storage",
|
|
||||||
"leveldb/table",
|
|
||||||
"leveldb/util"
|
|
||||||
]
|
|
||||||
revision = "917f41c560270110ceb73c5b38be2a9127387071"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/tinylib/msgp"
|
name = "github.com/tinylib/msgp"
|
||||||
|
@ -703,17 +544,6 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d21a03e0b1d9fc1df59ff54e7a513655c1748b0c"
|
revision = "d21a03e0b1d9fc1df59ff54e7a513655c1748b0c"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/twinj/uuid"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "89173bcdda19db0eb88aef1e1cb1cb2505561d31"
|
|
||||||
version = "0.10.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/ugorji/go"
|
|
||||||
packages = ["codec"]
|
|
||||||
revision = "c062049c1793b01a3cc3fe786108edabbaf7756b"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/urfave/cli"
|
name = "github.com/urfave/cli"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
@ -873,6 +703,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "036b8c882671cf8d2c5e2fdbe53b1bdfbd39f7ebd7765bd50276c7c4ecf16687"
|
inputs-digest = "5ae18d543bbb8186589c003422b333097d67bb5fed8b4c294be70c012ccffc94"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
11
Gopkg.toml
11
Gopkg.toml
|
@ -30,15 +30,14 @@ ignored = ["google.golang.org/appengine*"]
|
||||||
revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
|
revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
#version = "v1.0.0"
|
|
||||||
revision = "33197485abe227dcb254644cf5081c9a3c281669"
|
|
||||||
name = "github.com/pingcap/tidb"
|
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/go-xorm/xorm"
|
name = "github.com/go-xorm/xorm"
|
||||||
#version = "0.6.5"
|
#version = "0.6.5"
|
||||||
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
|
revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/go-sql-driver/mysql"
|
||||||
|
revision = "d523deb1b23d913de5bdada721a6071e71283618"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/gorilla/mux"
|
name = "github.com/gorilla/mux"
|
||||||
|
|
|
@ -22,3 +22,5 @@ Peter Žeby <morlinest@gmail.com> (@morlinest)
|
||||||
Matti Ranta <matti@mdranta.net> (@techknowlogick)
|
Matti Ranta <matti@mdranta.net> (@techknowlogick)
|
||||||
Michael Lustfield <mtecknology@debian.org> (@MTecknology)
|
Michael Lustfield <mtecknology@debian.org> (@MTecknology)
|
||||||
Jonas Franz <info@jonasfranz.software> (@JonasFranzDEV)
|
Jonas Franz <info@jonasfranz.software> (@JonasFranzDEV)
|
||||||
|
Flynn Lufmons <fluf@warpmail.net> (@flufmonster)
|
||||||
|
Alexey Terentyev <axifnx@gmail.com> (@axifive)
|
||||||
|
|
35
Makefile
35
Makefile
|
@ -21,7 +21,19 @@ GOFMT ?= gofmt -s
|
||||||
GOFLAGS := -i -v
|
GOFLAGS := -i -v
|
||||||
EXTRA_GOFLAGS ?=
|
EXTRA_GOFLAGS ?=
|
||||||
|
|
||||||
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
|
ifneq ($(DRONE_TAG),)
|
||||||
|
VERSION ?= $(subst v,,$(DRONE_TAG))
|
||||||
|
GITEA_VERSION := $(VERSION)
|
||||||
|
else
|
||||||
|
ifneq ($(DRONE_BRANCH),)
|
||||||
|
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||||
|
else
|
||||||
|
VERSION ?= master
|
||||||
|
endif
|
||||||
|
GITEA_VERSION := $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
|
||||||
|
endif
|
||||||
|
|
||||||
|
LDFLAGS := -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
|
|
||||||
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list ./... | grep -v /vendor/))
|
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list ./... | grep -v /vendor/))
|
||||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||||
|
@ -45,15 +57,8 @@ else
|
||||||
EXECUTABLE := gitea
|
EXECUTABLE := gitea
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(DRONE_TAG),)
|
# $(call strip-suffix,filename)
|
||||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
strip-suffix = $(firstword $(subst ., ,$(1)))
|
||||||
else
|
|
||||||
ifneq ($(DRONE_BRANCH),)
|
|
||||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
|
||||||
else
|
|
||||||
VERSION ?= master
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: build
|
all: build
|
||||||
|
@ -100,6 +105,13 @@ swagger-check: generate-swagger
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
|
.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
|
||||||
|
swagger validate ./public/swagger.v1.json
|
||||||
|
|
||||||
.PHONY: errcheck
|
.PHONY: errcheck
|
||||||
errcheck:
|
errcheck:
|
||||||
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
@ -294,7 +306,7 @@ public/js/index.js: $(JAVASCRIPTS)
|
||||||
|
|
||||||
.PHONY: stylesheets-check
|
.PHONY: stylesheets-check
|
||||||
stylesheets-check: generate-stylesheets
|
stylesheets-check: generate-stylesheets
|
||||||
@diff=$$(git diff public/css/index.css); \
|
@diff=$$(git diff public/css/*); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make generate-stylesheets' and commit the result:"; \
|
echo "Please run 'make generate-stylesheets' and commit the result:"; \
|
||||||
echo "$${diff}"; \
|
echo "$${diff}"; \
|
||||||
|
@ -304,6 +316,7 @@ stylesheets-check: generate-stylesheets
|
||||||
.PHONY: generate-stylesheets
|
.PHONY: generate-stylesheets
|
||||||
generate-stylesheets:
|
generate-stylesheets:
|
||||||
node_modules/.bin/lessc --clean-css public/less/index.less public/css/index.css
|
node_modules/.bin/lessc --clean-css public/less/index.less public/css/index.css
|
||||||
|
$(foreach file, $(filter-out public/less/themes/_base.less, $(wildcard public/less/themes/*)),node_modules/.bin/lessc --clean-css public/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;)
|
||||||
|
|
||||||
.PHONY: swagger-ui
|
.PHONY: swagger-ui
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
|
|
27
README.md
27
README.md
|
@ -12,13 +12,6 @@
|
||||||
[](https://www.codetriage.com/go-gitea/gitea)
|
[](https://www.codetriage.com/go-gitea/gitea)
|
||||||
[](https://opencollective.com/gitea)
|
[](https://opencollective.com/gitea)
|
||||||
|
|
||||||
| | | |
|
|
||||||
|:---:|:---:|:---:|
|
|
||||||
||||
|
|
||||||
||||
|
|
||||||
||||
|
|
||||||
||||
|
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
The goal of this project is to make the easiest, fastest, and most
|
The goal of this project is to make the easiest, fastest, and most
|
||||||
|
@ -91,8 +84,28 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
|
||||||
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**How do you pronounce Gitea?**
|
||||||
|
|
||||||
|
Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
|
||||||
|
|
||||||
|
**Why is this not hosted on a Gitea instance?**
|
||||||
|
|
||||||
|
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
This project is licensed under the MIT License.
|
||||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) file
|
See the [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) file
|
||||||
for the full license text.
|
for the full license text.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
Looking for an overview of the interface? Check it out!
|
||||||
|
|
||||||
|
| | | |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
||||
|
||||||
|
||||
|
||||||
|
||||
|
||||||
|
||||
|
||||||
|
|
|
@ -73,6 +73,11 @@ var (
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "New password to set for user",
|
Usage: "New password to set for user",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "config, c",
|
||||||
|
Value: "custom/conf/app.ini",
|
||||||
|
Usage: "Custom configuration file path",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +128,10 @@ func runChangePassword(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.IsSet("config") {
|
||||||
|
setting.CustomConf = c.String("config")
|
||||||
|
}
|
||||||
|
|
||||||
if err := initDB(); err != nil {
|
if err := initDB(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,7 +268,7 @@ func runServ(c *cli.Context) error {
|
||||||
claims := jwt.MapClaims{
|
claims := jwt.MapClaims{
|
||||||
"repo": repo.ID,
|
"repo": repo.ID,
|
||||||
"op": lfsVerb,
|
"op": lfsVerb,
|
||||||
"exp": now.Add(5 * time.Minute).Unix(),
|
"exp": now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
|
||||||
"nbf": now.Unix(),
|
"nbf": now.Unix(),
|
||||||
}
|
}
|
||||||
if user != nil {
|
if user != nil {
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
# Default values
|
# Default values
|
||||||
|
|
||||||
NAME=gitea
|
NAME=gitea
|
||||||
GITEA_HOME=/home/git/gitea
|
GITEA_HOME=/var/lib/${NAME}
|
||||||
GITEA_PATH=${GITEA_HOME}/$NAME
|
GITEA_PATH=/usr/local/bin/${NAME}
|
||||||
GITEA_USER=git
|
GITEA_USER=git
|
||||||
SERVICENAME="Gitea - Git with a cup of tea"
|
SERVICENAME="Gitea - Git with a cup of tea"
|
||||||
LOCKFILE=/var/lock/subsys/gitea
|
LOCKFILE=/var/lock/subsys/gitea
|
||||||
|
@ -49,11 +49,11 @@ DAEMON_OPTS="--check $NAME"
|
||||||
start() {
|
start() {
|
||||||
cd ${GITEA_HOME}
|
cd ${GITEA_HOME}
|
||||||
echo -n "Starting ${SERVICENAME}: "
|
echo -n "Starting ${SERVICENAME}: "
|
||||||
daemon $DAEMON_OPTS "${GITEA_PATH} web > ${LOGFILE} 2>&1 &"
|
daemon $DAEMON_OPTS "${GITEA_PATH} web -c /etc/${NAME}/app.ini > ${LOGFILE} 2>&1 &"
|
||||||
RETVAL=$?
|
RETVAL=$?
|
||||||
echo
|
echo
|
||||||
[ $RETVAL = 0 ] && touch ${LOCKFILE}
|
[ $RETVAL = 0 ] && touch ${LOCKFILE}
|
||||||
|
|
||||||
return $RETVAL
|
return $RETVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ stop() {
|
||||||
killproc ${NAME}
|
killproc ${NAME}
|
||||||
RETVAL=$?
|
RETVAL=$?
|
||||||
echo
|
echo
|
||||||
[ $RETVAL = 0 ] && rm -f ${LOCKFILE}
|
[ $RETVAL = 0 ] && rm -f ${LOCKFILE}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
|
@ -14,17 +14,20 @@
|
||||||
# Do NOT "set -e"
|
# Do NOT "set -e"
|
||||||
|
|
||||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
|
||||||
DESC="Git with a cup of tea"
|
DESC="Gitea - Git with a cup of tea"
|
||||||
NAME=gitea
|
NAME=gitea
|
||||||
SERVICEVERBOSE=yes
|
SERVICEVERBOSE=yes
|
||||||
PIDFILE=/var/run/$NAME.pid
|
PIDFILE=/var/run/$NAME.pid
|
||||||
SCRIPTNAME=/etc/init.d/$NAME
|
SCRIPTNAME=/etc/init.d/$NAME
|
||||||
WORKINGDIR=/home/git/gitea
|
WORKINGDIR=/var/lib/$NAME
|
||||||
DAEMON=$WORKINGDIR/$NAME
|
DAEMON=/usr/local/bin/$NAME
|
||||||
DAEMON_ARGS="web"
|
DAEMON_ARGS="web -c /etc/$NAME/app.ini"
|
||||||
USER=git
|
USER=git
|
||||||
USERBIND="setcap cap_net_bind_service=+ep"
|
USERBIND=""
|
||||||
|
# If you want to bind Gitea to a port below 1024 uncomment
|
||||||
|
# the line below
|
||||||
|
#USERBIND="setcap cap_net_bind_service=+ep"
|
||||||
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/1/KILL/5}"
|
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/1/KILL/5}"
|
||||||
|
|
||||||
# Read configuration variable file if it is present
|
# Read configuration variable file if it is present
|
||||||
|
@ -36,7 +39,7 @@ STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/1/KILL/5}"
|
||||||
do_start()
|
do_start()
|
||||||
{
|
{
|
||||||
$USERBIND $DAEMON
|
$USERBIND $DAEMON
|
||||||
sh -c "USER=$USER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
|
sh -c "USER=$USER HOME=/home/$USER GITEA_WORK_DIR=$WORKINGDIR start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
|
||||||
--background --chdir $WORKINGDIR --chuid $USER \\
|
--background --chdir $WORKINGDIR --chuid $USER \\
|
||||||
--exec $DAEMON -- $DAEMON_ARGS"
|
--exec $DAEMON -- $DAEMON_ARGS"
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,9 @@ load_rc_config $name
|
||||||
|
|
||||||
: ${gitea_user:="git"}
|
: ${gitea_user:="git"}
|
||||||
: ${gitea_enable:="NO"}
|
: ${gitea_enable:="NO"}
|
||||||
: ${gitea_directory:="/home/git"}
|
: ${gitea_directory:="/var/lib/gitea"}
|
||||||
|
|
||||||
command="${gitea_directory}/gitea web"
|
command="/usr/local/bin/gitea web -c /etc/gitea/app.ini"
|
||||||
procname="$(echo $command |cut -d' ' -f1)"
|
procname="$(echo $command |cut -d' ' -f1)"
|
||||||
|
|
||||||
pidfile="${gitea_directory}/${name}.pid"
|
pidfile="${gitea_directory}/${name}.pid"
|
||||||
|
@ -33,6 +33,7 @@ gitea_start() {
|
||||||
cd ${gitea_directory}
|
cd ${gitea_directory}
|
||||||
export USER=${gitea_user}
|
export USER=${gitea_user}
|
||||||
export HOME=/usr/home/${gitea_user}
|
export HOME=/usr/home/${gitea_user}
|
||||||
|
export GITEA_WORK_DIR=${gitea_directory}
|
||||||
/usr/sbin/daemon -f -u ${gitea_user} -p ${pidfile} $command
|
/usr/sbin/daemon -f -u ${gitea_user} -p ${pidfile} $command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#!/sbin/openrc-run
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
DIR=/home/git/gitea
|
DIR=/var/lib/gitea
|
||||||
USER=git
|
USER=git
|
||||||
|
|
||||||
start_stop_daemon_args="--user ${USER} --chdir ${DIR}"
|
start_stop_daemon_args="--user ${USER} --chdir ${DIR}"
|
||||||
command="${DIR}/gitea"
|
command="/usr/local/bin/gitea"
|
||||||
command_args="web"
|
command_args="web -c /etc/gitea/app.ini"
|
||||||
command_background=yes
|
command_background=yes
|
||||||
pidfile=/var/run/gitea.pid
|
pidfile=/var/run/gitea.pid
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
#
|
#
|
||||||
# $OpenBSD$
|
# $OpenBSD$
|
||||||
|
|
||||||
daemon="/home/git/gitea/gitea"
|
daemon="/usr/local/bin/gitea"
|
||||||
daemon_user="git"
|
daemon_user="git"
|
||||||
daemon_flags="web"
|
daemon_flags="web -c /etc/gitea/app.ini"
|
||||||
|
|
||||||
gitea_directory="/home/git/gitea"
|
gitea_directory="/var/lib/gitea"
|
||||||
|
|
||||||
rc_bg=YES
|
rc_bg=YES
|
||||||
|
|
||||||
|
|
46
contrib/init/sunos/gitea.xml
Normal file
46
contrib/init/sunos/gitea.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
|
||||||
|
<service_bundle type="manifest" name="export">
|
||||||
|
<service name="gitea" type="service" version="1">
|
||||||
|
<create_default_instance enabled="false"/>
|
||||||
|
|
||||||
|
<dependency name="network" grouping="require_all" restart_on="refresh" type="service">
|
||||||
|
<service_fmri value="svc:/milestone/network:default"/>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency name="filesystem" grouping="require_all" restart_on="refresh" type="service">
|
||||||
|
<service_fmri value="svc:/system/filesystem/local"/>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<exec_method
|
||||||
|
type="method"
|
||||||
|
name="start"
|
||||||
|
exec="/opt/local/bin/gitea web"
|
||||||
|
timeout_seconds="60">
|
||||||
|
<method_context>
|
||||||
|
<method_credential user="git" group="git" />
|
||||||
|
<method_environment>
|
||||||
|
<envvar name='GITEA_WORK_DIR' value='/opt/local/share/gitea'/>
|
||||||
|
<envvar name='GITEA_CUSTOM' value='/opt/local/etc/gitea'/>
|
||||||
|
<envvar name='HOME' value='/var/db/gitea'/>
|
||||||
|
<envvar name='PATH' value='/opt/local/bin:${PATH}'/>
|
||||||
|
<envvar name='USER' value='git'/>
|
||||||
|
</method_environment>
|
||||||
|
</method_context>
|
||||||
|
</exec_method>
|
||||||
|
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
|
||||||
|
|
||||||
|
<property_group name="application" type="application"></property_group>
|
||||||
|
<property_group name="startd" type="framework">
|
||||||
|
<propval name="duration" type="astring" value="child"/>
|
||||||
|
<propval name="ignore_error" type="astring" value="core,signal"/>
|
||||||
|
</property_group>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<common_name>
|
||||||
|
<loctext xml:lang="C">A painless, self-hosted Git service</loctext>
|
||||||
|
</common_name>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</service>
|
||||||
|
</service_bundle>
|
|
@ -18,10 +18,10 @@
|
||||||
# Default values
|
# Default values
|
||||||
|
|
||||||
NAME=gitea
|
NAME=gitea
|
||||||
GITEA_HOME=/home/git/gitea
|
GITEA_HOME=/var/lib/$NAME
|
||||||
GITEA_PATH=${GITEA_HOME}/$NAME
|
GITEA_PATH=/usr/local/bin/$NAME
|
||||||
GITEA_USER=git
|
GITEA_USER=git
|
||||||
SERVICENAME="Git - with a cup of tea"
|
SERVICENAME="Gitea - Git with a cup of tea"
|
||||||
LOCKFILE=/var/lock/subsys/gitea
|
LOCKFILE=/var/lock/subsys/gitea
|
||||||
LOGPATH=${GITEA_HOME}/log
|
LOGPATH=${GITEA_HOME}/log
|
||||||
LOGFILE=${LOGPATH}/error.log
|
LOGFILE=${LOGPATH}/error.log
|
||||||
|
@ -58,7 +58,7 @@ case "$1" in
|
||||||
# return skipped as service is already running
|
# return skipped as service is already running
|
||||||
(exit 5)
|
(exit 5)
|
||||||
else
|
else
|
||||||
su - ${GITEA_USER} -c "USER=${GITEA_USER} ${GITEA_PATH} web 2>&1 >>${LOGFILE} &"
|
su - ${GITEA_USER} -c "USER=${GITEA_USER} GITEA_WORK_DIR=${GITEA_HOME} ${GITEA_PATH} web -c /etc/${NAME}/app.ini 2>&1 >>${LOGFILE} &"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remember status and be verbose
|
# Remember status and be verbose
|
||||||
|
|
|
@ -18,10 +18,10 @@ RestartSec=2s
|
||||||
Type=simple
|
Type=simple
|
||||||
User=git
|
User=git
|
||||||
Group=git
|
Group=git
|
||||||
WorkingDirectory=/home/git/gitea
|
WorkingDirectory=/var/lib/gitea/
|
||||||
ExecStart=/home/git/gitea/gitea web
|
ExecStart=/usr/local/bin/gitea web -c /etc/gitea/app.ini
|
||||||
Restart=always
|
Restart=always
|
||||||
Environment=USER=git HOME=/home/git
|
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
||||||
# If you want to bind Gitea to a port below 1024 uncomment
|
# If you want to bind Gitea to a port below 1024 uncomment
|
||||||
# the two values below
|
# the two values below
|
||||||
###
|
###
|
||||||
|
|
|
@ -67,6 +67,8 @@ EXPLORE_PAGING_NUM = 20
|
||||||
ISSUE_PAGING_NUM = 10
|
ISSUE_PAGING_NUM = 10
|
||||||
; Number of maximum commits displayed in one activity feed
|
; Number of maximum commits displayed in one activity feed
|
||||||
FEED_MAX_COMMIT_NUM = 5
|
FEED_MAX_COMMIT_NUM = 5
|
||||||
|
; Number of maximum commits displayed in commit graph.
|
||||||
|
GRAPH_MAX_COMMIT_NUM = 100
|
||||||
; Value of `theme-color` meta tag, used by Android >= 5.0
|
; Value of `theme-color` meta tag, used by Android >= 5.0
|
||||||
; An invalid color like "none" or "disable" will have the default style
|
; An invalid color like "none" or "disable" will have the default style
|
||||||
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
|
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
|
||||||
|
@ -75,6 +77,8 @@ THEME_COLOR_META_TAG = `#6cc644`
|
||||||
MAX_DISPLAY_FILE_SIZE = 8388608
|
MAX_DISPLAY_FILE_SIZE = 8388608
|
||||||
; Whether the email of the user should be shown in the Explore Users page
|
; Whether the email of the user should be shown in the Explore Users page
|
||||||
SHOW_USER_EMAIL = true
|
SHOW_USER_EMAIL = true
|
||||||
|
; Set the default theme for the Gitea install
|
||||||
|
DEFAULT_THEME = gitea
|
||||||
|
|
||||||
[ui.admin]
|
[ui.admin]
|
||||||
; Number of users that are displayed on one page
|
; Number of users that are displayed on one page
|
||||||
|
@ -189,6 +193,8 @@ LFS_START_SERVER = false
|
||||||
LFS_CONTENT_PATH = data/lfs
|
LFS_CONTENT_PATH = data/lfs
|
||||||
; LFS authentication secret, change this yourself
|
; LFS authentication secret, change this yourself
|
||||||
LFS_JWT_SECRET =
|
LFS_JWT_SECRET =
|
||||||
|
; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail.
|
||||||
|
LFS_HTTP_AUTH_EXPIRY = 20m
|
||||||
|
|
||||||
; Define allowed algorithms and their minimum key length (use -1 to disable a type)
|
; Define allowed algorithms and their minimum key length (use -1 to disable a type)
|
||||||
[ssh.minimum_key_sizes]
|
[ssh.minimum_key_sizes]
|
||||||
|
@ -299,13 +305,22 @@ ENABLE_NOTIFY_MAIL = false
|
||||||
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||||
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
||||||
; Enable captcha validation for registration
|
; Enable captcha validation for registration
|
||||||
ENABLE_CAPTCHA = true
|
ENABLE_CAPTCHA = false
|
||||||
|
; Type of captcha you want to use. Options: image, recaptcha
|
||||||
|
CAPTCHA_TYPE = image
|
||||||
|
; Enable recaptcha to use Google's recaptcha service
|
||||||
|
; Go to https://www.google.com/recaptcha/admin to sign up for a key
|
||||||
|
RECAPTCHA_SECRET =
|
||||||
|
RECAPTCHA_SITEKEY =
|
||||||
; Default value for KeepEmailPrivate
|
; Default value for KeepEmailPrivate
|
||||||
; Each new user will get the value of this setting copied into their profile
|
; Each new user will get the value of this setting copied into their profile
|
||||||
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
||||||
; Default value for AllowCreateOrganization
|
; Default value for AllowCreateOrganization
|
||||||
; Every new user will have rights set to create organizations depending on this setting
|
; Every new user will have rights set to create organizations depending on this setting
|
||||||
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
|
||||||
|
; Default value for EnableDependencies
|
||||||
|
; Repositories will use depencies by default depending on this setting
|
||||||
|
DEFAULT_ENABLE_DEPENDENCIES = true
|
||||||
; Enable Timetracking
|
; Enable Timetracking
|
||||||
ENABLE_TIMETRACKING = true
|
ENABLE_TIMETRACKING = true
|
||||||
; Default value for EnableTimetracking
|
; Default value for EnableTimetracking
|
||||||
|
@ -400,6 +415,10 @@ SESSION_LIFE_TIME = 86400
|
||||||
|
|
||||||
[picture]
|
[picture]
|
||||||
AVATAR_UPLOAD_PATH = data/avatars
|
AVATAR_UPLOAD_PATH = data/avatars
|
||||||
|
; Max Width and Height of uploaded avatars. This is to limit the amount of RAM
|
||||||
|
; used when resizing the image.
|
||||||
|
AVATAR_MAX_WIDTH = 4096
|
||||||
|
AVATAR_MAX_HEIGHT = 3072
|
||||||
; Chinese users can choose "duoshuo"
|
; Chinese users can choose "duoshuo"
|
||||||
; or a custom avatar source, like: http://cn.gravatar.com/avatar/
|
; or a custom avatar source, like: http://cn.gravatar.com/avatar/
|
||||||
GRAVATAR_SOURCE = gravatar
|
GRAVATAR_SOURCE = gravatar
|
||||||
|
@ -412,7 +431,7 @@ ENABLE_FEDERATED_AVATAR = false
|
||||||
|
|
||||||
[attachment]
|
[attachment]
|
||||||
; Whether attachments are enabled. Defaults to `true`
|
; Whether attachments are enabled. Defaults to `true`
|
||||||
ENABLE = true
|
ENABLED = true
|
||||||
; Path for attachments. Defaults to `data/attachments`
|
; Path for attachments. Defaults to `data/attachments`
|
||||||
PATH = data/attachments
|
PATH = data/attachments
|
||||||
; One or more allowed types, e.g. image/jpeg|image/png
|
; One or more allowed types, e.g. image/jpeg|image/png
|
||||||
|
@ -599,9 +618,9 @@ ko-KR = ko
|
||||||
[U2F]
|
[U2F]
|
||||||
; Two Factor authentication with security keys
|
; Two Factor authentication with security keys
|
||||||
; https://developers.yubico.com/U2F/App_ID.html
|
; https://developers.yubico.com/U2F/App_ID.html
|
||||||
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
|
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
; Comma seperated list of truisted facets
|
; Comma seperated list of truisted facets
|
||||||
TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
|
TRUSTED_FACETS = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
|
|
||||||
; Extension mapping to highlight class
|
; Extension mapping to highlight class
|
||||||
; e.g. .toml=ini
|
; e.g. .toml=ini
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
/usr/sbin/update-ca-certificates
|
|
||||||
|
|
||||||
if [ ! -d /data/git/.ssh ]; then
|
if [ ! -d /data/git/.ssh ]; then
|
||||||
mkdir -p /data/git/.ssh
|
mkdir -p /data/git/.ssh
|
||||||
chmod 700 /data/git/.ssh
|
chmod 700 /data/git/.ssh
|
||||||
|
|
|
@ -4,6 +4,9 @@ RUN_MODE = $RUN_MODE
|
||||||
[repository]
|
[repository]
|
||||||
ROOT = /data/git/repositories
|
ROOT = /data/git/repositories
|
||||||
|
|
||||||
|
[repository.local]
|
||||||
|
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
|
||||||
|
|
||||||
[repository.upload]
|
[repository.upload]
|
||||||
TEMP_PATH = /data/gitea/uploads
|
TEMP_PATH = /data/gitea/uploads
|
||||||
|
|
||||||
|
@ -14,6 +17,7 @@ HTTP_PORT = $HTTP_PORT
|
||||||
ROOT_URL = $ROOT_URL
|
ROOT_URL = $ROOT_URL
|
||||||
DISABLE_SSH = $DISABLE_SSH
|
DISABLE_SSH = $DISABLE_SSH
|
||||||
SSH_PORT = $SSH_PORT
|
SSH_PORT = $SSH_PORT
|
||||||
|
LFS_CONTENT_PATH = /data/git/lfs
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
PATH = /data/gitea/gitea.db
|
PATH = /data/gitea/gitea.db
|
||||||
|
@ -23,6 +27,9 @@ NAME = $DB_NAME
|
||||||
USER = $DB_USER
|
USER = $DB_USER
|
||||||
PASSWD = $DB_PASSWD
|
PASSWD = $DB_PASSWD
|
||||||
|
|
||||||
|
[indexer]
|
||||||
|
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
|
||||||
|
|
||||||
[session]
|
[session]
|
||||||
PROVIDER_CONFIG = /data/gitea/sessions
|
PROVIDER_CONFIG = /data/gitea/sessions
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
# build stage
|
|
||||||
FROM golang:alpine AS build-env
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
RUN go get -d -v github.com/mholt/caddy/caddy github.com/pedronasser/caddy-search github.com/simia-tech/caddy-locale
|
|
||||||
WORKDIR /go/src/github.com/mholt/caddy/caddy
|
|
||||||
|
|
||||||
RUN sed -i '/This is where other plugins get plugged in (imported)/a _ "github.com/pedronasser/caddy-search"' caddymain/run.go \
|
|
||||||
&& sed -i '/This is where other plugins get plugged in (imported)/a _ "github.com/simia-tech/caddy-locale"' caddymain/run.go \
|
|
||||||
&& go install -v . \
|
|
||||||
&& /go/bin/caddy -version
|
|
||||||
|
|
||||||
FROM alpine:edge
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
RUN apk add --no-cache wget mailcap ca-certificates
|
|
||||||
COPY --from=build-env /go/bin/caddy /usr/sbin/caddy
|
|
||||||
|
|
||||||
COPY docker/caddy.conf /etc/caddy.conf
|
|
||||||
COPY public /srv/www
|
|
||||||
|
|
||||||
CMD ["/usr/sbin/caddy", "-conf", "/etc/caddy.conf"]
|
|
|
@ -31,7 +31,7 @@ menu:
|
||||||
post: active
|
post: active
|
||||||
- name: API
|
- name: API
|
||||||
url: https://try.gitea.io/api/swagger
|
url: https://try.gitea.io/api/swagger
|
||||||
weight: 25
|
weight: 45
|
||||||
pre: plug
|
pre: plug
|
||||||
- name: Blog
|
- name: Blog
|
||||||
url: https://blog.gitea.io/
|
url: https://blog.gitea.io/
|
||||||
|
@ -79,7 +79,7 @@ languages:
|
||||||
post: active
|
post: active
|
||||||
- name: API
|
- name: API
|
||||||
url: https://try.gitea.io/api/swagger
|
url: https://try.gitea.io/api/swagger
|
||||||
weight: 25
|
weight: 45
|
||||||
pre: plug
|
pre: plug
|
||||||
- name: 博客
|
- name: 博客
|
||||||
url: https://blog.gitea.io/
|
url: https://blog.gitea.io/
|
||||||
|
@ -122,7 +122,7 @@ languages:
|
||||||
post: active
|
post: active
|
||||||
- name: API
|
- name: API
|
||||||
url: https://try.gitea.io/api/swagger
|
url: https://try.gitea.io/api/swagger
|
||||||
weight: 25
|
weight: 45
|
||||||
pre: plug
|
pre: plug
|
||||||
- name: 部落格
|
- name: 部落格
|
||||||
url: https://blog.gitea.io/
|
url: https://blog.gitea.io/
|
||||||
|
@ -165,7 +165,7 @@ languages:
|
||||||
post: active
|
post: active
|
||||||
- name: API
|
- name: API
|
||||||
url: https://try.gitea.io/api/swagger
|
url: https://try.gitea.io/api/swagger
|
||||||
weight: 25
|
weight: 45
|
||||||
pre: plug
|
pre: plug
|
||||||
- name: Blog
|
- name: Blog
|
||||||
url: https://blog.gitea.io/
|
url: https://blog.gitea.io/
|
||||||
|
@ -208,7 +208,7 @@ languages:
|
||||||
post: active
|
post: active
|
||||||
- name: API
|
- name: API
|
||||||
url: https://try.gitea.io/api/swagger
|
url: https://try.gitea.io/api/swagger
|
||||||
weight: 25
|
weight: 45
|
||||||
pre: plug
|
pre: plug
|
||||||
- name: Blog
|
- name: Blog
|
||||||
url: https://blog.gitea.io/
|
url: https://blog.gitea.io/
|
||||||
|
@ -241,17 +241,17 @@ languages:
|
||||||
menu:
|
menu:
|
||||||
page:
|
page:
|
||||||
- name: Site
|
- name: Site
|
||||||
url: /fr-fr/
|
url: https://gitea.io/en-us/
|
||||||
weight: 10
|
weight: 10
|
||||||
pre: home
|
pre: home
|
||||||
post: active
|
post: active
|
||||||
- name: Documentation
|
- name: Documentation
|
||||||
url: https://docs.gitea.io/fr-fr/
|
url: /fr-fr/
|
||||||
weight: 20
|
weight: 20
|
||||||
pre: question
|
pre: question
|
||||||
- name: API
|
- name: API
|
||||||
url: https://try.gitea.io/api/swagger
|
url: https://try.gitea.io/api/swagger
|
||||||
weight: 25
|
weight: 45
|
||||||
pre: plug
|
pre: plug
|
||||||
- name: Blog
|
- name: Blog
|
||||||
url: https://blog.gitea.io/
|
url: https://blog.gitea.io/
|
||||||
|
|
75
docs/content/doc/advanced/api-usage.en-us.md
Normal file
75
docs/content/doc/advanced/api-usage.en-us.md
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
---
|
||||||
|
date: "2018-06-24:00:00+02:00"
|
||||||
|
title: "API Usage"
|
||||||
|
slug: "api-usage"
|
||||||
|
weight: 40
|
||||||
|
toc: true
|
||||||
|
draft: false
|
||||||
|
menu:
|
||||||
|
sidebar:
|
||||||
|
parent: "advanced"
|
||||||
|
name: "API Usage"
|
||||||
|
weight: 40
|
||||||
|
identifier: "api-usage"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gitea API Usage
|
||||||
|
|
||||||
|
## Enabling/configuring API access
|
||||||
|
|
||||||
|
By default, `ENABLE_SWAGGER_ENDPOINT` is true, and
|
||||||
|
`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat
|
||||||
|
Sheet](https://docs.gitea.io/en-us/config-cheat-sheet/) for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
## Authentication via the API
|
||||||
|
|
||||||
|
Gitea supports these methods of API authentication:
|
||||||
|
|
||||||
|
- HTTP basic authentication
|
||||||
|
- `token=...` parameter in URL query string
|
||||||
|
- `access_token=...` parameter in URL query string
|
||||||
|
- `Authorization: token ...` header in HTTP headers
|
||||||
|
|
||||||
|
All of these methods accept the same apiKey token type. You can
|
||||||
|
better understand this by looking at the code -- as of this writing,
|
||||||
|
Gitea parses queries and headers to find the token in
|
||||||
|
[modules/auth/auth.go](https://github.com/go-gitea/gitea/blob/6efdcaed86565c91a3dc77631372a9cc45a58e89/modules/auth/auth.go#L47).
|
||||||
|
|
||||||
|
You can create an apiKey token via your gitea install's web interface:
|
||||||
|
`Settings | Applications | Generate New Token`.
|
||||||
|
|
||||||
|
### More on the `Authorization:` header
|
||||||
|
|
||||||
|
For historical reasons, Gitea needs the word `token` included before
|
||||||
|
the apiKey token in an authorization header, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675
|
||||||
|
```
|
||||||
|
|
||||||
|
In a `curl` command, for instance, this would look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -X POST "http://localhost:4000/api/v1/repos/test1/test1/issues" \
|
||||||
|
-H "accept: application/json" \
|
||||||
|
-H "Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675" \
|
||||||
|
-H "Content-Type: application/json" -d "{ \"body\": \"testing\", \"title\": \"test 20\"}" -i
|
||||||
|
```
|
||||||
|
|
||||||
|
As mentioned above, the token used is the same one you would use in
|
||||||
|
the `token=` string in a GET request.
|
||||||
|
|
||||||
|
## Listing your issued tokens via the API
|
||||||
|
|
||||||
|
As mentioned in
|
||||||
|
[#3842](https://github.com/go-gitea/gitea/issues/3842#issuecomment-397743346),
|
||||||
|
`/users/:name/tokens` is special and requires you to authenticate
|
||||||
|
using BasicAuth, as follows:
|
||||||
|
|
||||||
|
### Using basic authentication:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens
|
||||||
|
[{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}]
|
||||||
|
```
|
|
@ -68,6 +68,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
- `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page.
|
- `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page.
|
||||||
- `ISSUE_PAGING_NUM`: **10**: Number of issues that are shown in one page (for all pages that list issues).
|
- `ISSUE_PAGING_NUM`: **10**: Number of issues that are shown in one page (for all pages that list issues).
|
||||||
- `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed.
|
- `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed.
|
||||||
|
- `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph.
|
||||||
|
- `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install.
|
||||||
|
|
||||||
### UI - Admin (`ui.admin`)
|
### UI - Admin (`ui.admin`)
|
||||||
|
|
||||||
|
@ -115,6 +117,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
- `LFS_START_SERVER`: **false**: Enables git-lfs support.
|
- `LFS_START_SERVER`: **false**: Enables git-lfs support.
|
||||||
- `LFS_CONTENT_PATH`: **./data/lfs**: Where to store LFS files.
|
- `LFS_CONTENT_PATH`: **./data/lfs**: Where to store LFS files.
|
||||||
- `LFS_JWT_SECRET`: **\<empty\>**: LFS authentication secret, change this a unique string.
|
- `LFS_JWT_SECRET`: **\<empty\>**: LFS authentication secret, change this a unique string.
|
||||||
|
- `LFS_HTTP_AUTH_EXPIRY`: **20m**: LFS authentication validity period in time.Duration, pushes taking longer than this may fail.
|
||||||
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, redirects http requests
|
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, redirects http requests
|
||||||
on another (https) port.
|
on another (https) port.
|
||||||
- `PORT_TO_REDIRECT`: **80**: Port used when `REDIRECT_OTHER_PORT` is true.
|
- `PORT_TO_REDIRECT`: **80**: Port used when `REDIRECT_OTHER_PORT` is true.
|
||||||
|
@ -176,7 +179,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication.
|
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication.
|
||||||
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration
|
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration
|
||||||
for reverse authentication.
|
for reverse authentication.
|
||||||
- `ENABLE_CAPTCHA`: **true**: Enable this to use captcha validation for registration.
|
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
|
||||||
|
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
|
||||||
|
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha
|
||||||
|
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha
|
||||||
|
- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
|
||||||
|
|
||||||
## Webhook (`webhook`)
|
## Webhook (`webhook`)
|
||||||
|
|
||||||
|
@ -278,6 +285,13 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
|
- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
|
||||||
- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`.
|
- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`.
|
||||||
|
|
||||||
|
## Git - Timeout settings (`git.timeout`)
|
||||||
|
- `MIGRATE`: **600**: Migrate external repositories timeout seconds.
|
||||||
|
- `MIRROR`: **300**: Mirror external repositories timeout seconds.
|
||||||
|
- `CLONE`: **300**: Git clone from internal repositories timeout seconds.
|
||||||
|
- `PULL`: **300**: Git pull from internal repositories timeout seconds.
|
||||||
|
- `GC`: **60**: Git repository GC timeout seconds.
|
||||||
|
|
||||||
## API (`api`)
|
## API (`api`)
|
||||||
|
|
||||||
- `ENABLE_SWAGGER_ENDPOINT`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
|
- `ENABLE_SWAGGER_ENDPOINT`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
|
||||||
|
|
|
@ -187,6 +187,13 @@ menu:
|
||||||
- `MAX_GIT_DIFF_FILES`: 比较视图中的最大现实文件数目。
|
- `MAX_GIT_DIFF_FILES`: 比较视图中的最大现实文件数目。
|
||||||
- `GC_ARGS`: 执行 `git gc` 命令的参数, 比如: `--aggressive --auto`。
|
- `GC_ARGS`: 执行 `git gc` 命令的参数, 比如: `--aggressive --auto`。
|
||||||
|
|
||||||
|
## Git - 超时设置 (`git.timeout`)
|
||||||
|
- `MIGRATE`: **600**: 迁移外部仓库时的超时时间,单位秒
|
||||||
|
- `MIRROR`: **300**: 镜像外部仓库的超时时间,单位秒
|
||||||
|
- `CLONE`: **300**: 内部仓库间克隆的超时时间,单位秒
|
||||||
|
- `PULL`: **300**: 内部仓库间拉取的超时时间,单位秒
|
||||||
|
- `GC`: **60**: git仓库GC的超时时间,单位秒
|
||||||
|
|
||||||
## markup (`markup`)
|
## markup (`markup`)
|
||||||
|
|
||||||
外部渲染工具支持,你可以用你熟悉的文档渲染工具. 比如一下将新增一个名字为 `asciidoc` 的渲染工具which is followed `markup.` ini section. And there are some config items below.
|
外部渲染工具支持,你可以用你熟悉的文档渲染工具. 比如一下将新增一个名字为 `asciidoc` 的渲染工具which is followed `markup.` ini section. And there are some config items below.
|
||||||
|
|
|
@ -91,3 +91,7 @@ Apart from `extra_links.tmpl` and `extra_tabs.tmpl`, there are other useful temp
|
||||||
## Customizing gitignores, labels, licenses, locales, and readmes.
|
## Customizing gitignores, labels, licenses, locales, and readmes.
|
||||||
|
|
||||||
Place custom files in corresponding sub-folder under `custom/options`.
|
Place custom files in corresponding sub-folder under `custom/options`.
|
||||||
|
|
||||||
|
## Customizing the look of Gitea
|
||||||
|
|
||||||
|
Gitea has two built-in themes, the default theme `gitea`, and a dark theme `arc-green`. To change the look of your Gitea install change the value of `DEFAULT_THEME` in the [ui](https://docs.gitea.io/en-us/config-cheat-sheet/#ui-ui) section of `app.ini` to another one of the available options.
|
||||||
|
|
|
@ -27,594 +27,93 @@ _Symbols used in table:_
|
||||||
|
|
||||||
* _✘ - unsupported_
|
* _✘ - unsupported_
|
||||||
|
|
||||||
<table border="1" cellpadding="4">
|
#### General Features
|
||||||
<thead>
|
|
||||||
<tr>
|
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|
||||||
<td>Feature</td>
|
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
|
||||||
<td>Gitea</td>
|
| Open source and free | ✓ | ✓ | ✘| ✓ | ✘ | ✘ | ✓ |
|
||||||
<td>Gogs</td>
|
| Low resource usage (RAM/CPU) | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ |
|
||||||
<td>GitHub EE</td>
|
| Multiple database support | ✓ | ✓ | ✘ | ⁄ | ⁄ | ✓ | ✓ |
|
||||||
<td>GitLab CE</td>
|
| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ |
|
||||||
<td>GitLab EE</td>
|
| Easy upgrade process | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
|
||||||
<td>BitBucket</td>
|
| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
</tr>
|
| Static Git-powered pages | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
</thead>
|
| Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<tbody>
|
| Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<tr>
|
| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
|
||||||
<td>Open source and free</td>
|
| Built-in Container Registry | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>✘</td>
|
| Built-in CI/CD | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Subgroups: groups within groups | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ |
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
#### Code management
|
||||||
</tr>
|
|
||||||
<tr>
|
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|
||||||
<td>Issue tracker</td>
|
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
|
||||||
<td>✓</td>
|
| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Global code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ⁄ | ✓ |
|
||||||
<td>✓</td>
|
| Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
</tr>
|
| Verified Committer | ✘ | ✘ | ? | ✓ | ✓ | ✓ | ✘ |
|
||||||
<tr>
|
| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>Pull/Merge requests</td>
|
| Reject unsigned commits | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
|
||||||
<td>✓</td>
|
| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Create new branches | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Web code editor | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Commit graph | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
#### Issue Tracker
|
||||||
<tr>
|
|
||||||
<td>Squash merging</td>
|
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|
||||||
<td>✓</td>
|
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
|
||||||
<td>✘</td>
|
| Issue tracker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>✓</td>
|
| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✘</td>
|
| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Multiple assignees for issues | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
</tr>
|
| Related issues | ✘ | ✘ | ⁄ | ✘ | ✓ | ✘ | ✘ |
|
||||||
<tr>
|
| Confidential issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>Rebase merging</td>
|
| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Lock Discussion | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Batch issue handling | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| Issue Boards | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✘</td>
|
| Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>⁄</td>
|
| Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>✘</td>
|
| Global issue search | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
</tr>
|
|
||||||
<tr>
|
#### Pull/Merge requests
|
||||||
<td>Pull/Merge request inline comments</td>
|
|
||||||
<td>✘</td>
|
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|
||||||
<td>✘</td>
|
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
|
||||||
<td>✓</td>
|
| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Squash merging | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Rebase merging | ✓ | ✓ | ✓ | ✘ | ⁄ | ✘ | ✓ |
|
||||||
<td>✓</td>
|
| Pull/Merge request inline comments | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
</tr>
|
| Pull/Merge request approval | ✘ | ✘ | ⁄ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<tr>
|
| Merge conflict resolution | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>Pull/Merge request approval</td>
|
| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | ⁄ | ✓ | ✓ | ✓ |
|
||||||
<td>✘</td>
|
| Revert specific commits or a merge request | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>✘</td>
|
| Pull/Merge requests templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>⁄</td>
|
| Cherry-picking changes | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
#### 3rd-party integrations
|
||||||
</tr>
|
|
||||||
<tr>
|
| Feature | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE |
|
||||||
<td>Merge conflict resolution</td>
|
|---------|-------|------|-----------|-----------|-----------|-----------|--------------|
|
||||||
<td>✘</td>
|
| Webhook support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✘</td>
|
| Custom Git Hooks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| AD / LDAP integration | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| Multiple LDAP / AD server support | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| LDAP user synchronization | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>✓</td>
|
| OpenId Connect support | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ |
|
||||||
</tr>
|
| OAuth 2.0 integration (external authorization) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ |
|
||||||
<tr>
|
| Act as OAuth 2.0 provider | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>Restrict push and merge access to certain users</td>
|
| Two factor authentication (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
|
||||||
<td>✓</td>
|
| Mattermost/Slack integration | ✓ | ✓ | ⁄ | ✓ | ✓ | ⁄ | ✓ |
|
||||||
<td>✘</td>
|
| Discord integration | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ |
|
||||||
<td>✓</td>
|
| External CI/CD status display | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||||
<td>⁄</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Markdown support</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Issues and pull/merge requests templates</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Revert specific commits or a merge request</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Labels</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Time tracking</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Multiple assignees for issues</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Related issues</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Confidential issues</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Comment reactions</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lock Discussion</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Batch issue handling</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Issue Boards</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Create new branches from issues</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Commit graph</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Web code editor</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Branch manager</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Create new branches</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Repository topics</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Repository code search</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Global code search</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Issue search</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Global issue search</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Git LFS 2.0</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Integrated Git-powered wiki</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Static Git-powered pages</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Group Milestones</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Granular user roles (Code, Issues, Wiki etc)</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Cherry-picking changes</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>GPG Signed Commits</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Reject unsigned commits</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Verified Committer</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>?</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Subgroups: groups within groups</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Custom Git Hooks</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Repository Activity page</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Deploy Tokens</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Repository Tokens with write rights</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Easy upgrade process</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Built-in Container Registry</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>External git mirroring</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>AD / LDAP integration</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Multiple LDAP / AD server support</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>LDAP user synchronization</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OpenId Connect support</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>?</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OAuth 2.0 integration (external authorization)</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>?</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Act as OAuth 2.0 provider</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Two factor authentication (2FA)</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>FIDO U2F (2FA)</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Webhook support</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Mattermost/Slack integration</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Discord integration</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Built-in CI/CD</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>External CI/CD status display</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Multiple database support</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
<td>⁄</td>
|
|
||||||
<td>✓</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Multiple OS support</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Low resource usage (RAM/CPU)</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✓</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
<td>✘</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
|
@ -20,11 +20,19 @@ embedded assets. This can be different for older releases. Choose the file match
|
||||||
the destination platform from the [downloads page](https://dl.gitea.io/gitea), copy
|
the destination platform from the [downloads page](https://dl.gitea.io/gitea), copy
|
||||||
the URL and replace the URL within the commands below:
|
the URL and replace the URL within the commands below:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
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/1.4.3/gitea-1.4.3-linux-amd64
|
||||||
chmod +x gitea
|
chmod +x gitea
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Verify GPG signature
|
||||||
|
Gitea signs all binaries with a [GPG key](https://pgp.mit.edu/pks/lookup?op=vindex&fingerprint=on&search=0x2D9AE806EC1592E2) to prevent against unwanted modification of binaries. To validate the binary download the signature file which ends in `.asc` for the binary you downloaded and use the gpg command line tool.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gpg --keyserver pgp.mit.edu --recv 0x2D9AE806EC1592E2
|
||||||
|
gpg --verify gitea-1.5.0-linux-amd64.asc gitea-1.5.0-linux-amd64
|
||||||
|
```
|
||||||
|
|
||||||
## Test
|
## Test
|
||||||
|
|
||||||
After getting a binary, it can be tested with `./gitea web` or moved to a permanent
|
After getting a binary, it can be tested with `./gitea web` or moved to a permanent
|
||||||
|
@ -34,6 +42,54 @@ location. When launched manually, Gitea can be killed using `Ctrl+C`.
|
||||||
./gitea web
|
./gitea web
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Recommended server configuration
|
||||||
|
|
||||||
|
### Prepare environment
|
||||||
|
|
||||||
|
Check that git is installed on the server, if it is not install it first.
|
||||||
|
```sh
|
||||||
|
git --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Create user to run gitea (ex. `git`)
|
||||||
|
```sh
|
||||||
|
adduser \
|
||||||
|
--system \
|
||||||
|
--shell /bin/bash \
|
||||||
|
--gecos 'Git Version Control' \
|
||||||
|
--group \
|
||||||
|
--disabled-password \
|
||||||
|
--home /home/git \
|
||||||
|
git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create required directory structure
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p /var/lib/gitea/{custom,data,indexers,public,log}
|
||||||
|
chown git:git /var/lib/gitea/{data,indexers,log}
|
||||||
|
chmod 750 /var/lib/gitea/{data,indexers,log}
|
||||||
|
mkdir /etc/gitea
|
||||||
|
chown root:git /etc/gitea
|
||||||
|
chmod 770 /etc/gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE:** `/etc/gitea` is temporary set with write rights for user `git` so that Web installer could write configuration file. After installation is done it is recommended to set rights to read-only using:
|
||||||
|
```
|
||||||
|
chmod 750 /etc/gitea
|
||||||
|
chmod 644 /etc/gitea/app.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
### Copy gitea binary to global location
|
||||||
|
|
||||||
|
```
|
||||||
|
cp gitea /usr/local/bin/gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create service file to start gitea automatically
|
||||||
|
|
||||||
|
See how to create [Linux service]({{< relref "run-as-service-in-ubuntu.en-us.md" >}})
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Old glibc versions
|
### Old glibc versions
|
||||||
|
|
|
@ -17,16 +17,13 @@ menu:
|
||||||
|
|
||||||
## Debian
|
## Debian
|
||||||
|
|
||||||
The only distribution that has any "official" package of Gitea is Debian. This is currently
|
Although there is a package of Gitea in Debian's [contrib](https://wiki.debian.org/SourcesList),
|
||||||
in Debian's [contrib](https://wiki.debian.org/SourcesList). This is (currently) only available
|
it is not supported directly by us.
|
||||||
in Debian testing and unstable (but should be installable/functional on stable).
|
|
||||||
|
|
||||||
- Edit /etc/apt/sourced.list
|
Unfortunately the package is not maintained anymore and broken because of missing sources.
|
||||||
- Add "contrib" to "deb http://deb.debian.org/debian unstable main contrib"
|
Please follow the [deployment from binary]({{< relref "from-binary.en-us.md" >}}) guide instead.
|
||||||
- apt-get update
|
|
||||||
- apt-get install gitea
|
|
||||||
|
|
||||||
For other distributions, see the [deployment from binary]({{< relref "from-binary.en-us.md" >}}) guide.
|
Should the packages get updated and fixed, we will provide up-to-date installation instructions here.
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
|
|
|
@ -231,7 +231,7 @@ You can configure some of Gitea's settings via environment variables:
|
||||||
|
|
||||||
* `APP_NAME`: **"Gitea: Git with a cup of tea"**: Application name, used in the page title.
|
* `APP_NAME`: **"Gitea: Git with a cup of tea"**: Application name, used in the page title.
|
||||||
* `RUN_MODE`: **dev**: For performance and other purposes, change this to `prod` when deployed to a production environment.
|
* `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 displayed clone UR in Gitea's UI.
|
* `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_PORT`: **22**: SSH port displayed in clone URL.
|
||||||
* `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
|
* `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
|
||||||
* `HTTP_PORT`: **3000**: HTTP listen port.
|
* `HTTP_PORT`: **3000**: HTTP listen port.
|
||||||
|
@ -245,6 +245,8 @@ You can configure some of Gitea's settings via environment variables:
|
||||||
* `SECRET_KEY`: **""**: Global secret key. This should be changed. If this has a value and `INSTALL_LOCK` is empty, `INSTALL_LOCK` will automatically set to `true`.
|
* `SECRET_KEY`: **""**: Global secret key. This should be changed. If this has a value and `INSTALL_LOCK` is empty, `INSTALL_LOCK` will automatically set to `true`.
|
||||||
* `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users.
|
* `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users.
|
||||||
* `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
|
* `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
|
||||||
|
* `USER_UID`: **1000**: The UID (Unix user ID) of the user that runs Gitea within the container. Match this to the UID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
|
||||||
|
* `USER_GID`: **1000**: The GID (Unix group ID) of the user that runs Gitea within the container. Match this to the GID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
|
||||||
|
|
||||||
# Customization
|
# Customization
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ file can be unpacked and used to restore an instance.
|
||||||
|
|
||||||
## Backup Command (`dump`)
|
## Backup Command (`dump`)
|
||||||
|
|
||||||
Switch to the user running gitea: `su git`. Run `./gitea dump` in the gitea installation
|
Switch to the user running gitea: `su git`. Run `./gitea dump -c /path/to/app.ini` in the gitea installation
|
||||||
directory. There should be some output similar to the following:
|
directory. There should be some output similar to the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -34,7 +34,8 @@ directory. There should be some output similar to the following:
|
||||||
|
|
||||||
Inside the `gitea-dump-1482906742.zip` file, will be the following:
|
Inside the `gitea-dump-1482906742.zip` file, will be the following:
|
||||||
|
|
||||||
* `custom/conf/app.ini` - Server config.
|
* `custom` - All config or customerize files in `custom/`.
|
||||||
|
* `data` - Data directory in <GITEA_WORK_DIR>, except sessions if you are using file session. This directory includes `attachments`, `avatars`, `lfs`, `indexers`, sqlite file if you are using sqlite.
|
||||||
* `gitea-db.sql` - SQL dump of database
|
* `gitea-db.sql` - SQL dump of database
|
||||||
* `gitea-repo.zip` - Complete copy of the repository directory.
|
* `gitea-repo.zip` - Complete copy of the repository directory.
|
||||||
* `log/` - Various logs. They are not needed for a recovery or migration.
|
* `log/` - Various logs. They are not needed for a recovery or migration.
|
||||||
|
|
60
docs/content/doc/usage/backup-and-restore.zh-cn.md
Normal file
60
docs/content/doc/usage/backup-and-restore.zh-cn.md
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
date: "2018-06-06T09:33:00+08:00"
|
||||||
|
title: "使用: 备份与恢复"
|
||||||
|
slug: "backup-and-restore"
|
||||||
|
weight: 11
|
||||||
|
toc: true
|
||||||
|
draft: false
|
||||||
|
menu:
|
||||||
|
sidebar:
|
||||||
|
parent: "usage"
|
||||||
|
name: "备份与恢复"
|
||||||
|
weight: 11
|
||||||
|
identifier: "backup-and-restore"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 备份与恢复
|
||||||
|
|
||||||
|
Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一个zip压缩文件。该压缩文件可以被用来进行数据恢复。
|
||||||
|
|
||||||
|
## 备份命令 (`dump`)
|
||||||
|
|
||||||
|
先转到git用户的权限: `su git`. 再Gitea目录运行 `./gitea dump`。一般会显示类似如下的输出:
|
||||||
|
|
||||||
|
```
|
||||||
|
2016/12/27 22:32:09 Creating tmp work dir: /tmp/gitea-dump-417443001
|
||||||
|
2016/12/27 22:32:09 Dumping local repositories.../home/git/gitea-repositories
|
||||||
|
2016/12/27 22:32:22 Dumping database...
|
||||||
|
2016/12/27 22:32:22 Packing dump files...
|
||||||
|
2016/12/27 22:32:34 Removing tmp work dir: /tmp/gitea-dump-417443001
|
||||||
|
2016/12/27 22:32:34 Finish dumping in file gitea-dump-1482906742.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
最后生成的 `gitea-dump-1482906742.zip` 文件将会包含如下内容:
|
||||||
|
|
||||||
|
* `custom` - 所有保存在 `custom/` 目录下的配置和自定义的文件。
|
||||||
|
* `data` - 数据目录下的所有内容不包含使用文件session的文件。该目录包含 `attachments`, `avatars`, `lfs`, `indexers`, 如果使用sqlite 还会包含 sqlite 数据库文件。
|
||||||
|
* `gitea-db.sql` - 数据库dump出来的 SQL。
|
||||||
|
* `gitea-repo.zip` - Git仓库压缩文件。
|
||||||
|
* `log/` - Logs文件,如果用作迁移不是必须的。
|
||||||
|
|
||||||
|
中间备份文件将会在临时目录进行创建,如果您要重新指定临时目录,可以用 `--tempdir` 参数,或者用 `TMPDIR` 环境变量。
|
||||||
|
|
||||||
|
## Restore Command (`restore`)
|
||||||
|
|
||||||
|
当前还没有恢复命令,恢复需要人工进行。主要是把文件和数据库进行恢复。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
apt-get install gitea
|
||||||
|
unzip gitea-dump-1482906742.zip
|
||||||
|
cd gitea-dump-1482906742
|
||||||
|
mv custom/conf/app.ini /etc/gitea/conf/app.ini
|
||||||
|
unzip gitea-repo.zip
|
||||||
|
mv gitea-repo/* /var/lib/gitea/repositories/
|
||||||
|
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea/repositories/
|
||||||
|
mysql -u$USER -p$PASS $DATABASE <gitea-db.sql
|
||||||
|
# or sqlite3 $DATABASE_PATH <gitea-db.sql
|
||||||
|
service gitea restart
|
||||||
|
```
|
|
@ -62,6 +62,7 @@ Admin operations:
|
||||||
- Options:
|
- Options:
|
||||||
- `--username value`, `-u value`: Username. Required.
|
- `--username value`, `-u value`: Username. Required.
|
||||||
- `--password value`, `-p value`: New password. Required.
|
- `--password value`, `-p value`: New password. Required.
|
||||||
|
- `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini).
|
||||||
- Examples:
|
- Examples:
|
||||||
- `gitea admin change-password --username myname --password asecurepassword`
|
- `gitea admin change-password --username myname --password asecurepassword`
|
||||||
- `regenerate`
|
- `regenerate`
|
||||||
|
|
46
docs/content/doc/usage/https-support.md
Normal file
46
docs/content/doc/usage/https-support.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
date: "2018-06-02T11:00:00+02:00"
|
||||||
|
title: "Usage: HTTPS setup"
|
||||||
|
slug: "https-setup"
|
||||||
|
weight: 12
|
||||||
|
toc: true
|
||||||
|
draft: false
|
||||||
|
menu:
|
||||||
|
sidebar:
|
||||||
|
parent: "usage"
|
||||||
|
name: "HTTPS setup"
|
||||||
|
weight: 12
|
||||||
|
identifier: "https-setup"
|
||||||
|
---
|
||||||
|
|
||||||
|
# HTTPS setup to encrypt connections to Gitea
|
||||||
|
|
||||||
|
## Using built-in server
|
||||||
|
|
||||||
|
Before you enable HTTPS make sure that you have valid SSL/TLS certificates.
|
||||||
|
You could use self-generated certificates for evaluation and testing. Please run `gitea cert --host [HOST]` to generate a self signed certificate.
|
||||||
|
|
||||||
|
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/`
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
CERT_FILE = cert.pem
|
||||||
|
KEY_FILE = key.pem
|
||||||
|
```
|
||||||
|
To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server).
|
||||||
|
|
||||||
|
## Using reverse proxy
|
||||||
|
|
||||||
|
Setup up your reverse proxy like shown in the [reverse proxy guide](../reverse-proxies).
|
||||||
|
|
||||||
|
After that, enable HTTPS by following one of these guides:
|
||||||
|
|
||||||
|
* [nginx](https://nginx.org/en/docs/http/configuring_https_servers.html)
|
||||||
|
* [apache2/httpd](https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html)
|
||||||
|
* [caddy](https://caddyserver.com/docs/tls)
|
||||||
|
|
||||||
|
Note: You connection between your reverse proxy and gitea might be unencrypted. To encrypt it too follow the [built-in server guide](#using-built-in-server) and change
|
||||||
|
the proxy url to `https://[URL]`.
|
|
@ -1,44 +0,0 @@
|
||||||
:80 {
|
|
||||||
root /srv/www
|
|
||||||
|
|
||||||
locale en-US zh-CN zh-TW pt-BR nl-NL fr-FR {
|
|
||||||
detect header
|
|
||||||
}
|
|
||||||
|
|
||||||
redir 301 {
|
|
||||||
if {path} match ^/$
|
|
||||||
/ /{>Detected-Locale}/
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite /en-US/ {
|
|
||||||
regexp (.*)
|
|
||||||
to /en-us/{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite /zh-CN/ {
|
|
||||||
regexp (.*)
|
|
||||||
to /zh-cn/{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite /zh-TW/ {
|
|
||||||
regexp (.*)
|
|
||||||
to /zh-tw/{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite /pt-BR/ {
|
|
||||||
regexp (.*)
|
|
||||||
to /pt-br/{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite /nl-NL/ {
|
|
||||||
regexp (.*)
|
|
||||||
to /nl-nl/{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite /fr-FR/ {
|
|
||||||
regexp (.*)
|
|
||||||
to /fr-fr/{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
header / Vary "Accept-Language"
|
|
||||||
}
|
|
0
docs/static/.gitkeep
vendored
0
docs/static/.gitkeep
vendored
6
docs/static/_headers
vendored
Normal file
6
docs/static/_headers
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/*
|
||||||
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' data: https://cdnjs.cloudflare.com https://fonts.gstatic.com
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
X-Xss-Protection: 1; mode=block
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
8
docs/static/_redirects
vendored
Normal file
8
docs/static/_redirects
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
https://gitea-docs.netlify.com/* https://docs.gitea.io/:splat 302!
|
||||||
|
|
||||||
|
/ /fr-fr/ 302! Language=fr
|
||||||
|
/ /nl-nl/ 302! Language=nl
|
||||||
|
/ /pt-br/ 302! Language=pt-br
|
||||||
|
/ /zh-cn/ 302! Language=zh-cn
|
||||||
|
/ /zh-tw/ 302! Language=zh-tw
|
||||||
|
/ /en-us/ 302!
|
|
@ -13,7 +13,7 @@ Make sure to perform a clean build before running tests:
|
||||||
|
|
||||||
## Run all tests via local drone
|
## Run all tests via local drone
|
||||||
```
|
```
|
||||||
drone exec --local --build.event "pull_request"
|
drone exec --local --build-event "pull_request"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run sqlite integrations tests
|
## Run sqlite integrations tests
|
||||||
|
|
|
@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
|
||||||
expectedResults
|
expectedResults
|
||||||
}{
|
}{
|
||||||
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
|
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
|
||||||
nil: {count: 15},
|
nil: {count: 16},
|
||||||
user: {count: 15},
|
user: {count: 16},
|
||||||
user2: {count: 15}},
|
user2: {count: 16}},
|
||||||
},
|
},
|
||||||
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
|
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
|
||||||
nil: {count: 10},
|
nil: {count: 10},
|
||||||
|
@ -235,3 +235,53 @@ func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
|
||||||
req := NewRequestf(t, "GET", "/api/v1/repositories/2")
|
req := NewRequestf(t, "GET", "/api/v1/repositories/2")
|
||||||
sess.MakeRequest(t, req, http.StatusNotFound)
|
sess.MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIRepoMigrate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
ctxUserID, userID int64
|
||||||
|
cloneURL, repoName string
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{ctxUserID: 1, userID: 2, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-admin", expectedStatus: http.StatusCreated},
|
||||||
|
{ctxUserID: 2, userID: 2, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-own", expectedStatus: http.StatusCreated},
|
||||||
|
{ctxUserID: 2, userID: 1, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-bad", expectedStatus: http.StatusForbidden},
|
||||||
|
{ctxUserID: 2, userID: 3, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-org", expectedStatus: http.StatusCreated},
|
||||||
|
{ctxUserID: 2, userID: 6, cloneURL: "https://github.com/go-gitea/git.git", repoName: "git-bad-org", expectedStatus: http.StatusForbidden},
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareTestEnv(t)
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User)
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &api.MigrateRepoOption{
|
||||||
|
CloneAddr: testCase.cloneURL,
|
||||||
|
UID: int(testCase.userID),
|
||||||
|
RepoName: testCase.repoName,
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, testCase.expectedStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIOrgRepoCreate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
ctxUserID int64
|
||||||
|
orgName, repoName string
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{ctxUserID: 1, orgName: "user3", repoName: "repo-admin", expectedStatus: http.StatusCreated},
|
||||||
|
{ctxUserID: 2, orgName: "user3", repoName: "repo-own", expectedStatus: http.StatusCreated},
|
||||||
|
{ctxUserID: 2, orgName: "user6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden},
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareTestEnv(t)
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User)
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{
|
||||||
|
Name: testCase.repoName,
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, testCase.expectedStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
50
integrations/api_token_test.go
Normal file
50
integrations/api_token_test.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
api "code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAPICreateAndDeleteToken tests that token that was just created can be deleted
|
||||||
|
func TestAPICreateAndDeleteToken(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{
|
||||||
|
"name": "test-key-1",
|
||||||
|
})
|
||||||
|
req = AddBasicAuthHeader(req, user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
var newAccessToken api.AccessToken
|
||||||
|
DecodeJSON(t, resp, &newAccessToken)
|
||||||
|
models.AssertExistsAndLoadBean(t, &models.AccessToken{
|
||||||
|
ID: newAccessToken.ID,
|
||||||
|
Name: newAccessToken.Name,
|
||||||
|
Sha1: newAccessToken.Sha1,
|
||||||
|
UID: user.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID)
|
||||||
|
req = AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAPIDeleteMissingToken ensures that error is thrown when token not found
|
||||||
|
func TestAPIDeleteMissingToken(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
|
||||||
|
|
||||||
|
req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", models.NonexistentID)
|
||||||
|
req = AddBasicAuthHeader(req, user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
}
|
|
@ -256,6 +256,11 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *ht
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddBasicAuthHeader(request *http.Request, username string) *http.Request {
|
||||||
|
request.SetBasicAuth(username, userPassword)
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
const NoExpectedStatus = -1
|
const NoExpectedStatus = -1
|
||||||
|
|
||||||
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {
|
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {
|
||||||
|
|
|
@ -68,3 +68,25 @@ func TestSettingShowUserEmailProfile(t *testing.T) {
|
||||||
|
|
||||||
setting.UI.ShowUserEmail = showUserEmail
|
setting.UI.ShowUserEmail = showUserEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSettingLandingPage(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
|
||||||
|
landingPage := setting.LandingPageURL
|
||||||
|
|
||||||
|
setting.LandingPageURL = setting.LandingPageHome
|
||||||
|
req := NewRequest(t, "GET", "/")
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
setting.LandingPageURL = setting.LandingPageExplore
|
||||||
|
req = NewRequest(t, "GET", "/")
|
||||||
|
resp := MakeRequest(t, req, http.StatusFound)
|
||||||
|
assert.Equal(t, "/explore", resp.Header().Get("Location"))
|
||||||
|
|
||||||
|
setting.LandingPageURL = setting.LandingPageOrganizations
|
||||||
|
req = NewRequest(t, "GET", "/")
|
||||||
|
resp = MakeRequest(t, req, http.StatusFound)
|
||||||
|
assert.Equal(t, "/explore/organizations", resp.Header().Get("Location"))
|
||||||
|
|
||||||
|
setting.LandingPageURL = landingPage
|
||||||
|
}
|
||||||
|
|
1
main.go
1
main.go
|
@ -14,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
// register supported doc types
|
// register supported doc types
|
||||||
|
_ "code.gitea.io/gitea/modules/markup/csv"
|
||||||
_ "code.gitea.io/gitea/modules/markup/markdown"
|
_ "code.gitea.io/gitea/modules/markup/markdown"
|
||||||
_ "code.gitea.io/gitea/modules/markup/orgmode"
|
_ "code.gitea.io/gitea/modules/markup/orgmode"
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,12 @@ func TestAccessLevel(t *testing.T) {
|
||||||
|
|
||||||
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
||||||
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
|
// A public repository owned by User 2
|
||||||
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
|
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||||
|
assert.False(t, repo1.IsPrivate)
|
||||||
|
// A private repository owned by Org 3
|
||||||
|
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
|
||||||
|
assert.True(t, repo2.IsPrivate)
|
||||||
|
|
||||||
level, err := AccessLevel(user1.ID, repo1)
|
level, err := AccessLevel(user1.ID, repo1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -47,8 +51,12 @@ func TestHasAccess(t *testing.T) {
|
||||||
|
|
||||||
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
||||||
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
|
// A public repository owned by User 2
|
||||||
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
|
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||||
|
assert.False(t, repo1.IsPrivate)
|
||||||
|
// A private repository owned by Org 3
|
||||||
|
repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
|
||||||
|
assert.True(t, repo2.IsPrivate)
|
||||||
|
|
||||||
for _, accessMode := range accessModes {
|
for _, accessMode := range accessModes {
|
||||||
has, err := HasAccess(user1.ID, repo1, accessMode)
|
has, err := HasAccess(user1.ID, repo1, accessMode)
|
||||||
|
|
|
@ -122,6 +122,12 @@ func (a *Action) loadRepo() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetActFullName gets the action's user full name.
|
||||||
|
func (a *Action) GetActFullName() string {
|
||||||
|
a.loadActUser()
|
||||||
|
return a.ActUser.FullName
|
||||||
|
}
|
||||||
|
|
||||||
// GetActUserName gets the action's user name.
|
// GetActUserName gets the action's user name.
|
||||||
func (a *Action) GetActUserName() string {
|
func (a *Action) GetActUserName() string {
|
||||||
a.loadActUser()
|
a.loadActUser()
|
||||||
|
@ -471,6 +477,10 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = issue.ChangeStatus(doer, repo, true); err != nil {
|
if err = issue.ChangeStatus(doer, repo, true); err != nil {
|
||||||
|
// Don't return an error when dependencies are open as this would let the push fail
|
||||||
|
if IsErrDependenciesLeft(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ func IsErrEmailAlreadyUsed(err error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrEmailAlreadyUsed) Error() string {
|
func (err ErrEmailAlreadyUsed) Error() string {
|
||||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
||||||
|
@ -117,7 +117,7 @@ func IsErrOpenIDAlreadyUsed(err error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrOpenIDAlreadyUsed) Error() string {
|
func (err ErrOpenIDAlreadyUsed) Error() string {
|
||||||
return fmt.Sprintf("OpenID has been used [oid: %s]", err.OpenID)
|
return fmt.Sprintf("OpenID already in use [oid: %s]", err.OpenID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
|
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
|
||||||
|
@ -1259,3 +1259,88 @@ func IsErrU2FRegistrationNotExist(err error) bool {
|
||||||
_, ok := err.(ErrU2FRegistrationNotExist)
|
_, ok := err.(ErrU2FRegistrationNotExist)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// .___ ________ .___ .__
|
||||||
|
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
|
||||||
|
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
|
||||||
|
// | |\___ \ \___ \| | /\ ___/ | ` \ ___/| |_> > ___/| | \/ /_/ \ ___/| | \ \___| \ ___/ \___ \
|
||||||
|
// |___/____ >____ >____/ \___ >_______ /\___ > __/ \___ >___| /\____ |\___ >___| /\___ >__|\___ >____ >
|
||||||
|
// \/ \/ \/ \/ \/|__| \/ \/ \/ \/ \/ \/ \/ \/
|
||||||
|
|
||||||
|
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
|
||||||
|
type ErrDependencyExists struct {
|
||||||
|
IssueID int64
|
||||||
|
DependencyID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrDependencyExists checks if an error is a ErrDependencyExists.
|
||||||
|
func IsErrDependencyExists(err error) bool {
|
||||||
|
_, ok := err.(ErrDependencyExists)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrDependencyExists) Error() string {
|
||||||
|
return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
|
||||||
|
type ErrDependencyNotExists struct {
|
||||||
|
IssueID int64
|
||||||
|
DependencyID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
|
||||||
|
func IsErrDependencyNotExists(err error) bool {
|
||||||
|
_, ok := err.(ErrDependencyNotExists)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrDependencyNotExists) Error() string {
|
||||||
|
return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCircularDependency represents a "DependencyCircular" kind of error.
|
||||||
|
type ErrCircularDependency struct {
|
||||||
|
IssueID int64
|
||||||
|
DependencyID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrCircularDependency checks if an error is a ErrCircularDependency.
|
||||||
|
func IsErrCircularDependency(err error) bool {
|
||||||
|
_, ok := err.(ErrCircularDependency)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrCircularDependency) Error() string {
|
||||||
|
return fmt.Sprintf("circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
|
||||||
|
type ErrDependenciesLeft struct {
|
||||||
|
IssueID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
|
||||||
|
func IsErrDependenciesLeft(err error) bool {
|
||||||
|
_, ok := err.(ErrDependenciesLeft)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrDependenciesLeft) Error() string {
|
||||||
|
return fmt.Sprintf("issue has open dependencies [issue id: %d]", err.IssueID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnknownDependencyType represents an error where an unknown dependency type was passed
|
||||||
|
type ErrUnknownDependencyType struct {
|
||||||
|
Type DependencyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
|
||||||
|
func IsErrUnknownDependencyType(err error) bool {
|
||||||
|
_, ok := err.(ErrUnknownDependencyType)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrUnknownDependencyType) Error() string {
|
||||||
|
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
|
||||||
|
}
|
||||||
|
|
|
@ -351,7 +351,7 @@
|
||||||
is_mirror: true
|
is_mirror: true
|
||||||
num_forks: 1
|
num_forks: 1
|
||||||
is_fork: false
|
is_fork: false
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 29
|
id: 29
|
||||||
fork_id: 27
|
fork_id: 27
|
||||||
|
@ -365,7 +365,7 @@
|
||||||
num_closed_pulls: 0
|
num_closed_pulls: 0
|
||||||
is_mirror: false
|
is_mirror: false
|
||||||
is_fork: true
|
is_fork: true
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 30
|
id: 30
|
||||||
fork_id: 28
|
fork_id: 28
|
||||||
|
@ -389,3 +389,14 @@
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
is_mirror: false
|
is_mirror: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 32
|
||||||
|
owner_id: 3
|
||||||
|
lower_name: repo21
|
||||||
|
name: repo21
|
||||||
|
is_private: false
|
||||||
|
num_stars: 0
|
||||||
|
num_forks: 0
|
||||||
|
num_issues: 0
|
||||||
|
is_mirror: false
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
lower_name: owners
|
lower_name: owners
|
||||||
name: Owners
|
name: Owners
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 2
|
num_repos: 3
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
|
@ -16,7 +15,6 @@
|
||||||
authorize: 2 # write
|
authorize: 2 # write
|
||||||
num_repos: 1
|
num_repos: 1
|
||||||
num_members: 2
|
num_members: 2
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 3
|
id: 3
|
||||||
|
@ -26,7 +24,6 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 4
|
id: 4
|
||||||
|
@ -36,7 +33,6 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 5
|
id: 5
|
||||||
|
@ -46,7 +42,6 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 2
|
num_repos: 2
|
||||||
num_members: 2
|
num_members: 2
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 6
|
id: 6
|
||||||
|
@ -56,4 +51,3 @@
|
||||||
authorize: 4 # owner
|
authorize: 4 # owner
|
||||||
num_repos: 2
|
num_repos: 2
|
||||||
num_members: 1
|
num_members: 1
|
||||||
unit_types: '[1,2,3,4,5,6,7]'
|
|
|
@ -33,9 +33,15 @@
|
||||||
org_id: 19
|
org_id: 19
|
||||||
team_id: 6
|
team_id: 6
|
||||||
repo_id: 27
|
repo_id: 27
|
||||||
|
|
||||||
-
|
-
|
||||||
id: 7
|
id: 7
|
||||||
org_id: 19
|
org_id: 19
|
||||||
team_id: 6
|
team_id: 6
|
||||||
repo_id: 28
|
repo_id: 28
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 8
|
||||||
|
org_id: 3
|
||||||
|
team_id: 1
|
||||||
|
repo_id: 32
|
||||||
|
|
209
models/fixtures/team_unit.yml
Normal file
209
models/fixtures/team_unit.yml
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
team_id: 1
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
team_id: 1
|
||||||
|
type: 2
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
team_id: 1
|
||||||
|
type: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 4
|
||||||
|
team_id: 1
|
||||||
|
type: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 5
|
||||||
|
team_id: 1
|
||||||
|
type: 5
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 6
|
||||||
|
team_id: 1
|
||||||
|
type: 6
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 7
|
||||||
|
team_id: 1
|
||||||
|
type: 7
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 8
|
||||||
|
team_id: 2
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 9
|
||||||
|
team_id: 2
|
||||||
|
type: 2
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 10
|
||||||
|
team_id: 2
|
||||||
|
type: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 11
|
||||||
|
team_id: 2
|
||||||
|
type: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 12
|
||||||
|
team_id: 2
|
||||||
|
type: 5
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 13
|
||||||
|
team_id: 2
|
||||||
|
type: 6
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 14
|
||||||
|
team_id: 2
|
||||||
|
type: 7
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 15
|
||||||
|
team_id: 3
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 16
|
||||||
|
team_id: 3
|
||||||
|
type: 2
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 17
|
||||||
|
team_id: 3
|
||||||
|
type: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 18
|
||||||
|
team_id: 3
|
||||||
|
type: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 19
|
||||||
|
team_id: 3
|
||||||
|
type: 5
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 20
|
||||||
|
team_id: 3
|
||||||
|
type: 6
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 21
|
||||||
|
team_id: 3
|
||||||
|
type: 7
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 22
|
||||||
|
team_id: 4
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 23
|
||||||
|
team_id: 4
|
||||||
|
type: 2
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 24
|
||||||
|
team_id: 4
|
||||||
|
type: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 25
|
||||||
|
team_id: 4
|
||||||
|
type: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 26
|
||||||
|
team_id: 4
|
||||||
|
type: 5
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 27
|
||||||
|
team_id: 4
|
||||||
|
type: 6
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 28
|
||||||
|
team_id: 4
|
||||||
|
type: 7
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 29
|
||||||
|
team_id: 5
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 30
|
||||||
|
team_id: 5
|
||||||
|
type: 2
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 31
|
||||||
|
team_id: 5
|
||||||
|
type: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 32
|
||||||
|
team_id: 5
|
||||||
|
type: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 33
|
||||||
|
team_id: 5
|
||||||
|
type: 5
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 34
|
||||||
|
team_id: 5
|
||||||
|
type: 6
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 35
|
||||||
|
team_id: 5
|
||||||
|
type: 7
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 36
|
||||||
|
team_id: 6
|
||||||
|
type: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 37
|
||||||
|
team_id: 6
|
||||||
|
type: 2
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 38
|
||||||
|
team_id: 6
|
||||||
|
type: 3
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 39
|
||||||
|
team_id: 6
|
||||||
|
type: 4
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 40
|
||||||
|
team_id: 6
|
||||||
|
type: 5
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 41
|
||||||
|
team_id: 6
|
||||||
|
type: 6
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 42
|
||||||
|
team_id: 6
|
||||||
|
type: 7
|
|
@ -45,7 +45,7 @@
|
||||||
is_admin: false
|
is_admin: false
|
||||||
avatar: avatar3
|
avatar: avatar3
|
||||||
avatar_email: user3@example.com
|
avatar_email: user3@example.com
|
||||||
num_repos: 2
|
num_repos: 3
|
||||||
num_members: 2
|
num_members: 2
|
||||||
num_teams: 2
|
num_teams: 2
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/git"
|
"code.gitea.io/git"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphItem represent one commit, or one relation in timeline
|
// GraphItem represent one commit, or one relation in timeline
|
||||||
|
@ -41,7 +42,7 @@ func GetCommitGraph(r *git.Repository) (GraphItems, error) {
|
||||||
"--all",
|
"--all",
|
||||||
"-C",
|
"-C",
|
||||||
"-M",
|
"-M",
|
||||||
"-n 100",
|
fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum),
|
||||||
"--date=iso",
|
"--date=iso",
|
||||||
fmt.Sprintf("--pretty=format:%s", format),
|
fmt.Sprintf("--pretty=format:%s", format),
|
||||||
)
|
)
|
||||||
|
@ -66,7 +67,7 @@ func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
|
||||||
|
|
||||||
var ascii string
|
var ascii string
|
||||||
var data = "|||||||"
|
var data = "|||||||"
|
||||||
lines := strings.Split(s, "DATA:")
|
lines := strings.SplitN(s, "DATA:", 2)
|
||||||
|
|
||||||
switch len(lines) {
|
switch len(lines) {
|
||||||
case 1:
|
case 1:
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/git"
|
"code.gitea.io/git"
|
||||||
|
@ -43,3 +44,32 @@ func BenchmarkParseCommitString(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommitStringParsing(t *testing.T) {
|
||||||
|
dataFirstPart := "* DATA:||4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Author|user@mail.something|4e61bac|"
|
||||||
|
tests := []struct {
|
||||||
|
shouldPass bool
|
||||||
|
testName string
|
||||||
|
commitMessage string
|
||||||
|
}{
|
||||||
|
{true, "normal", "not a fancy message"},
|
||||||
|
{true, "extra pipe", "An extra pipe: |"},
|
||||||
|
{true, "extra 'Data:'", "DATA: might be trouble"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
|
||||||
|
t.Run(test.testName, func(t *testing.T) {
|
||||||
|
testString := fmt.Sprintf("%s%s", dataFirstPart, test.commitMessage)
|
||||||
|
graphItem, err := graphItemFromString(testString, nil)
|
||||||
|
if err != nil && test.shouldPass {
|
||||||
|
t.Errorf("Could not parse %s", testString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.commitMessage != graphItem.Subject {
|
||||||
|
t.Errorf("%s does not match %s", test.commitMessage, graphItem.Subject)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -649,6 +649,20 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
|
||||||
if issue.IsClosed == isClosed {
|
if issue.IsClosed == isClosed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for open dependencies
|
||||||
|
if isClosed && issue.Repo.IsDependenciesEnabled() {
|
||||||
|
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
|
||||||
|
noDeps, err := IssueNoDependenciesLeft(issue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !noDeps {
|
||||||
|
return ErrDependenciesLeft{issue.ID}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
issue.IsClosed = isClosed
|
issue.IsClosed = isClosed
|
||||||
if isClosed {
|
if isClosed {
|
||||||
issue.ClosedUnix = util.TimeStampNow()
|
issue.ClosedUnix = util.TimeStampNow()
|
||||||
|
@ -1283,7 +1297,7 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
|
||||||
And("`comment`.type = ?", CommentTypeComment).
|
And("`comment`.type = ?", CommentTypeComment).
|
||||||
And("`user`.is_active = ?", true).
|
And("`user`.is_active = ?", true).
|
||||||
And("`user`.prohibit_login = ?", false).
|
And("`user`.prohibit_login = ?", false).
|
||||||
Join("INNER", "user", "`user`.id = `comment`.poster_id").
|
Join("INNER", "`user`", "`user`.id = `comment`.poster_id").
|
||||||
Distinct("poster_id").
|
Distinct("poster_id").
|
||||||
Find(&userIDs); err != nil {
|
Find(&userIDs); err != nil {
|
||||||
return nil, fmt.Errorf("get poster IDs: %v", err)
|
return nil, fmt.Errorf("get poster IDs: %v", err)
|
||||||
|
@ -1598,3 +1612,33 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User)
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Blocked By Dependencies, aka all issues this issue is blocked by.
|
||||||
|
func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*Issue, err error) {
|
||||||
|
return issueDeps, e.
|
||||||
|
Table("issue_dependency").
|
||||||
|
Select("issue.*").
|
||||||
|
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
|
||||||
|
Where("issue_id = ?", issue.ID).
|
||||||
|
Find(&issueDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Blocking Dependencies, aka all issues this issue blocks.
|
||||||
|
func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*Issue, err error) {
|
||||||
|
return issueDeps, e.
|
||||||
|
Table("issue_dependency").
|
||||||
|
Select("issue.*").
|
||||||
|
Join("INNER", "issue", "issue.id = issue_dependency.issue_id").
|
||||||
|
Where("dependency_id = ?", issue.ID).
|
||||||
|
Find(&issueDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedByDependencies finds all Dependencies an issue is blocked by
|
||||||
|
func (issue *Issue) BlockedByDependencies() ([]*Issue, error) {
|
||||||
|
return issue.getBlockedByDependencies(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
|
||||||
|
func (issue *Issue) BlockingDependencies() ([]*Issue, error) {
|
||||||
|
return issue.getBlockingDependencies(x)
|
||||||
|
}
|
||||||
|
|
|
@ -66,6 +66,10 @@ const (
|
||||||
CommentTypeModifiedDeadline
|
CommentTypeModifiedDeadline
|
||||||
// Removed a due date
|
// Removed a due date
|
||||||
CommentTypeRemovedDeadline
|
CommentTypeRemovedDeadline
|
||||||
|
// Dependency added
|
||||||
|
CommentTypeAddDependency
|
||||||
|
//Dependency removed
|
||||||
|
CommentTypeRemoveDependency
|
||||||
)
|
)
|
||||||
|
|
||||||
// CommentTag defines comment tag type
|
// CommentTag defines comment tag type
|
||||||
|
@ -81,23 +85,25 @@ const (
|
||||||
|
|
||||||
// Comment represents a comment in commit and issue page.
|
// Comment represents a comment in commit and issue page.
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
Type CommentType
|
Type CommentType
|
||||||
PosterID int64 `xorm:"INDEX"`
|
PosterID int64 `xorm:"INDEX"`
|
||||||
Poster *User `xorm:"-"`
|
Poster *User `xorm:"-"`
|
||||||
IssueID int64 `xorm:"INDEX"`
|
IssueID int64 `xorm:"INDEX"`
|
||||||
Issue *Issue `xorm:"-"`
|
Issue *Issue `xorm:"-"`
|
||||||
LabelID int64
|
LabelID int64
|
||||||
Label *Label `xorm:"-"`
|
Label *Label `xorm:"-"`
|
||||||
OldMilestoneID int64
|
OldMilestoneID int64
|
||||||
MilestoneID int64
|
MilestoneID int64
|
||||||
OldMilestone *Milestone `xorm:"-"`
|
OldMilestone *Milestone `xorm:"-"`
|
||||||
Milestone *Milestone `xorm:"-"`
|
Milestone *Milestone `xorm:"-"`
|
||||||
AssigneeID int64
|
AssigneeID int64
|
||||||
RemovedAssignee bool
|
RemovedAssignee bool
|
||||||
Assignee *User `xorm:"-"`
|
Assignee *User `xorm:"-"`
|
||||||
OldTitle string
|
OldTitle string
|
||||||
NewTitle string
|
NewTitle string
|
||||||
|
DependentIssueID int64
|
||||||
|
DependentIssue *Issue `xorm:"-"`
|
||||||
|
|
||||||
CommitID int64
|
CommitID int64
|
||||||
Line int64
|
Line int64
|
||||||
|
@ -147,6 +153,10 @@ func (c *Comment) AfterLoad(session *xorm.Session) {
|
||||||
|
|
||||||
// AfterDelete is invoked from XORM after the object is deleted.
|
// AfterDelete is invoked from XORM after the object is deleted.
|
||||||
func (c *Comment) AfterDelete() {
|
func (c *Comment) AfterDelete() {
|
||||||
|
if c.ID <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
_, err := DeleteAttachmentsByComment(c.ID, true)
|
_, err := DeleteAttachmentsByComment(c.ID, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -277,6 +287,15 @@ func (c *Comment) LoadAssigneeUser() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadDepIssueDetails loads Dependent Issue Details
|
||||||
|
func (c *Comment) LoadDepIssueDetails() (err error) {
|
||||||
|
if c.DependentIssueID <= 0 || c.DependentIssue != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.DependentIssue, err = getIssueByID(x, c.DependentIssueID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// MailParticipants sends new comment emails to repository watchers
|
// MailParticipants sends new comment emails to repository watchers
|
||||||
// and mentioned people.
|
// and mentioned people.
|
||||||
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
|
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
|
||||||
|
@ -328,22 +347,24 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
||||||
if opts.Label != nil {
|
if opts.Label != nil {
|
||||||
LabelID = opts.Label.ID
|
LabelID = opts.Label.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
comment := &Comment{
|
comment := &Comment{
|
||||||
Type: opts.Type,
|
Type: opts.Type,
|
||||||
PosterID: opts.Doer.ID,
|
PosterID: opts.Doer.ID,
|
||||||
Poster: opts.Doer,
|
Poster: opts.Doer,
|
||||||
IssueID: opts.Issue.ID,
|
IssueID: opts.Issue.ID,
|
||||||
LabelID: LabelID,
|
LabelID: LabelID,
|
||||||
OldMilestoneID: opts.OldMilestoneID,
|
OldMilestoneID: opts.OldMilestoneID,
|
||||||
MilestoneID: opts.MilestoneID,
|
MilestoneID: opts.MilestoneID,
|
||||||
RemovedAssignee: opts.RemovedAssignee,
|
RemovedAssignee: opts.RemovedAssignee,
|
||||||
AssigneeID: opts.AssigneeID,
|
AssigneeID: opts.AssigneeID,
|
||||||
CommitID: opts.CommitID,
|
CommitID: opts.CommitID,
|
||||||
CommitSHA: opts.CommitSHA,
|
CommitSHA: opts.CommitSHA,
|
||||||
Line: opts.LineNum,
|
Line: opts.LineNum,
|
||||||
Content: opts.Content,
|
Content: opts.Content,
|
||||||
OldTitle: opts.OldTitle,
|
OldTitle: opts.OldTitle,
|
||||||
NewTitle: opts.NewTitle,
|
NewTitle: opts.NewTitle,
|
||||||
|
DependentIssueID: opts.DependentIssueID,
|
||||||
}
|
}
|
||||||
if _, err = e.Insert(comment); err != nil {
|
if _, err = e.Insert(comment); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -545,6 +566,39 @@ func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, is
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates issue dependency comment
|
||||||
|
func createIssueDependencyComment(e *xorm.Session, doer *User, issue *Issue, dependentIssue *Issue, add bool) (err error) {
|
||||||
|
cType := CommentTypeAddDependency
|
||||||
|
if !add {
|
||||||
|
cType = CommentTypeRemoveDependency
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make two comments, one in each issue
|
||||||
|
_, err = createComment(e, &CreateCommentOptions{
|
||||||
|
Type: cType,
|
||||||
|
Doer: doer,
|
||||||
|
Repo: issue.Repo,
|
||||||
|
Issue: issue,
|
||||||
|
DependentIssueID: dependentIssue.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = createComment(e, &CreateCommentOptions{
|
||||||
|
Type: cType,
|
||||||
|
Doer: doer,
|
||||||
|
Repo: issue.Repo,
|
||||||
|
Issue: dependentIssue,
|
||||||
|
DependentIssueID: issue.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// CreateCommentOptions defines options for creating comment
|
// CreateCommentOptions defines options for creating comment
|
||||||
type CreateCommentOptions struct {
|
type CreateCommentOptions struct {
|
||||||
Type CommentType
|
Type CommentType
|
||||||
|
@ -553,17 +607,18 @@ type CreateCommentOptions struct {
|
||||||
Issue *Issue
|
Issue *Issue
|
||||||
Label *Label
|
Label *Label
|
||||||
|
|
||||||
OldMilestoneID int64
|
DependentIssueID int64
|
||||||
MilestoneID int64
|
OldMilestoneID int64
|
||||||
AssigneeID int64
|
MilestoneID int64
|
||||||
RemovedAssignee bool
|
AssigneeID int64
|
||||||
OldTitle string
|
RemovedAssignee bool
|
||||||
NewTitle string
|
OldTitle string
|
||||||
CommitID int64
|
NewTitle string
|
||||||
CommitSHA string
|
CommitID int64
|
||||||
LineNum int64
|
CommitSHA string
|
||||||
Content string
|
LineNum int64
|
||||||
Attachments []string // UUIDs of attachments
|
Content string
|
||||||
|
Attachments []string // UUIDs of attachments
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateComment creates comment of issue or commit.
|
// CreateComment creates comment of issue or commit.
|
||||||
|
|
137
models/issue_dependency.go
Normal file
137
models/issue_dependency.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IssueDependency represents an issue dependency
|
||||||
|
type IssueDependency struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"NOT NULL"`
|
||||||
|
IssueID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
|
||||||
|
DependencyID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
|
||||||
|
CreatedUnix util.TimeStamp `xorm:"created"`
|
||||||
|
UpdatedUnix util.TimeStamp `xorm:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DependencyType Defines Dependency Type Constants
|
||||||
|
type DependencyType int
|
||||||
|
|
||||||
|
// Define Dependency Types
|
||||||
|
const (
|
||||||
|
DependencyTypeBlockedBy DependencyType = iota
|
||||||
|
DependencyTypeBlocking
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateIssueDependency creates a new dependency for an issue
|
||||||
|
func CreateIssueDependency(user *User, issue, dep *Issue) error {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it aleready exists
|
||||||
|
exists, err := issueDepExists(sess, issue.ID, dep.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return ErrDependencyExists{issue.ID, dep.ID}
|
||||||
|
}
|
||||||
|
// And if it would be circular
|
||||||
|
circular, err := issueDepExists(sess, dep.ID, issue.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if circular {
|
||||||
|
return ErrCircularDependency{issue.ID, dep.ID}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.Insert(&IssueDependency{
|
||||||
|
UserID: user.ID,
|
||||||
|
IssueID: issue.ID,
|
||||||
|
DependencyID: dep.ID,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add comment referencing the new dependency
|
||||||
|
if err = createIssueDependencyComment(sess, user, issue, dep, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveIssueDependency removes a dependency from an issue
|
||||||
|
func RemoveIssueDependency(user *User, issue *Issue, dep *Issue, depType DependencyType) (err error) {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var issueDepToDelete IssueDependency
|
||||||
|
|
||||||
|
switch depType {
|
||||||
|
case DependencyTypeBlockedBy:
|
||||||
|
issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
|
||||||
|
case DependencyTypeBlocking:
|
||||||
|
issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
|
||||||
|
default:
|
||||||
|
return ErrUnknownDependencyType{depType}
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := sess.Delete(&issueDepToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we deleted nothing, the dependency did not exist
|
||||||
|
if affected <= 0 {
|
||||||
|
return ErrDependencyNotExists{issue.ID, dep.ID}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add comment referencing the removed dependency
|
||||||
|
if err = createIssueDependencyComment(sess, user, issue, dep, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the dependency already exists
|
||||||
|
func issueDepExists(e Engine, issueID int64, depID int64) (bool, error) {
|
||||||
|
return e.Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueNoDependenciesLeft checks if issue can be closed
|
||||||
|
func IssueNoDependenciesLeft(issue *Issue) (bool, error) {
|
||||||
|
|
||||||
|
exists, err := x.
|
||||||
|
Table("issue_dependency").
|
||||||
|
Select("issue.*").
|
||||||
|
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
|
||||||
|
Where("issue_dependency.issue_id = ?", issue.ID).
|
||||||
|
And("issue.is_closed = ?", "0").
|
||||||
|
Exist(&Issue{})
|
||||||
|
|
||||||
|
return !exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDependenciesEnabled returns if dependecies are enabled and returns the default setting if not set.
|
||||||
|
func (repo *Repository) IsDependenciesEnabled() bool {
|
||||||
|
var u *RepoUnit
|
||||||
|
var err error
|
||||||
|
if u, err = repo.GetUnit(UnitTypeIssues); err != nil {
|
||||||
|
log.Trace("%s", err)
|
||||||
|
return setting.Service.DefaultEnableDependencies
|
||||||
|
}
|
||||||
|
return u.IssuesConfig().EnableDependencies
|
||||||
|
}
|
57
models/issue_dependency_test.go
Normal file
57
models/issue_dependency_test.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateIssueDependency(t *testing.T) {
|
||||||
|
// Prepare
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
|
||||||
|
user1, err := GetUserByID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
issue1, err := GetIssueByID(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issue2, err := GetIssueByID(2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Create a dependency and check if it was successful
|
||||||
|
err = CreateIssueDependency(user1, issue1, issue2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Do it again to see if it will check if the dependency already exists
|
||||||
|
err = CreateIssueDependency(user1, issue1, issue2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, IsErrDependencyExists(err))
|
||||||
|
|
||||||
|
// Check for circular dependencies
|
||||||
|
err = CreateIssueDependency(user1, issue2, issue1)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, IsErrCircularDependency(err))
|
||||||
|
|
||||||
|
_ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID})
|
||||||
|
|
||||||
|
// Check if dependencies left is correct
|
||||||
|
left, err := IssueNoDependenciesLeft(issue1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, left)
|
||||||
|
|
||||||
|
// Close #2 and check again
|
||||||
|
err = issue2.ChangeStatus(user1, issue2.Repo, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
left, err = IssueNoDependenciesLeft(issue1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, left)
|
||||||
|
|
||||||
|
// Test removing the dependency
|
||||||
|
err = RemoveIssueDependency(user1, issue1, issue2, DependencyTypeBlockedBy)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
|
@ -166,7 +166,7 @@ func (issues IssueList) loadAssignees(e Engine) error {
|
||||||
|
|
||||||
var assignees = make(map[int64][]*User, len(issues))
|
var assignees = make(map[int64][]*User, len(issues))
|
||||||
rows, err := e.Table("issue_assignees").
|
rows, err := e.Table("issue_assignees").
|
||||||
Join("INNER", "user", "`user`.id = `issue_assignees`.assignee_id").
|
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
|
||||||
In("`issue_assignees`.issue_id", issues.getIssueIDs()).
|
In("`issue_assignees`.issue_id", issues.getIssueIDs()).
|
||||||
Rows(new(AssigneeIssue))
|
Rows(new(AssigneeIssue))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -67,7 +67,19 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error
|
||||||
Where("`issue_watch`.issue_id = ?", issueID).
|
Where("`issue_watch`.issue_id = ?", issueID).
|
||||||
And("`user`.is_active = ?", true).
|
And("`user`.is_active = ?", true).
|
||||||
And("`user`.prohibit_login = ?", false).
|
And("`user`.prohibit_login = ?", false).
|
||||||
Join("INNER", "user", "`user`.id = `issue_watch`.user_id").
|
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id").
|
||||||
Find(&watches)
|
Find(&watches)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeIssueWatchersByRepoID(e Engine, userID int64, repoID int64) error {
|
||||||
|
iw := &IssueWatch{
|
||||||
|
IsWatching: false,
|
||||||
|
}
|
||||||
|
_, err := e.
|
||||||
|
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", repoID).
|
||||||
|
Cols("is_watching", "updated_unix").
|
||||||
|
Where("`issue_watch`.user_id = ?", userID).
|
||||||
|
Update(iw)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -187,6 +187,14 @@ var migrations = []Migration{
|
||||||
// v66 -> v67
|
// v66 -> v67
|
||||||
NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
|
NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
|
||||||
// v67 -> v68
|
// v67 -> v68
|
||||||
|
NewMigration("remove stale watches", removeStaleWatches),
|
||||||
|
// v68 -> V69
|
||||||
|
NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics),
|
||||||
|
// v69 -> v70
|
||||||
|
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable),
|
||||||
|
// v70 -> v71
|
||||||
|
NewMigration("add issue_dependencies", addIssueDependencies),
|
||||||
|
// v71 -> v72
|
||||||
NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMergeCommit),
|
NewMigration("add pull request rebase with merge commit", addPullRequestRebaseWithMergeCommit),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,15 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
|
||||||
Created time.Time `xorm:"-"`
|
Created time.Time `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Team struct {
|
||||||
|
ID int64
|
||||||
|
UnitTypes []int `xorm:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
// Update team unit types
|
// Update team unit types
|
||||||
const batchSize = 100
|
const batchSize = 100
|
||||||
for start := 0; ; start += batchSize {
|
for start := 0; ; start += batchSize {
|
||||||
teams := make([]*models.Team, 0, batchSize)
|
teams := make([]*Team, 0, batchSize)
|
||||||
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
|
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -36,7 +41,7 @@ func removeCommitsUnitType(x *xorm.Engine) (err error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
ut := make([]models.UnitType, 0, len(team.UnitTypes))
|
ut := make([]int, 0, len(team.UnitTypes))
|
||||||
for _, u := range team.UnitTypes {
|
for _, u := range team.UnitTypes {
|
||||||
if u < V16UnitTypeCommits {
|
if u < V16UnitTypeCommits {
|
||||||
ut = append(ut, u)
|
ut = append(ut, u)
|
||||||
|
|
|
@ -34,7 +34,7 @@ func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
|
||||||
pageSize := models.RepositoryListDefaultPageSize
|
pageSize := models.RepositoryListDefaultPageSize
|
||||||
for {
|
for {
|
||||||
repos := make([]*models.Repository, 0, pageSize)
|
repos := make([]*models.Repository, 0, pageSize)
|
||||||
if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
|
if err := x.Table("repository").Cols("id", "name", "owner_id").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
|
||||||
return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
|
return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
|
||||||
}
|
}
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
|
|
|
@ -120,6 +120,14 @@ func addMultipleAssignees(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commit and begin new transaction for dropping columns
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := dropTableColumns(sess, "issue", "assignee_id"); err != nil {
|
if err := dropTableColumns(sess, "issue", "assignee_id"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,18 @@
|
||||||
package migrations
|
package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
<<<<<<< HEAD
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
=======
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
>>>>>>> origin/master
|
||||||
|
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
func addPullRequestRebaseWithMergeCommit(x *xorm.Engine) error {
|
func addPullRequestRebaseWithMergeCommit(x *xorm.Engine) error {
|
||||||
// RepoUnit describes all units of a repository
|
// RepoUnit describes all units of a repository
|
||||||
type RepoUnit struct {
|
type RepoUnit struct {
|
||||||
|
@ -20,6 +25,60 @@ func addPullRequestRebaseWithMergeCommit(x *xorm.Engine) error {
|
||||||
Type int `xorm:"INDEX(s)"`
|
Type int `xorm:"INDEX(s)"`
|
||||||
Config map[string]interface{} `xorm:"JSON"`
|
Config map[string]interface{} `xorm:"JSON"`
|
||||||
CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"`
|
CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"`
|
||||||
|
=======
|
||||||
|
func removeStaleWatches(x *xorm.Engine) error {
|
||||||
|
type Watch struct {
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
RepoID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type IssueWatch struct {
|
||||||
|
ID int64
|
||||||
|
UserID int64
|
||||||
|
RepoID int64
|
||||||
|
IsWatching bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
ID int64
|
||||||
|
IsPrivate bool
|
||||||
|
OwnerID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Access struct {
|
||||||
|
UserID int64
|
||||||
|
RepoID int64
|
||||||
|
Mode int
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccessModeNone no access
|
||||||
|
AccessModeNone int = iota // 0
|
||||||
|
// AccessModeRead read access
|
||||||
|
AccessModeRead // 1
|
||||||
|
)
|
||||||
|
|
||||||
|
accessLevel := func(userID int64, repo *Repository) (int, error) {
|
||||||
|
mode := AccessModeNone
|
||||||
|
if !repo.IsPrivate {
|
||||||
|
mode = AccessModeRead
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID == 0 {
|
||||||
|
return mode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID == repo.OwnerID {
|
||||||
|
return 4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &Access{UserID: userID, RepoID: repo.ID}
|
||||||
|
if has, err := x.Get(a); !has || err != nil {
|
||||||
|
return mode, err
|
||||||
|
}
|
||||||
|
return a.Mode, nil
|
||||||
|
>>>>>>> origin/master
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
|
@ -28,6 +87,7 @@ func addPullRequestRebaseWithMergeCommit(x *xorm.Engine) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
//Updating existing issue units
|
//Updating existing issue units
|
||||||
units := make([]*RepoUnit, 0, 100)
|
units := make([]*RepoUnit, 0, 100)
|
||||||
if err := sess.Where("`type` = ?", V16UnitTypePRs).Find(&units); err != nil {
|
if err := sess.Where("`type` = ?", V16UnitTypePRs).Find(&units); err != nil {
|
||||||
|
@ -46,4 +106,91 @@ func addPullRequestRebaseWithMergeCommit(x *xorm.Engine) error {
|
||||||
}
|
}
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
|
|
||||||
|
=======
|
||||||
|
repoCache := make(map[int64]*Repository)
|
||||||
|
err := x.BufferSize(setting.IterateBufferSize).Iterate(new(Watch),
|
||||||
|
func(idx int, bean interface{}) error {
|
||||||
|
watch := bean.(*Watch)
|
||||||
|
|
||||||
|
repo := repoCache[watch.RepoID]
|
||||||
|
if repo == nil {
|
||||||
|
repo = &Repository{
|
||||||
|
ID: watch.RepoID,
|
||||||
|
}
|
||||||
|
if _, err := x.Get(repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoCache[watch.RepoID] = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove watches from now unaccessible repositories
|
||||||
|
mode, err := accessLevel(watch.UserID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
has := AccessModeRead <= mode
|
||||||
|
if has {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Delete(&Watch{0, watch.UserID, repo.ID}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = sess.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repo.ID)
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoCache = make(map[int64]*Repository)
|
||||||
|
err = x.BufferSize(setting.IterateBufferSize).
|
||||||
|
Distinct("issue_watch.user_id", "issue.repo_id").
|
||||||
|
Join("INNER", "issue", "issue_watch.issue_id = issue.id").
|
||||||
|
Where("issue_watch.is_watching = ?", true).
|
||||||
|
Iterate(new(IssueWatch),
|
||||||
|
func(idx int, bean interface{}) error {
|
||||||
|
watch := bean.(*IssueWatch)
|
||||||
|
|
||||||
|
repo := repoCache[watch.RepoID]
|
||||||
|
if repo == nil {
|
||||||
|
repo = &Repository{
|
||||||
|
ID: watch.RepoID,
|
||||||
|
}
|
||||||
|
if _, err := x.Get(repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoCache[watch.RepoID] = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove issue watches from now unaccssible repositories
|
||||||
|
mode, err := accessLevel(watch.UserID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
has := AccessModeRead <= mode
|
||||||
|
if has {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iw := &IssueWatch{
|
||||||
|
IsWatching: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = sess.
|
||||||
|
Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", watch.RepoID).
|
||||||
|
Cols("is_watching", "updated_unix").
|
||||||
|
Where("`issue_watch`.user_id = ?", watch.UserID).
|
||||||
|
Update(iw)
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
>>>>>>> origin/master
|
||||||
}
|
}
|
||||||
|
|
213
models/migrations/v68.go
Normal file
213
models/migrations/v68.go
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||||
|
|
||||||
|
func validateTopic(topic string) bool {
|
||||||
|
return len(topic) <= 35 && topicPattern.MatchString(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
|
||||||
|
log.Info("This migration could take up to minutes, please be patient.")
|
||||||
|
|
||||||
|
type Topic struct {
|
||||||
|
ID int64
|
||||||
|
Name string `xorm:"UNIQUE"`
|
||||||
|
RepoCount int
|
||||||
|
CreatedUnix int64 `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix int64 `xorm:"INDEX updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepoTopic struct {
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
TopicID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Topics []string `xorm:"TEXT JSON"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := x.Sync2(new(Topic)); err != nil {
|
||||||
|
return fmt.Errorf("Sync2: %v", err)
|
||||||
|
}
|
||||||
|
if err := x.Sync2(new(RepoTopic)); err != nil {
|
||||||
|
return fmt.Errorf("Sync2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
const batchSize = 100
|
||||||
|
touchedRepo := make(map[int64]struct{})
|
||||||
|
delTopicIDs := make([]int64, 0, batchSize)
|
||||||
|
|
||||||
|
log.Info("Validating existed topics...")
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
topics := make([]*Topic, 0, batchSize)
|
||||||
|
if err := x.Cols("id", "name").Asc("id").Limit(batchSize, start).Find(&topics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(topics) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, topic := range topics {
|
||||||
|
if validateTopic(topic.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info("Incorrect topic: id = %v, name = %q", topic.ID, topic.Name)
|
||||||
|
|
||||||
|
topic.Name = strings.Replace(strings.TrimSpace(strings.ToLower(topic.Name)), " ", "-", -1)
|
||||||
|
|
||||||
|
ids := make([]int64, 0, 30)
|
||||||
|
if err := sess.Table("repo_topic").Cols("repo_id").
|
||||||
|
Where("topic_id = ?", topic.ID).Find(&ids); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Touched repo ids: %v", ids)
|
||||||
|
for _, id := range ids {
|
||||||
|
touchedRepo[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if validateTopic(topic.Name) {
|
||||||
|
unifiedTopic := Topic{Name: topic.Name}
|
||||||
|
exists, err := sess.Cols("id", "name").Get(&unifiedTopic)
|
||||||
|
log.Info("Exists topic with the name %q? %v, id = %v", topic.Name, exists, unifiedTopic.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
log.Info("Updating repo_topic rows with topic_id = %v to topic_id = %v", topic.ID, unifiedTopic.ID)
|
||||||
|
if _, err := sess.Where("topic_id = ? AND repo_id NOT IN "+
|
||||||
|
"(SELECT rt1.repo_id FROM repo_topic rt1 INNER JOIN repo_topic rt2 "+
|
||||||
|
"ON rt1.repo_id = rt2.repo_id WHERE rt1.topic_id = ? AND rt2.topic_id = ?)",
|
||||||
|
topic.ID, topic.ID, unifiedTopic.ID).Update(&RepoTopic{TopicID: unifiedTopic.ID}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Updating topic `repo_count` field")
|
||||||
|
if _, err := sess.Exec(
|
||||||
|
"UPDATE topic SET repo_count = (SELECT COUNT(*) FROM repo_topic WHERE topic_id = ? GROUP BY topic_id) WHERE id = ?",
|
||||||
|
unifiedTopic.ID, unifiedTopic.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info("Updating topic: id = %v, name = %q", topic.ID, topic.Name)
|
||||||
|
if _, err := sess.Table("topic").ID(topic.ID).
|
||||||
|
Update(&Topic{Name: topic.Name}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delTopicIDs = append(delTopicIDs, topic.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sess.Init()
|
||||||
|
|
||||||
|
log.Info("Deleting incorrect topics...")
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Deleting 'repo_topic' rows for topics with ids = %v", delTopicIDs)
|
||||||
|
if _, err := sess.In("topic_id", delTopicIDs).Delete(&RepoTopic{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Deleting topics with id = %v", delTopicIDs)
|
||||||
|
if _, err := sess.In("id", delTopicIDs).Delete(&Topic{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delRepoTopics := make([]*RepoTopic, 0, batchSize)
|
||||||
|
|
||||||
|
log.Info("Checking the number of topics in the repositories...")
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
repoTopics := make([]*RepoTopic, 0, batchSize)
|
||||||
|
if err := x.Cols("repo_id").Asc("repo_id").Limit(batchSize, start).
|
||||||
|
GroupBy("repo_id").Having("COUNT(*) > 25").Find(&repoTopics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(repoTopics) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Number of repositories with more than 25 topics: %v", len(repoTopics))
|
||||||
|
for _, repoTopic := range repoTopics {
|
||||||
|
touchedRepo[repoTopic.RepoID] = struct{}{}
|
||||||
|
|
||||||
|
tmpRepoTopics := make([]*RepoTopic, 0, 30)
|
||||||
|
if err := x.Where("repo_id = ?", repoTopic.RepoID).Find(&tmpRepoTopics); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Repository with id = %v has %v topics", repoTopic.RepoID, len(tmpRepoTopics))
|
||||||
|
|
||||||
|
for i := len(tmpRepoTopics) - 1; i > 24; i-- {
|
||||||
|
delRepoTopics = append(delRepoTopics, tmpRepoTopics[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sess.Init()
|
||||||
|
|
||||||
|
log.Info("Deleting superfluous topics for repositories (more than 25 topics)...")
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, repoTopic := range delRepoTopics {
|
||||||
|
log.Info("Deleting 'repo_topic' rows for 'repository' with id = %v. Topic id = %v",
|
||||||
|
repoTopic.RepoID, repoTopic.TopicID)
|
||||||
|
|
||||||
|
if _, err := sess.Where("repo_id = ? AND topic_id = ?", repoTopic.RepoID,
|
||||||
|
repoTopic.TopicID).Delete(&RepoTopic{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := sess.Exec(
|
||||||
|
"UPDATE topic SET repo_count = (SELECT repo_count FROM topic WHERE id = ?) - 1 WHERE id = ?",
|
||||||
|
repoTopic.TopicID, repoTopic.TopicID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Updating repositories 'topics' fields...")
|
||||||
|
for repoID := range touchedRepo {
|
||||||
|
topicNames := make([]string, 0, 30)
|
||||||
|
if err := sess.Table("topic").Cols("name").
|
||||||
|
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
|
||||||
|
Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Updating 'topics' field for repository with id = %v", repoID)
|
||||||
|
if _, err := sess.ID(repoID).Cols("topics").
|
||||||
|
Update(&Repository{Topics: topicNames}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
88
models/migrations/v69.go
Normal file
88
models/migrations/v69.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func moveTeamUnitsToTeamUnitTable(x *xorm.Engine) error {
|
||||||
|
// Team see models/team.go
|
||||||
|
type Team struct {
|
||||||
|
ID int64
|
||||||
|
OrgID int64
|
||||||
|
UnitTypes []int `xorm:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamUnit see models/org_team.go
|
||||||
|
type TeamUnit struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
OrgID int64 `xorm:"INDEX"`
|
||||||
|
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
Type int `xorm:"UNIQUE(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := x.Sync2(new(TeamUnit)); err != nil {
|
||||||
|
return fmt.Errorf("Sync2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update team unit types
|
||||||
|
const batchSize = 100
|
||||||
|
for start := 0; ; start += batchSize {
|
||||||
|
teams := make([]*Team, 0, batchSize)
|
||||||
|
if err := x.Limit(batchSize, start).Find(&teams); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(teams) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, team := range teams {
|
||||||
|
var unitTypes []int
|
||||||
|
if len(team.UnitTypes) == 0 {
|
||||||
|
unitTypes = allUnitTypes
|
||||||
|
} else {
|
||||||
|
unitTypes = team.UnitTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert units for team
|
||||||
|
var units = make([]TeamUnit, 0, len(unitTypes))
|
||||||
|
for _, tp := range unitTypes {
|
||||||
|
units = append(units, TeamUnit{
|
||||||
|
OrgID: team.OrgID,
|
||||||
|
TeamID: team.ID,
|
||||||
|
Type: tp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.Insert(&units); err != nil {
|
||||||
|
return fmt.Errorf("Insert team units: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit and begin new transaction for dropping columns
|
||||||
|
if err := sess.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dropTableColumns(sess, "team", "unit_types"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
100
models/migrations/v70.go
Normal file
100
models/migrations/v70.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addIssueDependencies(x *xorm.Engine) (err error) {
|
||||||
|
|
||||||
|
type IssueDependency struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"NOT NULL"`
|
||||||
|
IssueID int64 `xorm:"NOT NULL"`
|
||||||
|
DependencyID int64 `xorm:"NOT NULL"`
|
||||||
|
Created time.Time `xorm:"-"`
|
||||||
|
CreatedUnix int64 `xorm:"created"`
|
||||||
|
Updated time.Time `xorm:"-"`
|
||||||
|
UpdatedUnix int64 `xorm:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = x.Sync(new(IssueDependency)); err != nil {
|
||||||
|
return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Comment definition
|
||||||
|
// This (copied) struct does only contain fields used by xorm as the only use here is to update the database
|
||||||
|
|
||||||
|
// CommentType defines the comment type
|
||||||
|
type CommentType int
|
||||||
|
|
||||||
|
// TimeStamp defines a timestamp
|
||||||
|
type TimeStamp int64
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
Type CommentType
|
||||||
|
PosterID int64 `xorm:"INDEX"`
|
||||||
|
IssueID int64 `xorm:"INDEX"`
|
||||||
|
LabelID int64
|
||||||
|
OldMilestoneID int64
|
||||||
|
MilestoneID int64
|
||||||
|
OldAssigneeID int64
|
||||||
|
AssigneeID int64
|
||||||
|
OldTitle string
|
||||||
|
NewTitle string
|
||||||
|
DependentIssueID int64
|
||||||
|
|
||||||
|
CommitID int64
|
||||||
|
Line int64
|
||||||
|
Content string `xorm:"TEXT"`
|
||||||
|
|
||||||
|
CreatedUnix TimeStamp `xorm:"INDEX created"`
|
||||||
|
UpdatedUnix TimeStamp `xorm:"INDEX updated"`
|
||||||
|
|
||||||
|
// Reference issue in commit message
|
||||||
|
CommitSHA string `xorm:"VARCHAR(40)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = x.Sync(new(Comment)); err != nil {
|
||||||
|
return fmt.Errorf("Error updating issue_comment table column definition: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoUnit describes all units of a repository
|
||||||
|
type RepoUnit struct {
|
||||||
|
ID int64
|
||||||
|
RepoID int64 `xorm:"INDEX(s)"`
|
||||||
|
Type int `xorm:"INDEX(s)"`
|
||||||
|
Config map[string]interface{} `xorm:"JSON"`
|
||||||
|
CreatedUnix int64 `xorm:"INDEX CREATED"`
|
||||||
|
Created time.Time `xorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Updating existing issue units
|
||||||
|
units := make([]*RepoUnit, 0, 100)
|
||||||
|
err = x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Query repo units: %v", err)
|
||||||
|
}
|
||||||
|
for _, unit := range units {
|
||||||
|
if unit.Config == nil {
|
||||||
|
unit.Config = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
if _, ok := unit.Config["EnableDependencies"]; !ok {
|
||||||
|
unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
|
||||||
|
}
|
||||||
|
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
48
models/migrations/v71.go
Normal file
48
models/migrations/v71.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/go-xorm/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addPullRequestRebaseWithMergeCommit(x *xorm.Engine) error {
|
||||||
|
// RepoUnit describes all units of a repository
|
||||||
|
type RepoUnit struct {
|
||||||
|
ID int64
|
||||||
|
RepoID int64 `xorm:"INDEX(s)"`
|
||||||
|
Type int `xorm:"INDEX(s)"`
|
||||||
|
Config map[string]interface{} `xorm:"JSON"`
|
||||||
|
CreatedUnix util.TimeStamp `xorm:"INDEX CREATED"`
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Updating existing issue units
|
||||||
|
units := make([]*RepoUnit, 0, 100)
|
||||||
|
if err := sess.Where("`type` = ?", V16UnitTypePRs).Find(&units); err != nil {
|
||||||
|
return fmt.Errorf("Query repo units: %v", err)
|
||||||
|
}
|
||||||
|
for _, unit := range units {
|
||||||
|
if unit.Config == nil {
|
||||||
|
unit.Config = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
if _, ok := unit.Config["AllowRebaseMergeCommit"]; !ok {
|
||||||
|
unit.Config["AllowRebaseMergeCommit"] = true
|
||||||
|
}
|
||||||
|
if _, err := sess.ID(unit.ID).Cols("config").Update(unit); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -117,10 +118,12 @@ func init() {
|
||||||
new(TrackedTime),
|
new(TrackedTime),
|
||||||
new(DeletedBranch),
|
new(DeletedBranch),
|
||||||
new(RepoIndexerStatus),
|
new(RepoIndexerStatus),
|
||||||
|
new(IssueDependency),
|
||||||
new(LFSLock),
|
new(LFSLock),
|
||||||
new(Reaction),
|
new(Reaction),
|
||||||
new(IssueAssignees),
|
new(IssueAssignees),
|
||||||
new(U2FRegistration),
|
new(U2FRegistration),
|
||||||
|
new(TeamUnit),
|
||||||
)
|
)
|
||||||
|
|
||||||
gonicNames := []string{"SSL", "UID"}
|
gonicNames := []string{"SSL", "UID"}
|
||||||
|
@ -184,6 +187,18 @@ func parsePostgreSQLHostPort(info string) (string, string) {
|
||||||
return host, port
|
return host, port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPostgreSQLConnectionString(DBHost, DBUser, DBPasswd, DBName, DBParam, DBSSLMode string) (connStr string) {
|
||||||
|
host, port := parsePostgreSQLHostPort(DBHost)
|
||||||
|
if host[0] == '/' { // looks like a unix socket
|
||||||
|
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
|
||||||
|
url.PathEscape(DBUser), url.PathEscape(DBPasswd), port, DBName, DBParam, DBSSLMode, host)
|
||||||
|
} else {
|
||||||
|
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
|
||||||
|
url.PathEscape(DBUser), url.PathEscape(DBPasswd), host, port, DBName, DBParam, DBSSLMode)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseMSSQLHostPort(info string) (string, string) {
|
func parseMSSQLHostPort(info string) (string, string) {
|
||||||
host, port := "127.0.0.1", "1433"
|
host, port := "127.0.0.1", "1433"
|
||||||
if strings.Contains(info, ":") {
|
if strings.Contains(info, ":") {
|
||||||
|
@ -214,14 +229,7 @@ func getEngine() (*xorm.Engine, error) {
|
||||||
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
|
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
|
||||||
}
|
}
|
||||||
case "postgres":
|
case "postgres":
|
||||||
host, port := parsePostgreSQLHostPort(DbCfg.Host)
|
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
|
||||||
if host[0] == '/' { // looks like a unix socket
|
|
||||||
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
|
|
||||||
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), port, DbCfg.Name, Param, DbCfg.SSLMode, host)
|
|
||||||
} else {
|
|
||||||
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
|
|
||||||
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode)
|
|
||||||
}
|
|
||||||
case "mssql":
|
case "mssql":
|
||||||
host, port := parseMSSQLHostPort(DbCfg.Host)
|
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)
|
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -53,3 +54,42 @@ func Test_parsePostgreSQLHostPort(t *testing.T) {
|
||||||
assert.Equal(t, test.Port, port)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
// +build tidb
|
|
||||||
|
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/go-xorm/tidb"
|
|
||||||
"github.com/ngaut/log"
|
|
||||||
_ "github.com/pingcap/tidb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
EnableTiDB = true
|
|
||||||
log.SetLevelByString("error")
|
|
||||||
}
|
|
|
@ -119,7 +119,17 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
issue.loadRepo(e)
|
||||||
|
|
||||||
for _, watch := range watches {
|
for _, watch := range watches {
|
||||||
|
issue.Repo.Units = nil
|
||||||
|
if issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypePullRequests) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !issue.IsPull && !issue.Repo.CheckUnitUser(watch.UserID, false, UnitTypeIssues) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := notifyUser(watch.UserID); err != nil {
|
if err := notifyUser(watch.UserID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,12 +154,26 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||||
Name: ownerTeamName,
|
Name: ownerTeamName,
|
||||||
Authorize: AccessModeOwner,
|
Authorize: AccessModeOwner,
|
||||||
NumMembers: 1,
|
NumMembers: 1,
|
||||||
UnitTypes: allRepUnitTypes,
|
|
||||||
}
|
}
|
||||||
if _, err = sess.Insert(t); err != nil {
|
if _, err = sess.Insert(t); err != nil {
|
||||||
return fmt.Errorf("insert owner team: %v", err)
|
return fmt.Errorf("insert owner team: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert units for team
|
||||||
|
var units = make([]TeamUnit, 0, len(allRepUnitTypes))
|
||||||
|
for _, tp := range allRepUnitTypes {
|
||||||
|
units = append(units, TeamUnit{
|
||||||
|
OrgID: org.ID,
|
||||||
|
TeamID: t.ID,
|
||||||
|
Type: tp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Insert(&units); err != nil {
|
||||||
|
sess.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = sess.Insert(&TeamUser{
|
if _, err = sess.Insert(&TeamUser{
|
||||||
UID: owner.ID,
|
UID: owner.ID,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
|
@ -238,6 +252,7 @@ func deleteOrg(e *xorm.Session, u *User) error {
|
||||||
&Team{OrgID: u.ID},
|
&Team{OrgID: u.ID},
|
||||||
&OrgUser{OrgID: u.ID},
|
&OrgUser{OrgID: u.ID},
|
||||||
&TeamUser{OrgID: u.ID},
|
&TeamUser{OrgID: u.ID},
|
||||||
|
&TeamUnit{OrgID: u.ID},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("deleteBeans: %v", err)
|
return fmt.Errorf("deleteBeans: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -368,7 +383,7 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
|
||||||
func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) {
|
func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) {
|
||||||
ous := make([]*OrgUser, 0, 10)
|
ous := make([]*OrgUser, 0, 10)
|
||||||
sess := x.
|
sess := x.
|
||||||
Join("LEFT", "user", "`org_user`.org_id=`user`.id").
|
Join("LEFT", "`user`", "`org_user`.org_id=`user`.id").
|
||||||
Where("`org_user`.uid=?", uid)
|
Where("`org_user`.uid=?", uid)
|
||||||
if !all {
|
if !all {
|
||||||
// Only show public organizations
|
// Only show public organizations
|
||||||
|
@ -560,7 +575,7 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team,
|
||||||
return teams, e.
|
return teams, e.
|
||||||
Where("`team_user`.org_id = ?", org.ID).
|
Where("`team_user`.org_id = ?", org.ID).
|
||||||
Join("INNER", "team_user", "`team_user`.team_id = team.id").
|
Join("INNER", "team_user", "`team_user`.team_id = team.id").
|
||||||
Join("INNER", "user", "`user`.id=team_user.uid").
|
Join("INNER", "`user`", "`user`.id=team_user.uid").
|
||||||
And("`team_user`.uid = ?", userID).
|
And("`team_user`.uid = ?", userID).
|
||||||
Asc("`user`.name").
|
Asc("`user`.name").
|
||||||
Cols(cols...).
|
Cols(cols...).
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
@ -10,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,15 +28,16 @@ type Team struct {
|
||||||
Members []*User `xorm:"-"`
|
Members []*User `xorm:"-"`
|
||||||
NumRepos int
|
NumRepos int
|
||||||
NumMembers int
|
NumMembers int
|
||||||
UnitTypes []UnitType `xorm:"json"`
|
Units []*TeamUnit `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUnitTypes returns unit types the team owned, empty means all the unit types
|
func (t *Team) getUnits(e Engine) (err error) {
|
||||||
func (t *Team) GetUnitTypes() []UnitType {
|
if t.Units != nil {
|
||||||
if len(t.UnitTypes) == 0 {
|
return nil
|
||||||
return allRepUnitTypes
|
|
||||||
}
|
}
|
||||||
return t.UnitTypes
|
|
||||||
|
t.Units, err = getUnitsByTeamID(e, t.ID)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasWriteAccess returns true if team has at least write level access mode.
|
// HasWriteAccess returns true if team has at least write level access mode.
|
||||||
|
@ -178,6 +179,11 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
|
||||||
if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
|
if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := removeIssueWatchersByRepoID(e, teamUser.UID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -209,11 +215,12 @@ func (t *Team) RemoveRepository(repoID int64) error {
|
||||||
|
|
||||||
// UnitEnabled returns if the team has the given unit type enabled
|
// UnitEnabled returns if the team has the given unit type enabled
|
||||||
func (t *Team) UnitEnabled(tp UnitType) bool {
|
func (t *Team) UnitEnabled(tp UnitType) bool {
|
||||||
if len(t.UnitTypes) == 0 {
|
if err := t.getUnits(x); err != nil {
|
||||||
return true
|
log.Warn("Error loading repository (ID: %d) units: %s", t.ID, err.Error())
|
||||||
}
|
}
|
||||||
for _, u := range t.UnitTypes {
|
|
||||||
if u == tp {
|
for _, unit := range t.Units {
|
||||||
|
if unit.Type == tp {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,6 +277,17 @@ func NewTeam(t *Team) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert units for team
|
||||||
|
if len(t.Units) > 0 {
|
||||||
|
for _, unit := range t.Units {
|
||||||
|
unit.TeamID = t.ID
|
||||||
|
}
|
||||||
|
if _, err = sess.Insert(&t.Units); err != nil {
|
||||||
|
sess.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update organization number of teams.
|
// Update organization number of teams.
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
|
@ -374,11 +392,34 @@ func DeleteTeam(t *Team) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := t.getMembers(sess); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all accesses.
|
// Delete all accesses.
|
||||||
for _, repo := range t.Repos {
|
for _, repo := range t.Repos {
|
||||||
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
|
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove watches from all users and now unaccessible repos
|
||||||
|
for _, user := range t.Members {
|
||||||
|
has, err := hasAccess(sess, user.ID, repo, AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(sess, user.ID, repo.ID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err = removeIssueWatchersByRepoID(sess, user.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete team-repo
|
// Delete team-repo
|
||||||
|
@ -396,6 +437,13 @@ func DeleteTeam(t *Team) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete team-unit.
|
||||||
|
if _, err := sess.
|
||||||
|
Where("team_id=?", t.ID).
|
||||||
|
Delete(new(TeamUnit)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Delete team.
|
// Delete team.
|
||||||
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
|
if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -518,6 +566,10 @@ func AddTeamMember(team *Team, userID int64) error {
|
||||||
if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
|
if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(sess, userID, repo.ID, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
|
@ -558,6 +610,23 @@ func removeTeamMember(e *xorm.Session, team *Team, userID int64) error {
|
||||||
if err := repo.recalculateTeamAccesses(e, 0); err != nil {
|
if err := repo.recalculateTeamAccesses(e, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove watches from now unaccessible
|
||||||
|
has, err := hasAccess(e, userID, repo, AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(e, userID, repo.ID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := removeIssueWatchersByRepoID(e, userID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is a member of any team in the organization.
|
// Check if the user is a member of any team in the organization.
|
||||||
|
@ -646,3 +715,47 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, er
|
||||||
And("team_repo.repo_id = ?", repoID).
|
And("team_repo.repo_id = ?", repoID).
|
||||||
Find(&teams)
|
Find(&teams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ___________ ____ ___ .__ __
|
||||||
|
// \__ ___/___ _____ _____ | | \____ |__|/ |_
|
||||||
|
// | |_/ __ \\__ \ / \| | / \| \ __\
|
||||||
|
// | |\ ___/ / __ \| Y Y \ | / | \ || |
|
||||||
|
// |____| \___ >____ /__|_| /______/|___| /__||__|
|
||||||
|
// \/ \/ \/ \/
|
||||||
|
|
||||||
|
// TeamUnit describes all units of a repository
|
||||||
|
type TeamUnit struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
OrgID int64 `xorm:"INDEX"`
|
||||||
|
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
Type UnitType `xorm:"UNIQUE(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit returns Unit
|
||||||
|
func (t *TeamUnit) Unit() Unit {
|
||||||
|
return Units[t.Type]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnitsByTeamID(e Engine, teamID int64) (units []*TeamUnit, err error) {
|
||||||
|
return units, e.Where("team_id = ?", teamID).Find(&units)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTeamUnits updates a teams's units
|
||||||
|
func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) {
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = sess.Insert(units); err != nil {
|
||||||
|
sess.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
|
@ -489,8 +489,8 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, expectedCount, count)
|
assert.EqualValues(t, expectedCount, count)
|
||||||
}
|
}
|
||||||
testSuccess(2, 2)
|
testSuccess(2, 3)
|
||||||
testSuccess(4, 1)
|
testSuccess(4, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
|
@ -503,8 +503,8 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expectedRepoIDs, repoIDs)
|
assert.Equal(t, expectedRepoIDs, repoIDs)
|
||||||
}
|
}
|
||||||
testSuccess(2, 1, 100, []int64{3, 5})
|
testSuccess(2, 1, 100, []int64{3, 5, 32})
|
||||||
testSuccess(4, 0, 100, []int64{3})
|
testSuccess(4, 0, 100, []int64{3, 32})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||||
|
@ -522,8 +522,8 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, expectedRepos, repos)
|
assert.Equal(t, expectedRepos, repos)
|
||||||
}
|
}
|
||||||
testSuccess(2, []int64{3, 5})
|
testSuccess(2, []int64{3, 5, 32})
|
||||||
testSuccess(4, []int64{3})
|
testSuccess(4, []int64{3, 32})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||||
|
|
|
@ -448,6 +448,11 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rel.Repo = repo
|
||||||
|
if err = rel.LoadAttributes(); err != nil {
|
||||||
|
return fmt.Errorf("LoadAttributes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
mode, _ := accessLevel(x, u.ID, rel.Repo)
|
mode, _ := accessLevel(x, u.ID, rel.Repo)
|
||||||
if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{
|
if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{
|
||||||
Action: api.HookReleaseDeleted,
|
Action: api.HookReleaseDeleted,
|
||||||
|
|
|
@ -365,22 +365,14 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var allTypes = make(map[UnitType]struct{}, len(allRepUnitTypes))
|
|
||||||
for _, team := range teams {
|
|
||||||
// Administrators can not be limited
|
|
||||||
if team.Authorize >= AccessModeAdmin {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, unitType := range team.UnitTypes {
|
|
||||||
allTypes[unitType] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unique
|
// unique
|
||||||
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
|
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
|
||||||
for _, u := range repo.Units {
|
for _, u := range repo.Units {
|
||||||
if _, ok := allTypes[u.Type]; ok {
|
for _, team := range teams {
|
||||||
newRepoUnits = append(newRepoUnits, u)
|
if team.UnitEnabled(u.Type) {
|
||||||
|
newRepoUnits = append(newRepoUnits, u)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -789,7 +781,7 @@ var (
|
||||||
// DescriptionHTML does special handles to description and return HTML string.
|
// DescriptionHTML does special handles to description and return HTML string.
|
||||||
func (repo *Repository) DescriptionHTML() template.HTML {
|
func (repo *Repository) DescriptionHTML() template.HTML {
|
||||||
sanitize := func(s string) string {
|
sanitize := func(s string) string {
|
||||||
return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener">%[1]s</a>`, s)
|
return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener noreferrer">%[1]s</a>`, s)
|
||||||
}
|
}
|
||||||
return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize))
|
return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize))
|
||||||
}
|
}
|
||||||
|
@ -1353,7 +1345,17 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
|
||||||
units = append(units, RepoUnit{
|
units = append(units, RepoUnit{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
Type: tp,
|
Type: tp,
|
||||||
Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking, AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime},
|
Config: &IssuesConfig{
|
||||||
|
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
|
||||||
|
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
|
||||||
|
EnableDependencies: setting.Service.DefaultEnableDependencies,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if tp == UnitTypePullRequests {
|
||||||
|
units = append(units, RepoUnit{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Type: tp,
|
||||||
|
Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowSquash: true},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
units = append(units, RepoUnit{
|
units = append(units, RepoUnit{
|
||||||
|
@ -1848,6 +1850,12 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||||
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}); err != nil {
|
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
attachments := make([]*Attachment, 0, 5)
|
attachments := make([]*Attachment, 0, 5)
|
||||||
if err = sess.
|
if err = sess.
|
||||||
|
@ -1950,7 +1958,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||||
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
|
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) {
|
||||||
var repo Repository
|
var repo Repository
|
||||||
has, err := x.Select("repository.*").
|
has, err := x.Select("repository.*").
|
||||||
Join("INNER", "user", "`user`.id = repository.owner_id").
|
Join("INNER", "`user`", "`user`.id = repository.owner_id").
|
||||||
Where("repository.lower_name = ?", strings.ToLower(repoName)).
|
Where("repository.lower_name = ?", strings.ToLower(repoName)).
|
||||||
And("`user`.lower_name = ?", strings.ToLower(ownerName)).
|
And("`user`.lower_name = ?", strings.ToLower(ownerName)).
|
||||||
Get(&repo)
|
Get(&repo)
|
||||||
|
|
|
@ -172,5 +172,14 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(sess, uid, repo.ID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repository
|
||||||
|
if err := removeIssueWatchersByRepoID(sess, uid, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
count: 14},
|
count: 14},
|
||||||
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
||||||
count: 15},
|
count: 16},
|
||||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||||
count: 19},
|
count: 20},
|
||||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||||
count: 13},
|
count: 13},
|
||||||
|
@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
|
||||||
count: 11},
|
count: 11},
|
||||||
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
{name: "AllPublic/PublicRepositoriesOfOrganization",
|
||||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
|
||||||
count: 15},
|
count: 16},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|
|
@ -73,6 +73,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
|
||||||
type IssuesConfig struct {
|
type IssuesConfig struct {
|
||||||
EnableTimetracker bool
|
EnableTimetracker bool
|
||||||
AllowOnlyContributorsToTrackTime bool
|
AllowOnlyContributorsToTrackTime bool
|
||||||
|
EnableDependencies bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDB fills up a IssuesConfig from serialized format.
|
// FromDB fills up a IssuesConfig from serialized format.
|
||||||
|
@ -167,7 +168,6 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig {
|
||||||
func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
|
func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
|
||||||
return r.Config.(*ExternalTrackerConfig)
|
return r.Config.(*ExternalTrackerConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
|
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
|
||||||
return units, e.Where("repo_id = ?", repoID).Find(&units)
|
return units, e.Where("repo_id = ?", repoID).Find(&units)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
|
||||||
return watches, e.Where("`watch`.repo_id=?", repoID).
|
return watches, e.Where("`watch`.repo_id=?", repoID).
|
||||||
And("`user`.is_active=?", true).
|
And("`user`.is_active=?", true).
|
||||||
And("`user`.prohibit_login=?", false).
|
And("`user`.prohibit_login=?", false).
|
||||||
Join("INNER", "user", "`user`.id = `watch`.user_id").
|
Join("INNER", "`user`", "`user`.id = `watch`.user_id").
|
||||||
Find(&watches)
|
Find(&watches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +109,23 @@ func notifyWatchers(e Engine, act *Action) error {
|
||||||
|
|
||||||
act.ID = 0
|
act.ID = 0
|
||||||
act.UserID = watches[i].UserID
|
act.UserID = watches[i].UserID
|
||||||
|
act.Repo.Units = nil
|
||||||
|
|
||||||
|
switch act.OpType {
|
||||||
|
case ActionCommitRepo, ActionPushTag, ActionDeleteTag, ActionDeleteBranch:
|
||||||
|
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeCode) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case ActionCreateIssue, ActionCommentIssue, ActionCloseIssue, ActionReopenIssue:
|
||||||
|
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypeIssues) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case ActionCreatePullRequest, ActionMergePullRequest, ActionClosePullRequest, ActionReopenPullRequest:
|
||||||
|
if !act.Repo.CheckUnitUser(act.UserID, false, UnitTypePullRequests) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = e.InsertOne(act); err != nil {
|
if _, err = e.InsertOne(act); err != nil {
|
||||||
return fmt.Errorf("insert new action: %v", err)
|
return fmt.Errorf("insert new action: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
@ -20,10 +21,12 @@ func init() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||||
|
|
||||||
// Topic represents a topic of repositories
|
// Topic represents a topic of repositories
|
||||||
type Topic struct {
|
type Topic struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name string `xorm:"unique"`
|
Name string `xorm:"UNIQUE"`
|
||||||
RepoCount int
|
RepoCount int
|
||||||
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
|
||||||
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
|
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
|
||||||
|
@ -31,8 +34,8 @@ type Topic struct {
|
||||||
|
|
||||||
// RepoTopic represents associated repositories and topics
|
// RepoTopic represents associated repositories and topics
|
||||||
type RepoTopic struct {
|
type RepoTopic struct {
|
||||||
RepoID int64 `xorm:"unique(s)"`
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
TopicID int64 `xorm:"unique(s)"`
|
TopicID int64 `xorm:"UNIQUE(s)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrTopicNotExist represents an error that a topic is not exist
|
// ErrTopicNotExist represents an error that a topic is not exist
|
||||||
|
@ -51,6 +54,11 @@ func (err ErrTopicNotExist) Error() string {
|
||||||
return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
|
return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateTopic checks topics by length and match pattern rules
|
||||||
|
func ValidateTopic(topic string) bool {
|
||||||
|
return len(topic) <= 35 && topicPattern.MatchString(topic)
|
||||||
|
}
|
||||||
|
|
||||||
// GetTopicByName retrieves topic by name
|
// GetTopicByName retrieves topic by name
|
||||||
func GetTopicByName(name string) (*Topic, error) {
|
func GetTopicByName(name string) (*Topic, error) {
|
||||||
var topic Topic
|
var topic Topic
|
||||||
|
@ -182,6 +190,13 @@ func SaveTopics(repoID int64, topicNames ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
topicNames = make([]string, 0, 25)
|
||||||
|
if err := sess.Table("topic").Cols("name").
|
||||||
|
Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id").
|
||||||
|
Where("repo_topic.repo_id = ?", repoID).Desc("topic.repo_count").Find(&topicNames); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
|
if _, err := sess.ID(repoID).Cols("topics").Update(&Repository{
|
||||||
Topics: topicNames,
|
Topics: topicNames,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|
|
@ -55,3 +55,16 @@ func TestAddTopic(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 2, len(topics))
|
assert.EqualValues(t, 2, len(topics))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTopicValidator(t *testing.T) {
|
||||||
|
assert.True(t, ValidateTopic("12345"))
|
||||||
|
assert.True(t, ValidateTopic("2-test"))
|
||||||
|
assert.True(t, ValidateTopic("test-3"))
|
||||||
|
assert.True(t, ValidateTopic("first"))
|
||||||
|
assert.True(t, ValidateTopic("second-test-topic"))
|
||||||
|
assert.True(t, ValidateTopic("third-project-topic-with-max-length"))
|
||||||
|
|
||||||
|
assert.False(t, ValidateTopic("$fourth-test,topic"))
|
||||||
|
assert.False(t, ValidateTopic("-fifth-test-topic"))
|
||||||
|
assert.False(t, ValidateTopic("sixth-go-project-topic-with-excess-length"))
|
||||||
|
}
|
||||||
|
|
|
@ -374,9 +374,9 @@ func (u *User) GetFollowers(page int) ([]*User, error) {
|
||||||
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
||||||
Where("follow.follow_id=?", u.ID)
|
Where("follow.follow_id=?", u.ID)
|
||||||
if setting.UsePostgreSQL {
|
if setting.UsePostgreSQL {
|
||||||
sess = sess.Join("LEFT", "follow", `"user".id=follow.user_id`)
|
sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
|
||||||
} else {
|
} else {
|
||||||
sess = sess.Join("LEFT", "follow", "user.id=follow.user_id")
|
sess = sess.Join("LEFT", "follow", "`user`.id=follow.user_id")
|
||||||
}
|
}
|
||||||
return users, sess.Find(&users)
|
return users, sess.Find(&users)
|
||||||
}
|
}
|
||||||
|
@ -393,9 +393,9 @@ func (u *User) GetFollowing(page int) ([]*User, error) {
|
||||||
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
||||||
Where("follow.user_id=?", u.ID)
|
Where("follow.user_id=?", u.ID)
|
||||||
if setting.UsePostgreSQL {
|
if setting.UsePostgreSQL {
|
||||||
sess = sess.Join("LEFT", "follow", `"user".id=follow.follow_id`)
|
sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
|
||||||
} else {
|
} else {
|
||||||
sess = sess.Join("LEFT", "follow", "user.id=follow.follow_id")
|
sess = sess.Join("LEFT", "follow", "`user`.id=follow.follow_id")
|
||||||
}
|
}
|
||||||
return users, sess.Find(&users)
|
return users, sess.Find(&users)
|
||||||
}
|
}
|
||||||
|
@ -433,6 +433,17 @@ func (u *User) IsPasswordSet() bool {
|
||||||
// UploadAvatar saves custom avatar for user.
|
// UploadAvatar saves custom avatar for user.
|
||||||
// FIXME: split uploads to different subdirs in case we have massive users.
|
// FIXME: split uploads to different subdirs in case we have massive users.
|
||||||
func (u *User) UploadAvatar(data []byte) error {
|
func (u *User) UploadAvatar(data []byte) error {
|
||||||
|
imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("DecodeConfig: %v", err)
|
||||||
|
}
|
||||||
|
if imgCfg.Width > setting.AvatarMaxWidth {
|
||||||
|
return fmt.Errorf("Image width is to large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth)
|
||||||
|
}
|
||||||
|
if imgCfg.Height > setting.AvatarMaxHeight {
|
||||||
|
return fmt.Errorf("Image height is to large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight)
|
||||||
|
}
|
||||||
|
|
||||||
img, _, err := image.Decode(bytes.NewReader(data))
|
img, _, err := image.Decode(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Decode: %v", err)
|
return fmt.Errorf("Decode: %v", err)
|
||||||
|
@ -546,28 +557,46 @@ func (u *User) GetRepositories(page, pageSize int) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepositoryIDs returns repositories IDs where user owned
|
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes
|
||||||
func (u *User) GetRepositoryIDs() ([]int64, error) {
|
func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) {
|
||||||
var ids []int64
|
var ids []int64
|
||||||
return ids, x.Table("repository").Cols("id").Where("owner_id = ?", u.ID).Find(&ids)
|
|
||||||
|
sess := x.Table("repository").Cols("repository.id")
|
||||||
|
|
||||||
|
if len(units) > 0 {
|
||||||
|
sess = sess.Join("INNER", "repo_unit", "repository.id = repo_unit.repo_id")
|
||||||
|
sess = sess.In("repo_unit.type", units)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids, sess.Where("owner_id = ?", u.ID).Find(&ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgRepositoryIDs returns repositories IDs where user's team owned
|
// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes
|
||||||
func (u *User) GetOrgRepositoryIDs() ([]int64, error) {
|
func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) {
|
||||||
var ids []int64
|
var ids []int64
|
||||||
return ids, x.Table("repository").
|
|
||||||
|
sess := x.Table("repository").
|
||||||
Cols("repository.id").
|
Cols("repository.id").
|
||||||
Join("INNER", "team_user", "repository.owner_id = team_user.org_id AND team_user.uid = ?", u.ID).
|
Join("INNER", "team_user", "repository.owner_id = team_user.org_id").
|
||||||
|
Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true)
|
||||||
|
|
||||||
|
if len(units) > 0 {
|
||||||
|
sess = sess.Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id")
|
||||||
|
sess = sess.In("team_unit.type", units)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids, sess.
|
||||||
|
Where("team_user.uid = ?", u.ID).
|
||||||
GroupBy("repository.id").Find(&ids)
|
GroupBy("repository.id").Find(&ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
|
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
|
||||||
func (u *User) GetAccessRepoIDs() ([]int64, error) {
|
func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) {
|
||||||
ids, err := u.GetRepositoryIDs()
|
ids, err := u.GetRepositoryIDs(units...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ids2, err := u.GetOrgRepositoryIDs()
|
ids2, err := u.GetOrgRepositoryIDs(units...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -927,7 +956,7 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||||
Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil {
|
Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil {
|
||||||
return fmt.Errorf("get all watches: %v", err)
|
return fmt.Errorf("get all watches: %v", err)
|
||||||
}
|
}
|
||||||
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).Update(new(Repository)); err != nil {
|
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil {
|
||||||
return fmt.Errorf("decrease repository num_watches: %v", err)
|
return fmt.Errorf("decrease repository num_watches: %v", err)
|
||||||
}
|
}
|
||||||
// ***** END: Watch *****
|
// ***** END: Watch *****
|
||||||
|
@ -937,7 +966,7 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||||
if err = e.Table("star").Cols("star.repo_id").
|
if err = e.Table("star").Cols("star.repo_id").
|
||||||
Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil {
|
Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil {
|
||||||
return fmt.Errorf("get all stars: %v", err)
|
return fmt.Errorf("get all stars: %v", err)
|
||||||
} else if _, err = e.Decr("num_stars").In("id", starredRepoIDs).Update(new(Repository)); err != nil {
|
} else if _, err = e.Decr("num_stars").In("id", starredRepoIDs).NoAutoTime().Update(new(Repository)); err != nil {
|
||||||
return fmt.Errorf("decrease repository num_stars: %v", err)
|
return fmt.Errorf("decrease repository num_stars: %v", err)
|
||||||
}
|
}
|
||||||
// ***** END: Star *****
|
// ***** END: Star *****
|
||||||
|
|
|
@ -159,3 +159,25 @@ func BenchmarkHashPassword(b *testing.B) {
|
||||||
u.HashPassword(pass)
|
u.HashPassword(pass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetOrgRepositoryIDs(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
|
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
|
||||||
|
user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
|
||||||
|
|
||||||
|
accessibleRepos, err := user2.GetOrgRepositoryIDs()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization
|
||||||
|
assert.Equal(t, []int64{3, 5, 32}, accessibleRepos)
|
||||||
|
|
||||||
|
accessibleRepos, err = user4.GetOrgRepositoryIDs()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// User 4's team has access to private repo 3, repo 32 is a public repo of the organization
|
||||||
|
assert.Equal(t, []int64{3, 32}, accessibleRepos)
|
||||||
|
|
||||||
|
accessibleRepos, err = user5.GetOrgRepositoryIDs()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// User 5's team has no access to any repo
|
||||||
|
assert.Len(t, accessibleRepos, 0)
|
||||||
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo
|
||||||
profileURL = customURLMapping.ProfileURL
|
profileURL = customURLMapping.ProfileURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL)
|
provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user")
|
||||||
case "gplus":
|
case "gplus":
|
||||||
provider = gplus.New(clientID, clientSecret, callbackURL, "email")
|
provider = gplus.New(clientID, clientSecret, callbackURL, "email")
|
||||||
case "openidConnect":
|
case "openidConnect":
|
||||||
|
|
|
@ -114,6 +114,7 @@ type RepoSettingForm struct {
|
||||||
PullsAllowSquash bool
|
PullsAllowSquash bool
|
||||||
EnableTimetracker bool
|
EnableTimetracker bool
|
||||||
AllowOnlyContributorsToTrackTime bool
|
AllowOnlyContributorsToTrackTime bool
|
||||||
|
EnableIssueDependencies bool
|
||||||
|
|
||||||
// Admin settings
|
// Admin settings
|
||||||
EnableHealthCheck bool
|
EnableHealthCheck bool
|
||||||
|
@ -372,7 +373,7 @@ func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Error
|
||||||
|
|
||||||
// NewReleaseForm form for creating release
|
// NewReleaseForm form for creating release
|
||||||
type NewReleaseForm struct {
|
type NewReleaseForm struct {
|
||||||
TagName string `binding:"Required"`
|
TagName string `binding:"Required;GitRefName"`
|
||||||
Target string `form:"tag_target" binding:"Required"`
|
Target string `form:"tag_target" binding:"Required"`
|
||||||
Title string `binding:"Required"`
|
Title string `binding:"Required"`
|
||||||
Content string
|
Content string
|
||||||
|
|
|
@ -72,10 +72,11 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
|
||||||
|
|
||||||
// RegisterForm form for registering
|
// RegisterForm form for registering
|
||||||
type RegisterForm struct {
|
type RegisterForm struct {
|
||||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||||
Password string `binding:"Required;MaxSize(255)"`
|
Password string `binding:"Required;MaxSize(255)"`
|
||||||
Retype string
|
Retype string
|
||||||
|
GRecaptchaResponse string `form:"g-recaptcha-response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate valideates the fields
|
// Validate valideates the fields
|
||||||
|
|
|
@ -22,8 +22,9 @@ func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) b
|
||||||
|
|
||||||
// SignUpOpenIDForm form for signin up with OpenID
|
// SignUpOpenIDForm form for signin up with OpenID
|
||||||
type SignUpOpenIDForm struct {
|
type SignUpOpenIDForm struct {
|
||||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||||
|
GRecaptchaResponse string `form:"g-recaptcha-response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate valideates the fields
|
// Validate valideates the fields
|
||||||
|
|
|
@ -37,12 +37,6 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check non-logged users landing page.
|
|
||||||
if !ctx.IsSigned && ctx.Req.RequestURI == "/" && setting.LandingPageURL != setting.LandingPageHome {
|
|
||||||
ctx.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to dashboard if user tries to visit any non-login page.
|
// Redirect to dashboard if user tries to visit any non-login page.
|
||||||
if options.SignOutRequired && ctx.IsSigned && ctx.Req.RequestURI != "/" {
|
if options.SignOutRequired && ctx.IsSigned && ctx.Req.RequestURI != "/" {
|
||||||
ctx.Redirect(setting.AppSubURL + "/")
|
ctx.Redirect(setting.AppSubURL + "/")
|
||||||
|
|
|
@ -104,6 +104,11 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
|
||||||
r.IsWriter() || issue.IsPoster(user.ID) || isAssigned)
|
r.IsWriter() || issue.IsPoster(user.ID) || isAssigned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
|
||||||
|
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
|
||||||
|
return r.Repository.IsDependenciesEnabled() && r.IsWriter()
|
||||||
|
}
|
||||||
|
|
||||||
// GetCommitsCount returns cached commit count for current view
|
// GetCommitsCount returns cached commit count for current view
|
||||||
func (r *Repository) GetCommitsCount() (int64, error) {
|
func (r *Repository) GetCommitsCount() (int64, error) {
|
||||||
var contextName string
|
var contextName string
|
||||||
|
|
|
@ -85,9 +85,12 @@ type link struct {
|
||||||
|
|
||||||
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
|
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
|
||||||
|
|
||||||
|
func isOidValid(oid string) bool {
|
||||||
|
return oidRegExp.MatchString(oid)
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectOidHandler is the main request routing entry point into LFS server functions
|
// ObjectOidHandler is the main request routing entry point into LFS server functions
|
||||||
func ObjectOidHandler(ctx *context.Context) {
|
func ObjectOidHandler(ctx *context.Context) {
|
||||||
|
|
||||||
if !setting.LFS.StartServer {
|
if !setting.LFS.StartServer {
|
||||||
writeStatus(ctx, 404)
|
writeStatus(ctx, 404)
|
||||||
return
|
return
|
||||||
|
@ -110,6 +113,11 @@ func ObjectOidHandler(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAuthenticatedRepoAndMeta(ctx *context.Context, rv *RequestVars, requireWrite bool) (*models.LFSMetaObject, *models.Repository) {
|
func getAuthenticatedRepoAndMeta(ctx *context.Context, rv *RequestVars, requireWrite bool) (*models.LFSMetaObject, *models.Repository) {
|
||||||
|
if !isOidValid(rv.Oid) {
|
||||||
|
writeStatus(ctx, 404)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
|
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
|
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
|
||||||
|
@ -222,7 +230,7 @@ func PostHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !oidRegExp.MatchString(rv.Oid) {
|
if !isOidValid(rv.Oid) {
|
||||||
writeStatus(ctx, 404)
|
writeStatus(ctx, 404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -249,7 +257,6 @@ func PostHandler(ctx *context.Context) {
|
||||||
|
|
||||||
// BatchHandler provides the batch api
|
// BatchHandler provides the batch api
|
||||||
func BatchHandler(ctx *context.Context) {
|
func BatchHandler(ctx *context.Context) {
|
||||||
|
|
||||||
if !setting.LFS.StartServer {
|
if !setting.LFS.StartServer {
|
||||||
writeStatus(ctx, 404)
|
writeStatus(ctx, 404)
|
||||||
return
|
return
|
||||||
|
@ -266,6 +273,10 @@ func BatchHandler(ctx *context.Context) {
|
||||||
|
|
||||||
// Create a response object
|
// Create a response object
|
||||||
for _, object := range bv.Objects {
|
for _, object := range bv.Objects {
|
||||||
|
if !isOidValid(object.Oid) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo)
|
repository, err := models.GetRepositoryByOwnerAndName(object.User, object.Repo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -292,12 +303,10 @@ func BatchHandler(ctx *context.Context) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if oidRegExp.MatchString(object.Oid) {
|
// Object is not found
|
||||||
// Object is not found
|
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
|
||||||
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
|
if err == nil {
|
||||||
if err == nil {
|
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
|
||||||
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
58
modules/markup/csv/csv.go
Normal file
58
modules/markup/csv/csv.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
markup.RegisterParser(Parser{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser implements markup.Parser for orgmode
|
||||||
|
type Parser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements markup.Parser
|
||||||
|
func (Parser) Name() string {
|
||||||
|
return "csv"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions implements markup.Parser
|
||||||
|
func (Parser) Extensions() []string {
|
||||||
|
return []string{".csv"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render implements markup.Parser
|
||||||
|
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
|
||||||
|
rd := csv.NewReader(bytes.NewReader(rawBytes))
|
||||||
|
var tmpBlock bytes.Buffer
|
||||||
|
tmpBlock.WriteString(`<table class="table">`)
|
||||||
|
for {
|
||||||
|
fields, err := rd.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpBlock.WriteString("<tr>")
|
||||||
|
for _, field := range fields {
|
||||||
|
tmpBlock.WriteString("<td>")
|
||||||
|
tmpBlock.WriteString(html.EscapeString(field))
|
||||||
|
tmpBlock.WriteString("</td>")
|
||||||
|
}
|
||||||
|
tmpBlock.WriteString("<tr>")
|
||||||
|
}
|
||||||
|
tmpBlock.WriteString("</table>")
|
||||||
|
|
||||||
|
return tmpBlock.Bytes()
|
||||||
|
}
|
25
modules/markup/csv/csv_test.go
Normal file
25
modules/markup/csv/csv_test.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRenderCSV(t *testing.T) {
|
||||||
|
var parser Parser
|
||||||
|
var kases = map[string]string{
|
||||||
|
"a": "<table class=\"table\"><tr><td>a</td><tr></table>",
|
||||||
|
"1,2": "<table class=\"table\"><tr><td>1</td><td>2</td><tr></table>",
|
||||||
|
"<br/>": "<table class=\"table\"><tr><td><br/></td><tr></table>",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range kases {
|
||||||
|
res := parser.Render([]byte(k), "", nil, false)
|
||||||
|
assert.EqualValues(t, v, string(res))
|
||||||
|
}
|
||||||
|
}
|
|
@ -469,6 +469,9 @@ func shortLinkProcessorFull(ctx *postProcessCtx, node *html.Node, noLink bool) {
|
||||||
} else {
|
} else {
|
||||||
link = strings.Replace(link, " ", "-", -1)
|
link = strings.Replace(link, " ", "-", -1)
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(link, "/") {
|
||||||
|
link = url.PathEscape(link)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
urlPrefix := ctx.urlPrefix
|
urlPrefix := ctx.urlPrefix
|
||||||
if image {
|
if image {
|
||||||
|
|
|
@ -82,12 +82,18 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||||
rawtree := util.URLJoin(AppSubURL, "raw", "master")
|
rawtree := util.URLJoin(AppSubURL, "raw", "master")
|
||||||
url := util.URLJoin(tree, "Link")
|
url := util.URLJoin(tree, "Link")
|
||||||
otherURL := util.URLJoin(tree, "Other-Link")
|
otherURL := util.URLJoin(tree, "Other-Link")
|
||||||
|
encodedURL := util.URLJoin(tree, "Link%3F")
|
||||||
imgurl := util.URLJoin(rawtree, "Link.jpg")
|
imgurl := util.URLJoin(rawtree, "Link.jpg")
|
||||||
otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg")
|
otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg")
|
||||||
|
encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg")
|
||||||
|
notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg")
|
||||||
urlWiki := util.URLJoin(AppSubURL, "wiki", "Link")
|
urlWiki := util.URLJoin(AppSubURL, "wiki", "Link")
|
||||||
otherURLWiki := util.URLJoin(AppSubURL, "wiki", "Other-Link")
|
otherURLWiki := util.URLJoin(AppSubURL, "wiki", "Other-Link")
|
||||||
|
encodedURLWiki := util.URLJoin(AppSubURL, "wiki", "Link%3F")
|
||||||
imgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg")
|
imgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg")
|
||||||
otherImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link+Other.jpg")
|
otherImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link+Other.jpg")
|
||||||
|
encodedImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "Link+%23.jpg")
|
||||||
|
notencodedImgurlWiki := util.URLJoin(AppSubURL, "wiki", "raw", "some", "path", "Link+#.jpg")
|
||||||
favicon := "http://google.com/favicon.ico"
|
favicon := "http://google.com/favicon.ico"
|
||||||
|
|
||||||
test(
|
test(
|
||||||
|
@ -134,4 +140,24 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||||
"[[Link]] [[Other Link]]",
|
"[[Link]] [[Other Link]]",
|
||||||
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
|
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
|
||||||
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a></p>`)
|
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a></p>`)
|
||||||
|
test(
|
||||||
|
"[[Link?]]",
|
||||||
|
`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
|
||||||
|
`<p><a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
|
||||||
|
test(
|
||||||
|
"[[Link]] [[Other Link]] [[Link?]]",
|
||||||
|
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
|
||||||
|
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
|
||||||
|
test(
|
||||||
|
"[[Link #.jpg]]",
|
||||||
|
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`"/></a></p>`,
|
||||||
|
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`"/></a></p>`)
|
||||||
|
test(
|
||||||
|
"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
|
||||||
|
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Title" alt="AltName"/></a></p>`,
|
||||||
|
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
|
||||||
|
test(
|
||||||
|
"[[some/path/Link #.jpg]]",
|
||||||
|
`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`"/></a></p>`,
|
||||||
|
`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`"/></a></p>`)
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user