Compare commits

..

11 Commits

Author SHA1 Message Date
Thomas Boerger
155fb93b9b Added 1.0.1 to changelog (#595)
(cherry picked from commit 0ac40cc694)
2017-01-05 21:13:52 +08:00
Thomas Boerger
3a8c792303 Clone tags within drone for proper version generation (#591) (#593)
(cherry picked from commit 2d17d6bc16)
2017-01-05 19:34:59 +08:00
Lunny Xiao
807c64800c fix config session missing data row & resolved #517 (#578) 2017-01-05 08:49:49 +08:00
Lunny Xiao
6ddb2dcd57 change the default action when deleting a release to not delete tag (#579) 2017-01-05 08:49:43 +08:00
Lunny Xiao
729ab80065 resolved #517: fix admin ui data row missing (#580) 2017-01-05 08:49:32 +08:00
Lunny Xiao
a5ea9b4f30 fix installation page ssh domain unavilable (#506) 2017-01-04 14:40:44 +01:00
Lunny Xiao
8f08ccdb9f bug fixed for fork repos (#563) 2017-01-03 12:40:58 +08:00
Lunny Xiao
10d73d38e0 resolved #485: when migrate empty wiki repo, then ignore (#544) 2016-12-31 18:36:41 +08:00
Lunny Xiao
fbb424c61d fix 500 when delete orgnization and resolved #486 (#507) 2016-12-28 08:53:17 +08:00
Bwko
e8bac94d1f At the locales replaced 6 with MIN_PASSWORD_LENGTH (#501) 2016-12-27 18:44:49 +08:00
Matthias Loibl
99c6556ff3 Don't trim trailing whitespaces for markdown (#467) 2016-12-24 09:35:37 +08:00
1650 changed files with 16722 additions and 342884 deletions

View File

@@ -11,7 +11,8 @@ pipeline:
image: webhippie/golang:edge
pull: true
environment:
TAGS: bindata sqlite
CGO_ENABLED: 1
TAGS: sqlite bindata
GOPATH: /srv/app
commands:
- apk -U add openssh-client
@@ -28,40 +29,44 @@ pipeline:
image: webhippie/golang:edge
pull: true
environment:
TAGS: bindata
CGO_ENABLED: 1
TAGS: sqlite bindata
GOPATH: /srv/app
commands:
- make test-mysql
when:
event: [ push, tag, pull_request ]
event: [ push ]
test-pgsql:
image: webhippie/golang:edge
pull: true
environment:
TAGS: bindata
CGO_ENABLED: 1
TAGS: sqlite bindata
GOPATH: /srv/app
commands:
- make test-pgsql
when:
event: [ push, tag, pull_request ]
event: [ push ]
static:
updater:
image: karalabe/xgo-latest:latest
pull: true
environment:
TAGS: bindata sqlite
CGO_ENABLED: 1
TAGS: sqlite bindata
GOPATH: /srv/app
commands:
- make release
when:
event: [ push, tag, pull_request ]
event: [ push, tag ]
branch: [ master, release/*, refs/tags/* ]
# coverage:
# image: plugins/coverage
# server: https://coverage.gitea.io
# when:
# event: [ push, tag, pull_request ]
coverage:
image: plugins/coverage
server: https://coverage.gitea.io
when:
event: [ push, tag, pull_request ]
docker:
image: plugins/docker
@@ -135,11 +140,11 @@ services:
- MYSQL_DATABASE=test
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
when:
event: [ push, tag, pull_request ]
event: [ push ]
pgsql:
image: postgres:9.5
environment:
- POSTGRES_DB=test
when:
event: [ push, tag, pull_request ]
event: [ push ]

View File

@@ -1 +1 @@
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QKICAgICAgLSBtYWtlIGJ1aWxkCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtbXlzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHRlc3QtbXlzcWwKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1wZ3NxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1wZ3NxbAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBzdGF0aWM6CiAgICBpbWFnZToga2FyYWxhYmUveGdvLWxhdGVzdDpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhIHNxbGl0ZQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHJlbGVhc2UKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgIyBjb3ZlcmFnZToKICAjICAgaW1hZ2U6IHBsdWdpbnMvY292ZXJhZ2UKICAjICAgc2VydmVyOiBodHRwczovL2NvdmVyYWdlLmdpdGVhLmlvCiAgIyAgIHdoZW46CiAgIyAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICcke0RST05FX1RBRyMjdn0nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJyR7RFJPTkVfQlJBTkNIIyNyZWxlYXNlL3Z9JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIHJlbGVhc2UvKiBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJ2xhdGVzdCcgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyBtYXN0ZXIgXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvJHtEUk9ORV9UQUcjI3Z9CiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvJHtEUk9ORV9CUkFOQ0gjI3JlbGVhc2Uvdn0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgcmVsZWFzZS8qIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhL21hc3RlcgogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyBtYXN0ZXIgXQoKICBnaXRodWI6CiAgICBpbWFnZTogcGx1Z2lucy9naXRodWItcmVsZWFzZQogICAgZmlsZXM6CiAgICAgIC0gZGlzdC9yZWxlYXNlLyoKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgoKc2VydmljZXM6CiAgbXlzcWw6CiAgICBpbWFnZTogbXlzcWw6NS43CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBNWVNRTF9EQVRBQkFTRT10ZXN0CiAgICAgIC0gTVlTUUxfQUxMT1dfRU1QVFlfUEFTU1dPUkQ9eWVzCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHBnc3FsOgogICAgaW1hZ2U6IHBvc3RncmVzOjkuNQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfREI9dGVzdAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQo.uf02h57dWfCrxG3rcNcYlZPQP2XsFhKvcF2geGTpG50
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQ0dPX0VOQUJMRUQ6IDEKICAgICAgVEFHUzogc3FsaXRlIGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QKICAgICAgLSBtYWtlIGJ1aWxkCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtbXlzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQ0dPX0VOQUJMRUQ6IDEKICAgICAgVEFHUzogc3FsaXRlIGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSB0ZXN0LW15c3FsCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KCiAgdGVzdC1wZ3NxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBDR09fRU5BQkxFRDogMQogICAgICBUQUdTOiBzcWxpdGUgYmluZGF0YQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHRlc3QtcGdzcWwKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQoKICB1cGRhdGVyOgogICAgaW1hZ2U6IGthcmFsYWJlL3hnby1sYXRlc3Q6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQ0dPX0VOQUJMRUQ6IDEKICAgICAgVEFHUzogc3FsaXRlIGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSByZWxlYXNlCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcgXQogICAgICBicmFuY2g6IFsgbWFzdGVyLCByZWxlYXNlLyosIHJlZnMvdGFncy8qIF0KCiAgY292ZXJhZ2U6CiAgICBpbWFnZTogcGx1Z2lucy9jb3ZlcmFnZQogICAgc2VydmVyOiBodHRwczovL2NvdmVyYWdlLmdpdGVhLmlvCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJyR7RFJPTkVfVEFHIyN2fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9CUkFOQ0gjI3JlbGVhc2Uvdn0nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgcmVsZWFzZS8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnbGF0ZXN0JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX1RBRyMjdn0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvbWFzdGVyCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIGdpdGh1YjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdGh1Yi1yZWxlYXNlCiAgICBmaWxlczoKICAgICAgLSBkaXN0L3JlbGVhc2UvKgogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZ2l0dGVyOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0dGVyCgpzZXJ2aWNlczoKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQoKICBwZ3NxbDoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0RCPXRlc3QKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQo.NGE3UiNBappXiPimJXv1DzgjT3k2hofGPsCPhw7KsSM

View File

@@ -25,3 +25,6 @@ indent_size = 4
[Makefile]
indent_style = tab
[*.md]
trim_trailing_whitespace = false

6
.gitattributes vendored
View File

@@ -1,6 +0,0 @@
conf/* linguist-vendored
docker/* linguist-vendored
options/* linguist-vendored
public/* linguist-vendored
scripts/* linguist-vendored
templates/* linguist-vendored

View File

@@ -1,9 +1,9 @@
1. Please speak English, this is the language all of us can speak and write.
2. Please ask questions or configuration/deploy problems on our Gitter channel: https://gitter.im/go-gitea/gitea
3. Please take a moment to search check that your issue doesn't already exist.
4. Please give all relevant information below for bug reports, because incomplete details will be handled as an invalid report.
1. Please speak English, this is the language everybody of us can speak and write.
2. Please ask questions or config/deploy problems on our Gitter channel: https://gitter.im/go-gitea/gitea
3. Please take a moment to search that an issue doesn't already exist.
4. Please give all relevant information below for bug reports, incomplete details will be handled as an invalid report.
**You MUST delete the content above including this line before posting, otherwise your issue will be invalid.**
**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**
- Gitea version (or commit ref):
- Git version:
@@ -21,8 +21,3 @@
## Description
...
## Screenshots
**If this issue involves the Web Interface, please include a screenshot**

1
.gitignore vendored
View File

@@ -41,6 +41,5 @@ coverage.out
/dist
/custom
/data
/indexers
/log
/public/img/avatar

View File

@@ -1,140 +1,5 @@
# Changelog
## [1.1.3](https://github.com/go-gitea/gitea/releases/tag/v1.1.3) - 2017-08-03
* BUGFIXES
* Fix PR template error (#2008)
* Fix markdown rendering (fix #1530) (#2043)
* Fix missing less sources for oauth (backport #1288) (#2135)
* Don't ignore gravatar error (#2138)
* Fix diff of renamed and modified file (#2136)
* Fix fast-forward PR bug (#2137)
* Fix some security bugs
## [1.1.2](https://github.com/go-gitea/gitea/releases/tag/v1.1.2) - 2017-06-13
* BUGFIXES
* Enforce netgo build tag while cross-compilation (Backport of #1690) (#1731)
* fix update avatar
* fix delete user failed on sqlite (#1321)
* fix bug not to trim space of login username (#1806)
* Backport bugfixes #1220 and #1393 to v1.1 (#1758)
## [1.1.1](https://github.com/go-gitea/gitea/releases/tag/v1.1.1) - 2017-05-04
* BUGFIXES
* Markdown Sanitation Fix [#1646](https://github.com/go-gitea/gitea/pull/1646)
* Fix broken hooks [#1376](https://github.com/go-gitea/gitea/pull/1376)
* Fix migration issue [#1375](https://github.com/go-gitea/gitea/pull/1375)
* Fix Wiki Issues [#1338](https://github.com/go-gitea/gitea/pull/1338)
* Forgotten migration for wiki githooks [#1237](https://github.com/go-gitea/gitea/pull/1237)
* Commit messages can contain pipes [#1218](https://github.com/go-gitea/gitea/pull/1218)
* Verify external tracker URLs [#1236](https://github.com/go-gitea/gitea/pull/1236)
* Allow upgrade after downgrade [#1197](https://github.com/go-gitea/gitea/pull/1197)
* 500 on delete repo with issue [#1195](https://github.com/go-gitea/gitea/pull/1195)
* INI compat with CrowdIn [#1192](https://github.com/go-gitea/gitea/pull/1192)
## [1.1.0](https://github.com/go-gitea/gitea/releases/tag/v1.1.0) - 2017-03-09
* BREAKING
* The SSH keys can potentially break, make sure to regenerate the authorized keys
* FEATURE
* Git LFSv2 support [#122](https://github.com/go-gitea/gitea/pull/122)
* API endpoints for repo watching [#191](https://github.com/go-gitea/gitea/pull/191)
* Search within private repos [#222](https://github.com/go-gitea/gitea/pull/222)
* Hide user email address on explore page [#336](https://github.com/go-gitea/gitea/pull/336)
* Protected branch system [#339](https://github.com/go-gitea/gitea/pull/339)
* Sendmail for mail delivery [#355](https://github.com/go-gitea/gitea/pull/355)
* API endpoints for org webhooks [#372](https://github.com/go-gitea/gitea/pull/372)
* Enabled MSSQL support [#383](https://github.com/go-gitea/gitea/pull/383)
* API endpoints for org teams [#370](https://github.com/go-gitea/gitea/pull/370)
* API endpoints for collaborators [#375](https://github.com/go-gitea/gitea/pull/375)
* Graceful server restart [#416](https://github.com/go-gitea/gitea/pull/416)
* Commitgraph / timeline on commits page [#428](https://github.com/go-gitea/gitea/pull/428)
* API endpoints for repo forks [#509](https://github.com/go-gitea/gitea/pull/509)
* API endpoints for releases [#510](https://github.com/go-gitea/gitea/pull/510)
* Folder jumping [#511](https://github.com/go-gitea/gitea/pull/511)
* Stars tab on profile page [#519](https://github.com/go-gitea/gitea/pull/519)
* Notification system [#523](https://github.com/go-gitea/gitea/pull/523)
* Push and pull through reverse proxy basic auth [#524](https://github.com/go-gitea/gitea/pull/524)
* Search for issues and pull requests [#530](https://github.com/go-gitea/gitea/pull/530)
* API endpoint for stargazers [#597](https://github.com/go-gitea/gitea/pull/597)
* API endpoints for subscribers [#598](https://github.com/go-gitea/gitea/pull/598)
* PID file support [#610](https://github.com/go-gitea/gitea/pull/610)
* Two factor authentication (2FA) [#630](https://github.com/go-gitea/gitea/pull/630)
* API endpoints for org users [#645](https://github.com/go-gitea/gitea/pull/645)
* Release attachments [#673](https://github.com/go-gitea/gitea/pull/673)
* OAuth2 consumer [#679](https://github.com/go-gitea/gitea/pull/679)
* Add ability to fork your own repos [#761](https://github.com/go-gitea/gitea/pull/761)
* Search repository on dashboard [#773](https://github.com/go-gitea/gitea/pull/773)
* Search bar on user profile [#787](https://github.com/go-gitea/gitea/pull/787)
* Track label changes on issue view [#788](https://github.com/go-gitea/gitea/pull/788)
* Allow using custom time format [#798](https://github.com/go-gitea/gitea/pull/798)
* Redirects for renamed repos [#807](https://github.com/go-gitea/gitea/pull/807)
* Track assignee changes on issue view [#808](https://github.com/go-gitea/gitea/pull/808)
* Track title changes on issue view [#841](https://github.com/go-gitea/gitea/pull/841)
* Archive cleanup action [#885](https://github.com/go-gitea/gitea/pull/885)
* Basic Open Graph support [#901](https://github.com/go-gitea/gitea/pull/901)
* Take back control of Git hooks [#1006](https://github.com/go-gitea/gitea/pull/1006)
* API endpoints for user repos [#1059](https://github.com/go-gitea/gitea/pull/1059)
* BUGFIXES
* Fixed counting issues for issue filters [#413](https://github.com/go-gitea/gitea/pull/413)
* Added back default settings for SSH [#500](https://github.com/go-gitea/gitea/pull/500)
* Fixed repo permissions [#513](https://github.com/go-gitea/gitea/pull/513)
* Issues cannot be created with labels [#622](https://github.com/go-gitea/gitea/pull/622)
* Add a reserved wiki paths check to the wiki [#720](https://github.com/go-gitea/gitea/pull/720)
* Update website binding MaxSize to 255 [#722](https://github.com/go-gitea/gitea/pull/722)
* User can see the private activity on public history [#818](https://github.com/go-gitea/gitea/pull/818)
* Wrong pages number which includes private repositories [#844](https://github.com/go-gitea/gitea/pull/844)
* Trim whitespaces for search keyword [#893](https://github.com/go-gitea/gitea/pull/893)
* Don't rewrite non-gitea public keys [#906](https://github.com/go-gitea/gitea/pull/906)
* Use fingerprint to check instead content for public key [#911](https://github.com/go-gitea/gitea/pull/911)
* Fix random avatars [#1147](https://github.com/go-gitea/gitea/pull/1147)
* ENHANCEMENT
* Refactored process manager [#75](https://github.com/go-gitea/gitea/pull/75)
* Restrict rights to create new orgs [#193](https://github.com/go-gitea/gitea/pull/193)
* Added label and milestone sorting [#199](https://github.com/go-gitea/gitea/pull/199)
* Make minimum password length configurable [#223](https://github.com/go-gitea/gitea/pull/223)
* Speedup conflict checking on pull requests [#276](https://github.com/go-gitea/gitea/pull/276)
* Added button to delete merged pull request branches [#441](https://github.com/go-gitea/gitea/pull/441)
* Improved issue references within markdown [#471](https://github.com/go-gitea/gitea/pull/471)
* Dutch translation for the landingpage [#487](https://github.com/go-gitea/gitea/pull/487)
* Added Gogs migration script [#532](https://github.com/go-gitea/gitea/pull/532)
* Support a .gitea folder for issue templates [#582](https://github.com/go-gitea/gitea/pull/582)
* Enhanced diff-view coloring [#584](https://github.com/go-gitea/gitea/pull/584)
* Added ETag header to avatars [#721](https://github.com/go-gitea/gitea/pull/721)
* Added option to config to disable local path imports [#724](https://github.com/go-gitea/gitea/pull/724)
* Allow custom public files [#782](https://github.com/go-gitea/gitea/pull/782)
* Added pprof endpoint for debugging [#801](https://github.com/go-gitea/gitea/pull/801)
* Added X-GitHub-* headers [#809](https://github.com/go-gitea/gitea/pull/809)
* Fill SSH key title automatically [#863](https://github.com/go-gitea/gitea/pull/863)
* Display Git version on admin panel [#921](https://github.com/go-gitea/gitea/pull/921)
* Expose URL field on issue API [#982](https://github.com/go-gitea/gitea/pull/982)
* Statically compile the binaries [#985](https://github.com/go-gitea/gitea/pull/985)
* Embed build tags into version string [#1051](https://github.com/go-gitea/gitea/pull/1051)
* Gitignore support for FSharp and Clojure [#1072](https://github.com/go-gitea/gitea/pull/1072)
* Custom templates for static builds [#1087](https://github.com/go-gitea/gitea/pull/1087)
* Add ProxyFromEnvironment if none set [#1096](https://github.com/go-gitea/gitea/pull/1096)
* MISC
* Replaced remaining Gogs references
* Added more tests on various packages
* Use Crowdin for translations again
* Resolved some XSS attack vectors
* Optimized and reduced number of database queries
## [1.0.2](https://github.com/go-gitea/gitea/releases/tag/v1.0.2) - 2017-02-21
* BUGFIXES
* Fixed issue counter [#882](https://github.com/go-gitea/gitea/pull/882)
* Fixed XSS vulnerability on wiki page [#955](https://github.com/go-gitea/gitea/pull/955)
* Add data dir without session to dump [#587](https://github.com/go-gitea/gitea/pull/587)
* Fixed wiki page renaming [#958](https://github.com/go-gitea/gitea/pull/958)
* Drop default console logger if not required [#960](https://github.com/go-gitea/gitea/pull/960)
* Fixed docker docs link on install page [#972](https://github.com/go-gitea/gitea/pull/972)
* Handle SetModel errors [#957](https://github.com/go-gitea/gitea/pull/957)
* Fixed XSS vulnerability on milestones [#977](https://github.com/go-gitea/gitea/pull/977)
* Fixed XSS vulnerability on alerts [#981](https://github.com/go-gitea/gitea/pull/981)
## [1.0.1](https://github.com/go-gitea/gitea/releases/tag/v1.0.1) - 2017-01-05
* BUGFIXES

View File

@@ -2,7 +2,7 @@
## Introduction
This document explains how to contribute changes to the Gitea project. It assumes you have followed the [installation instructions](https://docs.gitea.io/en-us/). Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
This document explains how to contribute changes to the Gitea project. It assumes you have followed the [installation instructions](https://github.com/go-gitea/docs/tree/master/en-US/installation). Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
## Bug reports
@@ -10,7 +10,7 @@ Please search the issues on the issue tracker with a variety of keywords to ensu
If unique, [open an issue](https://github.com/go-gitea/gitea/issues/new) and answer the questions so we can understand and reproduce the problematic behavior.
To show us that the issue you are having is in Gitea itself, please write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we can fix the issue. Check out [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
The burden is on you to convince us that it is actually a bug in Gitea. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you. Check out [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help.
@@ -24,44 +24,17 @@ This process gives everyone a chance to validate the design, helps prevent dupli
## Testing redux
Before sending code out for review, run all the tests for the whole tree to make sure the changes don't break other usage and keep the compatibility on upgrade. To make sure you are running the test suite exactly like we do, you should install the CLI for [Drone CI](https://github.com/drone/drone), as we are using the server for continous testing, following [these instructions](http://readme.drone.io/usage/getting-started-cli). After that you can simply call `drone exec` within your working directory and it will try to run the test suite locally.
## Vendoring
We keep a cached copy of dependencies within the `vendor/` directory, managing updates via [govendor](http://github.com/kardianos/govendor).
Pull requests should only include `vendor/` updates if they are part of the same change, be it a bugfix or a feature addition.
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 an existing upstream commit.
Before sending code out for review, run all the tests for the whole tree to make sure the changes don't break other usage and keep the compatibility on upgrade. To make sure you are running the test suite exactly like we do you should install the CLI for [Drone CI](https://github.com/drone/drone), as we are using the server for continous testing, following [these instructions](http://readme.drone.io/0.5/install/cli/). After that you can simply call `drone exec` within your working directory and it will try to run the test suite locally.
## Code review
Changes to Gitea must be reviewed before they are accepted, no matter who makes the change even if it is an owner or a maintainer. We use GitHub's 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.
Please try to make your pull request easy to review for us. Please read the "[How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md)" guide, it has lots of useful tips for any project you may want to contribute. Some of the key points:
Please try to make your pull request easy to review for us. Please read the "[How to get faster PR reviews](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/faster_reviews.md)" guide, it has lots of useful tips for any project you may want to contribute. Some of the key points:
* Make small pull requests. The smaller, the faster to review and the more likely it will be merged soon.
* Don't make changes unrelated to your PR. Maybe there are typos on some comments, maybe refactoring would be welcome on a function... but if that is not related to your PR, please make *another* PR for that.
* Split big pull requests into multiple small ones. An incremental change will be faster to review than a huge PR.
## Styleguide
For imports you should use the following format (_without_ the comments)
```go
import (
// stdlib
"encoding/json"
"fmt"
// local packages
"code.gitea.io/gitea/models"
"code.gitea.io/sdk/gitea"
// external packages
"github.com/foo/bar"
"gopkg.io/baz.v1"
)
```
* Split big pull requests in multiple small ones. An incremental change will be faster to review than a huge PR.
## Sign your work
@@ -71,26 +44,20 @@ The sign-off is a simple line at the end of the explanation for the patch. Your
Signed-off-by: Joe Smith <joe.smith@email.com>
```
Please use your real name, we really dislike pseudonyms or anonymous contributions. We are in the open-source world without secrets. If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`.
## Release Cycle
We adopted a release schedule to streamline the process of working on, finishing, and issuing releases. The overall goal is to make a major release every two months, which breaks down into one month of general development followed by one month of testing and polishing known as the release freeze. A release is maintained by issuing minor releases to only correct critical problems such as crashes or security issues. All the feature pull requests should be merged in the first month of one release period.
The current release cycle is aligned to start on December 25 to February 24, next is February 25 to April 24, and etc. On this cycle, we also maybe publish the previous release minor version. For example, the current release version is v1.1, but we maybe also publish v1.0.2. When we publish v1.2, then we will stop publish v1.0.3.
Please use your real name, we really dislike pseudonyms or anonymous contributions. We are in the opensource world without secrets. If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`.
## Maintainers
To make sure every PR is checked, we have [team maintainers](https://github.com/orgs/go-gitea/teams/maintainers). Every PR **MUST** be reviewed by at least two maintainers (or owners) before it can get merged. A maintainer should be a contributor of Gitea (or Gogs) and contributed at least 4 accepted PRs. A contributor should apply as a maintainer in the [Gitter develop channel](https://gitter.im/go-gitea/develop). The owners or the team maintainers may invite the contributor. A maintainer should spend some time on code reviews. If a maintainer has no time to do that, they should apply to leave the maintainers team and we will give them the honor of being a member of the [advisors team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if an advisor has time to code review, we will gladly welcome them back to the maintainers team. If a maintainer is inactive for more than 3 months and forgets to leave the maintainers team, the owners may move him or her from the maintainers team to the advisors team.
To make sure every PR is checked, we got team maintainers. Every PR **MUST** be reviewed by at least two maintainers (or owners) before it can get merged. A maintainer should be a contributor of Gitea (or Gogs) and contributed at least 4 accepted PRs. A contributor should apply as a maintainer in the [Gitter develop channel](https://gitter.im/go-gitea/develop). The owners or the team maintainers may invite the contributor. A maintainer should spend some time on code reviews. If a maintainer has no time to do that, they should apply to leave the maintainers team and we will give them the honor of being a member of the advisors team. Of course, if an advisor has time to code review, we will gladly welcome them back to maintainers team. If someone has no time to code review and forgets to leave the maintainers team, the owners have the power to move him from maintainers team to advisors team.
## Owners
Since Gitea is a pure community organization without any company support, to keep the development healthy we will elect three owners every year. All contributors may vote to elect up to three candidates, one of which will be the main owner, and the other two the assistant owners. When the new owners have been elected, the old owners will give up ownership to the newly elected owners. If an owner is unable to do so, the other owners will assist in ceding ownership to the newly elected owners.
Since Gitea is a pure community organization without any company support, to keep the development healthy we will elect the owners every year. Every time we will elect three owners. All the contributors may vote up to three people, one of which is the main owner, and the others are assistant owners. When the new owners have been elected, the old owners MUST move the power to the new ones. If an owner don't obey these rules, the others are allowed to revoke his owner status.
After the election, the new owners should proactively agree with our [CONTRIBUTING](CONTRIBUTING.md) requirements on the [Gitter main channel](https://gitter.im/go-gitea/gitea). Below are the words to speak:
After the election, the new owners should say they agree with these rules on the [CONTRIBUTING](CONTRIBUTING.md) on the [Gitter main channel](https://gitter.im/go-gitea/gitea). Below are the words to speak:
```
I'm honored to having been elected an owner of Gitea, I agree with [CONTRIBUTING](CONTRIBUTING.md). I will spend part of my time on Gitea and lead the development of Gitea.
I'm glad to be an owner of Gitea, I agree with [CONTRIBUTING](CONTRIBUTING.md). I will spend part of my time on Gitea and lead the development of Gitea.
```
To honor the past owners, here's the history of the owners and the time they served:
@@ -111,7 +78,7 @@ Since the `master` branch is a tip version, if you wish to use Gitea in producti
Code that you contribute should use the standard copyright header:
```
// Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2016 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.
```

View File

@@ -26,8 +26,7 @@ RUN apk update && \
-s /bin/bash \
-u 1000 \
-G git \
git && \
echo "git:$(date +%s | sha256sum | base64 | head -c 32)" | chpasswd
git
ENV USER git
ENV GITEA_CUSTOM /data/gitea
@@ -39,4 +38,7 @@ ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/bin/s6-svscan", "/etc/s6"]
COPY docker /
COPY public /app/gitea/public
COPY templates /app/gitea/templates
COPY gitea /app/gitea/gitea

View File

@@ -1,7 +1,5 @@
Alexey Makhov <amakhov@avito.ru> (@makhov)
Andrey Nering <andrey.nering@gmail.com> (@andreynering)
Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy)
Ethan Koenig <ethantkoenig@gmail.com> (@ethantkoenig)
Kees de Vries <bouwko@gmail.com> (@Bwko)
Kim Carlbäcker <kim.carlbacker@gmail.com> (@bkcsoft)
LefsFlare <nobody@nobody.tld> (@LefsFlarey)

View File

@@ -1,19 +1,15 @@
DIST := dist
EXECUTABLE := gitea
IMPORT := code.gitea.io/gitea
ifeq ($(OS), Windows_NT)
EXECUTABLE := gitea.exe
else
EXECUTABLE := gitea
endif
BINDATA := modules/{options,public,templates}/bindata.go
STYLESHEETS := $(wildcard public/less/index.less public/less/_*.less)
JAVASCRIPTS :=
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
LDFLAGS += -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')"
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell go list ./... | grep -v /vendor/))
TARGETS ?= linux/*,darwin/*,windows/*
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
SOURCES ?= $(shell find . -name "*.go" -type f)
TAGS ?=
@@ -38,7 +34,7 @@ clean:
.PHONY: fmt
fmt:
find . -name "*.go" -type f -not -path "./vendor/*" | xargs gofmt -s -w
go fmt $(PACKAGES)
.PHONY: vet
vet:
@@ -46,30 +42,25 @@ vet:
.PHONY: generate
generate:
@hash go-bindata > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
@which go-bindata > /dev/null; if [ $$? -ne 0 ]; then \
go get -u github.com/jteeuwen/go-bindata/...; \
fi
go generate $(PACKAGES)
.PHONY: errcheck
errcheck:
@hash errcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
@which errcheck > /dev/null; if [ $$? -ne 0 ]; then \
go get -u github.com/kisielk/errcheck; \
fi
errcheck $(PACKAGES)
.PHONY: lint
lint:
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
@which golint > /dev/null; if [ $$? -ne 0 ]; then \
go get -u github.com/golang/lint/golint; \
fi
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
.PHONY: integrations
integrations: TAGS=bindata sqlite
integrations: build
go test code.gitea.io/gitea/integrations
.PHONY: test
test:
for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;
@@ -93,46 +84,26 @@ install: $(wildcard *.go)
build: $(EXECUTABLE)
$(EXECUTABLE): $(SOURCES)
go build -i -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
go build -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: docker
docker:
docker run -ti --rm -v $(CURDIR):/srv/app/src/code.gitea.io/gitea -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" webhippie/golang:edge make clean generate build
docker run -ti --rm -v $(CURDIR):/srv/app/src/code.gitea.io/gitea -w /srv/app/src/code.gitea.io/gitea -e TAGS="$(TAGS)" webhippie/golang:edge make clean generate build
docker build -t gitea/gitea:latest .
.PHONY: release
release: release-dirs release-windows release-linux release-darwin release-copy release-check
release: release-dirs release-build release-copy release-check
.PHONY: release-dirs
release-dirs:
mkdir -p $(DIST)/binaries $(DIST)/release
.PHONY: release-windows
release-windows:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
.PHONY: release-build
release-build:
@which xgo > /dev/null; if [ $$? -ne 0 ]; then \
go get -u github.com/karalabe/xgo; \
fi
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
mv /build/* $(DIST)/binaries
endif
.PHONY: release-linux
release-linux:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/karalabe/xgo; \
fi
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out gitea-$(VERSION) .
ifeq ($(CI),drone)
mv /build/* $(DIST)/binaries
endif
.PHONY: release-darwin
release-darwin:
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/karalabe/xgo; \
fi
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
xgo -dest $(DIST)/binaries -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets '$(TARGETS)' -out $(EXECUTABLE)-$(VERSION) $(IMPORT)
ifeq ($(CI),drone)
mv /build/* $(DIST)/binaries
endif

View File

@@ -2,13 +2,13 @@
# Gitea - Git with a cup of tea
[![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea)
[![Build Status](http://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](http://drone.gitea.io/go-gitea/gitea)
[![Join the chat at https://gitter.im/go-gitea/gitea](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-gitea/gitea?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![](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)](http://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
[![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)
[![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)
[![Release](http://github-release-version.herokuapp.com/github/go-gitea/gitea/release.svg?style=flat)](https://github.com/go-gitea/gitea/releases/latest)
||||
|:-------------:|:-------:|:-------:|
@@ -19,7 +19,7 @@
## Purpose
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. Using Go, this can be done with an independent binary distribution across **all platforms** which Go supports, including Linux, macOS, and Windows on x86, amd64, ARM and PowerPC architectures. Want to try it before doing anything else? Do it [with the online demo](https://try.gitea.io/)! This project has been [forked](https://blog.gitea.io/2016/12/welcome-to-gitea/) from [Gogs](https://gogs.io).
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. With Go, this can be done with an independent binary distribution across **all platforms** that Go supports, including Linux, macOS, and Windows on x86, amd64, ARM and PowerPC architectures. Want to try it before doing anything else? Do it [online](https://try.gitea.io/)!
## Notes
@@ -29,7 +29,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
## Docs
For more information and instructions about how to install Gitea please look at our [documentation](https://docs.gitea.io/en-us/). If you cannot find some specific information, then head over to our [Gitter](https://gitter.im/go-gitea/gitea) channel to chat with us.
For further information or instructions how to install Gitea please take a look at our [documentation](https://docs.gitea.io/en-us/), if you can not find some specific information just head over to our [Gitter](https://gitter.im/go-gitea/gitea) channel to have a chat with us.
## Contributing
@@ -43,4 +43,4 @@ Fork -> Patch -> Push -> Pull Request
## License
This project is licensed under the MIT License. See the [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) file for the full license text.
This project is under the MIT License. See the [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) file for the full license text.

View File

@@ -2,13 +2,13 @@
# Gitea - Git with a cup of tea
[![Build Status](https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](https://drone.gitea.io/go-gitea/gitea)
[![Build Status](http://drone.gitea.io/api/badges/go-gitea/gitea/status.svg)](http://drone.gitea.io/go-gitea/gitea)
[![Join the chat at https://gitter.im/go-gitea/gitea](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-gitea/gitea?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![](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)](http://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
[![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)
[![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)
[![Release](http://github-release-version.herokuapp.com/github/go-gitea/gitea/release.svg?style=flat)](https://github.com/go-gitea/gitea/releases/latest)
||||
|:-------------:|:-------:|:-------:|

View File

@@ -18,7 +18,7 @@ var (
// CmdAdmin represents the available admin sub-command.
CmdAdmin = cli.Command{
Name: "admin",
Usage: "Perform admin operations on command line",
Usage: "Preform admin operations on command line",
Description: `Allow using internal logic of Gitea without hacking into the source code
to make automatic initialization process more smoothly`,
Subcommands: []cli.Command{
@@ -74,11 +74,7 @@ func runCreateUser(c *cli.Context) error {
setting.NewContext()
models.LoadConfigs()
setting.NewXORMLogService(false)
if err := models.SetEngine(); err != nil {
return fmt.Errorf("models.SetEngine: %v", err)
}
models.SetEngine()
if err := models.CreateUser(&models.User{
Name: c.String("name"),

View File

@@ -82,7 +82,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
log.Fatalf("Unable to marshal ECDSA private key: %v", err)
log.Fatalf("Unable to marshal ECDSA private key: %v\n", err)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
@@ -112,7 +112,7 @@ func runCert(ctx *cli.Context) error {
log.Fatalf("Unrecognized elliptic curve: %q", ctx.String("ecdsa-curve"))
}
if err != nil {
log.Fatalf("Failed to generate private key: %v", err)
log.Fatalf("Failed to generate private key: %s", err)
}
var notBefore time.Time
@@ -121,7 +121,7 @@ func runCert(ctx *cli.Context) error {
} else {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", ctx.String("start-date"))
if err != nil {
log.Fatalf("Failed to parse creation date: %v", err)
log.Fatalf("Failed to parse creation date: %s", err)
}
}
@@ -130,7 +130,7 @@ func runCert(ctx *cli.Context) error {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Failed to generate serial number: %v", err)
log.Fatalf("Failed to generate serial number: %s", err)
}
template := x509.Certificate{
@@ -163,12 +163,12 @@ func runCert(ctx *cli.Context) error {
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
log.Fatalf("Failed to create certificate: %s", err)
}
certOut, err := os.Create("cert.pem")
if err != nil {
log.Fatalf("Failed to open cert.pem for writing: %v", err)
log.Fatalf("Failed to open cert.pem for writing: %s", err)
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
@@ -176,7 +176,7 @@ func runCert(ctx *cli.Context) error {
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Failed to open key.pem for writing: %v", err)
log.Fatalf("Failed to open key.pem for writing: %v\n", err)
}
pem.Encode(keyOut, pemBlockForKey(priv))
keyOut.Close()

View File

@@ -11,13 +11,11 @@ import (
"log"
"os"
"path"
"path/filepath"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
"github.com/urfave/cli"
)
@@ -43,10 +41,6 @@ It can be used for backup and capture Gitea server image to send to maintainer`,
Value: os.TempDir(),
Usage: "Temporary dir path",
},
cli.StringFlag{
Name: "database, d",
Usage: "Specify the database SQL syntax",
},
},
}
@@ -55,13 +49,8 @@ func runDump(ctx *cli.Context) error {
setting.CustomConf = ctx.String("config")
}
setting.NewContext()
setting.NewServices() // cannot access session settings otherwise
models.LoadConfigs()
err := models.SetEngine()
if err != nil {
return err
}
models.SetEngine()
tmpDir := ctx.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
@@ -69,7 +58,7 @@ func runDump(ctx *cli.Context) error {
}
TmpWorkDir, err := ioutil.TempDir(tmpDir, "gitea-dump-")
if err != nil {
log.Fatalf("Failed to create tmp work directory: %v", err)
log.Fatalf("Fail to create tmp work directory: %v", err)
}
log.Printf("Creating tmp work dir: %s", TmpWorkDir)
@@ -79,64 +68,42 @@ func runDump(ctx *cli.Context) error {
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
zip.Verbose = ctx.Bool("verbose")
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
log.Fatalf("Failed to dump local repositories: %v", err)
log.Fatalf("Fail to dump local repositories: %v", err)
}
targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != models.DbCfg.Type {
log.Printf("Dumping database %s => %s...", models.DbCfg.Type, targetDBType)
} else {
log.Printf("Dumping database...")
}
if err := models.DumpDatabase(dbDump, targetDBType); err != nil {
log.Fatalf("Failed to dump database: %v", err)
log.Printf("Dumping database...")
if err := models.DumpDatabase(dbDump); err != nil {
log.Fatalf("Fail to dump database: %v", err)
}
fileName := fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix())
log.Printf("Packing dump files...")
z, err := zip.Create(fileName)
if err != nil {
log.Fatalf("Failed to create %s: %v", fileName, err)
log.Fatalf("Fail to create %s: %v", fileName, err)
}
if err := z.AddFile("gitea-repo.zip", reposDump); err != nil {
log.Fatalf("Failed to include gitea-repo.zip: %v", err)
log.Fatalf("Fail to include gitea-repo.zip: %v", err)
}
if err := z.AddFile("gitea-db.sql", dbDump); err != nil {
log.Fatalf("Failed to include gitea-db.sql: %v", err)
log.Fatalf("Fail to include gitea-db.sql: %v", err)
}
customDir, err := os.Stat(setting.CustomPath)
if err == nil && customDir.IsDir() {
if err := z.AddDir("custom", setting.CustomPath); err != nil {
log.Fatalf("Failed to include custom: %v", err)
log.Fatalf("Fail to include custom: %v", err)
}
} else {
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath)
}
if com.IsExist(setting.AppDataPath) {
log.Printf("Packing data directory...%s", setting.AppDataPath)
var sessionAbsPath string
if setting.SessionConfig.Provider == "file" {
if len(setting.SessionConfig.ProviderConfig) == 0 {
setting.SessionConfig.ProviderConfig = "data/sessions"
}
sessionAbsPath, _ = filepath.Abs(setting.SessionConfig.ProviderConfig)
}
if err := zipAddDirectoryExclude(z, "data", setting.AppDataPath, sessionAbsPath); err != nil {
log.Fatalf("Failed to include data directory: %v", err)
}
}
if err := z.AddDir("log", setting.LogRootPath); err != nil {
log.Fatalf("Failed to include log: %v", err)
log.Fatalf("Fail to include log: %v", err)
}
// FIXME: SSH key file.
if err = z.Close(); err != nil {
_ = os.Remove(fileName)
log.Fatalf("Failed to save %s: %v", fileName, err)
log.Fatalf("Fail to save %s: %v", fileName, err)
}
if err := os.Chmod(fileName, 0600); err != nil {
@@ -146,46 +113,9 @@ func runDump(ctx *cli.Context) error {
log.Printf("Removing tmp work dir: %s", TmpWorkDir)
if err := os.RemoveAll(TmpWorkDir); err != nil {
log.Fatalf("Failed to remove %s: %v", TmpWorkDir, err)
log.Fatalf("Fail to remove %s: %v", TmpWorkDir, err)
}
log.Printf("Finish dumping in file %s", fileName)
return nil
}
// zipAddDirectoryExclude zips absPath to specified zipPath inside z excluding excludeAbsPath
func zipAddDirectoryExclude(zip *zip.ZipArchive, zipPath, absPath string, excludeAbsPath string) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
}
dir, err := os.Open(absPath)
if err != nil {
return err
}
defer dir.Close()
zip.AddEmptyDir(zipPath)
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := path.Join(absPath, file.Name())
currentZipPath := path.Join(zipPath, file.Name())
if file.IsDir() {
if currentAbsPath != excludeAbsPath {
if err = zipAddDirectoryExclude(zip, currentZipPath, currentAbsPath, excludeAbsPath); err != nil {
return err
}
}
} else {
if err = zip.AddFile(currentZipPath, currentAbsPath); err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,233 +0,0 @@
// Copyright 2017 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 cmd
import (
"bufio"
"bytes"
"crypto/tls"
"fmt"
"os"
"strconv"
"strings"
"code.gitea.io/git"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/urfave/cli"
)
var (
// CmdHook represents the available hooks sub-command.
CmdHook = cli.Command{
Name: "hook",
Usage: "Delegate commands to corresponding Git hooks",
Description: "This should only be called by Git",
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
},
Subcommands: []cli.Command{
subcmdHookPreReceive,
subcmdHookUpadte,
subcmdHookPostReceive,
},
}
subcmdHookPreReceive = cli.Command{
Name: "pre-receive",
Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPreReceive,
}
subcmdHookUpadte = cli.Command{
Name: "update",
Usage: "Delegate update Git hook",
Description: "This command should only be called by Git",
Action: runHookUpdate,
}
subcmdHookPostReceive = cli.Command{
Name: "post-receive",
Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git",
Action: runHookPostReceive,
}
)
func runHookPreReceive(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
if c.IsSet("config") {
setting.CustomConf = c.String("config")
} else if c.GlobalIsSet("config") {
setting.CustomConf = c.GlobalString("config")
}
if err := setup("hooks/pre-receive.log"); err != nil {
fail("Hook pre-receive init failed", fmt.Sprintf("setup: %v", err))
}
// the environment setted on serv command
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
//username := os.Getenv(models.EnvRepoUsername)
//reponame := os.Getenv(models.EnvRepoName)
//repoPath := models.RepoPath(username, reponame)
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf.Write(scanner.Bytes())
buf.WriteByte('\n')
// TODO: support news feeds for wiki
if isWiki {
continue
}
fields := bytes.Fields(scanner.Bytes())
if len(fields) != 3 {
continue
}
//oldCommitID := string(fields[0])
newCommitID := string(fields[1])
refFullName := string(fields[2])
// FIXME: when we add feature to protected branch to deny force push, then uncomment below
/*var isForce bool
// detect force push
if git.EmptySHA != oldCommitID {
output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).RunInDir(repoPath)
if err != nil {
fail("Internal error", "Fail to detect force push: %v", err)
} else if len(output) > 0 {
isForce = true
}
}*/
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
if err != nil {
log.GitLogger.Fatal(2, "retrieve protected branches information failed")
}
if protectBranch != nil {
// check and deletion
if newCommitID == git.EmptySHA {
fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "")
} else {
fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
//fail(fmt.Sprintf("branch %s is protected from force push", branchName), "")
}
}
}
return nil
}
func runHookUpdate(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
if c.IsSet("config") {
setting.CustomConf = c.String("config")
} else if c.GlobalIsSet("config") {
setting.CustomConf = c.GlobalString("config")
}
if err := setup("hooks/update.log"); err != nil {
fail("Hook update init failed", fmt.Sprintf("setup: %v", err))
}
return nil
}
func runHookPostReceive(c *cli.Context) error {
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
return nil
}
if c.IsSet("config") {
setting.CustomConf = c.String("config")
} else if c.GlobalIsSet("config") {
setting.CustomConf = c.GlobalString("config")
}
if err := setup("hooks/post-receive.log"); err != nil {
fail("Hook post-receive init failed", fmt.Sprintf("setup: %v", err))
}
// the environment setted on serv command
repoUser := os.Getenv(models.EnvRepoUsername)
repoUserSalt := os.Getenv(models.EnvRepoUserSalt)
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
repoName := os.Getenv(models.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
pusherName := os.Getenv(models.EnvPusherName)
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf.Write(scanner.Bytes())
buf.WriteByte('\n')
// TODO: support news feeds for wiki
if isWiki {
continue
}
fields := bytes.Fields(scanner.Bytes())
if len(fields) != 3 {
continue
}
oldCommitID := string(fields[0])
newCommitID := string(fields[1])
refFullName := string(fields[2])
if err := models.PushUpdate(models.PushUpdateOptions{
RefFullName: refFullName,
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
PusherID: pusherID,
PusherName: pusherName,
RepoUserName: repoUser,
RepoName: repoName,
}); err != nil {
log.GitLogger.Error(2, "Update: %v", err)
}
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + repoUser + "/" + repoName + "/tasks/trigger?branch=" +
strings.TrimPrefix(refFullName, git.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUserSalt) + "&pusher=" + com.ToStr(pusherID)
log.GitLogger.Trace("Trigger task: %s", reqURL)
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
InsecureSkipVerify: true,
}).Response()
if err == nil {
resp.Body.Close()
if resp.StatusCode/100 != 2 {
log.GitLogger.Error(2, "Failed to trigger task: not 2xx response code")
}
} else {
log.GitLogger.Error(2, "Failed to trigger task: %v", err)
}
}
return nil
}

View File

@@ -6,7 +6,7 @@
package cmd
import (
"encoding/json"
"crypto/tls"
"fmt"
"os"
"os/exec"
@@ -14,17 +14,19 @@ import (
"strings"
"time"
"code.gitea.io/git"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/dgrijalva/jwt-go"
gouuid "github.com/satori/go.uuid"
"github.com/urfave/cli"
)
const (
accessDenied = "Repository does not exist or you do not have access"
lfsAuthenticateVerb = "git-lfs-authenticate"
accessDenied = "Repository does not exist or you do not have access"
)
// CmdServ represents the available serv sub-command.
@@ -42,20 +44,20 @@ var CmdServ = cli.Command{
},
}
func setup(logPath string) error {
func setup(logPath string) {
setting.NewContext()
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
models.LoadConfigs()
if setting.UseSQLite3 || setting.UseTiDB {
workDir, _ := setting.WorkDir()
if err := os.Chdir(workDir); err != nil {
log.GitLogger.Fatal(4, "Failed to change directory %s: %v", workDir, err)
log.GitLogger.Fatal(4, "Fail to change directory %s: %v", workDir, err)
}
}
setting.NewXORMLogService(true)
return models.SetEngine()
models.SetEngine()
}
func parseCmd(cmd string) (string, string) {
@@ -71,7 +73,6 @@ var (
"git-upload-pack": models.AccessModeRead,
"git-upload-archive": models.AccessModeRead,
"git-receive-pack": models.AccessModeWrite,
lfsAuthenticateVerb: models.AccessModeNone,
}
)
@@ -90,14 +91,58 @@ func fail(userMessage, logMessage string, args ...interface{}) {
os.Exit(1)
}
func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string, isWiki bool) {
task, err := models.GetUpdateTaskByUUID(uuid)
if err != nil {
if models.IsErrUpdateTaskNotExist(err) {
log.GitLogger.Trace("No update task is presented: %s", uuid)
return
}
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
}
if isWiki {
return
}
if err = models.PushUpdate(models.PushUpdateOptions{
RefFullName: task.RefName,
OldCommitID: task.OldCommitID,
NewCommitID: task.NewCommitID,
PusherID: user.ID,
PusherName: user.Name,
RepoUserName: repoUser.Name,
RepoName: reponame,
}); err != nil {
log.GitLogger.Error(2, "Update: %v", err)
}
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + repoUser.Name + "/" + reponame + "/tasks/trigger?branch=" +
strings.TrimPrefix(task.RefName, git.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUser.Salt) + "&pusher=" + com.ToStr(user.ID)
log.GitLogger.Trace("Trigger task: %s", reqURL)
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
InsecureSkipVerify: true,
}).Response()
if err == nil {
resp.Body.Close()
if resp.StatusCode/100 != 2 {
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code")
}
} else {
log.GitLogger.Error(2, "Fail to trigger task: %v", err)
}
}
func runServ(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if err := setup("serv.log"); err != nil {
fail("System init failed", fmt.Sprintf("setup: %v", err))
}
setup("serv.log")
if setting.SSH.Disabled {
println("Gitea: SSH has been disabled")
@@ -116,26 +161,11 @@ func runServ(c *cli.Context) error {
}
verb, args := parseCmd(cmd)
var lfsVerb string
if verb == lfsAuthenticateVerb {
if !setting.LFS.StartServer {
fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
}
if strings.Contains(args, " ") {
argsSplit := strings.SplitN(args, " ", 2)
args = strings.TrimSpace(argsSplit[0])
lfsVerb = strings.TrimSpace(argsSplit[1])
}
}
repoPath := strings.ToLower(strings.Trim(args, "'"))
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
fail("Invalid repository path", "Invalid repository path: %v", args)
}
username := strings.ToLower(rr[0])
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
@@ -145,14 +175,6 @@ func runServ(c *cli.Context) error {
reponame = reponame[:len(reponame)-5]
}
os.Setenv(models.EnvRepoUsername, username)
if isWiki {
os.Setenv(models.EnvRepoIsWiki, "true")
} else {
os.Setenv(models.EnvRepoIsWiki, "false")
}
os.Setenv(models.EnvRepoName, reponame)
repoUser, err := models.GetUserByName(username)
if err != nil {
if models.IsErrUserNotExist(err) {
@@ -161,8 +183,6 @@ func runServ(c *cli.Context) error {
fail("Internal error", "Failed to get repository owner (%s): %v", username, err)
}
os.Setenv(models.EnvRepoUserSalt, repoUser.Salt)
repo, err := models.GetRepositoryByName(repoUser.ID, reponame)
if err != nil {
if models.IsErrRepoNotExist(err) {
@@ -176,14 +196,6 @@ func runServ(c *cli.Context) error {
fail("Unknown git command", "Unknown git command %s", verb)
}
if verb == lfsAuthenticateVerb {
if lfsVerb == "upload" {
requestedMode = models.AccessModeWrite
} else {
requestedMode = models.AccessModeRead
}
}
// Prohibit push to mirror repositories.
if requestedMode > models.AccessModeRead && repo.IsMirror {
fail("mirror repository is read-only", "")
@@ -234,7 +246,7 @@ func runServ(c *cli.Context) error {
mode, err := models.AccessLevel(user, repo)
if err != nil {
fail("Internal error", "Failed to check access: %v", err)
fail("Internal error", "Fail to check access: %v", err)
} else if mode < requestedMode {
clientMessage := accessDenied
if mode >= models.AccessModeRead {
@@ -245,43 +257,14 @@ func runServ(c *cli.Context) error {
user.Name, requestedMode, repoPath)
}
os.Setenv(models.EnvPusherName, user.Name)
os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID))
os.Setenv("GITEA_PUSHER_NAME", user.Name)
}
}
//LFS token authentication
if verb == lfsAuthenticateVerb {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, repoUser.Name, repo.Name)
now := time.Now()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"repo": repo.ID,
"op": lfsVerb,
"exp": now.Add(5 * time.Minute).Unix(),
"nbf": now.Unix(),
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
if err != nil {
fail("Internal error", "Failed to sign JWT token: %v", err)
}
tokenAuthentication := &models.LFSTokenResponse{
Header: make(map[string]string),
Href: url,
}
tokenAuthentication.Header["Authorization"] = fmt.Sprintf("Bearer %s", tokenString)
enc := json.NewEncoder(os.Stdout)
err = enc.Encode(tokenAuthentication)
if err != nil {
fail("Internal error", "Failed to encode LFS json response: %v", err)
}
return nil
}
uuid := gouuid.NewV4().String()
os.Setenv("GITEA_UUID", uuid)
// Keep the old env variable name for backward compability
os.Setenv("uuid", uuid)
// Special handle for Windows.
if setting.IsWindows {
@@ -295,9 +278,6 @@ func runServ(c *cli.Context) error {
} else {
gitcmd = exec.Command(verb, repoPath)
}
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))
gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin
@@ -306,6 +286,10 @@ func runServ(c *cli.Context) error {
fail("Internal error", "Failed to execute git command: %v", err)
}
if requestedMode == models.AccessModeWrite {
handleUpdateTask(uuid, user, repoUser, reponame, isWiki)
}
// Update user key activity.
if keyID > 0 {
key, err := models.GetPublicKeyByID(keyID)

63
cmd/update.go Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"os"
"github.com/urfave/cli"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// CmdUpdate represents the available update sub-command.
var CmdUpdate = cli.Command{
Name: "update",
Usage: "This command should only be called by Git hook",
Description: `Update get pushed info and insert into database`,
Action: runUpdate,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
},
}
func runUpdate(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
setup("update.log")
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
log.GitLogger.Trace("SSH_ORIGINAL_COMMAND is empty")
return nil
}
args := c.Args()
if len(args) != 3 {
log.GitLogger.Fatal(2, "Arguments received are not equal to three")
} else if len(args[0]) == 0 {
log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use")
}
task := models.UpdateTask{
UUID: os.Getenv("GITEA_UUID"),
RefName: args[0],
OldCommitID: args[1],
NewCommitID: args[2],
}
if err := models.AddUpdateTask(&task); err != nil {
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err)
}
return nil
}

