Compare commits
6 Commits
notificati
...
options-bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a46834168 | ||
|
|
8596ea5519 | ||
|
|
608b1ce642 | ||
|
|
d97b9cf0c2 | ||
|
|
dd5464e1f2 | ||
|
|
c06db22b24 |
70
.drone.yml
70
.drone.yml
@@ -3,20 +3,16 @@ workspace:
|
||||
path: src/code.gitea.io/gitea
|
||||
|
||||
pipeline:
|
||||
clone:
|
||||
image: plugins/git
|
||||
tags: true
|
||||
|
||||
test:
|
||||
image: webhippie/golang:edge
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
CGO_ENABLED: 1
|
||||
TAGS: sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- apk -U add openssh-client
|
||||
- make clean
|
||||
- make generate
|
||||
- make vet
|
||||
- make lint
|
||||
- make test
|
||||
@@ -28,34 +24,38 @@ pipeline:
|
||||
image: webhippie/golang:edge
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata
|
||||
CGO_ENABLED: 1
|
||||
TAGS: sqlite
|
||||
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
|
||||
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
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
event: [ push, tag ]
|
||||
branch: [ master, refs/tags/* ]
|
||||
|
||||
coverage:
|
||||
image: plugins/coverage
|
||||
@@ -66,19 +66,11 @@ pipeline:
|
||||
docker:
|
||||
image: plugins/docker
|
||||
repo: gitea/gitea
|
||||
tags: [ '${DRONE_TAG##v}' ]
|
||||
tags: [ '${TAG}' ]
|
||||
when:
|
||||
event: [ tag ]
|
||||
branch: [ refs/tags/* ]
|
||||
|
||||
docker:
|
||||
image: plugins/docker
|
||||
repo: gitea/gitea
|
||||
tags: [ '${DRONE_BRANCH##release/v}' ]
|
||||
when:
|
||||
event: [ push ]
|
||||
branch: [ release/* ]
|
||||
|
||||
docker:
|
||||
image: plugins/docker
|
||||
repo: gitea/gitea
|
||||
@@ -87,26 +79,6 @@ pipeline:
|
||||
event: [ push ]
|
||||
branch: [ master ]
|
||||
|
||||
release:
|
||||
image: plugins/s3
|
||||
path_style: true
|
||||
strip_prefix: dist/release/
|
||||
source: dist/release/*
|
||||
target: /gitea/${DRONE_TAG##v}
|
||||
when:
|
||||
event: [ tag ]
|
||||
branch: [ refs/tags/* ]
|
||||
|
||||
release:
|
||||
image: plugins/s3
|
||||
path_style: true
|
||||
strip_prefix: dist/release/
|
||||
source: dist/release/*
|
||||
target: /gitea/${DRONE_BRANCH##release/v}
|
||||
when:
|
||||
event: [ push ]
|
||||
branch: [ release/* ]
|
||||
|
||||
release:
|
||||
image: plugins/s3
|
||||
path_style: true
|
||||
@@ -117,6 +89,16 @@ pipeline:
|
||||
event: [ push ]
|
||||
branch: [ master ]
|
||||
|
||||
release:
|
||||
image: plugins/s3
|
||||
path_style: true
|
||||
strip_prefix: dist/release/
|
||||
source: dist/release/*
|
||||
target: /gitea/$$TAG
|
||||
when:
|
||||
event: [ tag ]
|
||||
branch: [ refs/tags/* ]
|
||||
|
||||
github:
|
||||
image: plugins/github-release
|
||||
files:
|
||||
@@ -135,11 +117,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 ]
|
||||
|
||||
@@ -1 +1 @@
|
||||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QKICAgICAgLSBtYWtlIGJ1aWxkCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtbXlzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHRlc3QtbXlzcWwKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1wZ3NxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1wZ3NxbAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBzdGF0aWM6CiAgICBpbWFnZToga2FyYWxhYmUveGdvLWxhdGVzdDpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhIHNxbGl0ZQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHJlbGVhc2UKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgY292ZXJhZ2U6CiAgICBpbWFnZTogcGx1Z2lucy9jb3ZlcmFnZQogICAgc2VydmVyOiBodHRwczovL2NvdmVyYWdlLmdpdGVhLmlvCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJyR7RFJPTkVfVEFHIyN2fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9CUkFOQ0gjI3JlbGVhc2Uvdn0nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgcmVsZWFzZS8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnbGF0ZXN0JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX1RBRyMjdn0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvbWFzdGVyCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIGdpdGh1YjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdGh1Yi1yZWxlYXNlCiAgICBmaWxlczoKICAgICAgLSBkaXN0L3JlbGVhc2UvKgogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZ2l0dGVyOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0dGVyCgpzZXJ2aWNlczoKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgcGdzcWw6CiAgICBpbWFnZTogcG9zdGdyZXM6OS41CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19EQj10ZXN0CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCg.hp6IsxbFIQOaxJdmGv32Vf34-Nra3KqVIWzH52W687I
|
||||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQ0dPX0VOQUJMRUQ6IDEKICAgICAgVEFHUzogc3FsaXRlCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIGFwayAtVSBhZGQgb3BlbnNzaC1jbGllbnQKICAgICAgLSBtYWtlIGNsZWFuCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QKICAgICAgLSBtYWtlIGJ1aWxkCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtbXlzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQ0dPX0VOQUJMRUQ6IDEKICAgICAgVEFHUzogc3FsaXRlCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1teXNxbAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCgogIHRlc3QtcGdzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgQ0dPX0VOQUJMRUQ6IDEKICAgICAgVEFHUzogc3FsaXRlCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1wZ3NxbAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCgogIHVwZGF0ZXI6CiAgICBpbWFnZToga2FyYWxhYmUveGdvLWxhdGVzdDpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBDR09fRU5BQkxFRDogMQogICAgICBUQUdTOiBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSByZWxlYXNlCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcgXQogICAgICBicmFuY2g6IFsgbWFzdGVyLCByZWZzL3RhZ3MvKiBdCgogIGNvdmVyYWdlOgogICAgaW1hZ2U6IHBsdWdpbnMvY292ZXJhZ2UKICAgIHNlcnZlcjogaHR0cHM6Ly9jb3ZlcmFnZS5naXRlYS5pbwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICcke1RBR30nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJ2xhdGVzdCcgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyBtYXN0ZXIgXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvbWFzdGVyCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8kJFRBRwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZ2l0aHViOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICAgIGZpbGVzOgogICAgICAtIGRpc3QvcmVsZWFzZS8qCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBnaXR0ZXI6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKCnNlcnZpY2VzOgogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCgogIHBnc3FsOgogICAgaW1hZ2U6IHBvc3RncmVzOjkuNQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfREI9dGVzdAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCg.LjU9u_DQ4fD2CytUu9RwwSimdcS3-KzR6nO4ff4edNU
|
||||
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1,6 +0,0 @@
|
||||
conf/* linguist-vendored
|
||||
docker/* linguist-vendored
|
||||
options/* linguist-vendored
|
||||
public/* linguist-vendored
|
||||
scripts/* linguist-vendored
|
||||
templates/* linguist-vendored
|
||||
20
.github/issue_template.md
vendored
20
.github/issue_template.md
vendored
@@ -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:
|
||||
@@ -11,19 +11,9 @@
|
||||
- Database (use `[x]`):
|
||||
- [ ] PostgreSQL
|
||||
- [ ] MySQL
|
||||
- [ ] MSSQL
|
||||
- [ ] SQLite
|
||||
- Can you reproduce the bug at https://try.gitea.io:
|
||||
- [ ] Yes (provide example URL)
|
||||
- [ ] No
|
||||
- [ ] Not relevant
|
||||
- Log gist:
|
||||
|
||||
## Description
|
||||
|
||||
...
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
**If this issue involves the Web Interface, please include a screenshot**
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -30,7 +30,6 @@ coverage.out
|
||||
|
||||
/modules/options/bindata.go
|
||||
/modules/public/bindata.go
|
||||
/modules/templates/bindata.go
|
||||
|
||||
*.db
|
||||
*.log
|
||||
@@ -41,6 +40,5 @@ coverage.out
|
||||
/dist
|
||||
/custom
|
||||
/data
|
||||
/indexers
|
||||
/log
|
||||
/public/img/avatar
|
||||
|
||||
162
CHANGELOG.md
162
CHANGELOG.md
@@ -1,162 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
* BREAKING
|
||||
* Password reset URL changed from `/user/forget_password` to `/user/forgot_password`
|
||||
|
||||
## [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
|
||||
* Fixed localized `MIN_PASSWORD_LENGTH` [#501](https://github.com/go-gitea/gitea/pull/501)
|
||||
* Fixed 500 error on organization delete [#507](https://github.com/go-gitea/gitea/pull/507)
|
||||
* Ignore empty wiki repo on migrate [#544](https://github.com/go-gitea/gitea/pull/544)
|
||||
* Proper check access for forking [#563](https://github.com/go-gitea/gitea/pull/563)
|
||||
* Fix SSH domain on installer [#506](https://github.com/go-gitea/gitea/pull/506)
|
||||
* Fix missing data rows on admin UI [#580](https://github.com/go-gitea/gitea/pull/580)
|
||||
* Do not delete tags with releases by default [#579](https://github.com/go-gitea/gitea/pull/579)
|
||||
* Fix missing session config data on admin UI [#578](https://github.com/go-gitea/gitea/pull/578)
|
||||
* Properly show the version within footer on the UI [#593](https://github.com/go-gitea/gitea/pull/593)
|
||||
|
||||
## [1.0.0](https://github.com/go-gitea/gitea/releases/tag/v1.0.0) - 2016-12-23
|
||||
|
||||
* BREAKING
|
||||
* We have various changes on the API, scripting against API must be updated
|
||||
* FEATURE
|
||||
* Show last login for admins [#121](https://github.com/go-gitea/gitea/pull/121)
|
||||
* BUGFIXES
|
||||
* Fixed sender of notifications [#2](https://github.com/go-gitea/gitea/pull/2)
|
||||
* Fixed keyword hijacking vulnerability [#20](https://github.com/go-gitea/gitea/pull/20)
|
||||
* Fixed non-markdown readme rendering [#95](https://github.com/go-gitea/gitea/pull/95)
|
||||
* Allow updating draft releases [#169](https://github.com/go-gitea/gitea/pull/169)
|
||||
* GitHub API compliance [#227](https://github.com/go-gitea/gitea/pull/227)
|
||||
* Added commit SHA to tag webhook [#286](https://github.com/go-gitea/gitea/issues/286)
|
||||
* Secured links via noopener [#315](https://github.com/go-gitea/gitea/issues/315)
|
||||
* Replace tabs with spaces on wiki title [#371](https://github.com/go-gitea/gitea/pull/371)
|
||||
* Fixed vulnerability on labels and releases [#409](https://github.com/go-gitea/gitea/pull/409)
|
||||
* Fixed issue comment API [#449](https://github.com/go-gitea/gitea/pull/449)
|
||||
* ENHANCEMENT
|
||||
* Use proper import path for libravatar [#3](https://github.com/go-gitea/gitea/pull/3)
|
||||
* Integrated DroneCI for tests and builds [#24](https://github.com/go-gitea/gitea/issues/24)
|
||||
* Integrated dependency manager [#29](https://github.com/go-gitea/gitea/issues/29)
|
||||
* Embedded bindata optionally [#30](https://github.com/go-gitea/gitea/issues/30)
|
||||
* Integrated pagination for releases [#73](https://github.com/go-gitea/gitea/pull/73)
|
||||
* Autogenerate version on every build [#91](https://github.com/go-gitea/gitea/issues/91)
|
||||
* Refactored Docker container [#104](https://github.com/go-gitea/gitea/issues/104)
|
||||
* Added short-hash support for downloads [#211](https://github.com/go-gitea/gitea/issues/211)
|
||||
* Display tooltip for downloads [#221](https://github.com/go-gitea/gitea/issues/221)
|
||||
* Improved HTTP headers for issue attachments [#270](https://github.com/go-gitea/gitea/pull/270)
|
||||
* Integrate public as bindata optionally [#293](https://github.com/go-gitea/gitea/pull/293)
|
||||
* Integrate templates as bindata optionally [#314](https://github.com/go-gitea/gitea/pull/314)
|
||||
* Inject more ENV variables into custom hooks [#316](https://github.com/go-gitea/gitea/issues/316)
|
||||
* Correct LDAP login validation [#342](https://github.com/go-gitea/gitea/pull/342)
|
||||
* Integrate conf as bindata optionally [#354](https://github.com/go-gitea/gitea/pull/354)
|
||||
* Serve video files in browser [#418](https://github.com/go-gitea/gitea/pull/418)
|
||||
* Configurable SSH host binding [#431](https://github.com/go-gitea/gitea/issues/431)
|
||||
* MISC
|
||||
* Forked from Gogs and renamed to Gitea
|
||||
* Catching more errors with logs
|
||||
* Fixed all linting errors
|
||||
* Made the go linter entirely happy
|
||||
* Really integrated vendoring
|
||||
@@ -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.
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
FROM aarch64/alpine:3.5
|
||||
|
||||
EXPOSE 22 3000
|
||||
|
||||
RUN apk update && \
|
||||
apk add \
|
||||
su-exec \
|
||||
ca-certificates \
|
||||
sqlite \
|
||||
bash \
|
||||
git \
|
||||
linux-pam \
|
||||
s6 \
|
||||
curl \
|
||||
openssh \
|
||||
tzdata && \
|
||||
rm -rf \
|
||||
/var/cache/apk/* && \
|
||||
addgroup \
|
||||
-S -g 1000 \
|
||||
git && \
|
||||
adduser \
|
||||
-S -H -D \
|
||||
-h /data/git \
|
||||
-s /bin/bash \
|
||||
-u 1000 \
|
||||
-G git \
|
||||
git && \
|
||||
echo "git:$(date +%s | sha256sum | base64 | head -c 32)" | chpasswd
|
||||
|
||||
ENV USER git
|
||||
ENV GITEA_CUSTOM /data/gitea
|
||||
|
||||
COPY docker /
|
||||
COPY gitea /app/gitea/gitea
|
||||
|
||||
ENV GODEBUG=netdns=go
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
ENTRYPOINT ["/usr/bin/entrypoint"]
|
||||
CMD ["/bin/s6-svscan", "/etc/s6"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -12,4 +10,3 @@ Rémy Boulanouar <admin@dblk.org> (@DblK)
|
||||
Sandro Santilli <strk@kbt.io> (@strk)
|
||||
Thibault Meyer <meyer.thibault@gmail.com> (@0xbaadf00d)
|
||||
Thomas Boerger <thomas@webhippie.de> (@tboerger)
|
||||
Patrick G <geek1011@outlook.com> (@geek1011)
|
||||
|
||||
75
Makefile
75
Makefile
@@ -1,28 +1,26 @@
|
||||
DIST := dist
|
||||
EXECUTABLE := gitea
|
||||
IMPORT := code.gitea.io/gitea
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE := gitea.exe
|
||||
else
|
||||
EXECUTABLE := gitea
|
||||
endif
|
||||
SHA := $(shell git rev-parse --short HEAD)
|
||||
DATE := $(shell date -u '+%Y-%m-%d %I:%M:%S %Z')
|
||||
|
||||
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 "code.gitea.io/gitea/modules/setting.BuildTime=$(DATE)"
|
||||
LDFLAGS += -X "code.gitea.io/gitea/modules/setting.BuildGitHash=$(SHA)"
|
||||
|
||||
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell go list ./... | grep -v /vendor/))
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
TARGETS ?= linux/*,darwin/*,windows/*
|
||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
TAGS ?=
|
||||
|
||||
ifneq ($(DRONE_TAG),)
|
||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
||||
VERSION ?= $(DRONE_TAG)
|
||||
else
|
||||
ifneq ($(DRONE_BRANCH),)
|
||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||
VERSION ?= $(DRONE_BRANCH)
|
||||
else
|
||||
VERSION ?= master
|
||||
endif
|
||||
@@ -34,11 +32,11 @@ all: build
|
||||
.PHONY: clean
|
||||
clean:
|
||||
go clean -i ./...
|
||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA)
|
||||
rm -rf $(EXECUTABLE) $(DIST)
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
find . -name "*.go" -type f -not -path "./vendor/*" | xargs gofmt -s -w
|
||||
go fmt $(PACKAGES)
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
@@ -46,30 +44,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;
|
||||
@@ -92,47 +85,23 @@ install: $(wildcard *.go)
|
||||
.PHONY: build
|
||||
build: $(EXECUTABLE)
|
||||
|
||||
$(EXECUTABLE): $(SOURCES)
|
||||
go build -i -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 build -t gitea/gitea:latest .
|
||||
.PHONY: $(EXECUTABLE)
|
||||
$(EXECUTABLE): $(wildcard *.go)
|
||||
go build -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.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 '$(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 '$(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 '$(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
|
||||
|
||||
131
README.md
131
README.md
@@ -1,34 +1,127 @@
|
||||
[简体中文](https://github.com/go-gitea/gitea/blob/master/README_ZH.md)
|
||||
|
||||
# Gitea - Git with a cup of tea
|
||||
|
||||
[](https://drone.gitea.io/go-gitea/gitea)
|
||||
[](http://drone.gitea.io/go-gitea/gitea)
|
||||
[](https://gitter.im/go-gitea/gitea?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
|
||||
[](http://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
|
||||
[](https://coverage.gitea.io/go-gitea/gitea)
|
||||
[](https://goreportcard.com/report/code.gitea.io/gitea)
|
||||
[](https://godoc.org/code.gitea.io/gitea)
|
||||
[](https://github.com/go-gitea/gitea/releases/latest)
|
||||
|
||||
| | | |
|
||||
|:---:|:---:|:---:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
[](https://github.com/go-gitea/gitea)
|
||||
|
||||
##### Status
|
||||
|
||||
**Current version**: (see [Releases](https://github.com/go-gitea/gitea/releases))
|
||||
|
||||
| Web | UI | Preview |
|
||||
|:-------------:|:-------:|:-------:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. **YOU MUST READ THE [Contributors Guide](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST**.
|
||||
2. If you think there are vulnerabilities in the project, please talk privately to **security@gitea.io**. Thanks!
|
||||
3. If you're interested in using APIs, we have experimental support with [documentation](https://godoc.org/github.com/go-gitea/go-sdk).
|
||||
|
||||
## 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, Mac OS X, Windows and ARM.
|
||||
|
||||
## Notes
|
||||
## Features
|
||||
|
||||
1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
|
||||
2. If you found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
|
||||
3. If you're interested in using our APIs, we have experimental support with [documentation](https://godoc.org/code.gitea.io/sdk/gitea).
|
||||
- Activity timeline
|
||||
- SSH and HTTP/HTTPS protocols
|
||||
- SMTP/LDAP/Reverse proxy authentication
|
||||
- Reverse proxy with sub-path
|
||||
- Account/Organization/Repository management
|
||||
- Add/Remove repository collaborators
|
||||
- Repository/Organization webhooks (including Slack)
|
||||
- Repository Git hooks/deploy keys
|
||||
- Repository issues, pull requests and wiki
|
||||
- Migrate and mirror repository and its wiki
|
||||
- Web editor for repository files and wiki
|
||||
- Gravatar and Federated avatar with custom source
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Multi-language support ([20 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
## Docs
|
||||
## System Requirements
|
||||
|
||||
- A cheap Raspberry Pi is powerful enough for basic functionality.
|
||||
- 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Please see [Semantic UI](https://github.com/Semantic-Org/Semantic-UI#browser-support) for specific versions of supported browsers.
|
||||
- The official support minimal size is **1024*768**, UI may still looks right in smaller size but no promises and fixes.
|
||||
|
||||
## Installation
|
||||
|
||||
**Note: As Gitea is a [Gogs](https://github.com/gogits/gogs) fork, tutorials and documentation related to gogs applies to Gitea too**
|
||||
|
||||
How to install Gitea:
|
||||
|
||||
- go get code.gitea.io/gitea
|
||||
- [Ship with Docker](https://github.com/go-gitea/gitea/tree/master/docker)
|
||||
- [Install with Vagrant](https://github.com/go-gitea/examples/tree/master/vagrant)
|
||||
|
||||
**Note: binary release will be available soon**
|
||||
|
||||
### Tutorials
|
||||
|
||||
- [How To Set Up Gogs on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-gogs-on-ubuntu-14-04)
|
||||
- [Run your own GitHub-like service with the help of Docker](http://blog.hypriot.com/post/run-your-own-github-like-service-with-docker/)
|
||||
- [Dockerized Gogs git server and alpine postgres in 20 minutes or less](http://garthwaite.org/docker-gogs.html)
|
||||
- [Host Your Own Private GitHub with Gogs.io](https://eladnava.com/host-your-own-private-github-with-gogs-io/)
|
||||
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs) (Chinese)
|
||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
|
||||
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
|
||||
- [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/)
|
||||
- [Cloudflare Full SSL with GOGS (Go Git Service) using NGINX](http://www.listekconsulting.com/articles/cloudflare-full-ssl-with-gogs-go-git-service-using-nginx/)
|
||||
|
||||
### Screencasts
|
||||
|
||||
- [How to install Gogs on a Linux Server (DigitalOcean)](https://www.youtube.com/watch?v=deSfX0gqefE)
|
||||
- [Instalando Gogs no Ubuntu](https://www.youtube.com/watch?v=4UkHAR1F7ZA) (Português)
|
||||
|
||||
### Deploy to Cloud
|
||||
|
||||
- [OpenShift](https://github.com/tkisme/gogs-openshift)
|
||||
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
|
||||
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
|
||||
- [Portal](https://portaldemo.xyz/cloud/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
|
||||
- [YunoHost](https://github.com/mbugeia/gogs_ynh)
|
||||
- [DPlatform](https://github.com/j8r/DPlatform)
|
||||
|
||||
## Software and Service Support
|
||||
|
||||
- [Drone](https://github.com/drone/drone) (CI)
|
||||
- [Fabric8](http://fabric8.io/) (DevOps)
|
||||
- [Taiga](https://taiga.io/) (Project Management)
|
||||
- [Puppet](https://forge.puppetlabs.com/Siteminds/gogs) (IT)
|
||||
- [Kanboard](http://kanboard.net/plugin/gogs-webhook) (Project Management)
|
||||
- [BearyChat](https://bearychat.com/) (Team Communication)
|
||||
- [HiWork](http://www.hiwork.cc/) (Team Communication)
|
||||
|
||||
### Product Support
|
||||
|
||||
- [Synology](https://www.synology.com) (Docker)
|
||||
- [One Space](http://www.onespace.cc) (App Store)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Router and middleware mechanism of [Macaron](https://github.com/go-macaron/macaron).
|
||||
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
|
||||
- Thanks [Rocker](http://weibo.com/rocker1989) for designing Logo.
|
||||
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
|
||||
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
|
||||
- Thanks [KeyCDN](https://www.keycdn.com/) and [QiNiu](http://www.qiniu.com/) for providing CDN service.
|
||||
|
||||
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.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -38,8 +131,8 @@ Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
* [Translators](options/locale/TRANSLATORS)
|
||||
* [Translators](conf/locale/TRANSLATORS)
|
||||
|
||||
## 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.
|
||||
|
||||
47
README_ZH.md
47
README_ZH.md
@@ -1,47 +0,0 @@
|
||||
[English](https://github.com/go-gitea/gitea/blob/master/README.md)
|
||||
|
||||
# Gitea - Git with a cup of tea
|
||||
|
||||
[](https://drone.gitea.io/go-gitea/gitea)
|
||||
[](https://gitter.im/go-gitea/gitea?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com")
|
||||
[](https://coverage.gitea.io/go-gitea/gitea)
|
||||
[](https://goreportcard.com/report/code.gitea.io/gitea)
|
||||
[](https://godoc.org/code.gitea.io/gitea)
|
||||
[](https://github.com/go-gitea/gitea/releases/latest)
|
||||
|
||||
| | | |
|
||||
|:---:|:---:|:---:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
## 目标
|
||||
|
||||
Gitea的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用Go作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了x86,amd64,还包括 ARM 和 PowerPC。
|
||||
|
||||
如果您想试用一下,请访问 [在线Demo](https://try.gitea.io/)!
|
||||
|
||||
## 提示
|
||||
|
||||
1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**.
|
||||
2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。谢谢!
|
||||
3. 如果你要使用API,请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea).
|
||||
|
||||
## 文档
|
||||
|
||||
关于如何安装请访问我们的 [文档站](https://docs.gitea.io/zh-cn/),如果没有找到对应的文档,你也可以通过 [Gitter - 英文](https://gitter.im/go-gitea/gitea) 和 QQ群 328432459 来和我们交流。
|
||||
|
||||
## 贡献流程
|
||||
|
||||
Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
## 作者
|
||||
|
||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
* [Translators](options/locale/TRANSLATORS)
|
||||
|
||||
## 授权许可
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) 文件中。
|
||||
63
cmd/admin.go
63
cmd/admin.go
@@ -1,5 +1,4 @@
|
||||
// Copyright 2016 The Gogs 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.
|
||||
|
||||
@@ -18,12 +17,11 @@ var (
|
||||
// CmdAdmin represents the available admin sub-command.
|
||||
CmdAdmin = cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Perform admin operations on command line",
|
||||
Description: `Allow using internal logic of Gitea without hacking into the source code
|
||||
Usage: "Preform admin operations on command line",
|
||||
Description: `Allow using internal logic of Gogs without hacking into the source code
|
||||
to make automatic initialization process more smoothly`,
|
||||
Subcommands: []cli.Command{
|
||||
subcmdCreateUser,
|
||||
subcmdChangePassword,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -58,59 +56,8 @@ to make automatic initialization process more smoothly`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
subcmdChangePassword = cli.Command{
|
||||
Name: "change-password",
|
||||
Usage: "Change a user's password",
|
||||
Action: runChangePassword,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "username,u",
|
||||
Value: "",
|
||||
Usage: "The user to change password for",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "password,p",
|
||||
Value: "",
|
||||
Usage: "New password to set for user",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func runChangePassword(c *cli.Context) error {
|
||||
if !c.IsSet("password") {
|
||||
return fmt.Errorf("Password is not specified")
|
||||
} else if !c.IsSet("username") {
|
||||
return fmt.Errorf("Username is not specified")
|
||||
}
|
||||
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
|
||||
setting.NewXORMLogService(false)
|
||||
if err := models.SetEngine(); err != nil {
|
||||
return fmt.Errorf("models.SetEngine: %v", err)
|
||||
}
|
||||
|
||||
uname := c.String("username")
|
||||
user, err := models.GetUserByName(uname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v", err)
|
||||
}
|
||||
user.Passwd = c.String("password")
|
||||
if user.Salt, err = models.GetUserSalt(); err != nil {
|
||||
return fmt.Errorf("%v", err)
|
||||
}
|
||||
user.EncodePasswd()
|
||||
if err := models.UpdateUser(user); err != nil {
|
||||
return fmt.Errorf("%v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("User '%s' password has been successfully updated!\n", uname)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCreateUser(c *cli.Context) error {
|
||||
if !c.IsSet("name") {
|
||||
return fmt.Errorf("Username is not specified")
|
||||
@@ -126,11 +73,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"),
|
||||
|
||||
16
cmd/cert.go
16
cmd/cert.go
@@ -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,14 +130,14 @@ 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{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Acme Co"},
|
||||
CommonName: "Gitea",
|
||||
CommonName: "Gogs",
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
@@ -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()
|
||||
|
||||
115
cmd/dump.go
115
cmd/dump.go
@@ -1,5 +1,4 @@
|
||||
// Copyright 2014 The Gogs 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.
|
||||
|
||||
@@ -11,22 +10,20 @@ 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"
|
||||
)
|
||||
|
||||
// CmdDump represents the available dump sub-command.
|
||||
var CmdDump = cli.Command{
|
||||
Name: "dump",
|
||||
Usage: "Dump Gitea files and database",
|
||||
Usage: "Dump Gogs files and database",
|
||||
Description: `Dump compresses all related files and database into zip file.
|
||||
It can be used for backup and capture Gitea server image to send to maintainer`,
|
||||
It can be used for backup and capture Gogs server image to send to maintainer`,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
@@ -43,10 +40,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,88 +48,61 @@ 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) {
|
||||
log.Fatalf("Path does not exist: %s", tmpDir)
|
||||
}
|
||||
TmpWorkDir, err := ioutil.TempDir(tmpDir, "gitea-dump-")
|
||||
TmpWorkDir, err := ioutil.TempDir(tmpDir, "gogs-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)
|
||||
|
||||
reposDump := path.Join(TmpWorkDir, "gitea-repo.zip")
|
||||
dbDump := path.Join(TmpWorkDir, "gitea-db.sql")
|
||||
reposDump := path.Join(TmpWorkDir, "gogs-repo.zip")
|
||||
dbDump := path.Join(TmpWorkDir, "gogs-db.sql")
|
||||
|
||||
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...")
|
||||
log.Printf("Dumping database...")
|
||||
if err := models.DumpDatabase(dbDump); err != nil {
|
||||
log.Fatalf("Fail to dump database: %v", err)
|
||||
}
|
||||
|
||||
if err := models.DumpDatabase(dbDump, targetDBType); err != nil {
|
||||
log.Fatalf("Failed to dump database: %v", err)
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix())
|
||||
fileName := fmt.Sprintf("gogs-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)
|
||||
if err := z.AddFile("gogs-repo.zip", reposDump); err != nil {
|
||||
log.Fatalf("Fail to include gogs-repo.zip: %v", err)
|
||||
}
|
||||
if err := z.AddFile("gitea-db.sql", dbDump); err != nil {
|
||||
log.Fatalf("Failed to include gitea-db.sql: %v", err)
|
||||
if err := z.AddFile("gogs-db.sql", dbDump); err != nil {
|
||||
log.Fatalf("Fail to include gogs-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 +112,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
|
||||
}
|
||||
|
||||
233
cmd/hook.go
233
cmd/hook.go
@@ -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
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2014 The Gogs 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.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -14,17 +13,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 +43,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,12 +72,11 @@ var (
|
||||
"git-upload-pack": models.AccessModeRead,
|
||||
"git-upload-archive": models.AccessModeRead,
|
||||
"git-receive-pack": models.AccessModeWrite,
|
||||
lfsAuthenticateVerb: models.AccessModeNone,
|
||||
}
|
||||
)
|
||||
|
||||
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
|
||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
if !setting.ProdMode {
|
||||
@@ -90,17 +90,61 @@ 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.BRANCH_PREFIX) + "&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")
|
||||
println("Gogs: SSH has been disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,32 +154,17 @@ func runServ(c *cli.Context) error {
|
||||
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
println("Hi there, You've successfully authenticated, but Gitea does not provide shell access.")
|
||||
println("If this is unexpected, please log in with password and setup Gitea under another user.")
|
||||
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.")
|
||||
println("If this is unexpected, please log in with password and setup Gogs under another user.")
|
||||
return nil
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
argsSplit := strings.Split(args, " ")
|
||||
if len(argsSplit) >= 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 +174,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 +182,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,16 +195,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 if lfsVerb == "download" {
|
||||
requestedMode = models.AccessModeRead
|
||||
} else {
|
||||
fail("Unknown LFS verb", "Unkown lfs verb %s", lfsVerb)
|
||||
}
|
||||
}
|
||||
|
||||
// Prohibit push to mirror repositories.
|
||||
if requestedMode > models.AccessModeRead && repo.IsMirror {
|
||||
fail("mirror repository is read-only", "")
|
||||
@@ -234,9 +243,9 @@ func runServ(c *cli.Context) error {
|
||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
||||
}
|
||||
|
||||
mode, err := models.AccessLevel(user.ID, repo)
|
||||
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 {
|
||||
@@ -246,44 +255,15 @@ func runServ(c *cli.Context) error {
|
||||
"User %s does not have level %v access to repository %s",
|
||||
user.Name, requestedMode, repoPath)
|
||||
}
|
||||
|
||||
os.Setenv(models.EnvPusherName, user.Name)
|
||||
os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID))
|
||||
}
|
||||
}
|
||||
|
||||
//LFS token authentication
|
||||
if verb == lfsAuthenticateVerb {
|
||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, repoUser.Name, repo.Name)
|
||||
os.Setenv("GITEA_PUSHER_NAME", user.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 {
|
||||
@@ -297,15 +277,6 @@ func runServ(c *cli.Context) error {
|
||||
} else {
|
||||
gitcmd = exec.Command(verb, repoPath)
|
||||
}
|
||||
|
||||
if isWiki {
|
||||
if err = repo.InitWiki(); err != nil {
|
||||
fail("Internal error", "Failed to init wiki repo: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))
|
||||
|
||||
gitcmd.Dir = setting.RepoRootPath
|
||||
gitcmd.Stdout = os.Stdout
|
||||
gitcmd.Stdin = os.Stdin
|
||||
@@ -314,6 +285,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
63
cmd/update.go
Normal 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
|
||||
}
|
||||
248
cmd/web.go
248
cmd/web.go
@@ -5,24 +5,25 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"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"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/template"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/routers/admin"
|
||||
apiv1 "code.gitea.io/gitea/routers/api/v1"
|
||||
@@ -30,7 +31,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,16 +39,18 @@ import (
|
||||
"github.com/go-macaron/i18n"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/go-macaron/toolbox"
|
||||
context2 "github.com/gorilla/context"
|
||||
"github.com/go-xorm/xorm"
|
||||
version "github.com/mcuadros/go-version"
|
||||
"github.com/urfave/cli"
|
||||
ini "gopkg.in/ini.v1"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// CmdWeb represents the available web sub-command.
|
||||
var CmdWeb = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "Start Gitea web server",
|
||||
Description: `Gitea web server is the only thing you need to run,
|
||||
Usage: "Start Gogs web server",
|
||||
Description: `Gogs web server is the only thing you need to run,
|
||||
and it takes care of all the other things for you`,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
@@ -62,14 +64,55 @@ 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
|
||||
}
|
||||
|
||||
// checkVersion checks if binary matches the version of templates files.
|
||||
func checkVersion() {
|
||||
// Templates.
|
||||
data, err := ioutil.ReadFile(setting.StaticRootPath + "/templates/.VERSION")
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to read 'templates/.VERSION': %v", err)
|
||||
}
|
||||
tplVer := string(data)
|
||||
if tplVer != setting.AppVer {
|
||||
if version.Compare(tplVer, setting.AppVer, ">") {
|
||||
log.Fatal(4, "Binary version is lower than template file version, did you forget to recompile Gogs?")
|
||||
} else {
|
||||
log.Fatal(4, "Binary version is higher than template file version, did you forget to update template files?")
|
||||
}
|
||||
}
|
||||
|
||||
// Check dependency version.
|
||||
checkers := []VerChecker{
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.5.5"},
|
||||
{"github.com/go-macaron/binding", binding.Version, "0.3.2"},
|
||||
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/go-macaron/csrf", csrf.Version, "0.1.0"},
|
||||
{"github.com/go-macaron/i18n", i18n.Version, "0.3.0"},
|
||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.8.4"},
|
||||
{"gopkg.in/macaron.v1", macaron.Version, "1.1.7"},
|
||||
{"code.gitea.io/git", git.Version, "0.4.1"},
|
||||
}
|
||||
for _, c := range checkers {
|
||||
if !version.Compare(c.Version(), c.Expected, ">=") {
|
||||
log.Fatal(4, `Dependency outdated!
|
||||
Package '%s' current version (%s) is below requirement (%s),
|
||||
please use following command to update this package and recompile Gogs:
|
||||
go get -u %[1]s`, c.ImportPath, c.Version(), c.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newMacaron initializes Macaron instance.
|
||||
func newMacaron() *macaron.Macaron {
|
||||
m := macaron.New()
|
||||
@@ -83,11 +126,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,17 +137,23 @@ func newMacaron() *macaron.Macaron {
|
||||
macaron.StaticOptions{
|
||||
Prefix: "avatars",
|
||||
SkipLogging: setting.DisableRouterLog,
|
||||
ETag: true,
|
||||
},
|
||||
))
|
||||
|
||||
m.Use(templates.Renderer())
|
||||
models.InitMailRender(templates.Mailer())
|
||||
funcMap := template.NewFuncMap()
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
AppendDirectories: []string{path.Join(setting.CustomPath, "templates")},
|
||||
Funcs: funcMap,
|
||||
IndentJSON: macaron.Env != macaron.PROD,
|
||||
}))
|
||||
models.InitMailRender(path.Join(setting.StaticRootPath, "templates/mail"),
|
||||
path.Join(setting.CustomPath, "templates/mail"), funcMap)
|
||||
|
||||
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,12 +206,8 @@ 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()
|
||||
checkVersion()
|
||||
|
||||
m := newMacaron()
|
||||
|
||||
@@ -178,8 +218,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.
|
||||
@@ -200,36 +238,10 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Group("/user", func() {
|
||||
m.Get("/login", user.SignIn)
|
||||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
|
||||
if setting.EnableOpenIDSignIn {
|
||||
m.Combo("/login/openid").
|
||||
Get(user.SignInOpenID).
|
||||
Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
|
||||
m.Group("/openid", func() {
|
||||
m.Combo("/connect").
|
||||
Get(user.ConnectOpenID).
|
||||
Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
|
||||
m.Combo("/register").
|
||||
Get(user.RegisterOpenID).
|
||||
Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
|
||||
})
|
||||
}
|
||||
m.Get("/sign_up", user.SignUp)
|
||||
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() {
|
||||
@@ -243,15 +255,6 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Post("/email/delete", user.DeleteEmail)
|
||||
m.Get("/password", user.SettingsPassword)
|
||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
if setting.EnableOpenIDSignIn {
|
||||
m.Group("/openid", func() {
|
||||
m.Combo("").Get(user.SettingsOpenID).
|
||||
Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost)
|
||||
m.Post("/delete", user.DeleteOpenID)
|
||||
m.Post("/toggle_visibility", user.ToggleOpenIDVisibility)
|
||||
})
|
||||
}
|
||||
|
||||
m.Combo("/ssh").Get(user.SettingsSSHKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||
m.Post("/ssh/delete", user.DeleteSSHKey)
|
||||
@@ -259,14 +262,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
|
||||
})
|
||||
@@ -276,8 +271,8 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Any("/activate", user.Activate)
|
||||
m.Any("/activate_email", user.ActivateEmail)
|
||||
m.Get("/email2user", user.Email2User)
|
||||
m.Get("/forgot_password", user.ForgotPasswd)
|
||||
m.Post("/forgot_password", user.ForgotPasswdPost)
|
||||
m.Get("/forget_password", user.ForgotPasswd)
|
||||
m.Post("/forget_password", user.ForgotPasswdPost)
|
||||
m.Get("/logout", user.SignOut)
|
||||
})
|
||||
// ***** END: User *****
|
||||
@@ -328,6 +323,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) {
|
||||
@@ -353,7 +349,7 @@ func runWeb(ctx *cli.Context) error {
|
||||
return
|
||||
}
|
||||
})
|
||||
m.Post("/attachments", repo.UploadAttachment)
|
||||
m.Post("/issues/attachments", repo.UploadIssueAttachment)
|
||||
}, ignSignIn)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
@@ -369,14 +365,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)
|
||||
@@ -445,11 +435,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)
|
||||
}, repo.MustBeNotBare)
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", repo.Webhooks)
|
||||
@@ -477,7 +462,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)
|
||||
@@ -488,17 +473,17 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
|
||||
|
||||
m.Group("/:index", func() {
|
||||
m.Post("/label", repo.UpdateIssueLabel)
|
||||
m.Post("/milestone", repo.UpdateIssueMilestone)
|
||||
m.Post("/assignee", repo.UpdateIssueAssignee)
|
||||
}, reqRepoWriter)
|
||||
|
||||
m.Group("/:index", func() {
|
||||
m.Post("/title", repo.UpdateIssueTitle)
|
||||
m.Post("/content", repo.UpdateIssueContent)
|
||||
m.Post("/watch", repo.IssueWatch)
|
||||
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
|
||||
})
|
||||
|
||||
m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter)
|
||||
m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter)
|
||||
m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter)
|
||||
m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter)
|
||||
})
|
||||
m.Group("/comments/:id", func() {
|
||||
m.Post("", repo.UpdateCommentContent)
|
||||
@@ -518,15 +503,17 @@ 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)
|
||||
}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef())
|
||||
}, reqRepoWriter, context.RepoRef())
|
||||
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/edit/*", repo.EditRelease)
|
||||
m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
}, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) {
|
||||
}, reqRepoWriter, func(ctx *context.Context) {
|
||||
var err error
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
@@ -565,17 +552,17 @@ func runWeb(ctx *cli.Context) error {
|
||||
return
|
||||
}
|
||||
})
|
||||
}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
|
||||
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
|
||||
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
|
||||
ctx.Handle(404, "", nil)
|
||||
return
|
||||
}
|
||||
})
|
||||
}, reqSignIn, context.RepoAssignment(), context.UnitTypes())
|
||||
}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("", func() {
|
||||
m.Get("/releases", repo.MustBeNotBare, repo.Releases)
|
||||
m.Get("/releases", repo.Releases)
|
||||
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
@@ -583,7 +570,6 @@ func runWeb(ctx *cli.Context) error {
|
||||
}, context.RepoRef())
|
||||
|
||||
// m.Get("/branches", repo.Branches)
|
||||
m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.MustBeNotBare, repo.DeleteBranchPost)
|
||||
|
||||
m.Group("/wiki", func() {
|
||||
m.Get("/?:page", repo.Wiki)
|
||||
@@ -598,12 +584,7 @@ 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.MustBeNotBare, repo.Download)
|
||||
m.Get("/archive/*", repo.Download)
|
||||
|
||||
m.Group("/pulls/:index", func() {
|
||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||
@@ -615,46 +596,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.MustBeNotBare, repo.RawDiff)
|
||||
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.MustBeNotBare, repo.CompareDiff)
|
||||
}, ignSignIn, context.RepoAssignment(), context.UnitTypes())
|
||||
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff)
|
||||
}, 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(), 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)
|
||||
m.Any("/*", func(ctx *context.Context) {
|
||||
ctx.Handle(404, "", nil)
|
||||
})
|
||||
}, 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)
|
||||
@@ -685,32 +651,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:
|
||||
listener, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Failed to bind %s", listenAddr, err)
|
||||
}
|
||||
defer listener.Close()
|
||||
err = fcgi.Serve(listener, 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"})
|
||||
@@ -723,13 +675,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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
88
conf/app.ini
88
conf/app.ini
@@ -1,3 +1,6 @@
|
||||
# NEVER EVER MODIFY THIS FILE
|
||||
# PLEASE MAKE CHANGES ON CORRESPONDING CUSTOM CONFIG FILE
|
||||
|
||||
; App name that shows on every page title
|
||||
APP_NAME = Gitea: Git with a cup of tea
|
||||
; Change it if you run locally
|
||||
@@ -54,11 +57,9 @@ FEED_MAX_COMMIT_NUM = 5
|
||||
; Value of `theme-color` meta tag, used by Android >= 5.0
|
||||
; An invalid color like "none" or "disable" will have the default style
|
||||
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
|
||||
THEME_COLOR_META_TAG = `#6cc644`
|
||||
THEME_COLOR_META_TAG = `#ff5343`
|
||||
; 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
|
||||
@@ -102,8 +103,6 @@ DISABLE_SSH = false
|
||||
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 =
|
||||
; Port number to be exposed in clone URL
|
||||
SSH_PORT = 22
|
||||
; Port number builtin SSH server listens on
|
||||
@@ -147,7 +146,7 @@ RSA = 2048
|
||||
DSA = 1024
|
||||
|
||||
[database]
|
||||
; Either "mysql", "postgres", "mssql" or "sqlite3", it's your choice
|
||||
; Either "mysql", "postgres" or "sqlite3", it's your choice
|
||||
DB_TYPE = mysql
|
||||
HOST = 127.0.0.1:3306
|
||||
NAME = gitea
|
||||
@@ -158,16 +157,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,43 +169,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
|
||||
|
||||
[openid]
|
||||
;
|
||||
; OpenID is an open standard and decentralized authentication protocol.
|
||||
; Your identity is the address of a webpage you provide, which describes
|
||||
; how to prove you are in control of that page.
|
||||
;
|
||||
; For more info: https://en.wikipedia.org/wiki/OpenID
|
||||
;
|
||||
; Current implementation supports OpenID-2.0
|
||||
;
|
||||
; Tested to work providers at the time of writing:
|
||||
; - Any GNUSocial node (your.hostname.tld/username)
|
||||
; - Any SimpleID provider (http://simpleid.koinic.net)
|
||||
; - http://openid.org.cn/
|
||||
; - openid.stackexchange.com
|
||||
; - login.launchpad.net
|
||||
; - <username>.livejournal.com
|
||||
;
|
||||
; Whether to allow signin in via OpenID
|
||||
ENABLE_OPENID_SIGNIN = true
|
||||
; Whether to allow registering via OpenID
|
||||
ENABLE_OPENID_SIGNUP = true
|
||||
; Allowed URI patterns (POSIX regexp).
|
||||
; Space separated.
|
||||
; Only these would be allowed if non-blank.
|
||||
; Example value: trusted.domain.org trusted.domain.net
|
||||
WHITELISTED_URIS =
|
||||
; Forbidden URI patterns (POSIX regexp).
|
||||
; Space sepaated.
|
||||
; Only used if WHITELISTED_URIS is blank.
|
||||
; Example value: loadaverage.org/badguy stackexchange.com/.*spammer
|
||||
BLACKLISTED_URIS =
|
||||
|
||||
[service]
|
||||
ACTIVE_CODE_LIVE_MINUTES = 180
|
||||
@@ -231,13 +186,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
|
||||
@@ -277,10 +225,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"
|
||||
@@ -302,14 +246,14 @@ 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
|
||||
ENABLE_SET_COOKIE = true
|
||||
; Session GC time interval in seconds, default is 86400 (1 day)
|
||||
; Session GC time interval, default is 86400
|
||||
GC_INTERVAL_TIME = 86400
|
||||
; Session life time in seconds, default is 86400 (1 day)
|
||||
; Session life time, default is 86400
|
||||
SESSION_LIFE_TIME = 86400
|
||||
|
||||
[picture]
|
||||
@@ -330,7 +274,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
|
||||
@@ -392,7 +336,7 @@ HOST =
|
||||
; Mailer user name and password
|
||||
USER =
|
||||
PASSWD =
|
||||
; Receivers, can be one or more, e.g. 1@example.com,2@example.com
|
||||
; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"]
|
||||
RECEIVERS =
|
||||
|
||||
; For "database" mode only
|
||||
@@ -426,13 +370,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
|
||||
@@ -463,8 +400,8 @@ DEFAULT_INTERVAL = 8
|
||||
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,한국어
|
||||
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
|
||||
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]
|
||||
@@ -488,7 +425,6 @@ tr-TR = tr
|
||||
cs-CZ = cs-CZ
|
||||
sr-SP = sr
|
||||
sv-SE = sv
|
||||
ko-KR = ko
|
||||
|
||||
; Extension mapping to highlight class
|
||||
; e.g. .toml=ini
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: gitea
|
||||
# Required-Start: $syslog $network
|
||||
# Required-Stop: $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: A self-hosted Git service written in Go.
|
||||
# Description: A self-hosted Git service written in Go.
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Danny Boisvert
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Go Git Service"
|
||||
NAME=gitea
|
||||
SERVICEVERBOSE=yes
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
WORKINGDIR=/home/git/gitea
|
||||
DAEMON=$WORKINGDIR/$NAME
|
||||
DAEMON_ARGS="web"
|
||||
USER=git
|
||||
USERBIND="setcap cap_net_bind_service=+ep"
|
||||
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/1/KILL/5}"
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
do_start()
|
||||
{
|
||||
$USERBIND $DAEMON
|
||||
sh -c "USER=$USER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
|
||||
--background --chdir $WORKINGDIR --chuid $USER \\
|
||||
--exec $DAEMON -- $DAEMON_ARGS"
|
||||
}
|
||||
|
||||
do_stop()
|
||||
{
|
||||
start-stop-daemon --stop --quiet --retry=$STOP_SCHEDULE --pidfile $PIDFILE --name $NAME --oknodo
|
||||
rm -f $PIDFILE
|
||||
}
|
||||
|
||||
do_status()
|
||||
{
|
||||
if [ -f $PIDFILE ]; then
|
||||
if kill -0 $(cat "$PIDFILE"); then
|
||||
echo "$NAME is running, PID is $(cat $PIDFILE)"
|
||||
else
|
||||
echo "$NAME process is dead, but pidfile exists"
|
||||
fi
|
||||
else
|
||||
echo "$NAME is not running"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
;;
|
||||
status)
|
||||
do_status
|
||||
;;
|
||||
restart)
|
||||
echo "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
do_start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/sbin/openrc-run
|
||||
|
||||
DIR=/home/git/gitea
|
||||
USER=git
|
||||
|
||||
start_stop_daemon_args="--user ${USER} --chdir ${DIR}"
|
||||
command="${DIR}/gitea"
|
||||
command_args="web"
|
||||
command_background=yes
|
||||
pidfile=/var/run/gitea.pid
|
||||
|
||||
depend()
|
||||
{
|
||||
need net
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
gitea_version=1.0.1
|
||||
tested_gogs_version="0.9.114.1227"
|
||||
gogs_binary=gogs
|
||||
gitea_binary=gitea
|
||||
download_gitea=true
|
||||
gitea_path=
|
||||
|
||||
function usage() {
|
||||
echo "Optional parameters: [-b Gitea binary] [-i Gitea install dir] [-o gogs binary] [-h help]";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
while getopts ":b::i:o:h:" opt; do
|
||||
case $opt in
|
||||
b)
|
||||
gitea_binary=${OPTARG}
|
||||
download_gitea=false
|
||||
;;
|
||||
i)
|
||||
gitea_path=${OPTARG}
|
||||
;;
|
||||
o)
|
||||
gogs_binary=${OPTARG}
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
\?)
|
||||
echo -e "Invalid option: -$OPTARG"
|
||||
exit 1
|
||||
;;
|
||||
:)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
function exitOnError() {
|
||||
if [ "$?" != "0" ]; then
|
||||
echo -e $1
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function checkBinary() {
|
||||
if [ ! -f $1 ]; then
|
||||
echo "Unable to find $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function continueYN(){
|
||||
while true; do
|
||||
echo -e "$1 Yes or No"
|
||||
read yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) exit 1;;
|
||||
* ) echo "Please answer yes or no.";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
########## Binary checks
|
||||
if pidof "$gogs_binary" >/dev/null; then
|
||||
echo "Please stop gogs before migrating to Gitea"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checkBinary "$gogs_binary"
|
||||
|
||||
if [ ! -x "$gogs_binary" ]; then
|
||||
echo "Please make sure that you are running this script as the gogs user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
########## Version check
|
||||
gogs_version=$(./$gogs_binary --version)
|
||||
original_IFS=$IFS
|
||||
IFS="." && current_version=(${gogs_version#"Gogs version "}) && minimal_version=($tested_gogs_version)
|
||||
IFS=$original_IFS
|
||||
|
||||
count=0
|
||||
for i in "${current_version[@]}"
|
||||
do
|
||||
if [ $i -gt ${minimal_version[$count]} ]; then
|
||||
echo -e "!!!--WARNING--!!!\nYour $gogs_version is newer than the tested Gogs version $tested_gogs_version\nUse this script on your own risk\n!!!--WARNING--!!!"
|
||||
break
|
||||
fi
|
||||
let count+=1
|
||||
done
|
||||
|
||||
########## Disclaimer
|
||||
continueYN "This migration script creates a backup before it starts with the actual migration
|
||||
If something goes wrong you could always resotre this backup.
|
||||
The backups are stored into your gogs folder in gogs-dump-[timestamp].zip file
|
||||
|
||||
Migrating from gogs to gitea, are you sure?"
|
||||
|
||||
########## gogs dump
|
||||
echo "Creating a backup of gogs, this could take a while..."
|
||||
./"$gogs_binary" dump
|
||||
exitOnError "Failed to create a gogs dump"
|
||||
|
||||
########## Create Gitea folder
|
||||
if [ -z "$gitea_path" ]; then
|
||||
echo "Where do you want to install Gitea?"
|
||||
read gitea_path
|
||||
fi
|
||||
|
||||
if [ ! -d "$gitea_path" ]; then
|
||||
mkdir -p "$gitea_path"
|
||||
exitOnError
|
||||
fi
|
||||
|
||||
if [ "$(ls -A $gitea_path)" ]; then
|
||||
continueYN "!!!--WARNING--!!!\nDirectory $gitea_path is not empty, do you want to continue?"
|
||||
fi
|
||||
|
||||
|
||||
########## Download Gitea
|
||||
if [ $download_gitea == true ]; then
|
||||
|
||||
########## Detect os
|
||||
case "$OSTYPE" in
|
||||
darwin*) platform="darwin-10.6";;
|
||||
linux*) platform="linux" ;;
|
||||
freebsd*) platform="bsd" ;;
|
||||
netbsd*) platform="bsd" ;;
|
||||
openbsd*) platform="bsd" ;;
|
||||
*) echo "Unsupported os: $OSTYPE\n Please download/compile your own binary and run this script with the -b option" exit 1;;
|
||||
esac
|
||||
|
||||
arch=""
|
||||
bits=""
|
||||
if [[ "$platform" == "linux" ]] || [[ "$platform" == "bsd" ]]; then
|
||||
arch="$(uname -m | sed -e 's/arm\(.*\)/arm-\1/' -e s/aarch64.*/arm64/)"
|
||||
fi
|
||||
|
||||
if [[ "$platform" == "bsd" ]] && [[ "$arch" != "arm"* ]]; then
|
||||
echo "Currently Gitea only supports arm prebuilt binarys on bsd"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$arch" != "arm"* ]] && [[ "$arch" != "mips"* ]]; then
|
||||
arch=""
|
||||
case "$(getconf LONG_BIT)" in
|
||||
64*) bits="amd64";;
|
||||
32*) bits="386" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
########## Wget Gitea
|
||||
echo "Downloading Gitea"
|
||||
file="gitea-$gitea_version-$platform-$arch$bits"
|
||||
url="https://dl.gitea.io/gitea/$gitea_version/$file"
|
||||
wget "$url" -P "$gitea_path"
|
||||
exitOnError "Failed to download $url"
|
||||
|
||||
wget "$url.sha256" -P "$gitea_path"
|
||||
exitOnError "Failed to Gitea checksum $url.sha256"
|
||||
|
||||
echo "Comparing checksums"
|
||||
gogs_dir=$(pwd)
|
||||
cd "$gitea_path"
|
||||
|
||||
sha256sum -c "$file.sha256"
|
||||
exitOnError "Downloaded Gitea checksums do not match"
|
||||
|
||||
rm "$file.sha256"
|
||||
mv "$file" gitea
|
||||
cd "$gogs_dir"
|
||||
|
||||
else
|
||||
checkBinary "$gitea_binary"
|
||||
if [ "$gitea_binary" != "$gitea_path/gitea" ];then
|
||||
cp "$gitea_binary" "$gitea_path/gitea"
|
||||
fi
|
||||
fi
|
||||
|
||||
########## Copy gogs data to Gitea folder
|
||||
echo "Copying gogs data to Gitea, this could take a while..."
|
||||
cp -R custom "$gitea_path"
|
||||
cp -R data "$gitea_path"
|
||||
#cp -R conf "$gitea_path"
|
||||
|
||||
########## Moving & deleting old files
|
||||
#mv $gitea_path/conf $gitea_path/options
|
||||
cd "$gitea_path"
|
||||
mv "custom/conf/app.ini" "custom/conf/gogs_app.ini"
|
||||
url="https://raw.githubusercontent.com/go-gitea/gitea/v$gitea_version/conf/app.ini"
|
||||
wget "$url" -P "custom/conf/"
|
||||
exitOnError "Unable to download Gitea app.ini"
|
||||
rm -f conf/README.md
|
||||
|
||||
echo -e "Migration is almost complete, you only need to merge custom/conf/gogs_app.ini into custom/conf/app.ini"
|
||||
continueYN "Do you want to start Gitea?"
|
||||
|
||||
########## Starting Gitea
|
||||
echo "Starting Gitea"
|
||||
chmod +x gitea
|
||||
./gitea web
|
||||
exitOnError "Failed to start Gitea"
|
||||
@@ -1,2 +0,0 @@
|
||||
DROP DATABASE IF EXISTS gitea;
|
||||
CREATE DATABASE IF NOT EXISTS gitea CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
@@ -2,5 +2,5 @@
|
||||
[[ -f ./setup ]] && source ./setup
|
||||
|
||||
pushd /root > /dev/null
|
||||
exec su-exec root /usr/sbin/sshd -D
|
||||
exec su-exec root /usr/sbin/sshd -E /var/log/sshd.log -D
|
||||
popd
|
||||
|
||||
@@ -18,6 +18,7 @@ UseDNS no
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
PrintMotd no
|
||||
PrintLastLog no
|
||||
|
||||
PermitUserEnvironment yes
|
||||
PermitRootLogin no
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,156 +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"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"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
|
||||
}
|
||||
|
||||
// GetAndPost provides a convenient helper function for testing an HTTP endpoint with GET and POST method.
|
||||
// The function sends GET first and then POST with the given form.
|
||||
func GetAndPost(url string, form map[string][]string) error {
|
||||
var err error
|
||||
var r *http.Response
|
||||
|
||||
r, err = http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("GET '%s': %s", url, r.Status)
|
||||
}
|
||||
|
||||
r, err = http.PostForm(url, form)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("POST '%s': %s", url, r.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,35 +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 (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/integrations/internal/utils"
|
||||
)
|
||||
|
||||
var signupFormSample map[string][]string = map[string][]string{
|
||||
"Name": {"tester"},
|
||||
"Email": {"user1@example.com"},
|
||||
"Passwd": {"12345678"},
|
||||
}
|
||||
|
||||
func signup(t *utils.T) error {
|
||||
return utils.GetAndPost("http://:"+ServerHTTPPort+"/user/sign_up", signupFormSample)
|
||||
}
|
||||
|
||||
func TestSignup(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, signup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
27
main.go
27
main.go
@@ -1,5 +1,5 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// 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.
|
||||
|
||||
@@ -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 = ""
|
||||
const Version = "0.9.99.0915"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -59,21 +63,21 @@ type Access struct {
|
||||
Mode AccessMode
|
||||
}
|
||||
|
||||
func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
|
||||
func accessLevel(e Engine, user *User, repo *Repository) (AccessMode, error) {
|
||||
mode := AccessModeNone
|
||||
if !repo.IsPrivate {
|
||||
mode = AccessModeRead
|
||||
}
|
||||
|
||||
if userID == 0 {
|
||||
if user == nil {
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
if userID == repo.OwnerID {
|
||||
if user.ID == repo.OwnerID {
|
||||
return AccessModeOwner, nil
|
||||
}
|
||||
|
||||
a := &Access{UserID: userID, RepoID: repo.ID}
|
||||
a := &Access{UserID: user.ID, RepoID: repo.ID}
|
||||
if has, err := e.Get(a); !has || err != nil {
|
||||
return mode, err
|
||||
}
|
||||
@@ -81,60 +85,44 @@ func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
|
||||
}
|
||||
|
||||
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
||||
// user does not have access.
|
||||
func AccessLevel(userID int64, repo *Repository) (AccessMode, error) {
|
||||
return accessLevel(x, userID, repo)
|
||||
// user does not have access. User can be nil!
|
||||
func AccessLevel(user *User, repo *Repository) (AccessMode, error) {
|
||||
return accessLevel(x, user, repo)
|
||||
}
|
||||
|
||||
func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
mode, err := accessLevel(e, userID, repo)
|
||||
func hasAccess(e Engine, user *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
mode, err := accessLevel(e, user, repo)
|
||||
return testMode <= mode, err
|
||||
}
|
||||
|
||||
// HasAccess returns true if user has access to repo
|
||||
func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
return hasAccess(x, userID, repo, testMode)
|
||||
}
|
||||
|
||||
type repoAccess struct {
|
||||
Access `xorm:"extends"`
|
||||
Repository `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (repoAccess) TableName() string {
|
||||
return "access"
|
||||
// HasAccess returns true if someone has the request access level. User can be nil!
|
||||
func HasAccess(user *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
return hasAccess(x, user, repo, testMode)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -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.ID, repo1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, AccessModeOwner, level)
|
||||
|
||||
level, err = AccessLevel(user1.ID, repo2)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, AccessModeWrite, level)
|
||||
|
||||
level, err = AccessLevel(user2.ID, repo1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, AccessModeRead, level)
|
||||
|
||||
level, err = AccessLevel(user2.ID, 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.ID, repo1, accessMode)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = HasAccess(user1.ID, repo2, accessMode)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, accessMode <= AccessModeWrite, has)
|
||||
|
||||
has, err = HasAccess(user2.ID, repo1, accessMode)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, accessMode <= AccessModeRead, has)
|
||||
|
||||
has, err = HasAccess(user2.ID, 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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -494,12 +494,12 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||
isNewBranch := false
|
||||
opType := ActionCommitRepo
|
||||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
|
||||
if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
|
||||
opType = ActionPushTag
|
||||
opts.Commits = &PushCommits{}
|
||||
} else {
|
||||
// if not the first commit, set the compare URL.
|
||||
if opts.OldCommitID == git.EmptySHA {
|
||||
if opts.OldCommitID == git.EMPTY_SHA {
|
||||
isNewBranch = true
|
||||
} else {
|
||||
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
|
||||
@@ -539,7 +539,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||
}()
|
||||
|
||||
apiPusher := pusher.APIFormat()
|
||||
apiRepo := repo.APIFormat(AccessModeNone)
|
||||
apiRepo := repo.APIFormat(nil)
|
||||
|
||||
var shaSum string
|
||||
switch opType {
|
||||
@@ -562,7 +562,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
|
||||
}
|
||||
shaSum, err = gitRepo.GetBranchCommitID(refName)
|
||||
shaSum, err = gitRepo.GetBranchCommitID(opts.RefFullName)
|
||||
if err != nil {
|
||||
log.Error(4, "GetBranchCommitID[%s]: %v", opts.RefFullName, err)
|
||||
}
|
||||
@@ -580,7 +580,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
|
||||
}
|
||||
shaSum, err = gitRepo.GetTagCommitID(refName)
|
||||
shaSum, err = gitRepo.GetTagCommitID(opts.RefFullName)
|
||||
if err != nil {
|
||||
log.Error(4, "GetTagCommitID[%s]: %v", opts.RefFullName, 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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 branches
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
164
models/error.go
164
models/error.go
@@ -93,21 +93,6 @@ func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
||||
type ErrOpenIDAlreadyUsed struct {
|
||||
OpenID string
|
||||
}
|
||||
|
||||
// IsErrOpenIDAlreadyUsed checks if an error is a ErrOpenIDAlreadyUsed.
|
||||
func IsErrOpenIDAlreadyUsed(err error) bool {
|
||||
_, ok := err.(ErrOpenIDAlreadyUsed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrOpenIDAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("OpenID has been used [oid: %s]", err.OpenID)
|
||||
}
|
||||
|
||||
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
|
||||
type ErrUserOwnRepos struct {
|
||||
UID int64
|
||||
@@ -138,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
|
||||
@@ -228,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.
|
||||
@@ -240,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.
|
||||
@@ -260,54 +229,6 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
}
|
||||
|
||||
// ErrGPGKeyNotExist represents a "GPGKeyNotExist" kind of error.
|
||||
type ErrGPGKeyNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
// IsErrGPGKeyNotExist checks if an error is a ErrGPGKeyNotExist.
|
||||
func IsErrGPGKeyNotExist(err error) bool {
|
||||
_, ok := err.(ErrGPGKeyNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrGPGKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
// ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error.
|
||||
type ErrGPGKeyIDAlreadyUsed struct {
|
||||
KeyID string
|
||||
}
|
||||
|
||||
// IsErrGPGKeyIDAlreadyUsed checks if an error is a ErrKeyNameAlreadyUsed.
|
||||
func IsErrGPGKeyIDAlreadyUsed(err error) bool {
|
||||
_, ok := err.(ErrGPGKeyIDAlreadyUsed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrGPGKeyIDAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists [key_id: %s]", err.KeyID)
|
||||
}
|
||||
|
||||
// ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error.
|
||||
type ErrGPGKeyAccessDenied struct {
|
||||
UserID int64
|
||||
KeyID int64
|
||||
}
|
||||
|
||||
// IsErrGPGKeyAccessDenied checks if an error is a ErrGPGKeyAccessDenied.
|
||||
func IsErrGPGKeyAccessDenied(err error) bool {
|
||||
_, ok := err.(ErrGPGKeyAccessDenied)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Error pretty-prints an error of type ErrGPGKeyAccessDenied.
|
||||
func (err ErrGPGKeyAccessDenied) Error() string {
|
||||
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d]",
|
||||
err.UserID, err.KeyID)
|
||||
}
|
||||
|
||||
// ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
|
||||
type ErrKeyAccessDenied struct {
|
||||
UserID int64
|
||||
@@ -473,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
|
||||
@@ -664,7 +569,7 @@ type ErrPullRequestNotExist struct {
|
||||
IssueID int64
|
||||
HeadRepoID int64
|
||||
BaseRepoID int64
|
||||
HeadBranch string
|
||||
HeadBarcnh string
|
||||
BaseBranch string
|
||||
}
|
||||
|
||||
@@ -676,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
|
||||
@@ -868,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)
|
||||
}
|
||||
|
||||
// ____ ___ .__ .___
|
||||
// | | \______ | | _________ __| _/
|
||||
// | | /\____ \| | / _ \__ \ / __ |
|
||||
@@ -910,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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,19 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
type: 7 # label
|
||||
poster_id: 2
|
||||
issue_id: 1 # in repo_id 1
|
||||
label_id: 1
|
||||
content: "1"
|
||||
-
|
||||
id: 2
|
||||
type: 0 # comment
|
||||
poster_id: 3 # user not watching (see watch.yml)
|
||||
issue_id: 1 # in repo_id 1
|
||||
content: "good work!"
|
||||
-
|
||||
id: 3
|
||||
type: 0 # comment
|
||||
poster_id: 5 # user not watching (see watch.yml)
|
||||
issue_id: 1 # in repo_id 1
|
||||
content: "meh..."
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
user_id: 4
|
||||
follow_id: 2
|
||||
@@ -1,5 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
hook_id: 1
|
||||
uuid: uuid1
|
||||
@@ -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: 2
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
owner_id: 2
|
||||
lower_name: oldrepo1
|
||||
redirect_repo_id: 1
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
uid: 2
|
||||
repo_id: 2
|
||||
|
||||
-
|
||||
id: 2
|
||||
uid: 2
|
||||
repo_id: 4
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,214 +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
|
||||
num_followers: 1
|
||||
|
||||
-
|
||||
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
|
||||
num_following: 1
|
||||
|
||||
-
|
||||
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
|
||||
num_following: 0
|
||||
|
||||
-
|
||||
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
|
||||
@@ -1,17 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
uid: 1
|
||||
uri: https://user1.domain1.tld/
|
||||
show: false
|
||||
|
||||
-
|
||||
id: 2
|
||||
uid: 1
|
||||
uri: http://user1.domain2.tld/
|
||||
show: true
|
||||
|
||||
-
|
||||
id: 3
|
||||
uid: 2
|
||||
uri: https://domain1.tld/user2/
|
||||
show: true
|
||||
@@ -1,9 +0,0 @@
|
||||
-
|
||||
id: 1
|
||||
user_id: 1
|
||||
repo_id: 1
|
||||
|
||||
-
|
||||
id: 2
|
||||
user_id: 4
|
||||
repo_id: 1
|
||||
@@ -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
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/highlight"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/template/highlight"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"golang.org/x/net/html/charset"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -378,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 {
|
||||
@@ -447,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
|
||||
@@ -483,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)
|
||||
}
|
||||
@@ -554,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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -1,462 +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 (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
"golang.org/x/crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
// GPGKey represents a GPG key.
|
||||
type GPGKey struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"INDEX NOT NULL"`
|
||||
KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
|
||||
PrimaryKeyID string `xorm:"CHAR(16)"`
|
||||
Content string `xorm:"TEXT NOT NULL"`
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
Expired time.Time `xorm:"-"`
|
||||
ExpiredUnix int64
|
||||
Added time.Time `xorm:"-"`
|
||||
AddedUnix int64
|
||||
SubsKey []*GPGKey `xorm:"-"`
|
||||
Emails []*EmailAddress
|
||||
CanSign bool
|
||||
CanEncryptComms bool
|
||||
CanEncryptStorage bool
|
||||
CanCertify bool
|
||||
}
|
||||
|
||||
// BeforeInsert will be invoked by XORM before inserting a record
|
||||
func (key *GPGKey) BeforeInsert() {
|
||||
key.AddedUnix = time.Now().Unix()
|
||||
key.ExpiredUnix = key.Expired.Unix()
|
||||
key.CreatedUnix = key.Created.Unix()
|
||||
}
|
||||
|
||||
// AfterSet is invoked from XORM after setting the value of a field of this object.
|
||||
func (key *GPGKey) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "key_id":
|
||||
x.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey)
|
||||
case "added_unix":
|
||||
key.Added = time.Unix(key.AddedUnix, 0).Local()
|
||||
case "expired_unix":
|
||||
key.Expired = time.Unix(key.ExpiredUnix, 0).Local()
|
||||
case "created_unix":
|
||||
key.Created = time.Unix(key.CreatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
// ListGPGKeys returns a list of public keys belongs to given user.
|
||||
func ListGPGKeys(uid int64) ([]*GPGKey, error) {
|
||||
keys := make([]*GPGKey, 0, 5)
|
||||
return keys, x.Where("owner_id=? AND primary_key_id=''", uid).Find(&keys)
|
||||
}
|
||||
|
||||
// GetGPGKeyByID returns public key by given ID.
|
||||
func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
|
||||
key := new(GPGKey)
|
||||
has, err := x.Id(keyID).Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrGPGKeyNotExist{keyID}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key.
|
||||
// The function returns the actual public key on success
|
||||
func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) {
|
||||
list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list[0], nil
|
||||
}
|
||||
|
||||
//addGPGKey add key and subkeys to database
|
||||
func addGPGKey(e Engine, key *GPGKey) (err error) {
|
||||
// Save GPG primary key.
|
||||
if _, err = e.Insert(key); err != nil {
|
||||
return err
|
||||
}
|
||||
// Save GPG subs key.
|
||||
for _, subkey := range key.SubsKey {
|
||||
if err := addGPGKey(e, subkey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddGPGKey adds new public key to database.
|
||||
func AddGPGKey(ownerID int64, content string) (*GPGKey, error) {
|
||||
ekey, err := checkArmoredGPGKeyString(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Key ID cannot be duplicated.
|
||||
has, err := x.Where("key_id=?", ekey.PrimaryKey.KeyIdString()).
|
||||
Get(new(GPGKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if has {
|
||||
return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()}
|
||||
}
|
||||
|
||||
//Get DB session
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := parseGPGKey(ownerID, ekey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = addGPGKey(sess, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, sess.Commit()
|
||||
}
|
||||
|
||||
//base64EncPubKey encode public kay content to base 64
|
||||
func base64EncPubKey(pubkey *packet.PublicKey) (string, error) {
|
||||
var w bytes.Buffer
|
||||
err := pubkey.Serialize(&w)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(w.Bytes()), nil
|
||||
}
|
||||
|
||||
//parseSubGPGKey parse a sub Key
|
||||
func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) {
|
||||
content, err := base64EncPubKey(pubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GPGKey{
|
||||
OwnerID: ownerID,
|
||||
KeyID: pubkey.KeyIdString(),
|
||||
PrimaryKeyID: primaryID,
|
||||
Content: content,
|
||||
Created: pubkey.CreationTime,
|
||||
Expired: expiry,
|
||||
CanSign: pubkey.CanSign(),
|
||||
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
|
||||
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
|
||||
CanCertify: pubkey.PubKeyAlgo.CanSign(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
|
||||
func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
|
||||
pubkey := e.PrimaryKey
|
||||
|
||||
//Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
|
||||
var selfSig *packet.Signature
|
||||
for _, ident := range e.Identities {
|
||||
if selfSig == nil {
|
||||
selfSig = ident.SelfSignature
|
||||
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
|
||||
selfSig = ident.SelfSignature
|
||||
break
|
||||
}
|
||||
}
|
||||
expiry := time.Time{}
|
||||
if selfSig.KeyLifetimeSecs != nil {
|
||||
expiry = selfSig.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
|
||||
}
|
||||
|
||||
//Parse Subkeys
|
||||
subkeys := make([]*GPGKey, len(e.Subkeys))
|
||||
for i, k := range e.Subkeys {
|
||||
subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subkeys[i] = subs
|
||||
}
|
||||
|
||||
//Check emails
|
||||
userEmails, err := GetEmailAddresses(ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emails := make([]*EmailAddress, len(e.Identities))
|
||||
n := 0
|
||||
for _, ident := range e.Identities {
|
||||
|
||||
for _, e := range userEmails {
|
||||
if e.Email == ident.UserId.Email && e.IsActivated {
|
||||
emails[n] = e
|
||||
break
|
||||
}
|
||||
}
|
||||
if emails[n] == nil {
|
||||
return nil, fmt.Errorf("Failed to found email or is not confirmed : %s", ident.UserId.Email)
|
||||
}
|
||||
n++
|
||||
}
|
||||
content, err := base64EncPubKey(pubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GPGKey{
|
||||
OwnerID: ownerID,
|
||||
KeyID: pubkey.KeyIdString(),
|
||||
PrimaryKeyID: "",
|
||||
Content: content,
|
||||
Created: pubkey.CreationTime,
|
||||
Expired: expiry,
|
||||
Emails: emails,
|
||||
SubsKey: subkeys,
|
||||
CanSign: pubkey.CanSign(),
|
||||
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
|
||||
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
|
||||
CanCertify: pubkey.PubKeyAlgo.CanSign(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// deleteGPGKey does the actual key deletion
|
||||
func deleteGPGKey(e *xorm.Session, keyID string) (int64, error) {
|
||||
if keyID == "" {
|
||||
return 0, fmt.Errorf("empty KeyId forbidden") //Should never happen but just to be sure
|
||||
}
|
||||
return e.Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey))
|
||||
}
|
||||
|
||||
// DeleteGPGKey deletes GPG key information in database.
|
||||
func DeleteGPGKey(doer *User, id int64) (err error) {
|
||||
key, err := GetGPGKeyByID(id)
|
||||
if err != nil {
|
||||
if IsErrGPGKeyNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("GetPublicKeyByID: %v", err)
|
||||
}
|
||||
|
||||
// Check if user has access to delete this key.
|
||||
if !doer.IsAdmin && doer.ID != key.OwnerID {
|
||||
return ErrGPGKeyAccessDenied{doer.ID, key.ID}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = deleteGPGKey(sess, key.KeyID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitVerification represents a commit validation of signature
|
||||
type CommitVerification struct {
|
||||
Verified bool
|
||||
Reason string
|
||||
SigningUser *User
|
||||
SigningKey *GPGKey
|
||||
}
|
||||
|
||||
// SignCommit represents a commit with validation of signature.
|
||||
type SignCommit struct {
|
||||
Verification *CommitVerification
|
||||
*UserCommit
|
||||
}
|
||||
|
||||
func readerFromBase64(s string) (io.Reader, error) {
|
||||
bs, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(bs), nil
|
||||
}
|
||||
|
||||
func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) {
|
||||
h := hashFunc.New()
|
||||
if _, err := h.Write(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// readArmoredSign read an armored signature block with the given type. https://sourcegraph.com/github.com/golang/crypto/-/blob/openpgp/read.go#L24:6-24:17
|
||||
func readArmoredSign(r io.Reader) (body io.Reader, err error) {
|
||||
block, err := armor.Decode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if block.Type != openpgp.SignatureType {
|
||||
return nil, fmt.Errorf("expected '" + openpgp.SignatureType + "', got: " + block.Type)
|
||||
}
|
||||
return block.Body, nil
|
||||
}
|
||||
|
||||
func extractSignature(s string) (*packet.Signature, error) {
|
||||
r, err := readArmoredSign(strings.NewReader(s))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read signature armor")
|
||||
}
|
||||
p, err := packet.Read(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read signature packet")
|
||||
}
|
||||
sig, ok := p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Packet is not a signature")
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
|
||||
//Check if key can sign
|
||||
if !k.CanSign {
|
||||
return fmt.Errorf("key can not sign")
|
||||
}
|
||||
//Decode key
|
||||
b, err := readerFromBase64(k.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//Read key
|
||||
p, err := packet.Read(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Check type
|
||||
pkey, ok := p.(*packet.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("key is not a public key")
|
||||
}
|
||||
|
||||
return pkey.VerifySignature(h, s)
|
||||
}
|
||||
|
||||
// ParseCommitWithSignature check if signature is good against keystore.
|
||||
func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
|
||||
|
||||
if c.Signature != nil {
|
||||
|
||||
//Parsing signature
|
||||
sig, err := extractSignature(c.Signature.Signature)
|
||||
if err != nil { //Skipping failed to extract sign
|
||||
log.Error(3, "SignatureRead err: %v", err)
|
||||
return &CommitVerification{
|
||||
Verified: false,
|
||||
Reason: "gpg.error.extract_sign",
|
||||
}
|
||||
}
|
||||
|
||||
//Find Committer account
|
||||
committer, err := GetUserByEmail(c.Committer.Email)
|
||||
if err != nil { //Skipping not user for commiter
|
||||
log.Error(3, "NoCommitterAccount: %v", err)
|
||||
return &CommitVerification{
|
||||
Verified: false,
|
||||
Reason: "gpg.error.no_committer_account",
|
||||
}
|
||||
}
|
||||
|
||||
keys, err := ListGPGKeys(committer.ID)
|
||||
if err != nil || len(keys) == 0 { //Skipping failed to get gpg keys of user
|
||||
log.Error(3, "ListGPGKeys: %v", err)
|
||||
return &CommitVerification{
|
||||
Verified: false,
|
||||
Reason: "gpg.error.failed_retrieval_gpg_keys",
|
||||
}
|
||||
}
|
||||
|
||||
//Generating hash of commit
|
||||
hash, err := populateHash(sig.Hash, []byte(c.Signature.Payload))
|
||||
if err != nil { //Skipping ailed to generate hash
|
||||
log.Error(3, "PopulateHash: %v", err)
|
||||
return &CommitVerification{
|
||||
Verified: false,
|
||||
Reason: "gpg.error.generate_hash",
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
//We get PK
|
||||
if err := verifySign(sig, hash, k); err == nil {
|
||||
return &CommitVerification{ //Everything is ok
|
||||
Verified: true,
|
||||
Reason: fmt.Sprintf("%s <%s> / %s", c.Committer.Name, c.Committer.Email, k.KeyID),
|
||||
SigningUser: committer,
|
||||
SigningKey: k,
|
||||
}
|
||||
}
|
||||
//And test also SubsKey
|
||||
for _, sk := range k.SubsKey {
|
||||
if err := verifySign(sig, hash, sk); err == nil {
|
||||
return &CommitVerification{ //Everything is ok
|
||||
Verified: true,
|
||||
Reason: fmt.Sprintf("%s <%s> / %s", c.Committer.Name, c.Committer.Email, sk.KeyID),
|
||||
SigningUser: committer,
|
||||
SigningKey: sk,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &CommitVerification{ //Default at this stage
|
||||
Verified: false,
|
||||
Reason: "gpg.error.no_gpg_keys_found",
|
||||
}
|
||||
}
|
||||
|
||||
return &CommitVerification{
|
||||
Verified: false, //Default value
|
||||
Reason: "gpg.error.not_signed_commit", //Default value
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
|
||||
func ParseCommitsWithSignature(oldCommits *list.List) *list.List {
|
||||
var (
|
||||
newCommits = list.New()
|
||||
e = oldCommits.Front()
|
||||
)
|
||||
for e != nil {
|
||||
c := e.Value.(UserCommit)
|
||||
newCommits.PushBack(SignCommit{
|
||||
UserCommit: &c,
|
||||
Verification: ParseCommitWithSignature(c.Commit),
|
||||
})
|
||||
e = e.Next()
|
||||
}
|
||||
return newCommits
|
||||
}
|
||||
@@ -1,164 +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 TestCheckArmoredGPGKeyString(t *testing.T) {
|
||||
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
|
||||
z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
|
||||
/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
|
||||
vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
|
||||
0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
|
||||
mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
|
||||
IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
|
||||
Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
|
||||
KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
|
||||
MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
|
||||
ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
|
||||
sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
|
||||
T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
|
||||
iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
|
||||
QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
|
||||
pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
|
||||
JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
|
||||
/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
|
||||
ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
|
||||
cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
|
||||
CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
|
||||
6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
|
||||
lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
|
||||
RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
|
||||
Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
|
||||
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
|
||||
=i9b7
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
|
||||
key, err := checkArmoredGPGKeyString(testGPGArmor)
|
||||
assert.Nil(t, err, "Could not parse a valid GPG armored key", key)
|
||||
//TODO verify value of key
|
||||
}
|
||||
|
||||
func TestExtractSignature(t *testing.T) {
|
||||
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
|
||||
z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
|
||||
/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
|
||||
vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
|
||||
0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
|
||||
mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
|
||||
IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
|
||||
Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
|
||||
KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
|
||||
MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
|
||||
ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
|
||||
sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
|
||||
T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
|
||||
iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
|
||||
QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
|
||||
pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
|
||||
JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
|
||||
/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
|
||||
ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
|
||||
cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
|
||||
CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
|
||||
6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
|
||||
lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
|
||||
RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
|
||||
Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
|
||||
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
|
||||
=i9b7
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
ekey, err := checkArmoredGPGKeyString(testGPGArmor)
|
||||
assert.Nil(t, err, "Could not parse a valid GPG armored key", ekey)
|
||||
|
||||
pubkey := ekey.PrimaryKey
|
||||
content, err := base64EncPubKey(pubkey)
|
||||
assert.Nil(t, err, "Could not base64 encode a valid PublicKey content", ekey)
|
||||
|
||||
key := &GPGKey{
|
||||
KeyID: pubkey.KeyIdString(),
|
||||
Content: content,
|
||||
Created: pubkey.CreationTime,
|
||||
CanSign: pubkey.CanSign(),
|
||||
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
|
||||
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
|
||||
CanCertify: pubkey.PubKeyAlgo.CanSign(),
|
||||
}
|
||||
|
||||
cannotsignkey := &GPGKey{
|
||||
KeyID: pubkey.KeyIdString(),
|
||||
Content: content,
|
||||
Created: pubkey.CreationTime,
|
||||
CanSign: false,
|
||||
CanEncryptComms: false,
|
||||
CanEncryptStorage: false,
|
||||
CanCertify: false,
|
||||
}
|
||||
|
||||
testGoodSigArmor := `-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCAAdFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAljAiQIACgkQroJVuKDY
|
||||
KORvCgf6A/Ehh0r7QbO2tFEghT+/Ab+bN7jRN3zP9ed6/q/ophYmkrU0NibtbJH9
|
||||
AwFVdHxCmj78SdiRjaTKyevklXw34nvMftmvnOI4lBNUdw6KWl25/n/7wN0l2oZW
|
||||
rW3UawYpZgodXiLTYarfEimkDQmT67ArScjRA6lLbkEYKO0VdwDu+Z6yBUH3GWtm
|
||||
45RkXpnsF6AXUfuD7YxnfyyDE1A7g7zj4vVYUAfWukJjqow/LsCUgETETJOqj9q3
|
||||
52/oQDs04fVkIEtCDulcY+K/fKlukBPJf9WceNDEqiENUzN/Z1y0E+tJ07cSy4bk
|
||||
yIJb+d0OAaG8bxloO7nJq4Res1Qa8Q==
|
||||
=puvG
|
||||
-----END PGP SIGNATURE-----`
|
||||
testGoodPayload := `tree 56ae8d2799882b20381fc11659db06c16c68c61a
|
||||
parent c7870c39e4e6b247235ca005797703ec4254613f
|
||||
author Antoine GIRARD <sapk@sapk.fr> 1489012989 +0100
|
||||
committer Antoine GIRARD <sapk@sapk.fr> 1489012989 +0100
|
||||
|
||||
Goog GPG
|
||||
`
|
||||
|
||||
testBadSigArmor := `-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCAAdFiEE5yr4rn9ulbdMxJFiPYI/ySNrtNkFAljAiYkACgkQPYI/ySNr
|
||||
tNmDdQf+NXhVRiOGt0GucpjJCGrOnK/qqVUmQyRUfrqzVUdb/1/Ws84V5/wE547I
|
||||
6z3oxeBKFsJa1CtIlxYaUyVhYnDzQtphJzub+Aw3UG0E2ywiE+N7RCa1Ufl7pPxJ
|
||||
U0SD6gvNaeTDQV/Wctu8v8DkCtEd3N8cMCDWhvy/FQEDztVtzm8hMe0Vdm0ozEH6
|
||||
P0W93sDNkLC5/qpWDN44sFlYDstW5VhMrnF0r/ohfaK2kpYHhkPk7WtOoHSUwQSg
|
||||
c4gfhjvXIQrWFnII1Kr5jFGlmgNSR02qpb31VGkMzSnBhWVf2OaHS/kI49QHJakq
|
||||
AhVDEnoYLCgoDGg9c3p1Ll2452/c6Q==
|
||||
=uoGV
|
||||
-----END PGP SIGNATURE-----`
|
||||
testBadPayload := `tree 3074ff04951956a974e8b02d57733b0766f7cf6c
|
||||
parent fd3577542f7ad1554c7c7c0eb86bb57a1324ad91
|
||||
author Antoine GIRARD <sapk@sapk.fr> 1489013107 +0100
|
||||
committer Antoine GIRARD <sapk@sapk.fr> 1489013107 +0100
|
||||
|
||||
Unkonwn GPG key with good email
|
||||
`
|
||||
//Reading Sign
|
||||
goodSig, err := extractSignature(testGoodSigArmor)
|
||||
assert.Nil(t, err, "Could not parse a valid GPG armored signature", testGoodSigArmor)
|
||||
badSig, err := extractSignature(testBadSigArmor)
|
||||
assert.Nil(t, err, "Could not parse a valid GPG armored signature", testBadSigArmor)
|
||||
|
||||
//Generating hash of commit
|
||||
goodHash, err := populateHash(goodSig.Hash, []byte(testGoodPayload))
|
||||
assert.Nil(t, err, "Could not generate a valid hash of payload", testGoodPayload)
|
||||
badHash, err := populateHash(badSig.Hash, []byte(testBadPayload))
|
||||
assert.Nil(t, err, "Could not generate a valid hash of payload", testBadPayload)
|
||||
|
||||
//Verify
|
||||
err = verifySign(goodSig, goodHash, key)
|
||||
assert.Nil(t, err, "Could not validate a good signature")
|
||||
err = verifySign(badSig, badHash, key)
|
||||
assert.NotNil(t, err, "Validate a bad signature")
|
||||
err = verifySign(goodSig, goodHash, cannotsignkey)
|
||||
assert.NotNil(t, err, "Validate a bad signature with a kay that can not sign")
|
||||
}
|
||||
108
models/graph.go
108
models/graph.go
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
1334
models/issue.go
1334
models/issue.go
File diff suppressed because it is too large
Load Diff
@@ -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)"`
|
||||
@@ -146,55 +123,14 @@ func (c *Comment) AfterDelete() {
|
||||
}
|
||||
}
|
||||
|
||||
// HTMLURL formats a URL-string to the issue-comment
|
||||
func (c *Comment) HTMLURL() string {
|
||||
issue, err := GetIssueByID(c.IssueID)
|
||||
if err != nil { // Silently dropping errors :unamused:
|
||||
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err)
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s#issuecomment-%d", issue.HTMLURL(), c.ID)
|
||||
}
|
||||
|
||||
// IssueURL formats a URL-string to the issue
|
||||
func (c *Comment) IssueURL() string {
|
||||
issue, err := GetIssueByID(c.IssueID)
|
||||
if err != nil { // Silently dropping errors :unamused:
|
||||
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if issue.IsPull {
|
||||
return ""
|
||||
}
|
||||
return issue.HTMLURL()
|
||||
}
|
||||
|
||||
// PRURL formats a URL-string to the pull-request
|
||||
func (c *Comment) PRURL() string {
|
||||
issue, err := GetIssueByID(c.IssueID)
|
||||
if err != nil { // Silently dropping errors :unamused:
|
||||
log.Error(4, "GetIssueByID(%d): %v", c.IssueID, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if !issue.IsPull {
|
||||
return ""
|
||||
}
|
||||
return issue.HTMLURL()
|
||||
}
|
||||
|
||||
// APIFormat converts a Comment to the api.Comment format
|
||||
func (c *Comment) APIFormat() *api.Comment {
|
||||
return &api.Comment{
|
||||
ID: c.ID,
|
||||
Poster: c.Poster.APIFormat(),
|
||||
HTMLURL: c.HTMLURL(),
|
||||
IssueURL: c.IssueURL(),
|
||||
PRURL: c.PRURL(),
|
||||
Body: c.Content,
|
||||
Created: c.Created,
|
||||
Updated: c.Updated,
|
||||
ID: c.ID,
|
||||
Poster: c.Poster.APIFormat(),
|
||||
Body: c.Content,
|
||||
Created: c.Created,
|
||||
Updated: c.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,76 +144,11 @@ 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) {
|
||||
func (c *Comment) MailParticipants(opType ActionType, issue *Issue) (err error) {
|
||||
mentions := markdown.FindAllMentions(c.Content)
|
||||
if err = UpdateIssueMentions(e, c.IssueID, mentions); err != nil {
|
||||
if err = UpdateIssueMentions(c.IssueID, mentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
|
||||
}
|
||||
|
||||
@@ -297,35 +168,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{
|
||||
@@ -406,9 +262,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
||||
if err = notifyWatchers(e, act); err != nil {
|
||||
log.Error(4, "notifyWatchers: %v", err)
|
||||
}
|
||||
if err = comment.MailParticipants(e, act.OpType, opts.Issue); err != nil {
|
||||
log.Error(4, "MailParticipants: %v", err)
|
||||
}
|
||||
comment.MailParticipants(act.OpType, opts.Issue)
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
@@ -427,83 +281,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.
|
||||
@@ -586,17 +375,6 @@ func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, erro
|
||||
return comments, sess.Find(&comments)
|
||||
}
|
||||
|
||||
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")
|
||||
if since > 0 {
|
||||
sess.And("comment.updated_unix >= ?", since)
|
||||
}
|
||||
return comments, sess.Find(&comments)
|
||||
}
|
||||
|
||||
func getCommentsByIssueID(e Engine, issueID int64) ([]*Comment, error) {
|
||||
return getCommentsByIssueIDSince(e, issueID, -1)
|
||||
}
|
||||
@@ -611,33 +389,34 @@ func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) {
|
||||
return getCommentsByIssueIDSince(x, issueID, since)
|
||||
}
|
||||
|
||||
// GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point.
|
||||
func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
|
||||
return getCommentsByRepoIDSince(x, repoID, since)
|
||||
}
|
||||
|
||||
// UpdateComment updates information of comment.
|
||||
func UpdateComment(c *Comment) error {
|
||||
_, err := x.Id(c.ID).AllCols().Update(c)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
posterIDs := issues.getPosterIDs()
|
||||
posterMaps := make(map[int64]*User, len(posterIDs))
|
||||
err := e.
|
||||
In("id", posterIDs).
|
||||
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)
|
||||
}
|
||||
@@ -1,65 +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 TestIssueList_LoadRepositories(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
issueList := IssueList{
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue),
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue),
|
||||
}
|
||||
|
||||
repos, err := issueList.LoadRepositories()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, repos, 2)
|
||||
for _, issue := range issueList {
|
||||
assert.EqualValues(t, issue.RepoID, issue.Repo.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueList_LoadAttributes(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
issueList := IssueList{
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue),
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue),
|
||||
}
|
||||
|
||||
assert.NoError(t, issueList.LoadAttributes())
|
||||
for _, issue := range issueList {
|
||||
assert.EqualValues(t, issue.RepoID, issue.Repo.ID)
|
||||
for _, label := range issue.Labels {
|
||||
assert.EqualValues(t, issue.RepoID, label.RepoID)
|
||||
AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID})
|
||||
}
|
||||
if issue.PosterID > 0 {
|
||||
assert.EqualValues(t, issue.PosterID, issue.Poster.ID)
|
||||
}
|
||||
if issue.AssigneeID > 0 {
|
||||
assert.EqualValues(t, issue.AssigneeID, issue.Assignee.ID)
|
||||
}
|
||||
if issue.MilestoneID > 0 {
|
||||
assert.EqualValues(t, issue.MilestoneID, issue.Milestone.ID)
|
||||
}
|
||||
if issue.IsPull {
|
||||
assert.EqualValues(t, issue.ID, issue.PullRequest.IssueID)
|
||||
}
|
||||
for _, attachment := range issue.Attachments {
|
||||
assert.EqualValues(t, issue.ID, attachment.IssueID)
|
||||
}
|
||||
for _, comment := range issue.Comments {
|
||||
assert.EqualValues(t, issue.ID, comment.IssueID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,27 +19,15 @@ func (issue *Issue) mailSubject() string {
|
||||
}
|
||||
|
||||
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
|
||||
// This function sends two list of emails:
|
||||
// 1. Repository watchers and users who are participated in comments.
|
||||
// 2. Users who are not in 1. but get mentioned in current issue/comment.
|
||||
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error {
|
||||
if !setting.Service.EnableNotifyMail {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mail wahtcers.
|
||||
watchers, err := GetWatchers(issue.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err)
|
||||
}
|
||||
participants, err := GetParticipantsByIssueID(issue.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err)
|
||||
}
|
||||
|
||||
// In case the issue poster is not watching the repository,
|
||||
// even if we have duplicated in watchers, can be safely filtered out.
|
||||
if issue.PosterID != doer.ID {
|
||||
participants = append(participants, issue.Poster)
|
||||
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err)
|
||||
}
|
||||
|
||||
tos := make([]string, 0, len(watchers)) // List of email addresses.
|
||||
@@ -60,16 +48,6 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
|
||||
tos = append(tos, to.Email)
|
||||
names = append(names, to.Name)
|
||||
}
|
||||
for i := range participants {
|
||||
if participants[i].ID == doer.ID {
|
||||
continue
|
||||
} else if com.IsSliceContainsStr(names, participants[i].Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
tos = append(tos, participants[i].Email)
|
||||
names = append(names, participants[i].Name)
|
||||
}
|
||||
SendIssueCommentMail(issue, doer, tos)
|
||||
|
||||
// Mail mentioned people and exclude watchers.
|
||||
@@ -91,7 +69,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
|
||||
// and mentioned people.
|
||||
func (issue *Issue) MailParticipants() (err error) {
|
||||
mentions := markdown.FindAllMentions(issue.Content)
|
||||
if err = UpdateIssueMentions(x, issue.ID, mentions); err != nil {
|
||||
if err = UpdateIssueMentions(issue.ID, mentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,86 +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 (
|
||||
"sort"
|
||||
"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())
|
||||
}
|
||||
|
||||
func TestGetIssuesByIDs(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
testSuccess := func(expectedIssueIDs []int64, nonExistentIssueIDs []int64) {
|
||||
issues, err := GetIssuesByIDs(append(expectedIssueIDs, nonExistentIssueIDs...))
|
||||
assert.NoError(t, err)
|
||||
actualIssueIDs := make([]int64, len(issues))
|
||||
for i, issue := range issues {
|
||||
actualIssueIDs[i] = issue.ID
|
||||
}
|
||||
assert.Equal(t, expectedIssueIDs, actualIssueIDs)
|
||||
|
||||
}
|
||||
testSuccess([]int64{1, 2, 3}, []int64{})
|
||||
testSuccess([]int64{1, 2, 3}, []int64{NonexistentID})
|
||||
}
|
||||
|
||||
func TestGetParticipantsByIssueID(t *testing.T) {
|
||||
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
checkPartecipants := func(issueID int64, userIDs []int) {
|
||||
partecipants, err := GetParticipantsByIssueID(issueID)
|
||||
if assert.NoError(t, err) {
|
||||
partecipantsIDs := make([]int, len(partecipants))
|
||||
for i, u := range partecipants {
|
||||
partecipantsIDs[i] = int(u.ID)
|
||||
}
|
||||
sort.Ints(partecipantsIDs)
|
||||
sort.Ints(userIDs)
|
||||
assert.Equal(t, userIDs, partecipantsIDs)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// User 1 is issue1 poster (see fixtures/issue.yml)
|
||||
// User 2 only labeled issue1 (see fixtures/comment.yml)
|
||||
// Users 3 and 5 made actual comments (see fixtures/comment.yml)
|
||||
checkPartecipants(1, []int{3, 5})
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// IssueWatch is connection request for receiving issue notification.
|
||||
type IssueWatch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(watch) NOT NULL"`
|
||||
IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"`
|
||||
IsWatching bool `xorm:"NOT NULL"`
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64 `xorm:"NOT NULL"`
|
||||
}
|
||||
|
||||
// BeforeInsert is invoked from XORM before inserting an object of this type.
|
||||
func (iw *IssueWatch) BeforeInsert() {
|
||||
iw.CreatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
// CreateOrUpdateIssueWatch set watching for a user and issue
|
||||
func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
|
||||
s := x.NewSession()
|
||||
defer s.Close()
|
||||
if err := s.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iw, exists, err := getIssueWatch(s, userID, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
iw = &IssueWatch{
|
||||
UserID: userID,
|
||||
IssueID: issueID,
|
||||
IsWatching: isWatching,
|
||||
}
|
||||
|
||||
if _, err := s.Insert(iw); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
iw.IsWatching = isWatching
|
||||
|
||||
if _, err := s.Id(iw.ID).Update(iw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIssueWatch returns an issue watch by user and issue
|
||||
func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
|
||||
iw, exists, err = getIssueWatch(x, userID, issueID)
|
||||
return
|
||||
}
|
||||
func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
|
||||
iw = new(IssueWatch)
|
||||
exists, err = e.
|
||||
Where("user_id = ?", userID).
|
||||
And("issue_id = ?", issueID).
|
||||
Get(iw)
|
||||
return
|
||||
}
|
||||
122
models/lfs.go
122
models/lfs.go
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -623,19 +548,9 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource,
|
||||
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,
|
||||
}
|
||||
}
|
||||
user = &User{Email: strings.ToLower(username)}
|
||||
} else {
|
||||
user = &User{LowerName: strings.ToLower(strings.TrimSpace(username))}
|
||||
user = &User{LowerName: strings.ToLower(username)}
|
||||
}
|
||||
|
||||
hasUser, err := x.Get(user)
|
||||
@@ -645,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
|
||||
}
|
||||
@@ -665,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
|
||||
@@ -685,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"path"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/mailer"
|
||||
"code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"gopkg.in/gomail.v2"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,16 +31,32 @@ const (
|
||||
mailNotifyCollaborator base.TplName = "notify/collaborator"
|
||||
)
|
||||
|
||||
var templates *template.Template
|
||||
type mailRenderInterface interface {
|
||||
HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error)
|
||||
}
|
||||
|
||||
var mailRender mailRenderInterface
|
||||
|
||||
// InitMailRender initializes the macaron mail renderer
|
||||
func InitMailRender(tmpls *template.Template) {
|
||||
templates = tmpls
|
||||
func InitMailRender(dir, appendDir string, funcMap []template.FuncMap) {
|
||||
opt := &macaron.RenderOptions{
|
||||
Directory: dir,
|
||||
AppendDirectories: []string{appendDir},
|
||||
Funcs: funcMap,
|
||||
Extensions: []string{".tmpl", ".html"},
|
||||
}
|
||||
ts := macaron.NewTemplateSet()
|
||||
ts.Set(macaron.DEFAULT_TPL_SET_NAME, opt)
|
||||
|
||||
mailRender = &macaron.TplRender{
|
||||
TemplateSet: ts,
|
||||
Opt: opt,
|
||||
}
|
||||
}
|
||||
|
||||
// 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}, "Gogs Test Email!", "Gogs Test Email!").Message)
|
||||
}
|
||||
|
||||
// SendUserMail sends a mail to the user
|
||||
@@ -51,15 +67,13 @@ func SendUserMail(c *macaron.Context, u *User, tpl base.TplName, code, subject,
|
||||
"ResetPwdCodeLives": setting.Service.ResetPwdCodeLives / 60,
|
||||
"Code": code,
|
||||
}
|
||||
|
||||
var content bytes.Buffer
|
||||
|
||||
if err := templates.ExecuteTemplate(&content, string(tpl), data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
body, err := mailRender.HTMLString(string(tpl), data)
|
||||
if err != nil {
|
||||
log.Error(3, "HTMLString: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := mailer.NewMessage([]string{u.Email}, subject, content.String())
|
||||
msg := mailer.NewMessage([]string{u.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID, info)
|
||||
|
||||
mailer.SendAsync(msg)
|
||||
@@ -83,15 +97,13 @@ func SendActivateEmailMail(c *macaron.Context, u *User, email *EmailAddress) {
|
||||
"Code": u.GenerateEmailActivateCode(email.Email),
|
||||
"Email": email.Email,
|
||||
}
|
||||
|
||||
var content bytes.Buffer
|
||||
|
||||
if err := templates.ExecuteTemplate(&content, string(mailAuthActivateEmail), data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
body, err := mailRender.HTMLString(string(mailAuthActivateEmail), data)
|
||||
if err != nil {
|
||||
log.Error(3, "HTMLString: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := mailer.NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), content.String())
|
||||
msg := mailer.NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID)
|
||||
|
||||
mailer.SendAsync(msg)
|
||||
@@ -102,15 +114,13 @@ func SendRegisterNotifyMail(c *macaron.Context, u *User) {
|
||||
data := map[string]interface{}{
|
||||
"Username": u.DisplayName(),
|
||||
}
|
||||
|
||||
var content bytes.Buffer
|
||||
|
||||
if err := templates.ExecuteTemplate(&content, string(mailAuthRegisterNotify), data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
body, err := mailRender.HTMLString(string(mailAuthRegisterNotify), data)
|
||||
if err != nil {
|
||||
log.Error(3, "HTMLString: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := mailer.NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), content.String())
|
||||
msg := mailer.NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID)
|
||||
|
||||
mailer.SendAsync(msg)
|
||||
@@ -126,15 +136,13 @@ func SendCollaboratorMail(u, doer *User, repo *Repository) {
|
||||
"RepoName": repoName,
|
||||
"Link": repo.HTMLURL(),
|
||||
}
|
||||
|
||||
var content bytes.Buffer
|
||||
|
||||
if err := templates.ExecuteTemplate(&content, string(mailNotifyCollaborator), data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
body, err := mailRender.HTMLString(string(mailNotifyCollaborator), data)
|
||||
if err != nil {
|
||||
log.Error(3, "HTMLString: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := mailer.NewMessage([]string{u.Email}, subject, content.String())
|
||||
msg := mailer.NewMessage([]string{u.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID)
|
||||
|
||||
mailer.SendAsync(msg)
|
||||
@@ -150,17 +158,14 @@ 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
|
||||
|
||||
var content bytes.Buffer
|
||||
|
||||
if err := templates.ExecuteTemplate(&content, string(tplName), data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
content, err := mailRender.HTMLString(string(tplName), data)
|
||||
if err != nil {
|
||||
log.Error(3, "HTMLString (%s): %v", tplName, err)
|
||||
}
|
||||
|
||||
msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.FromEmail), subject, content.String())
|
||||
msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.FromEmail), subject, content)
|
||||
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -76,32 +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),
|
||||
// v22 -> v23
|
||||
NewMigration("generate and migrate wiki Git hooks", generateAndMigrateWikiGitHooks),
|
||||
// v23 -> v24
|
||||
NewMigration("add user openid table", addUserOpenID),
|
||||
// v24 -> v25
|
||||
NewMigration("change the key_id and primary_key_id type", changeGPGKeysColumns),
|
||||
// v25 -> v26
|
||||
NewMigration("add show field in user openid table", addUserOpenIDShow),
|
||||
// v26 -> v27
|
||||
NewMigration("generate and migrate repo and wiki Git hooks", generateAndMigrateGitHookChains),
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
@@ -117,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 {
|
||||
@@ -127,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
|
||||
@@ -350,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 {
|
||||
@@ -482,12 +457,8 @@ func generateOrgRandsAndSalt(x *xorm.Engine) (err error) {
|
||||
}
|
||||
|
||||
for _, org := range orgs {
|
||||
if org.Rands, err = base.GetRandomString(10); err != nil {
|
||||
return err
|
||||
}
|
||||
if org.Salt, err = base.GetRandomString(10); err != nil {
|
||||
return err
|
||||
}
|
||||
org.Rands = base.GetRandomString(10)
|
||||
org.Salt = base.GetRandomString(10)
|
||||
if _, err = sess.Id(org.ID).Update(org); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type UserV14 struct {
|
||||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
||||
}
|
||||
|
||||
// TableName will be invoked by XORM to customize the table name
|
||||
// TableName will be invoked by XORM to customrize the table name
|
||||
func (*UserV14) TableName() string {
|
||||
return "user"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user