Merge branch 'master' into fix_tag

This commit is contained in:
Lunny Xiao 2018-06-04 09:27:05 +08:00 committed by GitHub
commit 07c5fc5611
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
802 changed files with 65039 additions and 48221 deletions

View File

@ -1,5 +1,5 @@
workspace: workspace:
base: /srv/app base: /go
path: src/code.gitea.io/gitea path: src/code.gitea.io/gitea
clone: clone:
@ -56,27 +56,25 @@ pipeline:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
build-without-gcc: build-without-gcc:
image: webhippie/golang:1.8 image: golang:1.8
pull: true pull: true
environment:
GOPATH: /srv/app
commands: commands:
- go build -o gitea_no_gcc # test if build succeeds without the sqlite tag - go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
when: when:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
build: build:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
environment: environment:
TAGS: bindata sqlite TAGS: bindata sqlite
GOPATH: /srv/app
commands: commands:
- make clean - make clean
- make generate - make generate
- make vet - make vet
- make lint - make lint
- make fmt-check - make fmt-check
- make swagger-check
- make misspell-check - make misspell-check
- make test-vendor - make test-vendor
- make build - make build
@ -84,12 +82,11 @@ pipeline:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
test: test:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
TAGS: bindata sqlite TAGS: bindata sqlite
GOPATH: /srv/app
commands: commands:
- make unit-test-coverage - make unit-test-coverage
when: when:
@ -97,12 +94,11 @@ pipeline:
branch: [ master ] branch: [ master ]
test: test:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
TAGS: bindata sqlite TAGS: bindata sqlite
GOPATH: /srv/app
commands: commands:
- make test - make test
when: when:
@ -110,12 +106,11 @@ pipeline:
branch: [ release/* ] branch: [ release/* ]
test: test:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
TAGS: bindata TAGS: bindata
GOPATH: /srv/app
commands: commands:
- make test - make test
when: when:
@ -123,60 +118,64 @@ pipeline:
# Commented until db locking have been resolved! # Commented until db locking have been resolved!
# test-sqlite: # test-sqlite:
# image: webhippie/golang:edge # image: golang:1.10
# pull: true # pull: true
# group: test # group: test
# environment: # environment:
# TAGS: bindata # TAGS: bindata
# GOPATH: /srv/app
# commands: # commands:
# - make test-sqlite # - make test-sqlite
# when: # when:
# event: [ push, tag, pull_request ] # event: [ push, tag, pull_request ]
test-mysql: test-mysql:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
TAGS: bindata TAGS: bindata
GOPATH: /srv/app TEST_LDAP: "1"
commands: commands:
- curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
- apt-get install -y git-lfs
- make integration-test-coverage - make integration-test-coverage
when: when:
event: [ push, pull_request ] event: [ push, pull_request ]
branch: [ master ] branch: [ master ]
test-mysql: test-mysql:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
TAGS: bindata TAGS: bindata
GOPATH: /srv/app TEST_LDAP: "1"
commands: commands:
- curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
- apt-get install -y git-lfs
- make test-mysql - make test-mysql
when: when:
event: [ tag ] event: [ tag ]
test-pgsql: test-pgsql:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
TAGS: bindata TAGS: bindata
GOPATH: /srv/app TEST_LDAP: "1"
commands: commands:
- curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
- apt-get install -y git-lfs
- make test-pgsql - make test-pgsql
when: when:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
generate-coverage: generate-coverage:
image: webhippie/golang:edge image: golang:1.10
pull: true pull: true
environment: environment:
TAGS: bindata TAGS: bindata
GOPATH: /srv/app
commands: commands:
- make coverage - make coverage
when: when:
@ -197,8 +196,8 @@ pipeline:
pull: true pull: true
environment: environment:
TAGS: bindata sqlite TAGS: bindata sqlite
GOPATH: /srv/app
commands: commands:
- export PATH=$PATH:$GOPATH/bin
- make release - make release
when: when:
event: [ push, tag ] event: [ push, tag ]
@ -237,16 +236,7 @@ pipeline:
branch: [ master ] branch: [ master ]
docker: docker:
image: plugins/docker:17.05 image: plugins/docker:17.12
pull: true
secrets: [ docker_username, docker_password ]
repo: gitea/gitea
tags: [ '${DRONE_TAG##v}' ]
when:
event: [ tag ]
docker:
image: plugins/docker:17.05
pull: true pull: true
secrets: [ docker_username, docker_password ] secrets: [ docker_username, docker_password ]
repo: gitea/gitea repo: gitea/gitea
@ -256,14 +246,13 @@ pipeline:
branch: [ release/* ] branch: [ release/* ]
docker: docker:
image: plugins/docker:17.05 image: plugins/docker:17.12
pull: true
secrets: [ docker_username, docker_password ] secrets: [ docker_username, docker_password ]
pull: true
repo: gitea/gitea repo: gitea/gitea
tags: [ 'latest' ] default_tags: true
when: when:
event: [ push ] event: [ push, tag ]
branch: [ master ]
release: release:
image: plugins/s3:1 image: plugins/s3:1
@ -351,3 +340,8 @@ services:
- POSTGRES_DB=test - POSTGRES_DB=test
when: when:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
ldap:
image: gitea/test-openldap:latest
when:
event: [ push, tag, pull_request ]

10
.gitignore vendored
View File

@ -59,3 +59,13 @@ coverage.all
/integrations/mysql.ini /integrations/mysql.ini
/integrations/pgsql.ini /integrations/pgsql.ini
/node_modules /node_modules
# Snapcraft
snap/.snapcraft/
parts/
stage/
prime/
*.snap
*.snap-build
*_source.tar.bz2

View File

@ -4,11 +4,32 @@ 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.4.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.4.0-rc1) - 2018-01-31 ## [1.4.1](https://github.com/go-gitea/gitea/releases/tag/v1.4.1) - 2018-05-03
* BREAKING
* Add "error" as reserved username (#3882) (#3886)
* SECURITY
* Do not allow inactive users to access repositories using private key (#3887) (#3889)
* Fix path cleanup in file editor, when initilizing new repository and LFS oids (#3871) (#3873)
* Remove unnecessary allowed safe HTML (#3778) (#3779)
* Correctly check http git access rights for reverse proxy authorized users (#3721) (#3743)
* BUGFIXES
* Fix to use only needed columns from tables to get repository git paths (#3870) (#3883)
* Fix GPG expire time display when time is zero (#3584) (#3884)
* Fix to update only issue last update time when adding a comment (#3855) (#3860)
* Fix repository star count after deleting user (#3781) (#3783)
* Use the active branch for the code tab (#3720) (#3776)
* Set default branch name on first push (#3715) (#3723)
* Show clipboard button if disable HTTP of git protocol (#3773) (#3774)
## [1.4.0](https://github.com/go-gitea/gitea/releases/tag/v1.4.0) - 2018-03-25
* BREAKING * BREAKING
* Drop deprecated GOGS\_WORK\_DIR use (#2946) * Drop deprecated GOGS\_WORK\_DIR use (#2946)
* Fix API status code for hook creation (#2814) * Fix API status code for hook creation (#2814)
* SECURITY * SECURITY
* Escape branch name in dropdown menu (#3691) (#3692)
* Refactor and simplify to correctly validate redirect to URL (#3674) (#3676)
* Fix escaping changed title in comments (#3530) (#3534)
 * Escape search query (#3486) (#3488)
* Sanitize logs for mirror sync (#3057) * Sanitize logs for mirror sync (#3057)
* FEATURE * FEATURE
* Serve .patch and .diff for pull requests (#3305, #3293) * Serve .patch and .diff for pull requests (#3305, #3293)
@ -24,6 +45,17 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Add dingtalk webhook (#2777) * Add dingtalk webhook (#2777)
* Responsive view (#2750) * Responsive view (#2750)
* BUGFIXES * BUGFIXES
* Fix wiki inter-links with spaces (#3560) (#3632)
* Fix query protected branch bug (#3563) (#3571)
* Fix remove team member issue (#3566) (#3570)
* Fix the protected branch panic issue (#3567) (#3569)
* If Mirrors repository no content is fetched, updated time should not be changed (#3551) (#3565)
* Bug fix for mirrored repository releases sorted (#3522) (#3555)
* Add issue closed time column to fix activity closed issues list (#3537) (#3540)
 * Update markbates/goth library to support OAuth2 with new dropbox API (#3533) (#3539)
 * Fixes missing avatars in offline mode (#3471) (#3477)
 * Fix synchronization bug in repo indexer (#3455) (#3461)
 * Fix rendering of wiki page list if wiki repo contains other files (#3454) (#3463)
* Fix webhook X-GitHub-* headers casing for better compatibility (#3429) * Fix webhook X-GitHub-* headers casing for better compatibility (#3429)
* Add content type and doctype to requests made with go-get (#3426, #3423) * Add content type and doctype to requests made with go-get (#3426, #3423)
* Fix SQL type error for webhooks (#3424) * Fix SQL type error for webhooks (#3424)

View File

@ -69,14 +69,14 @@ 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` within your working directory and it will try you can simply call `drone exec --local --build-event "pull_request"` within
to run the test suite locally. your working directory and it will try to run the test suite locally.
## Vendoring ## Vendoring
We keep a cached copy of dependencies within the `vendor/` directory, We keep a cached copy of dependencies within the `vendor/` directory,
managing updates via [govendor](http://github.com/kardianos/govendor). managing updates via [dep](https://github.com/golang/dep).
Pull requests should only include `vendor/` updates if they are part of Pull requests should only include `vendor/` updates if they are part of
the same change, be it a bugfix or a feature addition. the same change, be it a bugfix or a feature addition.
@ -85,6 +85,8 @@ The `vendor/` update needs to be justified as part of the PR description,
and must be verified by the reviewers and/or merger to always reference and must be verified by the reviewers and/or merger to always reference
an existing upstream commit. an existing upstream commit.
You can find more information on how to get started with it on the [dep project website](https://golang.github.io/dep/docs/introduction.html).
## Translation ## Translation
We do all translation work inside [Crowdin](https://crowdin.com/project/gitea). We do all translation work inside [Crowdin](https://crowdin.com/project/gitea).
@ -112,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:

878
Gopkg.lock generated Normal file
View File

@ -0,0 +1,878 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "code.gitea.io/git"
packages = ["."]
revision = "31f4b8e8c805438ac6d8914b38accb1d8aaf695e"
[[projects]]
branch = "master"
name = "code.gitea.io/sdk"
packages = ["gitea"]
revision = "b2308e3f700875a3642a78bd3f6e5db8ef6f974d"
[[projects]]
name = "github.com/PuerkitoBio/goquery"
packages = ["."]
revision = "ed7d758e9a34ba1f55e8084e0d731448b46921a8"
[[projects]]
name = "github.com/RoaringBitmap/roaring"
packages = ["."]
revision = "1a28a7fa985680f9f4e1644c0a857ec359a444b0"
version = "v0.4.7"
[[projects]]
branch = "master"
name = "github.com/Smerity/govarint"
packages = ["."]
revision = "7265e41f48f15fd61751e16da866af3c704bb3ab"
[[projects]]
branch = "master"
name = "github.com/Unknwon/cae"
packages = [
".",
"zip"
]
revision = "c6aac99ea2cae2ebaf23f26f76b04fe3fcfc9f8c"
[[projects]]
branch = "master"
name = "github.com/Unknwon/com"
packages = ["."]
revision = "7677a1d7c1137cd3dd5ba7a076d0c898a1ef4520"
[[projects]]
branch = "master"
name = "github.com/Unknwon/i18n"
packages = ["."]
revision = "b64d336589669d317928070e70ba0ae558f16633"
[[projects]]
name = "github.com/Unknwon/paginater"
packages = ["."]
revision = "7748a72e01415173a27d79866b984328e7b0c12b"
[[projects]]
name = "github.com/andybalholm/cascadia"
packages = ["."]
revision = "349dd0209470eabd9514242c688c403c0926d266"
[[projects]]
name = "github.com/blevesearch/bleve"
packages = [
".",
"analysis",
"analysis/analyzer/custom",
"analysis/analyzer/standard",
"analysis/datetime/flexible",
"analysis/datetime/optional",
"analysis/lang/en",
"analysis/token/camelcase",
"analysis/token/lowercase",
"analysis/token/porter",
"analysis/token/stop",
"analysis/token/unicodenorm",
"analysis/token/unique",
"analysis/tokenizer/unicode",
"document",
"geo",
"index",
"index/scorch",
"index/scorch/mergeplan",
"index/scorch/segment",
"index/scorch/segment/mem",
"index/scorch/segment/zap",
"index/store",
"index/store/boltdb",
"index/store/gtreap",
"index/upsidedown",
"mapping",
"numeric",
"registry",
"search",
"search/collector",
"search/facet",
"search/highlight",
"search/highlight/format/html",
"search/highlight/fragmenter/simple",
"search/highlight/highlighter/html",
"search/highlight/highlighter/simple",
"search/query",
"search/scorer",
"search/searcher"
]
revision = "ff210fbc6d348ad67aa5754eaea11a463fcddafd"
[[projects]]
branch = "master"
name = "github.com/blevesearch/go-porterstemmer"
packages = ["."]
revision = "23a2c8e5cf1f380f27722c6d2ae8896431dc7d0e"
[[projects]]
name = "github.com/blevesearch/segment"
packages = ["."]
revision = "db70c57796cc8c310613541dfade3dce627d09c7"
[[projects]]
name = "github.com/boltdb/bolt"
packages = ["."]
revision = "ccd680d8c1a0179ac3d68f692b01e1a1589cbfc7"
source = "github.com/go-gitea/bolt"
[[projects]]
name = "github.com/boombuler/barcode"
packages = [
".",
"qr",
"utils"
]
revision = "fe0f26ff6d26693948ee8189aa064ee8c54141fa"
[[projects]]
name = "github.com/bradfitz/gomemcache"
packages = ["memcache"]
revision = "fb1f79c6b65acda83063cbc69f6bba1522558bfc"
[[projects]]
name = "github.com/chaseadamsio/goorgeous"
packages = ["."]
revision = "098da33fde5f9220736531b3cb26a2dec86a8367"
[[projects]]
name = "github.com/coreos/etcd"
packages = ["error"]
revision = "01c303113d0a3d5a8075864321c3aedb72035bdd"
[[projects]]
branch = "master"
name = "github.com/coreos/go-etcd"
packages = ["etcd"]
revision = "003851be7bb0694fe3cc457a49529a19388ee7cf"
[[projects]]
branch = "master"
name = "github.com/couchbase/vellum"
packages = [
".",
"regexp",
"utf8"
]
revision = "eb6ae3743b3f300f2136f83ca78c08cc071edbd4"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/denisenkom/go-mssqldb"
packages = ["."]
revision = "e32ca5036449b7ea12c62ed761ea1ad7fc88a4e2"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "9ed569b5d1ac936e6494082958d63a6aa4fff99a"
[[projects]]
branch = "master"
name = "github.com/edsrzf/mmap-go"
packages = ["."]
revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e"
[[projects]]
name = "github.com/elazarl/go-bindata-assetfs"
packages = ["."]
revision = "57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2"
[[projects]]
name = "github.com/ethantkoenig/rupture"
packages = ["."]
revision = "0a76f03a811abcca2e6357329b673e9bb8ef9643"
[[projects]]
branch = "master"
name = "github.com/facebookgo/clock"
packages = ["."]
revision = "600d898af40aa09a7a93ecb9265d87b0504b6f03"
[[projects]]
name = "github.com/facebookgo/grace"
packages = [
"gracehttp",
"gracenet"
]
revision = "5729e484473f52048578af1b80d0008c7024089b"
[[projects]]
branch = "master"
name = "github.com/facebookgo/httpdown"
packages = ["."]
revision = "a3b1354551a26449fbe05f5d855937f6e7acbd71"
[[projects]]
branch = "master"
name = "github.com/facebookgo/stats"
packages = ["."]
revision = "1b76add642e42c6ffba7211ad7b3939ce654526e"
[[projects]]
branch = "master"
name = "github.com/glycerine/go-unsnap-stream"
packages = ["."]
revision = "9f0cb55181dd3a0a4c168d3dbc72d4aca4853126"
[[projects]]
branch = "master"
name = "github.com/go-macaron/bindata"
packages = ["."]
revision = "85786f57eee3e5544a9cc24fa2afe425b97a8652"
[[projects]]
name = "github.com/go-macaron/binding"
packages = ["."]
revision = "9440f336b443056c90d7d448a0a55ad8c7599880"
[[projects]]
branch = "master"
name = "github.com/go-macaron/cache"
packages = [
".",
"memcache",
"redis"
]
revision = "56173531277692bc2925924d51fda1cd0a6b8178"
[[projects]]
name = "github.com/go-macaron/captcha"
packages = ["."]
revision = "8aa5919789ab301e865595eb4b1114d6b9847deb"
[[projects]]
branch = "master"
name = "github.com/go-macaron/csrf"
packages = ["."]
revision = "503617c6b37257a55dff6293ec28556506c3a9a8"
[[projects]]
branch = "master"
name = "github.com/go-macaron/gzip"
packages = ["."]
revision = "cad1c6580a07c56f5f6bc52d66002a05985c5854"
[[projects]]
branch = "master"
name = "github.com/go-macaron/i18n"
packages = ["."]
revision = "ef57533c3b0fc2d8581deda14937e52f11a203ab"
[[projects]]
branch = "master"
name = "github.com/go-macaron/inject"
packages = ["."]
revision = "d8a0b8677191f4380287cfebd08e462217bac7ad"
[[projects]]
name = "github.com/go-macaron/session"
packages = [
".",
"redis"
]
revision = "66031fcb37a0fff002a1f028eb0b3a815c78306b"
[[projects]]
name = "github.com/go-macaron/toolbox"
packages = ["."]
revision = "99a42f20e9e88daec5c0d7beb4e7eac134680ab0"
[[projects]]
name = "github.com/go-sql-driver/mysql"
packages = ["."]
revision = "ce924a41eea897745442daaa1739089b0f3f561d"
[[projects]]
name = "github.com/go-xorm/builder"
packages = ["."]
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738"
[[projects]]
name = "github.com/go-xorm/core"
packages = ["."]
revision = "cb1d0ca71f42d3ee1bf4aba7daa16099bc31a7e9"
[[projects]]
name = "github.com/go-xorm/tidb"
packages = ["."]
revision = "21e49190ce47a766fa741cf7edc831a30c12c6ac"
[[projects]]
name = "github.com/go-xorm/xorm"
packages = ["."]
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
[[projects]]
branch = "master"
name = "github.com/gogits/chardet"
packages = ["."]
revision = "2404f777256163ea3eadb273dada5dcb037993c0"
[[projects]]
name = "github.com/gogits/cron"
packages = ["."]
revision = "7f3990acf1833faa5ebd0e86f0a4c72a4b5eba3c"
[[projects]]
name = "github.com/golang/protobuf"
packages = ["proto"]
revision = "99511271042a09d1e01baea8781caa5210fec66e"
[[projects]]
name = "github.com/golang/snappy"
packages = ["."]
revision = "5f1c01d9f64b941dd9582c638279d046eda6ca31"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "757bef944d0f21880861c2dd9c871ca543023cba"
[[projects]]
name = "github.com/gorilla/securecookie"
packages = ["."]
revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/sessions"
packages = ["."]
revision = "ca9ada44574153444b00d3fd9c8559e4cc95f896"
version = "v1.1"
[[projects]]
name = "github.com/issue9/identicon"
packages = ["."]
revision = "d36b54562f4cf70c83653e13dc95c220c79ef521"
[[projects]]
name = "github.com/jaytaylor/html2text"
packages = ["."]
revision = "8fb95d837f7d6db1913fecfd7bcc5333e6499596"
[[projects]]
name = "github.com/juju/errors"
packages = ["."]
revision = "b2c7a7da5b2995941048f60146e67702a292e468"
[[projects]]
name = "github.com/kballard/go-shellquote"
packages = ["."]
revision = "cd60e84ee657ff3dc51de0b4f55dd299a3e136f2"
[[projects]]
name = "github.com/keybase/go-crypto"
packages = [
"brainpool",
"cast5",
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"openpgp",
"openpgp/armor",
"openpgp/ecdh",
"openpgp/elgamal",
"openpgp/errors",
"openpgp/packet",
"openpgp/s2k",
"rsa"
]
revision = "00ac4db533f63ef97576cbc7b07939ff7daf7329"
[[projects]]
name = "github.com/klauspost/compress"
packages = [
"flate",
"gzip"
]
revision = "8df558b6cb6f9b445f9586446cfe7223e7d8bd6b"
version = "v1.1"
[[projects]]
name = "github.com/klauspost/cpuid"
packages = ["."]
revision = "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
version = "v1.0"
[[projects]]
name = "github.com/klauspost/crc32"
packages = ["."]
revision = "cb6bfca970f6908083f26f39a79009d608efd5cd"
version = "v1.1"
[[projects]]
name = "github.com/lafriks/xormstore"
packages = [
".",
"util"
]
revision = "9cab149ea91875cf056211bd6ef82379fce9cb67"
version = "v1.0.0"
[[projects]]
name = "github.com/lib/pq"
packages = [
".",
"oid"
]
revision = "456514e2defec52e0cd37f90ccf17ec8b28295e2"
[[projects]]
branch = "master"
name = "github.com/lunny/dingtalk_webhook"
packages = ["."]
revision = "e3534c89ef969912856dfa39e56b09e58c5f5daf"
[[projects]]
name = "github.com/markbates/goth"
packages = [
".",
"gothic",
"providers/bitbucket",
"providers/dropbox",
"providers/facebook",
"providers/github",
"providers/gitlab",
"providers/gplus",
"providers/openidConnect",
"providers/twitter"
]
revision = "4933f155d89c3c52ab4ca545c6602cf4a1e87913"
version = "1.45.5"
[[projects]]
name = "github.com/mattn/go-sqlite3"
packages = ["."]
revision = "acfa60124032040b9f5a9406f5a772ee16fe845e"
[[projects]]
branch = "master"
name = "github.com/mcuadros/go-version"
packages = ["."]
revision = "88e56e02bea1c203c99222c365fa52a69996ccac"
[[projects]]
name = "github.com/microcosm-cc/bluemonday"
packages = ["."]
revision = "f77f16ffc87a6a58814e64ae72d55f9c41374e6d"
[[projects]]
name = "github.com/mrjones/oauth"
packages = ["."]
revision = "3f67d9c274355678b2f9844b08d643e2f9213340"
[[projects]]
branch = "master"
name = "github.com/mschoch/smat"
packages = ["."]
revision = "90eadee771aeab36e8bf796039b8c261bebebe4f"
[[projects]]
name = "github.com/msteinert/pam"
packages = ["."]
revision = "02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63"
[[projects]]
name = "github.com/nfnt/resize"
packages = ["."]
revision = "891127d8d1b52734debe1b3c3d7e747502b6c366"
[[projects]]
name = "github.com/ngaut/deadline"
packages = ["."]
revision = "fae8f9dfd7048de16575b9d4c255278e38c28a4f"
[[projects]]
branch = "master"
name = "github.com/ngaut/go-zookeeper"
packages = ["zk"]
revision = "9c3719e318c7cfd072e41eb48cb71fcaa49d5e05"
[[projects]]
name = "github.com/ngaut/log"
packages = ["."]
revision = "d2af3a61f64d093457fb23b25d20f4ce3cd551ce"
[[projects]]
branch = "master"
name = "github.com/ngaut/pools"
packages = ["."]
revision = "b7bc8c42aac787667ba45adea78233f53f548443"
[[projects]]
branch = "master"
name = "github.com/ngaut/sync2"
packages = ["."]
revision = "7a24ed77b2efb460c1468b7dc917821c66e80e55"
[[projects]]
branch = "master"
name = "github.com/ngaut/tso"
packages = [
"client",
"proto",
"util"
]
revision = "118f6c141d58f1e72577ff61f43f649bf39355ee"
[[projects]]
branch = "master"
name = "github.com/ngaut/zkhelper"
packages = ["."]
revision = "6738bdc138d469112c6687fbfcfe049ccabd6a0a"
[[projects]]
branch = "master"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
name = "github.com/philhofer/fwd"
packages = ["."]
revision = "bb6d471dc95d4fe11e432687f8b70ff496cf3136"
version = "v1.0.0"
[[projects]]
name = "github.com/pingcap/go-hbase"
packages = [
".",
"iohelper",
"proto"
]
revision = "7a98d1fe4e9e115de8c77ae0e158c0d08732c550"
[[projects]]
branch = "master"
name = "github.com/pingcap/go-themis"
packages = [
".",
"oracle",
"oracle/oracles"
]
revision = "dbb996606c1d1fe8571fd9ac6da2254c76d2c5c9"
[[projects]]
name = "github.com/pingcap/tidb"
packages = [
".",
"ast",
"column",
"context",
"ddl",
"domain",
"evaluator",
"executor",
"infoschema",
"inspectkv",
"kv",
"kv/memkv",
"meta",
"meta/autoid",
"model",
"mysql",
"optimizer",
"optimizer/plan",
"parser",
"parser/opcode",
"perfschema",
"privilege",
"privilege/privileges",
"sessionctx",
"sessionctx/autocommit",
"sessionctx/db",
"sessionctx/forupdate",
"sessionctx/variable",
"store/hbase",
"store/localstore",
"store/localstore/boltdb",
"store/localstore/engine",
"store/localstore/goleveldb",
"structure",
"table",
"table/tables",
"terror",
"util",
"util/bytes",
"util/charset",
"util/codec",
"util/distinct",
"util/hack",
"util/segmentmap",
"util/sqlexec",
"util/stringutil",
"util/types"
]
revision = "33197485abe227dcb254644cf5081c9a3c281669"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/pquerna/otp"
packages = [
".",
"hotp",
"totp"
]
revision = "54653902c20e47f3417541d35435cb6d6162e28a"
[[projects]]
branch = "master"
name = "github.com/russross/blackfriday"
packages = ["."]
revision = "11635eb403ff09dbc3a6b5a007ab5ab09151c229"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "b061729afc07e77a8aa4fad0a2fd840958f1942a"
[[projects]]
name = "github.com/sergi/go-diff"
packages = ["diffmatchpatch"]
revision = "552b4e9bbdca9e5adafd95ee98c822fdd11b330b"
[[projects]]
name = "github.com/shurcooL/sanitized_anchor_name"
packages = ["."]
revision = "1dba4b3954bc059efc3991ec364f9f9a35f597d2"
[[projects]]
branch = "master"
name = "github.com/steveyen/gtreap"
packages = ["."]
revision = "0abe01ef9be25c4aedc174758ec2d917314d6d70"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
version = "v1.2.1"
[[projects]]
name = "github.com/syndtr/goleveldb"
packages = [
"leveldb",
"leveldb/cache",
"leveldb/comparer",
"leveldb/errors",
"leveldb/filter",
"leveldb/iterator",
"leveldb/journal",
"leveldb/memdb",
"leveldb/opt",
"leveldb/storage",
"leveldb/table",
"leveldb/util"
]
revision = "917f41c560270110ceb73c5b38be2a9127387071"
[[projects]]
branch = "master"
name = "github.com/tinylib/msgp"
packages = ["msgp"]
revision = "c8cf64dff2009d53fa8f8a16df54d1cdfc64c4a7"
[[projects]]
branch = "master"
name = "github.com/tstranex/u2f"
packages = ["."]
revision = "d21a03e0b1d9fc1df59ff54e7a513655c1748b0c"
[[projects]]
name = "github.com/twinj/uuid"
packages = ["."]
revision = "89173bcdda19db0eb88aef1e1cb1cb2505561d31"
version = "0.10.0"
[[projects]]
name = "github.com/ugorji/go"
packages = ["codec"]
revision = "c062049c1793b01a3cc3fe786108edabbaf7756b"
[[projects]]
name = "github.com/urfave/cli"
packages = ["."]
revision = "d86a009f5e13f83df65d0d6cee9a2e3f1445f0da"
[[projects]]
branch = "master"
name = "github.com/willf/bitset"
packages = ["."]
revision = "8ce1146b8621c95164efd9c8b1124cfa9b8afb4e"
[[projects]]
name = "github.com/yohcop/openid-go"
packages = ["."]
revision = "2c050d2dae5345c417db301f11fda6fbf5ad0f0a"
[[projects]]
name = "golang.org/x/crypto"
packages = [
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"md4",
"pbkdf2",
"ssh"
]
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
[[projects]]
name = "golang.org/x/net"
packages = [
"context",
"html",
"html/atom",
"html/charset"
]
revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
[[projects]]
name = "golang.org/x/oauth2"
packages = [
".",
"internal"
]
revision = "c10ba270aa0bf8b8c1c986e103859c67a9103061"
[[projects]]
name = "golang.org/x/sync"
packages = ["syncmap"]
revision = "5a06fca2c336a4b2b2fcb45702e8c47621b2aa2c"
[[projects]]
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
"windows/svc"
]
revision = "a646d33e2ee3172a661fc09bca23bb4889a41bc8"
[[projects]]
name = "golang.org/x/text"
packages = [
"encoding",
"encoding/charmap",
"encoding/htmlindex",
"encoding/internal",
"encoding/internal/identifier",
"encoding/japanese",
"encoding/korean",
"encoding/simplifiedchinese",
"encoding/traditionalchinese",
"encoding/unicode",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"internal/utf8internal",
"language",
"runes",
"transform",
"unicode/cldr",
"unicode/norm"
]
revision = "2bf8f2a19ec09c670e931282edfe6567f6be21c9"
[[projects]]
branch = "v3"
name = "gopkg.in/alexcesaro/quotedprintable.v3"
packages = ["."]
revision = "2caba252f4dc53eaf6b553000885530023f54623"
[[projects]]
name = "gopkg.in/asn1-ber.v1"
packages = ["."]
revision = "4e86f4367175e39f69d9358a5f17b4dda270378d"
version = "v1.1"
[[projects]]
name = "gopkg.in/bufio.v1"
packages = ["."]
revision = "567b2bfa514e796916c4747494d6ff5132a1dfce"
version = "v1"
[[projects]]
name = "gopkg.in/editorconfig/editorconfig-core-go.v1"
packages = ["."]
revision = "a872f05c2e34b37b567401384d202aff11ba06d4"
version = "v1.2.0"
[[projects]]
branch = "v2"
name = "gopkg.in/gomail.v2"
packages = ["."]
revision = "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1"
[[projects]]
name = "gopkg.in/ini.v1"
packages = ["."]
revision = "7e7da451323b6766da368f8a1e8ec9a88a16b4a0"
version = "v1.31.1"
[[projects]]
name = "gopkg.in/ldap.v2"
packages = ["."]
revision = "d0a5ced67b4dc310b9158d63a2c6f9c5ec13f105"
version = "v2.4.1"
[[projects]]
name = "gopkg.in/macaron.v1"
packages = ["."]
revision = "75f2e9b42e99652f0d82b28ccb73648f44615faa"
version = "v1.2.4"
[[projects]]
name = "gopkg.in/redis.v2"
packages = ["."]
revision = "e6179049628164864e6e84e973cfb56335748dea"
version = "v2.3.2"
[[projects]]
name = "gopkg.in/testfixtures.v2"
packages = ["."]
revision = "b9ef14dc461bf934d8df2dfc6f1f456be5664cca"
version = "v2.0.0"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "a5b47d31c556af34a302ce5d659e6fea44d90de0"
[[projects]]
name = "strk.kbt.io/projects/go/libravatar"
packages = ["."]
revision = "5eed7bff870ae19ef51c5773dbc8f3e9fcbd0982"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "036b8c882671cf8d2c5e2fdbe53b1bdfbd39f7ebd7765bd50276c7c4ecf16687"
solver-name = "gps-cdcl"
solver-version = 1

106
Gopkg.toml Normal file
View File

@ -0,0 +1,106 @@
ignored = ["google.golang.org/appengine*"]
[prune]
go-tests = true
unused-packages = true
non-go = true
[[constraint]]
branch = "master"
name = "code.gitea.io/git"
[[constraint]]
branch = "master"
name = "code.gitea.io/sdk"
[[constraint]]
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
name = "golang.org/x/crypto"
[[constraint]]
revision = "a646d33e2ee3172a661fc09bca23bb4889a41bc8"
name = "golang.org/x/sys"
[[constraint]]
revision = "2bf8f2a19ec09c670e931282edfe6567f6be21c9"
name = "golang.org/x/text"
[[constraint]]
revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
name = "golang.org/x/net"
[[constraint]]
#version = "v1.0.0"
revision = "33197485abe227dcb254644cf5081c9a3c281669"
name = "github.com/pingcap/tidb"
[[override]]
name = "github.com/go-xorm/xorm"
#version = "0.6.5"
revision = "d4149d1eee0c2c488a74a5863fd9caf13d60fd03"
[[override]]
name = "github.com/gorilla/mux"
revision = "757bef944d0f21880861c2dd9c871ca543023cba"
[[constraint]]
name = "github.com/gorilla/context"
version = "1.1.1"
[[constraint]]
name = "github.com/lafriks/xormstore"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/lunny/dingtalk_webhook"
[[constraint]]
name = "github.com/markbates/goth"
version = "1.45.5"
[[constraint]]
branch = "master"
name = "github.com/mcuadros/go-version"
[[constraint]]
branch = "master"
name = "github.com/russross/blackfriday"
[[constraint]]
branch = "master"
name = "github.com/tstranex/u2f"
[[constraint]]
name = "gopkg.in/editorconfig/editorconfig-core-go.v1"
version = "1.2.0"
[[constraint]]
branch = "v2"
name = "gopkg.in/gomail.v2"
[[constraint]]
name = "gopkg.in/ini.v1"
version = "1.31.1"
[[constraint]]
name = "gopkg.in/ldap.v2"
version = "2.4.1"
[[constraint]]
name = "gopkg.in/macaron.v1"
version = "1.2.4"
[[constraint]]
name = "gopkg.in/testfixtures.v2"
version = "2.0.0"
[[override]]
name = "github.com/boltdb/bolt"
revision = "ccd680d8c1a0179ac3d68f692b01e1a1589cbfc7"
source = "github.com/go-gitea/bolt"
[[override]]
revision = "c10ba270aa0bf8b8c1c986e103859c67a9103061"
name = "golang.org/x/oauth2"

View File

@ -91,6 +91,22 @@ generate-swagger:
fi fi
swagger generate spec -o ./public/swagger.v1.json swagger generate spec -o ./public/swagger.v1.json
.PHONY: swagger-check
swagger-check: generate-swagger
@diff=$$(git diff public/swagger.v1.json); \
if [ -n "$$diff" ]; then \
echo "Please run 'make generate-swagger' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
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 \
@ -144,18 +160,22 @@ coverage:
unit-test-coverage: unit-test-coverage:
for PKG in $(PACKAGES); do $(GO) test -tags=sqlite -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; for PKG in $(PACKAGES); do $(GO) test -tags=sqlite -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;
.PHONY: test-vendor .PHONY: vendor
test-vendor: vendor:
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash dep > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/kardianos/govendor; \ $(GO) get -u github.com/golang/dep/cmd/dep; \
fi fi
govendor list +unused | tee "$(TMPDIR)/wc-gitea-unused" dep ensure -vendor-only
[ $$(cat "$(TMPDIR)/wc-gitea-unused" | wc -l) -eq 0 ] || echo "Warning: /!\\ Some vendor are not used /!\\"
govendor list +outside | tee "$(TMPDIR)/wc-gitea-outside" .PHONY: test-vendor
[ $$(cat "$(TMPDIR)/wc-gitea-outside" | wc -l) -eq 0 ] || exit 1 test-vendor: vendor
@diff=$$(git diff vendor/); \
govendor status || exit 1 if [ -n "$$diff" ]; then \
echo "Please run 'make vendor' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi;
#TODO add dep status -missing when implemented
.PHONY: test-sqlite .PHONY: test-sqlite
test-sqlite: integrations.sqlite.test test-sqlite: integrations.sqlite.test
@ -221,7 +241,7 @@ $(EXECUTABLE): $(SOURCES)
$(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: release .PHONY: release
release: release-dirs release-windows release-linux release-darwin release-copy release-check release: release-dirs release-windows release-linux release-darwin release-copy release-compress release-check
.PHONY: release-dirs .PHONY: release-dirs
release-dirs: release-dirs:
@ -265,6 +285,13 @@ release-copy:
release-check: release-check:
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
.PHONY: release-compress
release-compress:
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
fi
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),gxz -k -9 $(notdir $(file));)
.PHONY: javascripts .PHONY: javascripts
javascripts: public/js/index.js javascripts: public/js/index.js
@ -283,12 +310,12 @@ stylesheets-check: generate-stylesheets
.PHONY: generate-stylesheets .PHONY: generate-stylesheets
generate-stylesheets: generate-stylesheets:
node_modules/.bin/lessc --no-ie-compat --clean-css public/less/index.less public/css/index.css node_modules/.bin/lessc --clean-css public/less/index.less public/css/index.css
.PHONY: swagger-ui .PHONY: swagger-ui
swagger-ui: swagger-ui:
rm -Rf public/vendor/assets/swagger-ui rm -Rf public/vendor/assets/swagger-ui
git clone --depth=10 -b v3.3.2 --single-branch https://github.com/swagger-api/swagger-ui.git $(TMPDIR)/swagger-ui git clone --depth=10 -b v3.13.4 --single-branch https://github.com/swagger-api/swagger-ui.git $(TMPDIR)/swagger-ui
mv $(TMPDIR)/swagger-ui/dist public/vendor/assets/swagger-ui mv $(TMPDIR)/swagger-ui/dist public/vendor/assets/swagger-ui
rm -Rf $(TMPDIR)/swagger-ui rm -Rf $(TMPDIR)/swagger-ui
$(SED_INPLACE) "s;http://petstore.swagger.io/v2/swagger.json;../../../swagger.v1.json;g" public/vendor/assets/swagger-ui/index.html $(SED_INPLACE) "s;http://petstore.swagger.io/v2/swagger.json;../../../swagger.v1.json;g" public/vendor/assets/swagger-ui/index.html

View File

@ -4,13 +4,13 @@
[![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea) [![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea)
[![Join the Discord chat at https://discord.gg/NsatcWJ](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ) [![Join the Discord chat at https://discord.gg/NsatcWJ](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/NsatcWJ)
[![Join the Matrix chat at https://matrix.to/#/#gitea:matrix.org](https://img.shields.io/badge/matrix-%23gitea%3Amatrix.org-7bc9a4.svg)](https://matrix.to/#/#gitea:matrix.org)
[![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") [![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
[![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea) [![codecov](https://codecov.io/gh/go-gitea/gitea/branch/master/graph/badge.svg)](https://codecov.io/gh/go-gitea/gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea)
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea) [![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![Release](https://github-release-version.herokuapp.com/github/go-gitea/gitea/release.svg?style=flat)](https://github.com/go-gitea/gitea/releases/latest) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest)
[![Help Contribute to Open Source](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea) [![Help Contribute to Open Source](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea)
[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea)
| | | | | | | |
|:---:|:---:|:---:| |:---:|:---:|:---:|
@ -62,7 +62,6 @@ For more information and instructions about how to install Gitea, please look
at our [documentation](https://docs.gitea.io/en-us/). If you have questions at our [documentation](https://docs.gitea.io/en-us/). If you have questions
that are not covered by the documentation, you can get in contact with us on that are not covered by the documentation, you can get in contact with us on
our [Discord server](https://discord.gg/NsatcWJ), our [Discord server](https://discord.gg/NsatcWJ),
[Matrix room](https://matrix.to/#/#gitea:matrix.org),
or [forum](https://discourse.gitea.io/)! or [forum](https://discourse.gitea.io/)!
## Authors ## Authors
@ -71,6 +70,27 @@ or [forum](https://discourse.gitea.io/)!
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors) * [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
* [Translators](options/locale/TRANSLATORS) * [Translators](options/locale/TRANSLATORS)
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/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>
## License ## License
This project is licensed under the MIT License. This project is licensed under the MIT License.

View File

@ -8,7 +8,8 @@
[![Coverage Status](https://coverage.gitea.io/badges/go-gitea/gitea/coverage.svg)](https://coverage.gitea.io/go-gitea/gitea) [![Coverage Status](https://coverage.gitea.io/badges/go-gitea/gitea/coverage.svg)](https://coverage.gitea.io/go-gitea/gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea)
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea) [![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![Release](https://github-release-version.herokuapp.com/github/go-gitea/gitea/release.svg?style=flat)](https://github.com/go-gitea/gitea/releases/latest) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest)
[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea)
| | | | | | | |
|:---:|:---:|:---:| |:---:|:---:|:---:|

View File

@ -25,6 +25,7 @@ var (
subcmdCreateUser, subcmdCreateUser,
subcmdChangePassword, subcmdChangePassword,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate,
}, },
} }
@ -80,6 +81,41 @@ var (
Usage: "Synchronize repository releases with tags", Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases, Action: runRepoSyncReleases,
} }
subcmdRegenerate = cli.Command{
Name: "regenerate",
Usage: "Regenerate specific files",
Subcommands: []cli.Command{
microcmdRegenHooks,
microcmdRegenKeys,
},
}
microcmdRegenHooks = cli.Command{
Name: "hooks",
Usage: "Regenerate git-hooks",
Action: runRegenerateHooks,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
},
}
microcmdRegenKeys = cli.Command{
Name: "keys",
Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
},
}
) )
func runChangePassword(c *cli.Context) error { func runChangePassword(c *cli.Context) error {
@ -195,3 +231,25 @@ func getReleaseCount(id int64) (int64, error) {
}, },
) )
} }
func runRegenerateHooks(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if err := initDB(); err != nil {
return err
}
return models.SyncRepositoryHooks()
}
func runRegenerateKeys(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if err := initDB(); err != nil {
return err
}
return models.RewriteAllPublicKeys()
}

View File

@ -230,6 +230,12 @@ func runServ(c *cli.Context) error {
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err) fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
} }
if !user.IsActive || user.ProhibitLogin {
fail("Your account is not active or has been disabled by Administrator",
"User %s is disabled and have no access to repository %s",
user.Name, repoPath)
}
mode, err := models.AccessLevel(user.ID, repo) mode, err := models.AccessLevel(user.ID, repo)
if err != nil { if err != nil {
fail("Internal error", "Failed to check access: %v", err) fail("Internal error", "Failed to check access: %v", err)
@ -262,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 {

View File

@ -22,6 +22,11 @@ WorkingDirectory=/home/git/gitea
ExecStart=/home/git/gitea/gitea web ExecStart=/home/git/gitea/gitea web
Restart=always Restart=always
Environment=USER=git HOME=/home/git Environment=USER=git HOME=/home/git
# If you want to bind Gitea to a port below 1024 uncomment
# the two values below
###
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
#AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -2,7 +2,9 @@
; Copy required sections to your own app.ini (default is custom/conf/app.ini) ; Copy required sections to your own app.ini (default is custom/conf/app.ini)
; and modify as needed. ; and modify as needed.
; App name that shows on every page title ; see https://docs.gitea.io/en-us/config-cheat-sheet/ for additional documentation.
; App name that shows in every page title
APP_NAME = Gitea: Git with a cup of tea APP_NAME = Gitea: Git with a cup of tea
; Change it if you run locally ; Change it if you run locally
RUN_USER = git RUN_USER = git
@ -16,28 +18,28 @@ SCRIPT_TYPE = bash
ANSI_CHARSET = ANSI_CHARSET =
; Force every new repository to be private ; Force every new repository to be private
FORCE_PRIVATE = false FORCE_PRIVATE = false
; Default private when create a new repository, could be: last, private, public. Default is last which means last user repo visiblity. ; Default privacy setting when creating a new repository, allowed values: last, private, public. Default is last which means the last setting used.
DEFAULT_PRIVATE = last DEFAULT_PRIVATE = last
; Global maximum creation limit of repository per user, -1 means no limit ; Global limit of repositories per user, applied at creation time. -1 means no limit
MAX_CREATION_LIMIT = -1 MAX_CREATION_LIMIT = -1
; Mirror sync queue length, increase if mirror syncing starts hanging ; Mirror sync queue length, increase if mirror syncing starts hanging
MIRROR_QUEUE_LENGTH = 1000 MIRROR_QUEUE_LENGTH = 1000
; Patch test queue length, increase if pull request patch testing starts hanging ; Patch test queue length, increase if pull request patch testing starts hanging
PULL_REQUEST_QUEUE_LENGTH = 1000 PULL_REQUEST_QUEUE_LENGTH = 1000
; Preferred Licenses to place at the top of the List ; Preferred Licenses to place at the top of the List
; Name must match file name in conf/license or custom/conf/license ; The name here must match the filename in conf/license or custom/conf/license
PREFERRED_LICENSES = Apache License 2.0,MIT License PREFERRED_LICENSES = Apache License 2.0,MIT License
; Disable ability to interact with repositories by HTTP protocol ; Disable the ability to interact with repositories using the HTTP protocol
DISABLE_HTTP_GIT = false DISABLE_HTTP_GIT = false
; Force ssh:// clone url instead of scp-style uri when default SSH port is used ; Force ssh:// clone url instead of scp-style uri when default SSH port is used
USE_COMPAT_SSH_URI = false USE_COMPAT_SSH_URI = false
[repository.editor] [repository.editor]
; List of file extensions that should have line wraps in the CodeMirror editor ; List of file extensions for which lines should be wrapped in the CodeMirror editor
; Separate extensions with a comma. To line wrap files w/o extension, just put a comma ; Separate extensions with a comma. To line wrap files without an extension, just put a comma
LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd, LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
; Valid file modes that have a preview API associated with them, such as api/v1/markdown ; Valid file modes that have a preview API associated with them, such as api/v1/markdown
; Separate values by commas. Preview tab in edit mode won't show if the file extension doesn't match ; Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match
PREVIEWABLE_FILE_MODES = markdown PREVIEWABLE_FILE_MODES = markdown
[repository.local] [repository.local]
@ -53,39 +55,39 @@ ENABLED = true
TEMP_PATH = data/tmp/uploads TEMP_PATH = data/tmp/uploads
; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type ; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type
ALLOWED_TYPES = ALLOWED_TYPES =
; Max size of each file in MB. Defaults to 3MB ; Max size of each file in megabytes. Defaults to 3MB
FILE_MAX_SIZE = 3 FILE_MAX_SIZE = 3
; Max number of files per upload. Defaults to 5 ; Max number of files per upload. Defaults to 5
MAX_FILES = 5 MAX_FILES = 5
[ui] [ui]
; Number of repositories that are showed in one explore page ; Number of repositories that are displayed on one explore page
EXPLORE_PAGING_NUM = 20 EXPLORE_PAGING_NUM = 20
; Number of issues that are showed in one page ; Number of issues that are displayed on one page
ISSUE_PAGING_NUM = 10 ISSUE_PAGING_NUM = 10
; Number of maximum commits showed in one activity feed ; Number of maximum commits displayed in one activity feed
FEED_MAX_COMMIT_NUM = 5 FEED_MAX_COMMIT_NUM = 5
; 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
THEME_COLOR_META_TAG = `#6cc644` THEME_COLOR_META_TAG = `#6cc644`
; Max size of files to be displayed (defaults is 8MiB) ; Max size of files to be displayed (default is 8MiB)
MAX_DISPLAY_FILE_SIZE = 8388608 MAX_DISPLAY_FILE_SIZE = 8388608
; Whether show the user email 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
[ui.admin] [ui.admin]
; Number of users that are showed in one page ; Number of users that are displayed on one page
USER_PAGING_NUM = 50 USER_PAGING_NUM = 50
; Number of repos that are showed in one page ; Number of repos that are displayed on one page
REPO_PAGING_NUM = 50 REPO_PAGING_NUM = 50
; Number of notices that are showed in one page ; Number of notices that are displayed on in one page
NOTICE_PAGING_NUM = 25 NOTICE_PAGING_NUM = 25
; Number of organization that are showed in one page ; Number of organizations that are displayed on one page
ORG_PAGING_NUM = 50 ORG_PAGING_NUM = 50
[ui.user] [ui.user]
; Number of repos that are showed in one page ; Number of repos that are displayed on one page
REPO_PAGING_NUM = 15 REPO_PAGING_NUM = 15
[ui.meta] [ui.meta]
@ -100,19 +102,19 @@ ENABLE_HARD_LINE_BREAK = false
; for example git,magnet ; for example git,magnet
CUSTOM_URL_SCHEMES = CUSTOM_URL_SCHEMES =
; List of file extensions that should be rendered/edited as Markdown ; List of file extensions that should be rendered/edited as Markdown
; Separate extensions with a comma. To render files w/o extension as markdown, just put a comma ; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma
FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
[server] [server]
; Listen protocol. One of 'http', 'https', 'unix' or 'fcgi'. ; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'.
PROTOCOL = http PROTOCOL = http
DOMAIN = localhost DOMAIN = localhost
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
; Listen address. Either a IPv4/IPv6 address or the path to a unix socket. ; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
HTTP_ADDR = 0.0.0.0 HTTP_ADDR = 0.0.0.0
HTTP_PORT = 3000 HTTP_PORT = 3000
; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server ; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
; will be started on PORT_TO_REDIRECT and redirect request to the main ; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for ; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
; PORT_TO_REDIRECT. ; PORT_TO_REDIRECT.
REDIRECT_OTHER_PORT = false REDIRECT_OTHER_PORT = false
@ -122,36 +124,37 @@ UNIX_SOCKET_PERMISSION = 666
; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. ; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service.
; In most cases you do not need to change the default value. ; In most cases you do not need to change the default value.
; Alter it only if your SSH server node is not the same as HTTP node. ; Alter it only if your SSH server node is not the same as HTTP node.
; Do not set this variable if PROTOCOL is set to 'unix'.
LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
; Disable SSH feature when not available ; Disable SSH feature when not available
DISABLE_SSH = false DISABLE_SSH = false
; Whether use builtin SSH server or not. ; Whether to use the builtin SSH server or not.
START_SSH_SERVER = false START_SSH_SERVER = false
; Username to use for builtin SSH server. If blank, then it is the value of RUN_USER. ; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
BUILTIN_SSH_SERVER_USER = BUILTIN_SSH_SERVER_USER =
; Domain name to be exposed in clone URL ; Domain name to be exposed in clone URL
SSH_DOMAIN = %(DOMAIN)s SSH_DOMAIN = %(DOMAIN)s
; Network interface builtin SSH server listens on ; THe network interface the builtin SSH server should listen on
SSH_LISTEN_HOST = SSH_LISTEN_HOST =
; Port number to be exposed in clone URL ; Port number to be exposed in clone URL
SSH_PORT = 22 SSH_PORT = 22
; Port number builtin SSH server listens on ; The port number the builtin SSH server should listen on
SSH_LISTEN_PORT = %(SSH_PORT)s SSH_LISTEN_PORT = %(SSH_PORT)s
; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. ; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
SSH_ROOT_PATH = SSH_ROOT_PATH =
; For built-in SSH server only, choose the ciphers to support for SSH connections, ; For the built-in SSH server, choose the ciphers to support for SSH connections,
; for system SSH this setting has no effect ; for system SSH this setting has no effect
SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128 SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128
; For built-in SSH server only, choose the key exchange algorithms to support for SSH connections, ; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
; for system SSH this setting has no effect ; for system SSH this setting has no effect
SSH_SERVER_KEY_EXCHANGES = diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org SSH_SERVER_KEY_EXCHANGES = diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org
; For built-in SSH server only, choose the MACs to support for SSH connections, ; For the built-in SSH server, choose the MACs to support for SSH connections,
; for system SSH this setting has no effect ; for system SSH this setting has no effect
SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96 SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96
; Directory to create temporary files when test public key using ssh-keygen, ; Directory to create temporary files in when testing public keys using ssh-keygen,
; default is system temporary directory. ; default is the system temporary directory.
SSH_KEY_TEST_PATH = SSH_KEY_TEST_PATH =
; Path to ssh-keygen, default is 'ssh-keygen' and let shell find out which one to call. ; Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call.
SSH_KEYGEN_PATH = ssh-keygen SSH_KEYGEN_PATH = ssh-keygen
; Enable SSH Authorized Key Backup when rewriting all keys, default is true ; Enable SSH Authorized Key Backup when rewriting all keys, default is true
SSH_BACKUP_AUTHORIZED_KEYS = true SSH_BACKUP_AUTHORIZED_KEYS = true
@ -171,7 +174,7 @@ DISABLE_ROUTER_LOG = false
; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes ; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes
CERT_FILE = custom/https/cert.pem CERT_FILE = custom/https/cert.pem
KEY_FILE = custom/https/key.pem KEY_FILE = custom/https/key.pem
; Upper level of template and static file path ; Root directory containing templates and static files.
; default is the path where Gitea is executed ; default is the path where Gitea is executed
STATIC_ROOT_PATH = STATIC_ROOT_PATH =
; Default path for App data ; Default path for App data
@ -182,10 +185,12 @@ ENABLE_GZIP = false
LANDING_PAGE = home LANDING_PAGE = home
; Enables git-lfs support. true or false, default is false. ; Enables git-lfs support. true or false, default is false.
LFS_START_SERVER = false LFS_START_SERVER = false
; Where your lfs files put on, default is data/lfs. ; Where your lfs files reside, default is data/lfs.
LFS_CONTENT_PATH = data/lfs LFS_CONTENT_PATH = data/lfs
; LFS authentication secret, changed this to 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]
@ -204,12 +209,14 @@ USER = root
PASSWD = PASSWD =
; For "postgres" only, either "disable", "require" or "verify-full" ; For "postgres" only, either "disable", "require" or "verify-full"
SSL_MODE = disable SSL_MODE = disable
; For "sqlite3" and "tidb", use absolute path when you start as service ; For "sqlite3" and "tidb", use absolute path when you start gitea as service
PATH = data/gitea.db PATH = data/gitea.db
; For "sqlite3" only. Query timeout ; For "sqlite3" only. Query timeout
SQLITE_TIMEOUT = 500 SQLITE_TIMEOUT = 500
; For iterate buffer, default is 50 ; For iterate buffer, default is 50
ITERATE_BUFFER_SIZE = 50 ITERATE_BUFFER_SIZE = 50
; Show the database generated SQL
LOG_SQL = true
[indexer] [indexer]
ISSUE_INDEXER_PATH = indexers/issues.bleve ISSUE_INDEXER_PATH = indexers/issues.bleve
@ -220,7 +227,7 @@ UPDATE_BUFFER_LEN = 20
MAX_FILE_SIZE = 1048576 MAX_FILE_SIZE = 1048576
[admin] [admin]
; Disable regular (non-admin) users to create organizations ; Disallow regular (non-admin) users from creating organizations.
DISABLE_REGULAR_ORG_CREATION = false DISABLE_REGULAR_ORG_CREATION = false
[security] [security]
@ -228,13 +235,13 @@ DISABLE_REGULAR_ORG_CREATION = false
INSTALL_LOCK = false INSTALL_LOCK = false
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
SECRET_KEY = !#@FDEWREWR&*( SECRET_KEY = !#@FDEWREWR&*(
; Auto-login remember days ; How long to remember that an user is logged in before requiring relogin (in days)
LOGIN_REMEMBER_DAYS = 7 LOGIN_REMEMBER_DAYS = 7
COOKIE_USERNAME = gitea_awesome COOKIE_USERNAME = gitea_awesome
COOKIE_REMEMBER_NAME = gitea_incredible COOKIE_REMEMBER_NAME = gitea_incredible
; Reverse proxy authentication header name of user name ; Reverse proxy authentication header name of user name
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
; Sets the minimum password length for new Users ; The minimum password length for new Users
MIN_PASSWORD_LENGTH = 6 MIN_PASSWORD_LENGTH = 6
; True when users are allowed to import local server paths ; True when users are allowed to import local server paths
IMPORT_LOCAL_PATHS = false IMPORT_LOCAL_PATHS = false
@ -243,7 +250,7 @@ DISABLE_GIT_HOOKS = false
[openid] [openid]
; ;
; OpenID is an open standard and decentralized authentication protocol. ; OpenID is an open, standard and decentralized authentication protocol.
; Your identity is the address of a webpage you provide, which describes ; Your identity is the address of a webpage you provide, which describes
; how to prove you are in control of that page. ; how to prove you are in control of that page.
; ;
@ -262,7 +269,7 @@ DISABLE_GIT_HOOKS = false
; Whether to allow signin in via OpenID ; Whether to allow signin in via OpenID
ENABLE_OPENID_SIGNIN = true ENABLE_OPENID_SIGNIN = true
; Whether to allow registering via OpenID ; Whether to allow registering via OpenID
; Do not include to rely on DISABLE_REGISTRATION setting ; Do not include to rely on rhw DISABLE_REGISTRATION setting
;ENABLE_OPENID_SIGNUP = true ;ENABLE_OPENID_SIGNUP = true
; Allowed URI patterns (POSIX regexp). ; Allowed URI patterns (POSIX regexp).
; Space separated. ; Space separated.
@ -278,12 +285,14 @@ BLACKLISTED_URIS =
[service] [service]
; Time limit to confirm account/email registration ; Time limit to confirm account/email registration
ACTIVE_CODE_LIVE_MINUTES = 180 ACTIVE_CODE_LIVE_MINUTES = 180
; Time limit to confirm forgot password reset process ; Time limit to perform the reset of a forgotten password
RESET_PASSWD_CODE_LIVE_MINUTES = 180 RESET_PASSWD_CODE_LIVE_MINUTES = 180
; User need to confirm e-mail for registration ; Whether a new user needs to confirm their email when registering.
REGISTER_EMAIL_CONFIRM = false REGISTER_EMAIL_CONFIRM = false
; Does not allow register and admin create account only ; Disallow registration, only allow admins to create accounts.
DISABLE_REGISTRATION = false DISABLE_REGISTRATION = false
; Allow registration only using third part services, it works only when DISABLE_REGISTRATION is false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
; User must sign in to view anything. ; User must sign in to view anything.
REQUIRE_SIGNIN_VIEW = false REQUIRE_SIGNIN_VIEW = false
; Mail notification ; Mail notification
@ -294,19 +303,21 @@ ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; Enable captcha validation for registration ; Enable captcha validation for registration
ENABLE_CAPTCHA = true ENABLE_CAPTCHA = true
; Default value for KeepEmailPrivate ; Default value for KeepEmailPrivate
; 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
; 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
; Enable Timetracking
ENABLE_TIMETRACKING = true
; Default value for EnableTimetracking ; Default value for EnableTimetracking
; Repositories will use timetracking by default depending on this setting ; Repositories will use timetracking by default depending on this setting
DEFAULT_ENABLE_TIMETRACKING = true DEFAULT_ENABLE_TIMETRACKING = true
; Default value for AllowOnlyContributorsToTrackTime ; Default value for AllowOnlyContributorsToTrackTime
; Only users with write permissions could track time if this is true ; Only users with write permissions can track time if this is true
DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = true DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = true
; Default value for the domain part of the user's email address in the git log ; Default value for the domain part of the user's email address in the git log
; if he has set KeepEmailPrivate true. The user's email replaced with a ; if he has set KeepEmailPrivate to true. The user's email will be replaced with a
; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. ; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
NO_REPLY_ADDRESS = noreply.example.org NO_REPLY_ADDRESS = noreply.example.org
@ -331,9 +342,9 @@ SUBJECT = %(APP_NAME)s
; QQ: smtp.qq.com:465 ; QQ: smtp.qq.com:465
; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used. ; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
HOST = HOST =
; Disable HELO operation when hostname are different. ; Disable HELO operation when hostnames are different.
DISABLE_HELO = DISABLE_HELO =
; Custom hostname for HELO operation, default is from system. ; Custom hostname for HELO operation, if no value is provided, one is retrieved from system.
HELO_HOSTNAME = HELO_HOSTNAME =
; Do not verify the certificate of the server. Only use this for self-signed certificates ; Do not verify the certificate of the server. Only use this for self-signed certificates
SKIP_VERIFY = SKIP_VERIFY =
@ -373,7 +384,7 @@ ITEM_TTL = 16h
; Either "memory", "file", or "redis", default is "memory" ; Either "memory", "file", or "redis", default is "memory"
PROVIDER = memory PROVIDER = memory
; Provider config options ; Provider config options
; memory: not have any config yet ; memory: doesn't have any config yet
; file: session file path, e.g. `data/sessions` ; file: session file path, e.g. `data/sessions`
; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 ; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` ; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
@ -394,23 +405,23 @@ AVATAR_UPLOAD_PATH = data/avatars
; 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
; This value will be forced to be true in offline mode. ; This value will always be true in offline mode.
DISABLE_GRAVATAR = false DISABLE_GRAVATAR = false
; Federated avatar lookup uses DNS to discover avatar associated ; Federated avatar lookup uses DNS to discover avatar associated
; with emails, see https://www.libravatar.org ; with emails, see https://www.libravatar.org
; This value will be forced to be false in offline mode or Gravatar is disabled. ; This value will always be false in offline mode or when Gravatar is disabled.
ENABLE_FEDERATED_AVATAR = false 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
ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip
; Max size of each file. Defaults to 32MB ; Max size of each file. Defaults to 4MB
MAX_SIZE = 4 MAX_SIZE = 4
; Max number of files per upload. Defaults to 10 ; Max number of files per upload. Defaults to 5
MAX_FILES = 5 MAX_FILES = 5
[time] [time]
@ -424,7 +435,7 @@ ROOT_PATH =
; Either "console", "file", "conn", "smtp" or "database", default is "console" ; Either "console", "file", "conn", "smtp" or "database", default is "console"
; Use comma to separate multiple modes, e.g. "console, file" ; Use comma to separate multiple modes, e.g. "console, file"
MODE = console MODE = console
; Buffer length of channel, keep it as it is if you don't know what it is. ; Buffer length of the channel, keep it as it is if you don't know what it is.
BUFFER_LEN = 10000 BUFFER_LEN = 10000
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace" ; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace"
LEVEL = Trace LEVEL = Trace
@ -438,13 +449,13 @@ LEVEL =
LEVEL = LEVEL =
; This enables automated log rotate(switch of following options), default is true ; This enables automated log rotate(switch of following options), default is true
LOG_ROTATE = true LOG_ROTATE = true
; Max line number of single file, default is 1000000 ; Max number of lines in a single file, default is 1000000
MAX_LINES = 1000000 MAX_LINES = 1000000
; Max size shift of single file, default is 28 means 1 << 28, 256MB ; Max size shift of a single file, default is 28 means 1 << 28, 256MB
MAX_SIZE_SHIFT = 28 MAX_SIZE_SHIFT = 28
; Segment log daily, default is true ; Segment log daily, default is true
DAILY_ROTATE = true DAILY_ROTATE = true
; Expired days of log file(delete after max days), default is 7 ; delete the log file after n days, default is 7
MAX_DAYS = 7 MAX_DAYS = 7
; For "conn" mode only ; For "conn" mode only
@ -528,9 +539,9 @@ UPDATE_EXISTING = true
[git] [git]
; Disables highlight of added and removed changes ; Disables highlight of added and removed changes
DISABLE_DIFF_HIGHLIGHT = false DISABLE_DIFF_HIGHLIGHT = false
; Max number of lines allowed of a single file in diff view ; Max number of lines allowed in a single file in diff view
MAX_GIT_DIFF_LINES = 1000 MAX_GIT_DIFF_LINES = 1000
; Max number of characters of a line allowed in diff view ; Max number of allowed characters in a line in diff view
MAX_GIT_DIFF_LINE_CHARACTERS = 5000 MAX_GIT_DIFF_LINE_CHARACTERS = 5000
; Max number of files shown in diff view ; Max number of files shown in diff view
MAX_GIT_DIFF_FILES = 100 MAX_GIT_DIFF_FILES = 100
@ -555,24 +566,25 @@ MIN_INTERVAL = 10m
[api] [api]
; Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true. ; Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
ENABLE_SWAGGER_ENDPOINT = true ENABLE_SWAGGER_ENDPOINT = true
; Max number of items will response in a page ; Max number of items in a page
MAX_RESPONSE_ITEMS = 50 MAX_RESPONSE_ITEMS = 50
[i18n] [i18n]
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR
NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어 NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어
; Used for datetimepicker ; Used for datetimepicker
[i18n.datelang] [i18n.datelang]
en-US = en en-US = en
zh-CN = zh zh-CN = zh
zh-HK = zh-TW zh-HK = zh-HK
zh-TW = zh-TW zh-TW = zh-TW
de-DE = de de-DE = de
fr-FR = fr fr-FR = fr
nl-NL = nl nl-NL = nl
lv-LV = lv lv-LV = lv
ru-RU = ru ru-RU = ru
uk-UA = uk
ja-JP = ja ja-JP = ja
es-ES = es es-ES = es
pt-BR = pt-BR pt-BR = pt-BR
@ -586,6 +598,13 @@ sr-SP = sr
sv-SE = sv sv-SE = sv
ko-KR = ko ko-KR = ko
[U2F]
; Two Factor authentication with security keys
; https://developers.yubico.com/U2F/App_ID.html
APP_ID = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s
; Comma seperated list of truisted facets
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
[highlight.mapping] [highlight.mapping]
@ -594,7 +613,7 @@ ko-KR = ko
SHOW_FOOTER_BRANDING = false SHOW_FOOTER_BRANDING = false
; Show version information about Gitea and Go in the footer ; Show version information about Gitea and Go in the footer
SHOW_FOOTER_VERSION = true SHOW_FOOTER_VERSION = true
; Show time of template execution in the footer ; Show template execution time in the footer
SHOW_FOOTER_TEMPLATE_LOAD_TIME = true SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
[markup.asciidoc] [markup.asciidoc]
@ -603,5 +622,5 @@ ENABLED = false
FILE_EXTENSIONS = .adoc,.asciidoc FILE_EXTENSIONS = .adoc,.asciidoc
; External command to render all matching extensions ; External command to render all matching extensions
RENDER_COMMAND = "asciidoc --out-file=- -" RENDER_COMMAND = "asciidoc --out-file=- -"
; Input is not a standard input but a file ; Don't pass the file on STDIN, pass the filename as argument instead.
IS_INPUT_FILE = false IS_INPUT_FILE = false

View File

@ -2,5 +2,5 @@
[[ -f ./setup ]] && source ./setup [[ -f ./setup ]] && source ./setup
pushd /app/gitea > /dev/null pushd /app/gitea > /dev/null
exec su-exec git /app/gitea/gitea web exec su-exec $USER /app/gitea/gitea web
popd popd

View File

@ -35,9 +35,11 @@ if [ ! -f /data/gitea/conf/app.ini ]; then
DB_USER=${DB_USER:-"root"} \ DB_USER=${DB_USER:-"root"} \
DB_PASSWD=${DB_PASSWD:-""} \ DB_PASSWD=${DB_PASSWD:-""} \
INSTALL_LOCK=${INSTALL_LOCK:-"false"} \ INSTALL_LOCK=${INSTALL_LOCK:-"false"} \
DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-"false"} \
REQUIRE_SIGNIN_VIEW=${REQUIRE_SIGNIN_VIEW:-"false"} \
SECRET_KEY=${SECRET_KEY:-""} \ SECRET_KEY=${SECRET_KEY:-""} \
envsubst < /etc/templates/app.ini > /data/gitea/conf/app.ini envsubst < /etc/templates/app.ini > /data/gitea/conf/app.ini
fi fi
chown -R git:git /data/gitea /app/gitea /data/git chown -R ${USER}:git /data/gitea /app/gitea /data/git
chmod 0755 /data/gitea /app/gitea /data/git chmod 0755 /data/gitea /app/gitea /data/git

View File

@ -38,3 +38,7 @@ ROOT_PATH = /data/gitea/log
[security] [security]
INSTALL_LOCK = $INSTALL_LOCK INSTALL_LOCK = $INSTALL_LOCK
SECRET_KEY = $SECRET_KEY SECRET_KEY = $SECRET_KEY
[service]
DISABLE_REGISTRATION = $DISABLE_REGISTRATION
REQUIRE_SIGNIN_VIEW = $REQUIRE_SIGNIN_VIEW

View File

@ -1,5 +1,12 @@
#!/bin/sh #!/bin/sh
if [ "${USER}" != "git" ]; then
# rename user
sed -i -e "s/^git\:/${USER}\:/g" /etc/passwd
# switch sshd config to different user
sed -i -e "s/AllowUsers git/AllowUsers ${USER}/g" /etc/ssh/sshd_config
fi
## Change GID for USER? ## Change GID for USER?
if [ -n "${USER_GID}" ] && [ "${USER_GID}" != "`id -g ${USER}`" ]; then if [ -n "${USER_GID}" ] && [ "${USER_GID}" != "`id -g ${USER}`" ]; then
sed -i -e "s/^${USER}:\([^:]*\):[0-9]*/${USER}:\1:${USER_GID}/" /etc/group sed -i -e "s/^${USER}:\([^:]*\):[0-9]*/${USER}:\1:${USER_GID}/" /etc/group

View File

@ -84,7 +84,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `PROTOCOL`: **http**: \[http, https, fcgi, unix\] - `PROTOCOL`: **http**: \[http, https, fcgi, unix\]
- `DOMAIN`: **localhost**: Domain name of this server. - `DOMAIN`: **localhost**: Domain name of this server.
- `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**: Full public URL of Gitea server. - `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**:
Overwrite the automatically generated public URL.
This is useful if the internal and the external URL don't match (e.g. in Docker).
- `HTTP_ADDR`: **0.0.0.0**: HTTP listen address. - `HTTP_ADDR`: **0.0.0.0**: HTTP listen address.
- If `PROTOCOL` is set to `fcgi`, Gitea will listen for FastCGI requests on TCP socket - If `PROTOCOL` is set to `fcgi`, Gitea will listen for FastCGI requests on TCP socket
defined by `HTTP_ADDR` and `HTTP_PORT` configuration settings. defined by `HTTP_ADDR` and `HTTP_PORT` configuration settings.
@ -93,6 +95,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- If `PROTOCOL` is set to `fcgi`, Gitea will listen for FastCGI requests on TCP socket - If `PROTOCOL` is set to `fcgi`, Gitea will listen for FastCGI requests on TCP socket
defined by `HTTP_ADDR` and `HTTP_PORT` configuration settings. defined by `HTTP_ADDR` and `HTTP_PORT` configuration settings.
- `UNIX_SOCKET_PERMISSION`: **666**: Permissions for the Unix socket. - `UNIX_SOCKET_PERMISSION`: **666**: Permissions for the Unix socket.
- `LOCAL_ROOT_URL`: **%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/**: Local
(DMZ) URL for Gitea workers (such as SSH update) accessing web service. In
most cases you do not need to change the default value. Alter it only if
your SSH server node is not the same as HTTP node. Do not set this variable
if `PROTOCOL` is set to `unix`.
- `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. - `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
- `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server.
- `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL.
@ -108,6 +115,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.
@ -121,6 +129,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `PASSWD`: **\<empty\>**: Database user password. Use \`your password\` for quoting if you use special characters in the password. - `PASSWD`: **\<empty\>**: Database user password. Use \`your password\` for quoting if you use special characters in the password.
- `SSL_MODE`: **disable**: For PostgreSQL only. - `SSL_MODE`: **disable**: For PostgreSQL only.
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. - `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
- `LOG_SQL`: **true**: Log the executed SQL.
## Indexer (`indexer`) ## Indexer (`indexer`)
@ -142,6 +151,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
authentication. authentication.
- `DISABLE_GIT_HOOKS`: **false**: Prevent all users (including admin) from creating custom - `DISABLE_GIT_HOOKS`: **false**: Prevent all users (including admin) from creating custom
git hooks. git hooks.
- `IMPORT_LOCAL_PATHS`: **false**: Prevent all users (including admin) from importing local path on server.
## OpenID (`openid`) ## OpenID (`openid`)
@ -198,8 +208,6 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
## Cache (`cache`) ## Cache (`cache`)
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`. - `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`.
- To use `redis` or `memcache`, be sure to rebuild everything with build tags `redis` or
`memcache`: `go build -tags='redis'`.
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only. - `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only.
- `HOST`: **\<empty\>**: Connection string for `redis` and `memcache`. - `HOST`: **\<empty\>**: Connection string for `redis` and `memcache`.
- Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180` - Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180`
@ -271,6 +279,45 @@ 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`.
## API (`api`)
- `ENABLE_SWAGGER_ENDPOINT`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
- `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page
## i18n (`i18n`)
- `LANGS`: **en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR**: List of locales shown in language selector
- `NAMES`: **English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어**: Visible names corresponding to the locales
### i18n - Datepicker Language (`i18n.datelang`)
Maps locales to the languages used by the datepicker plugin
- `en-US`: **en**
- `zh-CN`: **zh**
- `zh-HK`: **zh-HK**
- `zh-TW`: **zh-TW**
- `de-DE`: **de**
- `fr-FR`: **fr**
- `nl-NL`: **nl**
- `lv-LV`: **lv**
- `ru-RU`: **ru**
- `ja-JP`: **ja**
- `es-ES`: **es**
- `pt-BR`: **pt-BR**
- `pl-PL`: **pl**
- `bg-BG`: **bg**
- `it-IT`: **it**
- `fi-FI`: **fi**
- `tr-TR`: **tr**
- `cs-CZ`: **cs-CZ**
- `sr-SP`: **sr**
- `sv-SE`: **sv**
- `ko-KR`: **ko**
## U2F (`U2F`)
- `APP_ID`: **`ROOT_URL`**: Declares the facet of the application. Requires HTTPS.
- `TRUSTED_FACETS`: List of additional facets which are trusted. This is not support by all browsers.
## Markup (`markup`) ## Markup (`markup`)
Gitea can support Markup using external tools. The example below will add a markup named `asciidoc`. Gitea can support Markup using external tools. The example below will add a markup named `asciidoc`.

View File

@ -80,6 +80,7 @@ menu:
- `PASSWD`: 数据库用户密码。 - `PASSWD`: 数据库用户密码。
- `SSL_MODE`: PostgreSQL数据库是否启用SSL模式。 - `SSL_MODE`: PostgreSQL数据库是否启用SSL模式。
- `PATH`: Tidb 或者 SQLite3 数据文件存放路径。 - `PATH`: Tidb 或者 SQLite3 数据文件存放路径。
- `LOG_SQL`: **true**: 显示生成的SQL默认为真。
## Security (`security`) ## Security (`security`)

View File

@ -0,0 +1,620 @@
---
date: "2018-05-07T13:00:00+02:00"
title: "Gitea compared to other Git hosting options"
slug: "comparison"
weight: 5
toc: true
draft: false
menu:
sidebar:
parent: "features"
name: "Comparison"
weight: 5
identifier: "comparison"
---
# Gitea compared to other Git hosting options
To help decide if Gitea is suited for your needs here is how it compares to other Git self hosted options.
Be warned that we don't regularly check for feature changes in other products so this list can be outdated. If you find anything that needs to be updated in table below please report [issue on Github](https://github.com/go-gitea/gitea/issues).
_Symbols used in table:_
* _✓ - supported_
* _ - supported with limited functionality_
* _✘ - unsupported_
<table border="1" cellpadding="4">
<thead>
<tr>
<td>Feature</td>
<td>Gitea</td>
<td>Gogs</td>
<td>GitHub EE</td>
<td>GitLab CE</td>
<td>GitLab EE</td>
<td>BitBucket</td>
</tr>
</thead>
<tbody>
<tr>
<td>Open source and free</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Issue tracker</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pull/Merge requests</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Squash merging</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Rebase merging</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pull/Merge request inline comments</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pull/Merge request approval</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Merge conflict resolution</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Restrict push and merge access to certain users</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Markdown support</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</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>

View File

@ -1,5 +1,5 @@
--- ---
date: "2017-01-20T15:00:00+08:00" date: "2018-05-21T15:00:00+00:00"
title: "Support Options" title: "Support Options"
slug: "seek-help" slug: "seek-help"
weight: 10 weight: 10
@ -16,8 +16,7 @@ menu:
# Support Options # Support Options
- [Discord](https://discord.gg/NsatcWJ) - [Discord](https://discord.gg/NsatcWJ)
- [#gitea on Freenode](http://webchat.freenode.net?nick=giteachat....&channels=%23gitea&prompt=1) - [Discourse Forum](https://discourse.gitea.io/)
- [Matrix](https://matrix.to/#/#gitea-dev:matrix.org)
## Bugs ## Bugs

View File

@ -66,3 +66,17 @@ In this case, look into the following settings:
* `usermod` or `chsh` can be used to modify this. * `usermod` or `chsh` can be used to modify this.
* Ensure that the `gitea serv` command in `.ssh/authorized_keys` uses the * Ensure that the `gitea serv` command in `.ssh/authorized_keys` uses the
correct configuration file. correct configuration file.
## Missing releases after migrating repository with tags
To migrate an repository *with* all tags you need to do two things
* Push tags to the repository:
```
git push --tags
```
* (Re-)sync tags of all repositories within gitea:
```
gitea admin repo-sync-releases
```

View File

@ -223,6 +223,29 @@ favorite browser to finalize the installation. Visit http://server-ip:3000 and f
installation wizard. If the database was started with the `docker-compose` setup as installation wizard. If the database was started with the `docker-compose` setup as
documented above please note that `db` must be used as the database hostname. documented above please note that `db` must be used as the database hostname.
## Environments variables
You can configure some of Gitea's settings via environment variables:
(Default values are provided in **bold**)
* `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.
* `SSH_DOMAIN`: **localhost**: Domain name of this server, used for displayed clone UR in Gitea's UI.
* `SSH_PORT`: **22**: SSH port displayed in clone URL.
* `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
* `HTTP_PORT`: **3000**: HTTP listen port.
* `ROOT_URL`: **""**: Overwrite the automatically generated public URL. This is useful if the internal and the external URL don't match (e.g. in Docker).
* `DB_TYPE`: **sqlite3**: The database type in use \[mysql, postgres, mssql, sqlite3\].
* `DB_HOST`: **localhost:3306**: Database host address and port.
* `DB_NAME`: **gitea**: Database name.
* `DB_USER`: **root**: Database username.
* `DB_PASSWD`: **"<empty>"**: Database user password. Use \`your password\` for quoting if you use special characters in the password.
* `INSTALL_LOCK`: **false**: Disallow access to the install page.
* `SECRET_KEY`: **""**: Global secret key. This should be changed. If this has a value and `INSTALL_LOCK` is empty, `INSTALL_LOCK` will automatically set to `true`.
* `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users.
* `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page.
# Customization # Customization
Customization files described [here](https://docs.gitea.io/en-us/customizing-gitea/) should Customization files described [here](https://docs.gitea.io/en-us/customizing-gitea/) should

View File

@ -64,6 +64,13 @@ Admin operations:
- `--password value`, `-p value`: New password. Required. - `--password value`, `-p value`: New password. Required.
- Examples: - Examples:
- `gitea admin change-password --username myname --password asecurepassword` - `gitea admin change-password --username myname --password asecurepassword`
- `regenerate`
- Options:
- `hooks`: Regenerate git-hooks for all repositories
- `keys`: Regenerate authorized_keys file
- Examples:
- `gitea admin regenerate hooks`
- `gitea admin regenerate keys`
#### cert #### cert

View File

@ -0,0 +1,60 @@
---
date: "2018-05-11T11:00:00+02:00"
title: "Usage: Setup fail2ban"
slug: "fail2ban-setup"
weight: 16
toc: true
draft: false
menu:
sidebar:
parent: "usage"
name: "Fail2ban setup"
weight: 16
identifier: "fail2ban-setup"
---
# Fail2ban setup to block users after failed login attemts
**Remember that fail2ban is powerful and can cause lots of issues if you do it incorrectly, so make
sure to test this before relying on it so you don't lock yourself out.**
Gitea returns an HTTP 200 for bad logins in the web logs, but if you have logging options on in
`app.ini`, then you should be able to go off of log/gitea.log, which gives you something like this
on a bad authentication:
```log
2018/04/26 18:15:54 [I] Failed authentication attempt for user from xxx.xxx.xxx.xxx
```
So we set our filter in `/etc/fail2ban/filter.d/gitea.conf`:
```ini
# gitea.conf
[Definition]
failregex = .*Failed authentication attempt for .* from <HOST>
ignoreregex =
```
And configure it in `/etc/fail2ban/jail.d/jail.local`:
```ini
[gitea]
enabled = true
port = http,https
filter = gitea
logpath = /home/git/gitea/log/gitea.log
maxretry = 10
findtime = 3600
bantime = 900
action = iptables-allports
```
Make sure and read up on fail2ban and configure it to your needs, this bans someone
for **15 minutes** (from all ports) when they fail authentication 10 times in an hour.
If you run Gitea behind a reverse proxy with nginx (for example with docker), you need to add
this to your nginx configuration so that IPs don't show up as 127.0.0.1:
```
proxy_set_header X-Real-IP $remote_addr;
```

View 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]`.

View File

@ -0,0 +1,41 @@
---
date: "2018-05-10T16:00:00+02:00"
title: "Usage: Issue and Pull Request templates"
slug: "issue-pull-request-templates"
weight: 15
toc: true
draft: false
menu:
sidebar:
parent: "usage"
name: "Issue and Pull Request templates"
weight: 15
identifier: "issue-pull-request-templates"
---
# Issue and Pull Request Templates
For some projects there are a standard list of questions that users need to be asked
for creating an issue, or adding a pull request. Gitea supports adding templates to the
main branch of the repository so that they can autopopulate the form when users are
creating issues, and pull requests. This will cut down on the initial back and forth
of getting some clarifiying details.
Possible file names for issue templates:
* ISSUE_TEMPLATE.md
* issue_template.md
* .gitea/ISSUE_TEMPLATE.md
* .gitea/issue_template.md
* .github/ISSUE_TEMPLATE.md
* .github/issue_template.md
Possible file names for PR templates:
* PULL_REQUEST_TEMPLATE.md
* pull_request_template.md
* .gitea/PULL_REQUEST_TEMPLATE.md
* .gitea/pull_request_template.md
* .github/PULL_REQUEST_TEMPLATE.md
* .github/pull_request_template.md

View File

@ -0,0 +1,104 @@
---
date: "2018-05-22T11:00:00+00:00"
title: "Usage: Reverse Proxies"
slug: "reverse-proxies"
weight: 17
toc: true
draft: false
menu:
sidebar:
parent: "usage"
name: "Reverse Proxies"
weight: 16
identifier: "reverse-proxies"
---
## Using Nginx as a reverse proxy
If you want Nginx to serve your Gitea instance you can the following `server` section inside the `http` section of `nginx.conf`:
```
server {
listen 80;
server_name git.example.com;
location / {
proxy_pass http://localhost:3000;
}
}
```
## Using Nginx with a Sub-path as a reverse proxy
In case you already have a site, and you want Gitea to share the domain name, you can setup Nginx to serve Gitea under a sub-path by adding the following `server` section inside the `http` section of `nginx.conf`:
```
server {
listen 80;
server_name git.example.com;
location /git/ { # Note: Trailing slash
proxy_pass http://localhost:3000/; # Note: Trailing slash
}
}
```
Then set `[server] ROOT_URL = http://git.example.com/git/` in your configuration.
## Using Apache HTTPD as a reverse proxy
If you want Apache HTTPD to serve your Gitea instance you can add the following to you Apache HTTPD configuration (usually located at `/etc/apache2/httpd.conf` in Ubuntu):
```
<VirtualHost *:80>
...
ProxyPreserveHost On
ProxyRequests off
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
</VirtualHost>
```
Note: The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`
## Using Apache HTTPD with a Sub-path as a reverse proxy
In case you already have a site, and you want Gitea to share the domain name, you can setup Apache HTTPD to serve Gitea under a sub-path by adding the following to you Apache HTTPD configuration (usually located at `/etc/apache2/httpd.conf` in Ubuntu):
```
<VirtualHost *:80>
...
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
ProxyPass /git http://localhost:3000 # Note: no trailing slash after either /git or port
ProxyPassReverse /git http://localhost:3000 # Note: no trailing slash after either /git or port
</VirtualHost>
```
Then set `[server] ROOT_URL = http://git.example.com/git/` in your configuration.
Note: The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`
## Using Caddy with a Sub-path as a reverse proxy
If you want Caddy to serve your Gitea instance you can add the following server block to your Caddyfile:
```
git.example.com {
proxy / http://localhost:3000
}
```
## Using Caddy with a Sub-path as a reverse proxy
In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to you server block in your Caddyfile:
```
git.example.com {
proxy /git/ http://localhost:3000 # Note: Trailing Slash after /git/
}
```
Then set `[server] ROOT_URL = http://git.example.com/git/` in your configuration.

View File

@ -2,6 +2,7 @@
date: "2016-11-08T16:00:00+02:00" date: "2016-11-08T16:00:00+02:00"
title: "Documentation" title: "Documentation"
slug: "documentation" slug: "documentation"
url: "/en-us/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false

View File

@ -2,6 +2,7 @@
date: "2017-08-23T09:00:00+02:00" date: "2017-08-23T09:00:00+02:00"
title: "Documentation" title: "Documentation"
slug: "documentation" slug: "documentation"
url: "/fr-fr/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false
@ -48,7 +49,7 @@ Le but de ce projet est de fournir de la manière la plus simple, la plus rapide
- Migré - Migré
- Notifications (courriel et web) - Notifications (courriel et web)
- Lu - Lu
- Non lu - Non lu
- Épinglé - Épinglé
- Page d'exploration - Page d'exploration
- Utilisateurs - Utilisateurs

View File

@ -2,6 +2,7 @@
date: "2016-11-08T16:00:00+02:00" date: "2016-11-08T16:00:00+02:00"
title: "文档" title: "文档"
slug: "documentation" slug: "documentation"
url: "/zh-cn/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false

View File

@ -2,6 +2,7 @@
date: "2016-11-08T16:00:00+02:00" date: "2016-11-08T16:00:00+02:00"
title: "文件" title: "文件"
slug: "documentation" slug: "documentation"
url: "/zh-tw/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false

View File

@ -28,6 +28,8 @@ for SOURCE in $(find ${ROOT}/content -type f -iname *.en-us.md); do
if [[ ! -f ${DEST} ]]; then if [[ ! -f ${DEST} ]]; then
echo "Creating fallback for ${DEST#${ROOT}/content/}" echo "Creating fallback for ${DEST#${ROOT}/content/}"
cp ${SOURCE} ${DEST} cp ${SOURCE} ${DEST}
sed -i.bak "s/en\-us/${LOCALE}/g" ${DEST}
rm ${DEST}.bak
fi fi
done done
done done

View File

@ -0,0 +1,231 @@
// 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"
"os"
"strings"
"testing"
"code.gitea.io/gitea/models"
"github.com/Unknwon/i18n"
"github.com/stretchr/testify/assert"
)
type ldapUser struct {
UserName string
Password string
FullName string
Email string
OtherEmails []string
IsAdmin bool
SSHKeys []string
}
var gitLDAPUsers = []ldapUser{
{
UserName: "professor",
Password: "professor",
FullName: "Hubert Farnsworth",
Email: "professor@planetexpress.com",
OtherEmails: []string{"hubert@planetexpress.com"},
IsAdmin: true,
},
{
UserName: "hermes",
Password: "hermes",
FullName: "Conrad Hermes",
Email: "hermes@planetexpress.com",
SSHKeys: []string{
"SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8",
"SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ",
},
IsAdmin: true,
},
{
UserName: "fry",
Password: "fry",
FullName: "Philip Fry",
Email: "fry@planetexpress.com",
},
{
UserName: "leela",
Password: "leela",
FullName: "Leela Turanga",
Email: "leela@planetexpress.com",
},
{
UserName: "bender",
Password: "bender",
FullName: "Bender Rodríguez",
Email: "bender@planetexpress.com",
},
}
var otherLDAPUsers = []ldapUser{
{
UserName: "zoidberg",
Password: "zoidberg",
FullName: "John Zoidberg",
Email: "zoidberg@planetexpress.com",
},
{
UserName: "amy",
Password: "amy",
FullName: "Amy Kroker",
Email: "amy@planetexpress.com",
},
}
func skipLDAPTests() bool {
return os.Getenv("TEST_LDAP") != "1"
}
func getLDAPServerHost() string {
host := os.Getenv("TEST_LDAP_HOST")
if len(host) == 0 {
host = "ldap"
}
return host
}
func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string) {
session := loginUser(t, "user1")
csrf := GetCSRF(t, session, "/admin/auths/new")
req := NewRequestWithValues(t, "POST", "/admin/auths/new", map[string]string{
"_csrf": csrf,
"type": "2",
"name": "ldap",
"host": getLDAPServerHost(),
"port": "389",
"bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com",
"bind_password": "password",
"user_base": "ou=people,dc=planetexpress,dc=com",
"filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
"admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
"attribute_username": "uid",
"attribute_name": "givenName",
"attribute_surname": "sn",
"attribute_mail": "mail",
"attribute_ssh_public_key": sshKeyAttribute,
"is_sync_enabled": "on",
"is_active": "on",
})
session.MakeRequest(t, req, http.StatusFound)
}
func TestLDAPUserSignin(t *testing.T) {
if skipLDAPTests() {
t.Skip()
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t, "")
u := gitLDAPUsers[0]
session := loginUserWithPassword(t, u.UserName, u.Password)
req := NewRequest(t, "GET", "/user/settings")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name"))
assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name"))
assert.Equal(t, u.Email, htmlDoc.GetInputValueByName("email"))
}
func TestLDAPUserSync(t *testing.T) {
if skipLDAPTests() {
t.Skip()
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t, "")
models.SyncExternalUsers()
session := loginUser(t, "user1")
// Check if users exists
for _, u := range gitLDAPUsers {
req := NewRequest(t, "GET", "/admin/users?q="+u.UserName)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
tr := htmlDoc.doc.Find("table.table tbody tr")
if !assert.True(t, tr.Length() == 1) {
continue
}
tds := tr.Find("td")
if !assert.True(t, tds.Length() > 0) {
continue
}
assert.Equal(t, u.UserName, strings.TrimSpace(tds.Find("td:nth-child(2) a").Text()))
assert.Equal(t, u.Email, strings.TrimSpace(tds.Find("td:nth-child(3) span").Text()))
if u.IsAdmin {
assert.True(t, tds.Find("td:nth-child(5) i").HasClass("fa-check-square-o"))
} else {
assert.True(t, tds.Find("td:nth-child(5) i").HasClass("fa-square-o"))
}
}
// Check if no users exist
for _, u := range otherLDAPUsers {
req := NewRequest(t, "GET", "/admin/users?q="+u.UserName)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
tr := htmlDoc.doc.Find("table.table tbody tr")
assert.True(t, tr.Length() == 0)
}
}
func TestLDAPUserSigninFailed(t *testing.T) {
if skipLDAPTests() {
t.Skip()
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t, "")
u := otherLDAPUsers[0]
testLoginFailed(t, u.UserName, u.Password, i18n.Tr("en", "form.username_password_incorrect"))
}
func TestLDAPUserSSHKeySync(t *testing.T) {
if skipLDAPTests() {
t.Skip()
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t, "sshPublicKey")
models.SyncExternalUsers()
// Check if users has SSH keys synced
for _, u := range gitLDAPUsers {
if len(u.SSHKeys) == 0 {
continue
}
session := loginUserWithPassword(t, u.UserName, u.Password)
req := NewRequest(t, "GET", "/user/settings/keys")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
divs := htmlDoc.doc.Find(".key.list .print.meta")
syncedKeys := make([]string, divs.Length())
for i := 0; i < divs.Length(); i++ {
syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text())
}
assert.ElementsMatch(t, u.SSHKeys, syncedKeys)
}
}

View File

@ -43,8 +43,8 @@ func TestUserDeleteAccount(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user8") session := loginUser(t, "user8")
csrf := GetCSRF(t, session, "/user/settings/delete") csrf := GetCSRF(t, session, "/user/settings/account")
urlStr := fmt.Sprintf("/user/settings/delete?password=%s", userPassword) urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword)
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"_csrf": csrf, "_csrf": csrf,
}) })
@ -58,8 +58,8 @@ func TestUserDeleteAccountStillOwnRepos(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user2") session := loginUser(t, "user2")
csrf := GetCSRF(t, session, "/user/settings/delete") csrf := GetCSRF(t, session, "/user/settings/account")
urlStr := fmt.Sprintf("/user/settings/delete?password=%s", userPassword) urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword)
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
"_csrf": csrf, "_csrf": csrf,
}) })

View File

@ -52,7 +52,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
// Check if master branch has been locked successfully // Check if master branch has been locked successfully
flashCookie := session.GetCookie("macaron_flash") flashCookie := session.GetCookie("macaron_flash")
assert.NotNil(t, flashCookie) assert.NotNil(t, flashCookie)
assert.EqualValues(t, "success%3DBranch%2Bmaster%2Bprotect%2Boptions%2Bchanged%2Bsuccessfully.", flashCookie.Value) assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
// Request editor page // Request editor page
req = NewRequest(t, "GET", "/user2/repo1/_new/master/") req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
@ -73,7 +73,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
// Check body for error message // Check body for error message
assert.Contains(t, resp.Body.String(), "Can not commit to protected branch &#39;master&#39;.") assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch &#39;master&#39;.")
// remove the protected branch // remove the protected branch
csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") csrf = GetCSRF(t, session, "/user2/repo1/settings/branches")
@ -86,7 +86,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
// Check if master branch has been locked successfully // Check if master branch has been locked successfully
flashCookie = session.GetCookie("macaron_flash") flashCookie = session.GetCookie("macaron_flash")
assert.NotNil(t, flashCookie) assert.NotNil(t, flashCookie)
assert.EqualValues(t, "success%3DBranch%2Bmaster%2Bprotect%2Boptions%2Bremoved%2Bsuccessfully", flashCookie.Value) assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Bbranch%2B%2527master%2527%2Bhas%2Bbeen%2Bdisabled.", flashCookie.Value)
} }

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@ -0,0 +1,114 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 1) and a time in nanoseconds
# formatted as a string and outputs to stdout all files that have been
# modified since the given time. Paths must be relative to the root of
# the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $time) = @ARGV;
# Check the hook interface version
if ($version == 1) {
# convert nanoseconds to seconds
$time = int $time / 1000000000;
} else {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$git_work_tree = Win32::GetCwd();
$git_work_tree =~ tr/\\/\//;
} else {
require Cwd;
$git_work_tree = Cwd::cwd();
}
my $retry = 1;
launch_watchman();
sub launch_watchman {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $time but were not transient (ie created after
# $time but no longer exist).
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
#
# The category of transient files that we want to ignore will have a
# creation clock (cclock) newer than $time_t value and will also not
# currently exist.
my $query = <<" END";
["query", "$git_work_tree", {
"since": $time,
"fields": ["name"],
"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
}]
END
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
my $o = $json_pkg->new->utf8->decode($response);
if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
$retry--;
qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
print "/\0";
eval { launch_watchman() };
exit 0;
}
die "Watchman: $o->{error}.\n" .
"Falling back to scanning...\n" if $o->{error};
binmode STDOUT, ":utf8";
local $, = "\0";
print @{$o->{files}};
}

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"/home/tris/Projects/go/src/code.gitea.io/gitea/gitea" hook --config='/home/tris/Projects/go/src/code.gitea.io/gitea/custom/conf/app.ini' post-receive

View File

@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local sha1> <remote ref> <remote sha1>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
z40=0000000000000000000000000000000000000000
while read local_ref local_sha remote_ref remote_sha
do
if [ "$local_sha" = $z40 ]
then
# Handle delete
:
else
if [ "$remote_sha" = $z40 ]
then
# New branch, examine all commits
range="$local_sha"
else
# Update to existing branch, examine new commits
range="$remote_sha..$local_sha"
fi
# Check for WIP commit
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
if [ -n "$commit" ]
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"/home/tris/Projects/go/src/code.gitea.io/gitea/gitea" hook --config='/home/tris/Projects/go/src/code.gitea.io/gitea/custom/conf/app.ini' pre-receive

View File

@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

View File

@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" || continue
"${hook}" $1 $2 $3
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"/home/tris/Projects/go/src/code.gitea.io/gitea/gitea" hook --config='/home/tris/Projects/go/src/code.gitea.io/gitea/custom/conf/app.ini' update $1 $2 $3

View File

@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
allowmodifytag=$(git config --bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero="0000000000000000000000000000000000000000"
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0

View File

@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@ -0,0 +1 @@
808038d2f71b0ab020991439cffd24309c7bc530 refs/heads/master

View File

@ -0,0 +1 @@
808038d2f71b0ab020991439cffd24309c7bc530

View File

@ -93,15 +93,12 @@ func testLinksAsUser(userName string, t *testing.T) {
"/user2?tab=stars", "/user2?tab=stars",
"/user2?tab=activity", "/user2?tab=activity",
"/user/settings", "/user/settings",
"/user/settings/avatar", "/user/settings/account",
"/user/settings/security", "/user/settings/security",
"/user/settings/security/two_factor/enroll", "/user/settings/security/two_factor/enroll",
"/user/settings/email",
"/user/settings/keys", "/user/settings/keys",
"/user/settings/applications",
"/user/settings/account_link",
"/user/settings/organization", "/user/settings/organization",
"/user/settings/delete", "/user/settings/repos",
} }
session := loginUser(t, userName) session := loginUser(t, userName)

View File

@ -121,5 +121,5 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() resultMsg := htmlDoc.doc.Find(".ui.message>p").Text()
assert.EqualValues(t, "user1/feature/test has been deleted.", resultMsg) assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg)
} }

View File

@ -7,10 +7,12 @@ package integrations
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
"testing" "testing"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -74,3 +76,26 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.RunUser, setting.SSH.Domain, setting.SSH.Port) sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.RunUser, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link) assert.Equal(t, sshURL, link)
} }
func TestViewRepoWithSymlinks(t *testing.T) {
prepareTestEnv(t)
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo20.git")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
files := htmlDoc.doc.Find("#repo-files-table > TBODY > TR > TD.name")
items := files.Map(func(i int, s *goquery.Selection) string {
cls, _ := s.Find("SPAN").Attr("class")
file := strings.Trim(s.Find("A").Text(), " \t\n")
return fmt.Sprintf("%s: %s", file, cls)
})
assert.Equal(t, len(items), 5)
assert.Equal(t, items[0], "a: octicon octicon-file-directory")
assert.Equal(t, items[1], "link_b: octicon octicon-file-symlink-directory")
assert.Equal(t, items[2], "link_d: octicon octicon-file-symlink-file")
assert.Equal(t, items[3], "link_hi: octicon octicon-file-symlink-file")
assert.Equal(t, items[4], "link_link: octicon octicon-file-symlink-file")
}

View File

@ -27,9 +27,10 @@ func TestRenameUsername(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings"), "_csrf": GetCSRF(t, session, "/user/settings"),
"name": "newUsername", "name": "newUsername",
"email": "user2@example.com", "email": "user2@example.com",
"language": "en-us",
}) })
session.MakeRequest(t, req, http.StatusFound) session.MakeRequest(t, req, http.StatusFound)
@ -81,9 +82,10 @@ func TestRenameReservedUsername(t *testing.T) {
for _, reservedUsername := range reservedUsernames { for _, reservedUsername := range reservedUsernames {
t.Logf("Testing username %s", reservedUsername) t.Logf("Testing username %s", reservedUsername)
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings"), "_csrf": GetCSRF(t, session, "/user/settings"),
"name": reservedUsername, "name": reservedUsername,
"email": "user2@example.com", "email": "user2@example.com",
"language": "en-us",
}) })
resp := session.MakeRequest(t, req, http.StatusFound) resp := session.MakeRequest(t, req, http.StatusFound)

View File

@ -24,6 +24,7 @@ func TestXSSUserFullName(t *testing.T) {
"name": user.Name, "name": user.Name,
"full_name": fullName, "full_name": fullName,
"email": user.Email, "email": user.Email,
"language": "en-us",
}) })
session.MakeRequest(t, req, http.StatusFound) session.MakeRequest(t, req, http.StatusFound)

View File

@ -618,6 +618,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
case ActionDeleteBranch: // Delete Branch case ActionDeleteBranch: // Delete Branch
isHookEventPush = true isHookEventPush = true
if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{
Ref: refName,
RefType: "branch",
PusherType: api.PusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err)
}
case ActionPushTag: // Create case ActionPushTag: // Create
isHookEventPush = true isHookEventPush = true
@ -640,6 +650,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
} }
case ActionDeleteTag: // Delete Tag case ActionDeleteTag: // Delete Tag
isHookEventPush = true isHookEventPush = true
if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{
Ref: refName,
RefType: "tag",
PusherType: api.PusherTypeUser,
Repo: apiRepo,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err)
}
} }
if isHookEventPush { if isHookEventPush {

View File

@ -11,7 +11,6 @@ import (
"os" "os"
"path" "path"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
@ -29,6 +28,7 @@ type Attachment struct {
CommentID int64 CommentID int64
Name string Name string
DownloadCount int64 `xorm:"DEFAULT 0"` DownloadCount int64 `xorm:"DEFAULT 0"`
Size int64 `xorm:"DEFAULT 0"`
CreatedUnix util.TimeStamp `xorm:"created"` CreatedUnix util.TimeStamp `xorm:"created"`
} }
@ -44,13 +44,12 @@ func (a *Attachment) IncreaseDownloadCount() error {
// APIFormat converts models.Attachment to api.Attachment // APIFormat converts models.Attachment to api.Attachment
func (a *Attachment) APIFormat() *api.Attachment { func (a *Attachment) APIFormat() *api.Attachment {
size, _ := a.Size()
return &api.Attachment{ return &api.Attachment{
ID: a.ID, ID: a.ID,
Name: a.Name, Name: a.Name,
Created: a.CreatedUnix.AsTime(), Created: a.CreatedUnix.AsTime(),
DownloadCount: a.DownloadCount, DownloadCount: a.DownloadCount,
Size: size, Size: a.Size,
UUID: a.UUID, UUID: a.UUID,
DownloadURL: a.DownloadURL(), DownloadURL: a.DownloadURL(),
} }
@ -67,25 +66,6 @@ func (a *Attachment) LocalPath() string {
return AttachmentLocalPath(a.UUID) return AttachmentLocalPath(a.UUID)
} }
// Size returns the file's size of the attachment
func (a *Attachment) Size() (int64, error) {
fi, err := os.Stat(a.LocalPath())
if err != nil {
return 0, err
}
return fi.Size(), nil
}
// MustSize returns the result of a.Size() by ignoring errors
func (a *Attachment) MustSize() int64 {
size, err := a.Size()
if err != nil {
log.Error(4, "size: %v", err)
return 0
}
return size
}
// DownloadURL returns the download url of the attached file // DownloadURL returns the download url of the attached file
func (a *Attachment) DownloadURL() string { func (a *Attachment) DownloadURL() string {
return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID) return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID)
@ -115,6 +95,13 @@ func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment,
return nil, fmt.Errorf("Copy: %v", err) return nil, fmt.Errorf("Copy: %v", err)
} }
// Update file size
var fi os.FileInfo
if fi, err = fw.Stat(); err != nil {
return nil, fmt.Errorf("file size: %v", err)
}
attach.Size = fi.Size()
if _, err := x.Insert(attach); err != nil { if _, err := x.Insert(attach); err != nil {
return nil, err return nil, err
} }

View File

@ -733,6 +733,22 @@ func (err ErrRepoFileAlreadyExist) Error() string {
return fmt.Sprintf("repository file already exists [file_name: %s]", err.FileName) return fmt.Sprintf("repository file already exists [file_name: %s]", err.FileName)
} }
// ErrUserDoesNotHaveAccessToRepo represets an error where the user doesn't has access to a given repo
type ErrUserDoesNotHaveAccessToRepo struct {
UserID int64
RepoName string
}
// IsErrUserDoesNotHaveAccessToRepo checks if an error is a ErrRepoFileAlreadyExist.
func IsErrUserDoesNotHaveAccessToRepo(err error) bool {
_, ok := err.(ErrUserDoesNotHaveAccessToRepo)
return ok
}
func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
return fmt.Sprintf("user doesn't have acces to repo [user_id: %d, repo_name: %s]", err.UserID, err.RepoName)
}
// __________ .__ // __________ .__
// \______ \____________ ____ ____ | |__ // \______ \____________ ____ ____ | |__
// | | _/\_ __ \__ \ / \_/ ___\| | \ // | | _/\_ __ \__ \ / \_/ ___\| | \
@ -1221,3 +1237,25 @@ func IsErrExternalLoginUserNotExist(err error) bool {
func (err ErrExternalLoginUserNotExist) Error() string { func (err ErrExternalLoginUserNotExist) Error() string {
return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID) return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
} }
// ____ ________________________________ .__ __ __ .__
// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____
// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \
// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \
// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| /
// \/ \/ \/ \/_____/ \/ \/ \/
// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
type ErrU2FRegistrationNotExist struct {
ID int64
}
func (err ErrU2FRegistrationNotExist) Error() string {
return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
}
// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}

View File

@ -3,3 +3,4 @@
repo_id: 1 repo_id: 1
hook_id: 1 hook_id: 1
uuid: uuid1 uuid: uuid1
is_delivered: true

View File

@ -3,7 +3,6 @@
repo_id: 1 repo_id: 1
index: 1 index: 1
poster_id: 1 poster_id: 1
assignee_id: 1
name: issue1 name: issue1
content: content for the first issue content: content for the first issue
is_closed: false is_closed: false
@ -67,7 +66,6 @@
repo_id: 3 repo_id: 3
index: 1 index: 1
poster_id: 1 poster_id: 1
assignee_id: 1
name: issue6 name: issue6
content: content6 content: content6
is_closed: false is_closed: false

View File

@ -0,0 +1,8 @@
-
id: 1
assignee_id: 1
issue_id: 1
-
id: 2
assignee_id: 1
issue_id: 6

View File

@ -3,7 +3,6 @@
uid: 1 uid: 1
issue_id: 1 issue_id: 1
is_read: true is_read: true
is_assigned: true
is_mentioned: false is_mentioned: false
- -
@ -11,7 +10,6 @@
uid: 2 uid: 2
issue_id: 1 issue_id: 1
is_read: true is_read: true
is_assigned: false
is_mentioned: false is_mentioned: false
- -
@ -19,5 +17,4 @@
uid: 4 uid: 4
issue_id: 1 issue_id: 1
is_read: false is_read: false
is_assigned: false
is_mentioned: false is_mentioned: false

View File

@ -0,0 +1 @@
[] # empty

View File

@ -0,0 +1,11 @@
-
repo_id: 1
topic_id: 1
-
repo_id: 1
topic_id: 2
-
repo_id: 1
topic_id: 3

View File

@ -67,3 +67,10 @@
type: 5 type: 5
config: "{}" config: "{}"
created_unix: 946684810 created_unix: 946684810
-
id: 11
repo_id: 31
type: 1
config: "{}"
created_unix: 1524304355

View File

@ -378,4 +378,14 @@
num_pulls: 0 num_pulls: 0
num_closed_pulls: 0 num_closed_pulls: 0
is_mirror: false is_mirror: false
is_fork: true is_fork: true
-
id: 31
owner_id: 2
lower_name: repo20
name: repo20
num_stars: 0
num_forks: 0
num_issues: 0
is_mirror: false

13
models/fixtures/topic.yml Normal file
View File

@ -0,0 +1,13 @@
-
id: 1
name: golang
repo_count: 1
-
id: 2
name: database
repo_count: 1
- id: 3
name: SQL
repo_count: 1

View File

@ -0,0 +1,7 @@
-
id: 1
name: "U2F Key"
user_id: 1
counter: 0
created_unix: 946684800
updated_unix: 946684800

View File

@ -27,7 +27,7 @@
is_admin: false is_admin: false
avatar: avatar2 avatar: avatar2
avatar_email: user2@example.com avatar_email: user2@example.com
num_repos: 4 num_repos: 5
num_stars: 2 num_stars: 2
num_followers: 2 num_followers: 2
num_following: 1 num_following: 1
@ -313,4 +313,4 @@
avatar: avatar20 avatar: avatar20
avatar_email: user20@example.com avatar_email: user20@example.com
num_repos: 4 num_repos: 4
is_active: true is_active: true

View File

@ -21,16 +21,16 @@ func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) {
func TestDiffToHTML(t *testing.T) { func TestDiffToHTML(t *testing.T) {
assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{ assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{
{dmp.DiffEqual, "foo "}, {Type: dmp.DiffEqual, Text: "foo "},
{dmp.DiffInsert, "bar"}, {Type: dmp.DiffInsert, Text: "bar"},
{dmp.DiffDelete, " baz"}, {Type: dmp.DiffDelete, Text: " baz"},
{dmp.DiffEqual, " biz"}, {Type: dmp.DiffEqual, Text: " biz"},
}, DiffLineAdd)) }, DiffLineAdd))
assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{ assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{
{dmp.DiffEqual, "foo "}, {Type: dmp.DiffEqual, Text: "foo "},
{dmp.DiffDelete, "bar"}, {Type: dmp.DiffDelete, Text: "bar"},
{dmp.DiffInsert, " baz"}, {Type: dmp.DiffInsert, Text: " baz"},
{dmp.DiffEqual, " biz"}, {Type: dmp.DiffEqual, Text: " biz"},
}, DiffLineDel)) }, DiffLineDel))
} }

View File

@ -37,7 +37,7 @@ type Issue struct {
MilestoneID int64 `xorm:"INDEX"` MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`
Priority int Priority int
AssigneeID int64 `xorm:"INDEX"` AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"` Assignee *User `xorm:"-"`
IsClosed bool `xorm:"INDEX"` IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"` IsRead bool `xorm:"-"`
@ -47,13 +47,16 @@ type Issue struct {
Ref string Ref string
DeadlineUnix util.TimeStamp `xorm:"INDEX"` DeadlineUnix util.TimeStamp `xorm:"INDEX"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
ClosedUnix util.TimeStamp `xorm:"INDEX"`
Attachments []*Attachment `xorm:"-"` CreatedUnix util.TimeStamp `xorm:"INDEX created"`
Comments []*Comment `xorm:"-"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
Reactions ReactionList `xorm:"-"` ClosedUnix util.TimeStamp `xorm:"INDEX"`
Attachments []*Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"`
Reactions ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
Assignees []*User `xorm:"-"`
} }
var ( var (
@ -69,6 +72,20 @@ func init() {
issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr) issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr)
} }
func (issue *Issue) loadTotalTimes(e Engine) (err error) {
opts := FindTrackedTimesOptions{IssueID: issue.ID}
issue.TotalTrackedTime, err = opts.ToSession(e).SumInt(&TrackedTime{}, "time")
if err != nil {
return err
}
return nil
}
// IsOverdue checks if the issue is overdue
func (issue *Issue) IsOverdue() bool {
return util.TimeStampNow() >= issue.DeadlineUnix
}
func (issue *Issue) loadRepo(e Engine) (err error) { func (issue *Issue) loadRepo(e Engine) (err error) {
if issue.Repo == nil { if issue.Repo == nil {
issue.Repo, err = getRepositoryByID(e, issue.RepoID) issue.Repo, err = getRepositoryByID(e, issue.RepoID)
@ -79,6 +96,15 @@ func (issue *Issue) loadRepo(e Engine) (err error) {
return nil return nil
} }
// IsTimetrackerEnabled returns true if the repo enables timetracking
func (issue *Issue) IsTimetrackerEnabled() bool {
if err := issue.loadRepo(x); err != nil {
log.Error(4, fmt.Sprintf("loadRepo: %v", err))
return false
}
return issue.Repo.IsTimetrackerEnabled()
}
// GetPullRequest returns the issue pull request // GetPullRequest returns the issue pull request
func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) {
if !issue.IsPull { if !issue.IsPull {
@ -115,22 +141,6 @@ func (issue *Issue) loadPoster(e Engine) (err error) {
return return
} }
func (issue *Issue) loadAssignee(e Engine) (err error) {
if issue.Assignee == nil && issue.AssigneeID > 0 {
issue.Assignee, err = getUserByID(e, issue.AssigneeID)
if err != nil {
issue.AssigneeID = -1
issue.Assignee = NewGhostUser()
if !IsErrUserNotExist(err) {
return fmt.Errorf("getUserByID.(assignee) [%d]: %v", issue.AssigneeID, err)
}
err = nil
return
}
}
return
}
func (issue *Issue) loadPullRequest(e Engine) (err error) { func (issue *Issue) loadPullRequest(e Engine) (err error) {
if issue.IsPull && issue.PullRequest == nil { if issue.IsPull && issue.PullRequest == nil {
issue.PullRequest, err = getPullRequestByIssueID(e, issue.ID) issue.PullRequest, err = getPullRequestByIssueID(e, issue.ID)
@ -206,7 +216,7 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
} }
} }
if err = issue.loadAssignee(e); err != nil { if err = issue.loadAssignees(e); err != nil {
return return
} }
@ -225,6 +235,11 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
if err = issue.loadComments(e); err != nil { if err = issue.loadComments(e); err != nil {
return err return err
} }
if issue.IsTimetrackerEnabled() {
if err = issue.loadTotalTimes(e); err != nil {
return err
}
}
return issue.loadReactions(e) return issue.loadReactions(e)
} }
@ -313,8 +328,11 @@ func (issue *Issue) APIFormat() *api.Issue {
if issue.Milestone != nil { if issue.Milestone != nil {
apiIssue.Milestone = issue.Milestone.APIFormat() apiIssue.Milestone = issue.Milestone.APIFormat()
} }
if issue.Assignee != nil { if len(issue.Assignees) > 0 {
apiIssue.Assignee = issue.Assignee.APIFormat() for _, assignee := range issue.Assignees {
apiIssue.Assignees = append(apiIssue.Assignees, assignee.APIFormat())
}
apiIssue.Assignee = issue.Assignees[0].APIFormat() // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
} }
if issue.IsPull { if issue.IsPull {
apiIssue.PullRequest = &api.PullRequestMeta{ apiIssue.PullRequest = &api.PullRequestMeta{
@ -324,6 +342,9 @@ func (issue *Issue) APIFormat() *api.Issue {
apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr() apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
} }
} }
if issue.DeadlineUnix != 0 {
apiIssue.Deadline = issue.DeadlineUnix.AsTimePtr()
}
return apiIssue return apiIssue
} }
@ -349,11 +370,19 @@ func (issue *Issue) HasLabel(labelID int64) bool {
func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
var err error var err error
if err = issue.loadRepo(x); err != nil {
log.Error(4, "loadRepo: %v", err)
return
}
if err = issue.loadPoster(x); err != nil {
log.Error(4, "loadPoster: %v", err)
return
}
mode, _ := AccessLevel(issue.Poster.ID, issue.Repo)
if issue.IsPull { if issue.IsPull {
if err = issue.loadRepo(x); err != nil {
log.Error(4, "loadRepo: %v", err)
return
}
if err = issue.loadPullRequest(x); err != nil { if err = issue.loadPullRequest(x); err != nil {
log.Error(4, "loadPullRequest: %v", err) log.Error(4, "loadPullRequest: %v", err)
return return
@ -369,6 +398,14 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
Repository: issue.Repo.APIFormat(AccessModeNone), Repository: issue.Repo.APIFormat(AccessModeNone),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
} else {
err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
} }
if err != nil { if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
@ -484,6 +521,11 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
return fmt.Errorf("Commit: %v", err) return fmt.Errorf("Commit: %v", err)
} }
if err = issue.loadPoster(x); err != nil {
return fmt.Errorf("loadPoster: %v", err)
}
mode, _ := AccessLevel(issue.Poster.ID, issue.Repo)
if issue.IsPull { if issue.IsPull {
err = issue.PullRequest.LoadIssue() err = issue.PullRequest.LoadIssue()
if err != nil { if err != nil {
@ -494,9 +536,17 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
Action: api.HookIssueLabelCleared, Action: api.HookIssueLabelCleared,
Index: issue.Index, Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(), PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(AccessModeNone), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
} else {
err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{
Action: api.HookIssueLabelCleared,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
} }
if err != nil { if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
@ -572,19 +622,6 @@ func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) {
return sess.Commit() return sess.Commit()
} }
// GetAssignee sets the Assignee attribute of this issue.
func (issue *Issue) GetAssignee() (err error) {
if issue.AssigneeID == 0 || issue.Assignee != nil {
return nil
}
issue.Assignee, err = GetUserByID(issue.AssigneeID)
if IsErrUserNotExist(err) {
return nil
}
return err
}
// ReadBy sets issue to be read by given user. // ReadBy sets issue to be read by given user.
func (issue *Issue) ReadBy(userID int64) error { func (issue *Issue) ReadBy(userID int64) error {
if err := UpdateIssueUserByRead(userID, issue.ID); err != nil { if err := UpdateIssueUserByRead(userID, issue.ID); err != nil {
@ -667,13 +704,14 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
return fmt.Errorf("Commit: %v", err) return fmt.Errorf("Commit: %v", err)
} }
mode, _ := AccessLevel(issue.Poster.ID, issue.Repo)
if issue.IsPull { if issue.IsPull {
// Merge pull request calls issue.changeStatus so we need to handle separately. // Merge pull request calls issue.changeStatus so we need to handle separately.
issue.PullRequest.Issue = issue issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: issue.Index, Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(), PullRequest: issue.PullRequest.APIFormat(),
Repository: repo.APIFormat(AccessModeNone), Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
} }
if isClosed { if isClosed {
@ -682,6 +720,19 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
apiPullRequest.Action = api.HookIssueReOpened apiPullRequest.Action = api.HookIssueReOpened
} }
err = PrepareWebhooks(repo, HookEventPullRequest, apiPullRequest) err = PrepareWebhooks(repo, HookEventPullRequest, apiPullRequest)
} else {
apiIssue := &api.IssuePayload{
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(),
}
if isClosed {
apiIssue.Action = api.HookIssueClosed
} else {
apiIssue.Action = api.HookIssueReOpened
}
err = PrepareWebhooks(repo, HookEventIssues, apiIssue)
} }
if err != nil { if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err) log.Error(4, "PrepareWebhooks [is_pull: %v, is_closed: %v]: %v", issue.IsPull, isClosed, err)
@ -715,6 +766,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
return err return err
} }
mode, _ := AccessLevel(issue.Poster.ID, issue.Repo)
if issue.IsPull { if issue.IsPull {
issue.PullRequest.Issue = issue issue.PullRequest.Issue = issue
err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
@ -726,10 +778,24 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
}, },
}, },
PullRequest: issue.PullRequest.APIFormat(), PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(AccessModeNone), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
} else {
err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{
Action: api.HookIssueEdited,
Index: issue.Index,
Changes: &api.ChangesPayload{
Title: &api.ChangesFromPayload{
From: oldTitle,
},
},
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: issue.Poster.APIFormat(),
})
} }
if err != nil { if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
} else { } else {
@ -766,6 +832,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
return fmt.Errorf("UpdateIssueCols: %v", err) return fmt.Errorf("UpdateIssueCols: %v", err)
} }
mode, _ := AccessLevel(issue.Poster.ID, issue.Repo)
if issue.IsPull { if issue.IsPull {
issue.PullRequest.Issue = issue issue.PullRequest.Issue = issue
err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{
@ -777,9 +844,22 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
}, },
}, },
PullRequest: issue.PullRequest.APIFormat(), PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(AccessModeNone), Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(), Sender: doer.APIFormat(),
}) })
} else {
err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{
Action: api.HookIssueEdited,
Index: issue.Index,
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
From: oldContent,
},
},
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
})
} }
if err != nil { if err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) log.Error(4, "PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err)
@ -790,55 +870,6 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
return nil return nil
} }
// ChangeAssignee changes the Assignee field of this issue.
func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
var oldAssigneeID = issue.AssigneeID
issue.AssigneeID = assigneeID
if err = UpdateIssueUserByAssignee(issue); err != nil {
return fmt.Errorf("UpdateIssueUserByAssignee: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err = issue.loadRepo(sess); err != nil {
return fmt.Errorf("loadRepo: %v", err)
}
if _, err = createAssigneeComment(sess, doer, issue.Repo, issue, oldAssigneeID, assigneeID); err != nil {
return fmt.Errorf("createAssigneeComment: %v", err)
}
issue.Assignee, err = GetUserByID(issue.AssigneeID)
if err != nil && !IsErrUserNotExist(err) {
log.Error(4, "GetUserByID [assignee_id: %v]: %v", issue.AssigneeID, err)
return nil
}
// Error not nil here means user does not exist, which is remove assignee.
isRemoveAssignee := err != nil
if issue.IsPull {
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(AccessModeNone),
Sender: doer.APIFormat(),
}
if isRemoveAssignee {
apiPullRequest.Action = api.HookIssueUnassigned
} else {
apiPullRequest.Action = api.HookIssueAssigned
}
if err := PrepareWebhooks(issue.Repo, HookEventPullRequest, apiPullRequest); err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, isRemoveAssignee, err)
return nil
}
}
go HookQueue.Add(issue.RepoID)
return nil
}
// GetTasks returns the amount of tasks in the issues content // GetTasks returns the amount of tasks in the issues content
func (issue *Issue) GetTasks() int { func (issue *Issue) GetTasks() int {
return len(issueTasksPat.FindAllStringIndex(issue.Content, -1)) return len(issueTasksPat.FindAllStringIndex(issue.Content, -1))
@ -854,6 +885,7 @@ type NewIssueOptions struct {
Repo *Repository Repo *Repository
Issue *Issue Issue *Issue
LabelIDs []int64 LabelIDs []int64
AssigneeIDs []int64
Attachments []string // In UUID format. Attachments []string // In UUID format.
IsPull bool IsPull bool
} }
@ -876,14 +908,32 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
} }
} }
if assigneeID := opts.Issue.AssigneeID; assigneeID > 0 { // Keep the old assignee id thingy for compatibility reasons
valid, err := hasAccess(e, assigneeID, opts.Repo, AccessModeWrite) if opts.Issue.AssigneeID > 0 {
if err != nil { isAdded := false
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err) // Check if the user has already been passed to issue.AssigneeIDs, if not, add it
for _, aID := range opts.AssigneeIDs {
if aID == opts.Issue.AssigneeID {
isAdded = true
break
}
} }
if !valid {
opts.Issue.AssigneeID = 0 if !isAdded {
opts.Issue.Assignee = nil opts.AssigneeIDs = append(opts.AssigneeIDs, opts.Issue.AssigneeID)
}
}
// Check for and validate assignees
if len(opts.AssigneeIDs) > 0 {
for _, assigneeID := range opts.AssigneeIDs {
valid, err := hasAccess(e, assigneeID, opts.Repo, AccessModeWrite)
if err != nil {
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err)
}
if !valid {
return ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: opts.Repo.Name}
}
} }
} }
@ -898,11 +948,10 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
} }
} }
if opts.Issue.AssigneeID > 0 { // Insert the assignees
if err = opts.Issue.loadRepo(e); err != nil { for _, assigneeID := range opts.AssigneeIDs {
return err err = opts.Issue.changeAssignee(e, doer, assigneeID)
} if err != nil {
if _, err = createAssigneeComment(e, doer, opts.Issue.Repo, opts.Issue, -1, opts.Issue.AssigneeID); err != nil {
return err return err
} }
} }
@ -962,7 +1011,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
} }
// NewIssue creates new issue with labels for repository. // NewIssue creates new issue with labels for repository.
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err = sess.Begin(); err != nil { if err = sess.Begin(); err != nil {
@ -974,7 +1023,11 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
Issue: issue, Issue: issue,
LabelIDs: labelIDs, LabelIDs: labelIDs,
Attachments: uuids, Attachments: uuids,
AssigneeIDs: assigneeIDs,
}); err != nil { }); err != nil {
if IsErrUserDoesNotHaveAccessToRepo(err) {
return err
}
return fmt.Errorf("newIssue: %v", err) return fmt.Errorf("newIssue: %v", err)
} }
@ -999,6 +1052,19 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
log.Error(4, "MailParticipants: %v", err) log.Error(4, "MailParticipants: %v", err)
} }
mode, _ := AccessLevel(issue.Poster.ID, issue.Repo)
if err = PrepareWebhooks(repo, HookEventIssues, &api.IssuePayload{
Action: api.HookIssueOpened,
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: issue.Poster.APIFormat(),
}); err != nil {
log.Error(4, "PrepareWebhooks: %v", err)
} else {
go HookQueue.Add(issue.RepoID)
}
return nil return nil
} }
@ -1117,7 +1183,8 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error {
} }
if opts.AssigneeID > 0 { if opts.AssigneeID > 0 {
sess.And("issue.assignee_id=?", opts.AssigneeID) sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.AssigneeID)
} }
if opts.PosterID > 0 { if opts.PosterID > 0 {
@ -1339,7 +1406,8 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
} }
if opts.AssigneeID > 0 { if opts.AssigneeID > 0 {
sess.And("issue.assignee_id = ?", opts.AssigneeID) sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.AssigneeID)
} }
if opts.PosterID > 0 { if opts.PosterID > 0 {
@ -1405,13 +1473,15 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
} }
case FilterModeAssign: case FilterModeAssign:
stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false).
And("assignee_id = ?", opts.UserID). Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true).
And("assignee_id = ?", opts.UserID). Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
@ -1433,7 +1503,8 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed}) cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
stats.AssignCount, err = x.Where(cond). stats.AssignCount, err = x.Where(cond).
And("assignee_id = ?", opts.UserID). Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
@ -1472,8 +1543,10 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen
switch filterMode { switch filterMode {
case FilterModeAssign: case FilterModeAssign:
openCountSession.And("assignee_id = ?", uid) openCountSession.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
closedCountSession.And("assignee_id = ?", uid) And("issue_assignees.assignee_id = ?", uid)
closedCountSession.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", uid)
case FilterModeCreate: case FilterModeCreate:
openCountSession.And("poster_id = ?", uid) openCountSession.And("poster_id = ?", uid)
closedCountSession.And("poster_id = ?", uid) closedCountSession.And("poster_id = ?", uid)
@ -1498,3 +1571,30 @@ func updateIssue(e Engine, issue *Issue) error {
func UpdateIssue(issue *Issue) error { func UpdateIssue(issue *Issue) error {
return updateIssue(x, issue) return updateIssue(x, issue)
} }
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User) (err error) {
// if the deadline hasn't changed do nothing
if issue.DeadlineUnix == deadlineUnix {
return nil
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// Update the deadline
if err = updateIssueCols(sess, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil {
return err
}
// Make the comment
if _, err = createDeadlineComment(sess, doer, issue, deadlineUnix); err != nil {
return fmt.Errorf("createRemovedDueDateComment: %v", err)
}
return sess.Commit()
}

283
models/issue_assignees.go Normal file
View File

@ -0,0 +1,283 @@
// 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 (
"fmt"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/xorm"
)
// IssueAssignees saves all issue assignees
type IssueAssignees struct {
ID int64 `xorm:"pk autoincr"`
AssigneeID int64 `xorm:"INDEX"`
IssueID int64 `xorm:"INDEX"`
}
// This loads all assignees of an issue
func (issue *Issue) loadAssignees(e Engine) (err error) {
// Reset maybe preexisting assignees
issue.Assignees = []*User{}
err = e.Table("`user`").
Join("INNER", "issue_assignees", "assignee_id = `user`.id").
Where("issue_assignees.issue_id = ?", issue.ID).
Find(&issue.Assignees)
if err != nil {
return err
}
// Check if we have at least one assignee and if yes put it in as `Assignee`
if len(issue.Assignees) > 0 {
issue.Assignee = issue.Assignees[0]
}
return
}
// GetAssigneesByIssue returns everyone assigned to that issue
func GetAssigneesByIssue(issue *Issue) (assignees []*User, err error) {
err = issue.loadAssignees(x)
if err != nil {
return assignees, err
}
return issue.Assignees, nil
}
// IsUserAssignedToIssue returns true when the user is assigned to the issue
func IsUserAssignedToIssue(issue *Issue, user *User) (isAssigned bool, err error) {
isAssigned, err = x.Exist(&IssueAssignees{IssueID: issue.ID, AssigneeID: user.ID})
return
}
// DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array
func DeleteNotPassedAssignee(issue *Issue, doer *User, assignees []*User) (err error) {
var found bool
for _, assignee := range issue.Assignees {
found = false
for _, alreadyAssignee := range assignees {
if assignee.ID == alreadyAssignee.ID {
found = true
break
}
}
if !found {
// This function also does comments and hooks, which is why we call it seperatly instead of directly removing the assignees here
if err := UpdateAssignee(issue, doer, assignee.ID); err != nil {
return err
}
}
}
return nil
}
// MakeAssigneeList concats a string with all names of the assignees. Useful for logs.
func MakeAssigneeList(issue *Issue) (assigneeList string, err error) {
err = issue.loadAssignees(x)
if err != nil {
return "", err
}
for in, assignee := range issue.Assignees {
assigneeList += assignee.Name
if len(issue.Assignees) > (in + 1) {
assigneeList += ", "
}
}
return
}
// ClearAssigneeByUserID deletes all assignments of an user
func clearAssigneeByUserID(sess *xorm.Session, userID int64) (err error) {
_, err = sess.Delete(&IssueAssignees{AssigneeID: userID})
return
}
// AddAssigneeIfNotAssigned adds an assignee only if he isn't aleady assigned to the issue
func AddAssigneeIfNotAssigned(issue *Issue, doer *User, assigneeID int64) (err error) {
// Check if the user is already assigned
isAssigned, err := IsUserAssignedToIssue(issue, &User{ID: assigneeID})
if err != nil {
return err
}
if !isAssigned {
return issue.ChangeAssignee(doer, assigneeID)
}
return nil
}
// UpdateAssignee deletes or adds an assignee to an issue
func UpdateAssignee(issue *Issue, doer *User, assigneeID int64) (err error) {
return issue.ChangeAssignee(doer, assigneeID)
}
// ChangeAssignee changes the Assignee of this issue.
func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := issue.changeAssignee(sess, doer, assigneeID); err != nil {
return err
}
return sess.Commit()
}
func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID int64) (err error) {
// Update the assignee
removed, err := updateIssueAssignee(sess, issue, assigneeID)
if err != nil {
return fmt.Errorf("UpdateIssueUserByAssignee: %v", err)
}
// Repo infos
if err = issue.loadRepo(sess); err != nil {
return fmt.Errorf("loadRepo: %v", err)
}
// Comment
if _, err = createAssigneeComment(sess, doer, issue.Repo, issue, assigneeID, removed); err != nil {
return fmt.Errorf("createAssigneeComment: %v", err)
}
mode, _ := accessLevel(sess, doer.ID, issue.Repo)
if issue.IsPull {
if err = issue.loadPullRequest(sess); err != nil {
return fmt.Errorf("loadPullRequest: %v", err)
}
issue.PullRequest.Issue = issue
apiPullRequest := &api.PullRequestPayload{
Index: issue.Index,
PullRequest: issue.PullRequest.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}
if removed {
apiPullRequest.Action = api.HookIssueUnassigned
} else {
apiPullRequest.Action = api.HookIssueAssigned
}
if err := prepareWebhooks(sess, issue.Repo, HookEventPullRequest, apiPullRequest); err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
return nil
}
} else {
apiIssue := &api.IssuePayload{
Index: issue.Index,
Issue: issue.APIFormat(),
Repository: issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}
if removed {
apiIssue.Action = api.HookIssueUnassigned
} else {
apiIssue.Action = api.HookIssueAssigned
}
if err := prepareWebhooks(sess, issue.Repo, HookEventIssues, apiIssue); err != nil {
log.Error(4, "PrepareWebhooks [is_pull: %v, remove_assignee: %v]: %v", issue.IsPull, removed, err)
return nil
}
}
go HookQueue.Add(issue.RepoID)
return nil
}
// UpdateAPIAssignee is a helper function to add or delete one or multiple issue assignee(s)
// Deleting is done the Github way (quote from their api documentation):
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
func UpdateAPIAssignee(issue *Issue, oneAssignee string, multipleAssignees []string, doer *User) (err error) {
var allNewAssignees []*User
// Keep the old assignee thingy for compatibility reasons
if oneAssignee != "" {
// Prevent double adding assignees
var isDouble bool
for _, assignee := range multipleAssignees {
if assignee == oneAssignee {
isDouble = true
break
}
}
if !isDouble {
multipleAssignees = append(multipleAssignees, oneAssignee)
}
}
// Loop through all assignees to add them
for _, assigneeName := range multipleAssignees {
assignee, err := GetUserByName(assigneeName)
if err != nil {
return err
}
allNewAssignees = append(allNewAssignees, assignee)
}
// Delete all old assignees not passed
if err = DeleteNotPassedAssignee(issue, doer, allNewAssignees); err != nil {
return err
}
// Add all new assignees
// Update the assignee. The function will check if the user exists, is already
// assigned (which he shouldn't as we deleted all assignees before) and
// has access to the repo.
for _, assignee := range allNewAssignees {
// Extra method to prevent double adding (which would result in removing)
err = AddAssigneeIfNotAssigned(issue, doer, assignee.ID)
if err != nil {
return err
}
}
return
}
// MakeIDsFromAPIAssigneesToAdd returns an array with all assignee IDs
func MakeIDsFromAPIAssigneesToAdd(oneAssignee string, multipleAssignees []string) (assigneeIDs []int64, err error) {
// Keeping the old assigning method for compatibility reasons
if oneAssignee != "" {
// Prevent double adding assignees
var isDouble bool
for _, assignee := range multipleAssignees {
if assignee == oneAssignee {
isDouble = true
break
}
}
if !isDouble {
multipleAssignees = append(multipleAssignees, oneAssignee)
}
}
// Get the IDs of all assignees
assigneeIDs = GetUserIDsByNames(multipleAssignees)
return
}

View File

@ -0,0 +1,71 @@
// 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 TestUpdateAssignee(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
// Fake issue with assignees
issue, err := GetIssueByID(1)
assert.NoError(t, err)
// Assign multiple users
user2, err := GetUserByID(2)
assert.NoError(t, err)
err = UpdateAssignee(issue, &User{ID: 1}, user2.ID)
assert.NoError(t, err)
user3, err := GetUserByID(3)
assert.NoError(t, err)
err = UpdateAssignee(issue, &User{ID: 1}, user3.ID)
assert.NoError(t, err)
user1, err := GetUserByID(1) // This user is already assigned (see the definition in fixtures), so running UpdateAssignee should unassign him
assert.NoError(t, err)
err = UpdateAssignee(issue, &User{ID: 1}, user1.ID)
assert.NoError(t, err)
// Check if he got removed
isAssigned, err := IsUserAssignedToIssue(issue, user1)
assert.NoError(t, err)
assert.False(t, isAssigned)
// Check if they're all there
assignees, err := GetAssigneesByIssue(issue)
assert.NoError(t, err)
var expectedAssignees []*User
expectedAssignees = append(expectedAssignees, user2)
expectedAssignees = append(expectedAssignees, user3)
for in, assignee := range assignees {
assert.Equal(t, assignee.ID, expectedAssignees[in].ID)
}
// Check if the user is assigned
isAssigned, err = IsUserAssignedToIssue(issue, user2)
assert.NoError(t, err)
assert.True(t, isAssigned)
// This user should not be assigned
isAssigned, err = IsUserAssignedToIssue(issue, &User{ID: 4})
assert.NoError(t, err)
assert.False(t, isAssigned)
// Clean everyone
err = DeleteNotPassedAssignee(issue, user1, []*User{})
assert.NoError(t, err)
// Check they're gone
assignees, err = GetAssigneesByIssue(issue)
assert.NoError(t, err)
assert.Equal(t, 0, len(assignees))
}

View File

@ -60,6 +60,12 @@ const (
CommentTypeAddTimeManual CommentTypeAddTimeManual
// Cancel a stopwatch for time tracking // Cancel a stopwatch for time tracking
CommentTypeCancelTracking CommentTypeCancelTracking
// Added a due date
CommentTypeAddedDeadline
// Modified the due date
CommentTypeModifiedDeadline
// Removed a due date
CommentTypeRemovedDeadline
) )
// CommentTag defines comment tag type // CommentTag defines comment tag type
@ -75,23 +81,23 @@ 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"`
LabelID int64 Issue *Issue `xorm:"-"`
Label *Label `xorm:"-"` LabelID int64
OldMilestoneID int64 Label *Label `xorm:"-"`
MilestoneID int64 OldMilestoneID int64
OldMilestone *Milestone `xorm:"-"` MilestoneID int64
Milestone *Milestone `xorm:"-"` OldMilestone *Milestone `xorm:"-"`
OldAssigneeID int64 Milestone *Milestone `xorm:"-"`
AssigneeID int64 AssigneeID int64
Assignee *User `xorm:"-"` RemovedAssignee bool
OldAssignee *User `xorm:"-"` Assignee *User `xorm:"-"`
OldTitle string OldTitle string
NewTitle string NewTitle string
CommitID int64 CommitID int64
Line int64 Line int64
@ -111,6 +117,15 @@ type Comment struct {
ShowTag CommentTag `xorm:"-"` ShowTag CommentTag `xorm:"-"`
} }
// LoadIssue loads issue from database
func (c *Comment) LoadIssue() (err error) {
if c.Issue != nil {
return nil
}
c.Issue, err = GetIssueByID(c.IssueID)
return
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (c *Comment) AfterLoad(session *xorm.Session) { func (c *Comment) AfterLoad(session *xorm.Session) {
var err error var err error
@ -141,40 +156,40 @@ func (c *Comment) AfterDelete() {
// HTMLURL formats a URL-string to the issue-comment // HTMLURL formats a URL-string to the issue-comment
func (c *Comment) HTMLURL() string { func (c *Comment) HTMLURL() string {
issue, err := GetIssueByID(c.IssueID) err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
return fmt.Sprintf("%s#%s", issue.HTMLURL(), c.HashTag()) return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
} }
// IssueURL formats a URL-string to the issue // IssueURL formats a URL-string to the issue
func (c *Comment) IssueURL() string { func (c *Comment) IssueURL() string {
issue, err := GetIssueByID(c.IssueID) err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
if issue.IsPull { if c.Issue.IsPull {
return "" return ""
} }
return issue.HTMLURL() return c.Issue.HTMLURL()
} }
// PRURL formats a URL-string to the pull-request // PRURL formats a URL-string to the pull-request
func (c *Comment) PRURL() string { func (c *Comment) PRURL() string {
issue, err := GetIssueByID(c.IssueID) err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
if !issue.IsPull { if !c.Issue.IsPull {
return "" return ""
} }
return issue.HTMLURL() return c.Issue.HTMLURL()
} }
// APIFormat converts a Comment to the api.Comment format // APIFormat converts a Comment to the api.Comment format
@ -191,9 +206,14 @@ func (c *Comment) APIFormat() *api.Comment {
} }
} }
// CommentHashTag returns unique hash tag for comment id.
func CommentHashTag(id int64) string {
return fmt.Sprintf("issuecomment-%d", id)
}
// HashTag returns unique hash tag for comment. // HashTag returns unique hash tag for comment.
func (c *Comment) HashTag() string { func (c *Comment) HashTag() string {
return "issuecomment-" + com.ToStr(c.ID) return CommentHashTag(c.ID)
} }
// EventTag returns unique event hash tag for comment. // EventTag returns unique event hash tag for comment.
@ -241,18 +261,9 @@ func (c *Comment) LoadMilestone() error {
return nil return nil
} }
// LoadAssignees if comment.Type is CommentTypeAssignees, then load assignees // LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
func (c *Comment) LoadAssignees() error { func (c *Comment) LoadAssigneeUser() error {
var err error var err error
if c.OldAssigneeID > 0 {
c.OldAssignee, err = getUserByID(x, c.OldAssigneeID)
if err != nil {
if !IsErrUserNotExist(err) {
return err
}
c.OldAssignee = NewGhostUser()
}
}
if c.AssigneeID > 0 { if c.AssigneeID > 0 {
c.Assignee, err = getUserByID(x, c.AssigneeID) c.Assignee, err = getUserByID(x, c.AssigneeID)
@ -318,21 +329,21 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
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,
OldAssigneeID: opts.OldAssigneeID, 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,
} }
if _, err = e.Insert(comment); err != nil { if _, err = e.Insert(comment); err != nil {
return nil, err return nil, err
@ -418,7 +429,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
} }
// update the issue's updated_unix column // update the issue's updated_unix column
if err = updateIssueCols(e, opts.Issue); err != nil { if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil {
return nil, err return nil, err
} }
@ -474,14 +485,42 @@ func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue
}) })
} }
func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldAssigneeID, assigneeID int64) (*Comment, error) { func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, assigneeID int64, removedAssignee bool) (*Comment, error) {
return createComment(e, &CreateCommentOptions{ return createComment(e, &CreateCommentOptions{
Type: CommentTypeAssignees, Type: CommentTypeAssignees,
Doer: doer, Doer: doer,
Repo: repo, Repo: repo,
Issue: issue, Issue: issue,
OldAssigneeID: oldAssigneeID, RemovedAssignee: removedAssignee,
AssigneeID: assigneeID, AssigneeID: assigneeID,
})
}
func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix util.TimeStamp) (*Comment, error) {
var content string
var commentType CommentType
// newDeadline = 0 means deleting
if newDeadlineUnix == 0 {
commentType = CommentTypeRemovedDeadline
content = issue.DeadlineUnix.Format("2006-01-02")
} else if issue.DeadlineUnix == 0 {
// Check if the new date was added or modified
// If the actual deadline is 0 => deadline added
commentType = CommentTypeAddedDeadline
content = newDeadlineUnix.Format("2006-01-02")
} else { // Otherwise modified
commentType = CommentTypeModifiedDeadline
content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02")
}
return createComment(e, &CreateCommentOptions{
Type: commentType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
Content: content,
}) })
} }
@ -514,17 +553,17 @@ type CreateCommentOptions struct {
Issue *Issue Issue *Issue
Label *Label Label *Label
OldMilestoneID int64 OldMilestoneID int64
MilestoneID int64 MilestoneID int64
OldAssigneeID int64 AssigneeID int64
AssigneeID int64 RemovedAssignee bool
OldTitle string OldTitle string
NewTitle string NewTitle string
CommitID int64 CommitID int64
CommitSHA string CommitSHA string
LineNum int64 LineNum int64
Content string Content string
Attachments []string // UUIDs of attachments Attachments []string // UUIDs of attachments
} }
// CreateComment creates comment of issue or commit. // CreateComment creates comment of issue or commit.
@ -552,7 +591,7 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
// CreateIssueComment creates a plain issue comment. // CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) { func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
return CreateComment(&CreateCommentOptions{ comment, err := CreateComment(&CreateCommentOptions{
Type: CommentTypeComment, Type: CommentTypeComment,
Doer: doer, Doer: doer,
Repo: repo, Repo: repo,
@ -560,6 +599,23 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
Content: content, Content: content,
Attachments: attachments, Attachments: attachments,
}) })
if err != nil {
return nil, fmt.Errorf("CreateComment: %v", err)
}
mode, _ := AccessLevel(doer.ID, repo)
if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated,
Issue: issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
} else {
go HookQueue.Add(repo.ID)
}
return comment, nil
} }
// CreateRefComment creates a commit reference comment to issue. // CreateRefComment creates a commit reference comment to issue.
@ -672,17 +728,43 @@ func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
} }
// UpdateComment updates information of comment. // UpdateComment updates information of comment.
func UpdateComment(c *Comment) error { func UpdateComment(doer *User, c *Comment, oldContent string) error {
if _, err := x.ID(c.ID).AllCols().Update(c); err != nil { if _, err := x.ID(c.ID).AllCols().Update(c); err != nil {
return err return err
} else if c.Type == CommentTypeComment { } else if c.Type == CommentTypeComment {
UpdateIssueIndexer(c.IssueID) UpdateIssueIndexer(c.IssueID)
} }
if err := c.LoadIssue(); err != nil {
return err
}
if err := c.Issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, c.Issue.Repo)
if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited,
Issue: c.Issue.APIFormat(),
Comment: c.APIFormat(),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
From: oldContent,
},
},
Repository: c.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
} else {
go HookQueue.Add(c.Issue.Repo.ID)
}
return nil return nil
} }
// DeleteComment deletes the comment // DeleteComment deletes the comment
func DeleteComment(comment *Comment) error { func DeleteComment(doer *User, comment *Comment) error {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err := sess.Begin(); err != nil { if err := sess.Begin(); err != nil {
@ -709,5 +791,27 @@ func DeleteComment(comment *Comment) error {
} else if comment.Type == CommentTypeComment { } else if comment.Type == CommentTypeComment {
UpdateIssueIndexer(comment.IssueID) UpdateIssueIndexer(comment.IssueID)
} }
if err := comment.LoadIssue(); err != nil {
return err
}
if err := comment.Issue.LoadAttributes(); err != nil {
return err
}
mode, _ := AccessLevel(doer.ID, comment.Issue.Repo)
if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted,
Issue: comment.Issue.APIFormat(),
Comment: comment.APIFormat(),
Repository: comment.Issue.Repo.APIFormat(mode),
Sender: doer.APIFormat(),
}); err != nil {
log.Error(2, "PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
} else {
go HookQueue.Add(comment.Issue.Repo.ID)
}
return nil return nil
} }

View File

@ -154,38 +154,38 @@ func (issues IssueList) loadMilestones(e Engine) error {
return nil return nil
} }
func (issues IssueList) getAssigneeIDs() []int64 {
var ids = make(map[int64]struct{}, len(issues))
for _, issue := range issues {
if _, ok := ids[issue.AssigneeID]; !ok {
ids[issue.AssigneeID] = struct{}{}
}
}
return keysInt64(ids)
}
func (issues IssueList) loadAssignees(e Engine) error { func (issues IssueList) loadAssignees(e Engine) error {
assigneeIDs := issues.getAssigneeIDs() if len(issues) == 0 {
if len(assigneeIDs) == 0 {
return nil return nil
} }
assigneeMaps := make(map[int64]*User, len(assigneeIDs)) type AssigneeIssue struct {
err := e. IssueAssignee *IssueAssignees `xorm:"extends"`
In("id", assigneeIDs). Assignee *User `xorm:"extends"`
Find(&assigneeMaps) }
var assignees = make(map[int64][]*User, len(issues))
rows, err := e.Table("issue_assignees").
Join("INNER", "user", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issues.getIssueIDs()).
Rows(new(AssigneeIssue))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() {
var assigneeIssue AssigneeIssue
err = rows.Scan(&assigneeIssue)
if err != nil {
return err
}
assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
}
for _, issue := range issues { for _, issue := range issues {
if issue.AssigneeID <= 0 { issue.Assignees = assignees[issue.ID]
continue
}
var ok bool
if issue.Assignee, ok = assigneeMaps[issue.AssigneeID]; !ok {
issue.Assignee = NewGhostUser()
}
} }
return nil return nil
} }
@ -290,6 +290,50 @@ func (issues IssueList) loadComments(e Engine) (err error) {
return nil return nil
} }
func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) {
type totalTimesByIssue struct {
IssueID int64
Time int64
}
if len(issues) == 0 {
return nil
}
var trackedTimes = make(map[int64]int64, len(issues))
var ids = make([]int64, 0, len(issues))
for _, issue := range issues {
if issue.Repo.IsTimetrackerEnabled() {
ids = append(ids, issue.ID)
}
}
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
rows, err := e.Table("tracked_time").
Select("issue_id, sum(time) as time").
In("issue_id", ids).
GroupBy("issue_id").
Rows(new(totalTimesByIssue))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var totalTime totalTimesByIssue
err = rows.Scan(&totalTime)
if err != nil {
return err
}
trackedTimes[totalTime.IssueID] = totalTime.Time
}
for _, issue := range issues {
issue.TotalTrackedTime = trackedTimes[issue.ID]
}
return nil
}
// loadAttributes loads all attributes, expect for attachments and comments // loadAttributes loads all attributes, expect for attachments and comments
func (issues IssueList) loadAttributes(e Engine) (err error) { func (issues IssueList) loadAttributes(e Engine) (err error) {
if _, err = issues.loadRepositories(e); err != nil { if _, err = issues.loadRepositories(e); err != nil {
@ -316,6 +360,10 @@ func (issues IssueList) loadAttributes(e Engine) (err error) {
return return
} }
if err = issues.loadTotalTrackedTimes(e); err != nil {
return
}
return nil return nil
} }

View File

@ -7,6 +7,8 @@ package models
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -29,7 +31,7 @@ func TestIssueList_LoadRepositories(t *testing.T) {
func TestIssueList_LoadAttributes(t *testing.T) { func TestIssueList_LoadAttributes(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
setting.Service.EnableTimetracking = true
issueList := IssueList{ issueList := IssueList{
AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue), AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue),
AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue), AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),
@ -61,5 +63,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
for _, comment := range issue.Comments { for _, comment := range issue.Comments {
assert.EqualValues(t, issue.ID, comment.IssueID) assert.EqualValues(t, issue.ID, comment.IssueID)
} }
if issue.ID == int64(1) {
assert.Equal(t, int64(400), issue.TotalTrackedTime)
} else if issue.ID == int64(2) {
assert.Equal(t, int64(3662), issue.TotalTrackedTime)
}
} }
} }

View File

@ -46,9 +46,16 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, content
participants = append(participants, issue.Poster) participants = append(participants, issue.Poster)
} }
// Assignee must receive any communications // Assignees must receive any communications
if issue.Assignee != nil && issue.AssigneeID > 0 && issue.AssigneeID != doer.ID { assignees, err := GetAssigneesByIssue(issue)
participants = append(participants, issue.Assignee) if err != nil {
return err
}
for _, assignee := range assignees {
if assignee.ID != doer.ID {
participants = append(participants, assignee)
}
} }
tos := make([]string, 0, len(watchers)) // List of email addresses. tos := make([]string, 0, len(watchers)) // List of email addresses.

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