View File

@@ -5,11 +5,11 @@
package cmd
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/http/fcgi"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
"os"
"path"
"strings"
@@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/public"
@@ -30,7 +29,6 @@ import (
"code.gitea.io/gitea/routers/org"
"code.gitea.io/gitea/routers/repo"
"code.gitea.io/gitea/routers/user"
"github.com/go-macaron/binding"
"github.com/go-macaron/cache"
"github.com/go-macaron/captcha"
@@ -39,7 +37,6 @@ import (
"github.com/go-macaron/i18n"
"github.com/go-macaron/session"
"github.com/go-macaron/toolbox"
context2 "github.com/gorilla/context"
"github.com/urfave/cli"
macaron "gopkg.in/macaron.v1"
)
@@ -62,14 +59,16 @@ and it takes care of all the other things for you`,
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
cli.StringFlag{
Name: "pid, P",
Value: "/var/run/gitea.pid",
Usage: "Custom pid file path",
},
},
}
// VerChecker is a listing of required dependency versions.
type VerChecker struct {
ImportPath string
Version func() string
Expected string
}
// newMacaron initializes Macaron instance.
func newMacaron() *macaron.Macaron {
m := macaron.New()
@@ -83,11 +82,6 @@ func newMacaron() *macaron.Macaron {
if setting.Protocol == setting.FCGI {
m.SetURLPrefix(setting.AppSubURL)
}
m.Use(public.Custom(
&public.Options{
SkipLogging: setting.DisableRouterLog,
},
))
m.Use(public.Static(
&public.Options{
Directory: path.Join(setting.StaticRootPath, "public"),
@@ -99,7 +93,6 @@ func newMacaron() *macaron.Macaron {
macaron.StaticOptions{
Prefix: "avatars",
SkipLogging: setting.DisableRouterLog,
ETag: true,
},
))
@@ -109,7 +102,7 @@ func newMacaron() *macaron.Macaron {
localeNames, err := options.Dir("locale")
if err != nil {
log.Fatal(4, "Failed to list locale files: %v", err)
log.Fatal(4, "Fail to list locale files: %v", err)
}
localFiles := make(map[string][]byte)
@@ -162,11 +155,6 @@ func runWeb(ctx *cli.Context) error {
if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config")
}
if ctx.IsSet("pid") {
setting.CustomPID = ctx.String("pid")
}
routers.GlobalInit()
m := newMacaron()
@@ -178,8 +166,6 @@ func runWeb(ctx *cli.Context) error {
bindIgnErr := binding.BindIgnErr
m.Use(user.GetNotificationCount)
// FIXME: not all routes need go through same middlewares.
// Especially some AJAX requests, we can reduce middleware number to improve performance.
// Routers.
@@ -204,19 +190,6 @@ func runWeb(ctx *cli.Context) error {
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
m.Get("/reset_password", user.ResetPasswd)
m.Post("/reset_password", user.ResetPasswdPost)
m.Group("/oauth2", func() {
m.Get("/:provider", user.SignInOAuth)
m.Get("/:provider/callback", user.SignInOAuthCallback)
})
m.Get("/link_account", user.LinkAccount)
m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn)
m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister)
m.Group("/two_factor", func() {
m.Get("", user.TwoFactor)
m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
m.Get("/scratch", user.TwoFactorScratch)
m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
})
}, reqSignOut)
m.Group("/user/settings", func() {
@@ -237,14 +210,6 @@ func runWeb(ctx *cli.Context) error {
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
m.Post("/applications/delete", user.SettingsDeleteApplication)
m.Route("/delete", "GET,POST", user.SettingsDelete)
m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink)
m.Group("/two_factor", func() {
m.Get("", user.SettingsTwoFactor)
m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch)
m.Post("/disable", user.SettingsTwoFactorDisable)
m.Get("/enroll", user.SettingsTwoFactorEnroll)
m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), user.SettingsTwoFactorEnrollPost)
})
}, reqSignIn, func(ctx *context.Context) {
ctx.Data["PageIsUserSettings"] = true
})
@@ -306,6 +271,7 @@ func runWeb(ctx *cli.Context) error {
m.Get("", user.Profile)
m.Get("/followers", user.Followers)
m.Get("/following", user.Following)
m.Get("/stars", user.Stars)
})
m.Get("/attachments/:uuid", func(ctx *context.Context) {
@@ -331,7 +297,7 @@ func runWeb(ctx *cli.Context) error {
return
}
})
m.Post("/attachments", repo.UploadAttachment)
m.Post("/issues/attachments", repo.UploadIssueAttachment)
}, ignSignIn)
m.Group("/:username", func() {
@@ -347,14 +313,8 @@ func runWeb(ctx *cli.Context) error {
// ***** START: Organization *****
m.Group("/org", func() {
m.Group("", func() {
m.Get("/create", org.Create)
m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
}, func(ctx *context.Context) {
if !ctx.User.CanCreateOrganization() {
ctx.NotFound()
}
})
m.Get("/create", org.Create)
m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
m.Group("/:org", func() {
m.Get("/dashboard", user.Dashboard)
@@ -423,11 +383,6 @@ func runWeb(ctx *cli.Context) error {
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
m.Post("/delete", repo.DeleteCollaboration)
})
m.Group("/branches", func() {
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
m.Post("/can_push", repo.ChangeProtectedBranch)
m.Post("/delete", repo.DeleteProtectedBranch)
})
m.Group("/hooks", func() {
m.Get("", repo.Webhooks)
@@ -455,7 +410,7 @@ func runWeb(ctx *cli.Context) error {
}, func(ctx *context.Context) {
ctx.Data["PageIsSettings"] = true
}, context.UnitTypes())
})
}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
@@ -496,11 +451,13 @@ func runWeb(ctx *cli.Context) error {
m.Get("/:id/:action", repo.ChangeMilestonStatus)
m.Post("/delete", repo.DeleteMilestone)
}, reqRepoWriter, context.RepoRef())
m.Group("/releases", func() {
m.Get("/new", repo.NewRelease)
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
m.Post("/delete", repo.DeleteRelease)
}, reqRepoWriter, context.RepoRef())
m.Group("/releases", func() {
m.Get("/edit/*", repo.EditRelease)
m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
@@ -549,7 +506,7 @@ func runWeb(ctx *cli.Context) error {
return
}
})
}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes())
}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare)
m.Group("/:username/:reponame", func() {
m.Group("", func() {
@@ -561,7 +518,6 @@ func runWeb(ctx *cli.Context) error {
}, context.RepoRef())
// m.Get("/branches", repo.Branches)
m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
m.Group("/wiki", func() {
m.Get("/?:page", repo.Wiki)
@@ -576,11 +532,6 @@ func runWeb(ctx *cli.Context) error {
}, reqSignIn, reqRepoWriter)
}, repo.MustEnableWiki, context.RepoRef())
m.Group("/wiki", func() {
m.Get("/raw/*", repo.WikiRaw)
m.Get("/*", repo.WikiRaw)
}, repo.MustEnableWiki)
m.Get("/archive/*", repo.Download)
m.Group("/pulls/:index", func() {
@@ -593,43 +544,31 @@ func runWeb(ctx *cli.Context) error {
m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home)
m.Get("/raw/*", repo.SingleDownload)
m.Get("/commits/*", repo.RefCommits)
m.Get("/graph", repo.Graph)
m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
m.Get("/forks", repo.Forks)
}, context.RepoRef())
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.RawDiff)
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff)
}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes())
}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare)
m.Group("/:username/:reponame", func() {
m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers)
}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
}, ignSignIn, context.RepoAssignment(), context.RepoRef())
m.Group("/:username", func() {
m.Group("/:reponame", func() {
m.Get("", repo.SetEditorconfigIfExists, repo.Home)
m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home)
}, ignSignIn, context.RepoAssignment(true), context.RepoRef(), context.UnitTypes())
}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
m.Group("/:reponame", func() {
m.Group("/info/lfs", func() {
m.Post("/objects/batch", lfs.BatchHandler)
m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
m.Any("/objects/:oid", lfs.ObjectOidHandler)
m.Post("/objects", lfs.PostHandler)
}, ignSignInAndCsrf)
m.Any("/*", ignSignInAndCsrf, repo.HTTP)
m.Head("/tasks/trigger", repo.TriggerTask)
})
})
// ***** END: Repository *****
m.Group("/notifications", func() {
m.Get("", user.Notifications)
m.Post("/status", user.NotificationStatusPost)
}, reqSignIn)
m.Group("/api", func() {
apiv1.RegisterRoutes(m)
}, ignSignIn)
@@ -660,27 +599,18 @@ func runWeb(ctx *cli.Context) error {
}
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
if setting.LFS.StartServer {
log.Info("LFS server enabled")
}
if setting.EnablePprof {
go func() {
log.Info("%v", http.ListenAndServe("localhost:6060", nil))
}()
}
var err error
switch setting.Protocol {
case setting.HTTP:
err = runHTTP(listenAddr, context2.ClearHandler(m))
err = http.ListenAndServe(listenAddr, m)
case setting.HTTPS:
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
server := &http.Server{Addr: listenAddr, TLSConfig: &tls.Config{MinVersion: tls.VersionTLS10}, Handler: m}
err = server.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
case setting.FCGI:
err = fcgi.Serve(nil, context2.ClearHandler(m))
err = fcgi.Serve(nil, m)
case setting.UnixSocket:
if err := os.Remove(listenAddr); err != nil && !os.IsNotExist(err) {
log.Fatal(4, "Failed to remove unix socket directory %s: %v", listenAddr, err)
if err := os.Remove(listenAddr); err != nil {
log.Fatal(4, "Fail to remove unix socket directory %s: %v", listenAddr, err)
}
var listener *net.UnixListener
listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: listenAddr, Net: "unix"})
@@ -693,13 +623,13 @@ func runWeb(ctx *cli.Context) error {
if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
log.Fatal(4, "Failed to set permission of unix socket: %v", err)
}
err = http.Serve(listener, context2.ClearHandler(m))
err = http.Serve(listener, m)
default:
log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
}
if err != nil {
log.Fatal(4, "Failed to start server: %v", err)
log.Fatal(4, "Fail to start server: %v", err)
}
return nil

View File

@@ -1,44 +0,0 @@
// +build !windows
// Copyright 2016 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 cmd
import (
"crypto/tls"
"net/http"
"code.gitea.io/gitea/modules/log"
"github.com/facebookgo/grace/gracehttp"
)
func runHTTP(listenAddr string, m http.Handler) error {
return gracehttp.Serve(&http.Server{
Addr: listenAddr,
Handler: m,
})
}
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
config := &tls.Config{
MinVersion: tls.VersionTLS10,
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
config.Certificates = make([]tls.Certificate, 1)
var err error
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatal(4, "Failed to load https cert file %s: %v", listenAddr, err)
}
return gracehttp.Serve(&http.Server{
Addr: listenAddr,
Handler: m,
TLSConfig: config,
})
}

View File

@@ -1,19 +0,0 @@
// +build windows
// Copyright 2016 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 cmd
import (
"net/http"
)
func runHTTP(listenAddr string, m http.Handler) error {
return http.ListenAndServe(listenAddr, m)
}
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error {
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, m)
}

View File

@@ -57,8 +57,6 @@ FEED_MAX_COMMIT_NUM = 5
THEME_COLOR_META_TAG = `#6cc644`
; Max size of files to be displayed (defaults is 8MiB)
MAX_DISPLAY_FILE_SIZE = 8388608
; Whether show the user email in the Explore Users page
SHOW_USER_EMAIL = true
[ui.admin]
; Number of users that are showed in one page
@@ -103,7 +101,7 @@ START_SSH_SERVER = false
; Domain name to be exposed in clone URL
SSH_DOMAIN = %(DOMAIN)s
; Network interface builtin SSH server listens on
SSH_LISTEN_HOST =
SSH_LISTEN_HOST =
; Port number to be exposed in clone URL
SSH_PORT = 22
; Port number builtin SSH server listens on
@@ -158,16 +156,9 @@ SSL_MODE = disable
; For "sqlite3" and "tidb", use absolute path when you start as service
PATH = data/gitea.db
[indexer]
ISSUE_INDEXER_PATH = indexers/issues.bleve
UPDATE_BUFFER_LEN = 20
[admin]
; Disable regular (non-admin) users to create organizations
DISABLE_REGULAR_ORG_CREATION = false
[security]
; Whether the installer is disabled
INSTALL_LOCK = false
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
SECRET_KEY = !#@FDEWREWR&*(
@@ -177,10 +168,6 @@ COOKIE_USERNAME = gitea_awesome
COOKIE_REMEMBER_NAME = gitea_incredible
; Reverse proxy authentication header name of user name
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
; Sets the minimum password length for new Users
MIN_PASSWORD_LENGTH = 6
; True when users are allowed to import local server paths
IMPORT_LOCAL_PATHS = false
[service]
ACTIVE_CODE_LIVE_MINUTES = 180
@@ -198,13 +185,6 @@ ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; Enable captcha validation for registration
ENABLE_CAPTCHA = true
; Default value for KeepEmailPrivate
; New user will get the value of this setting copied into their profile
DEFAULT_KEEP_EMAIL_PRIVATE = false
; 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
; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
NO_REPLY_ADDRESS = noreply.example.org
[webhook]
; Hook task queue length, increase if webhook shooting starts hanging
@@ -244,10 +224,6 @@ USER =
PASSWD =
; Use text/html as alternative format of content
ENABLE_HTML_ALTERNATIVE = false
; Enable sendmail (override SMTP)
USE_SENDMAIL = false
; Specifiy an alternative sendmail binary
SENDMAIL_PATH = sendmail
[cache]
; Either "memory", "redis", or "memcache", default is "memory"
@@ -269,7 +245,7 @@ PROVIDER = memory
; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
PROVIDER_CONFIG = data/sessions
; Session cookie name
COOKIE_NAME = i_like_gitea
COOKIE_NAME = i_like_gogits
; If you use session in https only, default is false
COOKIE_SECURE = false
; Enable set cookie, default is true
@@ -297,7 +273,7 @@ ENABLE = true
; Path for attachments. Defaults to `data/attachments`
PATH = data/attachments
; 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
; Max size of each file. Defaults to 32MB
MAX_SIZE = 4
; Max number of files per upload. Defaults to 10
@@ -393,13 +369,6 @@ ARGS =
RUN_AT_START = true
SCHEDULE = @every 24h
; Clean up old repository archives
[cron.archive_cleanup]
RUN_AT_START = true
SCHEDULE = @every 24h
; Archives created more than OLDER_THAN ago are subject to deletion
OLDER_THAN = 24h
[git]
; Disables highlight of added and removed changes
DISABLE_DIFF_HIGHLIGHT = false
@@ -431,7 +400,7 @@ MAX_RESPONSE_ITEMS = 50
[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
NAMES = English,简体中文,繁體中文(香港),繁體中文(台,Deutsch,Français,Nederlands,Latviešu,Русский,日本語,Español,Português do Brasil,Polski,български,Italiano,Suomalainen,Türkçe,čeština,Српски,Svenska,한국어
NAMES = English,简体中文,繁體中文(香港),繁體中文(台,Deutsch,Français,Nederlands,Latviešu,Русский,日本語,Español,Português do Brasil,Polski,български,Italiano,Suomalainen,Türkçe,čeština,Српски,Svenska,한국어
; Used for datetimepicker
[i18n.datelang]

View File

@@ -1,91 +0,0 @@
// Copyright 2017 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 integration
import (
"fmt"
"net/http"
"os"
"os/user"
"testing"
"time"
"code.gitea.io/gitea/integrations/internal/utils"
)
// The HTTP port listened by the Gitea server.
const ServerHTTPPort = "3001"
const _RetryLimit = 10
func makeSimpleSettings(user, port string) map[string][]string {
return map[string][]string{
"db_type": {"SQLite3"},
"db_host": {"localhost"},
"db_path": {"data/gitea.db"},
"app_name": {"Gitea: Git with a cup of tea"},
"repo_root_path": {"repositories"},
"run_user": {user},
"domain": {"localhost"},
"ssh_port": {"22"},
"http_port": {port},
"app_url": {"http://localhost:" + port},
"log_root_path": {"log"},
}
}
func install(t *utils.T) error {
var r *http.Response
var err error
for i := 1; i <= _RetryLimit; i++ {
r, err = http.Get("http://:" + ServerHTTPPort + "/")
if err == nil {
fmt.Fprintln(os.Stderr)
break
}
// Give the server some amount of time to warm up.
time.Sleep(100 * time.Millisecond)
fmt.Fprint(os.Stderr, ".")
}
if err != nil {
return err
}
defer r.Body.Close()
_user, err := user.Current()
if err != nil {
return err
}
settings := makeSimpleSettings(_user.Username, ServerHTTPPort)
r, err = http.PostForm("http://:"+ServerHTTPPort+"/install", settings)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
return fmt.Errorf("'/install': %s", r.Status)
}
return nil
}
func TestInstall(t *testing.T) {
conf := utils.Config{
Program: "../gitea",
WorkDir: "",
Args: []string{"web", "--port", ServerHTTPPort},
LogFile: os.Stderr,
}
if err := utils.New(t, &conf).RunTest(install); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,125 +0,0 @@
// Copyright 2017 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 utils
import (
"errors"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
)
// T wraps testing.T and the configurations of the testing instance.
type T struct {
*testing.T
Config *Config
}
// New create an instance of T
func New(t *testing.T, c *Config) *T {
return &T{T: t, Config: c}
}
// Config Settings of the testing program
type Config struct {
// The executable path of the tested program.
Program string
// Working directory prepared for the tested program.
// If empty, a directory named with random suffixes is picked, and created under the platform-dependent default temporary directory.
// The directory will be removed when the test finishes.
WorkDir string
// Command-line arguments passed to the tested program.
Args []string
// Where to redirect the stdout/stderr to. For debugging purposes.
LogFile *os.File
}
func redirect(cmd *exec.Cmd, f *os.File) error {
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
go io.Copy(f, stdout)
go io.Copy(f, stderr)
return nil
}
// RunTest Helper function for setting up a running Gitea server for functional testing and then gracefully terminating it.
func (t *T) RunTest(tests ...func(*T) error) (err error) {
if t.Config.Program == "" {
return errors.New("Need input file")
}
path, err := filepath.Abs(t.Config.Program)
if err != nil {
return err
}
workdir := t.Config.WorkDir
if workdir == "" {
workdir, err = ioutil.TempDir(os.TempDir(), "gitea_tests-")
if err != nil {
return err
}
defer os.RemoveAll(workdir)
}
newpath := filepath.Join(workdir, filepath.Base(path))
if err := os.Symlink(path, newpath); err != nil {
return err
}
log.Printf("Starting the server: %s args:%s workdir:%s", newpath, t.Config.Args, workdir)
cmd := exec.Command(newpath, t.Config.Args...)
cmd.Dir = workdir
if t.Config.LogFile != nil && testing.Verbose() {
if err := redirect(cmd, t.Config.LogFile); err != nil {
return err
}
}
if err := cmd.Start(); err != nil {
return err
}
log.Println("Server started.")
defer func() {
// Do not early return. We have to call Wait anyway.
_ = cmd.Process.Signal(syscall.SIGTERM)
if _err := cmd.Wait(); _err != nil {
if _err.Error() != "signal: terminated" {
err = _err
return
}
}
log.Println("Server exited")
}()
for _, fn := range tests {
if err := fn(t); err != nil {
return err
}
}
// Note that the return value 'err' may be updated by the 'defer' statement before despite it's returning nil here.
return nil
}

View File

@@ -1,82 +0,0 @@
// Copyright 2017 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 integration
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"code.gitea.io/gitea/integrations/internal/utils"
"code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
)
func version(t *utils.T) error {
var err error
path, err := filepath.Abs(t.Config.Program)
if err != nil {
return err
}
cmd := exec.Command(path, "--version")
out, err := cmd.Output()
if err != nil {
return err
}
fields := strings.Fields(string(out))
if !strings.HasPrefix(string(out), "Gitea version") {
return fmt.Errorf("unexpected version string '%s' of the gitea executable", out)
}
expected := fields[2]
var r *http.Response
r, err = http.Get("http://:" + ServerHTTPPort + "/api/v1/version")
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
return fmt.Errorf("'/api/v1/version': %s\n", r.Status)
}
var v gitea.ServerVersion
dec := json.NewDecoder(r.Body)
if err := dec.Decode(&v); err != nil {
return err
}
actual := v.Version
log.Printf("Actual: \"%s\" Expected: \"%s\"\n", actual, expected)
assert.Equal(t, expected, actual)
return nil
}
func TestVersion(t *testing.T) {
conf := utils.Config{
Program: "../gitea",
WorkDir: "",
Args: []string{"web", "--port", ServerHTTPPort},
LogFile: os.Stderr,
}
if err := utils.New(t, &conf).RunTest(install, version); err != nil {
t.Fatal(err)
}
}

25
main.go
View File

@@ -8,34 +8,32 @@ package main // import "code.gitea.io/gitea"
import (
"os"
"strings"
"runtime"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
)
// Version holds the current Gitea version
var Version = "1.1.0+dev"
// Tags holds the build tags used
var Tags = ""
var Version = "1.0.0+dev"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
setting.AppVer = Version
setting.AppBuiltWith = formatBuiltWith(Tags)
}
func main() {
app := cli.NewApp()
app.Name = "Gitea"
app.Usage = "A painless self-hosted Git service"
app.Version = Version + formatBuiltWith(Tags)
app.Version = Version
app.Commands = []cli.Command{
cmd.CmdWeb,
cmd.CmdServ,
cmd.CmdHook,
cmd.CmdUpdate,
cmd.CmdDump,
cmd.CmdCert,
cmd.CmdAdmin,
@@ -43,14 +41,7 @@ func main() {
app.Flags = append(app.Flags, []cli.Flag{}...)
err := app.Run(os.Args)
if err != nil {
log.Fatal(4, "Failed to run app with %s: %v", os.Args, err)
}
}
func formatBuiltWith(Tags string) string {
if len(Tags) == 0 {
return ""
log.Fatal(4, "Fail to run app with %s: %v", os.Args, err)
}
return " built with: " + strings.Replace(Tags, " ", ", ", -1)
}

View File

@@ -4,7 +4,11 @@
package models
import "fmt"
import (
"fmt"
"code.gitea.io/gitea/modules/log"
)
// AccessMode specifies the users access mode
type AccessMode int
@@ -96,45 +100,29 @@ func HasAccess(user *User, repo *Repository, testMode AccessMode) (bool, error)
return hasAccess(x, user, repo, testMode)
}
type repoAccess struct {
Access `xorm:"extends"`
Repository `xorm:"extends"`
}
func (repoAccess) TableName() string {
return "access"
}
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
func (user *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
rows, err := x.
Join("INNER", "repository", "repository.id = access.repo_id").
Where("access.user_id = ?", user.ID).
And("repository.owner_id <> ?", user.ID).
Rows(new(repoAccess))
if err != nil {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: user.ID}); err != nil {
return nil, err
}
defer rows.Close()
var repos = make(map[*Repository]AccessMode, 10)
var ownerCache = make(map[int64]*User, 10)
for rows.Next() {
var repo repoAccess
err = rows.Scan(&repo)
repos := make(map[*Repository]AccessMode, len(accesses))
for _, access := range accesses {
repo, err := GetRepositoryByID(access.RepoID)
if err != nil {
if IsErrRepoNotExist(err) {
log.Error(4, "GetRepositoryByID: %v", err)
continue
}
return nil, err
}
var ok bool
if repo.Owner, ok = ownerCache[repo.OwnerID]; !ok {
if err = repo.GetOwner(); err != nil {
return nil, err
}
ownerCache[repo.OwnerID] = repo.Owner
if err = repo.GetOwner(); err != nil {
return nil, err
} else if repo.OwnerID == user.ID {
continue
}
repos[&repo.Repository] = repo.Access.Mode
repos[repo] = access.Mode
}
return repos, nil
}
@@ -166,7 +154,7 @@ func maxAccessMode(modes ...AccessMode) AccessMode {
return max
}
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
// FIXME: do corss-comparison so reduce deletions and additions to the minimum?
func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) {
minMode := AccessModeRead
if !repo.IsPrivate {

View File

@@ -1,125 +0,0 @@
// Copyright 2017 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"
)
var accessModes = []AccessMode{
AccessModeRead,
AccessModeWrite,
AccessModeAdmin,
AccessModeOwner,
}
func TestAccessLevel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
level, err := AccessLevel(user1, repo1)
assert.NoError(t, err)
assert.Equal(t, AccessModeOwner, level)
level, err = AccessLevel(user1, repo2)
assert.NoError(t, err)
assert.Equal(t, AccessModeWrite, level)
level, err = AccessLevel(user2, repo1)
assert.NoError(t, err)
assert.Equal(t, AccessModeRead, level)
level, err = AccessLevel(user2, repo2)
assert.NoError(t, err)
assert.Equal(t, AccessModeNone, level)
}
func TestHasAccess(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user2 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
for _, accessMode := range accessModes {
has, err := HasAccess(user1, repo1, accessMode)
assert.NoError(t, err)
assert.True(t, has)
has, err = HasAccess(user1, repo2, accessMode)
assert.NoError(t, err)
assert.Equal(t, accessMode <= AccessModeWrite, has)
has, err = HasAccess(user2, repo1, accessMode)
assert.NoError(t, err)
assert.Equal(t, accessMode <= AccessModeRead, has)
has, err = HasAccess(user2, repo2, accessMode)
assert.NoError(t, err)
assert.Equal(t, accessMode <= AccessModeNone, has)
}
}
func TestUser_GetRepositoryAccesses(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
accesses, err := user1.GetRepositoryAccesses()
assert.NoError(t, err)
assert.Len(t, accesses, 0)
}
func TestUser_GetAccessibleRepositories(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user1 := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
repos, err := user1.GetAccessibleRepositories(0)
assert.NoError(t, err)
assert.Len(t, repos, 0)
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repos, err = user2.GetAccessibleRepositories(0)
assert.NoError(t, err)
assert.Len(t, repos, 1)
}
func TestRepository_RecalculateAccesses(t *testing.T) {
// test with organization repo
assert.NoError(t, PrepareTestDatabase())
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.NoError(t, repo1.GetOwner())
_, err := x.Delete(&Collaboration{UserID: 2, RepoID: 3})
assert.NoError(t, err)
assert.NoError(t, repo1.RecalculateAccesses())
access := &Access{UserID: 2, RepoID: 3}
has, err := x.Get(access)
assert.NoError(t, err)
assert.True(t, has)
assert.Equal(t, AccessModeOwner, access.Mode)
}
func TestRepository_RecalculateAccesses2(t *testing.T) {
// test with non-organization repo
assert.NoError(t, PrepareTestDatabase())
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository)
assert.NoError(t, repo1.GetOwner())
_, err := x.Delete(&Collaboration{UserID: 4, RepoID: 4})
assert.NoError(t, err)
assert.NoError(t, repo1.RecalculateAccesses())
has, err := x.Get(&Access{UserID: 4, RepoID: 4})
assert.NoError(t, err)
assert.False(t, has)
}

View File

@@ -71,19 +71,19 @@ func init() {
// used in template render.
type Action struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX"` // Receiver user id.
UserID int64 // Receiver user id.
OpType ActionType
ActUserID int64 `xorm:"INDEX"` // Action user id.
ActUserID int64 // Action user id.
ActUserName string // Action user name.
ActAvatar string `xorm:"-"`
RepoID int64 `xorm:"INDEX"`
RepoID int64
RepoUserName string
RepoName string
RefName string
IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"`
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
Content string `xorm:"TEXT"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"`
CreatedUnix int64
}
// BeforeInsert will be invoked by XORM before inserting a record
@@ -145,7 +145,7 @@ func (a *Action) GetRepoPath() string {
}
// ShortRepoPath returns the virtual path to the action repository
// trimmed to max 20 + 1 + 33 chars.
// trimed to max 20 + 1 + 33 chars.
func (a *Action) ShortRepoPath() string {
return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
}
@@ -360,7 +360,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) || err == errMissingIssueNumber {
if IsErrIssueNotExist(err) {
continue
}
return err
@@ -398,7 +398,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) || err == errMissingIssueNumber {
if IsErrIssueNotExist(err) {
continue
}
return err
@@ -418,7 +418,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
}
}
// It is conflict to have close and reopen at same time, so refsMarked doesn't need to reinit here.
// It is conflict to have close and reopen at same time, so refsMarkd doesn't need to reinit here.
for _, ref := range issueReopenKeywordsPat.FindAllString(c.Message, -1) {
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
ref = strings.TrimRightFunc(ref, issueIndexTrimRight)
@@ -438,7 +438,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) || err == errMissingIssueNumber {
if IsErrIssueNotExist(err) {
continue
}
return err
@@ -658,14 +658,17 @@ func GetFeeds(ctxUser *User, actorID, offset int64, isProfile bool) ([]*Action,
And("is_private = ?", false).
And("act_user_id = ?", ctxUser.ID)
} else if actorID != -1 && ctxUser.IsOrganization() {
env, err := ctxUser.AccessibleReposEnv(actorID)
if err != nil {
return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
}
repoIDs, err := env.RepoIDs(1, ctxUser.NumRepos)
// FIXME: only need to get IDs here, not all fields of repository.
repos, _, err := ctxUser.GetUserRepositories(actorID, 1, ctxUser.NumRepos)
if err != nil {
return nil, fmt.Errorf("GetUserRepositories: %v", err)
}
var repoIDs []int64
for _, repo := range repos {
repoIDs = append(repoIDs, repo.ID)
}
if len(repoIDs) > 0 {
sess.In("repo_id", repoIDs)
}

View File

@@ -1,337 +0,0 @@
package models
import (
"strings"
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestAction_GetRepoPath(t *testing.T) {
action := &Action{
RepoUserName: "username",
RepoName: "reponame",
}
assert.Equal(t, "username/reponame", action.GetRepoPath())
}
func TestAction_GetRepoLink(t *testing.T) {
action := &Action{
RepoUserName: "username",
RepoName: "reponame",
}
setting.AppSubURL = "/suburl/"
assert.Equal(t, "/suburl/username/reponame", action.GetRepoLink())
setting.AppSubURL = ""
assert.Equal(t, "/username/reponame", action.GetRepoLink())
}
func TestNewRepoAction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{OwnerID: user.ID}).(*Repository)
repo.Owner = user
actionBean := &Action{
OpType: ActionCreateRepo,
ActUserID: user.ID,
RepoID: repo.ID,
ActUserName: user.Name,
RepoName: repo.Name,
RepoUserName: repo.Owner.Name,
IsPrivate: repo.IsPrivate,
}
AssertNotExistsBean(t, actionBean)
assert.NoError(t, NewRepoAction(user, repo))
AssertExistsAndLoadBean(t, actionBean)
CheckConsistencyFor(t, &Action{})
}
func TestRenameRepoAction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{OwnerID: user.ID}).(*Repository)
repo.Owner = user
oldRepoName := repo.Name
const newRepoName = "newRepoName"
repo.Name = newRepoName
repo.LowerName = strings.ToLower(newRepoName)
actionBean := &Action{
OpType: ActionRenameRepo,
ActUserID: user.ID,
ActUserName: user.Name,
RepoID: repo.ID,
RepoName: repo.Name,
RepoUserName: repo.Owner.Name,
IsPrivate: repo.IsPrivate,
Content: oldRepoName,
}
AssertNotExistsBean(t, actionBean)
assert.NoError(t, RenameRepoAction(user, oldRepoName, repo))
AssertExistsAndLoadBean(t, actionBean)
_, err := x.Id(repo.ID).Cols("name", "lower_name").Update(repo)
assert.NoError(t, err)
CheckConsistencyFor(t, &Action{})
}
func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "message1",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "message2",
},
}
pushCommits.Len = len(pushCommits.Commits)
payloadCommits := pushCommits.ToAPIPayloadCommits("/username/reponame")
assert.Len(t, payloadCommits, 2)
assert.Equal(t, "abcdef1", payloadCommits[0].ID)
assert.Equal(t, "message1", payloadCommits[0].Message)
assert.Equal(t, "/username/reponame/commit/abcdef1", payloadCommits[0].URL)
assert.Equal(t, "User Two", payloadCommits[0].Committer.Name)
assert.Equal(t, "user2", payloadCommits[0].Committer.UserName)
assert.Equal(t, "User Four", payloadCommits[0].Author.Name)
assert.Equal(t, "user4", payloadCommits[0].Author.UserName)
assert.Equal(t, "abcdef2", payloadCommits[1].ID)
assert.Equal(t, "message2", payloadCommits[1].Message)
assert.Equal(t, "/username/reponame/commit/abcdef2", payloadCommits[1].URL)
assert.Equal(t, "User Two", payloadCommits[1].Committer.Name)
assert.Equal(t, "user2", payloadCommits[1].Committer.UserName)
assert.Equal(t, "User Two", payloadCommits[1].Author.Name)
assert.Equal(t, "user2", payloadCommits[1].Author.UserName)
}
func TestPushCommits_AvatarLink(t *testing.T) {
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "message1",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "message2",
},
}
pushCommits.Len = len(pushCommits.Commits)
assert.Equal(t,
"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f",
pushCommits.AvatarLink("user2@example.com"))
assert.Equal(t,
"https://secure.gravatar.com/avatar/19ade630b94e1e0535b3df7387434154",
pushCommits.AvatarLink("nonexistent@example.com"))
}
func TestUpdateIssuesCommit(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pushCommits := []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "start working on #1",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "a plain message",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "close #2",
},
}
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
repo.Owner = user
commentBean := &Comment{
Type: CommentTypeCommitRef,
CommitSHA: "abcdef1",
PosterID: user.ID,
IssueID: 1,
}
issueBean := &Issue{RepoID: repo.ID, Index: 2}
AssertNotExistsBean(t, commentBean)
AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits))
AssertExistsAndLoadBean(t, commentBean)
AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
CheckConsistencyFor(t, &Action{})
}
func TestCommitRepoAction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 2, OwnerID: user.ID}).(*Repository)
repo.Owner = user
pushCommits := NewPushCommits()
pushCommits.Commits = []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "message1",
},
{
Sha1: "abcdef2",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user2@example.com",
AuthorName: "User Two",
Message: "message2",
},
}
pushCommits.Len = len(pushCommits.Commits)
actionBean := &Action{
OpType: ActionCommitRepo,
ActUserID: user.ID,
ActUserName: user.Name,
RepoID: repo.ID,
RepoName: repo.Name,
RefName: "refName",
IsPrivate: repo.IsPrivate,
}
AssertNotExistsBean(t, actionBean)
assert.NoError(t, CommitRepoAction(CommitRepoActionOptions{
PusherName: user.Name,
RepoOwnerID: user.ID,
RepoName: repo.Name,
RefFullName: "refName",
OldCommitID: "oldCommitID",
NewCommitID: "newCommitID",
Commits: pushCommits,
}))
AssertExistsAndLoadBean(t, actionBean)
CheckConsistencyFor(t, &Action{})
}
func TestTransferRepoAction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, OwnerID: user2.ID}).(*Repository)
repo.OwnerID = user4.ID
repo.Owner = user4
actionBean := &Action{
OpType: ActionTransferRepo,
ActUserID: user2.ID,
ActUserName: user2.Name,
RepoID: repo.ID,
RepoName: repo.Name,
RepoUserName: repo.Owner.Name,
IsPrivate: repo.IsPrivate,
}
AssertNotExistsBean(t, actionBean)
assert.NoError(t, TransferRepoAction(user2, user2, repo))
AssertExistsAndLoadBean(t, actionBean)
_, err := x.Id(repo.ID).Cols("owner_id").Update(repo)
assert.NoError(t, err)
CheckConsistencyFor(t, &Action{})
}
func TestMergePullRequestAction(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1, OwnerID: user.ID}).(*Repository)
repo.Owner = user
issue := AssertExistsAndLoadBean(t, &Issue{ID: 3, RepoID: repo.ID}).(*Issue)
actionBean := &Action{
OpType: ActionMergePullRequest,
ActUserID: user.ID,
ActUserName: user.Name,
RepoID: repo.ID,
RepoName: repo.Name,
RepoUserName: repo.Owner.Name,
IsPrivate: repo.IsPrivate,
}
AssertNotExistsBean(t, actionBean)
assert.NoError(t, MergePullRequestAction(user, repo, issue))
AssertExistsAndLoadBean(t, actionBean)
CheckConsistencyFor(t, &Action{})
}
func TestGetFeeds(t *testing.T) {
// test with an individual user
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
actions, err := GetFeeds(user, user.ID, 0, false)
assert.NoError(t, err)
assert.Len(t, actions, 1)
assert.Equal(t, int64(1), actions[0].ID)
assert.Equal(t, user.ID, actions[0].UserID)
actions, err = GetFeeds(user, user.ID, 0, true)
assert.NoError(t, err)
assert.Len(t, actions, 0)
}
func TestGetFeeds2(t *testing.T) {
// test with an organization user
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
actions, err := GetFeeds(user, user.ID, 0, false)
assert.NoError(t, err)
assert.Len(t, actions, 1)
assert.Equal(t, int64(2), actions[0].ID)
assert.Equal(t, user.ID, actions[0].UserID)
actions, err = GetFeeds(user, user.ID, 0, true)
assert.NoError(t, err)
assert.Len(t, actions, 0)
}

View File

@@ -32,7 +32,7 @@ type Notice struct {
Type NoticeType
Description string `xorm:"TEXT"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"`
CreatedUnix int64
}
// BeforeInsert is invoked from XORM before inserting an object of this type.
@@ -55,30 +55,27 @@ func (n *Notice) TrStr() string {
// CreateNotice creates new system notice.
func CreateNotice(tp NoticeType, desc string) error {
return createNotice(x, tp, desc)
}
// prevent panic if database connection is not available at this point
if x == nil {
return fmt.Errorf("Could not save notice due database connection not being available: %d %s", tp, desc)
}
func createNotice(e Engine, tp NoticeType, desc string) error {
n := &Notice{
Type: tp,
Description: desc,
}
_, err := e.Insert(n)
_, err := x.Insert(n)
return err
}
// CreateRepositoryNotice creates new system notice with type NoticeRepository.
func CreateRepositoryNotice(desc string) error {
return createNotice(x, NoticeRepository, desc)
return CreateNotice(NoticeRepository, desc)
}
// RemoveAllWithNotice removes all directories in given path and
// creates a system notice when error occurs.
func RemoveAllWithNotice(title, path string) {
removeAllWithNotice(x, title, path)
}
func removeAllWithNotice(e Engine, title, path string) {
var err error
// workaround for Go not being able to remove read-only files/folders: https://github.com/golang/go/issues/9606
// this bug should be fixed on Go 1.7, so the workaround should be removed when Gogs don't support Go 1.6 anymore:
@@ -94,7 +91,7 @@ func removeAllWithNotice(e Engine, title, path string) {
if err != nil {
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
log.Warn(desc)
if err = createNotice(e, NoticeRepository, desc); err != nil {
if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "CreateRepositoryNotice: %v", err)
}
}
@@ -106,7 +103,7 @@ func CountNotices() int64 {
return count
}
// Notices returns notices in given page.
// Notices returns number of notices in given page.
func Notices(page, pageSize int) ([]*Notice, error) {
notices := make([]*Notice, 0, pageSize)
return notices, x.

View File

@@ -1,111 +0,0 @@
// Copyright 2017 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 TestNotice_TrStr(t *testing.T) {
notice := &Notice{
Type: NoticeRepository,
Description: "test description",
}
assert.Equal(t, "admin.notices.type_1", notice.TrStr())
}
func TestCreateNotice(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
noticeBean := &Notice{
Type: NoticeRepository,
Description: "test description",
}
AssertNotExistsBean(t, noticeBean)
assert.NoError(t, CreateNotice(noticeBean.Type, noticeBean.Description))
AssertExistsAndLoadBean(t, noticeBean)
}
func TestCreateRepositoryNotice(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
noticeBean := &Notice{
Type: NoticeRepository,
Description: "test description",
}
AssertNotExistsBean(t, noticeBean)
assert.NoError(t, CreateRepositoryNotice(noticeBean.Description))
AssertExistsAndLoadBean(t, noticeBean)
}
// TODO TestRemoveAllWithNotice
func TestCountNotices(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.Equal(t, int64(3), CountNotices())
}
func TestNotices(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
notices, err := Notices(1, 2)
assert.NoError(t, err)
assert.Len(t, notices, 2)
assert.Equal(t, int64(3), notices[0].ID)
assert.Equal(t, int64(2), notices[1].ID)
notices, err = Notices(2, 2)
assert.NoError(t, err)
assert.Len(t, notices, 1)
assert.Equal(t, int64(1), notices[0].ID)
}
func TestDeleteNotice(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
AssertExistsAndLoadBean(t, &Notice{ID: 3})
assert.NoError(t, DeleteNotice(3))
AssertNotExistsBean(t, &Notice{ID: 3})
}
func TestDeleteNotices(t *testing.T) {
// delete a non-empty range
assert.NoError(t, PrepareTestDatabase())
AssertExistsAndLoadBean(t, &Notice{ID: 1})
AssertExistsAndLoadBean(t, &Notice{ID: 2})
AssertExistsAndLoadBean(t, &Notice{ID: 3})
assert.NoError(t, DeleteNotices(1, 2))
AssertNotExistsBean(t, &Notice{ID: 1})
AssertNotExistsBean(t, &Notice{ID: 2})
AssertExistsAndLoadBean(t, &Notice{ID: 3})
}
func TestDeleteNotices2(t *testing.T) {
// delete an empty range
assert.NoError(t, PrepareTestDatabase())
AssertExistsAndLoadBean(t, &Notice{ID: 1})
AssertExistsAndLoadBean(t, &Notice{ID: 2})
AssertExistsAndLoadBean(t, &Notice{ID: 3})
assert.NoError(t, DeleteNotices(3, 2))
AssertExistsAndLoadBean(t, &Notice{ID: 1})
AssertExistsAndLoadBean(t, &Notice{ID: 2})
AssertExistsAndLoadBean(t, &Notice{ID: 3})
}
func TestDeleteNoticesByIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
AssertExistsAndLoadBean(t, &Notice{ID: 1})
AssertExistsAndLoadBean(t, &Notice{ID: 2})
AssertExistsAndLoadBean(t, &Notice{ID: 3})
assert.NoError(t, DeleteNoticesByIDs([]int64{1, 3}))
AssertNotExistsBean(t, &Notice{ID: 1})
AssertExistsAndLoadBean(t, &Notice{ID: 2})
AssertNotExistsBean(t, &Notice{ID: 3})
}

View File

@@ -1,175 +0,0 @@
// Copyright 2017 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"
"io"
"mime/multipart"
"os"
"path"
"time"
"github.com/go-xorm/xorm"
gouuid "github.com/satori/go.uuid"
"code.gitea.io/gitea/modules/setting"
)
// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
ID int64 `xorm:"pk autoincr"`
UUID string `xorm:"uuid UNIQUE"`
IssueID int64 `xorm:"INDEX"`
CommentID int64
ReleaseID int64 `xorm:"INDEX"`
Name string
Created time.Time `xorm:"-"`
CreatedUnix int64
}
// BeforeInsert is invoked from XORM before inserting an object of this type.
func (a *Attachment) BeforeInsert() {
a.CreatedUnix = time.Now().Unix()
}
// AfterSet is invoked from XORM after setting the value of a field of
// this object.
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
a.Created = time.Unix(a.CreatedUnix, 0).Local()
}
}
// AttachmentLocalPath returns where attachment is stored in local file
// system based on given UUID.
func AttachmentLocalPath(uuid string) string {
return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
}
// LocalPath returns where attachment is stored in local file system.
func (a *Attachment) LocalPath() string {
return AttachmentLocalPath(a.UUID)
}
// NewAttachment creates a new attachment object.
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
attach := &Attachment{
UUID: gouuid.NewV4().String(),
Name: name,
}
localPath := attach.LocalPath()
if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
return nil, fmt.Errorf("MkdirAll: %v", err)
}
fw, err := os.Create(localPath)
if err != nil {
return nil, fmt.Errorf("Create: %v", err)
}
defer fw.Close()
if _, err = fw.Write(buf); err != nil {
return nil, fmt.Errorf("Write: %v", err)
} else if _, err = io.Copy(fw, file); err != nil {
return nil, fmt.Errorf("Copy: %v", err)
}
if _, err := x.Insert(attach); err != nil {
return nil, err
}
return attach, nil
}
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
attach := &Attachment{UUID: uuid}
has, err := e.Get(attach)
if err != nil {
return nil, err
} else if !has {
return nil, ErrAttachmentNotExist{0, uuid}
}
return attach, nil
}
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) {
if len(uuids) == 0 {
return []*Attachment{}, nil
}
// Silently drop invalid uuids.
attachments := make([]*Attachment, 0, len(uuids))
return attachments, e.In("uuid", uuids).Find(&attachments)
}
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
return getAttachmentByUUID(x, uuid)
}
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
}
// GetAttachmentsByIssueID returns all attachments of an issue.
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
return getAttachmentsByIssueID(x, issueID)
}
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
return attachments, x.Where("comment_id=?", commentID).Find(&attachments)
}
// DeleteAttachment deletes the given attachment and optionally the associated file.
func DeleteAttachment(a *Attachment, remove bool) error {
_, err := DeleteAttachments([]*Attachment{a}, remove)
return err
}
// DeleteAttachments deletes the given attachments and optionally the associated files.
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
for i, a := range attachments {
if remove {
if err := os.Remove(a.LocalPath()); err != nil {
return i, err
}
}
if _, err := x.Delete(a); err != nil {
return i, err
}
}
return len(attachments), nil
}
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) {
attachments, err := GetAttachmentsByIssueID(issueID)
if err != nil {
return 0, err
}
return DeleteAttachments(attachments, remove)
}
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) {
attachments, err := GetAttachmentsByCommentID(commentID)
if err != nil {
return 0, err
}
return DeleteAttachments(attachments, remove)
}

View File

@@ -1,156 +0,0 @@
// Copyright 2016 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"
"strings"
"time"
)
const (
// ProtectedBranchRepoID protected Repo ID
ProtectedBranchRepoID = "GITEA_REPO_ID"
)
// ProtectedBranch struct
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
}
// BeforeInsert before protected branch insert create and update time
func (protectBranch *ProtectedBranch) BeforeInsert() {
protectBranch.CreatedUnix = time.Now().Unix()
protectBranch.UpdatedUnix = protectBranch.CreatedUnix
}
// BeforeUpdate before protected branch update time
func (protectBranch *ProtectedBranch) BeforeUpdate() {
protectBranch.UpdatedUnix = time.Now().Unix()
}
// GetProtectedBranchByRepoID getting protected branch by repo ID
func GetProtectedBranchByRepoID(RepoID int64) ([]*ProtectedBranch, error) {
protectedBranches := make([]*ProtectedBranch, 0)
return protectedBranches, x.Where("repo_id = ?", RepoID).Desc("updated_unix").Find(&protectedBranches)
}
// GetProtectedBranchBy getting protected branch by ID/Name
func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, error) {
rel := &ProtectedBranch{RepoID: repoID, BranchName: strings.ToLower(BranchName)}
has, err := x.Get(rel)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return rel, nil
}
// GetProtectedBranches get all protected btanches
func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
protectedBranches := make([]*ProtectedBranch, 0)
return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})
}
// AddProtectedBranch add protection to branch
func (repo *Repository) AddProtectedBranch(branchName string, canPush bool) error {
protectedBranch := &ProtectedBranch{
RepoID: repo.ID,
BranchName: branchName,
}
has, err := x.Get(protectedBranch)
if err != nil {
return err
} else if has {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
protectedBranch.CanPush = canPush
if _, err = sess.InsertOne(protectedBranch); err != nil {
return err
}
return sess.Commit()
}
// ChangeProtectedBranch access mode sets new access mode for the ProtectedBranch.
func (repo *Repository) ChangeProtectedBranch(id int64, canPush bool) error {
ProtectedBranch := &ProtectedBranch{
RepoID: repo.ID,
ID: id,
}
has, err := x.Get(ProtectedBranch)
if err != nil {
return fmt.Errorf("get ProtectedBranch: %v", err)
} else if !has {
return nil
}
if ProtectedBranch.CanPush == canPush {
return nil
}
ProtectedBranch.CanPush = canPush
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Id(ProtectedBranch.ID).AllCols().Update(ProtectedBranch); err != nil {
return fmt.Errorf("update ProtectedBranch: %v", err)
}
return sess.Commit()
}
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
protectedBranch := &ProtectedBranch{
RepoID: repo.ID,
ID: id,
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if affected, err := sess.Delete(protectedBranch); err != nil {
return err
} else if affected != 1 {
return fmt.Errorf("delete protected branch ID(%v) failed", id)
}
return sess.Commit()
}
// newProtectedBranch insert one queue
func newProtectedBranch(protectedBranch *ProtectedBranch) error {
_, err := x.InsertOne(protectedBranch)
return err
}
// UpdateProtectedBranch update queue
func UpdateProtectedBranch(protectedBranch *ProtectedBranch) error {
_, err := x.Update(protectedBranch)
return err
}

View File

@@ -1,172 +0,0 @@
// Copyright 2017 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 (
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
// ConsistencyCheckable a type that can be tested for database consistency
type ConsistencyCheckable interface {
CheckForConsistency(t *testing.T)
}
// CheckConsistencyForAll test that the entire database is consistent
func CheckConsistencyForAll(t *testing.T) {
CheckConsistencyFor(t,
&User{},
&Repository{},
&Issue{},
&PullRequest{},
&Milestone{},
&Label{},
&Team{},
&Action{})
}
// CheckConsistencyFor test that all matching database entries are consistent
func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) {
for _, bean := range beansToCheck {
sliceType := reflect.SliceOf(reflect.TypeOf(bean))
sliceValue := reflect.MakeSlice(sliceType, 0, 10)
ptrToSliceValue := reflect.New(sliceType)
ptrToSliceValue.Elem().Set(sliceValue)
assert.NoError(t, x.Where(bean).Find(ptrToSliceValue.Interface()))
sliceValue = ptrToSliceValue.Elem()
for i := 0; i < sliceValue.Len(); i++ {
entity := sliceValue.Index(i).Interface()
checkable, ok := entity.(ConsistencyCheckable)
if !ok {
t.Errorf("Expected %+v (of type %T) to be checkable for consistency",
entity, entity)
} else {
checkable.CheckForConsistency(t)
}
}
}
}
// getCount get the count of database entries matching bean
func getCount(t *testing.T, e Engine, bean interface{}) int64 {
count, err := e.Count(bean)
assert.NoError(t, err)
return count
}
// assertCount test the count of database entries matching bean
func assertCount(t *testing.T, bean interface{}, expected int) {
assert.EqualValues(t, expected, getCount(t, x, bean),
"Failed consistency test, the counted bean (of type %T) was %+v", bean, bean)
}
func (user *User) CheckForConsistency(t *testing.T) {
assertCount(t, &Repository{OwnerID: user.ID}, user.NumRepos)
assertCount(t, &Star{UID: user.ID}, user.NumStars)
assertCount(t, &OrgUser{OrgID: user.ID}, user.NumMembers)
assertCount(t, &Team{OrgID: user.ID}, user.NumTeams)
assertCount(t, &Follow{UserID: user.ID}, user.NumFollowing)
assertCount(t, &Follow{FollowID: user.ID}, user.NumFollowers)
if user.Type != UserTypeOrganization {
assert.EqualValues(t, 0, user.NumMembers)
assert.EqualValues(t, 0, user.NumTeams)
}
}
func (repo *Repository) CheckForConsistency(t *testing.T) {
assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches)
assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
if repo.IsFork {
AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID})
}
actual := getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
assert.EqualValues(t, repo.NumIssues, actual,
"Unexpected number of issues for repo %+v", repo)
actual = getCount(t, x.Where("is_pull=? AND is_closed=?", false, true), &Issue{RepoID: repo.ID})
assert.EqualValues(t, repo.NumClosedIssues, actual,
"Unexpected number of closed issues for repo %+v", repo)
actual = getCount(t, x.Where("is_pull=?", true), &Issue{RepoID: repo.ID})
assert.EqualValues(t, repo.NumPulls, actual,
"Unexpected number of pulls for repo %+v", repo)
actual = getCount(t, x.Where("is_pull=? AND is_closed=?", true, true), &Issue{RepoID: repo.ID})
assert.EqualValues(t, repo.NumClosedPulls, actual,
"Unexpected number of closed pulls for repo %+v", repo)
actual = getCount(t, x.Where("is_closed=?", true), &Milestone{RepoID: repo.ID})
assert.EqualValues(t, repo.NumClosedMilestones, actual,
"Unexpected number of closed milestones for repo %+v", repo)
}
func (issue *Issue) CheckForConsistency(t *testing.T) {
actual := getCount(t, x.Where("type=?", CommentTypeComment), &Comment{IssueID: issue.ID})
assert.EqualValues(t, issue.NumComments, actual,
"Unexpected number of comments for issue %+v", issue)
if issue.IsPull {
pr := AssertExistsAndLoadBean(t, &PullRequest{IssueID: issue.ID}).(*PullRequest)
assert.EqualValues(t, pr.Index, issue.Index)
}
}
func (pr *PullRequest) CheckForConsistency(t *testing.T) {
issue := AssertExistsAndLoadBean(t, &Issue{ID: pr.IssueID}).(*Issue)
assert.True(t, issue.IsPull)
assert.EqualValues(t, issue.Index, pr.Index)
}
func (milestone *Milestone) CheckForConsistency(t *testing.T) {
assertCount(t, &Issue{MilestoneID: milestone.ID}, milestone.NumIssues)
actual := getCount(t, x.Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID})
assert.EqualValues(t, milestone.NumClosedIssues, actual,
"Unexpected number of closed issues for milestone %+v", milestone)
}
func (label *Label) CheckForConsistency(t *testing.T) {
issueLabels := make([]*IssueLabel, 0, 10)
assert.NoError(t, x.Find(&issueLabels, &IssueLabel{LabelID: label.ID}))
assert.EqualValues(t, label.NumIssues, len(issueLabels),
"Unexpected number of issue for label %+v", label)
issueIDs := make([]int64, len(issueLabels))
for i, issueLabel := range issueLabels {
issueIDs[i] = issueLabel.IssueID
}
expected := int64(0)
if len(issueIDs) > 0 {
expected = getCount(t, x.In("id", issueIDs).Where("is_closed=?", true), &Issue{})
}
assert.EqualValues(t, expected, label.NumClosedIssues,
"Unexpected number of closed issues for label %+v", label)
}
func (team *Team) CheckForConsistency(t *testing.T) {
assertCount(t, &TeamUser{TeamID: team.ID}, team.NumMembers)
assertCount(t, &TeamRepo{TeamID: team.ID}, team.NumRepos)
}
func (action *Action) CheckForConsistency(t *testing.T) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository)
owner := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
actor := AssertExistsAndLoadBean(t, &User{ID: action.ActUserID}).(*User)
assert.Equal(t, repo.Name, action.RepoName, "action: %+v", action)
assert.Equal(t, repo.IsPrivate, action.IsPrivate, "action: %+v", action)
assert.Equal(t, owner.Name, action.RepoUserName, "action: %+v", action)
assert.Equal(t, actor.Name, action.ActUserName, "action: %+v", action)
}

View File

@@ -123,20 +123,6 @@ func (err ErrUserHasOrgs) Error() string {
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
}
// ErrUserNotAllowedCreateOrg represents a "UserNotAllowedCreateOrg" kind of error.
type ErrUserNotAllowedCreateOrg struct {
}
// IsErrUserNotAllowedCreateOrg checks if an error is an ErrUserNotAllowedCreateOrg.
func IsErrUserNotAllowedCreateOrg(err error) bool {
_, ok := err.(ErrUserNotAllowedCreateOrg)
return ok
}
func (err ErrUserNotAllowedCreateOrg) Error() string {
return fmt.Sprintf("user is not allowed to create organizations")
}
// ErrReachLimitOfRepo represents a "ReachLimitOfRepo" kind of error.
type ErrReachLimitOfRepo struct {
Limit int
@@ -213,9 +199,8 @@ func (err ErrKeyNotExist) Error() string {
// ErrKeyAlreadyExist represents a "KeyAlreadyExist" kind of error.
type ErrKeyAlreadyExist struct {
OwnerID int64
Fingerprint string
Content string
OwnerID int64
Content string
}
// IsErrKeyAlreadyExist checks if an error is a ErrKeyAlreadyExist.
@@ -225,8 +210,7 @@ func IsErrKeyAlreadyExist(err error) bool {
}
func (err ErrKeyAlreadyExist) Error() string {
return fmt.Sprintf("public key already exists [owner_id: %d, finter_print: %s, content: %s]",
err.OwnerID, err.Fingerprint, err.Content)
return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
}
// ErrKeyNameAlreadyUsed represents a "KeyNameAlreadyUsed" kind of error.
@@ -410,22 +394,6 @@ func (err ErrRepoAlreadyExist) Error() string {
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
}
// ErrRepoRedirectNotExist represents a "RepoRedirectNotExist" kind of error.
type ErrRepoRedirectNotExist struct {
OwnerID int64
RepoName string
}
// IsErrRepoRedirectNotExist check if an error is an ErrRepoRedirectNotExist
func IsErrRepoRedirectNotExist(err error) bool {
_, ok := err.(ErrRepoRedirectNotExist)
return ok
}
func (err ErrRepoRedirectNotExist) Error() string {
return fmt.Sprintf("repository redirect does not exist [uid: %d, name: %s]", err.OwnerID, err.RepoName)
}
// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
type ErrInvalidCloneAddr struct {
IsURLError bool
@@ -601,7 +569,7 @@ type ErrPullRequestNotExist struct {
IssueID int64
HeadRepoID int64
BaseRepoID int64
HeadBranch string
HeadBarcnh string
BaseBranch string
}
@@ -613,7 +581,7 @@ func IsErrPullRequestNotExist(err error) bool {
func (err ErrPullRequestNotExist) Error() string {
return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
}
// ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error
@@ -805,25 +773,6 @@ func (err ErrTeamAlreadyExist) Error() string {
return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
}
//
// Two-factor authentication
//
// ErrTwoFactorNotEnrolled indicates that a user is not enrolled in two-factor authentication.
type ErrTwoFactorNotEnrolled struct {
UID int64
}
// IsErrTwoFactorNotEnrolled checks if an error is a ErrTwoFactorNotEnrolled.
func IsErrTwoFactorNotEnrolled(err error) bool {
_, ok := err.(ErrTwoFactorNotEnrolled)
return ok
}
func (err ErrTwoFactorNotEnrolled) Error() string {
return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
}
// ____ ___ .__ .___
// | | \______ | | _________ __| _/
// | | /\____ \| | / _ \__ \ / __ |
@@ -847,43 +796,3 @@ func IsErrUploadNotExist(err error) bool {
func (err ErrUploadNotExist) Error() string {
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
}
// ___________ __ .__ .____ .__ ____ ___
// \_ _____/__ ____/ |_ ___________ ____ _____ | | | | ____ ____ |__| ____ | | \______ ___________
// | __)_\ \/ /\ __\/ __ \_ __ \/ \\__ \ | | | | / _ \ / ___\| |/ \ | | / ___// __ \_ __ \
// | \> < | | \ ___/| | \/ | \/ __ \| |__ | |__( <_> ) /_/ > | | \ | | /\___ \\ ___/| | \/
// /_______ /__/\_ \ |__| \___ >__| |___| (____ /____/ |_______ \____/\___ /|__|___| / |______//____ >\___ >__|
// \/ \/ \/ \/ \/ \/ /_____/ \/ \/ \/
// ErrExternalLoginUserAlreadyExist represents a "ExternalLoginUserAlreadyExist" kind of error.
type ErrExternalLoginUserAlreadyExist struct {
ExternalID string
UserID int64
LoginSourceID int64
}
// IsErrExternalLoginUserAlreadyExist checks if an error is a ExternalLoginUserAlreadyExist.
func IsErrExternalLoginUserAlreadyExist(err error) bool {
_, ok := err.(ErrExternalLoginUserAlreadyExist)
return ok
}
func (err ErrExternalLoginUserAlreadyExist) Error() string {
return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
}
// ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
type ErrExternalLoginUserNotExist struct {
UserID int64
LoginSourceID int64
}
// IsErrExternalLoginUserNotExist checks if an error is a ExternalLoginUserNotExist.
func IsErrExternalLoginUserNotExist(err error) bool {
_, ok := err.(ErrExternalLoginUserNotExist)
return ok
}
func (err ErrExternalLoginUserNotExist) Error() string {
return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
}

View File

@@ -1,74 +0,0 @@
// Copyright 2017 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 "github.com/markbates/goth"
// ExternalLoginUser makes the connecting between some existing user and additional external login sources
type ExternalLoginUser struct {
ExternalID string `xorm:"NOT NULL"`
UserID int64 `xorm:"NOT NULL"`
LoginSourceID int64 `xorm:"NOT NULL"`
}
// GetExternalLogin checks if a externalID in loginSourceID scope already exists
func GetExternalLogin(externalLoginUser *ExternalLoginUser) (bool, error) {
return x.Get(externalLoginUser)
}
// ListAccountLinks returns a map with the ExternalLoginUser and its LoginSource
func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) {
externalAccounts := make([]*ExternalLoginUser, 0, 5)
err := x.Where("user_id=?", user.ID).
Desc("login_source_id").
Find(&externalAccounts)
if err != nil {
return nil, err
}
return externalAccounts, nil
}
// LinkAccountToUser link the gothUser to the user
func LinkAccountToUser(user *User, gothUser goth.User) error {
loginSource, err := GetActiveOAuth2LoginSourceByName(gothUser.Provider)
if err != nil {
return err
}
externalLoginUser := &ExternalLoginUser{
ExternalID: gothUser.UserID,
UserID: user.ID,
LoginSourceID: loginSource.ID,
}
has, err := x.Get(externalLoginUser)
if err != nil {
return err
} else if has {
return ErrExternalLoginUserAlreadyExist{gothUser.UserID, user.ID, loginSource.ID}
}
_, err = x.Insert(externalLoginUser)
return err
}
// RemoveAccountLink will remove all external login sources for the given user
func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) {
deleted, err := x.Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID})
if err != nil {
return deleted, err
}
if deleted < 1 {
return deleted, ErrExternalLoginUserNotExist{user.ID, loginSourceID}
}
return deleted, err
}
// removeAllAccountLinks will remove all external login sources for the given user
func removeAllAccountLinks(e Engine, user *User) error {
_, err := e.Delete(&ExternalLoginUser{UserID: user.ID})
return err
}

View File

@@ -1,11 +0,0 @@
-
id: 1
user_id: 2
repo_id: 3
mode: 2 # write
-
id: 2
user_id: 4
repo_id: 4
mode: 2 # write

View File

@@ -1,23 +0,0 @@
-
id: 1
uid: 1
name: Token A
sha1: hash1
created_unix: 946687980
updated_unix: 946687980
-
id: 2
uid: 1
name: Token B
sha1: hash2
created_unix: 946687980
updated_unix: 946687980
-
id: 3
uid: 2
name: Token A
sha1: hash3
created_unix: 946687980
updated_unix: 946687980

View File

@@ -1,33 +0,0 @@
-
id: 1
user_id: 2
op_type: 12 # close issue
act_user_id: 2
act_user_name: user2
repo_id: 2
repo_user_name: user2
repo_name: repo2
is_private: true
-
id: 2
user_id: 3
op_type: 2 # rename repo
act_user_id: 3
act_user_name: user3
repo_id: 3
repo_user_name: user3
repo_name: repo3
is_private: true
content: oldRepoName
-
id: 3
user_id: 11
op_type: 1 # create repo
act_user_id: 11
act_user_name: user11
repo_id: 9
repo_user_name: user11
repo_name: repo9
is_private: false

View File

@@ -1,11 +0,0 @@
-
id: 1
repo_id: 3
user_id: 2
mode: 2 # write
-
id: 2
repo_id: 4
user_id: 4
mode: 2 # write

View File

@@ -1,7 +0,0 @@
-
id: 1
type: 7 # label
poster_id: 2
issue_id: 1
label_id: 1
content: "1"

View File

@@ -1,35 +0,0 @@
-
id: 1
uid: 1
email: user11@example.com
is_activated: false
-
id: 2
uid: 1
email: user12@example.com
is_activated: false
-
id: 3
uid: 2
email: user2@example.com
is_activated: true
-
id: 4
uid: 2
email: user21@example.com
is_activated: false
-
id: 5
uid: 9999999
email: user9999999@example.com
is_activated: true
-
id: 6
uid: 10
email: user101@example.com
is_activated: true

View File

@@ -1,5 +0,0 @@
-
id: 1
repo_id: 1
hook_id: 1
uuid: uuid1

View File

@@ -1,59 +0,0 @@
-
id: 1
repo_id: 1
index: 1
poster_id: 1
assignee_id: 1
name: issue1
content: content1
is_closed: false
is_pull: false
num_comments: 0
created_unix: 946684800
updated_unix: 978307200
-
id: 2
repo_id: 1
index: 2
poster_id: 1
name: issue2
content: content2
milestone_id: 1
is_closed: false
is_pull: true
created_unix: 946684810
updated_unix: 978307190
-
id: 3
repo_id: 1
index: 3
poster_id: 1
name: issue3
content: content4
is_closed: false
is_pull: true
created_unix: 946684820
updated_unix: 978307180
-
id: 4
repo_id: 2
index: 1
poster_id: 2
name: issue4
content: content4
is_closed: true
is_pull: false
-
id: 5
repo_id: 1
index: 4
poster_id: 2
name: issue5
content: content5
is_closed: true
is_pull: false

View File

@@ -1,14 +0,0 @@
-
id: 1
issue_id: 1
label_id: 1
-
id: 2
issue_id: 5
label_id: 2
-
id: 3
issue_id: 2
label_id: 1

View File

@@ -1,23 +0,0 @@
-
id: 1
uid: 1
issue_id: 1
is_read: true
is_assigned: true
is_mentioned: false
-
id: 2
uid: 2
issue_id: 1
is_read: true
is_assigned: false
is_mentioned: false
-
id: 3
uid: 4
issue_id: 1
is_read: false
is_assigned: false
is_mentioned: false

View File

@@ -1,15 +0,0 @@
-
id: 1
repo_id: 1
name: label1
color: '#abcdef'
num_issues: 2
num_closed_issues: 0
-
id: 2
repo_id: 1
name: label2
color: '#000000'
num_issues: 1
num_closed_issues: 1

View File

@@ -1,15 +0,0 @@
-
id: 1
repo_id: 1
name: milestone1
content: content1
is_closed: false
num_issues: 1
-
id: 2
repo_id: 1
name: milestone2
content: content2
is_closed: false
num_issues: 0

View File

@@ -1,14 +0,0 @@
-
id: 1
type: 1 # NoticeRepository
description: description1
-
id: 2
type: 1 # NoticeRepository
description: description2
-
id: 3
type: 1 # NoticeRepository
description: description3

View File

@@ -1,21 +0,0 @@
-
id: 1
user_id: 1
repo_id: 1
status: 1 # unread
source: 1 # issue
updated_by: 2
issue_id: 1
created_unix: 946684800
updated_unix: 946684800
-
id: 2
user_id: 2
repo_id: 1
status: 2 # read
source: 1 # issue
updated_by: 1
issue_id: 2
created_unix: 946684800
updated_unix: 946684800

View File

@@ -1,31 +0,0 @@
-
id: 1
uid: 2
org_id: 3
is_public: true
is_owner: true
num_teams: 1
-
id: 2
uid: 4
org_id: 3
is_public: false
is_owner: false
num_teams: 0
-
id: 3
uid: 5
org_id: 6
is_public: true
is_owner: true
num_teams: 1
-
id: 4
uid: 5
org_id: 7
is_public: false
is_owner: true
num_teams: 1

View File

@@ -1,28 +0,0 @@
-
id: 1
type: 0 # gitea pull request
status: 2 # mergable
issue_id: 2
index: 2
head_repo_id: 1
base_repo_id: 1
head_user_name: user1
head_branch: branch1
base_branch: master
merge_base: 1234567890abcdef
has_merged: true
merger_id: 2
-
id: 2
type: 0 # gitea pull request
status: 1 # checking
issue_id: 3
index: 3
head_repo_id: 1
base_repo_id: 1
head_user_name: user1
head_branch: branch2
base_branch: master
merge_base: fedcba9876543210
has_merged: false

View File

@@ -1,5 +0,0 @@
-
id: 1
owner_id: 2
lower_name: oldrepo1
redirect_repo_id: 1

View File

@@ -1,171 +0,0 @@
-
id: 1
owner_id: 2
lower_name: repo1
name: repo1
is_private: false
num_issues: 2
num_closed_issues: 1
num_pulls: 2
num_closed_pulls: 0
num_milestones: 2
num_watches: 2
-
id: 2
owner_id: 2
lower_name: repo2
name: repo2
is_private: true
num_issues: 1
num_closed_issues: 1
num_pulls: 0
num_closed_pulls: 0
num_stars: 1
-
id: 3
owner_id: 3
lower_name: repo3
name: repo3
is_private: true
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
num_watches: 0
-
id: 4
owner_id: 5
lower_name: repo4
name: repo4
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
num_stars: 1
-
id: 5
owner_id: 3
lower_name: repo5
name: repo5
is_private: true
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
num_watches: 0
is_mirror: true
-
id: 6
owner_id: 10
lower_name: repo6
name: repo6
is_private: true
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 7
owner_id: 10
lower_name: repo7
name: repo7
is_private: true
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 8
owner_id: 10
lower_name: repo8
name: repo8
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 9
owner_id: 11
lower_name: repo9
name: repo9
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 10
owner_id: 12
lower_name: repo10
name: repo10
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
num_forks: 1
-
id: 11
fork_id: 10
owner_id: 13
lower_name: repo11
name: repo11
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 12
owner_id: 14
lower_name: test_repo_12
name: test_repo_12
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 13
owner_id: 14
lower_name: test_repo_13
name: test_repo_13
is_private: true
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false
-
id: 14
owner_id: 14
lower_name: test_repo_14
name: test_repo_14
is_private: false
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
is_mirror: false

View File

@@ -1,9 +0,0 @@
-
id: 1
uid: 2
repo_id: 2
-
id: 2
uid: 2
repo_id: 4

View File

@@ -1,35 +0,0 @@
-
id: 1
org_id: 3
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 2
num_members: 1
-
id: 2
org_id: 3
lower_name: team1
name: team1
authorize: 2 # write
num_repos: 1
num_members: 2
-
id: 3
org_id: 6
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 0
num_members: 1
-
id: 4
org_id: 7
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 0
num_members: 1

View File

@@ -1,17 +0,0 @@
-
id: 1
org_id: 3
team_id: 1
repo_id: 3
-
id: 2
org_id: 3
team_id: 2
repo_id: 3
-
id: 3
org_id: 3
team_id: 1
repo_id: 5

View File

@@ -1,29 +0,0 @@
-
id: 1
org_id: 3
team_id: 1
uid: 2
-
id: 2
org_id: 3
team_id: 2
uid: 2
-
id: 3
org_id: 3
team_id: 2
uid: 4
-
id: 4
org_id: 6
team_id: 3
uid: 5
-
id: 5
org_id: 7
team_id: 4
uid: 5

View File

@@ -1,211 +0,0 @@
- # NOTE: this user (id=1) is the admin
id: 1
lower_name: user1
name: user1
full_name: User One
email: user1@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: true
avatar: avatar1
avatar_email: user1@example.com
num_repos: 0
-
id: 2
lower_name: user2
name: user2
full_name: User Two
email: user2@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar2
avatar_email: user2@example.com
num_repos: 2
num_stars: 2
-
id: 3
lower_name: user3
name: user3
full_name: User Three
email: user3@example.com
passwd: password
type: 1 # organization
salt: salt
is_admin: false
avatar: avatar3
avatar_email: user3@example.com
num_repos: 2
num_members: 2
num_teams: 2
-
id: 4
lower_name: user4
name: user4
full_name: User Four
email: user4@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar4
avatar_email: user4@example.com
num_repos: 0
-
id: 5
lower_name: user5
name: user5
full_name: User Five
email: user5@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar5
avatar_email: user5@example.com
num_repos: 1
allow_create_organization: false
is_active: true
-
id: 6
lower_name: user6
name: user6
full_name: User Six
email: user6@example.com
passwd: password
type: 1 # organization
salt: salt
is_admin: false
avatar: avatar6
avatar_email: user6@example.com
num_repos: 0
num_members: 1
num_teams: 1
-
id: 7
lower_name: user7
name: user7
full_name: User Seven
email: user7@example.com
passwd: password
type: 1 # organization
salt: salt
is_admin: false
avatar: avatar7
avatar_email: user7@example.com
num_repos: 0
num_members: 1
num_teams: 1
-
id: 8
lower_name: user8
name: user8
full_name: User Eight
email: user8@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar8
avatar_email: user8@example.com
num_repos: 0
is_active: true
-
id: 9
lower_name: user9
name: user9
full_name: User Nine
email: user9@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar9
avatar_email: user9@example.com
num_repos: 0
is_active: false
-
id: 10
lower_name: user10
name: user10
full_name: User Ten
email: user10@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar10
avatar_email: user10@example.com
num_repos: 3
is_active: true
-
id: 11
lower_name: user11
name: user11
full_name: User Eleven
email: user11@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar11
avatar_email: user11@example.com
num_repos: 1
is_active: true
-
id: 12
lower_name: user12
name: user12
full_name: User 12
email: user12@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar12
avatar_email: user12@example.com
num_repos: 1
is_active: true
-
id: 13
lower_name: user13
name: user13
full_name: User 13
email: user13@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar13
avatar_email: user13@example.com
num_repos: 1
is_active: true
-
id: 14
lower_name: user14
name: user14
full_name: User 14
email: user14@example.com
passwd: password
type: 0 # individual
salt: salt
is_admin: false
avatar: avatar14
avatar_email: user13@example.com
num_repos: 3
is_active: true

View File

@@ -1,9 +0,0 @@
-
id: 1
user_id: 1
repo_id: 1
-
id: 2
user_id: 4
repo_id: 1

View File

@@ -1,24 +0,0 @@
-
id: 1
repo_id: 1
url: www.example.com/url1
content_type: 1 # json
events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}'
is_active: true
-
id: 2
repo_id: 1
url: www.example.com/url2
content_type: 1 # json
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
is_active: false
-
id: 3
org_id: 3
repo_id: 3
url: www.example.com/url3
content_type: 1 # json
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
is_active: true

View File

@@ -78,7 +78,7 @@ var (
func diffToHTML(diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
buf := bytes.NewBuffer(nil)
// Reproduce signs which are cut for inline diff before.
// Reproduce signs which are cutted for inline diff before.
switch lineType {
case DiffLineAdd:
buf.WriteByte('+')
@@ -200,7 +200,6 @@ type DiffFile struct {
IsCreated bool
IsDeleted bool
IsBin bool
IsLFSFile bool
IsRenamed bool
IsSubmodule bool
Sections []*DiffSection
@@ -234,7 +233,7 @@ const cmdDiffHead = "diff --git "
// ParsePatch builds a Diff object from a io.Reader and some
// parameters.
// TODO: move this function to gogits/git-module
func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*Diff, error) {
func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) {
var (
diff = &Diff{Files: make([]*DiffFile, 0)}
@@ -246,7 +245,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
leftLine, rightLine int
lineCount int
curFileLinesCount int
curFileLFSPrefix bool
)
input := bufio.NewReader(reader)
@@ -270,33 +268,11 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
continue
}
trimLine := strings.Trim(line, "+- ")
if trimLine == LFSMetaFileIdentifier {
curFileLFSPrefix = true
}
if curFileLFSPrefix && strings.HasPrefix(trimLine, LFSMetaFileOidPrefix) {
oid := strings.TrimPrefix(trimLine, LFSMetaFileOidPrefix)
if len(oid) == 64 {
m := &LFSMetaObject{Oid: oid}
count, err := x.Count(m)
if err == nil && count > 0 {
curFile.IsBin = true
curFile.IsLFSFile = true
curSection.Lines = nil
break
}
}
}
curFileLinesCount++
lineCount++
// Diff data too large, we only show the first about maxLines lines
if curFileLinesCount >= maxLines || len(line) >= maxLineCharacters {
// Diff data too large, we only show the first about maxlines lines
if curFileLinesCount >= maxLines || len(line) >= maxLineCharacteres {
curFile.IsIncomplete = true
}
@@ -366,12 +342,10 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
}
curFile = &DiffFile{
Name: b,
OldName: a,
Index: len(diff.Files) + 1,
Type: DiffFileChange,
Sections: make([]*DiffSection, 0, 10),
IsRenamed: a != b,
Name: a,
Index: len(diff.Files) + 1,
Type: DiffFileChange,
Sections: make([]*DiffSection, 0, 10),
}
diff.Files = append(diff.Files, curFile)
if len(diff.Files) >= maxFiles {
@@ -380,7 +354,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
break
}
curFileLinesCount = 0
curFileLFSPrefix = false
// Check file diff type and is submodule.
for {
@@ -404,6 +377,9 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
curFile.Type = DiffFileChange
case strings.HasPrefix(line, "similarity index 100%"):
curFile.Type = DiffFileRename
curFile.IsRenamed = true
curFile.OldName = curFile.Name
curFile.Name = b
}
if curFile.Type > 0 {
if strings.HasSuffix(line, " 160000\n") {
@@ -446,7 +422,7 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
// GetDiffRange builds a Diff between two commits of a repository.
// passing the empty string as beforeCommitID returns a diff from the
// parent commit.
func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
return nil, err
@@ -482,10 +458,10 @@ func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxL
return nil, fmt.Errorf("Start: %v", err)
}
pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cmd)
defer process.GetManager().Remove(pid)
pid := process.Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cmd)
defer process.Remove(pid)
diff, err := ParsePatch(maxLines, maxLineCharacters, maxFiles, stdout)
diff, err := ParsePatch(maxLines, maxLineCharacteres, maxFiles, stdout)
if err != nil {
return nil, fmt.Errorf("ParsePatch: %v", err)
}
@@ -553,6 +529,6 @@ func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Write
}
// GetDiffCommit builds a Diff representing the given commitID.
func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles)
func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacteres, maxFiles)
}

View File

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

View File

@@ -1,108 +0,0 @@
// Copyright 2016 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"
"strings"
"code.gitea.io/git"
)
// GraphItem represent one commit, or one relation in timeline
type GraphItem struct {
GraphAcii string
Relation string
Branch string
Rev string
Date string
Author string
AuthorEmail string
ShortRev string
Subject string
OnlyRelation bool
}
// GraphItems is a list of commits from all branches
type GraphItems []GraphItem
// GetCommitGraph return a list of commit (GraphItems) from all branches
func GetCommitGraph(r *git.Repository) (GraphItems, error) {
var CommitGraph []GraphItem
format := "DATA:|%d|%H|%ad|%an|%ae|%h|%s"
graphCmd := git.NewCommand("log")
graphCmd.AddArguments("--graph",
"--date-order",
"--all",
"-C",
"-M",
"-n 100",
"--date=iso",
fmt.Sprintf("--pretty=format:%s", format),
)
graph, err := graphCmd.RunInDir(r.Path)
if err != nil {
return CommitGraph, err
}
CommitGraph = make([]GraphItem, 0, 100)
for _, s := range strings.Split(graph, "\n") {
GraphItem, err := graphItemFromString(s, r)
if err != nil {
return CommitGraph, err
}
CommitGraph = append(CommitGraph, GraphItem)
}
return CommitGraph, nil
}
func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
var ascii string
var data = "|||||||"
lines := strings.Split(s, "DATA:")
switch len(lines) {
case 1:
ascii = lines[0]
case 2:
ascii = lines[0]
data = lines[1]
default:
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s. Expect 1 or two fields", s)
}
rows := strings.SplitN(data, "|", 8)
if len(rows) < 8 {
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s - Should containt 8 datafields", s)
}
/* // see format in getCommitGraph()
0 Relation string
1 Branch string
2 Rev string
3 Date string
4 Author string
5 AuthorEmail string
6 ShortRev string
7 Subject string
*/
gi := GraphItem{ascii,
rows[0],
rows[1],
rows[2],
rows[3],
rows[4],
rows[5],
rows[6],
rows[7],
len(rows[2]) == 0, // no commits referred to, only relation in current line.
}
return gi, nil
}

View File

@@ -1,45 +0,0 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"code.gitea.io/git"
)
func BenchmarkGetCommitGraph(b *testing.B) {
currentRepo, err := git.OpenRepository(".")
if err != nil {
b.Error("Could not open repository")
}
for i := 0; i < b.N; i++ {
graph, err := GetCommitGraph(currentRepo)
if err != nil {
b.Error("Could get commit graph")
}
if len(graph) < 100 {
b.Error("Should get 100 log lines.")
}
}
}
func BenchmarkParseCommitString(b *testing.B) {
testString := "* DATA:||4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Kjell Kvinge|kjell@kvinge.biz|4e61bac|Add route for graph"
for i := 0; i < b.N; i++ {
graphItem, err := graphItemFromString(testString, nil)
if err != nil {
b.Error("could not parse teststring")
}
if graphItem.Author != "Kjell Kvinge" {
b.Error("Did not get expected data")
}
}
}

View File

@@ -1,21 +0,0 @@
// Copyright 2017 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
func keysInt64(m map[int64]struct{}) []int64 {
var keys = make([]int64, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func valuesRepository(m map[int64]*Repository) []*Repository {
var values = make([]*Repository, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}

File diff suppressed because it is too large Load Diff

View File

@@ -36,16 +36,6 @@ const (
CommentTypeCommentRef
// Reference from a pull request
CommentTypePullRef
// Labels changed
CommentTypeLabel
// Milestone changed
CommentTypeMilestone
// Assignees changed
CommentTypeAssignees
// Change Title
CommentTypeChangeTitle
// Delete Branch
CommentTypeDeleteBranch
)
// CommentTag defines comment tag type
@@ -61,33 +51,20 @@ const (
// Comment represents a comment in commit and issue page.
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
LabelID int64
Label *Label `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
OldAssigneeID int64
AssigneeID int64
Assignee *User `xorm:"-"`
OldAssignee *User `xorm:"-"`
OldTitle string
NewTitle string
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
CommitID int64
Line int64
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"INDEX"`
UpdatedUnix int64
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
@@ -208,71 +185,6 @@ func (c *Comment) EventTag() string {
return "event-" + com.ToStr(c.ID)
}
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
func (c *Comment) LoadLabel() error {
var label Label
has, err := x.ID(c.LabelID).Get(&label)
if err != nil {
return err
} else if has {
c.Label = &label
} else {
// Ignore Label is deleted, but not clear this table
log.Warn("Commit %d cannot load label %d", c.ID, c.LabelID)
}
return nil
}
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func (c *Comment) LoadMilestone() error {
if c.OldMilestoneID > 0 {
var oldMilestone Milestone
has, err := x.ID(c.OldMilestoneID).Get(&oldMilestone)
if err != nil {
return err
} else if !has {
return ErrMilestoneNotExist{
ID: c.OldMilestoneID,
}
}
c.OldMilestone = &oldMilestone
}
if c.MilestoneID > 0 {
var milestone Milestone
has, err := x.ID(c.MilestoneID).Get(&milestone)
if err != nil {
return err
} else if !has {
return ErrMilestoneNotExist{
ID: c.MilestoneID,
}
}
c.Milestone = &milestone
}
return nil
}
// LoadAssignees if comment.Type is CommentTypeAssignees, then load assignees
func (c *Comment) LoadAssignees() error {
var err error
if c.OldAssigneeID > 0 {
c.OldAssignee, err = getUserByID(x, c.OldAssigneeID)
if err != nil {
return err
}
}
if c.AssigneeID > 0 {
c.Assignee, err = getUserByID(x, c.AssigneeID)
if err != nil {
return err
}
}
return nil
}
// MailParticipants sends new comment emails to repository watchers
// and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
@@ -297,35 +209,20 @@ func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (e
}
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
var LabelID int64
if opts.Label != nil {
LabelID = opts.Label.ID
}
comment := &Comment{
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
LabelID: LabelID,
OldMilestoneID: opts.OldMilestoneID,
MilestoneID: opts.MilestoneID,
OldAssigneeID: opts.OldAssigneeID,
AssigneeID: opts.AssigneeID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
Type: opts.Type,
PosterID: opts.Doer.ID,
Poster: opts.Doer,
IssueID: opts.Issue.ID,
CommitID: opts.CommitID,
CommitSHA: opts.CommitSHA,
Line: opts.LineNum,
Content: opts.Content,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
}
if err = opts.Repo.getOwner(e); err != nil {
return nil, err
}
// Compose comment action, could be plain comment, close or reopen issue/pull request.
// This object will be used to notify watchers in the end of function.
act := &Action{
@@ -427,83 +324,18 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I
})
}
func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, label *Label, add bool) (*Comment, error) {
var content string
if add {
content = "1"
}
return createComment(e, &CreateCommentOptions{
Type: CommentTypeLabel,
Doer: doer,
Repo: repo,
Issue: issue,
Label: label,
Content: content,
})
}
func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldMilestoneID, milestoneID int64) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeMilestone,
Doer: doer,
Repo: repo,
Issue: issue,
OldMilestoneID: oldMilestoneID,
MilestoneID: milestoneID,
})
}
func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldAssigneeID, assigneeID int64) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeAssignees,
Doer: doer,
Repo: repo,
Issue: issue,
OldAssigneeID: oldAssigneeID,
AssigneeID: assigneeID,
})
}
func createChangeTitleComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldTitle, newTitle string) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeChangeTitle,
Doer: doer,
Repo: repo,
Issue: issue,
OldTitle: oldTitle,
NewTitle: newTitle,
})
}
func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, branchName string) (*Comment, error) {
return createComment(e, &CreateCommentOptions{
Type: CommentTypeDeleteBranch,
Doer: doer,
Repo: repo,
Issue: issue,
CommitSHA: branchName,
})
}
// CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct {
Type CommentType
Doer *User
Repo *Repository
Issue *Issue
Label *Label
OldMilestoneID int64
MilestoneID int64
OldAssigneeID int64
AssigneeID int64
OldTitle string
NewTitle string
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
CommitID int64
CommitSHA string
LineNum int64
Content string
Attachments []string // UUIDs of attachments
}
// CreateComment creates comment of issue or commit.
@@ -588,11 +420,9 @@ func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, erro
func getCommentsByRepoIDSince(e Engine, repoID, since int64) ([]*Comment, error) {
comments := make([]*Comment, 0, 10)
sess := e.Where("issue.repo_id = ?", repoID).
Join("INNER", "issue", "issue.id = comment.issue_id").
Asc("comment.created_unix")
sess := e.Where("issue.repo_id = ?", repoID).Join("INNER", "issue", "issue.id = comment.issue_id", repoID).Asc("created_unix")
if since > 0 {
sess.And("comment.updated_unix >= ?", since)
sess.And("updated_unix >= ?", since)
}
return comments, sess.Find(&comments)
}
@@ -622,22 +452,28 @@ func UpdateComment(c *Comment) error {
return err
}
// DeleteComment deletes the comment
func DeleteComment(comment *Comment) error {
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
// DeleteCommentByID deletes the comment by given ID.
func DeleteCommentByID(id int64) error {
comment, err := GetCommentByID(id)
if err != nil {
if IsErrCommentNotExist(err) {
return nil
}
return err
}
if _, err := sess.Delete(&Comment{
ID: comment.ID,
}); err != nil {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Id(comment.ID).Delete(new(Comment)); err != nil {
return err
}
if comment.Type == CommentTypeComment {
if _, err := sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
return err
}
}

View File

@@ -1,183 +0,0 @@
// Copyright 2017 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"
"os"
"strconv"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/blevesearch/bleve"
"github.com/blevesearch/bleve/analysis/analyzer/simple"
"github.com/blevesearch/bleve/search/query"
)
// issueIndexerUpdateQueue queue of issues that need to be updated in the issues
// indexer
var issueIndexerUpdateQueue chan *Issue
// issueIndexer (thread-safe) index for searching issues
var issueIndexer bleve.Index
// issueIndexerData data stored in the issue indexer
type issueIndexerData struct {
ID int64
RepoID int64
Title string
Content string
}
// numericQuery an numeric-equality query for the given value and field
func numericQuery(value int64, field string) *query.NumericRangeQuery {
f := float64(value)
tru := true
q := bleve.NewNumericRangeInclusiveQuery(&f, &f, &tru, &tru)
q.SetField(field)
return q
}
// SearchIssuesByKeyword searches for issues by given conditions.
// Returns the matching issue IDs
func SearchIssuesByKeyword(repoID int64, keyword string) ([]int64, error) {
terms := strings.Fields(strings.ToLower(keyword))
indexerQuery := bleve.NewConjunctionQuery(
numericQuery(repoID, "RepoID"),
bleve.NewDisjunctionQuery(
bleve.NewPhraseQuery(terms, "Title"),
bleve.NewPhraseQuery(terms, "Content"),
))
search := bleve.NewSearchRequestOptions(indexerQuery, 2147483647, 0, false)
search.Fields = []string{"ID"}
result, err := issueIndexer.Search(search)
if err != nil {
return nil, err
}
issueIDs := make([]int64, len(result.Hits))
for i, hit := range result.Hits {
issueIDs[i] = int64(hit.Fields["ID"].(float64))
}
return issueIDs, nil
}
// InitIssueIndexer initialize issue indexer
func InitIssueIndexer() {
_, err := os.Stat(setting.Indexer.IssuePath)
if err != nil {
if os.IsNotExist(err) {
if err = createIssueIndexer(); err != nil {
log.Fatal(4, "CreateIssuesIndexer: %v", err)
}
if err = populateIssueIndexer(); err != nil {
log.Fatal(4, "PopulateIssuesIndex: %v", err)
}
} else {
log.Fatal(4, "InitIssuesIndexer: %v", err)
}
} else {
issueIndexer, err = bleve.Open(setting.Indexer.IssuePath)
if err != nil {
log.Fatal(4, "InitIssuesIndexer, open index: %v", err)
}
}
issueIndexerUpdateQueue = make(chan *Issue, setting.Indexer.UpdateQueueLength)
go processIssueIndexerUpdateQueue()
// TODO close issueIndexer when Gitea closes
}
// createIssueIndexer create an issue indexer if one does not already exist
func createIssueIndexer() error {
mapping := bleve.NewIndexMapping()
docMapping := bleve.NewDocumentMapping()
docMapping.AddFieldMappingsAt("ID", bleve.NewNumericFieldMapping())
docMapping.AddFieldMappingsAt("RepoID", bleve.NewNumericFieldMapping())
textFieldMapping := bleve.NewTextFieldMapping()
textFieldMapping.Analyzer = simple.Name
docMapping.AddFieldMappingsAt("Title", textFieldMapping)
docMapping.AddFieldMappingsAt("Content", textFieldMapping)
mapping.AddDocumentMapping("issues", docMapping)
var err error
issueIndexer, err = bleve.New(setting.Indexer.IssuePath, mapping)
return err
}
// populateIssueIndexer populate the issue indexer with issue data
func populateIssueIndexer() error {
for page := 1; ; page++ {
repos, _, err := Repositories(&SearchRepoOptions{
Page: page,
PageSize: 10,
})
if err != nil {
return fmt.Errorf("Repositories: %v", err)
}
if len(repos) == 0 {
return nil
}
batch := issueIndexer.NewBatch()
for _, repo := range repos {
issues, err := Issues(&IssuesOptions{
RepoID: repo.ID,
IsClosed: util.OptionalBoolNone,
IsPull: util.OptionalBoolNone,
Page: -1, // do not page
})
if err != nil {
return fmt.Errorf("Issues: %v", err)
}
for _, issue := range issues {
err = batch.Index(issue.indexUID(), issue.issueData())
if err != nil {
return fmt.Errorf("batch.Index: %v", err)
}
}
}
if err = issueIndexer.Batch(batch); err != nil {
return fmt.Errorf("index.Batch: %v", err)
}
}
}
func processIssueIndexerUpdateQueue() {
for {
select {
case issue := <-issueIndexerUpdateQueue:
if err := issueIndexer.Index(issue.indexUID(), issue.issueData()); err != nil {
log.Error(4, "issuesIndexer.Index: %v", err)
}
}
}
}
// indexUID a unique identifier for an issue used in full-text indices
func (issue *Issue) indexUID() string {
return strconv.FormatInt(issue.ID, 36)
}
func (issue *Issue) issueData() *issueIndexerData {
return &issueIndexerData{
ID: issue.ID,
RepoID: issue.RepoID,
Title: issue.Title,
Content: issue.Content,
}
}
// UpdateIssueIndexer add/update an issue to the issue indexer
func UpdateIssueIndexer(issue *Issue) {
go func() {
issueIndexerUpdateQueue <- issue
}()
}

View File

@@ -114,7 +114,7 @@ func getLabelInRepoByName(e Engine, repoID int64, labelName string) (*Label, err
Name: labelName,
RepoID: repoID,
}
has, err := e.Get(l)
has, err := x.Get(l)
if err != nil {
return nil, err
} else if !has {
@@ -135,7 +135,7 @@ func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
ID: labelID,
RepoID: repoID,
}
has, err := e.Get(l)
has, err := x.Get(l)
if err != nil {
return nil, err
} else if !has {
@@ -171,29 +171,32 @@ func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
}
// GetLabelsByRepoID returns all labels that belong to given repository by ID.
func GetLabelsByRepoID(repoID int64, sortType string) ([]*Label, error) {
func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
labels := make([]*Label, 0, 10)
sess := x.Where("repo_id = ?", repoID)
switch sortType {
case "reversealphabetically":
sess.Desc("name")
case "leastissues":
sess.Asc("num_issues")
case "mostissues":
sess.Desc("num_issues")
default:
sess.Asc("name")
}
return labels, sess.Find(&labels)
return labels, x.
Where("repo_id = ?", repoID).
Asc("name").
Find(&labels)
}
func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
var labels []*Label
return labels, e.Where("issue_label.issue_id = ?", issueID).
Join("LEFT", "issue_label", "issue_label.label_id = label.id").
Asc("label.name").
issueLabels, err := getIssueLabels(e, issueID)
if err != nil {
return nil, fmt.Errorf("getIssueLabels: %v", err)
} else if len(issueLabels) == 0 {
return []*Label{}, nil
}
labelIDs := make([]int64, len(issueLabels))
for i := range issueLabels {
labelIDs[i] = issueLabels[i].LabelID
}
labels := make([]*Label, 0, len(labelIDs))
return labels, e.
Where("id > 0").
In("id", labelIDs).
Asc("name").
Find(&labels)
}
@@ -236,11 +239,6 @@ func DeleteLabel(repoID, labelID int64) error {
return err
}
// Clear label id in comment table
if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Update(&Comment{}); err != nil {
return err
}
return sess.Commit()
}
@@ -251,7 +249,7 @@ func DeleteLabel(repoID, labelID int64) error {
// |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
// \/ \/ \/ \/ \/ \/ \/
// IssueLabel represents an issue-label relation.
// IssueLabel represetns an issue-lable relation.
type IssueLabel struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"UNIQUE(s)"`
@@ -268,7 +266,7 @@ func HasIssueLabel(issueID, labelID int64) bool {
return hasIssueLabel(x, issueID, labelID)
}
func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) {
func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
if _, err = e.Insert(&IssueLabel{
IssueID: issue.ID,
LabelID: label.ID,
@@ -276,14 +274,6 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err
return err
}
if err = issue.loadRepo(e); err != nil {
return
}
if _, err = createLabelComment(e, doer, issue.Repo, issue, label, true); err != nil {
return err
}
label.NumIssues++
if issue.IsClosed {
label.NumClosedIssues++
@@ -292,7 +282,7 @@ func newIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err
}
// NewIssueLabel creates a new issue-label relation.
func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
func NewIssueLabel(issue *Issue, label *Label) (err error) {
if HasIssueLabel(issue.ID, label.ID) {
return nil
}
@@ -303,20 +293,20 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
return err
}
if err = newIssueLabel(sess, issue, label, doer); err != nil {
if err = newIssueLabel(sess, issue, label); err != nil {
return err
}
return sess.Commit()
}
func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label, doer *User) (err error) {
func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) {
for i := range labels {
if hasIssueLabel(e, issue.ID, labels[i].ID) {
continue
}
if err = newIssueLabel(e, issue, labels[i], doer); err != nil {
if err = newIssueLabel(e, issue, labels[i]); err != nil {
return fmt.Errorf("newIssueLabel: %v", err)
}
}
@@ -325,14 +315,14 @@ func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label, doer *User)
}
// NewIssueLabels creates a list of issue-label relations.
func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) {
func NewIssueLabels(issue *Issue, labels []*Label) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = newIssueLabels(sess, issue, labels, doer); err != nil {
if err = newIssueLabels(sess, issue, labels); err != nil {
return err
}
@@ -347,22 +337,17 @@ func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
Find(&issueLabels)
}
func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (err error) {
if count, err := e.Delete(&IssueLabel{
// GetIssueLabels returns all issue-label relations of given issue by ID.
func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
return getIssueLabels(x, issueID)
}
func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
if _, err = e.Delete(&IssueLabel{
IssueID: issue.ID,
LabelID: label.ID,
}); err != nil {
return err
} else if count == 0 {
return nil
}
if err = issue.loadRepo(e); err != nil {
return
}
if _, err = createLabelComment(e, doer, issue.Repo, issue, label, false); err != nil {
return err
}
label.NumIssues--
@@ -373,14 +358,14 @@ func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label, doer *User) (
}
// DeleteIssueLabel deletes issue-label relation.
func DeleteIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
func DeleteIssueLabel(issue *Issue, label *Label) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = deleteIssueLabel(sess, issue, label, doer); err != nil {
if err = deleteIssueLabel(sess, issue, label); err != nil {
return err
}

View File

@@ -1,252 +0,0 @@
// Copyright 2017 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 (
"html/template"
"testing"
api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
)
// TODO TestGetLabelTemplateFile
func TestLabel_APIFormat(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.Equal(t, api.Label{
ID: label.ID,
Name: label.Name,
Color: "abcdef",
}, *label.APIFormat())
}
func TestLabel_CalOpenIssues(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
label.CalOpenIssues()
assert.EqualValues(t, 2, label.NumOpenIssues)
}
func TestLabel_ForegroundColor(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.Equal(t, template.CSS("#000"), label.ForegroundColor())
label = AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label)
assert.Equal(t, template.CSS("#fff"), label.ForegroundColor())
}
func TestNewLabels(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
labels := []*Label{
{RepoID: 2, Name: "labelName2", Color: "#123456"},
{RepoID: 3, Name: "labelName3", Color: "#234567"},
}
for _, label := range labels {
AssertNotExistsBean(t, label)
}
assert.NoError(t, NewLabels(labels...))
for _, label := range labels {
AssertExistsAndLoadBean(t, label)
}
CheckConsistencyFor(t, &Label{}, &Repository{})
}
func TestGetLabelByID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label, err := GetLabelByID(1)
assert.NoError(t, err)
assert.EqualValues(t, 1, label.ID)
_, err = GetLabelByID(NonexistentID)
assert.True(t, IsErrLabelNotExist(err))
}
func TestGetLabelInRepoByName(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label, err := GetLabelInRepoByName(1, "label1")
assert.NoError(t, err)
assert.EqualValues(t, 1, label.ID)
assert.Equal(t, "label1", label.Name)
_, err = GetLabelInRepoByName(1, "")
assert.True(t, IsErrLabelNotExist(err))
_, err = GetLabelInRepoByName(NonexistentID, "nonexistent")
assert.True(t, IsErrLabelNotExist(err))
}
func TestGetLabelInRepoByID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label, err := GetLabelInRepoByID(1, 1)
assert.NoError(t, err)
assert.EqualValues(t, 1, label.ID)
_, err = GetLabelInRepoByID(1, -1)
assert.True(t, IsErrLabelNotExist(err))
_, err = GetLabelInRepoByID(NonexistentID, NonexistentID)
assert.True(t, IsErrLabelNotExist(err))
}
func TestGetLabelsInRepoByIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
labels, err := GetLabelsInRepoByIDs(1, []int64{1, 2, NonexistentID})
assert.NoError(t, err)
assert.Len(t, labels, 2)
assert.EqualValues(t, 1, labels[0].ID)
assert.EqualValues(t, 2, labels[1].ID)
}
func TestGetLabelsByRepoID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) {
labels, err := GetLabelsByRepoID(repoID, sortType)
assert.NoError(t, err)
assert.Len(t, labels, len(expectedIssueIDs))
for i, label := range labels {
assert.EqualValues(t, expectedIssueIDs[i], label.ID)
}
}
testSuccess(1, "leastissues", []int64{2, 1})
testSuccess(1, "mostissues", []int64{1, 2})
testSuccess(1, "reversealphabetically", []int64{2, 1})
testSuccess(1, "default", []int64{1, 2})
}
func TestGetLabelsByIssueID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
labels, err := GetLabelsByIssueID(1)
assert.NoError(t, err)
assert.Len(t, labels, 1)
assert.EqualValues(t, 1, labels[0].ID)
labels, err = GetLabelsByIssueID(NonexistentID)
assert.NoError(t, err)
assert.Len(t, labels, 0)
}
func TestUpdateLabel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
label.Color = "#ffff00"
label.Name = "newLabelName"
assert.NoError(t, UpdateLabel(label))
newLabel := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.Equal(t, *label, *newLabel)
CheckConsistencyFor(t, &Label{}, &Repository{})
}
func TestDeleteLabel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.NoError(t, DeleteLabel(label.RepoID, label.ID))
AssertNotExistsBean(t, &Label{ID: label.ID, RepoID: label.RepoID})
assert.NoError(t, DeleteLabel(label.RepoID, label.ID))
AssertNotExistsBean(t, &Label{ID: label.ID, RepoID: label.RepoID})
assert.NoError(t, DeleteLabel(NonexistentID, NonexistentID))
CheckConsistencyFor(t, &Label{}, &Repository{})
}
func TestHasIssueLabel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, HasIssueLabel(1, 1))
assert.False(t, HasIssueLabel(1, 2))
assert.False(t, HasIssueLabel(NonexistentID, NonexistentID))
}
func TestNewIssueLabel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label := AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label)
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
// add new IssueLabel
prevNumIssues := label.NumIssues
assert.NoError(t, NewIssueLabel(issue, label, doer))
AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID})
AssertExistsAndLoadBean(t, &Comment{
Type: CommentTypeLabel,
PosterID: doer.ID,
IssueID: issue.ID,
LabelID: label.ID,
Content: "1",
})
assert.EqualValues(t, prevNumIssues+1, label.NumIssues)
// re-add existing IssueLabel
assert.NoError(t, NewIssueLabel(issue, label, doer))
CheckConsistencyFor(t, &Issue{}, &Label{})
}
func TestNewIssueLabels(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
label1 := AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
label2 := AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label)
issue := AssertExistsAndLoadBean(t, &Issue{ID: 5}).(*Issue)
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.NoError(t, NewIssueLabels(issue, []*Label{label1, label2}, doer))
AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label1.ID})
AssertExistsAndLoadBean(t, &Comment{
Type: CommentTypeLabel,
PosterID: doer.ID,
IssueID: issue.ID,
LabelID: label1.ID,
Content: "1",
})
AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label1.ID})
label1 = AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
assert.EqualValues(t, 3, label1.NumIssues)
assert.EqualValues(t, 1, label1.NumClosedIssues)
label2 = AssertExistsAndLoadBean(t, &Label{ID: 2}).(*Label)
assert.EqualValues(t, 1, label2.NumIssues)
assert.EqualValues(t, 1, label2.NumClosedIssues)
// corner case: test empty slice
assert.NoError(t, NewIssueLabels(issue, []*Label{}, doer))
CheckConsistencyFor(t, &Issue{}, &Label{})
}
func TestDeleteIssueLabel(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(labelID, issueID, doerID int64) {
label := AssertExistsAndLoadBean(t, &Label{ID: labelID}).(*Label)
issue := AssertExistsAndLoadBean(t, &Issue{ID: issueID}).(*Issue)
doer := AssertExistsAndLoadBean(t, &User{ID: doerID}).(*User)
expectedNumIssues := label.NumIssues
expectedNumClosedIssues := label.NumClosedIssues
if BeanExists(t, &IssueLabel{IssueID: issueID, LabelID: labelID}) {
expectedNumIssues--
if issue.IsClosed {
expectedNumClosedIssues--
}
}
assert.NoError(t, DeleteIssueLabel(issue, label, doer))
AssertNotExistsBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID})
AssertExistsAndLoadBean(t, &Comment{
Type: CommentTypeLabel,
PosterID: doerID,
IssueID: issueID,
LabelID: labelID,
}, `content=""`)
label = AssertExistsAndLoadBean(t, &Label{ID: labelID}).(*Label)
assert.EqualValues(t, expectedNumIssues, label.NumIssues)
assert.EqualValues(t, expectedNumClosedIssues, label.NumClosedIssues)
}
testSuccess(1, 1, 2)
testSuccess(2, 5, 2)
testSuccess(1, 1, 2) // delete non-existent IssueLabel
CheckConsistencyFor(t, &Issue{}, &Label{})
}

View File

@@ -1,320 +0,0 @@
// Copyright 2017 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"
// IssueList defines a list of issues
type IssueList []*Issue
func (issues IssueList) getRepoIDs() []int64 {
repoIDs := make(map[int64]struct{}, len(issues))
for _, issue := range issues {
if _, ok := repoIDs[issue.RepoID]; !ok {
repoIDs[issue.RepoID] = struct{}{}
}
}
return keysInt64(repoIDs)
}
func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) {
if len(issues) == 0 {
return nil, nil
}
repoIDs := issues.getRepoIDs()
repoMaps := make(map[int64]*Repository, len(repoIDs))
err := e.
In("id", repoIDs).
Find(&repoMaps)
if err != nil {
return nil, fmt.Errorf("find repository: %v", err)
}
for _, issue := range issues {
issue.Repo = repoMaps[issue.RepoID]
}
return valuesRepository(repoMaps), nil
}
// LoadRepositories loads issues' all repositories
func (issues IssueList) LoadRepositories() ([]*Repository, error) {
return issues.loadRepositories(x)
}
func (issues IssueList) getPosterIDs() []int64 {
posterIDs := make(map[int64]struct{}, len(issues))
for _, issue := range issues {
if _, ok := posterIDs[issue.PosterID]; !ok {
posterIDs[issue.PosterID] = struct{}{}
}
}
return keysInt64(posterIDs)
}
func (issues IssueList) loadPosters(e Engine) error {
if len(issues) == 0 {
return nil
}
postgerIDs := issues.getPosterIDs()
posterMaps := make(map[int64]*User, len(postgerIDs))
err := e.
In("id", postgerIDs).
Find(&posterMaps)
if err != nil {
return err
}
for _, issue := range issues {
issue.Poster = posterMaps[issue.PosterID]
}
return nil
}
func (issues IssueList) getIssueIDs() []int64 {
var ids = make([]int64, 0, len(issues))
for _, issue := range issues {
ids = append(ids, issue.ID)
}
return ids
}
func (issues IssueList) loadLabels(e Engine) error {
if len(issues) == 0 {
return nil
}
type LabelIssue struct {
Label *Label `xorm:"extends"`
IssueLabel *IssueLabel `xorm:"extends"`
}
var issueLabels = make(map[int64][]*Label, len(issues)*3)
rows, err := e.Table("label").
Join("LEFT", "issue_label", "issue_label.label_id = label.id").
In("issue_label.issue_id", issues.getIssueIDs()).
Asc("label.name").
Rows(new(LabelIssue))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var labelIssue LabelIssue
err = rows.Scan(&labelIssue)
if err != nil {
return err
}
issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label)
}
for _, issue := range issues {
issue.Labels = issueLabels[issue.ID]
}
return nil
}
func (issues IssueList) getMilestoneIDs() []int64 {
var ids = make(map[int64]struct{}, len(issues))
for _, issue := range issues {
if _, ok := ids[issue.MilestoneID]; !ok {
ids[issue.MilestoneID] = struct{}{}
}
}
return keysInt64(ids)
}
func (issues IssueList) loadMilestones(e Engine) error {
milestoneIDs := issues.getMilestoneIDs()
if len(milestoneIDs) == 0 {
return nil
}
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
err := e.
In("id", milestoneIDs).
Find(&milestoneMaps)
if err != nil {
return err
}
for _, issue := range issues {
issue.Milestone = milestoneMaps[issue.MilestoneID]
}
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 {
assigneeIDs := issues.getAssigneeIDs()
if len(assigneeIDs) == 0 {
return nil
}
assigneeMaps := make(map[int64]*User, len(assigneeIDs))
err := e.
In("id", assigneeIDs).
Find(&assigneeMaps)
if err != nil {
return err
}
for _, issue := range issues {
issue.Assignee = assigneeMaps[issue.AssigneeID]
}
return nil
}
func (issues IssueList) getPullIssueIDs() []int64 {
var ids = make([]int64, 0, len(issues))
for _, issue := range issues {
if issue.IsPull && issue.PullRequest == nil {
ids = append(ids, issue.ID)
}
}
return ids
}
func (issues IssueList) loadPullRequests(e Engine) error {
issuesIDs := issues.getPullIssueIDs()
if len(issuesIDs) == 0 {
return nil
}
pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
rows, err := e.
In("issue_id", issuesIDs).
Rows(new(PullRequest))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var pr PullRequest
err = rows.Scan(&pr)
if err != nil {
return err
}
pullRequestMaps[pr.IssueID] = &pr
}
for _, issue := range issues {
issue.PullRequest = pullRequestMaps[issue.ID]
}
return nil
}
func (issues IssueList) loadAttachments(e Engine) (err error) {
if len(issues) == 0 {
return nil
}
var attachments = make(map[int64][]*Attachment, len(issues))
rows, err := e.Table("attachment").
Join("INNER", "issue", "issue.id = attachment.issue_id").
In("issue.id", issues.getIssueIDs()).
Rows(new(Attachment))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var attachment Attachment
err = rows.Scan(&attachment)
if err != nil {
return err
}
attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
}
for _, issue := range issues {
issue.Attachments = attachments[issue.ID]
}
return nil
}
func (issues IssueList) loadComments(e Engine) (err error) {
if len(issues) == 0 {
return nil
}
var comments = make(map[int64][]*Comment, len(issues))
rows, err := e.Table("comment").
Join("INNER", "issue", "issue.id = comment.issue_id").
In("issue.id", issues.getIssueIDs()).
Rows(new(Comment))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var comment Comment
err = rows.Scan(&comment)
if err != nil {
return err
}
comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
}
for _, issue := range issues {
issue.Comments = comments[issue.ID]
}
return nil
}
func (issues IssueList) loadAttributes(e Engine) (err error) {
if _, err = issues.loadRepositories(e); err != nil {
return
}
if err = issues.loadPosters(e); err != nil {
return
}
if err = issues.loadLabels(e); err != nil {
return
}
if err = issues.loadMilestones(e); err != nil {
return
}
if err = issues.loadAssignees(e); err != nil {
return
}
if err = issues.loadPullRequests(e); err != nil {
return
}
if err = issues.loadAttachments(e); err != nil {
return
}
if err = issues.loadComments(e); err != nil {
return
}
return nil
}
// LoadAttributes loads atrributes of the issues
func (issues IssueList) LoadAttributes() error {
return issues.loadAttributes(x)
}

View File

@@ -24,7 +24,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
return nil
}
// Mail watchers.
// Mail wahtcers.
watchers, err := GetWatchers(issue.RepoID)
if err != nil {
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err)

View File

@@ -1,352 +0,0 @@
// Copyright 2017 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 (
"time"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/sdk/gitea"
"github.com/go-xorm/xorm"
)
// Milestone represents a milestone of repository.
type Milestone struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Name string
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
IsClosed bool
NumIssues int
NumClosedIssues int
NumOpenIssues int `xorm:"-"`
Completeness int // Percentage(1-100).
IsOverDue bool `xorm:"-"`
DeadlineString string `xorm:"-"`
Deadline time.Time `xorm:"-"`
DeadlineUnix int64
ClosedDate time.Time `xorm:"-"`
ClosedDateUnix int64
}
// BeforeInsert is invoked from XORM before inserting an object of this type.
func (m *Milestone) BeforeInsert() {
m.DeadlineUnix = m.Deadline.Unix()
}
// BeforeUpdate is invoked from XORM before updating this object.
func (m *Milestone) BeforeUpdate() {
if m.NumIssues > 0 {
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
} else {
m.Completeness = 0
}
m.DeadlineUnix = m.Deadline.Unix()
m.ClosedDateUnix = m.ClosedDate.Unix()
}
// AfterSet is invoked from XORM after setting the value of a field of
// this object.
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "num_closed_issues":
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
case "deadline_unix":
m.Deadline = time.Unix(m.DeadlineUnix, 0).Local()
if m.Deadline.Year() == 9999 {
return
}
m.DeadlineString = m.Deadline.Format("2006-01-02")
if time.Now().Local().After(m.Deadline) {
m.IsOverDue = true
}
case "closed_date_unix":
m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local()
}
}
// State returns string representation of milestone status.
func (m *Milestone) State() api.StateType {
if m.IsClosed {
return api.StateClosed
}
return api.StateOpen
}
// APIFormat returns this Milestone in API format.
func (m *Milestone) APIFormat() *api.Milestone {
apiMilestone := &api.Milestone{
ID: m.ID,
State: m.State(),
Title: m.Name,
Description: m.Content,
OpenIssues: m.NumOpenIssues,
ClosedIssues: m.NumClosedIssues,
}
if m.IsClosed {
apiMilestone.Closed = &m.ClosedDate
}
if m.Deadline.Year() < 9999 {
apiMilestone.Deadline = &m.Deadline
}
return apiMilestone
}
// NewMilestone creates new milestone of repository.
func NewMilestone(m *Milestone) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(m); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil {
return err
}
return sess.Commit()
}
func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) {
m := &Milestone{
ID: id,
RepoID: repoID,
}
has, err := e.Get(m)
if err != nil {
return nil, err
} else if !has {
return nil, ErrMilestoneNotExist{id, repoID}
}
return m, nil
}
// GetMilestoneByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
return getMilestoneByRepoID(x, repoID, id)
}
// GetMilestonesByRepoID returns all milestones of a repository.
func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) {
miles := make([]*Milestone, 0, 10)
return miles, x.Where("repo_id = ?", repoID).Find(&miles)
}
// GetMilestones returns a list of milestones of given repository and status.
func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*Milestone, error) {
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed)
if page > 0 {
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum)
}
switch sortType {
case "furthestduedate":
sess.Desc("deadline_unix")
case "leastcomplete":
sess.Asc("completeness")
case "mostcomplete":
sess.Desc("completeness")
case "leastissues":
sess.Asc("num_issues")
case "mostissues":
sess.Desc("num_issues")
default:
sess.Asc("deadline_unix")
}
return miles, sess.Find(&miles)
}
func updateMilestone(e Engine, m *Milestone) error {
_, err := e.Id(m.ID).AllCols().Update(m)
return err
}
// UpdateMilestone updates information of given milestone.
func UpdateMilestone(m *Milestone) error {
return updateMilestone(x, m)
}
func countRepoMilestones(e Engine, repoID int64) int64 {
count, _ := e.
Where("repo_id=?", repoID).
Count(new(Milestone))
return count
}
func countRepoClosedMilestones(e Engine, repoID int64) int64 {
closed, _ := e.
Where("repo_id=? AND is_closed=?", repoID, true).
Count(new(Milestone))
return closed
}
// CountRepoClosedMilestones returns number of closed milestones in given repository.
func CountRepoClosedMilestones(repoID int64) int64 {
return countRepoClosedMilestones(x, repoID)
}
// MilestoneStats returns number of open and closed milestones of given repository.
func MilestoneStats(repoID int64) (open int64, closed int64) {
open, _ = x.
Where("repo_id=? AND is_closed=?", repoID, false).
Count(new(Milestone))
return open, CountRepoClosedMilestones(repoID)
}
// ChangeMilestoneStatus changes the milestone open/closed status.
func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
repo, err := GetRepositoryByID(m.RepoID)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
m.IsClosed = isClosed
if err = updateMilestone(sess, m); err != nil {
return err
}
repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil {
return err
}
return sess.Commit()
}
func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
if issue.MilestoneID == 0 {
return nil
}
m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
if err != nil {
return err
}
if issue.IsClosed {
m.NumOpenIssues--
m.NumClosedIssues++
} else {
m.NumOpenIssues++
m.NumClosedIssues--
}
return updateMilestone(e, m)
}
func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error {
if oldMilestoneID > 0 {
m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID)
if err != nil {
return err
}
m.NumIssues--
if issue.IsClosed {
m.NumClosedIssues--
}
if err = updateMilestone(e, m); err != nil {
return err
}
}
if issue.MilestoneID > 0 {
m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
if err != nil {
return err
}
m.NumIssues++
if issue.IsClosed {
m.NumClosedIssues++
}
if err = updateMilestone(e, m); err != nil {
return err
}
}
if err := issue.loadRepo(e); err != nil {
return err
}
if oldMilestoneID > 0 || issue.MilestoneID > 0 {
if _, err := createMilestoneComment(e, doer, issue.Repo, issue, oldMilestoneID, issue.MilestoneID); err != nil {
return err
}
}
return updateIssue(e, issue)
}
// ChangeMilestoneAssign changes assignment of milestone for issue.
func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil {
return err
}
return sess.Commit()
}
// DeleteMilestoneByRepoID deletes a milestone from a repository.
func DeleteMilestoneByRepoID(repoID, id int64) error {
m, err := GetMilestoneByRepoID(repoID, id)
if err != nil {
if IsErrMilestoneNotExist(err) {
return nil
}
return err
}
repo, err := GetRepositoryByID(m.RepoID)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil {
return err
}
repo.NumMilestones = int(countRepoMilestones(sess, repo.ID))
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID))
if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil {
return err
}
return sess.Commit()
}

View File

@@ -1,240 +0,0 @@
// Copyright 2017 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"
api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
"sort"
"time"
)
func TestMilestone_State(t *testing.T) {
assert.Equal(t, api.StateOpen, (&Milestone{IsClosed: false}).State())
assert.Equal(t, api.StateClosed, (&Milestone{IsClosed: true}).State())
}
func TestMilestone_APIFormat(t *testing.T) {
milestone := &Milestone{
ID: 3,
RepoID: 4,
Name: "milestoneName",
Content: "milestoneContent",
IsClosed: false,
NumOpenIssues: 5,
NumClosedIssues: 6,
Deadline: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
}
assert.Equal(t, api.Milestone{
ID: milestone.ID,
State: api.StateOpen,
Title: milestone.Name,
Description: milestone.Content,
OpenIssues: milestone.NumOpenIssues,
ClosedIssues: milestone.NumClosedIssues,
Deadline: &milestone.Deadline,
}, *milestone.APIFormat())
}
func TestNewMilestone(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
milestone := &Milestone{
RepoID: 1,
Name: "milestoneName",
Content: "milestoneContent",
}
assert.NoError(t, NewMilestone(milestone))
AssertExistsAndLoadBean(t, milestone)
CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{})
}
func TestGetMilestoneByRepoID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
milestone, err := GetMilestoneByRepoID(1, 1)
assert.NoError(t, err)
assert.EqualValues(t, 1, milestone.ID)
assert.EqualValues(t, 1, milestone.RepoID)
_, err = GetMilestoneByRepoID(NonexistentID, NonexistentID)
assert.True(t, IsErrMilestoneNotExist(err))
}
func TestGetMilestonesByRepoID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
milestones, err := GetMilestonesByRepoID(repo.ID)
assert.NoError(t, err)
assert.Len(t, milestones, repo.NumMilestones)
for _, milestone := range milestones {
assert.EqualValues(t, repoID, milestone.RepoID)
}
}
test(1)
test(2)
test(3)
milestones, err := GetMilestonesByRepoID(NonexistentID)
assert.NoError(t, err)
assert.Len(t, milestones, 0)
}
func TestGetMilestones(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
test := func(sortType string, sortCond func(*Milestone) int) {
for _, page := range []int{0, 1} {
milestones, err := GetMilestones(repo.ID, page, false, sortType)
assert.NoError(t, err)
assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones)
values := make([]int, len(milestones))
for i, milestone := range milestones {
values[i] = sortCond(milestone)
}
assert.True(t, sort.IntsAreSorted(values))
milestones, err = GetMilestones(repo.ID, page, true, sortType)
assert.NoError(t, err)
assert.Len(t, milestones, repo.NumClosedMilestones)
values = make([]int, len(milestones))
for i, milestone := range milestones {
values[i] = sortCond(milestone)
}
assert.True(t, sort.IntsAreSorted(values))
}
}
test("furthestduedate", func(milestone *Milestone) int {
return -int(milestone.DeadlineUnix)
})
test("leastcomplete", func(milestone *Milestone) int {
return milestone.Completeness
})
test("mostcomplete", func(milestone *Milestone) int {
return -milestone.Completeness
})
test("leastissues", func(milestone *Milestone) int {
return milestone.NumIssues
})
test("mostissues", func(milestone *Milestone) int {
return -milestone.NumIssues
})
test("soonestduedate", func(milestone *Milestone) int {
return int(milestone.DeadlineUnix)
})
}
func TestUpdateMilestone(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
milestone.Name = "newMilestoneName"
milestone.Content = "newMilestoneContent"
assert.NoError(t, UpdateMilestone(milestone))
AssertExistsAndLoadBean(t, milestone)
CheckConsistencyFor(t, &Milestone{})
}
func TestCountRepoMilestones(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
assert.EqualValues(t, repo.NumMilestones, countRepoMilestones(x, repoID))
}
test(1)
test(2)
test(3)
assert.EqualValues(t, 0, countRepoMilestones(x, NonexistentID))
}
func TestCountRepoClosedMilestones(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
assert.EqualValues(t, repo.NumClosedMilestones, CountRepoClosedMilestones(repoID))
}
test(1)
test(2)
test(3)
assert.EqualValues(t, 0, countRepoMilestones(x, NonexistentID))
}
func TestMilestoneStats(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(repoID int64) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
open, closed := MilestoneStats(repoID)
assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, open)
assert.EqualValues(t, repo.NumClosedMilestones, closed)
}
test(1)
test(2)
test(3)
open, closed := MilestoneStats(NonexistentID)
assert.EqualValues(t, 0, open)
assert.EqualValues(t, 0, closed)
}
func TestChangeMilestoneStatus(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.NoError(t, ChangeMilestoneStatus(milestone, true))
AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=1")
CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{})
assert.NoError(t, ChangeMilestoneStatus(milestone, false))
AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=0")
CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{})
}
func TestChangeMilestoneIssueStats(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1},
"is_closed=0").(*Issue)
issue.IsClosed = true
_, err := x.Cols("is_closed").Update(issue)
assert.NoError(t, err)
assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
CheckConsistencyFor(t, &Milestone{})
issue.IsClosed = false
_, err = x.Cols("is_closed").Update(issue)
assert.NoError(t, err)
assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
CheckConsistencyFor(t, &Milestone{})
}
func TestChangeMilestoneAssign(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue)
doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = 2
assert.NoError(t, ChangeMilestoneAssign(issue, doer, oldMilestoneID))
AssertExistsAndLoadBean(t, &Comment{
IssueID: issue.ID,
Type: CommentTypeMilestone,
MilestoneID: issue.MilestoneID,
OldMilestoneID: oldMilestoneID,
})
CheckConsistencyFor(t, &Milestone{}, &Issue{})
}
func TestDeleteMilestoneByRepoID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.NoError(t, DeleteMilestoneByRepoID(1, 1))
AssertNotExistsBean(t, &Milestone{ID: 1})
CheckConsistencyFor(t, &Repository{ID: 1})
assert.NoError(t, DeleteMilestoneByRepoID(NonexistentID, NonexistentID))
}

View File

@@ -1,44 +0,0 @@
// Copyright 2017 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 TestIssue_ReplaceLabels(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(issueID int64, labelIDs []int64) {
issue := AssertExistsAndLoadBean(t, &Issue{ID: issueID}).(*Issue)
repo := AssertExistsAndLoadBean(t, &Repository{ID: issue.RepoID}).(*Repository)
doer := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
labels := make([]*Label, len(labelIDs))
for i, labelID := range labelIDs {
labels[i] = AssertExistsAndLoadBean(t, &Label{ID: labelID, RepoID: repo.ID}).(*Label)
}
assert.NoError(t, issue.ReplaceLabels(labels, doer))
AssertCount(t, &IssueLabel{IssueID: issueID}, len(labelIDs))
for _, labelID := range labelIDs {
AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issueID, LabelID: labelID})
}
}
testSuccess(1, []int64{2})
testSuccess(1, []int64{1, 2})
testSuccess(1, []int64{})
}
func TestIssueAPIURL(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
err := issue.LoadAttributes()
assert.NoError(t, err)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL())
}

View File

@@ -1,113 +0,0 @@
// Copyright 2017 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"
)
// IssueUser represents an issue-user relation.
type IssueUser struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX"` // User ID.
IssueID int64
IsRead bool
IsAssigned bool
IsMentioned bool
}
func newIssueUsers(e Engine, repo *Repository, issue *Issue) error {
assignees, err := repo.getAssignees(e)
if err != nil {
return fmt.Errorf("getAssignees: %v", err)
}
// Poster can be anyone, append later if not one of assignees.
isPosterAssignee := false
// Leave a seat for poster itself to append later, but if poster is one of assignee
// and just waste 1 unit is cheaper than re-allocate memory once.
issueUsers := make([]*IssueUser, 0, len(assignees)+1)
for _, assignee := range assignees {
issueUsers = append(issueUsers, &IssueUser{
IssueID: issue.ID,
UID: assignee.ID,
IsAssigned: assignee.ID == issue.AssigneeID,
})
isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID
}
if !isPosterAssignee {
issueUsers = append(issueUsers, &IssueUser{
IssueID: issue.ID,
UID: issue.PosterID,
})
}
if _, err = e.Insert(issueUsers); err != nil {
return err
}
return nil
}
func updateIssueUserByAssignee(e Engine, issue *Issue) (err error) {
if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil {
return err
}
// Assignee ID equals to 0 means clear assignee.
if issue.AssigneeID > 0 {
if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil {
return err
}
}
return updateIssue(e, issue)
}
// UpdateIssueUserByAssignee updates issue-user relation for assignee.
func UpdateIssueUserByAssignee(issue *Issue) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = updateIssueUserByAssignee(sess, issue); err != nil {
return err
}
return sess.Commit()
}
// UpdateIssueUserByRead updates issue-user relation for reading.
func UpdateIssueUserByRead(uid, issueID int64) error {
_, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID)
return err
}
// UpdateIssueUsersByMentions updates issue-user pairs by mentioning.
func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error {
for _, uid := range uids {
iu := &IssueUser{
UID: uid,
IssueID: issueID,
}
has, err := e.Get(iu)
if err != nil {
return err
}
iu.IsMentioned = true
if has {
_, err = e.Id(iu.ID).AllCols().Update(iu)
} else {
_, err = e.Insert(iu)
}
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,74 +0,0 @@
// Copyright 2017 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_newIssueUsers(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
newIssue := &Issue{
RepoID: repo.ID,
PosterID: 4,
Index: 5,
Title: "newTestIssueTitle",
Content: "newTestIssueContent",
}
// artificially insert new issue
AssertSuccessfulInsert(t, newIssue)
assert.NoError(t, newIssueUsers(x, repo, newIssue))
// issue_user table should now have entries for new issue
AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID})
AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID})
}
func TestUpdateIssueUserByAssignee(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
// artificially change assignee in issue_user table
AssertSuccessfulInsert(t, &IssueUser{IssueID: issue.ID, UID: 5, IsAssigned: true})
_, err := x.Cols("is_assigned").
Update(&IssueUser{IsAssigned: false}, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID})
assert.NoError(t, err)
assert.NoError(t, UpdateIssueUserByAssignee(issue))
// issue_user table should now be correct again
AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID}, "is_assigned=1")
AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 5}, "is_assigned=0")
}
func TestUpdateIssueUserByRead(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
assert.NoError(t, UpdateIssueUserByRead(4, issue.ID))
AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
assert.NoError(t, UpdateIssueUserByRead(4, issue.ID))
AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
assert.NoError(t, UpdateIssueUserByRead(NonexistentID, NonexistentID))
}
func TestUpdateIssueUsersByMentions(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
uids := []int64{2, 5}
assert.NoError(t, UpdateIssueUsersByMentions(x, issue.ID, uids))
for _, uid := range uids {
AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1")
}
}

View File

@@ -1,122 +0,0 @@
package models
import (
"errors"
"github.com/go-xorm/xorm"
"time"
)
// LFSMetaObject stores metadata for LFS tracked files.
type LFSMetaObject struct {
ID int64 `xorm:"pk autoincr"`
Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"`
Size int64 `xorm:"NOT NULL"`
RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
Existing bool `xorm:"-"`
Created time.Time `xorm:"-"`
CreatedUnix int64
}
// LFSTokenResponse defines the JSON structure in which the JWT token is stored.
// This structure is fetched via SSH and passed by the Git LFS client to the server
// endpoint for authorization.
type LFSTokenResponse struct {
Header map[string]string `json:"header"`
Href string `json:"href"`
}
var (
// ErrLFSObjectNotExist is returned from lfs models functions in order
// to differentiate between database and missing object errors.
ErrLFSObjectNotExist = errors.New("LFS Meta object does not exist")
)
const (
// LFSMetaFileIdentifier is the string appearing at the first line of LFS pointer files.
// https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
LFSMetaFileIdentifier = "version https://git-lfs.github.com/spec/v1"
// LFSMetaFileOidPrefix appears in LFS pointer files on a line before the sha256 hash.
LFSMetaFileOidPrefix = "oid sha256:"
)
// NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
// if it is not already present.
func NewLFSMetaObject(m *LFSMetaObject) (*LFSMetaObject, error) {
var err error
has, err := x.Get(m)
if err != nil {
return nil, err
}
if has {
m.Existing = true
return m, nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return nil, err
}
if _, err = sess.Insert(m); err != nil {
return nil, err
}
return m, sess.Commit()
}
// GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
// It may return ErrLFSObjectNotExist or a database error. If the error is nil,
// the returned pointer is a valid LFSMetaObject.
func GetLFSMetaObjectByOid(oid string) (*LFSMetaObject, error) {
if len(oid) == 0 {
return nil, ErrLFSObjectNotExist
}
m := &LFSMetaObject{Oid: oid}
has, err := x.Get(m)
if err != nil {
return nil, err
} else if !has {
return nil, ErrLFSObjectNotExist
}
return m, nil
}
// RemoveLFSMetaObjectByOid removes a LFSMetaObject entry from database by its OID.
// It may return ErrLFSObjectNotExist or a database error.
func RemoveLFSMetaObjectByOid(oid string) error {
if len(oid) == 0 {
return ErrLFSObjectNotExist
}
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return err
}
m := &LFSMetaObject{Oid: oid}
if _, err := sess.Delete(m); err != nil {
return err
}
return sess.Commit()
}
// BeforeInsert sets the time at which the LFSMetaObject was created.
func (m *LFSMetaObject) BeforeInsert() {
m.CreatedUnix = time.Now().Unix()
}
// AfterSet stores the LFSMetaObject creation time in the database as local time.
func (m *LFSMetaObject) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "created_unix":
m.Created = time.Unix(m.CreatedUnix, 0).Local()
}
}

View File

@@ -20,7 +20,6 @@ import (
"github.com/go-xorm/xorm"
"code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/log"
)
@@ -36,16 +35,14 @@ const (
LoginSMTP // 3
LoginPAM // 4
LoginDLDAP // 5
LoginOAuth2 // 6
)
// LoginNames contains the name of LoginType values.
var LoginNames = map[LoginType]string{
LoginLDAP: "LDAP (via BindDN)",
LoginDLDAP: "LDAP (simple auth)", // Via direct bind
LoginSMTP: "SMTP",
LoginPAM: "PAM",
LoginOAuth2: "OAuth2",
LoginLDAP: "LDAP (via BindDN)",
LoginDLDAP: "LDAP (simple auth)", // Via direct bind
LoginSMTP: "SMTP",
LoginPAM: "PAM",
}
// SecurityProtocolNames contains the name of SecurityProtocol values.
@@ -60,7 +57,6 @@ var (
_ core.Conversion = &LDAPConfig{}
_ core.Conversion = &SMTPConfig{}
_ core.Conversion = &PAMConfig{}
_ core.Conversion = &OAuth2Config{}
)
// LDAPConfig holds configuration for LDAP login source.
@@ -119,35 +115,18 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}
// OAuth2Config holds configuration for the OAuth2 login source.
type OAuth2Config struct {
Provider string
ClientID string
ClientSecret string
}
// FromDB fills up an OAuth2Config from serialized format.
func (cfg *OAuth2Config) FromDB(bs []byte) error {
return json.Unmarshal(bs, cfg)
}
// ToDB exports an SMTPConfig to a serialized format.
func (cfg *OAuth2Config) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}
// LoginSource represents an external way for authorizing users.
type LoginSource struct {
ID int64 `xorm:"pk autoincr"`
Type LoginType
Name string `xorm:"UNIQUE"`
IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"`
IsActived bool `xorm:"NOT NULL DEFAULT false"`
Cfg core.Conversion `xorm:"TEXT"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"INDEX"`
UpdatedUnix int64
}
// BeforeInsert is invoked from XORM before inserting an object of this type.
@@ -183,8 +162,6 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
source.Cfg = new(SMTPConfig)
case LoginPAM:
source.Cfg = new(PAMConfig)
case LoginOAuth2:
source.Cfg = new(OAuth2Config)
default:
panic("unrecognized login source type: " + com.ToStr(*val))
}
@@ -226,11 +203,6 @@ func (source *LoginSource) IsPAM() bool {
return source.Type == LoginPAM
}
// IsOAuth2 returns true of this source is of the OAuth2 type.
func (source *LoginSource) IsOAuth2() bool {
return source.Type == LoginOAuth2
}
// HasTLS returns true of this source supports TLS.
func (source *LoginSource) HasTLS() bool {
return ((source.IsLDAP() || source.IsDLDAP()) &&
@@ -278,11 +250,6 @@ func (source *LoginSource) PAM() *PAMConfig {
return source.Cfg.(*PAMConfig)
}
// OAuth2 returns OAuth2Config for this source, if of OAuth2 type.
func (source *LoginSource) OAuth2() *OAuth2Config {
return source.Cfg.(*OAuth2Config)
}
// CreateLoginSource inserts a LoginSource in the DB if not already
// existing with the given name.
func CreateLoginSource(source *LoginSource) error {
@@ -294,16 +261,12 @@ func CreateLoginSource(source *LoginSource) error {
}
_, err = x.Insert(source)
if err == nil && source.IsOAuth2() {
oAuth2Config := source.OAuth2()
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
}
return err
}
// LoginSources returns a slice of all login sources found in DB.
func LoginSources() ([]*LoginSource, error) {
auths := make([]*LoginSource, 0, 6)
auths := make([]*LoginSource, 0, 5)
return auths, x.Find(&auths)
}
@@ -322,11 +285,6 @@ func GetLoginSourceByID(id int64) (*LoginSource, error) {
// UpdateSource updates a LoginSource record in DB.
func UpdateSource(source *LoginSource) error {
_, err := x.Id(source.ID).AllCols().Update(source)
if err == nil && source.IsOAuth2() {
oAuth2Config := source.OAuth2()
oauth2.RemoveProvider(source.Name)
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
}
return err
}
@@ -338,18 +296,6 @@ func DeleteSource(source *LoginSource) error {
} else if count > 0 {
return ErrLoginSourceInUse{source.ID}
}
count, err = x.Count(&ExternalLoginUser{LoginSourceID: source.ID})
if err != nil {
return err
} else if count > 0 {
return ErrLoginSourceInUse{source.ID}
}
if source.IsOAuth2() {
oauth2.RemoveProvider(source.Name)
}
_, err = x.Id(source.ID).Delete(new(LoginSource))
return err
}
@@ -580,27 +526,6 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
return user, CreateUser(user)
}
// ________ _____ __ .__ ________
// \_____ \ / _ \ __ ___/ |_| |__ \_____ \
// / | \ / /_\ \| | \ __\ | \ / ____/
// / | \/ | \ | /| | | Y \/ \
// \_______ /\____|__ /____/ |__| |___| /\_______ \
// \/ \/ \/ \/
// OAuth2Provider describes the display values of a single OAuth2 provider
type OAuth2Provider struct {
Name string
DisplayName string
Image string
}
// OAuth2Providers contains the map of registered OAuth2 providers in Gitea (based on goth)
// key is used to map the OAuth2Provider with the goth provider type (also in LoginSource.OAuth2Config.Provider)
// value is used to store display data
var OAuth2Providers = map[string]OAuth2Provider{
"github": {Name: "github", DisplayName: "GitHub", Image: "/img/github.png"},
}
// ExternalUserLogin attempts a login using external source types.
func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
if !source.IsActived {
@@ -624,23 +549,8 @@ func UserSignIn(username, password string) (*User, error) {
var user *User
if strings.Contains(username, "@") {
user = &User{Email: strings.ToLower(strings.TrimSpace(username))}
// check same email
cnt, err := x.Count(user)
if err != nil {
return nil, err
}
if cnt > 1 {
return nil, ErrEmailAlreadyUsed{
Email: user.Email,
}
}
} else {
trimmedUsername := strings.TrimSpace(username)
if len(trimmedUsername) == 0 {
return nil, ErrUserNotExist{0, username, 0}
}
user = &User{LowerName: strings.ToLower(trimmedUsername)}
user = &User{LowerName: strings.ToLower(strings.TrimSpace(username))}
}
hasUser, err := x.Get(user)
@@ -650,7 +560,7 @@ func UserSignIn(username, password string) (*User, error) {
if hasUser {
switch user.LoginType {
case LoginNoType, LoginPlain, LoginOAuth2:
case LoginNoType, LoginPlain:
if user.ValidatePassword(password) {
return user, nil
}
@@ -670,16 +580,12 @@ func UserSignIn(username, password string) (*User, error) {
}
}
sources := make([]*LoginSource, 0, 5)
sources := make([]*LoginSource, 0, 3)
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
return nil, err
}
for _, source := range sources {
if source.IsOAuth2() {
// don't try to authenticate against OAuth2 sources
continue
}
authUser, err := ExternalUserLogin(nil, username, password, source, true)
if err == nil {
return authUser, nil
@@ -690,58 +596,3 @@ func UserSignIn(username, password string) (*User, error) {
return nil, ErrUserNotExist{user.ID, user.Name, 0}
}
// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources
func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
sources := make([]*LoginSource, 0, 1)
if err := x.UseBool().Find(&sources, &LoginSource{IsActived: true, Type: LoginOAuth2}); err != nil {
return nil, err
}
return sources, nil
}
// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name
func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) {
loginSource := &LoginSource{
Name: name,
Type: LoginOAuth2,
IsActived: true,
}
has, err := x.UseBool().Get(loginSource)
if !has || err != nil {
return nil, err
}
return loginSource, nil
}
// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
// key is used as technical name (like in the callbackURL)
// values to display
func GetActiveOAuth2Providers() (map[string]OAuth2Provider, error) {
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type
loginSources, err := GetActiveOAuth2ProviderLoginSources()
if err != nil {
return nil, err
}
providers := make(map[string]OAuth2Provider)
for _, source := range loginSources {
providers[source.Name] = OAuth2Providers[source.OAuth2().Provider]
}
return providers, nil
}
// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
func InitOAuth2() {
oauth2.Init()
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
for _, source := range loginSources {
oAuth2Config := source.OAuth2()
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret)
}
}

View File

@@ -40,7 +40,7 @@ func InitMailRender(tmpls *template.Template) {
// SendTestMail sends a test mail
func SendTestMail(email string) error {
return gomail.Send(mailer.Sender, mailer.NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").Message)
return gomail.Send(&mailer.Sender{}, mailer.NewMessage([]string{email}, "Gitea Test Email!", "Gitea Test Email!").Message)
}
// SendUserMail sends a mail to the user
@@ -150,7 +150,7 @@ func composeTplData(subject, body, link string) map[string]interface{} {
func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message {
subject := issue.mailSubject()
body := string(markdown.RenderString(issue.Content, issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
body := string(markdown.RenderSpecialLink([]byte(issue.Content), issue.Repo.HTMLURL(), issue.Repo.ComposeMetas()))
data := composeTplData(subject, body, issue.HTMLURL())
data["Doer"] = doer

View File

@@ -76,24 +76,8 @@ var migrations = []Migration{
// v13 -> v14:v0.9.87
NewMigration("set comment updated with created", setCommentUpdatedWithCreated),
// v14 -> v15
NewMigration("create user column diff view style", createUserColumnDiffViewStyle),
// v15 -> v16
NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn),
// V16 -> v17
NewMigration("create repo unit table and add units for all repos", addUnitsToTables),
// v17 -> v18
NewMigration("set protect branches updated with created", setProtectedBranchUpdatedWithCreated),
// v18 -> v19
NewMigration("add external login user", addExternalLoginUser),
// v19 -> v20
NewMigration("generate and migrate Git hooks", generateAndMigrateGitHooks),
// v20 -> v21
NewMigration("use new avatar path name for security reason", useNewNameAvatars),
// v21 -> v22
NewMigration("rewrite authorized_keys file via new format", useNewPublickeyFormat),
// v21 -> v22
NewMigration("generate and migrate wiki Git hooks", generateAndMigrateWikiGitHooks),
}
// Migrate database to current version
@@ -109,7 +93,6 @@ func Migrate(x *xorm.Engine) error {
} else if !has {
// If the version record does not exist we think
// it is a fresh installation and we can skip all migrations.
currentVersion.ID = 0
currentVersion.Version = int64(minDBVersion + len(migrations))
if _, err = x.InsertOne(currentVersion); err != nil {
@@ -119,13 +102,13 @@ func Migrate(x *xorm.Engine) error {
v := currentVersion.Version
if minDBVersion > v {
log.Fatal(4, `Gitea no longer supports auto-migration from your previously installed version.
log.Fatal(4, `Gogs no longer supports auto-migration from your previously installed version.
Please try to upgrade to a lower version (>= v0.6.0) first, then upgrade to current version.`)
return nil
}
if int(v-minDBVersion) > len(migrations) {
// User downgraded Gitea.
// User downgraded Gogs.
currentVersion.Version = int64(len(migrations) + minDBVersion)
_, err = x.Id(1).Update(currentVersion)
return err
@@ -342,7 +325,7 @@ func attachmentRefactor(x *xorm.Engine) error {
dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
log.Info("Failed to rename some attachments, old and new paths are saved into: %s", dumpPath)
fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
}()
for _, attach := range attachments {
if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {

View File

@@ -1,30 +0,0 @@
// Copyright 2016 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
// UserV15 describes the added field for User
type UserV15 struct {
AllowCreateOrganization bool
}
// TableName will be invoked by XORM to customrize the table name
func (*UserV15) TableName() string {
return "user"
}
func createAllowCreateOrganizationColumn(x *xorm.Engine) error {
if err := x.Sync2(new(UserV15)); err != nil {
return fmt.Errorf("Sync2: %v", err)
} else if _, err = x.Where("type=0").Cols("allow_create_organization").Update(&UserV15{AllowCreateOrganization: true}); err != nil {
return fmt.Errorf("set allow_create_organization: %v", err)
}
return nil
}

View File

@@ -1,127 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/markdown"
"github.com/go-xorm/xorm"
)
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Index int
Config map[string]string `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
}
// Enumerate all the unit types
const (
UnitTypeCode = iota + 1 // 1 code
UnitTypeIssues // 2 issues
UnitTypePRs // 3 PRs
UnitTypeCommits // 4 Commits
UnitTypeReleases // 5 Releases
UnitTypeWiki // 6 Wiki
UnitTypeSettings // 7 Settings
UnitTypeExternalWiki // 8 ExternalWiki
UnitTypeExternalTracker // 9 ExternalTracker
)
// Repo describes a repository
type Repo struct {
ID int64
EnableWiki, EnableExternalWiki, EnableIssues, EnableExternalTracker, EnablePulls bool
ExternalWikiURL, ExternalTrackerURL, ExternalTrackerFormat, ExternalTrackerStyle string
}
func addUnitsToTables(x *xorm.Engine) error {
var repos []Repo
err := x.Table("repository").Select("*").Find(&repos)
if err != nil {
return fmt.Errorf("Query repositories: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
var repoUnit RepoUnit
if exist, err := sess.IsTableExist(&repoUnit); err != nil {
return fmt.Errorf("IsExist RepoUnit: %v", err)
} else if exist {
return nil
}
if err := sess.CreateTable(&repoUnit); err != nil {
return fmt.Errorf("CreateTable RepoUnit: %v", err)
}
if err := sess.CreateUniques(&repoUnit); err != nil {
return fmt.Errorf("CreateUniques RepoUnit: %v", err)
}
if err := sess.CreateIndexes(&repoUnit); err != nil {
return fmt.Errorf("CreateIndexes RepoUnit: %v", err)
}
for _, repo := range repos {
for i := 1; i <= 9; i++ {
if (i == UnitTypeWiki || i == UnitTypeExternalWiki) && !repo.EnableWiki {
continue
}
if i == UnitTypeExternalWiki && !repo.EnableExternalWiki {
continue
}
if i == UnitTypePRs && !repo.EnablePulls {
continue
}
if (i == UnitTypeIssues || i == UnitTypeExternalTracker) && !repo.EnableIssues {
continue
}
if i == UnitTypeExternalTracker && !repo.EnableExternalTracker {
continue
}
var config = make(map[string]string)
switch i {
case UnitTypeExternalTracker:
config["ExternalTrackerURL"] = repo.ExternalTrackerURL
config["ExternalTrackerFormat"] = repo.ExternalTrackerFormat
if len(repo.ExternalTrackerStyle) == 0 {
repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric
}
config["ExternalTrackerStyle"] = repo.ExternalTrackerStyle
case UnitTypeExternalWiki:
config["ExternalWikiURL"] = repo.ExternalWikiURL
}
if _, err = sess.Insert(&RepoUnit{
RepoID: repo.ID,
Type: i,
Index: i,
Config: config,
}); err != nil {
return fmt.Errorf("Insert repo unit: %v", err)
}
}
}
if err := sess.Commit(); err != nil {
return err
}
return nil
}

View File

@@ -1,29 +0,0 @@
// Copyright 2016 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"time"
"github.com/go-xorm/xorm"
)
func setProtectedBranchUpdatedWithCreated(x *xorm.Engine) (err error) {
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
}
if err = x.Sync2(new(ProtectedBranch)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}

View File

@@ -1,25 +0,0 @@
// Copyright 2016 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
// ExternalLoginUser makes the connecting between some existing user and additional external login sources
type ExternalLoginUser struct {
ExternalID string `xorm:"NOT NULL"`
UserID int64 `xorm:"NOT NULL"`
LoginSourceID int64 `xorm:"NOT NULL"`
}
func addExternalLoginUser(x *xorm.Engine) error {
if err := x.Sync2(new(ExternalLoginUser)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}

View File

@@ -1,91 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
type Repository struct {
ID int64
OwnerID int64
Name string
}
type User struct {
ID int64
Name string
}
var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/pre-receive.d\"`; do\n sh \"$SHELL_FOLDER/pre-receive.d/$i\"\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/update.d\"`; do\n sh \"$SHELL_FOLDER/update.d/$i\" $1 $2 $3\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/post-receive.d\"`; do\n sh \"$SHELL_FOLDER/post-receive.d/$i\"\ndone", setting.ScriptType),
}
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
}
)
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
user := new(User)
has, err := x.Where("id = ?", repo.OwnerID).Get(user)
if err != nil {
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err)
} else if !has {
return nil
}
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
hookDir := filepath.Join(repoPath, "hooks")
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
customHooksDir := filepath.Join(hookDir, hookName+".d")
// if it's exist, that means you have upgraded ever
if com.IsExist(customHooksDir) {
continue
}
if err = os.MkdirAll(customHooksDir, os.ModePerm); err != nil {
return fmt.Errorf("create hooks dir '%s': %v", customHooksDir, err)
}
// WARNING: Old server-side hooks will be moved to sub directory with the same name
if hookName != "update" && com.IsExist(oldHookPath) {
newPlace := filepath.Join(hookDir, hookName+".d", hookName)
if err = os.Rename(oldHookPath, newPlace); err != nil {
return fmt.Errorf("Remove old hook file '%s' to '%s': %v", oldHookPath, newPlace, err)
}
}
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil {
return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err)
}
if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil {
return fmt.Errorf("write new hook file '%s': %v", oldHookPath, err)
}
}
return nil
})
}

View File

@@ -1,73 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"crypto/md5"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func useNewNameAvatars(x *xorm.Engine) error {
d, err := os.Open(setting.AvatarUploadPath)
if err != nil {
if os.IsNotExist(err) {
// Nothing to do if AvatarUploadPath does not exist
return nil
}
return err
}
names, err := d.Readdirnames(0)
if err != nil {
return err
}
type User struct {
ID int64 `xorm:"pk autoincr"`
Avatar string
UseCustomAvatar bool
}
for _, name := range names {
userID, err := strconv.ParseInt(name, 10, 64)
if err != nil {
log.Warn("ignore avatar %s rename: %v", name, err)
continue
}
var user User
if has, err := x.ID(userID).Get(&user); err != nil {
return err
} else if !has {
return errors.New("Avatar user is not exist")
}
fPath := filepath.Join(setting.AvatarUploadPath, name)
bs, err := ioutil.ReadFile(fPath)
if err != nil {
return err
}
user.Avatar = fmt.Sprintf("%x", md5.Sum(bs))
err = os.Rename(fPath, filepath.Join(setting.AvatarUploadPath, user.Avatar))
if err != nil {
return err
}
_, err = x.ID(userID).Cols("avatar").Update(&user)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,58 +0,0 @@
// Copyright 2017 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"os"
"path/filepath"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
const (
tplCommentPrefix = `# gitea public key`
tplPublicKey = tplCommentPrefix + "\n" + `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
)
func useNewPublickeyFormat(x *xorm.Engine) error {
fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
if !com.IsExist(fpath) {
return nil
}
tmpPath := fpath + ".tmp"
f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer func() {
f.Close()
os.Remove(tmpPath)
}()
type PublicKey struct {
ID int64
Content string
}
err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
key := bean.(*PublicKey)
_, err = f.WriteString(fmt.Sprintf(tplPublicKey, setting.AppPath, key.ID, setting.CustomConf, key.Content))
return err
})
if err != nil {
return err
}
f.Close()
if err = os.Rename(tmpPath, fpath); err != nil {
return err
}
return nil
}

View File

@@ -1,94 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
func generateAndMigrateWikiGitHooks(x *xorm.Engine) (err error) {
type Repository struct {
ID int64
OwnerID int64
Name string
}
type User struct {
ID int64
Name string
}
var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/pre-receive.d\"`; do\n sh \"$SHELL_FOLDER/pre-receive.d/$i\"\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/update.d\"`; do\n sh \"$SHELL_FOLDER/update.d/$i\" $1 $2 $3\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/post-receive.d\"`; do\n sh \"$SHELL_FOLDER/post-receive.d/$i\"\ndone", setting.ScriptType),
}
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
}
)
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
user := new(User)
has, err := x.Where("id = ?", repo.OwnerID).Get(user)
if err != nil {
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err)
} else if !has {
return nil
}
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".wiki.git"
if !com.IsExist(repoPath) {
return nil
}
hookDir := filepath.Join(repoPath, "hooks")
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
customHooksDir := filepath.Join(hookDir, hookName+".d")
// if it's exist, that means you have upgraded ever
if com.IsExist(customHooksDir) {
continue
}
if err = os.MkdirAll(customHooksDir, os.ModePerm); err != nil {
return fmt.Errorf("create hooks dir '%s': %v", customHooksDir, err)
}
// WARNING: Old server-side hooks will be moved to sub directory with the same name
if hookName != "update" && com.IsExist(oldHookPath) {
newPlace := filepath.Join(hookDir, hookName+".d", hookName)
if err = os.Rename(oldHookPath, newPlace); err != nil {
return fmt.Errorf("Remove old hook file '%s' to '%s': %v", oldHookPath, newPlace, err)
}
}
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil {
return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err)
}
if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil {
return fmt.Errorf("write new hook file '%s': %v", oldHookPath, err)
}
}
return nil
})
}

View File

@@ -21,30 +21,21 @@ import (
// Needed for the Postgresql driver
_ "github.com/lib/pq"
// Needed for the MSSSQL driver
_ "github.com/denisenkom/go-mssqldb"
"code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// Engine represents a xorm engine or session.
type Engine interface {
Table(tableNameOrBean interface{}) *xorm.Session
Count(interface{}) (int64, error)
Decr(column string, arg ...interface{}) *xorm.Session
Delete(interface{}) (int64, error)
Exec(string, ...interface{}) (sql.Result, error)
Find(interface{}, ...interface{}) error
Get(interface{}) (bool, error)
Id(interface{}) *xorm.Session
In(string, ...interface{}) *xorm.Session
Incr(column string, arg ...interface{}) *xorm.Session
Insert(...interface{}) (int64, error)
InsertOne(interface{}) (int64, error)
Iterate(interface{}, xorm.IterFunc) error
Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *xorm.Session
SQL(interface{}, ...interface{}) *xorm.Session
Where(interface{}, ...interface{}) *xorm.Session
}
@@ -77,45 +68,15 @@ var (
func init() {
tables = append(tables,
new(User),
new(PublicKey),
new(AccessToken),
new(Repository),
new(DeployKey),
new(Collaboration),
new(Access),
new(Upload),
new(Watch),
new(Star),
new(Follow),
new(Action),
new(Issue),
new(PullRequest),
new(Comment),
new(Attachment),
new(Label),
new(IssueLabel),
new(Milestone),
new(Mirror),
new(Release),
new(LoginSource),
new(Webhook),
new(HookTask),
new(Team),
new(OrgUser),
new(TeamUser),
new(TeamRepo),
new(Notice),
new(EmailAddress),
new(Notification),
new(IssueUser),
new(LFSMetaObject),
new(TwoFactor),
new(RepoUnit),
new(RepoRedirect),
new(ExternalLoginUser),
new(ProtectedBranch),
)
new(User), new(PublicKey), new(AccessToken),
new(Repository), new(DeployKey), new(Collaboration), new(Access), new(Upload),
new(Watch), new(Star), new(Follow), new(Action),
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
new(Label), new(IssueLabel), new(Milestone),
new(Mirror), new(Release), new(LoginSource), new(Webhook),
new(UpdateTask), new(HookTask),
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
new(Notice), new(EmailAddress))
gonicNames := []string{"SSL", "UID"}
for _, name := range gonicNames {
@@ -136,8 +97,6 @@ func LoadConfigs() {
setting.UsePostgreSQL = true
case "tidb":
setting.UseTiDB = true
case "mssql":
setting.UseMSSQL = true
}
DbCfg.Host = sec.Key("HOST").String()
DbCfg.Name = sec.Key("NAME").String()
@@ -147,10 +106,6 @@ func LoadConfigs() {
}
DbCfg.SSLMode = sec.Key("SSL_MODE").String()
DbCfg.Path = sec.Key("PATH").MustString("data/gitea.db")
sec = setting.Cfg.Section("indexer")
setting.Indexer.IssuePath = sec.Key("ISSUE_INDEXER_PATH").MustString("indexers/issues.bleve")
setting.Indexer.UpdateQueueLength = sec.Key("UPDATE_BUFFER_LEN").MustInt(20)
}
// parsePostgreSQLHostPort parses given input in various forms defined in
@@ -168,20 +123,6 @@ func parsePostgreSQLHostPort(info string) (string, string) {
return host, port
}
func parseMSSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "1433"
if strings.Contains(info, ":") {
host = strings.Split(info, ":")[0]
port = strings.Split(info, ":")[1]
} else if strings.Contains(info, ",") {
host = strings.Split(info, ",")[0]
port = strings.TrimSpace(strings.Split(info, ",")[1])
} else if len(info) > 0 {
host = info
}
return host, port
}
func getEngine() (*xorm.Engine, error) {
connStr := ""
var Param = "?"
@@ -206,15 +147,12 @@ func getEngine() (*xorm.Engine, error) {
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
url.QueryEscape(DbCfg.User), url.QueryEscape(DbCfg.Passwd), host, port, DbCfg.Name, Param, DbCfg.SSLMode)
}
case "mssql":
host, port := parseMSSQLHostPort(DbCfg.Host)
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, DbCfg.Name, DbCfg.User, DbCfg.Passwd)
case "sqlite3":
if !EnableSQLite3 {
return nil, errors.New("this binary version does not build support for SQLite3")
}
if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil {
return nil, fmt.Errorf("Failed to create directories: %v", err)
return nil, fmt.Errorf("Fail to create directories: %v", err)
}
connStr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc"
case "tidb":
@@ -222,13 +160,12 @@ func getEngine() (*xorm.Engine, error) {
return nil, errors.New("this binary version does not build support for TiDB")
}
if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil {
return nil, fmt.Errorf("Failed to create directories: %v", err)
return nil, fmt.Errorf("Fail to create directories: %v", err)
}
connStr = "goleveldb://" + DbCfg.Path
default:
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
}
return xorm.NewEngine(DbCfg.Type, connStr)
}
@@ -240,7 +177,6 @@ func NewTestEngine(x *xorm.Engine) (err error) {
}
x.SetMapper(core.GonicMapper{})
x.SetLogger(log.XORMLogger)
return x.StoreEngine("InnoDB").Sync2(tables...)
}
@@ -248,13 +184,24 @@ func NewTestEngine(x *xorm.Engine) (err error) {
func SetEngine() (err error) {
x, err = getEngine()
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
return fmt.Errorf("Fail to connect to database: %v", err)
}
x.SetMapper(core.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout.
x.SetLogger(log.XORMLogger)
logPath := path.Join(setting.LogRootPath, "xorm.log")
if err := os.MkdirAll(path.Dir(logPath), os.ModePerm); err != nil {
return fmt.Errorf("Fail to create dir %s: %v", logPath, err)
}
f, err := os.Create(logPath)
if err != nil {
return fmt.Errorf("Fail to create xorm.log: %v", err)
}
x.SetLogger(xorm.NewSimpleLogger(f))
x.ShowSQL(true)
return nil
}
@@ -314,6 +261,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.Label, _ = x.Count(new(Label))
stats.Counter.HookTask, _ = x.Count(new(HookTask))
stats.Counter.Team, _ = x.Count(new(Team))
stats.Counter.UpdateTask, _ = x.Count(new(UpdateTask))
stats.Counter.Attachment, _ = x.Count(new(Attachment))
return
}
@@ -323,14 +271,7 @@ func Ping() error {
return x.Ping()
}
// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
func DumpDatabase(filePath string, dbType string) error {
var tbs []*core.Table
for _, t := range tables {
tbs = append(tbs, x.TableInfo(t).Table)
}
if len(dbType) > 0 {
return x.DumpTablesToFile(tbs, filePath, core.DbType(dbType))
}
return x.DumpTablesToFile(tbs, filePath)
// DumpDatabase dumps all data from database to file system.
func DumpDatabase(filePath string) error {
return x.DumpAllToFile(filePath)
}

View File

@@ -7,19 +7,27 @@ package models
import (
"testing"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey"
)
func Test_parsePostgreSQLHostPort(t *testing.T) {
test := func(input, expectedHost, expectedPort string) {
host, port := parsePostgreSQLHostPort(input)
assert.Equal(t, expectedHost, host)
assert.Equal(t, expectedPort, port)
testSuites := []struct {
input string
host, port string
}{
{"127.0.0.1:1234", "127.0.0.1", "1234"},
{"127.0.0.1", "127.0.0.1", "5432"},
{"[::1]:1234", "[::1]", "1234"},
{"[::1]", "[::1]", "5432"},
{"/tmp/pg.sock:1234", "/tmp/pg.sock", "1234"},
{"/tmp/pg.sock", "/tmp/pg.sock", "5432"},
}
test("127.0.0.1:1234", "127.0.0.1", "1234")
test("127.0.0.1", "127.0.0.1", "5432")
test("[::1]:1234", "[::1]", "1234")
test("[::1]", "[::1]", "5432")
test("/tmp/pg.sock:1234", "/tmp/pg.sock", "1234")
test("/tmp/pg.sock", "/tmp/pg.sock", "5432")
Convey("Parse PostgreSQL host and port", t, func() {
for _, suite := range testSuites {
host, port := parsePostgreSQLHostPort(suite.input)
So(host, ShouldEqual, suite.host)
So(port, ShouldEqual, suite.port)
}
})
}

View File

@@ -1,289 +0,0 @@
// Copyright 2016 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"
"time"
)
type (
// NotificationStatus is the status of the notification (read or unread)
NotificationStatus uint8
// NotificationSource is the source of the notification (issue, PR, commit, etc)
NotificationSource uint8
)
const (
// NotificationStatusUnread represents an unread notification
NotificationStatusUnread NotificationStatus = iota + 1
// NotificationStatusRead represents a read notification
NotificationStatusRead
// NotificationStatusPinned represents a pinned notification
NotificationStatusPinned
)
const (
// NotificationSourceIssue is a notification of an issue
NotificationSourceIssue NotificationSource = iota + 1
// NotificationSourcePullRequest is a notification of a pull request
NotificationSourcePullRequest
// NotificationSourceCommit is a notification of a commit
NotificationSourceCommit
)
// Notification represents a notification
type Notification struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX NOT NULL"`
RepoID int64 `xorm:"INDEX NOT NULL"`
Status NotificationStatus `xorm:"SMALLINT INDEX NOT NULL"`
Source NotificationSource `xorm:"SMALLINT INDEX NOT NULL"`
IssueID int64 `xorm:"INDEX NOT NULL"`
CommitID string `xorm:"INDEX"`
UpdatedBy int64 `xorm:"INDEX NOT NULL"`
Issue *Issue `xorm:"-"`
Repository *Repository `xorm:"-"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX NOT NULL"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"INDEX NOT NULL"`
}
// BeforeInsert runs while inserting a record
func (n *Notification) BeforeInsert() {
var (
now = time.Now()
nowUnix = now.Unix()
)
n.Created = now
n.CreatedUnix = nowUnix
n.Updated = now
n.UpdatedUnix = nowUnix
}
// BeforeUpdate runs while updating a record
func (n *Notification) BeforeUpdate() {
var (
now = time.Now()
nowUnix = now.Unix()
)
n.Updated = now
n.UpdatedUnix = nowUnix
}
// CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists
func CreateOrUpdateIssueNotifications(issue *Issue, notificationAuthorID int64) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := createOrUpdateIssueNotifications(sess, issue, notificationAuthorID); err != nil {
return err
}
return sess.Commit()
}
func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error {
watches, err := getWatchers(e, issue.RepoID)
if err != nil {
return err
}
notifications, err := getNotificationsByIssueID(e, issue.ID)
if err != nil {
return err
}
for _, watch := range watches {
// do not send notification for the own issuer/commenter
if watch.UserID == notificationAuthorID {
continue
}
if notificationExists(notifications, issue.ID, watch.UserID) {
err = updateIssueNotification(e, watch.UserID, issue.ID, notificationAuthorID)
} else {
err = createIssueNotification(e, watch.UserID, issue, notificationAuthorID)
}
if err != nil {
return err
}
}
return nil
}
func getNotificationsByIssueID(e Engine, issueID int64) (notifications []*Notification, err error) {
err = e.
Where("issue_id = ?", issueID).
Find(&notifications)
return
}
func notificationExists(notifications []*Notification, issueID, userID int64) bool {
for _, notification := range notifications {
if notification.IssueID == issueID && notification.UserID == userID {
return true
}
}
return false
}
func createIssueNotification(e Engine, userID int64, issue *Issue, updatedByID int64) error {
notification := &Notification{
UserID: userID,
RepoID: issue.RepoID,
Status: NotificationStatusUnread,
IssueID: issue.ID,
UpdatedBy: updatedByID,
}
if issue.IsPull {
notification.Source = NotificationSourcePullRequest
} else {
notification.Source = NotificationSourceIssue
}
_, err := e.Insert(notification)
return err
}
func updateIssueNotification(e Engine, userID, issueID, updatedByID int64) error {
notification, err := getIssueNotification(e, userID, issueID)
if err != nil {
return err
}
notification.Status = NotificationStatusUnread
notification.UpdatedBy = updatedByID
_, err = e.Id(notification.ID).Update(notification)
return err
}
func getIssueNotification(e Engine, userID, issueID int64) (*Notification, error) {
notification := new(Notification)
_, err := e.
Where("user_id = ?", userID).
And("issue_id = ?", issueID).
Get(notification)
return notification, err
}
// NotificationsForUser returns notifications for a given user and status
func NotificationsForUser(user *User, statuses []NotificationStatus, page, perPage int) ([]*Notification, error) {
return notificationsForUser(x, user, statuses, page, perPage)
}
func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, page, perPage int) (notifications []*Notification, err error) {
if len(statuses) == 0 {
return
}
sess := e.
Where("user_id = ?", user.ID).
In("status", statuses).
OrderBy("updated_unix DESC")
if page > 0 && perPage > 0 {
sess.Limit(perPage, (page-1)*perPage)
}
err = sess.Find(&notifications)
return
}
// GetRepo returns the repo of the notification
func (n *Notification) GetRepo() (*Repository, error) {
n.Repository = new(Repository)
_, err := x.
Where("id = ?", n.RepoID).
Get(n.Repository)
return n.Repository, err
}
// GetIssue returns the issue of the notification
func (n *Notification) GetIssue() (*Issue, error) {
n.Issue = new(Issue)
_, err := x.
Where("id = ?", n.IssueID).
Get(n.Issue)
return n.Issue, err
}
// GetNotificationCount returns the notification count for user
func GetNotificationCount(user *User, status NotificationStatus) (int64, error) {
return getNotificationCount(x, user, status)
}
func getNotificationCount(e Engine, user *User, status NotificationStatus) (count int64, err error) {
count, err = e.
Where("user_id = ?", user.ID).
And("status = ?", status).
Count(&Notification{})
return
}
func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
notification, err := getIssueNotification(e, userID, issueID)
// ignore if not exists
if err != nil {
return nil
}
if notification.Status != NotificationStatusUnread {
return nil
}
notification.Status = NotificationStatusRead
_, err = e.Id(notification.ID).Update(notification)
return err
}
// SetNotificationStatus change the notification status
func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error {
notification, err := getNotificationByID(notificationID)
if err != nil {
return err
}
if notification.UserID != user.ID {
return fmt.Errorf("Can't change notification of another user: %d, %d", notification.UserID, user.ID)
}
notification.Status = status
_, err = x.Id(notificationID).Update(notification)
return err
}
func getNotificationByID(notificationID int64) (*Notification, error) {
notification := new(Notification)
ok, err := x.
Where("id = ?", notificationID).
Get(notification)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("Notification %d does not exists", notificationID)
}
return notification, nil
}

View File

@@ -1,78 +0,0 @@
// Copyright 2017 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 TestCreateOrUpdateIssueNotifications(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
assert.NoError(t, CreateOrUpdateIssueNotifications(issue, 2))
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
assert.Equal(t, NotificationStatusUnread, notf.Status)
notf = AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification)
assert.Equal(t, NotificationStatusUnread, notf.Status)
CheckConsistencyFor(t, &Issue{ID: issue.ID})
}
func TestNotificationsForUser(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread}
notfs, err := NotificationsForUser(user, statuses, 1, 10)
assert.NoError(t, err)
assert.Len(t, notfs, 1)
assert.EqualValues(t, 2, notfs[0].ID)
assert.EqualValues(t, user.ID, notfs[0].UserID)
}
func TestNotification_GetRepo(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
notf := AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification)
repo, err := notf.GetRepo()
assert.NoError(t, err)
assert.Equal(t, repo, notf.Repository)
assert.EqualValues(t, notf.RepoID, repo.ID)
}
func TestNotification_GetIssue(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
notf := AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification)
issue, err := notf.GetIssue()
assert.NoError(t, err)
assert.Equal(t, issue, notf.Issue)
assert.EqualValues(t, notf.IssueID, issue.ID)
}
func TestGetNotificationCount(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
cnt, err := GetNotificationCount(user, NotificationStatusUnread)
assert.NoError(t, err)
assert.EqualValues(t, 0, cnt)
cnt, err = GetNotificationCount(user, NotificationStatusRead)
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
}
func TestSetNotificationStatus(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
notf := AssertExistsAndLoadBean(t,
&Notification{UserID: user.ID, Status: NotificationStatusRead}).(*Notification)
assert.NoError(t, SetNotificationStatus(notf.ID, user, NotificationStatusPinned))
AssertExistsAndLoadBean(t,
&Notification{ID: notf.ID, Status: NotificationStatusPinned})
assert.Error(t, SetNotificationStatus(1, user, NotificationStatusRead))
assert.Error(t, SetNotificationStatus(NonexistentID, user, NotificationStatusRead))
}

View File

@@ -10,7 +10,6 @@ import (
"os"
"strings"
"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
)
@@ -98,10 +97,6 @@ func (org *User) RemoveOrgRepo(repoID int64) error {
// CreateOrganization creates record of a new organization.
func CreateOrganization(org, owner *User) (err error) {
if !owner.CanCreateOrganization() {
return ErrUserNotAllowedCreateOrg{}
}
if err = IsUsableUsername(org.Name); err != nil {
return err
}
@@ -124,7 +119,6 @@ func CreateOrganization(org, owner *User) (err error) {
org.MaxRepoCreation = -1
org.NumTeams = 1
org.NumMembers = 1
org.Type = UserTypeOrganization
sess := x.NewSession()
defer sessionRelease(sess)
@@ -135,9 +129,7 @@ func CreateOrganization(org, owner *User) (err error) {
if _, err = sess.Insert(org); err != nil {
return fmt.Errorf("insert organization: %v", err)
}
if err = org.generateRandomAvatar(sess); err != nil {
return fmt.Errorf("generate random avatar: %v", err)
}
org.GenerateRandomAvatar()
// Add initial creator to organization and owner team.
if _, err = sess.Insert(&OrgUser{
@@ -203,19 +195,12 @@ func CountOrganizations() int64 {
}
// Organizations returns number of organizations in given page.
func Organizations(opts *SearchUserOptions) ([]*User, error) {
orgs := make([]*User, 0, opts.PageSize)
if len(opts.OrderBy) == 0 {
opts.OrderBy = "name ASC"
}
sess := x.
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
Where("type=1")
return orgs, sess.
OrderBy(opts.OrderBy).
func Organizations(page, pageSize int) ([]*User, error) {
orgs := make([]*User, 0, pageSize)
return orgs, x.
Limit(pageSize, (page-1)*pageSize).
Where("type=1").
Asc("name").
Find(&orgs)
}
@@ -228,65 +213,23 @@ func DeleteOrganization(org *User) (err error) {
return err
}
if err = deleteOrg(sess, org); err != nil {
if IsErrUserOwnRepos(err) {
return err
} else if err != nil {
return fmt.Errorf("deleteOrg: %v", err)
}
if err = deleteBeans(sess,
&Team{OrgID: org.ID},
&OrgUser{OrgID: org.ID},
&TeamUser{OrgID: org.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
if err = deleteUser(sess, org); err != nil {
return fmt.Errorf("deleteUser: %v", err)
}
if err = sess.Commit(); err != nil {
return err
}
return nil
}
func deleteOrg(e *xorm.Session, u *User) error {
if !u.IsOrganization() {
return fmt.Errorf("You can't delete none organization user: %s", u.Name)
}
// Check ownership of repository.
count, err := getRepositoryCount(e, u)
if err != nil {
return fmt.Errorf("GetRepositoryCount: %v", err)
} else if count > 0 {
return ErrUserOwnRepos{UID: u.ID}
}
if err := deleteBeans(e,
&Team{OrgID: u.ID},
&OrgUser{OrgID: u.ID},
&TeamUser{OrgID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
if _, err = e.Id(u.ID).Delete(new(User)); err != nil {
return fmt.Errorf("Delete: %v", err)
}
// FIXME: system notice
// Note: There are something just cannot be roll back,
// so just keep error logs of those operations.
path := UserPath(u.Name)
if err := os.RemoveAll(path); err != nil {
return fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
}
if len(u.Avatar) > 0 {
avatarPath := u.CustomAvatarPath()
if com.IsExist(avatarPath) {
if err := os.Remove(avatarPath); err != nil {
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
}
}
}
return nil
return RewriteAllPublicKeys()
}
// ________ ____ ___
@@ -301,7 +244,7 @@ type OrgUser struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX UNIQUE(s)"`
OrgID int64 `xorm:"INDEX UNIQUE(s)"`
IsPublic bool `xorm:"INDEX"`
IsPublic bool
IsOwner bool
NumTeams int
}
@@ -350,9 +293,13 @@ func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, e
// GetOrgsByUserID returns a list of organizations that the given user ID
// has joined.
func GetOrgsByUserID(userID int64, showAll bool) ([]*User, error) {
sess := x.NewSession()
defer sess.Close()
return getOrgsByUserID(sess, userID, showAll)
return getOrgsByUserID(x.NewSession(), userID, showAll)
}
// GetOrgsByUserIDDesc returns a list of organizations that the given user ID
// has joined, ordered descending by the given condition.
func GetOrgsByUserIDDesc(userID int64, desc string, showAll bool) ([]*User, error) {
return getOrgsByUserID(x.NewSession().Desc(desc), userID, showAll)
}
func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
@@ -368,14 +315,14 @@ func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
sess := x.NewSession()
defer sess.Close()
return getOwnedOrgsByUserID(sess, userID)
}
// GetOwnedOrgsByUserIDDesc returns a list of organizations are owned by
// given user ID, ordered descending by the given condition.
func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
return getOwnedOrgsByUserID(x.Desc(desc), userID)
sess := x.NewSession()
return getOwnedOrgsByUserID(sess.Desc(desc), userID)
}
// GetOrgUsersByUserID returns all organization-user relations by user ID.
@@ -463,11 +410,21 @@ func RemoveOrgUser(orgID, userID int64) error {
return nil
}
user, err := GetUserByID(userID)
if err != nil {
return fmt.Errorf("GetUserByID [%d]: %v", userID, err)
}
org, err := GetUserByID(orgID)
if err != nil {
return fmt.Errorf("GetUserByID [%d]: %v", orgID, err)
}
// FIXME: only need to get IDs here, not all fields of repository.
repos, _, err := org.GetUserRepositories(user.ID, 1, org.NumRepos)
if err != nil {
return fmt.Errorf("GetUserRepositories [%d]: %v", user.ID, err)
}
// Check if the user to delete is the last member in owner team.
if IsOrganizationOwner(orgID, userID) {
t, err := org.GetOwnerTeam()
@@ -492,23 +449,17 @@ func RemoveOrgUser(orgID, userID int64) error {
}
// Delete all repository accesses and unwatch them.
env, err := org.AccessibleReposEnv(userID)
if err != nil {
return fmt.Errorf("AccessibleReposEnv: %v", err)
}
repoIDs, err := env.RepoIDs(1, org.NumRepos)
if err != nil {
return fmt.Errorf("GetUserRepositories [%d]: %v", userID, err)
}
for _, repoID := range repoIDs {
if err = watchRepo(sess, userID, repoID, false); err != nil {
repoIDs := make([]int64, len(repos))
for i := range repos {
repoIDs = append(repoIDs, repos[i].ID)
if err = watchRepo(sess, user.ID, repos[i].ID, false); err != nil {
return err
}
}
if len(repoIDs) > 0 {
if _, err = sess.
Where("user_id = ?", userID).
Where("user_id = ?", user.ID).
In("repo_id", repoIDs).
Delete(new(Access)); err != nil {
return err
@@ -516,12 +467,12 @@ func RemoveOrgUser(orgID, userID int64) error {
}
// Delete member in his/her teams.
teams, err := getUserTeams(sess, org.ID, userID)
teams, err := getUserTeams(sess, org.ID, user.ID)
if err != nil {
return err
}
for _, t := range teams {
if err = removeTeamMember(sess, t, userID); err != nil {
if err = removeTeamMember(sess, org.ID, t.ID, user.ID); err != nil {
return err
}
}
@@ -530,31 +481,18 @@ func RemoveOrgUser(orgID, userID int64) error {
}
func removeOrgRepo(e Engine, orgID, repoID int64) error {
teamRepos := make([]*TeamRepo, 0, 10)
if err := e.Find(&teamRepos, &TeamRepo{OrgID: orgID, RepoID: repoID}); err != nil {
return err
}
if len(teamRepos) == 0 {
return nil
}
if _, err := e.Delete(&TeamRepo{
_, err := e.Delete(&TeamRepo{
OrgID: orgID,
RepoID: repoID,
}); err != nil {
return err
}
teamIDs := make([]int64, len(teamRepos))
for i, teamRepo := range teamRepos {
teamIDs[i] = teamRepo.ID
}
_, err := x.Decr("num_repos").In("id", teamIDs).Update(new(Team))
})
return err
}
// RemoveOrgRepo removes all team-repository relations of given organization.
func RemoveOrgRepo(orgID, repoID int64) error {
return removeOrgRepo(x, orgID, repoID)
}
func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team, error) {
teams := make([]*Team, 0, org.NumTeams)
return teams, e.
@@ -567,20 +505,18 @@ func (org *User) getUserTeams(e Engine, userID int64, cols ...string) ([]*Team,
Find(&teams)
}
func (org *User) getUserTeamIDs(e Engine, userID int64) ([]int64, error) {
teamIDs := make([]int64, 0, org.NumTeams)
return teamIDs, e.
Table("team").
Cols("team.id").
Where("`team_user`.org_id = ?", org.ID).
Join("INNER", "team_user", "`team_user`.team_id = team.id").
And("`team_user`.uid = ?", userID).
Find(&teamIDs)
}
// GetUserTeamIDs returns of all team IDs of the organization that user is member of.
func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) {
return org.getUserTeamIDs(x, userID)
teams, err := org.getUserTeams(x, userID, "team.id")
if err != nil {
return nil, fmt.Errorf("getUserTeams [%d]: %v", userID, err)
}
teamIDs := make([]int64, len(teams))
for i := range teams {
teamIDs[i] = teams[i].ID
}
return teamIDs, nil
}
// GetUserTeams returns all teams that belong to user,
@@ -589,93 +525,65 @@ func (org *User) GetUserTeams(userID int64) ([]*Team, error) {
return org.getUserTeams(x, userID)
}
// AccessibleReposEnvironment operations involving the repositories that are
// accessible to a particular user
type AccessibleReposEnvironment interface {
CountRepos() (int64, error)
RepoIDs(page, pageSize int) ([]int64, error)
Repos(page, pageSize int) ([]*Repository, error)
MirrorRepos() ([]*Repository, error)
}
type accessibleReposEnv struct {
org *User
userID int64
teamIDs []int64
}
// AccessibleReposEnv an AccessibleReposEnvironment for the repositories in `org`
// that are accessible to the specified user.
func (org *User) AccessibleReposEnv(userID int64) (AccessibleReposEnvironment, error) {
// GetUserRepositories returns a range of repositories in organization
// that the user with the given userID has access to,
// and total number of records based on given condition.
func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repository, int64, error) {
teamIDs, err := org.GetUserTeamIDs(userID)
if err != nil {
return nil, err
return nil, 0, fmt.Errorf("GetUserTeamIDs: %v", err)
}
if len(teamIDs) == 0 {
// user has no team but "IN ()" is invalid SQL
teamIDs = []int64{-1} // there is no repo with id=-1
}
return &accessibleReposEnv{org: org, userID: userID, teamIDs: teamIDs}, nil
}
func (env *accessibleReposEnv) cond() builder.Cond {
var cond builder.Cond = builder.Eq{
"`repository`.owner_id": env.org.ID,
"`repository`.is_private": false,
}
if len(env.teamIDs) > 0 {
cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs))
}
return cond
}
func (env *accessibleReposEnv) CountRepos() (int64, error) {
repoCount, err := x.
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
Where(env.cond()).
Distinct("`repository`.id").
Count(&Repository{})
if err != nil {
return 0, fmt.Errorf("count user repositories in organization: %v", err)
}
return repoCount, nil
}
func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) {
if page <= 0 {
page = 1
}
repoIDs := make([]int64, 0, pageSize)
return repoIDs, x.
Table("repository").
repos := make([]*Repository, 0, pageSize)
if err := x.
Select("`repository`.*").
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
Where(env.cond()).
GroupBy("`repository`.id,`repository`.updated_unix").
Where("(`repository`.owner_id=? AND `repository`.is_private=?)", org.ID, false).
Or(builder.In("team_repo.team_id", teamIDs)).
GroupBy("`repository`.id").
OrderBy("updated_unix DESC").
Limit(pageSize, (page-1)*pageSize).
Cols("`repository`.id").
Find(&repoIDs)
}
Find(&repos); err != nil {
return nil, 0, fmt.Errorf("get repositories: %v", err)
}
func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*Repository, error) {
repoIDs, err := env.RepoIDs(page, pageSize)
repoCount, err := x.
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
Where("(`repository`.owner_id=? AND `repository`.is_private=?)", org.ID, false).
Or(builder.In("team_repo.team_id", teamIDs)).
GroupBy("`repository`.id").
Count(&Repository{})
if err != nil {
return nil, fmt.Errorf("GetUserRepositoryIDs: %v", err)
return nil, 0, fmt.Errorf("count user repositories in organization: %v", err)
}
repos := make([]*Repository, 0, len(repoIDs))
if len(repoIDs) <= 0 {
return repos, nil
}
return repos, x.
In("`repository`.id", repoIDs).
Find(&repos)
return repos, repoCount, nil
}
func (env *accessibleReposEnv) MirrorRepos() ([]*Repository, error) {
// GetUserMirrorRepositories returns mirror repositories of the user
// that the user with the given userID has access to.
func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
teamIDs, err := org.GetUserTeamIDs(userID)
if err != nil {
return nil, fmt.Errorf("GetUserTeamIDs: %v", err)
}
if len(teamIDs) == 0 {
teamIDs = []int64{-1}
}
repos := make([]*Repository, 0, 10)
return repos, x.
Select("`repository`.*").
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true).
Where(env.cond()).
Where("(`repository`.owner_id=? AND `repository`.is_private=?)", org.ID, false).
Or(builder.In("team_repo.team_id", teamIDs)).
GroupBy("`repository`.id").
OrderBy("updated_unix DESC").
Find(&repos)

View File

@@ -36,9 +36,23 @@ func (t *Team) IsMember(userID int64) bool {
return IsTeamMember(t.OrgID, t.ID, userID)
}
func (t *Team) getRepositories(e Engine) error {
return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
Where("team_repo.team_id=?", t.ID).Find(&t.Repos)
func (t *Team) getRepositories(e Engine) (err error) {
teamRepos := make([]*TeamRepo, 0, t.NumRepos)
if err = x.
Where("team_id=?", t.ID).
Find(&teamRepos); err != nil {
return fmt.Errorf("get team-repos: %v", err)
}
t.Repos = make([]*Repository, 0, len(teamRepos))
for i := range teamRepos {
repo, err := getRepositoryByID(e, teamRepos[i].RepoID)
if err != nil {
return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err)
}
t.Repos = append(t.Repos, repo)
}
return nil
}
// GetRepositories returns all repositories in team of organization.
@@ -59,12 +73,12 @@ func (t *Team) GetMembers() (err error) {
// AddMember adds new membership of the team to the organization,
// the user will have membership to the organization automatically when needed.
func (t *Team) AddMember(userID int64) error {
return AddTeamMember(t, userID)
return AddTeamMember(t.OrgID, t.ID, userID)
}
// RemoveMember removes member from team of organization.
func (t *Team) RemoveMember(userID int64) error {
return RemoveTeamMember(t, userID)
return RemoveTeamMember(t.OrgID, t.ID, userID)
}
func (t *Team) hasRepository(e Engine, repoID int64) bool {
@@ -183,13 +197,16 @@ func (t *Team) RemoveRepository(repoID int64) error {
}
// IsUsableTeamName tests if a name could be as team name
func IsUsableTeamName(name string) error {
switch name {
case "new":
return ErrNameReserved{name}
default:
return nil
func IsUsableTeamName(name string) (err error) {
var reservedTeamNames = []string{"new"}
for i := range reservedTeamNames {
if name == reservedTeamNames[i] {
return ErrNameReserved{name}
}
}
return nil
}
// NewTeam creates a record of new team.
@@ -292,7 +309,7 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
}
t.LowerName = strings.ToLower(t.Name)
has, err := sess.
has, err := x.
Where("org_id=?", t.OrgID).
And("lower_name=?", t.LowerName).
And("id!=?", t.ID).
@@ -310,7 +327,7 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
// Update access for team members if needed.
if authChanged {
if err = t.getRepositories(sess); err != nil {
return fmt.Errorf("getRepositories: %v", err)
return fmt.Errorf("getRepositories:%v", err)
}
for _, repo := range t.Repos {
@@ -330,40 +347,39 @@ func DeleteTeam(t *Team) error {
return err
}
// Get organization.
org, err := GetUserByID(t.OrgID)
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
if err = sess.Begin(); err != nil {
return err
}
// Delete all accesses.
for _, repo := range t.Repos {
if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
if err = repo.recalculateTeamAccesses(sess, t.ID); err != nil {
return err
}
}
// Delete team-repo
if _, err := sess.
Where("team_id=?", t.ID).
Delete(new(TeamRepo)); err != nil {
return err
}
// Delete team-user.
if _, err := sess.
Where("org_id=?", t.OrgID).
if _, err = sess.
Where("org_id=?", org.ID).
Where("team_id=?", t.ID).
Delete(new(TeamUser)); err != nil {
return err
}
// Delete team.
if _, err := sess.Id(t.ID).Delete(new(Team)); err != nil {
if _, err = sess.Id(t.ID).Delete(new(Team)); err != nil {
return err
}
// Update organization number of teams.
if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
return err
}
@@ -422,12 +438,27 @@ func GetTeamMembers(teamID int64) ([]*User, error) {
return getTeamMembers(x, teamID)
}
func getUserTeams(e Engine, orgID, userID int64) (teams []*Team, err error) {
return teams, e.
Join("INNER", "team_user", "team_user.team_id = team.id").
Where("team.org_id = ?", orgID).
And("team_user.uid=?", userID).
Find(&teams)
func getUserTeams(e Engine, orgID, userID int64) ([]*Team, error) {
tus := make([]*TeamUser, 0, 5)
if err := e.
Where("uid=?", userID).
And("org_id=?", orgID).
Find(&tus); err != nil {
return nil, err
}
ts := make([]*Team, len(tus))
for i, tu := range tus {
t := new(Team)
has, err := e.Id(tu.TeamID).Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrTeamNotExist
}
ts[i] = t
}
return ts, nil
}
// GetUserTeams returns all teams that user belongs to in given organization.
@@ -437,111 +468,129 @@ func GetUserTeams(orgID, userID int64) ([]*Team, error) {
// AddTeamMember adds new membership of given team to given organization,
// the user will have membership to given organization automatically when needed.
func AddTeamMember(team *Team, userID int64) error {
if IsTeamMember(team.OrgID, team.ID, userID) {
func AddTeamMember(orgID, teamID, userID int64) error {
if IsTeamMember(orgID, teamID, userID) {
return nil
}
if err := AddOrgUser(team.OrgID, userID); err != nil {
if err := AddOrgUser(orgID, userID); err != nil {
return err
}
// Get team and its repositories.
team.NumMembers++
t, err := GetTeamByID(teamID)
if err != nil {
return err
}
t.NumMembers++
if err := team.GetRepositories(); err != nil {
if err = t.GetRepositories(); err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
if err = sess.Begin(); err != nil {
return err
}
if _, err := sess.Insert(&TeamUser{
tu := &TeamUser{
UID: userID,
OrgID: team.OrgID,
TeamID: team.ID,
}); err != nil {
OrgID: orgID,
TeamID: teamID,
}
if _, err = sess.Insert(tu); err != nil {
return err
} else if _, err := sess.Id(team.ID).Update(team); err != nil {
} else if _, err = sess.Id(t.ID).Update(t); err != nil {
return err
}
// Give access to team repositories.
for _, repo := range team.Repos {
if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
return err
}
}
// We make sure it exists before.
ou := new(OrgUser)
if _, err := sess.
if _, err = sess.
Where("uid = ?", userID).
And("org_id = ?", team.OrgID).
And("org_id = ?", orgID).
Get(ou); err != nil {
return err
}
ou.NumTeams++
if team.IsOwnerTeam() {
if t.IsOwnerTeam() {
ou.IsOwner = true
}
if _, err := sess.Id(ou.ID).AllCols().Update(ou); err != nil {
if _, err = sess.Id(ou.ID).AllCols().Update(ou); err != nil {
return err
}
return sess.Commit()
}
func removeTeamMember(e Engine, team *Team, userID int64) error {
if !isTeamMember(e, team.OrgID, team.ID, userID) {
func removeTeamMember(e Engine, orgID, teamID, userID int64) error {
if !isTeamMember(e, orgID, teamID, userID) {
return nil
}
// Get team and its repositories.
t, err := getTeamByID(e, teamID)
if err != nil {
return err
}
// Check if the user to delete is the last member in owner team.
if team.IsOwnerTeam() && team.NumMembers == 1 {
if t.IsOwnerTeam() && t.NumMembers == 1 {
return ErrLastOrgOwner{UID: userID}
}
team.NumMembers--
t.NumMembers--
if err := team.getRepositories(e); err != nil {
if err = t.getRepositories(e); err != nil {
return err
}
if _, err := e.Delete(&TeamUser{
// Get organization.
org, err := getUserByID(e, orgID)
if err != nil {
return err
}
tu := &TeamUser{
UID: userID,
OrgID: team.OrgID,
TeamID: team.ID,
}); err != nil {
OrgID: orgID,
TeamID: teamID,
}
if _, err := e.Delete(tu); err != nil {
return err
} else if _, err = e.
Id(team.ID).
Id(t.ID).
AllCols().
Update(team); err != nil {
Update(t); err != nil {
return err
}
// Delete access to team repositories.
for _, repo := range team.Repos {
if err := repo.recalculateTeamAccesses(e, 0); err != nil {
for _, repo := range t.Repos {
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
return err
}
}
// This must exist.
ou := new(OrgUser)
_, err := e.
_, err = e.
Where("uid = ?", userID).
And("org_id = ?", team.OrgID).
And("org_id = ?", org.ID).
Get(ou)
if err != nil {
return err
}
ou.NumTeams--
if team.IsOwnerTeam() {
if t.IsOwnerTeam() {
ou.IsOwner = false
}
if _, err = e.
@@ -554,13 +603,13 @@ func removeTeamMember(e Engine, team *Team, userID int64) error {
}
// RemoveTeamMember removes member from given team of given organization.
func RemoveTeamMember(team *Team, userID int64) error {
func RemoveTeamMember(orgID, teamID, userID int64) error {
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return err
}
if err := removeTeamMember(sess, team, userID); err != nil {
if err := removeTeamMember(sess, orgID, teamID, userID); err != nil {
return err
}
return sess.Commit()
@@ -604,6 +653,11 @@ func addTeamRepo(e Engine, orgID, teamID, repoID int64) error {
return err
}
// AddTeamRepo adds new repository relation to team.
func AddTeamRepo(orgID, teamID, repoID int64) error {
return addTeamRepo(x, orgID, teamID, repoID)
}
func removeTeamRepo(e Engine, teamID, repoID int64) error {
_, err := e.Delete(&TeamRepo{
TeamID: teamID,
@@ -611,3 +665,8 @@ func removeTeamRepo(e Engine, teamID, repoID int64) error {
})
return err
}
// RemoveTeamRepo deletes repository relation to team.
func RemoveTeamRepo(teamID, repoID int64) error {
return removeTeamRepo(x, teamID, repoID)
}

View File

@@ -1,343 +0,0 @@
// Copyright 2017 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 (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTeam_IsOwnerTeam(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team)
assert.True(t, team.IsOwnerTeam())
team = AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team)
assert.False(t, team.IsOwnerTeam())
}
func TestTeam_IsMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team)
assert.True(t, team.IsMember(2))
assert.False(t, team.IsMember(4))
assert.False(t, team.IsMember(NonexistentID))
team = AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team)
assert.True(t, team.IsMember(2))
assert.True(t, team.IsMember(4))
assert.False(t, team.IsMember(NonexistentID))
}
func TestTeam_GetRepositories(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.GetRepositories())
assert.Len(t, team.Repos, team.NumRepos)
for _, repo := range team.Repos {
AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repo.ID})
}
}
test(1)
test(3)
}
func TestTeam_GetMembers(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.GetMembers())
assert.Len(t, team.Members, team.NumMembers)
for _, member := range team.Members {
AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID})
}
}
test(1)
test(3)
}
func TestTeam_AddMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID, userID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.AddMember(userID))
AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID})
CheckConsistencyFor(t, &Team{ID: teamID}, &User{ID: team.OrgID})
}
test(1, 2)
test(1, 4)
test(3, 2)
}
func TestTeam_RemoveMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(teamID, userID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.RemoveMember(userID))
AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID})
CheckConsistencyFor(t, &Team{ID: teamID})
}
testSuccess(1, 4)
testSuccess(2, 2)
testSuccess(3, 2)
testSuccess(3, NonexistentID)
team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team)
err := team.RemoveMember(2)
assert.True(t, IsErrLastOrgOwner(err))
}
func TestTeam_HasRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID, repoID int64, expected bool) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.Equal(t, expected, team.HasRepository(repoID))
}
test(1, 1, false)
test(1, 3, true)
test(1, 5, true)
test(1, NonexistentID, false)
test(2, 3, true)
test(2, 5, false)
}
func TestTeam_AddRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSucess := func(teamID, repoID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
assert.NoError(t, team.AddRepository(repo))
AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID})
CheckConsistencyFor(t, &Team{ID: teamID}, &Repository{ID: repoID})
}
testSucess(2, 3)
testSucess(2, 5)
team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
assert.Error(t, team.AddRepository(repo))
CheckConsistencyFor(t, &Team{ID: 1}, &Repository{ID: 1})
}
func TestTeam_RemoveRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(teamID, repoID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.RemoveRepository(repoID))
AssertNotExistsBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID})
CheckConsistencyFor(t, &Team{ID: teamID}, &Repository{ID: repoID})
}
testSuccess(2, 3)
testSuccess(2, 5)
testSuccess(1, NonexistentID)
}
func TestIsUsableTeamName(t *testing.T) {
assert.NoError(t, IsUsableTeamName("usable"))
assert.True(t, IsErrNameReserved(IsUsableTeamName("new")))
}
func TestNewTeam(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
const teamName = "newTeamName"
team := &Team{Name: teamName, OrgID: 3}
assert.NoError(t, NewTeam(team))
AssertExistsAndLoadBean(t, &Team{Name: teamName})
CheckConsistencyFor(t, &Team{}, &User{ID: team.OrgID})
}
func TestGetTeam(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(orgID int64, name string) {
team, err := GetTeam(orgID, name)
assert.NoError(t, err)
assert.EqualValues(t, orgID, team.OrgID)
assert.Equal(t, name, team.Name)
}
testSuccess(3, "Owners")
testSuccess(3, "team1")
_, err := GetTeam(3, "nonexistent")
assert.Error(t, err)
_, err = GetTeam(NonexistentID, "Owners")
assert.Error(t, err)
}
func TestGetTeamByID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(teamID int64) {
team, err := GetTeamByID(teamID)
assert.NoError(t, err)
assert.EqualValues(t, teamID, team.ID)
}
testSuccess(1)
testSuccess(2)
testSuccess(3)
testSuccess(4)
_, err := GetTeamByID(NonexistentID)
assert.Error(t, err)
}
func TestUpdateTeam(t *testing.T) {
// successful update
assert.NoError(t, PrepareTestDatabase())
team := AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team)
team.LowerName = "newname"
team.Name = "newName"
team.Description = strings.Repeat("A long description!", 100)
team.Authorize = AccessModeAdmin
assert.NoError(t, UpdateTeam(team, true))
team = AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team)
assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
access := AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: 3}).(*Access)
assert.EqualValues(t, AccessModeAdmin, access.Mode)
CheckConsistencyFor(t, &Team{ID: team.ID})
}
func TestUpdateTeam2(t *testing.T) {
// update to already-existing team
assert.NoError(t, PrepareTestDatabase())
team := AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team)
team.LowerName = "owners"
team.Name = "Owners"
team.Description = strings.Repeat("A long description!", 100)
err := UpdateTeam(team, true)
assert.True(t, IsErrTeamAlreadyExist(err))
CheckConsistencyFor(t, &Team{ID: team.ID})
}
func TestDeleteTeam(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
team := AssertExistsAndLoadBean(t, &Team{ID: 2}).(*Team)
assert.NoError(t, DeleteTeam(team))
AssertNotExistsBean(t, &Team{ID: team.ID})
AssertNotExistsBean(t, &TeamRepo{TeamID: team.ID})
AssertNotExistsBean(t, &TeamUser{TeamID: team.ID})
// check that team members don't have "leftover" access to repos
user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
accessMode, err := AccessLevel(user, repo)
assert.NoError(t, err)
assert.True(t, accessMode < AccessModeWrite)
}
func TestIsTeamMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsTeamMember(3, 1, 2))
assert.False(t, IsTeamMember(3, 1, 4))
assert.False(t, IsTeamMember(3, 1, NonexistentID))
assert.True(t, IsTeamMember(3, 2, 2))
assert.True(t, IsTeamMember(3, 2, 4))
assert.False(t, IsTeamMember(3, NonexistentID, NonexistentID))
assert.False(t, IsTeamMember(NonexistentID, NonexistentID, NonexistentID))
}
func TestGetTeamMembers(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
members, err := GetTeamMembers(teamID)
assert.NoError(t, err)
assert.Len(t, members, team.NumMembers)
for _, member := range members {
AssertExistsAndLoadBean(t, &TeamUser{UID: member.ID, TeamID: teamID})
}
}
test(1)
test(3)
}
func TestGetUserTeams(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(orgID, userID int64) {
teams, err := GetUserTeams(orgID, userID)
assert.NoError(t, err)
for _, team := range teams {
assert.EqualValues(t, orgID, team.OrgID)
AssertExistsAndLoadBean(t, &TeamUser{TeamID: team.ID, UID: userID})
}
}
test(3, 2)
test(3, 4)
test(3, NonexistentID)
}
func TestAddTeamMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID, userID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, AddTeamMember(team, userID))
AssertExistsAndLoadBean(t, &TeamUser{UID: userID, TeamID: teamID})
CheckConsistencyFor(t, &Team{ID: teamID}, &User{ID: team.OrgID})
}
test(1, 2)
test(1, 4)
test(3, 2)
}
func TestRemoveTeamMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(teamID, userID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, RemoveTeamMember(team, userID))
AssertNotExistsBean(t, &TeamUser{UID: userID, TeamID: teamID})
CheckConsistencyFor(t, &Team{ID: teamID})
}
testSuccess(1, 4)
testSuccess(2, 2)
testSuccess(3, 2)
testSuccess(3, NonexistentID)
team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team)
err := RemoveTeamMember(team, 2)
assert.True(t, IsErrLastOrgOwner(err))
}
func TestHasTeamRepo(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
test := func(teamID, repoID int64, expected bool) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.Equal(t, expected, HasTeamRepo(team.OrgID, teamID, repoID))
}
test(1, 1, false)
test(1, 3, true)
test(1, 5, true)
test(1, NonexistentID, false)
test(2, 3, true)
test(2, 5, false)
}

View File

@@ -1,536 +0,0 @@
// Copyright 2017 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 TestUser_IsOwnedBy(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.True(t, org.IsOwnedBy(2))
assert.False(t, org.IsOwnedBy(1))
assert.False(t, org.IsOwnedBy(3))
assert.False(t, org.IsOwnedBy(4))
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.False(t, nonOrg.IsOwnedBy(2))
assert.False(t, nonOrg.IsOwnedBy(3))
}
func TestUser_IsOrgMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.True(t, org.IsOrgMember(2))
assert.True(t, org.IsOrgMember(4))
assert.False(t, org.IsOrgMember(1))
assert.False(t, org.IsOrgMember(3))
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.False(t, nonOrg.IsOrgMember(2))
assert.False(t, nonOrg.IsOrgMember(3))
}
func TestUser_GetTeam(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
team, err := org.GetTeam("team1")
assert.NoError(t, err)
assert.Equal(t, org.ID, team.OrgID)
assert.Equal(t, "team1", team.LowerName)
_, err = org.GetTeam("does not exist")
assert.Equal(t, ErrTeamNotExist, err)
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
_, err = nonOrg.GetTeam("team")
assert.Equal(t, ErrTeamNotExist, err)
}
func TestUser_GetOwnerTeam(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
team, err := org.GetOwnerTeam()
assert.NoError(t, err)
assert.Equal(t, org.ID, team.OrgID)
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
_, err = nonOrg.GetOwnerTeam()
assert.Equal(t, ErrTeamNotExist, err)
}
func TestUser_GetTeams(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.NoError(t, org.GetTeams())
assert.Len(t, org.Teams, 2)
assert.Equal(t, int64(1), org.Teams[0].ID)
assert.Equal(t, int64(2), org.Teams[1].ID)
}
func TestUser_GetMembers(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.NoError(t, org.GetMembers())
assert.Len(t, org.Members, 2)
assert.Equal(t, int64(2), org.Members[0].ID)
assert.Equal(t, int64(4), org.Members[1].ID)
}
func TestUser_AddMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
// add a user that is not a member
AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3})
prevNumMembers := org.NumMembers
assert.NoError(t, org.AddMember(5))
AssertExistsAndLoadBean(t, &OrgUser{UID: 5, OrgID: 3})
org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.Equal(t, prevNumMembers+1, org.NumMembers)
// add a user that is already a member
AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3})
prevNumMembers = org.NumMembers
assert.NoError(t, org.AddMember(4))
AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3})
org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.Equal(t, prevNumMembers, org.NumMembers)
CheckConsistencyFor(t, &User{})
}
func TestUser_RemoveMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
// remove a user that is a member
AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3})
prevNumMembers := org.NumMembers
assert.NoError(t, org.RemoveMember(4))
AssertNotExistsBean(t, &OrgUser{UID: 4, OrgID: 3})
org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.Equal(t, prevNumMembers-1, org.NumMembers)
// remove a user that is not a member
AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3})
prevNumMembers = org.NumMembers
assert.NoError(t, org.RemoveMember(5))
AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3})
org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
assert.Equal(t, prevNumMembers, org.NumMembers)
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestUser_RemoveOrgRepo(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{OwnerID: org.ID}).(*Repository)
// remove a repo that does belong to org
AssertExistsAndLoadBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID})
assert.NoError(t, org.RemoveOrgRepo(repo.ID))
AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID})
AssertExistsAndLoadBean(t, &Repository{ID: repo.ID}) // repo should still exist
// remove a repo that does not belong to org
assert.NoError(t, org.RemoveOrgRepo(repo.ID))
AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID})
assert.NoError(t, org.RemoveOrgRepo(NonexistentID))
CheckConsistencyFor(t,
&User{ID: org.ID},
&Team{OrgID: org.ID},
&Repository{ID: repo.ID})
}
func TestCreateOrganization(t *testing.T) {
// successful creation of org
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
const newOrgName = "neworg"
org := &User{
Name: newOrgName,
}
AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization})
assert.NoError(t, CreateOrganization(org, owner))
org = AssertExistsAndLoadBean(t,
&User{Name: newOrgName, Type: UserTypeOrganization}).(*User)
ownerTeam := AssertExistsAndLoadBean(t,
&Team{Name: ownerTeamName, OrgID: org.ID}).(*Team)
AssertExistsAndLoadBean(t, &TeamUser{UID: owner.ID, TeamID: ownerTeam.ID})
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestCreateOrganization2(t *testing.T) {
// unauthorized creation of org
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
const newOrgName = "neworg"
org := &User{
Name: newOrgName,
}
AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization})
err := CreateOrganization(org, owner)
assert.Error(t, err)
assert.True(t, IsErrUserNotAllowedCreateOrg(err))
AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization})
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestCreateOrganization3(t *testing.T) {
// create org with same name as existent org
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
org := &User{Name: "user3"} // should already exist
AssertExistsAndLoadBean(t, &User{Name: org.Name}) // sanity check
err := CreateOrganization(org, owner)
assert.Error(t, err)
assert.True(t, IsErrUserAlreadyExist(err))
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestCreateOrganization4(t *testing.T) {
// create org with unusable name
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
err := CreateOrganization(&User{Name: "assets"}, owner)
assert.Error(t, err)
assert.True(t, IsErrNameReserved(err))
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestGetOrgByName(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org, err := GetOrgByName("user3")
assert.NoError(t, err)
assert.EqualValues(t, 3, org.ID)
assert.Equal(t, "user3", org.Name)
org, err = GetOrgByName("user2") // user2 is an individual
assert.Equal(t, ErrOrgNotExist, err)
org, err = GetOrgByName("") // corner case
assert.Equal(t, ErrOrgNotExist, err)
}
func TestCountOrganizations(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
expected, err := x.Where("type=?", UserTypeOrganization).Count(&User{})
assert.NoError(t, err)
assert.Equal(t, expected, CountOrganizations())
}
func TestOrganizations(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(opts *SearchUserOptions, expectedOrgIDs []int64) {
orgs, err := Organizations(opts)
assert.NoError(t, err)
assert.Len(t, orgs, len(expectedOrgIDs))
for i, expectedOrgID := range expectedOrgIDs {
assert.EqualValues(t, expectedOrgID, orgs[i].ID)
}
}
testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, PageSize: 2},
[]int64{3, 6})
testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
[]int64{7})
testSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
[]int64{})
}
func TestDeleteOrganization(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 6}).(*User)
assert.NoError(t, DeleteOrganization(org))
AssertNotExistsBean(t, &User{ID: 6})
AssertNotExistsBean(t, &OrgUser{OrgID: 6})
AssertNotExistsBean(t, &Team{OrgID: 6})
org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
err := DeleteOrganization(org)
assert.Error(t, err)
assert.True(t, IsErrUserOwnRepos(err))
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
assert.Error(t, DeleteOrganization(nonOrg))
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestIsOrganizationOwner(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsOrganizationOwner(3, 2))
assert.False(t, IsOrganizationOwner(3, 3))
assert.True(t, IsOrganizationOwner(6, 5))
assert.False(t, IsOrganizationOwner(6, 4))
assert.False(t, IsOrganizationOwner(NonexistentID, NonexistentID))
}
func TestIsOrganizationMember(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsOrganizationMember(3, 2))
assert.False(t, IsOrganizationMember(3, 3))
assert.True(t, IsOrganizationMember(3, 4))
assert.True(t, IsOrganizationMember(6, 5))
assert.False(t, IsOrganizationMember(6, 4))
assert.False(t, IsOrganizationMember(NonexistentID, NonexistentID))
}
func TestIsPublicMembership(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsPublicMembership(3, 2))
assert.False(t, IsPublicMembership(3, 3))
assert.False(t, IsPublicMembership(3, 4))
assert.True(t, IsPublicMembership(6, 5))
assert.False(t, IsPublicMembership(6, 4))
assert.False(t, IsPublicMembership(NonexistentID, NonexistentID))
}
func TestGetOrgsByUserID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
orgs, err := GetOrgsByUserID(4, true)
assert.NoError(t, err)
assert.Len(t, orgs, 1)
assert.EqualValues(t, 3, orgs[0].ID)
orgs, err = GetOrgsByUserID(4, false)
assert.NoError(t, err)
assert.Len(t, orgs, 0)
}
func TestGetOwnedOrgsByUserID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
orgs, err := GetOwnedOrgsByUserID(2)
assert.NoError(t, err)
assert.Len(t, orgs, 1)
assert.EqualValues(t, 3, orgs[0].ID)
orgs, err = GetOwnedOrgsByUserID(4)
assert.NoError(t, err)
assert.Len(t, orgs, 0)
}
func TestGetOwnedOrgsByUserIDDesc(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
orgs, err := GetOwnedOrgsByUserIDDesc(5, "id")
assert.NoError(t, err)
assert.Len(t, orgs, 2)
assert.EqualValues(t, 7, orgs[0].ID)
assert.EqualValues(t, 6, orgs[1].ID)
orgs, err = GetOwnedOrgsByUserIDDesc(4, "id")
assert.NoError(t, err)
assert.Len(t, orgs, 0)
}
func TestGetOrgUsersByUserID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
orgUsers, err := GetOrgUsersByUserID(5, true)
assert.NoError(t, err)
assert.Len(t, orgUsers, 2)
assert.Equal(t, OrgUser{
ID: orgUsers[0].ID,
OrgID: 6,
UID: 5,
IsOwner: true,
IsPublic: true,
NumTeams: 1}, *orgUsers[0])
assert.Equal(t, OrgUser{
ID: orgUsers[1].ID,
OrgID: 7,
UID: 5,
IsOwner: true,
IsPublic: false,
NumTeams: 1}, *orgUsers[1])
publicOrgUsers, err := GetOrgUsersByUserID(5, false)
assert.NoError(t, err)
assert.Len(t, publicOrgUsers, 1)
assert.Equal(t, *orgUsers[0], *publicOrgUsers[0])
orgUsers, err = GetOrgUsersByUserID(1, true)
assert.NoError(t, err)
assert.Len(t, orgUsers, 0)
}
func TestGetOrgUsersByOrgID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
orgUsers, err := GetOrgUsersByOrgID(3)
assert.NoError(t, err)
assert.Len(t, orgUsers, 2)
assert.Equal(t, OrgUser{
ID: orgUsers[0].ID,
OrgID: 3,
UID: 2,
IsOwner: true,
IsPublic: true,
NumTeams: 1}, *orgUsers[0])
assert.Equal(t, OrgUser{
ID: orgUsers[1].ID,
OrgID: 3,
UID: 4,
IsOwner: false,
IsPublic: false,
NumTeams: 0}, *orgUsers[1])
orgUsers, err = GetOrgUsersByOrgID(NonexistentID)
assert.NoError(t, err)
assert.Len(t, orgUsers, 0)
}
func TestChangeOrgUserStatus(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(orgID, userID int64, public bool) {
assert.NoError(t, ChangeOrgUserStatus(orgID, userID, public))
orgUser := AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID}).(*OrgUser)
assert.Equal(t, public, orgUser.IsPublic)
}
testSuccess(3, 2, false)
testSuccess(3, 2, false)
testSuccess(3, 4, true)
assert.NoError(t, ChangeOrgUserStatus(NonexistentID, NonexistentID, true))
}
func TestAddOrgUser(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(orgID, userID int64) {
org := AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User)
expectedNumMembers := org.NumMembers
if !BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) {
expectedNumMembers++
}
assert.NoError(t, AddOrgUser(orgID, userID))
AssertExistsAndLoadBean(t, &OrgUser{OrgID: orgID, UID: userID})
org = AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User)
assert.EqualValues(t, expectedNumMembers, org.NumMembers)
}
testSuccess(3, 5)
testSuccess(3, 5)
testSuccess(6, 2)
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestRemoveOrgUser(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(orgID, userID int64) {
org := AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User)
expectedNumMembers := org.NumMembers
if BeanExists(t, &OrgUser{OrgID: orgID, UID: userID}) {
expectedNumMembers--
}
assert.NoError(t, RemoveOrgUser(orgID, userID))
AssertNotExistsBean(t, &OrgUser{OrgID: orgID, UID: userID})
org = AssertExistsAndLoadBean(t, &User{ID: orgID}).(*User)
assert.EqualValues(t, expectedNumMembers, org.NumMembers)
}
testSuccess(3, 4)
testSuccess(3, 4)
err := RemoveOrgUser(7, 5)
assert.Error(t, err)
assert.True(t, IsErrLastOrgOwner(err))
AssertExistsAndLoadBean(t, &OrgUser{OrgID: 7, UID: 5})
CheckConsistencyFor(t, &User{}, &Team{})
}
func TestUser_GetUserTeamIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
testSuccess := func(userID int64, expected []int64) {
teamIDs, err := org.GetUserTeamIDs(userID)
assert.NoError(t, err)
assert.Equal(t, expected, teamIDs)
}
testSuccess(2, []int64{1, 2})
testSuccess(4, []int64{2})
testSuccess(NonexistentID, []int64{})
}
func TestAccessibleReposEnv_CountRepos(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
testSuccess := func(userID, expectedCount int64) {
env, err := org.AccessibleReposEnv(userID)
assert.NoError(t, err)
count, err := env.CountRepos()
assert.NoError(t, err)
assert.EqualValues(t, expectedCount, count)
}
testSuccess(2, 2)
testSuccess(4, 1)
}
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
testSuccess := func(userID, page, pageSize int64, expectedRepoIDs []int64) {
env, err := org.AccessibleReposEnv(userID)
assert.NoError(t, err)
repoIDs, err := env.RepoIDs(1, 100)
assert.NoError(t, err)
assert.Equal(t, expectedRepoIDs, repoIDs)
}
testSuccess(2, 1, 100, []int64{3, 5})
testSuccess(4, 0, 100, []int64{3})
}
func TestAccessibleReposEnv_Repos(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
testSuccess := func(userID int64, expectedRepoIDs []int64) {
env, err := org.AccessibleReposEnv(userID)
assert.NoError(t, err)
repos, err := env.Repos(1, 100)
assert.NoError(t, err)
expectedRepos := make([]*Repository, len(expectedRepoIDs))
for i, repoID := range expectedRepoIDs {
expectedRepos[i] = AssertExistsAndLoadBean(t,
&Repository{ID: repoID}).(*Repository)
}
assert.Equal(t, expectedRepos, repos)
}
testSuccess(2, []int64{3, 5})
testSuccess(4, []int64{3})
}
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
testSuccess := func(userID int64, expectedRepoIDs []int64) {
env, err := org.AccessibleReposEnv(userID)
assert.NoError(t, err)
repos, err := env.MirrorRepos()
assert.NoError(t, err)
expectedRepos := make([]*Repository, len(expectedRepoIDs))
for i, repoID := range expectedRepoIDs {
expectedRepos[i] = AssertExistsAndLoadBean(t,
&Repository{ID: repoID}).(*Repository)
}
assert.Equal(t, expectedRepos, repos)
}
testSuccess(2, []int64{5})
testSuccess(4, []int64{})
}

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