Compare commits
74 Commits
notificati
...
bkcsoft/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0c7a3bb5c | ||
|
|
77ef39112d | ||
|
|
8c708202b6 | ||
|
|
3e61f49da4 | ||
|
|
fca7ddc6ea | ||
|
|
431b26f6d7 | ||
|
|
6853bf323a | ||
|
|
e3c2963222 | ||
|
|
e4a0a04829 | ||
|
|
8371f94d06 | ||
|
|
b7da5a6cb7 | ||
|
|
c58708d3ee | ||
|
|
3012971e92 | ||
|
|
eb1075dd4c | ||
|
|
fcc7cdab11 | ||
|
|
5b8fe1e181 | ||
|
|
4bea219128 | ||
|
|
52627032bc | ||
|
|
f0db3da713 | ||
|
|
42072783c9 | ||
|
|
bb14c97d40 | ||
|
|
fa2a513c62 | ||
|
|
a2d365c81f | ||
|
|
f995bcc87a | ||
|
|
2eeae84cbd | ||
|
|
f42ec6120e | ||
|
|
941281ae12 | ||
|
|
c764a542c0 | ||
|
|
cf91cfb993 | ||
|
|
237270ef50 | ||
|
|
d409d3ab57 | ||
|
|
21290d4e80 | ||
|
|
22295944df | ||
|
|
be6edaddcb | ||
|
|
54f0293f0a | ||
|
|
edbb9eefd6 | ||
|
|
5c0bee9b20 | ||
|
|
d9db188274 | ||
|
|
e0df611cbc | ||
|
|
5acfc7c4bc | ||
|
|
cf6699fb4f | ||
|
|
cbeeaa1b11 | ||
|
|
410af6971b | ||
|
|
a78a0266c4 | ||
|
|
d800305b34 | ||
|
|
0cee52e0d3 | ||
|
|
e7493e953f | ||
|
|
2d1efcc270 | ||
|
|
b746757209 | ||
|
|
93c25c9a35 | ||
|
|
5d6b71fdbb | ||
|
|
21fd3da6f5 | ||
|
|
37a34c1a28 | ||
|
|
f6e5ce65b2 | ||
|
|
e5c56fe30d | ||
|
|
88112a5324 | ||
|
|
095e1f5155 | ||
|
|
6a39250579 | ||
|
|
fac7a6fecf | ||
|
|
e6781d5488 | ||
|
|
a90ffffb1a | ||
|
|
18952c40f8 | ||
|
|
4b284f814c | ||
|
|
4fa691c4dd | ||
|
|
912b340d0d | ||
|
|
caed86fc6e | ||
|
|
e4a33ed4d0 | ||
|
|
cb362513f0 | ||
|
|
aa6e949b3d | ||
|
|
b674460748 | ||
|
|
a0d0de7233 | ||
|
|
129b0d6a4b | ||
|
|
08f7fded3c | ||
|
|
d0298ea2fb |
16
.drone.yml
16
.drone.yml
@@ -19,11 +19,23 @@ pipeline:
|
||||
- make generate
|
||||
- make vet
|
||||
- make lint
|
||||
- make test-vendor
|
||||
- make test
|
||||
- make build
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
test-sqlite:
|
||||
image: webhippie/golang:edge
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make test-sqlite
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
test-mysql:
|
||||
image: webhippie/golang:edge
|
||||
pull: true
|
||||
@@ -31,7 +43,7 @@ pipeline:
|
||||
TAGS: bindata
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make test-mysql
|
||||
- echo make test-mysql # Not ready yet
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -42,7 +54,7 @@ pipeline:
|
||||
TAGS: bindata
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make test-pgsql
|
||||
- echo make test-pqsql # Not ready yet
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QKICAgICAgLSBtYWtlIGJ1aWxkCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtbXlzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHRlc3QtbXlzcWwKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1wZ3NxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1wZ3NxbAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBzdGF0aWM6CiAgICBpbWFnZToga2FyYWxhYmUveGdvLWxhdGVzdDpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhIHNxbGl0ZQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHJlbGVhc2UKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgY292ZXJhZ2U6CiAgICBpbWFnZTogcGx1Z2lucy9jb3ZlcmFnZQogICAgc2VydmVyOiBodHRwczovL2NvdmVyYWdlLmdpdGVhLmlvCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJyR7RFJPTkVfVEFHIyN2fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9CUkFOQ0gjI3JlbGVhc2Uvdn0nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgcmVsZWFzZS8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnbGF0ZXN0JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX1RBRyMjdn0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvbWFzdGVyCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIGdpdGh1YjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdGh1Yi1yZWxlYXNlCiAgICBmaWxlczoKICAgICAgLSBkaXN0L3JlbGVhc2UvKgogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZ2l0dGVyOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0dGVyCgpzZXJ2aWNlczoKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgcGdzcWw6CiAgICBpbWFnZTogcG9zdGdyZXM6OS41CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19EQj10ZXN0CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCg.hp6IsxbFIQOaxJdmGv32Vf34-Nra3KqVIWzH52W687I
|
||||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QtdmVuZG9yCiAgICAgIC0gbWFrZSB0ZXN0CiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LXNxbGl0ZToKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1zcWxpdGUKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1teXNxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIGVjaG8gbWFrZSB0ZXN0LW15c3FsICMgTm90IHJlYWR5IHlldAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LXBnc3FsOgogICAgaW1hZ2U6IHdlYmhpcHBpZS9nb2xhbmc6ZWRnZQogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gZWNobyBtYWtlIHRlc3QtcHFzcWwgIyBOb3QgcmVhZHkgeWV0CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHN0YXRpYzoKICAgIGltYWdlOiBrYXJhbGFiZS94Z28tbGF0ZXN0OmxhdGVzdAogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEgc3FsaXRlCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgcmVsZWFzZQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBjb3ZlcmFnZToKICAgIGltYWdlOiBwbHVnaW5zL2NvdmVyYWdlCiAgICBzZXJ2ZXI6IGh0dHBzOi8vY292ZXJhZ2UuZ2l0ZWEuaW8KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9UQUcjI3Z9JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICcke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICdsYXRlc3QnIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgbWFzdGVyIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhLyR7RFJPTkVfVEFHIyN2fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhLyR7RFJPTkVfQlJBTkNIIyNyZWxlYXNlL3Z9CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIHJlbGVhc2UvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS9tYXN0ZXIKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgbWFzdGVyIF0KCiAgZ2l0aHViOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICAgIGZpbGVzOgogICAgICAtIGRpc3QvcmVsZWFzZS8qCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBnaXR0ZXI6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKCnNlcnZpY2VzOgogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBwZ3NxbDoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0RCPXRlc3QKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0K.dA2VK6LdoPXvBTYAUywWervhOZmgOjU32uiiPrBbVdQ
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -36,6 +36,7 @@ coverage.out
|
||||
*.log
|
||||
|
||||
/gitea
|
||||
/integrations.test
|
||||
|
||||
/bin
|
||||
/dist
|
||||
@@ -44,3 +45,4 @@ coverage.out
|
||||
/indexers
|
||||
/log
|
||||
/public/img/avatar
|
||||
/integrations/gitea-integration
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
* BREAKING
|
||||
* Password reset URL changed from `/user/forget_password` to `/user/forgot_password`
|
||||
* SSH keys management URL changed from `/user/settings/ssh` to `/user/settings/keys`
|
||||
|
||||
|
||||
## [1.1.0](https://github.com/go-gitea/gitea/releases/tag/v1.1.0) - 2017-03-09
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ The current release cycle is aligned to start on December 25 to February 24, nex
|
||||
|
||||
## 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 have [team maintainers](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.
|
||||
|
||||
## Owners
|
||||
|
||||
|
||||
@@ -13,3 +13,4 @@ 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)
|
||||
Antoine Girard <sapk@sapk.fr> (@sapk)
|
||||
|
||||
32
Makefile
32
Makefile
@@ -11,7 +11,12 @@ 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)"
|
||||
GOFLAGS := -i -v
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
VERSION := $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
|
||||
|
||||
LDFLAGS := -X "main.Version=$(VERSION)" -X "main.Tags=$(TAGS)"
|
||||
|
||||
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell go list ./... | grep -v /vendor/))
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
@@ -74,13 +79,28 @@ integrations: build
|
||||
test:
|
||||
for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;
|
||||
|
||||
.PHONY: test-vendor
|
||||
test-vendor:
|
||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/kardianos/govendor; \
|
||||
fi
|
||||
govendor status +outside +unused || exit 1
|
||||
|
||||
.PHONY: test-sqlite
|
||||
test-sqlite: integrations.test
|
||||
GITEA_CONF=integrations/sqlite.ini ./integrations.test
|
||||
|
||||
.PHONY: test-mysql
|
||||
test-mysql:
|
||||
@echo "Not integrated yet!"
|
||||
test-mysql: integrations.test
|
||||
echo "CREATE DATABASE IF NOT EXISTS testgitea" | mysql -u root
|
||||
GITEA_CONF=integrations/mysql.ini ./integrations.test
|
||||
|
||||
.PHONY: test-pgsql
|
||||
test-pgsql:
|
||||
@echo "Not integrated yet!"
|
||||
test-pgsql: integrations.test
|
||||
GITEA_CONF=integrations/pgsql.ini ./integrations.test
|
||||
|
||||
integrations.test: $(SOURCES)
|
||||
go test -c code.gitea.io/gitea/integrations -tags 'sqlite'
|
||||
|
||||
.PHONY: check
|
||||
check: test
|
||||
@@ -93,7 +113,7 @@ install: $(wildcard *.go)
|
||||
build: $(EXECUTABLE)
|
||||
|
||||
$(EXECUTABLE): $(SOURCES)
|
||||
go build -i -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
go build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
|
||||
3
changelog/unreleased/restore-backup.yml
Normal file
3
changelog/unreleased/restore-backup.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
title: Implement proper `gitea restore` & `gitea backup`. Deprecating `gitea dump`
|
||||
kind: feature
|
||||
pull_request: 1637
|
||||
@@ -8,10 +8,10 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
197
cmd/backup.go
Normal file
197
cmd/backup.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright 2017 the Gitea Authors. All rights reserved.
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// Backup files and database
|
||||
var Backup = cli.Command{
|
||||
Name: "backup",
|
||||
Usage: "Backup files and database",
|
||||
Description: `Backup dumps and compresses all related files and database into zip file,
|
||||
which can be used for migrating Gitea to another server. The output format is meant to be
|
||||
portable among all supported database engines.`,
|
||||
Action: runBackup,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Value: "custom/conf/app.ini",
|
||||
Usage: "Custom configuration `FILE` path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tempdir, t",
|
||||
Value: os.TempDir(),
|
||||
Usage: "Temporary directory `PATH`",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "target",
|
||||
Value: "./",
|
||||
Usage: "Target directory `PATH` to save backup archive",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "verbose, v",
|
||||
Usage: "Show process details",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "db",
|
||||
Usage: "Backup the database (default: true)",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "repos",
|
||||
Usage: "Backup repositories (default: true)",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "data",
|
||||
Usage: "Backup attachments and avatars (default: true)",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "custom",
|
||||
Usage: "Backup custom files (default: true)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
archiveRootDir = "gitea-backup"
|
||||
backupVersion = 1
|
||||
)
|
||||
|
||||
func runBackup(c *cli.Context) error {
|
||||
zip.Verbose = c.Bool("verbose")
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
}
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
if err := models.SetEngine(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup temp-dir
|
||||
tmpDir := c.String("tempdir")
|
||||
if !com.IsExist(tmpDir) {
|
||||
log.Fatal(0, "'--tempdir' does not exist: %s", tmpDir)
|
||||
}
|
||||
rootDir, err := ioutil.TempDir(tmpDir, "gitea-backup-")
|
||||
if err != nil {
|
||||
log.Fatal(0, "Fail to create backup root directory '%s': %v", rootDir, err)
|
||||
}
|
||||
defer func(rootDir string) {
|
||||
os.RemoveAll(rootDir)
|
||||
}(rootDir)
|
||||
log.Info("Backup root directory: %s", rootDir)
|
||||
|
||||
// Metadata
|
||||
metaFile := path.Join(rootDir, "metadata.ini")
|
||||
metadata := ini.Empty()
|
||||
metadata.Section("").Key("VERSION").SetValue(fmt.Sprintf("%d", backupVersion))
|
||||
metadata.Section("").Key("DATE_TIME").SetValue(time.Now().String())
|
||||
metadata.Section("").Key("GITEA_VERSION").SetValue(setting.AppVer)
|
||||
if err = metadata.SaveTo(metaFile); err != nil {
|
||||
log.Fatal(0, "Fail to save metadata '%s': %v", metaFile, err)
|
||||
}
|
||||
|
||||
// Create ZIP-file
|
||||
archiveName := path.Join(c.String("target"), fmt.Sprintf("gitea-backup-%d.zip", time.Now().Unix()))
|
||||
log.Info("Packing backup files to: %s", archiveName)
|
||||
|
||||
z, err := zip.Create(archiveName)
|
||||
if err != nil {
|
||||
log.Fatal(0, "Fail to create backup archive '%s': %v", archiveName, err)
|
||||
}
|
||||
defer func(archiveName string) {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
err, ok = r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
debug.PrintStack()
|
||||
log.Info("Removing partial backup-file %s\n", archiveName)
|
||||
os.Remove(archiveName)
|
||||
log.Fatal(9, "%v\n", err)
|
||||
}
|
||||
}(archiveName)
|
||||
|
||||
// Add metadata-file
|
||||
if err = z.AddFile(archiveRootDir+"/metadata.ini", metaFile); err != nil {
|
||||
log.Fatal(0, "Fail to include 'metadata.ini': %v", err)
|
||||
}
|
||||
|
||||
// Database
|
||||
if c.Bool("db") {
|
||||
log.Info("Backing up database")
|
||||
dbDir := path.Join(rootDir, "db")
|
||||
if err = models.DumpDatabase(dbDir); err != nil {
|
||||
log.Fatal(0, "Fail to dump database: %v", err)
|
||||
}
|
||||
if err = z.AddDir(archiveRootDir+"/db", dbDir); err != nil {
|
||||
log.Fatal(0, "Fail to include 'db': %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Custom files
|
||||
if c.Bool("custom") {
|
||||
log.Info("Backing up custom files")
|
||||
if err = z.AddDir(archiveRootDir+"/custom", setting.CustomPath); err != nil {
|
||||
log.Fatal(0, "Fail to include 'custom': %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Data files
|
||||
if c.Bool("data") {
|
||||
log.Info("Backing up attachments and avatars")
|
||||
for _, dir := range []string{"attachments", "avatars"} {
|
||||
dirPath := path.Join(setting.AppDataPath, dir)
|
||||
if !com.IsDir(dirPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = z.AddDir(path.Join(archiveRootDir+"/data", dir), dirPath); err != nil {
|
||||
log.Fatal(0, "Fail to include 'data': %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Repositories
|
||||
if c.Bool("repos") {
|
||||
log.Info("Backing up repositories")
|
||||
reposDump := path.Join(rootDir, "repositories.zip")
|
||||
log.Info("Dumping repositories in '%s'", setting.RepoRootPath)
|
||||
if err = zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
|
||||
log.Fatal(0, "Fail to dump repositories: %v", err)
|
||||
}
|
||||
log.Info("Repositories dumped to: %s", reposDump)
|
||||
|
||||
if err = z.AddFile(archiveRootDir+"/repositories.zip", reposDump); err != nil {
|
||||
log.Fatal(0, "Fail to include 'repositories.zip': %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = z.Close(); err != nil {
|
||||
log.Fatal(0, "Fail to save backup archive '%s': %v", archiveName, err)
|
||||
}
|
||||
|
||||
os.RemoveAll(rootDir)
|
||||
log.Info("Backup succeed! Archive is located at: %s", archiveName)
|
||||
return nil
|
||||
}
|
||||
72
cmd/dump.go
72
cmd/dump.go
@@ -8,23 +8,24 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"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{
|
||||
// Dump represents the available dump sub-command.
|
||||
var Dump = cli.Command{
|
||||
Name: "dump",
|
||||
Usage: "Dump Gitea files and database",
|
||||
Usage: "DEPRICATED! Dump Gitea 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`,
|
||||
Action: runDump,
|
||||
@@ -58,65 +59,66 @@ func runDump(ctx *cli.Context) error {
|
||||
setting.NewServices() // cannot access session settings otherwise
|
||||
models.LoadConfigs()
|
||||
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
log.Info("")
|
||||
|
||||
if err := models.SetEngine(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpDir := ctx.String("tempdir")
|
||||
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
|
||||
log.Fatalf("Path does not exist: %s", tmpDir)
|
||||
log.Fatal(4, "Path does not exist: %s", tmpDir)
|
||||
}
|
||||
TmpWorkDir, err := ioutil.TempDir(tmpDir, "gitea-dump-")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create tmp work directory: %v", err)
|
||||
log.Fatal(4, "Failed to create tmp work directory: %v", err)
|
||||
}
|
||||
log.Printf("Creating tmp work dir: %s", TmpWorkDir)
|
||||
log.Info("Creating tmp work dir: %s", TmpWorkDir)
|
||||
|
||||
reposDump := path.Join(TmpWorkDir, "gitea-repo.zip")
|
||||
dbDump := path.Join(TmpWorkDir, "gitea-db.sql")
|
||||
|
||||
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
|
||||
log.Info("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)
|
||||
if err = zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
|
||||
log.Fatal(4, "Failed 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)
|
||||
log.Info("Dumping database %s => %s...", models.DbCfg.Type, targetDBType)
|
||||
} else {
|
||||
log.Printf("Dumping database...")
|
||||
log.Info("Dumping database...")
|
||||
}
|
||||
|
||||
if err := models.DumpDatabase(dbDump, targetDBType); err != nil {
|
||||
log.Fatalf("Failed to dump database: %v", err)
|
||||
if err = models.DumpDatabaseOld(dbDump, targetDBType); err != nil {
|
||||
log.Fatal(4, "Failed to dump database: %v", err)
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix())
|
||||
log.Printf("Packing dump files...")
|
||||
log.Info("Packing dump files...")
|
||||
z, err := zip.Create(fileName)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create %s: %v", fileName, err)
|
||||
log.Fatal(4, "Failed 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("gitea-repo.zip", reposDump); err != nil {
|
||||
log.Fatal(4, "Failed to include gitea-repo.zip: %v", err)
|
||||
}
|
||||
if err := z.AddFile("gitea-db.sql", dbDump); err != nil {
|
||||
log.Fatalf("Failed to include gitea-db.sql: %v", err)
|
||||
if err = z.AddFile("gitea-db.sql", dbDump); err != nil {
|
||||
log.Fatal(4, "Failed to include gitea-db.sql: %v", err)
|
||||
}
|
||||
customDir, err := os.Stat(setting.CustomPath)
|
||||
if err == nil && customDir.IsDir() {
|
||||
if err := z.AddDir("custom", setting.CustomPath); err != nil {
|
||||
log.Fatalf("Failed to include custom: %v", err)
|
||||
if err = z.AddDir("custom", setting.CustomPath); err != nil {
|
||||
log.Fatal(4, "Failed to include custom: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath)
|
||||
log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
|
||||
}
|
||||
|
||||
if com.IsExist(setting.AppDataPath) {
|
||||
log.Printf("Packing data directory...%s", setting.AppDataPath)
|
||||
log.Info("Packing data directory...%s", setting.AppDataPath)
|
||||
|
||||
var sessionAbsPath string
|
||||
if setting.SessionConfig.Provider == "file" {
|
||||
@@ -125,30 +127,30 @@ func runDump(ctx *cli.Context) error {
|
||||
}
|
||||
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 = zipAddDirectoryExclude(z, "data", setting.AppDataPath, sessionAbsPath); err != nil {
|
||||
log.Fatal(4, "Failed to include data directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := z.AddDir("log", setting.LogRootPath); err != nil {
|
||||
log.Fatalf("Failed to include log: %v", err)
|
||||
if err = z.AddDir("log", setting.LogRootPath); err != nil {
|
||||
log.Fatal(4, "Failed to include log: %v", err)
|
||||
}
|
||||
|
||||
if err = z.Close(); err != nil {
|
||||
_ = os.Remove(fileName)
|
||||
log.Fatalf("Failed to save %s: %v", fileName, err)
|
||||
log.Fatal(4, "Failed to save %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
if err := os.Chmod(fileName, 0600); err != nil {
|
||||
log.Printf("Can't change file access permissions mask to 0600: %v", err)
|
||||
log.Info("Can't change file access permissions mask to 0600: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Removing tmp work dir: %s", TmpWorkDir)
|
||||
log.Info("Removing tmp work dir: %s", TmpWorkDir)
|
||||
|
||||
if err := os.RemoveAll(TmpWorkDir); err != nil {
|
||||
log.Fatalf("Failed to remove %s: %v", TmpWorkDir, err)
|
||||
log.Fatal(4, "Failed to remove %s: %v", TmpWorkDir, err)
|
||||
}
|
||||
log.Printf("Finish dumping in file %s", fileName)
|
||||
log.Info("Finish dumping in file %s", fileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
166
cmd/restore.go
Normal file
166
cmd/restore.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/mcuadros/go-version"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// Restore a backup
|
||||
var Restore = cli.Command{
|
||||
Name: "restore",
|
||||
Usage: "Restore files and database from backup",
|
||||
Description: `Restore imports all related files and database from a backup archive.
|
||||
The backup version must lower or equal to current Gitea version. You can also import
|
||||
backup from other database engines, which is useful for database migrating.
|
||||
|
||||
If corresponding files or database tables are not presented in the archive, they will
|
||||
be skipped and remian unchanged.`,
|
||||
Action: runRestore,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Value: "custom/conf/app.ini",
|
||||
Usage: "Custom configuration file path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tempdir, t",
|
||||
Value: os.TempDir(),
|
||||
Usage: "Temporary directory path",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "from",
|
||||
Value: "",
|
||||
Usage: "Path to backup archive",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "verbose, v",
|
||||
Usage: "Show process details",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "repos",
|
||||
Usage: "Restore repositories (default: true)",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "data",
|
||||
Usage: "Restore attachments and avatars (default: true)",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "custom",
|
||||
Usage: "Restore custom files (default: true)",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "db",
|
||||
Usage: "Restore database (default: true)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runRestore(c *cli.Context) error {
|
||||
zip.Verbose = c.Bool("verbose")
|
||||
|
||||
tmpDir := c.String("tempdir")
|
||||
if !com.IsExist(tmpDir) {
|
||||
log.Fatal(0, "'--tempdir' does not exist: %s", tmpDir)
|
||||
}
|
||||
|
||||
log.Info("Restore backup from: %s", c.String("from"))
|
||||
if err := zip.ExtractTo(c.String("from"), tmpDir); err != nil {
|
||||
log.Fatal(0, "Fail to extract backup archive: %v", err)
|
||||
}
|
||||
archivePath := path.Join(tmpDir, archiveRootDir)
|
||||
|
||||
// Check backup version
|
||||
metaFile := path.Join(archivePath, "metadata.ini")
|
||||
if !com.IsExist(metaFile) {
|
||||
log.Fatal(0, "File 'metadata.ini' is missing")
|
||||
}
|
||||
metadata, err := ini.Load(metaFile)
|
||||
if err != nil {
|
||||
log.Fatal(0, "Fail to load metadata '%s': %v", metaFile, err)
|
||||
}
|
||||
ver := metadata.Section("").Key("VERSION").MustInt(10000000)
|
||||
if ver != backupVersion {
|
||||
log.Fatal(0, "Current Backup version does not match the version in the backup: %d != %d", ver, backupVersion)
|
||||
}
|
||||
backupVersion := metadata.Section("").Key("GITEA_VERSION").MustString("999.0")
|
||||
if version.Compare(setting.AppVer, backupVersion, "<") {
|
||||
log.Fatal(0, "Current Gitea version is lower than backup version: %s < %s", setting.AppVer, backupVersion)
|
||||
}
|
||||
|
||||
// If config file is not present in backup, user must set this file via flag.
|
||||
// Otherwise, it's optional to set config file flag.
|
||||
configFile := path.Join(archivePath, "custom/conf/app.ini")
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
} else if !com.IsExist(configFile) {
|
||||
log.Fatal(0, "'--config' is not specified and custom config file is not found in backup")
|
||||
} else {
|
||||
setting.CustomConf = configFile
|
||||
}
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
models.SetEngine()
|
||||
|
||||
// Database
|
||||
if c.Bool("db") {
|
||||
dbDir := path.Join(archivePath, "db")
|
||||
if err = models.ImportDatabase(dbDir); err != nil {
|
||||
log.Fatal(0, "Fail to import database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Custom files
|
||||
if c.Bool("custom") {
|
||||
if com.IsExist(setting.CustomPath) {
|
||||
if err = os.Rename(setting.CustomPath, setting.CustomPath+".bak"); err != nil {
|
||||
log.Fatal(0, "Fail to backup current 'custom': %v", err)
|
||||
}
|
||||
}
|
||||
if err = os.Rename(path.Join(archivePath, "custom"), setting.CustomPath); err != nil {
|
||||
log.Fatal(0, "Fail to import 'custom': %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Data files
|
||||
if c.Bool("data") {
|
||||
for _, dir := range []string{"attachments", "avatars"} {
|
||||
dirPath := path.Join(setting.AppDataPath, dir)
|
||||
if com.IsExist(dirPath) {
|
||||
if err = os.Rename(dirPath, dirPath+".bak"); err != nil {
|
||||
log.Fatal(0, "Fail to backup current 'data': %v", err)
|
||||
}
|
||||
}
|
||||
if err = os.Rename(path.Join(archivePath, "data", dir), dirPath); err != nil {
|
||||
log.Fatal(0, "Fail to import 'data': %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Repositories
|
||||
if c.Bool("repos") {
|
||||
reposPath := path.Join(archivePath, "repositories.zip")
|
||||
if !c.Bool("exclude-repos") && !c.Bool("database-only") && com.IsExist(reposPath) {
|
||||
if err := zip.ExtractTo(reposPath, path.Dir(setting.RepoRootPath)); err != nil {
|
||||
log.Fatal(0, "Fail to extract 'repositories.zip': %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.RemoveAll(path.Join(tmpDir, archiveRootDir))
|
||||
log.Info("Restore succeed!")
|
||||
return nil
|
||||
}
|
||||
13
cmd/serv.go
13
cmd/serv.go
@@ -16,7 +16,9 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/urfave/cli"
|
||||
@@ -105,7 +107,8 @@ func runServ(c *cli.Context) error {
|
||||
}
|
||||
|
||||
if len(c.Args()) < 1 {
|
||||
fail("Not enough arguments", "Not enough arguments")
|
||||
cli.ShowSubcommandHelp(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
@@ -316,13 +319,7 @@ func runServ(c *cli.Context) error {
|
||||
|
||||
// Update user key activity.
|
||||
if keyID > 0 {
|
||||
key, err := models.GetPublicKeyByID(keyID)
|
||||
if err != nil {
|
||||
fail("Internal error", "GetPublicKeyById: %v", err)
|
||||
}
|
||||
|
||||
key.Updated = time.Now()
|
||||
if err = models.UpdatePublicKey(key); err != nil {
|
||||
if err = private.UpdatePublicKeyUpdated(keyID); err != nil {
|
||||
fail("Internal error", "UpdatePublicKey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
614
cmd/web.go
614
cmd/web.go
@@ -11,37 +11,15 @@ import (
|
||||
"net/http/fcgi"
|
||||
_ "net/http/pprof" // Used for debugging if enabled and a web server is running
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"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/routers"
|
||||
"code.gitea.io/gitea/routers/admin"
|
||||
apiv1 "code.gitea.io/gitea/routers/api/v1"
|
||||
"code.gitea.io/gitea/routers/dev"
|
||||
"code.gitea.io/gitea/routers/org"
|
||||
"code.gitea.io/gitea/routers/repo"
|
||||
"code.gitea.io/gitea/routers/user"
|
||||
"code.gitea.io/gitea/routers/routes"
|
||||
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/cache"
|
||||
"github.com/go-macaron/captcha"
|
||||
"github.com/go-macaron/csrf"
|
||||
"github.com/go-macaron/gzip"
|
||||
"github.com/go-macaron/i18n"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/go-macaron/toolbox"
|
||||
context2 "github.com/gorilla/context"
|
||||
"github.com/urfave/cli"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// CmdWeb represents the available web sub-command.
|
||||
@@ -70,94 +48,6 @@ and it takes care of all the other things for you`,
|
||||
},
|
||||
}
|
||||
|
||||
// newMacaron initializes Macaron instance.
|
||||
func newMacaron() *macaron.Macaron {
|
||||
m := macaron.New()
|
||||
if !setting.DisableRouterLog {
|
||||
m.Use(macaron.Logger())
|
||||
}
|
||||
m.Use(macaron.Recovery())
|
||||
if setting.EnableGzip {
|
||||
m.Use(gzip.Gziper())
|
||||
}
|
||||
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"),
|
||||
SkipLogging: setting.DisableRouterLog,
|
||||
},
|
||||
))
|
||||
m.Use(macaron.Static(
|
||||
setting.AvatarUploadPath,
|
||||
macaron.StaticOptions{
|
||||
Prefix: "avatars",
|
||||
SkipLogging: setting.DisableRouterLog,
|
||||
ETag: true,
|
||||
},
|
||||
))
|
||||
|
||||
m.Use(templates.Renderer())
|
||||
models.InitMailRender(templates.Mailer())
|
||||
|
||||
localeNames, err := options.Dir("locale")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(4, "Failed to list locale files: %v", err)
|
||||
}
|
||||
|
||||
localFiles := make(map[string][]byte)
|
||||
|
||||
for _, name := range localeNames {
|
||||
localFiles[name], err = options.Locale(name)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(4, "Failed to load %s locale file. %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
m.Use(i18n.I18n(i18n.Options{
|
||||
SubURL: setting.AppSubURL,
|
||||
Files: localFiles,
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: true,
|
||||
}))
|
||||
m.Use(cache.Cacher(cache.Options{
|
||||
Adapter: setting.CacheAdapter,
|
||||
AdapterConfig: setting.CacheConn,
|
||||
Interval: setting.CacheInterval,
|
||||
}))
|
||||
m.Use(captcha.Captchaer(captcha.Options{
|
||||
SubURL: setting.AppSubURL,
|
||||
}))
|
||||
m.Use(session.Sessioner(setting.SessionConfig))
|
||||
m.Use(csrf.Csrfer(csrf.Options{
|
||||
Secret: setting.SecretKey,
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Header: "X-Csrf-Token",
|
||||
CookiePath: setting.AppSubURL,
|
||||
}))
|
||||
m.Use(toolbox.Toolboxer(m, toolbox.Options{
|
||||
HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
|
||||
{
|
||||
Desc: "Database connection",
|
||||
Func: models.Ping,
|
||||
},
|
||||
},
|
||||
}))
|
||||
m.Use(context.Contexter())
|
||||
return m
|
||||
}
|
||||
|
||||
func runWeb(ctx *cli.Context) error {
|
||||
if ctx.IsSet("config") {
|
||||
setting.CustomConf = ctx.String("config")
|
||||
@@ -169,506 +59,8 @@ func runWeb(ctx *cli.Context) error {
|
||||
|
||||
routers.GlobalInit()
|
||||
|
||||
m := newMacaron()
|
||||
|
||||
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
|
||||
ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
|
||||
ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
|
||||
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
|
||||
|
||||
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.
|
||||
m.Get("/", ignSignIn, routers.Home)
|
||||
m.Group("/explore", func() {
|
||||
m.Get("", func(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubURL + "/explore/repos")
|
||||
})
|
||||
m.Get("/repos", routers.ExploreRepos)
|
||||
m.Get("/users", routers.ExploreUsers)
|
||||
m.Get("/organizations", routers.ExploreOrganizations)
|
||||
}, ignSignIn)
|
||||
m.Combo("/install", routers.InstallInit).Get(routers.Install).
|
||||
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
|
||||
|
||||
// ***** START: User *****
|
||||
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() {
|
||||
m.Get("", user.Settings)
|
||||
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
|
||||
m.Combo("/avatar").Get(user.SettingsAvatar).
|
||||
Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost)
|
||||
m.Post("/avatar/delete", user.SettingsDeleteAvatar)
|
||||
m.Combo("/email").Get(user.SettingsEmails).
|
||||
Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||
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)
|
||||
m.Combo("/applications").Get(user.SettingsApplications).
|
||||
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
|
||||
})
|
||||
|
||||
m.Group("/user", func() {
|
||||
// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
|
||||
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("/logout", user.SignOut)
|
||||
})
|
||||
// ***** END: User *****
|
||||
|
||||
adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
|
||||
|
||||
// ***** START: Admin *****
|
||||
m.Group("/admin", func() {
|
||||
m.Get("", adminReq, admin.Dashboard)
|
||||
m.Get("/config", admin.Config)
|
||||
m.Post("/config/test_mail", admin.SendTestMail)
|
||||
m.Get("/monitor", admin.Monitor)
|
||||
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.Users)
|
||||
m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost)
|
||||
m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Post("/:userid/delete", admin.DeleteUser)
|
||||
})
|
||||
|
||||
m.Group("/orgs", func() {
|
||||
m.Get("", admin.Organizations)
|
||||
})
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", admin.Repos)
|
||||
m.Post("/delete", admin.DeleteRepo)
|
||||
})
|
||||
|
||||
m.Group("/auths", func() {
|
||||
m.Get("", admin.Authentications)
|
||||
m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
m.Combo("/:authid").Get(admin.EditAuthSource).
|
||||
Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Post("/:authid/delete", admin.DeleteAuthSource)
|
||||
})
|
||||
|
||||
m.Group("/notices", func() {
|
||||
m.Get("", admin.Notices)
|
||||
m.Post("/delete", admin.DeleteNotices)
|
||||
m.Get("/empty", admin.EmptyNotices)
|
||||
})
|
||||
}, adminReq)
|
||||
// ***** END: Admin *****
|
||||
|
||||
m.Group("", func() {
|
||||
m.Group("/:username", func() {
|
||||
m.Get("", user.Profile)
|
||||
m.Get("/followers", user.Followers)
|
||||
m.Get("/following", user.Following)
|
||||
})
|
||||
|
||||
m.Get("/attachments/:uuid", func(ctx *context.Context) {
|
||||
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
|
||||
if err != nil {
|
||||
if models.IsErrAttachmentNotExist(err) {
|
||||
ctx.Error(404)
|
||||
} else {
|
||||
ctx.Handle(500, "GetAttachmentByUUID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fr, err := os.Open(attach.LocalPath())
|
||||
if err != nil {
|
||||
ctx.Handle(500, "Open", err)
|
||||
return
|
||||
}
|
||||
defer fr.Close()
|
||||
|
||||
if err = repo.ServeData(ctx, attach.Name, fr); err != nil {
|
||||
ctx.Handle(500, "ServeData", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
m.Post("/attachments", repo.UploadAttachment)
|
||||
}, ignSignIn)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Get("/action/:action", user.Action)
|
||||
}, reqSignIn)
|
||||
|
||||
if macaron.Env == macaron.DEV {
|
||||
m.Get("/template/*", dev.TemplatePreview)
|
||||
}
|
||||
|
||||
reqRepoAdmin := context.RequireRepoAdmin()
|
||||
reqRepoWriter := context.RequireRepoWriter()
|
||||
|
||||
// ***** 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.Group("/:org", func() {
|
||||
m.Get("/dashboard", user.Dashboard)
|
||||
m.Get("/^:type(issues|pulls)$", user.Issues)
|
||||
m.Get("/members", org.Members)
|
||||
m.Get("/members/action/:action", org.MembersAction)
|
||||
|
||||
m.Get("/teams", org.Teams)
|
||||
}, context.OrgAssignment(true))
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/teams/:team", org.TeamMembers)
|
||||
m.Get("/teams/:team/repositories", org.TeamRepositories)
|
||||
m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
|
||||
m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
|
||||
}, context.OrgAssignment(true, false, true))
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/teams/new", org.NewTeam)
|
||||
m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
|
||||
m.Get("/teams/:team/edit", org.EditTeam)
|
||||
m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
|
||||
m.Post("/teams/:team/delete", org.DeleteTeam)
|
||||
|
||||
m.Group("/settings", func() {
|
||||
m.Combo("").Get(org.Settings).
|
||||
Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
|
||||
m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar)
|
||||
m.Post("/avatar/delete", org.SettingsDeleteAvatar)
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", org.Webhooks)
|
||||
m.Post("/delete", org.DeleteWebhook)
|
||||
m.Get("/:type/new", repo.WebhooksNew)
|
||||
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
m.Get("/:id", repo.WebHooksEdit)
|
||||
m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
})
|
||||
|
||||
m.Route("/delete", "GET,POST", org.SettingsDelete)
|
||||
})
|
||||
|
||||
m.Route("/invitations/new", "GET,POST", org.Invitation)
|
||||
}, context.OrgAssignment(true, true))
|
||||
}, reqSignIn)
|
||||
// ***** END: Organization *****
|
||||
|
||||
// ***** START: Repository *****
|
||||
m.Group("/repo", func() {
|
||||
m.Get("/create", repo.Create)
|
||||
m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
|
||||
m.Get("/migrate", repo.Migrate)
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
|
||||
m.Combo("/fork/:repoid").Get(repo.Fork).
|
||||
Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
|
||||
}, reqSignIn)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("/settings", func() {
|
||||
m.Combo("").Get(repo.Settings).
|
||||
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
||||
m.Group("/collaboration", func() {
|
||||
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
|
||||
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)
|
||||
m.Post("/delete", repo.DeleteWebhook)
|
||||
m.Get("/:type/new", repo.WebhooksNew)
|
||||
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
m.Get("/:id", repo.WebHooksEdit)
|
||||
m.Post("/:id/test", repo.TestWebhook)
|
||||
m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
|
||||
m.Group("/git", func() {
|
||||
m.Get("", repo.GitHooks)
|
||||
m.Combo("/:name").Get(repo.GitHooksEdit).
|
||||
Post(repo.GitHooksEditPost)
|
||||
}, context.GitHookService())
|
||||
})
|
||||
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").Get(repo.DeployKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
|
||||
m.Post("/delete", repo.DeleteDeployKey)
|
||||
})
|
||||
|
||||
}, 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)
|
||||
m.Group("/:username/:reponame", func() {
|
||||
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
|
||||
// So they can apply their own enable/disable logic on routers.
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
|
||||
|
||||
m.Group("/:index", func() {
|
||||
m.Post("/title", repo.UpdateIssueTitle)
|
||||
m.Post("/content", repo.UpdateIssueContent)
|
||||
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)
|
||||
m.Post("/delete", repo.DeleteComment)
|
||||
})
|
||||
m.Group("/labels", func() {
|
||||
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||
m.Post("/delete", repo.DeleteLabel)
|
||||
m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
|
||||
}, reqRepoWriter, context.RepoRef())
|
||||
m.Group("/milestones", func() {
|
||||
m.Combo("/new").Get(repo.NewMilestone).
|
||||
Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
|
||||
m.Get("/:id/edit", repo.EditMilestone)
|
||||
m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
|
||||
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())
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/edit/*", repo.EditRelease)
|
||||
m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
}, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) {
|
||||
var err error
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
})
|
||||
|
||||
m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists).
|
||||
Get(repo.CompareAndPullRequest).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Combo("/_edit/*").Get(repo.EditFile).
|
||||
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
|
||||
m.Combo("/_new/*").Get(repo.NewFile).
|
||||
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
|
||||
m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
|
||||
m.Combo("/_delete/*").Get(repo.DeleteFile).
|
||||
Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Combo("/_upload/*").Get(repo.UploadFile).
|
||||
Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
|
||||
m.Post("/upload-file", repo.UploadFileToServer)
|
||||
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
||||
}, func(ctx *context.Context) {
|
||||
if !setting.Repository.Upload.Enabled {
|
||||
ctx.Handle(404, "", nil)
|
||||
return
|
||||
}
|
||||
})
|
||||
}, repo.MustBeNotBare, 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())
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("", func() {
|
||||
m.Get("/releases", repo.MustBeNotBare, 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)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
}, 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)
|
||||
m.Get("/_pages", repo.WikiPages)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Combo("/_new").Get(repo.NewWiki).
|
||||
Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
|
||||
m.Combo("/:page/_edit").Get(repo.EditWiki).
|
||||
Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
|
||||
m.Post("/:page/delete", repo.DeleteWikiPagePost)
|
||||
}, 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.Group("/pulls/:index", func() {
|
||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||
m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
|
||||
m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
|
||||
}, repo.MustAllowPulls)
|
||||
|
||||
m.Group("", func() {
|
||||
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("/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.Group("/:username/:reponame", func() {
|
||||
m.Get("/stars", repo.Stars)
|
||||
m.Get("/watchers", repo.Watchers)
|
||||
}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
|
||||
|
||||
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())
|
||||
|
||||
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)
|
||||
|
||||
// robots.txt
|
||||
m.Get("/robots.txt", func(ctx *context.Context) {
|
||||
if setting.HasRobotsTxt {
|
||||
ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt"))
|
||||
} else {
|
||||
ctx.Error(404)
|
||||
}
|
||||
})
|
||||
|
||||
// Not found handler.
|
||||
m.NotFound(routers.NotFound)
|
||||
m := routes.NewMacaron()
|
||||
routes.RegisterRoutes(m)
|
||||
|
||||
// Flag for port number in case first time run conflict.
|
||||
if ctx.IsSet("port") {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/facebookgo/grace/gracehttp"
|
||||
)
|
||||
|
||||
|
||||
14
conf/app.ini
vendored
14
conf/app.ini
vendored
@@ -74,6 +74,11 @@ ORG_PAGING_NUM = 50
|
||||
; Number of repos that are showed in one page
|
||||
REPO_PAGING_NUM = 15
|
||||
|
||||
[ui.meta]
|
||||
AUTHOR = Gitea - Git with a cup of tea
|
||||
DESCRIPTION = Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go
|
||||
KEYWORDS = go,git,self-hosted,gitea
|
||||
|
||||
[markdown]
|
||||
; Enable hard line break extension
|
||||
ENABLE_HARD_LINE_BREAK = false
|
||||
@@ -203,7 +208,8 @@ IMPORT_LOCAL_PATHS = false
|
||||
; Whether to allow signin in via OpenID
|
||||
ENABLE_OPENID_SIGNIN = true
|
||||
; Whether to allow registering via OpenID
|
||||
ENABLE_OPENID_SIGNUP = true
|
||||
; Do not include to rely on DISABLE_REGISTRATION setting
|
||||
;ENABLE_OPENID_SIGNUP = true
|
||||
; Allowed URI patterns (POSIX regexp).
|
||||
; Space separated.
|
||||
; Only these would be allowed if non-blank.
|
||||
@@ -455,8 +461,10 @@ PULL = 300
|
||||
GC = 60
|
||||
|
||||
[mirror]
|
||||
; Default interval in hours between each check
|
||||
DEFAULT_INTERVAL = 8
|
||||
; Default interval as a duration between each check
|
||||
DEFAULT_INTERVAL = 8h
|
||||
; Min interval as a duration must be > 1m
|
||||
MIN_INTERVAL = 10m
|
||||
|
||||
[api]
|
||||
; Max number of items will response in a page
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Go Git Service"
|
||||
DESC="Git with a cup of tea"
|
||||
NAME=gitea
|
||||
SERVICEVERBOSE=yes
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
|
||||
21
integrations/gitea-integration-meta/LICENSE
Normal file
21
integrations/gitea-integration-meta/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Ethan Koenig
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2
integrations/gitea-integration-meta/README.md
Normal file
2
integrations/gitea-integration-meta/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# gitea-integration
|
||||
Gitea integration test environment
|
||||
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
@@ -0,0 +1,4 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||
:
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
ORI_DIR=`pwd`
|
||||
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
|
||||
cd "$ORI_DIR"
|
||||
for i in `ls "$SHELL_FOLDER/post-receive.d"`; do
|
||||
sh "$SHELL_FOLDER/post-receive.d/$i"
|
||||
done
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' post-receive
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed
|
||||
# by applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||
:
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
||||
fi
|
||||
|
||||
# If you want to allow non-ASCII filenames set this variable to true.
|
||||
allownonascii=$(git config --bool hooks.allownonascii)
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
then
|
||||
cat <<\EOF
|
||||
Error: Attempt to add a non-ASCII file name.
|
||||
|
||||
This can cause problems if you want to work with people on other platforms.
|
||||
|
||||
To be portable it is advisable to rename the file.
|
||||
|
||||
If you know what you are doing you can disable this check using:
|
||||
|
||||
git config hooks.allownonascii true
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If there are whitespace errors, print the offending file names and fail.
|
||||
exec git diff-index --check --cached $against --
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
|
||||
# An example hook script to verify what is about to be pushed. Called by "git
|
||||
# push" after it has checked the remote status, but before anything has been
|
||||
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||
#
|
||||
# This hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- Name of the remote to which the push is being done
|
||||
# $2 -- URL to which the push is being done
|
||||
#
|
||||
# If pushing without using a named remote those arguments will be equal.
|
||||
#
|
||||
# Information about the commits which are being pushed is supplied as lines to
|
||||
# the standard input in the form:
|
||||
#
|
||||
# <local ref> <local sha1> <remote ref> <remote sha1>
|
||||
#
|
||||
# This sample shows how to prevent push of commits where the log message starts
|
||||
# with "WIP" (work in progress).
|
||||
|
||||
remote="$1"
|
||||
url="$2"
|
||||
|
||||
z40=0000000000000000000000000000000000000000
|
||||
|
||||
while read local_ref local_sha remote_ref remote_sha
|
||||
do
|
||||
if [ "$local_sha" = $z40 ]
|
||||
then
|
||||
# Handle delete
|
||||
:
|
||||
else
|
||||
if [ "$remote_sha" = $z40 ]
|
||||
then
|
||||
# New branch, examine all commits
|
||||
range="$local_sha"
|
||||
else
|
||||
# Update to existing branch, examine new commits
|
||||
range="$remote_sha..$local_sha"
|
||||
fi
|
||||
|
||||
# Check for WIP commit
|
||||
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
|
||||
if [ -n "$commit" ]
|
||||
then
|
||||
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||
#
|
||||
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||
# its job, and can prevent the command from running by exiting with
|
||||
# non-zero status.
|
||||
#
|
||||
# The hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- the upstream the series was forked from.
|
||||
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||
#
|
||||
# This sample shows how to prevent topic branches that are already
|
||||
# merged to 'next' branch from getting rebased, because allowing it
|
||||
# would result in rebasing already published history.
|
||||
|
||||
publish=next
|
||||
basebranch="$1"
|
||||
if test "$#" = 2
|
||||
then
|
||||
topic="refs/heads/$2"
|
||||
else
|
||||
topic=`git symbolic-ref HEAD` ||
|
||||
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||
fi
|
||||
|
||||
case "$topic" in
|
||||
refs/heads/??/*)
|
||||
;;
|
||||
*)
|
||||
exit 0 ;# we do not interrupt others.
|
||||
;;
|
||||
esac
|
||||
|
||||
# Now we are dealing with a topic branch being rebased
|
||||
# on top of master. Is it OK to rebase it?
|
||||
|
||||
# Does the topic really exist?
|
||||
git show-ref -q "$topic" || {
|
||||
echo >&2 "No such branch $topic"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Is topic fully merged to master?
|
||||
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||
if test -z "$not_in_master"
|
||||
then
|
||||
echo >&2 "$topic is fully merged to master; better remove it."
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
fi
|
||||
|
||||
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||
if test "$only_next_1" = "$only_next_2"
|
||||
then
|
||||
not_in_topic=`git rev-list "^$topic" master`
|
||||
if test -z "$not_in_topic"
|
||||
then
|
||||
echo >&2 "$topic is already up-to-date with master"
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||
/usr/bin/perl -e '
|
||||
my $topic = $ARGV[0];
|
||||
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||
my (%not_in_next) = map {
|
||||
/^([0-9a-f]+) /;
|
||||
($1 => 1);
|
||||
} split(/\n/, $ARGV[1]);
|
||||
for my $elem (map {
|
||||
/^([0-9a-f]+) (.*)$/;
|
||||
[$1 => $2];
|
||||
} split(/\n/, $ARGV[2])) {
|
||||
if (!exists $not_in_next{$elem->[0]}) {
|
||||
if ($msg) {
|
||||
print STDERR $msg;
|
||||
undef $msg;
|
||||
}
|
||||
print STDERR " $elem->[1]\n";
|
||||
}
|
||||
}
|
||||
' "$topic" "$not_in_next" "$not_in_master"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
<<\DOC_END
|
||||
|
||||
This sample hook safeguards topic branches that have been
|
||||
published from being rewound.
|
||||
|
||||
The workflow assumed here is:
|
||||
|
||||
* Once a topic branch forks from "master", "master" is never
|
||||
merged into it again (either directly or indirectly).
|
||||
|
||||
* Once a topic branch is fully cooked and merged into "master",
|
||||
it is deleted. If you need to build on top of it to correct
|
||||
earlier mistakes, a new topic branch is created by forking at
|
||||
the tip of the "master". This is not strictly necessary, but
|
||||
it makes it easier to keep your history simple.
|
||||
|
||||
* Whenever you need to test or publish your changes to topic
|
||||
branches, merge them into "next" branch.
|
||||
|
||||
The script, being an example, hardcodes the publish branch name
|
||||
to be "next", but it is trivial to make it configurable via
|
||||
$GIT_DIR/config mechanism.
|
||||
|
||||
With this workflow, you would want to know:
|
||||
|
||||
(1) ... if a topic branch has ever been merged to "next". Young
|
||||
topic branches can have stupid mistakes you would rather
|
||||
clean up before publishing, and things that have not been
|
||||
merged into other branches can be easily rebased without
|
||||
affecting other people. But once it is published, you would
|
||||
not want to rewind it.
|
||||
|
||||
(2) ... if a topic branch has been fully merged to "master".
|
||||
Then you can delete it. More importantly, you should not
|
||||
build on top of it -- other people may already want to
|
||||
change things related to the topic as patches against your
|
||||
"master", so if you need further changes, it is better to
|
||||
fork the topic (perhaps with the same name) afresh from the
|
||||
tip of "master".
|
||||
|
||||
Let's look at this example:
|
||||
|
||||
o---o---o---o---o---o---o---o---o---o "next"
|
||||
/ / / /
|
||||
/ a---a---b A / /
|
||||
/ / / /
|
||||
/ / c---c---c---c B /
|
||||
/ / / \ /
|
||||
/ / / b---b C \ /
|
||||
/ / / / \ /
|
||||
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||
|
||||
|
||||
A, B and C are topic branches.
|
||||
|
||||
* A has one fix since it was merged up to "next".
|
||||
|
||||
* B has finished. It has been fully merged up to "master" and "next",
|
||||
and is ready to be deleted.
|
||||
|
||||
* C has not merged to "next" at all.
|
||||
|
||||
We would want to allow C to be rebased, refuse A, and encourage
|
||||
B to be deleted.
|
||||
|
||||
To compute (1):
|
||||
|
||||
git rev-list ^master ^topic next
|
||||
git rev-list ^master next
|
||||
|
||||
if these match, topic has not merged in next at all.
|
||||
|
||||
To compute (2):
|
||||
|
||||
git rev-list master..topic
|
||||
|
||||
if this is empty, it is fully merged to "master".
|
||||
|
||||
DOC_END
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
ORI_DIR=`pwd`
|
||||
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
|
||||
cd "$ORI_DIR"
|
||||
for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do
|
||||
sh "$SHELL_FOLDER/pre-receive.d/$i"
|
||||
done
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' pre-receive
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare the commit log message.
|
||||
# Called by "git commit" with the name of the file that has the
|
||||
# commit message, followed by the description of the commit
|
||||
# message's source. The hook's purpose is to edit the commit
|
||||
# message file. If the hook fails with a non-zero status,
|
||||
# the commit is aborted.
|
||||
#
|
||||
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||
|
||||
# This hook includes three examples. The first comments out the
|
||||
# "Conflicts:" part of a merge commit.
|
||||
#
|
||||
# The second includes the output of "git diff --name-status -r"
|
||||
# into the message, just before the "git status" output. It is
|
||||
# commented because it doesn't cope with --amend or with squashed
|
||||
# commits.
|
||||
#
|
||||
# The third example adds a Signed-off-by line to the message, that can
|
||||
# still be edited. This is rarely a good idea.
|
||||
|
||||
case "$2,$3" in
|
||||
merge,)
|
||||
/usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
|
||||
|
||||
# ,|template,)
|
||||
# /usr/bin/perl -i.bak -pe '
|
||||
# print "\n" . `git diff --cached --name-status -r`
|
||||
# if /^#/ && $first++ == 0' "$1" ;;
|
||||
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
ORI_DIR=`pwd`
|
||||
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
|
||||
cd "$ORI_DIR"
|
||||
for i in `ls "$SHELL_FOLDER/update.d"`; do
|
||||
sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3
|
||||
done
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' update $1 $2 $3
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to block unannotated tags from entering.
|
||||
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||
#
|
||||
# To enable this hook, rename this file to "update".
|
||||
#
|
||||
# Config
|
||||
# ------
|
||||
# hooks.allowunannotated
|
||||
# This boolean sets whether unannotated tags will be allowed into the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowdeletetag
|
||||
# This boolean sets whether deleting tags will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowmodifytag
|
||||
# This boolean sets whether a tag may be modified after creation. By default
|
||||
# it won't be.
|
||||
# hooks.allowdeletebranch
|
||||
# This boolean sets whether deleting branches will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.denycreatebranch
|
||||
# This boolean sets whether remotely creating branches will be denied
|
||||
# in the repository. By default this is allowed.
|
||||
#
|
||||
|
||||
# --- Command line
|
||||
refname="$1"
|
||||
oldrev="$2"
|
||||
newrev="$3"
|
||||
|
||||
# --- Safety check
|
||||
if [ -z "$GIT_DIR" ]; then
|
||||
echo "Don't run this script from the command line." >&2
|
||||
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Config
|
||||
allowunannotated=$(git config --bool hooks.allowunannotated)
|
||||
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
|
||||
denycreatebranch=$(git config --bool hooks.denycreatebranch)
|
||||
allowdeletetag=$(git config --bool hooks.allowdeletetag)
|
||||
allowmodifytag=$(git config --bool hooks.allowmodifytag)
|
||||
|
||||
# check for no description
|
||||
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||
case "$projectdesc" in
|
||||
"Unnamed repository"* | "")
|
||||
echo "*** Project description file hasn't been set" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Check types
|
||||
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||
zero="0000000000000000000000000000000000000000"
|
||||
if [ "$newrev" = "$zero" ]; then
|
||||
newrev_type=delete
|
||||
else
|
||||
newrev_type=$(git cat-file -t $newrev)
|
||||
fi
|
||||
|
||||
case "$refname","$newrev_type" in
|
||||
refs/tags/*,commit)
|
||||
# un-annotated tag
|
||||
short_refname=${refname##refs/tags/}
|
||||
if [ "$allowunannotated" != "true" ]; then
|
||||
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,delete)
|
||||
# delete tag
|
||||
if [ "$allowdeletetag" != "true" ]; then
|
||||
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,tag)
|
||||
# annotated tag
|
||||
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||
then
|
||||
echo "*** Tag '$refname' already exists." >&2
|
||||
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,commit)
|
||||
# branch
|
||||
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,delete)
|
||||
# delete branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/remotes/*,commit)
|
||||
# tracking branch
|
||||
;;
|
||||
refs/remotes/*,delete)
|
||||
# delete tracking branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Anything else (is there anything else?)
|
||||
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Finished
|
||||
exit 0
|
||||
@@ -0,0 +1,6 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
||||
@@ -0,0 +1 @@
|
||||
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
100
integrations/integration_test.go
Normal file
100
integrations/integration_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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 integrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/routers/routes"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/macaron.v1"
|
||||
"gopkg.in/testfixtures.v2"
|
||||
)
|
||||
|
||||
var mac *macaron.Macaron
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
appIniPath := os.Getenv("GITEA_CONF")
|
||||
if appIniPath == "" {
|
||||
fmt.Println("Environment variable $GITEA_CONF not set")
|
||||
os.Exit(1)
|
||||
}
|
||||
setting.CustomConf = appIniPath
|
||||
routers.GlobalInit()
|
||||
mac = routes.NewMacaron()
|
||||
routes.RegisterRoutes(mac)
|
||||
|
||||
var helper testfixtures.Helper
|
||||
if setting.UseMySQL {
|
||||
helper = &testfixtures.MySQL{}
|
||||
} else if setting.UsePostgreSQL {
|
||||
helper = &testfixtures.PostgreSQL{}
|
||||
} else if setting.UseSQLite3 {
|
||||
helper = &testfixtures.SQLite{}
|
||||
} else {
|
||||
fmt.Println("Unsupported RDBMS for integration tests")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err := models.InitFixtures(
|
||||
helper,
|
||||
"models/fixtures/",
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("Error initializing test database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func prepareTestEnv(t *testing.T) {
|
||||
assert.NoError(t, models.LoadFixtures())
|
||||
assert.NoError(t, os.RemoveAll("integrations/gitea-integration"))
|
||||
assert.NoError(t, com.CopyDir("integrations/gitea-integration-meta", "integrations/gitea-integration"))
|
||||
}
|
||||
|
||||
type TestResponseWriter struct {
|
||||
HeaderCode int
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
func (w *TestResponseWriter) Header() http.Header {
|
||||
return make(map[string][]string)
|
||||
}
|
||||
|
||||
func (w *TestResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w *TestResponseWriter) WriteHeader(n int) {
|
||||
w.HeaderCode = n
|
||||
}
|
||||
|
||||
type TestResponse struct {
|
||||
HeaderCode int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func MakeRequest(req *http.Request) *TestResponse {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
respWriter := &TestResponseWriter{
|
||||
Writer: buffer,
|
||||
}
|
||||
mac.ServeHTTP(respWriter, req)
|
||||
return &TestResponse{
|
||||
HeaderCode: respWriter.HeaderCode,
|
||||
Body: buffer.Bytes(),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
56
integrations/mysql.ini
Normal file
56
integrations/mysql.ini
Normal file
@@ -0,0 +1,56 @@
|
||||
APP_NAME = Gitea: Git with a cup of tea
|
||||
RUN_MODE = prod
|
||||
|
||||
[database]
|
||||
DB_TYPE = mysql
|
||||
HOST = 127.0.0.1:3306
|
||||
NAME = testgitea
|
||||
USER = root
|
||||
PASSWD =
|
||||
SSL_MODE = disable
|
||||
PATH = data/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = integrations/gitea-integration/gitea-repositories
|
||||
|
||||
[server]
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3000
|
||||
ROOT_URL = http://localhost:3000/
|
||||
DISABLE_SSH = false
|
||||
SSH_PORT = 22
|
||||
LFS_START_SERVER = false
|
||||
OFFLINE_MODE = false
|
||||
|
||||
[mailer]
|
||||
ENABLED = false
|
||||
|
||||
[service]
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
DISABLE_REGISTRATION = false
|
||||
ENABLE_CAPTCHA = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
||||
NO_REPLY_ADDRESS = noreply.example.org
|
||||
|
||||
[picture]
|
||||
DISABLE_GRAVATAR = false
|
||||
ENABLE_FEDERATED_AVATAR = false
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
|
||||
[log]
|
||||
MODE = console,file
|
||||
|
||||
[log.console]
|
||||
LEVEL = Warn
|
||||
|
||||
[log.file]
|
||||
LEVEL = Info
|
||||
ROOT_PATH = log
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = 9pCviYTWSb
|
||||
56
integrations/pgsql.ini
Normal file
56
integrations/pgsql.ini
Normal file
@@ -0,0 +1,56 @@
|
||||
APP_NAME = Gitea: Git with a cup of tea
|
||||
RUN_MODE = prod
|
||||
|
||||
[database]
|
||||
DB_TYPE = postgres
|
||||
HOST = 127.0.0.1:5432
|
||||
NAME = testgitea
|
||||
USER = postgres
|
||||
PASSWD = postgres
|
||||
SSL_MODE = disable
|
||||
PATH = data/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = integrations/gitea-integration/gitea-repositories
|
||||
|
||||
[server]
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3000
|
||||
ROOT_URL = http://localhost:3000/
|
||||
DISABLE_SSH = false
|
||||
SSH_PORT = 22
|
||||
LFS_START_SERVER = false
|
||||
OFFLINE_MODE = false
|
||||
|
||||
[mailer]
|
||||
ENABLED = false
|
||||
|
||||
[service]
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
DISABLE_REGISTRATION = false
|
||||
ENABLE_CAPTCHA = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
||||
NO_REPLY_ADDRESS = noreply.example.org
|
||||
|
||||
[picture]
|
||||
DISABLE_GRAVATAR = false
|
||||
ENABLE_FEDERATED_AVATAR = false
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
|
||||
[log]
|
||||
MODE = console,file
|
||||
|
||||
[log.console]
|
||||
LEVEL = Warn
|
||||
|
||||
[log.file]
|
||||
LEVEL = Info
|
||||
ROOT_PATH = log
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = 9pCviYTWSb
|
||||
@@ -2,34 +2,40 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integration
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"os"
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/integrations/internal/utils"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
prepareTestEnv(t)
|
||||
|
||||
if err := utils.New(t, &conf).RunTest(install, signup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setting.Service.EnableCaptcha = false
|
||||
|
||||
req, err := http.NewRequest("POST", "/user/sign_up",
|
||||
bytes.NewBufferString(url.Values{
|
||||
"user_name": []string{"exampleUser"},
|
||||
"email": []string{"exampleUser@example.com"},
|
||||
"password": []string{"examplePassword"},
|
||||
"retype": []string{"examplePassword"},
|
||||
}.Encode()),
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp := MakeRequest(req)
|
||||
assert.EqualValues(t, http.StatusFound, resp.HeaderCode)
|
||||
|
||||
// should be able to view new user's page
|
||||
req, err = http.NewRequest("GET", "/exampleUser", nil)
|
||||
assert.NoError(t, err)
|
||||
resp = MakeRequest(req)
|
||||
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
|
||||
}
|
||||
|
||||
58
integrations/sqlite.ini
Normal file
58
integrations/sqlite.ini
Normal file
@@ -0,0 +1,58 @@
|
||||
APP_NAME = Gitea: Git with a cup of tea
|
||||
RUN_MODE = prod
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
HOST = 127.0.0.1:3306
|
||||
NAME = testgitea
|
||||
USER = gitea
|
||||
PASSWD =
|
||||
SSL_MODE = disable
|
||||
PATH = :memory:
|
||||
|
||||
[repository]
|
||||
ROOT = integrations/gitea-integration/gitea-repositories
|
||||
|
||||
[server]
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3000
|
||||
ROOT_URL = http://localhost:3000/
|
||||
DISABLE_SSH = false
|
||||
SSH_PORT = 22
|
||||
LFS_START_SERVER = false
|
||||
OFFLINE_MODE = false
|
||||
|
||||
[mailer]
|
||||
ENABLED = false
|
||||
|
||||
[service]
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
DISABLE_REGISTRATION = false
|
||||
ENABLE_CAPTCHA = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = false
|
||||
NO_REPLY_ADDRESS = noreply.example.org
|
||||
|
||||
[picture]
|
||||
DISABLE_GRAVATAR = false
|
||||
ENABLE_FEDERATED_AVATAR = false
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
|
||||
[log]
|
||||
MODE = console,file
|
||||
|
||||
[log.console]
|
||||
LEVEL = Warn
|
||||
|
||||
[log.file]
|
||||
LEVEL = Info
|
||||
ROOT_PATH = log
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = 9pCviYTWSb
|
||||
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTI3OTU5ODN9.OQkH5UmzID2XBdwQ9TAI6Jj2t1X-wElVTjbE7aoN4I8
|
||||
|
||||
@@ -2,81 +2,32 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package integration
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/integrations/internal/utils"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"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,
|
||||
}
|
||||
prepareTestEnv(t)
|
||||
|
||||
if err := utils.New(t, &conf).RunTest(install, version); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setting.AppVer = "1.1.0+dev"
|
||||
req, err := http.NewRequest("GET", "/api/v1/version", nil)
|
||||
assert.NoError(t, err)
|
||||
resp := MakeRequest(req)
|
||||
|
||||
var version gitea.ServerVersion
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(resp.Body))
|
||||
assert.NoError(t, decoder.Decode(&version))
|
||||
|
||||
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
|
||||
assert.Equal(t, setting.AppVer, string(version.Version))
|
||||
}
|
||||
|
||||
30
integrations/view_test.go
Normal file
30
integrations/view_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 integrations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestViewRepo(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
|
||||
req, err := http.NewRequest("GET", "/user2/repo1", nil)
|
||||
assert.NoError(t, err)
|
||||
resp := MakeRequest(req)
|
||||
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
|
||||
}
|
||||
|
||||
func TestViewUser(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
|
||||
req, err := http.NewRequest("GET", "/user2", nil)
|
||||
assert.NoError(t, err)
|
||||
resp := MakeRequest(req)
|
||||
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
|
||||
}
|
||||
4
main.go
4
main.go
@@ -36,9 +36,11 @@ func main() {
|
||||
cmd.CmdWeb,
|
||||
cmd.CmdServ,
|
||||
cmd.CmdHook,
|
||||
cmd.CmdDump,
|
||||
cmd.CmdCert,
|
||||
cmd.CmdAdmin,
|
||||
cmd.Backup,
|
||||
cmd.Restore,
|
||||
cmd.Dump,
|
||||
}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
err := app.Run(os.Args)
|
||||
|
||||
@@ -398,7 +398,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
if IsErrIssueNotExist(err) || err == errMissingIssueNumber {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
@@ -438,7 +438,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
if IsErrIssueNotExist(err) || err == errMissingIssueNumber {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -6,16 +6,13 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
//NoticeType describes the notice type
|
||||
@@ -79,19 +76,7 @@ func RemoveAllWithNotice(title, path string) {
|
||||
}
|
||||
|
||||
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:
|
||||
// https://github.com/golang/go/commit/2ffb3e5d905b5622204d199128dec06cefd57790
|
||||
if setting.IsWindows {
|
||||
// converting "/" to "\" in path on Windows
|
||||
path = strings.Replace(path, "/", "\\", -1)
|
||||
err = exec.Command("cmd", "/C", "rmdir", "/S", "/Q", path).Run()
|
||||
} else {
|
||||
err = os.RemoveAll(path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err := util.RemoveAll(path); err != nil {
|
||||
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
|
||||
log.Warn(desc)
|
||||
if err = createNotice(e, NoticeRepository, desc); err != nil {
|
||||
|
||||
@@ -20,15 +20,15 @@ import (
|
||||
|
||||
// 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
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
ReleaseID int64 `xorm:"INDEX"`
|
||||
CommentID int64
|
||||
Name string
|
||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
// BeforeInsert is invoked from XORM before inserting an object of this type.
|
||||
@@ -45,6 +45,19 @@ func (a *Attachment) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// IncreaseDownloadCount is update download count + 1
|
||||
func (a *Attachment) IncreaseDownloadCount() error {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
|
||||
// Update download count.
|
||||
if _, err := sess.Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil {
|
||||
return fmt.Errorf("increase attachment count: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachmentLocalPath returns where attachment is stored in local file
|
||||
// system based on given UUID.
|
||||
func AttachmentLocalPath(uuid string) string {
|
||||
|
||||
60
models/attachment_test.go
Normal file
60
models/attachment_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 TestIncreaseDownloadCount(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
attachment, err := GetAttachmentByUUID("1234567890")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), attachment.DownloadCount)
|
||||
|
||||
// increase download count
|
||||
err = attachment.IncreaseDownloadCount()
|
||||
assert.NoError(t, err)
|
||||
|
||||
attachment, err = GetAttachmentByUUID("1234567890")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), attachment.DownloadCount)
|
||||
}
|
||||
|
||||
func TestGetByCommentOrIssueID(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
// count of attachments from issue ID
|
||||
attachments, err := GetAttachmentsByIssueID(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(attachments))
|
||||
|
||||
attachments, err = GetAttachmentsByCommentID(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(attachments))
|
||||
}
|
||||
|
||||
func TestDeleteAttachments(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
count, err := DeleteAttachmentsByIssue(4, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
count, err = DeleteAttachmentsByComment(2, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
|
||||
err = DeleteAttachment(&Attachment{ID: 8}, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
attachment, err := GetAttachmentByUUID("test-12345")
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrAttachmentNotExist(err))
|
||||
assert.Nil(t, attachment)
|
||||
}
|
||||
@@ -260,6 +260,36 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
}
|
||||
|
||||
// ErrGPGEmailNotFound represents a "ErrGPGEmailNotFound" kind of error.
|
||||
type ErrGPGEmailNotFound struct {
|
||||
Email string
|
||||
}
|
||||
|
||||
// IsErrGPGEmailNotFound checks if an error is a ErrGPGEmailNotFound.
|
||||
func IsErrGPGEmailNotFound(err error) bool {
|
||||
_, ok := err.(ErrGPGEmailNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrGPGEmailNotFound) Error() string {
|
||||
return fmt.Sprintf("failed to found email or is not confirmed : %s", err.Email)
|
||||
}
|
||||
|
||||
// ErrGPGKeyParsing represents a "ErrGPGKeyParsing" kind of error.
|
||||
type ErrGPGKeyParsing struct {
|
||||
ParseError error
|
||||
}
|
||||
|
||||
// IsErrGPGKeyParsing checks if an error is a ErrGPGKeyParsing.
|
||||
func IsErrGPGKeyParsing(err error) bool {
|
||||
_, ok := err.(ErrGPGKeyParsing)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrGPGKeyParsing) Error() string {
|
||||
return fmt.Sprintf("failed to parse gpg key %s", err.ParseError.Error())
|
||||
}
|
||||
|
||||
// ErrGPGKeyNotExist represents a "GPGKeyNotExist" kind of error.
|
||||
type ErrGPGKeyNotExist struct {
|
||||
ID int64
|
||||
|
||||
71
models/fixtures/attachment.yml
Normal file
71
models/fixtures/attachment.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
-
|
||||
id: 1
|
||||
uuid: 1234567890
|
||||
issue_id: 1
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 2
|
||||
uuid: 1122334455
|
||||
issue_id: 1
|
||||
comment_id: 0
|
||||
name: attach2
|
||||
download_count: 1
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 3
|
||||
uuid: comment-id-1
|
||||
issue_id: 2
|
||||
comment_id: 1
|
||||
name: attach1
|
||||
download_count: 0
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 4
|
||||
uuid: comment-id-2
|
||||
issue_id: 3
|
||||
comment_id: 1
|
||||
name: attach2
|
||||
download_count: 1
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 5
|
||||
uuid: comment-id-3
|
||||
issue_id: 4
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 6
|
||||
uuid: comment-id-4
|
||||
issue_id: 5
|
||||
comment_id: 2
|
||||
name: attach1
|
||||
download_count: 0
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 7
|
||||
uuid: comment-id-5
|
||||
issue_id: 5
|
||||
comment_id: 2
|
||||
name: attach1
|
||||
download_count: 0
|
||||
created_unix: 946684800
|
||||
|
||||
-
|
||||
id: 8
|
||||
uuid: test-12345
|
||||
issue_id: 6
|
||||
comment_id: 0
|
||||
name: attach1
|
||||
download_count: 0
|
||||
created_unix: 946684800
|
||||
54
models/fixtures/commit_status.yml
Normal file
54
models/fixtures/commit_status.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
-
|
||||
id: 1
|
||||
index: 1
|
||||
repo_id: 1
|
||||
state: "pending"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/builds/
|
||||
description: My awesome CI-service
|
||||
context: ci/awesomeness
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 2
|
||||
index: 2
|
||||
repo_id: 1
|
||||
state: "warning"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/converage/
|
||||
description: My awesome Coverage service
|
||||
context: cov/awesomeness
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 3
|
||||
index: 3
|
||||
repo_id: 1
|
||||
state: "success"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/converage/
|
||||
description: My awesome Coverage service
|
||||
context: cov/awesomeness
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 4
|
||||
index: 4
|
||||
repo_id: 1
|
||||
state: "failure"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/builds/
|
||||
description: My awesome CI-service
|
||||
context: ci/awesomeness
|
||||
creator_id: 2
|
||||
|
||||
-
|
||||
id: 5
|
||||
index: 5
|
||||
repo_id: 1
|
||||
state: "error"
|
||||
sha: "1234123412341234123412341234123412341234"
|
||||
target_url: https://example.com/builds/
|
||||
description: My awesome deploy service
|
||||
context: deploy/awesomeness
|
||||
creator_id: 2
|
||||
15
models/fixtures/issue_watch.yml
Normal file
15
models/fixtures/issue_watch.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
-
|
||||
id: 1
|
||||
user_id: 1
|
||||
issue_id: 1
|
||||
is_watching: true
|
||||
created_unix: 946684800
|
||||
updated_unix: 946684800
|
||||
|
||||
-
|
||||
id: 2
|
||||
user_id: 2
|
||||
issue_id: 2
|
||||
is_watching: false
|
||||
created_unix: 946684800
|
||||
updated_unix: 946684800
|
||||
@@ -287,7 +287,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||
curFile.IsBin = true
|
||||
curFile.IsLFSFile = true
|
||||
curSection.Lines = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
|
||||
func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) {
|
||||
list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, ErrGPGKeyParsing{err}
|
||||
}
|
||||
return list[0], nil
|
||||
}
|
||||
@@ -219,7 +219,7 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
|
||||
}
|
||||
}
|
||||
if emails[n] == nil {
|
||||
return nil, fmt.Errorf("Failed to found email or is not confirmed : %s", ident.UserId.Email)
|
||||
return nil, ErrGPGEmailNotFound{ident.UserId.Email}
|
||||
}
|
||||
n++
|
||||
}
|
||||
@@ -400,17 +400,16 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
|
||||
}
|
||||
}
|
||||
|
||||
//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 {
|
||||
//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",
|
||||
}
|
||||
}
|
||||
//We get PK
|
||||
if err := verifySign(sig, hash, k); err == nil {
|
||||
return &CommitVerification{ //Everything is ok
|
||||
@@ -422,6 +421,16 @@ func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
|
||||
}
|
||||
//And test also SubsKey
|
||||
for _, sk := range k.SubsKey {
|
||||
|
||||
//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",
|
||||
}
|
||||
}
|
||||
if err := verifySign(sig, hash, sk); err == nil {
|
||||
return &CommitVerification{ //Everything is ok
|
||||
Verified: true,
|
||||
|
||||
96
models/issue_watch.go
Normal file
96
models/issue_watch.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
Updated time.Time `xorm:"-"`
|
||||
UpdatedUnix int64 `xorm:"NOT NULL"`
|
||||
}
|
||||
|
||||
// BeforeInsert is invoked from XORM before inserting an object of this type.
|
||||
func (iw *IssueWatch) BeforeInsert() {
|
||||
var (
|
||||
t = time.Now()
|
||||
u = t.Unix()
|
||||
)
|
||||
iw.Created = t
|
||||
iw.CreatedUnix = u
|
||||
iw.Updated = t
|
||||
iw.UpdatedUnix = u
|
||||
}
|
||||
|
||||
// BeforeUpdate is invoked from XORM before updating an object of this type.
|
||||
func (iw *IssueWatch) BeforeUpdate() {
|
||||
var (
|
||||
t = time.Now()
|
||||
u = t.Unix()
|
||||
)
|
||||
iw.Updated = t
|
||||
iw.UpdatedUnix = u
|
||||
}
|
||||
|
||||
// CreateOrUpdateIssueWatch set watching for a user and issue
|
||||
func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
|
||||
iw, exists, err := getIssueWatch(x, userID, issueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
iw = &IssueWatch{
|
||||
UserID: userID,
|
||||
IssueID: issueID,
|
||||
IsWatching: isWatching,
|
||||
}
|
||||
|
||||
if _, err := x.Insert(iw); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
iw.IsWatching = isWatching
|
||||
|
||||
if _, err := x.Id(iw.ID).Cols("is_watching", "updated_unix").Update(iw); 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) {
|
||||
return getIssueWatch(x, userID, issueID)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetIssueWatchers returns watchers/unwatchers of a given issue
|
||||
func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {
|
||||
return getIssueWatchers(x, issueID)
|
||||
}
|
||||
|
||||
func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) {
|
||||
err = e.
|
||||
Where("issue_id = ?", issueID).
|
||||
Find(&watches)
|
||||
return
|
||||
}
|
||||
51
models/issue_watch_test.go
Normal file
51
models/issue_watch_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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 TestCreateOrUpdateIssueWatch(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
assert.NoError(t, CreateOrUpdateIssueWatch(3, 1, true))
|
||||
iw := AssertExistsAndLoadBean(t, &IssueWatch{UserID: 3, IssueID: 1}).(*IssueWatch)
|
||||
assert.Equal(t, true, iw.IsWatching)
|
||||
|
||||
assert.NoError(t, CreateOrUpdateIssueWatch(1, 1, false))
|
||||
iw = AssertExistsAndLoadBean(t, &IssueWatch{UserID: 1, IssueID: 1}).(*IssueWatch)
|
||||
assert.Equal(t, false, iw.IsWatching)
|
||||
}
|
||||
|
||||
func TestGetIssueWatch(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
_, exists, err := GetIssueWatch(1, 1)
|
||||
assert.Equal(t, true, exists)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, exists, err = GetIssueWatch(2, 2)
|
||||
assert.Equal(t, true, exists)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, exists, err = GetIssueWatch(3, 1)
|
||||
assert.Equal(t, false, exists)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetIssueWatchers(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
iws, err := GetIssueWatchers(1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(iws))
|
||||
|
||||
iws, err = GetIssueWatchers(5)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(iws))
|
||||
}
|
||||
@@ -102,6 +102,12 @@ var migrations = []Migration{
|
||||
NewMigration("add show field in user openid table", addUserOpenIDShow),
|
||||
// v26 -> v27
|
||||
NewMigration("generate and migrate repo and wiki Git hooks", generateAndMigrateGitHookChains),
|
||||
// v27 -> v28
|
||||
NewMigration("change mirror interval from hours to time.Duration", convertIntervalToDuration),
|
||||
// v28 -> v29
|
||||
NewMigration("add field for repo size", addRepoSize),
|
||||
// v29 -> v30
|
||||
NewMigration("add commit status table", addCommitStatus),
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
|
||||
56
models/migrations/v27.go
Normal file
56
models/migrations/v27.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017 Gitea. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func convertIntervalToDuration(x *xorm.Engine) (err error) {
|
||||
type Repository struct {
|
||||
ID int64
|
||||
OwnerID int64
|
||||
Name string
|
||||
}
|
||||
type Mirror struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
Interval time.Duration
|
||||
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
|
||||
|
||||
Updated time.Time `xorm:"-"`
|
||||
UpdatedUnix int64 `xorm:"INDEX"`
|
||||
NextUpdate time.Time `xorm:"-"`
|
||||
NextUpdateUnix int64 `xorm:"INDEX"`
|
||||
|
||||
address string `xorm:"-"`
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var mirrors []Mirror
|
||||
err = sess.Table("mirror").Select("*").Find(&mirrors)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Query repositories: %v", err)
|
||||
}
|
||||
for _, mirror := range mirrors {
|
||||
mirror.Interval = mirror.Interval * time.Hour
|
||||
_, err := sess.Id(mirror.ID).Cols("interval").Update(mirror)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update mirror interval failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
77
models/migrations/v28.go
Normal file
77
models/migrations/v28.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2017 Gitea. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func addRepoSize(x *xorm.Engine) (err error) {
|
||||
log.Info("This migration could take up to minutes, please be patient.")
|
||||
type Repository struct {
|
||||
ID int64
|
||||
OwnerID int64
|
||||
Name string
|
||||
Size int64
|
||||
}
|
||||
type User struct {
|
||||
ID int64
|
||||
Name string
|
||||
}
|
||||
if err = x.Sync2(new(Repository)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
}
|
||||
|
||||
// For the sake of SQLite3, we can't use x.Iterate here.
|
||||
offset := 0
|
||||
for {
|
||||
repos := make([]*Repository, 0, 10)
|
||||
if err = x.Sql(fmt.Sprintf("SELECT * FROM `repository` ORDER BY id ASC LIMIT 10 OFFSET %d", offset)).
|
||||
Find(&repos); err != nil {
|
||||
return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
|
||||
}
|
||||
log.Trace("Select [offset: %d, repos: %d]", offset, len(repos))
|
||||
if len(repos) == 0 {
|
||||
break
|
||||
}
|
||||
offset += 10
|
||||
|
||||
for _, repo := range repos {
|
||||
if repo.Name == "." || repo.Name == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
user := new(User)
|
||||
has, err := x.Where("id = ?", repo.OwnerID).Get(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err)
|
||||
} else if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
|
||||
countObject, err := git.GetRepoSize(repoPath)
|
||||
if err != nil {
|
||||
log.Warn("GetRepoSize: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
repo.Size = countObject.Size + countObject.SizePack
|
||||
if _, err = x.Id(repo.ID).Cols("size").Update(repo); err != nil {
|
||||
return fmt.Errorf("update size: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
34
models/migrations/v29.go
Normal file
34
models/migrations/v29.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
// CommitStatus see models/status.go
|
||||
type CommitStatus struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
State string `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
Context string `xorm:"TEXT"`
|
||||
CreatorID int64 `xorm:"INDEX"`
|
||||
|
||||
CreatedUnix int64 `xorm:"INDEX"`
|
||||
UpdatedUnix int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
func addCommitStatus(x *xorm.Engine) error {
|
||||
if err := x.Sync2(new(CommitStatus)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
119
models/models.go
119
models/models.go
@@ -5,7 +5,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -13,20 +15,21 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
// Needed for the MySQL driver
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
// Needed for the MySQL driver
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
// Needed for the Postgresql driver
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
// Needed for the MSSSQL driver
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
|
||||
"code.gitea.io/gitea/models/migrations"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// Engine represents a xorm engine or session.
|
||||
@@ -117,6 +120,8 @@ func init() {
|
||||
new(ExternalLoginUser),
|
||||
new(ProtectedBranch),
|
||||
new(UserOpenID),
|
||||
new(IssueWatch),
|
||||
new(CommitStatus),
|
||||
)
|
||||
|
||||
gonicNames := []string{"SSL", "UID"}
|
||||
@@ -325,14 +330,104 @@ func Ping() error {
|
||||
return x.Ping()
|
||||
}
|
||||
|
||||
// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
|
||||
func DumpDatabase(filePath string, dbType string) error {
|
||||
// Version describes the version table. Should have only one row with id==1
|
||||
type Version struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
}
|
||||
|
||||
// DumpDatabaseOld dumps all data from database according the special database SQL syntax to file system.
|
||||
// NOTE: DEPRICATED in favour of DumpDatabase
|
||||
func DumpDatabaseOld(filePath string, dbType string) error {
|
||||
var tbs []*core.Table
|
||||
for _, t := range tables {
|
||||
tbs = append(tbs, x.TableInfo(t).Table)
|
||||
if len(dbType) > 0 {
|
||||
return x.DumpTablesToFile(tbs, filePath, core.DbType(dbType))
|
||||
}
|
||||
}
|
||||
if len(dbType) > 0 {
|
||||
return x.DumpTablesToFile(tbs, filePath, core.DbType(dbType))
|
||||
}
|
||||
return x.DumpTablesToFile(tbs, filePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DumpDatabase dumps all data from database to file system in JSON format.
|
||||
func DumpDatabase(dirPath string) (err error) {
|
||||
os.MkdirAll(dirPath, os.ModePerm)
|
||||
// Purposely create a local variable to not modify global variable
|
||||
internalTables := append(tables, new(Version))
|
||||
for _, table := range internalTables {
|
||||
tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*models.")
|
||||
tableFile := path.Join(dirPath, tableName+".json")
|
||||
f, err := os.Create(tableFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to create JSON file: %v", err)
|
||||
}
|
||||
|
||||
if err = x.Asc("id").Iterate(table, func(idx int, bean interface{}) (err error) {
|
||||
enc := json.NewEncoder(f)
|
||||
return enc.Encode(bean)
|
||||
}); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("fail to dump table '%s': %v", tableName, err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportDatabase imports data from backup archive.
|
||||
func ImportDatabase(dirPath string) (err error) {
|
||||
// Purposely create a local variable to not modify global variable
|
||||
internalTables := append(tables, new(Version))
|
||||
for _, table := range internalTables {
|
||||
tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*models.")
|
||||
tableFile := path.Join(dirPath, tableName+".json")
|
||||
if !com.IsExist(tableFile) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = x.DropTables(table); err != nil {
|
||||
return fmt.Errorf("fail to drop table '%s': %v", tableName, err)
|
||||
} else if err = x.Sync2(table); err != nil {
|
||||
return fmt.Errorf("fail to sync table '%s': %v", tableName, err)
|
||||
}
|
||||
|
||||
f, err := os.Open(tableFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to open JSON file: %v", err)
|
||||
}
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
switch bean := table.(type) {
|
||||
case *LoginSource:
|
||||
meta := make(map[string]interface{})
|
||||
if err = json.Unmarshal(scanner.Bytes(), &meta); err != nil {
|
||||
return fmt.Errorf("fail to unmarshal to map: %v", err)
|
||||
}
|
||||
|
||||
tp := LoginType(com.StrTo(com.ToStr(meta["Type"])).MustInt64())
|
||||
switch tp {
|
||||
case LoginLDAP, LoginDLDAP:
|
||||
bean.Cfg = new(LDAPConfig)
|
||||
case LoginSMTP:
|
||||
bean.Cfg = new(SMTPConfig)
|
||||
case LoginPAM:
|
||||
bean.Cfg = new(PAMConfig)
|
||||
case LoginOAuth2:
|
||||
bean.Cfg = new(OAuth2Config)
|
||||
default:
|
||||
return fmt.Errorf("unrecognized login source type:: %v", tp)
|
||||
}
|
||||
table = bean
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(scanner.Bytes(), table); err != nil {
|
||||
return fmt.Errorf("fail to unmarshal to struct: %v", err)
|
||||
}
|
||||
|
||||
if _, err = x.Insert(table); err != nil {
|
||||
return fmt.Errorf("fail to insert strcut: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -96,6 +96,11 @@ func CreateOrUpdateIssueNotifications(issue *Issue, notificationAuthorID int64)
|
||||
}
|
||||
|
||||
func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthorID int64) error {
|
||||
issueWatches, err := getIssueWatchers(e, issue.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
watches, err := getWatchers(e, issue.RepoID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -106,23 +111,42 @@ func createOrUpdateIssueNotifications(e Engine, issue *Issue, notificationAuthor
|
||||
return err
|
||||
}
|
||||
|
||||
for _, watch := range watches {
|
||||
alreadyNotified := make(map[int64]struct{}, len(issueWatches)+len(watches))
|
||||
|
||||
notifyUser := func(userID int64) error {
|
||||
// do not send notification for the own issuer/commenter
|
||||
if watch.UserID == notificationAuthorID {
|
||||
if userID == notificationAuthorID {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := alreadyNotified[userID]; ok {
|
||||
return nil
|
||||
}
|
||||
alreadyNotified[userID] = struct{}{}
|
||||
|
||||
if notificationExists(notifications, issue.ID, userID) {
|
||||
return updateIssueNotification(e, userID, issue.ID, notificationAuthorID)
|
||||
}
|
||||
return createIssueNotification(e, userID, issue, notificationAuthorID)
|
||||
}
|
||||
|
||||
for _, issueWatch := range issueWatches {
|
||||
// ignore if user unwatched the issue
|
||||
if !issueWatch.IsWatching {
|
||||
alreadyNotified[issueWatch.UserID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if notificationExists(notifications, issue.ID, watch.UserID) {
|
||||
err = updateIssueNotification(e, watch.UserID, issue.ID, notificationAuthorID)
|
||||
} else {
|
||||
err = createIssueNotification(e, watch.UserID, issue, notificationAuthorID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err := notifyUser(issueWatch.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, watch := range watches {
|
||||
if err := notifyUser(watch.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -670,13 +670,30 @@ func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*Repository, error)
|
||||
Find(&repos)
|
||||
}
|
||||
|
||||
func (env *accessibleReposEnv) MirrorRepos() ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, 10)
|
||||
return repos, x.
|
||||
Select("`repository`.*").
|
||||
func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) {
|
||||
repoIDs := make([]int64, 0, 10)
|
||||
return repoIDs, x.
|
||||
Table("repository").
|
||||
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true).
|
||||
Where(env.cond()).
|
||||
GroupBy("`repository`.id").
|
||||
OrderBy("updated_unix DESC").
|
||||
Cols("`repository`.id").
|
||||
Find(&repoIDs)
|
||||
}
|
||||
|
||||
func (env *accessibleReposEnv) MirrorRepos() ([]*Repository, error) {
|
||||
repoIDs, err := env.MirrorRepoIDs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MirrorRepoIDs: %v", err)
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, len(repoIDs))
|
||||
if len(repoIDs) <= 0 {
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
return repos, x.
|
||||
In("`repository`.id", repoIDs).
|
||||
Find(&repos)
|
||||
}
|
||||
|
||||
@@ -197,6 +197,8 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
|
||||
Base: apiBaseBranchInfo,
|
||||
Head: apiHeadBranchInfo,
|
||||
MergeBase: pr.MergeBase,
|
||||
Created: &pr.Issue.Created,
|
||||
Updated: &pr.Issue.Updated,
|
||||
}
|
||||
|
||||
if pr.Status != PullRequestStatusChecking {
|
||||
@@ -507,7 +509,9 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
|
||||
mergeCommit, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git rev-list --ancestry-path --merges --reverse): %d", pr.BaseRepo.ID),
|
||||
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
|
||||
"git", "rev-list", "--ancestry-path", "--merges", "--reverse", cmd)
|
||||
|
||||
if err == nil && len(mergeCommit) != 40 {
|
||||
err = fmt.Errorf("unexpected length of output (got:%d bytes) '%s'", len(mergeCommit), mergeCommit)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v %v", stderr, err)
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ type Repository struct {
|
||||
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||
ForkID int64 `xorm:"INDEX"`
|
||||
BaseRepo *Repository `xorm:"-"`
|
||||
Size int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64 `xorm:"INDEX"`
|
||||
@@ -546,6 +547,18 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
|
||||
return repo.OwnerID == userID
|
||||
}
|
||||
|
||||
// UpdateSize updates the repository size, calculating it using git.GetRepoSize
|
||||
func (repo *Repository) UpdateSize() error {
|
||||
repoInfoSize, err := git.GetRepoSize(repo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("UpdateSize: %v", err)
|
||||
}
|
||||
|
||||
repo.Size = repoInfoSize.Size + repoInfoSize.SizePack
|
||||
_, err = x.ID(repo.ID).Cols("size").Update(repo)
|
||||
return err
|
||||
}
|
||||
|
||||
// CanBeForked returns true if repository meets the requirements of being forked.
|
||||
func (repo *Repository) CanBeForked() bool {
|
||||
return !repo.IsBare
|
||||
@@ -582,7 +595,7 @@ func (repo *Repository) DescriptionHTML() template.HTML {
|
||||
sanitize := func(s string) string {
|
||||
return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener">%[1]s</a>`, s)
|
||||
}
|
||||
return template.HTML(descPattern.ReplaceAllStringFunc(markdown.Sanitizer.Sanitize(repo.Description), sanitize))
|
||||
return template.HTML(descPattern.ReplaceAllStringFunc(markdown.Sanitize(repo.Description), sanitize))
|
||||
}
|
||||
|
||||
// LocalCopyPath returns the local repository copy path
|
||||
@@ -810,12 +823,16 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo.UpdateSize(); err != nil {
|
||||
log.Error(4, "Failed to update size for repository: %v", err)
|
||||
}
|
||||
|
||||
if opts.IsMirror {
|
||||
if _, err = x.InsertOne(&Mirror{
|
||||
RepoID: repo.ID,
|
||||
Interval: setting.Mirror.DefaultInterval,
|
||||
EnablePrune: true,
|
||||
NextUpdate: time.Now().Add(time.Duration(setting.Mirror.DefaultInterval) * time.Hour),
|
||||
NextUpdate: time.Now().Add(setting.Mirror.DefaultInterval),
|
||||
}); err != nil {
|
||||
return repo, fmt.Errorf("InsertOne: %v", err)
|
||||
}
|
||||
@@ -844,8 +861,8 @@ func cleanUpMigrateGitConfig(configPath string) error {
|
||||
// createDelegateHooks creates all the hooks scripts for the repo
|
||||
func createDelegateHooks(repoPath string) (err error) {
|
||||
var (
|
||||
hookNames = []string{"pre-receive", "update", "post-receive"}
|
||||
hookTpl = fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType)
|
||||
hookNames = []string{"pre-receive", "update", "post-receive"}
|
||||
hookTpl = fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType)
|
||||
giteaHookTpls = []string{
|
||||
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
|
||||
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
|
||||
@@ -1464,6 +1481,10 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
|
||||
return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo.UpdateSize(); err != nil {
|
||||
log.Error(4, "Failed to update size for repository: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -2171,6 +2192,10 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = repo.UpdateSize(); err != nil {
|
||||
log.Error(4, "Failed to update size for repository: %v", err)
|
||||
}
|
||||
|
||||
// Copy LFS meta objects in new session
|
||||
sess2 := x.NewSession()
|
||||
defer sessionRelease(sess2)
|
||||
|
||||
@@ -27,8 +27,8 @@ type Mirror struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
Interval int // Hour.
|
||||
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
|
||||
Interval time.Duration
|
||||
EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
|
||||
|
||||
Updated time.Time `xorm:"-"`
|
||||
UpdatedUnix int64 `xorm:"INDEX"`
|
||||
@@ -61,14 +61,14 @@ func (m *Mirror) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
case "updated_unix":
|
||||
m.Updated = time.Unix(m.UpdatedUnix, 0).Local()
|
||||
case "next_updated_unix":
|
||||
case "next_update_unix":
|
||||
m.NextUpdate = time.Unix(m.NextUpdateUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
// ScheduleNextUpdate calculates and sets next update time.
|
||||
func (m *Mirror) ScheduleNextUpdate() {
|
||||
m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
|
||||
m.NextUpdate = time.Now().Add(m.Interval)
|
||||
}
|
||||
|
||||
func (m *Mirror) readAddress() {
|
||||
@@ -147,6 +147,11 @@ func (m *Mirror) runSync() bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if err := m.Repo.UpdateSize(); err != nil {
|
||||
log.Error(4, "Failed to update size for mirror repository: %v", err)
|
||||
}
|
||||
|
||||
if m.Repo.HasWiki() {
|
||||
if _, stderr, err := process.GetManager().ExecDir(
|
||||
timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath),
|
||||
|
||||
@@ -37,11 +37,8 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
var fixtures *testfixtures.Context
|
||||
|
||||
// CreateTestEngine create an xorm engine for testing
|
||||
func CreateTestEngine() error {
|
||||
testfixtures.SkipDatabaseNameCheck(true)
|
||||
var err error
|
||||
x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
|
||||
if err != nil {
|
||||
@@ -52,8 +49,7 @@ func CreateTestEngine() error {
|
||||
return err
|
||||
}
|
||||
|
||||
fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/")
|
||||
return err
|
||||
return InitFixtures(&testfixtures.SQLite{}, "fixtures/")
|
||||
}
|
||||
|
||||
func TestFixturesAreConsistent(t *testing.T) {
|
||||
@@ -63,7 +59,7 @@ func TestFixturesAreConsistent(t *testing.T) {
|
||||
|
||||
// PrepareTestDatabase load test fixtures into test database
|
||||
func PrepareTestDatabase() error {
|
||||
return fixtures.Load()
|
||||
return LoadFixtures()
|
||||
}
|
||||
|
||||
func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) {
|
||||
|
||||
@@ -500,6 +500,22 @@ func UpdatePublicKey(key *PublicKey) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdatePublicKeyUpdated updates public key use time.
|
||||
func UpdatePublicKeyUpdated(id int64) error {
|
||||
now := time.Now()
|
||||
cnt, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{
|
||||
Updated: now,
|
||||
UpdatedUnix: now.Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt != 1 {
|
||||
return ErrKeyNotExist{id}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// deletePublicKeys does the actual key deletion but does not update authorized_keys file.
|
||||
func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error {
|
||||
if len(keyIDs) == 0 {
|
||||
|
||||
254
models/status.go
Normal file
254
models/status.go
Normal file
@@ -0,0 +1,254 @@
|
||||
// Copyright 2017 Gitea. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
// CommitStatusState holds the state of a Status
|
||||
// It can be "pending", "success", "error", "failure", and "warning"
|
||||
type CommitStatusState string
|
||||
|
||||
// IsWorseThan returns true if this State is worse than the given State
|
||||
func (css CommitStatusState) IsWorseThan(css2 CommitStatusState) bool {
|
||||
switch css {
|
||||
case CommitStatusError:
|
||||
return true
|
||||
case CommitStatusFailure:
|
||||
return css2 != CommitStatusError
|
||||
case CommitStatusWarning:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure
|
||||
case CommitStatusSuccess:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning
|
||||
default:
|
||||
return css2 != CommitStatusError && css2 != CommitStatusFailure && css2 != CommitStatusWarning && css2 != CommitStatusSuccess
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// CommitStatusPending is for when the Status is Pending
|
||||
CommitStatusPending CommitStatusState = "pending"
|
||||
// CommitStatusSuccess is for when the Status is Success
|
||||
CommitStatusSuccess CommitStatusState = "success"
|
||||
// CommitStatusError is for when the Status is Error
|
||||
CommitStatusError CommitStatusState = "error"
|
||||
// CommitStatusFailure is for when the Status is Failure
|
||||
CommitStatusFailure CommitStatusState = "failure"
|
||||
// CommitStatusWarning is for when the Status is Warning
|
||||
CommitStatusWarning CommitStatusState = "warning"
|
||||
)
|
||||
|
||||
// CommitStatus holds a single Status of a single Commit
|
||||
type CommitStatus struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
State CommitStatusState `xorm:"VARCHAR(7) NOT NULL"`
|
||||
SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"`
|
||||
TargetURL string `xorm:"TEXT"`
|
||||
Description string `xorm:"TEXT"`
|
||||
Context string `xorm:"TEXT"`
|
||||
Creator *User `xorm:"-"`
|
||||
CreatorID int64
|
||||
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64 `xorm:"INDEX"`
|
||||
Updated time.Time `xorm:"-"`
|
||||
UpdatedUnix int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
// BeforeInsert is invoked from XORM before inserting an object of this type.
|
||||
func (status *CommitStatus) BeforeInsert() {
|
||||
status.CreatedUnix = time.Now().Unix()
|
||||
status.UpdatedUnix = status.CreatedUnix
|
||||
}
|
||||
|
||||
// BeforeUpdate is invoked from XORM before updating this object.
|
||||
func (status *CommitStatus) BeforeUpdate() {
|
||||
status.UpdatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
// AfterSet is invoked from XORM after setting the value of a field of
|
||||
// this object.
|
||||
func (status *CommitStatus) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created_unix":
|
||||
status.Created = time.Unix(status.CreatedUnix, 0).Local()
|
||||
case "updated_unix":
|
||||
status.Updated = time.Unix(status.UpdatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
func (status *CommitStatus) loadRepo(e Engine) (err error) {
|
||||
if status.Repo == nil {
|
||||
status.Repo, err = getRepositoryByID(e, status.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getRepositoryByID [%d]: %v", status.RepoID, err)
|
||||
}
|
||||
}
|
||||
if status.Creator == nil && status.CreatorID > 0 {
|
||||
status.Creator, err = getUserByID(e, status.CreatorID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getUserByID [%d]: %v", status.CreatorID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// APIURL returns the absolute APIURL to this commit-status.
|
||||
func (status *CommitStatus) APIURL() string {
|
||||
status.loadRepo(x)
|
||||
return fmt.Sprintf("%sapi/v1/%s/statuses/%s",
|
||||
setting.AppURL, status.Repo.FullName(), status.SHA)
|
||||
}
|
||||
|
||||
// APIFormat assumes some fields assigned with values:
|
||||
// Required - Repo, Creator
|
||||
func (status *CommitStatus) APIFormat() *api.Status {
|
||||
status.loadRepo(x)
|
||||
apiStatus := &api.Status{
|
||||
Created: status.Created,
|
||||
Updated: status.Created,
|
||||
State: api.StatusState(status.State),
|
||||
TargetURL: status.TargetURL,
|
||||
Description: status.Description,
|
||||
ID: status.Index,
|
||||
URL: status.APIURL(),
|
||||
Context: status.Context,
|
||||
}
|
||||
if status.Creator != nil {
|
||||
apiStatus.Creator = status.Creator.APIFormat()
|
||||
}
|
||||
|
||||
return apiStatus
|
||||
}
|
||||
|
||||
// GetCommitStatuses returns all statuses for a given commit.
|
||||
func GetCommitStatuses(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
|
||||
statuses := make([]*CommitStatus, 0, 10)
|
||||
return statuses, x.Limit(10, page*10).Where("repo_id = ?", repo.ID).And("sha = ?", sha).Find(&statuses)
|
||||
}
|
||||
|
||||
// GetLatestCommitStatus returns all statuses with a unique context for a given commit.
|
||||
func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitStatus, error) {
|
||||
statuses := make([]*CommitStatus, 0, 10)
|
||||
return statuses, x.Limit(10, page*10).
|
||||
Where("repo_id = ?", repo.ID).And("sha = ?", sha).Select("*").
|
||||
GroupBy("context").Desc("created_unix").Find(&statuses)
|
||||
}
|
||||
|
||||
// GetCommitStatus populates a given status for a given commit.
|
||||
// NOTE: If ID or Index isn't given, and only Context, TargetURL and/or Description
|
||||
// is given, the CommitStatus created _last_ will be returned.
|
||||
func GetCommitStatus(repo *Repository, sha string, status *CommitStatus) (*CommitStatus, error) {
|
||||
conds := &CommitStatus{
|
||||
Context: status.Context,
|
||||
State: status.State,
|
||||
TargetURL: status.TargetURL,
|
||||
Description: status.Description,
|
||||
}
|
||||
has, err := x.Where("repo_id = ?", repo.ID).And("sha = ?", sha).Desc("created_unix").Get(conds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetCommitStatus[%s, %s]: %v", repo.RepoPath(), sha, err)
|
||||
}
|
||||
if !has {
|
||||
return nil, fmt.Errorf("GetCommitStatus[%s, %s]: not found", repo.RepoPath(), sha)
|
||||
}
|
||||
|
||||
return conds, nil
|
||||
}
|
||||
|
||||
// NewCommitStatusOptions holds options for creating a CommitStatus
|
||||
type NewCommitStatusOptions struct {
|
||||
Repo *Repository
|
||||
Creator *User
|
||||
SHA string
|
||||
CommitStatus *CommitStatus
|
||||
}
|
||||
|
||||
func newCommitStatus(sess *xorm.Session, opts NewCommitStatusOptions) error {
|
||||
opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
|
||||
opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
|
||||
opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
|
||||
opts.CommitStatus.SHA = opts.SHA
|
||||
opts.CommitStatus.CreatorID = opts.Creator.ID
|
||||
|
||||
if opts.Repo == nil {
|
||||
return fmt.Errorf("newCommitStatus[nil, %s]: no repository specified", opts.SHA)
|
||||
}
|
||||
opts.CommitStatus.RepoID = opts.Repo.ID
|
||||
|
||||
if opts.Creator == nil {
|
||||
return fmt.Errorf("newCommitStatus[%s, %s]: no user specified", opts.Repo.RepoPath(), opts.SHA)
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(opts.Repo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository[%s]: %v", opts.Repo.RepoPath(), err)
|
||||
}
|
||||
if _, err := gitRepo.GetCommit(opts.SHA); err != nil {
|
||||
return fmt.Errorf("GetCommit[%s]: %v", opts.SHA, err)
|
||||
}
|
||||
|
||||
// Get the next Status Index
|
||||
var nextIndex int64
|
||||
lastCommitStatus := &CommitStatus{
|
||||
SHA: opts.SHA,
|
||||
RepoID: opts.Repo.ID,
|
||||
}
|
||||
has, err := sess.Desc("index").Limit(1).Get(lastCommitStatus)
|
||||
if err != nil {
|
||||
sess.Rollback()
|
||||
return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
|
||||
}
|
||||
if has {
|
||||
log.Debug("newCommitStatus[%s, %s]: found", opts.Repo.RepoPath(), opts.SHA)
|
||||
nextIndex = lastCommitStatus.Index
|
||||
}
|
||||
opts.CommitStatus.Index = nextIndex + 1
|
||||
log.Debug("newCommitStatus[%s, %s]: %d", opts.Repo.RepoPath(), opts.SHA, opts.CommitStatus.Index)
|
||||
|
||||
// Insert new CommitStatus
|
||||
if _, err = sess.Insert(opts.CommitStatus); err != nil {
|
||||
sess.Rollback()
|
||||
return fmt.Errorf("newCommitStatus[%s, %s]: %v", opts.Repo.RepoPath(), opts.SHA, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCommitStatus creates a new CommitStatus given a bunch of parameters
|
||||
// NOTE: All text-values will be trimmed from whitespaces.
|
||||
// Requires: Repo, Creator, SHA
|
||||
func NewCommitStatus(repo *Repository, creator *User, sha string, status *CommitStatus) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := sess.Begin(); err != nil {
|
||||
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
|
||||
}
|
||||
|
||||
if err := newCommitStatus(sess, NewCommitStatusOptions{
|
||||
Repo: repo,
|
||||
Creator: creator,
|
||||
SHA: sha,
|
||||
CommitStatus: status,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", repo.ID, creator.ID, sha, err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
39
models/status_test.go
Normal file
39
models/status_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Gitea. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetCommitStatuses(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
|
||||
sha1 := "1234123412341234123412341234123412341234"
|
||||
|
||||
statuses, err := GetCommitStatuses(repo1, sha1, 0)
|
||||
assert.NoError(t, err)
|
||||
if assert.Equal(t, 5, len(statuses), "Expected to get 5 statuses") {
|
||||
|
||||
assert.Equal(t, statuses[0].Context, "ci/awesomeness")
|
||||
assert.Equal(t, statuses[0].State, CommitStatusPending)
|
||||
|
||||
assert.Equal(t, statuses[1].Context, "cov/awesomeness")
|
||||
assert.Equal(t, statuses[1].State, CommitStatusWarning)
|
||||
|
||||
assert.Equal(t, statuses[2].Context, "cov/awesomeness")
|
||||
assert.Equal(t, statuses[2].State, CommitStatusSuccess)
|
||||
|
||||
assert.Equal(t, statuses[3].Context, "ci/awesomeness")
|
||||
assert.Equal(t, statuses[3].State, CommitStatusFailure)
|
||||
|
||||
assert.Equal(t, statuses[4].Context, "deploy/awesomeness")
|
||||
assert.Equal(t, statuses[4].State, CommitStatusError)
|
||||
}
|
||||
}
|
||||
23
models/test_fixtures.go
Normal file
23
models/test_fixtures.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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 (
|
||||
"gopkg.in/testfixtures.v2"
|
||||
)
|
||||
|
||||
var fixtures *testfixtures.Context
|
||||
|
||||
// InitFixtures initialize test fixtures for a test database
|
||||
func InitFixtures(helper testfixtures.Helper, dir string) (err error) {
|
||||
testfixtures.SkipDatabaseNameCheck(true)
|
||||
fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadFixtures load fixtures for a test database
|
||||
func LoadFixtures() error {
|
||||
return fixtures.Load()
|
||||
}
|
||||
@@ -101,6 +101,10 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
|
||||
return fmt.Errorf("GetRepositoryByName: %v", err)
|
||||
}
|
||||
|
||||
if err = repo.UpdateSize(); err != nil {
|
||||
log.Error(4, "Failed to update size for repository: %v", err)
|
||||
}
|
||||
|
||||
// Push tags.
|
||||
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
|
||||
if err := CommitRepoAction(CommitRepoActionOptions{
|
||||
|
||||
@@ -163,7 +163,7 @@ func (u *User) UpdateDiffViewStyle(style string) error {
|
||||
func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "full_name":
|
||||
u.FullName = markdown.Sanitizer.Sanitize(u.FullName)
|
||||
u.FullName = markdown.Sanitize(u.FullName)
|
||||
case "created_unix":
|
||||
u.Created = time.Unix(u.CreatedUnix, 0).Local()
|
||||
case "updated_unix":
|
||||
@@ -867,7 +867,7 @@ func updateUser(e Engine, u *User) error {
|
||||
u.Website = base.TruncateString(u.Website, 255)
|
||||
u.Description = base.TruncateString(u.Description, 255)
|
||||
|
||||
u.FullName = markdown.Sanitizer.Sanitize(u.FullName)
|
||||
u.FullName = markdown.Sanitize(u.FullName)
|
||||
_, err := e.Id(u.ID).AllCols().Update(u)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ type AdminEditUserForm struct {
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(255)"`
|
||||
Website string `binding:"ValidUrl;MaxSize(255)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
MaxRepoCreation int
|
||||
Active bool
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
)
|
||||
|
||||
// IsAPIPath if URL is an api path
|
||||
@@ -253,6 +254,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
|
||||
case binding.ERR_ALPHA_DASH_DOT:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
|
||||
case validation.ErrGitRefName:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error")
|
||||
case binding.ERR_SIZE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field))
|
||||
case binding.ERR_MIN_SIZE:
|
||||
|
||||
@@ -31,7 +31,7 @@ type UpdateOrgSettingForm struct {
|
||||
Name string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(255)"`
|
||||
Website string `binding:"ValidUrl;MaxSize(255)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
MaxRepoCreation int
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(255)"`
|
||||
Interval int
|
||||
Website string `binding:"ValidUrl;MaxSize(255)"`
|
||||
Interval string
|
||||
MirrorAddress string
|
||||
Private bool
|
||||
EnablePrune bool
|
||||
@@ -143,7 +143,7 @@ func (f WebhookForm) ChooseEvents() bool {
|
||||
|
||||
// NewWebhookForm form for creating web hook
|
||||
type NewWebhookForm struct {
|
||||
PayloadURL string `binding:"Required;Url"`
|
||||
PayloadURL string `binding:"Required;ValidUrl"`
|
||||
ContentType int `binding:"Required"`
|
||||
Secret string
|
||||
WebhookForm
|
||||
@@ -156,7 +156,7 @@ func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) bin
|
||||
|
||||
// NewSlackHookForm form for creating slack hook
|
||||
type NewSlackHookForm struct {
|
||||
PayloadURL string `binding:"Required;Url"`
|
||||
PayloadURL string `binding:"Required;ValidUrl"`
|
||||
Channel string `binding:"Required"`
|
||||
Username string
|
||||
IconURL string
|
||||
@@ -323,7 +323,7 @@ type EditRepoFileForm struct {
|
||||
CommitSummary string `binding:"MaxSize(100)"`
|
||||
CommitMessage string
|
||||
CommitChoice string `binding:"Required;MaxSize(50)"`
|
||||
NewBranchName string `binding:"AlphaDashDot;MaxSize(100)"`
|
||||
NewBranchName string `binding:"GitRefName;MaxSize(100)"`
|
||||
LastCommit string
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ type UploadRepoFileForm struct {
|
||||
CommitSummary string `binding:"MaxSize(100)"`
|
||||
CommitMessage string
|
||||
CommitChoice string `binding:"Required;MaxSize(50)"`
|
||||
NewBranchName string `binding:"AlphaDashDot;MaxSize(100)"`
|
||||
NewBranchName string `binding:"GitRefName;MaxSize(100)"`
|
||||
Files []string
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ type DeleteRepoFileForm struct {
|
||||
CommitSummary string `binding:"MaxSize(100)"`
|
||||
CommitMessage string
|
||||
CommitChoice string `binding:"Required;MaxSize(50)"`
|
||||
NewBranchName string `binding:"AlphaDashDot;MaxSize(100)"`
|
||||
NewBranchName string `binding:"GitRefName;MaxSize(100)"`
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
|
||||
@@ -103,7 +103,7 @@ type UpdateProfileForm struct {
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
KeepEmailPrivate bool
|
||||
Website string `binding:"Url;MaxSize(255)"`
|
||||
Website string `binding:"ValidUrl;MaxSize(255)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
}
|
||||
|
||||
@@ -163,14 +163,15 @@ func (f *AddOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AddSSHKeyForm form for adding SSH key
|
||||
type AddSSHKeyForm struct {
|
||||
// AddKeyForm form for adding SSH/GPG key
|
||||
type AddKeyForm struct {
|
||||
Type string `binding:"OmitEmpty"`
|
||||
Title string `binding:"Required;MaxSize(50)"`
|
||||
Content string `binding:"Required"`
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
func (f *AddKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ func Contexter() macaron.Handler {
|
||||
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
||||
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
|
||||
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.EnableOpenIDSignIn
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
||||
|
||||
c.Map(ctx)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func composeGoGetImport(owner, repo string) string {
|
||||
func earlyResponseForGoGetMeta(ctx *Context) {
|
||||
ctx.PlainText(200, []byte(com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
|
||||
map[string]string{
|
||||
"GoGetImport": composeGoGetImport(ctx.Params(":username"), ctx.Params(":reponame")),
|
||||
"GoGetImport": composeGoGetImport(ctx.Params(":username"), strings.TrimSuffix(ctx.Params(":reponame"), ".git")),
|
||||
"CloneLink": models.ComposeHTTPSCloneURL(ctx.Params(":username"), ctx.Params(":reponame")),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@ func newRequest(url, method string) *Request {
|
||||
return &Request{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil}
|
||||
}
|
||||
|
||||
// NewRequest returns *Request with specific method
|
||||
func NewRequest(url, method string) *Request {
|
||||
return newRequest(url, method)
|
||||
}
|
||||
|
||||
// Get returns *Request with GET method.
|
||||
func Get(url string) *Request {
|
||||
return newRequest(url, "GET")
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/syncmap"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,10 +43,11 @@ func NewLogger(bufLen int64, mode, config string) {
|
||||
// DelLogger removes loggers that are for the given mode
|
||||
func DelLogger(mode string) error {
|
||||
for _, l := range loggers {
|
||||
if _, ok := l.outputs[mode]; ok {
|
||||
if _, ok := l.outputs.Load(mode); ok {
|
||||
return l.DelLogger(mode)
|
||||
}
|
||||
}
|
||||
|
||||
Trace("Log adapter %s not found, no need to delete", mode)
|
||||
return nil
|
||||
}
|
||||
@@ -174,19 +176,17 @@ type logMsg struct {
|
||||
// it can contain several providers and log message into all providers.
|
||||
type Logger struct {
|
||||
adapter string
|
||||
lock sync.Mutex
|
||||
level int
|
||||
msg chan *logMsg
|
||||
outputs map[string]LoggerInterface
|
||||
outputs syncmap.Map
|
||||
quit chan bool
|
||||
}
|
||||
|
||||
// newLogger initializes and returns a new logger.
|
||||
func newLogger(buffer int64) *Logger {
|
||||
l := &Logger{
|
||||
msg: make(chan *logMsg, buffer),
|
||||
outputs: make(map[string]LoggerInterface),
|
||||
quit: make(chan bool),
|
||||
msg: make(chan *logMsg, buffer),
|
||||
quit: make(chan bool),
|
||||
}
|
||||
go l.StartLogger()
|
||||
return l
|
||||
@@ -194,14 +194,12 @@ func newLogger(buffer int64) *Logger {
|
||||
|
||||
// SetLogger sets new logger instance with given logger adapter and config.
|
||||
func (l *Logger) SetLogger(adapter string, config string) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
if log, ok := adapters[adapter]; ok {
|
||||
lg := log()
|
||||
if err := lg.Init(config); err != nil {
|
||||
return err
|
||||
}
|
||||
l.outputs[adapter] = lg
|
||||
l.outputs.Store(adapter, lg)
|
||||
l.adapter = adapter
|
||||
} else {
|
||||
panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)")
|
||||
@@ -211,11 +209,9 @@ func (l *Logger) SetLogger(adapter string, config string) error {
|
||||
|
||||
// DelLogger removes a logger adapter instance.
|
||||
func (l *Logger) DelLogger(adapter string) error {
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
if lg, ok := l.outputs[adapter]; ok {
|
||||
lg.Destroy()
|
||||
delete(l.outputs, adapter)
|
||||
if lg, ok := l.outputs.Load(adapter); ok {
|
||||
lg.(LoggerInterface).Destroy()
|
||||
l.outputs.Delete(adapter)
|
||||
} else {
|
||||
panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)")
|
||||
}
|
||||
@@ -264,11 +260,12 @@ func (l *Logger) StartLogger() {
|
||||
for {
|
||||
select {
|
||||
case bm := <-l.msg:
|
||||
for _, l := range l.outputs {
|
||||
if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil {
|
||||
l.outputs.Range(func(k, v interface{}) bool {
|
||||
if err := v.(LoggerInterface).WriteMsg(bm.msg, bm.skip, bm.level); err != nil {
|
||||
fmt.Println("ERROR, unable to WriteMsg:", err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
case <-l.quit:
|
||||
return
|
||||
}
|
||||
@@ -277,9 +274,10 @@ func (l *Logger) StartLogger() {
|
||||
|
||||
// Flush flushes all chan data.
|
||||
func (l *Logger) Flush() {
|
||||
for _, l := range l.outputs {
|
||||
l.Flush()
|
||||
}
|
||||
l.outputs.Range(func(k, v interface{}) bool {
|
||||
v.(LoggerInterface).Flush()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Close closes logger, flush all chan data and destroy all adapter instances.
|
||||
@@ -288,19 +286,21 @@ func (l *Logger) Close() {
|
||||
for {
|
||||
if len(l.msg) > 0 {
|
||||
bm := <-l.msg
|
||||
for _, l := range l.outputs {
|
||||
if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil {
|
||||
l.outputs.Range(func(k, v interface{}) bool {
|
||||
if err := v.(LoggerInterface).WriteMsg(bm.msg, bm.skip, bm.level); err != nil {
|
||||
fmt.Println("ERROR, unable to WriteMsg:", err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, l := range l.outputs {
|
||||
l.Flush()
|
||||
l.Destroy()
|
||||
}
|
||||
l.outputs.Range(func(k, v interface{}) bool {
|
||||
v.(LoggerInterface).Flush()
|
||||
v.(LoggerInterface).Destroy()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Trace records trace log
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
@@ -29,24 +29,6 @@ const (
|
||||
IssueNameStyleAlphanumeric = "alphanumeric"
|
||||
)
|
||||
|
||||
// Sanitizer markdown sanitizer
|
||||
var Sanitizer = bluemonday.UGCPolicy()
|
||||
|
||||
// BuildSanitizer initializes sanitizer with allowed attributes based on settings.
|
||||
// This function should only be called once during entire application lifecycle.
|
||||
func BuildSanitizer() {
|
||||
// Normal markdown-stuff
|
||||
Sanitizer.AllowAttrs("class").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).OnElements("code", "div", "ul", "ol", "dl")
|
||||
|
||||
// Checkboxes
|
||||
Sanitizer.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||
Sanitizer.AllowAttrs("checked", "disabled").OnElements("input")
|
||||
Sanitizer.AllowNoAttrs().OnElements("label")
|
||||
|
||||
// Custom URL-Schemes
|
||||
Sanitizer.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
}
|
||||
|
||||
// IsMarkdownFile reports whether name looks like a Markdown file
|
||||
// based on its extension.
|
||||
func IsMarkdownFile(name string) bool {
|
||||
@@ -59,18 +41,6 @@ func IsMarkdownFile(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsReadmeFile reports whether name looks like a README file
|
||||
// based on its name.
|
||||
func IsReadmeFile(name string) bool {
|
||||
name = strings.ToLower(name)
|
||||
if len(name) < 6 {
|
||||
return false
|
||||
} else if len(name) == 6 {
|
||||
return name == "readme"
|
||||
}
|
||||
return name[:7] == "readme."
|
||||
}
|
||||
|
||||
var (
|
||||
// MentionPattern matches string that mentions someone, e.g. @Unknwon
|
||||
MentionPattern = regexp.MustCompile(`(\s|^|\W)@[0-9a-zA-Z-_\.]+`)
|
||||
@@ -175,11 +145,14 @@ func (r *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
switch {
|
||||
case bytes.HasPrefix(text, []byte(prefix+"[ ] ")):
|
||||
text = append([]byte(`<div class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></div>`), text[3+len(prefix):]...)
|
||||
if prefix != "" {
|
||||
text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
|
||||
}
|
||||
case bytes.HasPrefix(text, []byte(prefix+"[x] ")):
|
||||
text = append([]byte(`<div class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></div>`), text[3+len(prefix):]...)
|
||||
}
|
||||
if prefix != "" {
|
||||
text = bytes.Replace(text, []byte("</p>"), []byte{}, 1)
|
||||
if prefix != "" {
|
||||
text = bytes.Replace(text, []byte(prefix), []byte{}, 1)
|
||||
}
|
||||
}
|
||||
r.Renderer.ListItem(out, text, flags)
|
||||
}
|
||||
@@ -657,10 +630,8 @@ OUTER_LOOP:
|
||||
// Copy the token to the output verbatim
|
||||
buf.Write(RenderShortLinks([]byte(token.String()), urlPrefix, true, isWikiMarkdown))
|
||||
|
||||
if token.Type == html.StartTagToken {
|
||||
if !com.IsSliceContainsStr(noEndTags, token.Data) {
|
||||
stackNum++
|
||||
}
|
||||
if token.Type == html.StartTagToken && !com.IsSliceContainsStr(noEndTags, token.Data) {
|
||||
stackNum++
|
||||
}
|
||||
|
||||
// If this is the close tag to the outer-most, we are done
|
||||
@@ -675,8 +646,8 @@ OUTER_LOOP:
|
||||
continue OUTER_LOOP
|
||||
}
|
||||
|
||||
if !com.IsSliceContainsStr(noEndTags, token.Data) {
|
||||
startTags = append(startTags, token.Data)
|
||||
if !com.IsSliceContainsStr(noEndTags, tagName) {
|
||||
startTags = append(startTags, tagName)
|
||||
}
|
||||
|
||||
case html.EndTagToken:
|
||||
@@ -708,7 +679,7 @@ func render(rawBytes []byte, urlPrefix string, metas map[string]string, isWikiMa
|
||||
urlPrefix = strings.Replace(urlPrefix, " ", "+", -1)
|
||||
result := RenderRaw(rawBytes, urlPrefix, isWikiMarkdown)
|
||||
result = PostProcess(result, urlPrefix, metas, isWikiMarkdown)
|
||||
result = Sanitizer.SanitizeBytes(result)
|
||||
result = SanitizeBytes(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -726,3 +697,31 @@ func RenderString(raw, urlPrefix string, metas map[string]string) string {
|
||||
func RenderWiki(rawBytes []byte, urlPrefix string, metas map[string]string) string {
|
||||
return string(render(rawBytes, urlPrefix, metas, true))
|
||||
}
|
||||
|
||||
var (
|
||||
// MarkupName describes markup's name
|
||||
MarkupName = "markdown"
|
||||
)
|
||||
|
||||
func init() {
|
||||
markup.RegisterParser(Parser{})
|
||||
}
|
||||
|
||||
// Parser implements markup.Parser
|
||||
type Parser struct {
|
||||
}
|
||||
|
||||
// Name implements markup.Parser
|
||||
func (Parser) Name() string {
|
||||
return MarkupName
|
||||
}
|
||||
|
||||
// Extensions implements markup.Parser
|
||||
func (Parser) Extensions() []string {
|
||||
return setting.Markdown.FileExtensions
|
||||
}
|
||||
|
||||
// Render implements markup.Parser
|
||||
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
|
||||
return render(rawBytes, urlPrefix, metas, isWiki)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// 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 markdown_test
|
||||
|
||||
import (
|
||||
@@ -586,31 +590,6 @@ func TestMisc_IsMarkdownFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMisc_IsReadmeFile(t *testing.T) {
|
||||
trueTestCases := []string{
|
||||
"readme",
|
||||
"README",
|
||||
"readME.mdown",
|
||||
"README.md",
|
||||
}
|
||||
falseTestCases := []string{
|
||||
"test.md",
|
||||
"wow.MARKDOWN",
|
||||
"LOL.mDoWn",
|
||||
"test",
|
||||
"abcdefg",
|
||||
"abcdefghijklmnopqrstuvwxyz",
|
||||
"test.md.test",
|
||||
}
|
||||
|
||||
for _, testCase := range trueTestCases {
|
||||
assert.True(t, IsReadmeFile(testCase))
|
||||
}
|
||||
for _, testCase := range falseTestCases {
|
||||
assert.False(t, IsReadmeFile(testCase))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMisc_IsSameDomain(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
58
modules/markdown/sanitizer.go
Normal file
58
modules/markdown/sanitizer.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// 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 markdown
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
)
|
||||
|
||||
// Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
|
||||
// any modification to the underlying policies once it's been created.
|
||||
type Sanitizer struct {
|
||||
policy *bluemonday.Policy
|
||||
init sync.Once
|
||||
}
|
||||
|
||||
var sanitizer = &Sanitizer{}
|
||||
|
||||
// NewSanitizer initializes sanitizer with allowed attributes based on settings.
|
||||
// Multiple calls to this function will only create one instance of Sanitizer during
|
||||
// entire application lifecycle.
|
||||
func NewSanitizer() {
|
||||
sanitizer.init.Do(func() {
|
||||
sanitizer.policy = bluemonday.UGCPolicy()
|
||||
// We only want to allow HighlightJS specific classes for code blocks
|
||||
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code")
|
||||
|
||||
// Checkboxes
|
||||
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
|
||||
|
||||
// Custom URL-Schemes
|
||||
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
})
|
||||
}
|
||||
|
||||
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
||||
func Sanitize(s string) string {
|
||||
NewSanitizer()
|
||||
return sanitizer.policy.Sanitize(s)
|
||||
}
|
||||
|
||||
// SanitizeBytes takes a []byte slice that contains a HTML fragment or document and applies policy whitelist.
|
||||
func SanitizeBytes(b []byte) []byte {
|
||||
if len(b) == 0 {
|
||||
// nothing to sanitize
|
||||
return b
|
||||
}
|
||||
NewSanitizer()
|
||||
return sanitizer.policy.SanitizeBytes(b)
|
||||
}
|
||||
44
modules/markdown/sanitizer_test.go
Normal file
44
modules/markdown/sanitizer_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// 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 markdown
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Sanitizer(t *testing.T) {
|
||||
NewSanitizer()
|
||||
testCases := []string{
|
||||
// Regular
|
||||
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`, `<a href="http://www.google.com" rel="nofollow">Google</a>`,
|
||||
|
||||
// Code highlighting class
|
||||
`<code class="random string"></code>`, `<code></code>`,
|
||||
`<code class="language-random ui tab active menu attached animating sidebar following bar center"></code>`, `<code></code>`,
|
||||
`<code class="language-go"></code>`, `<code class="language-go"></code>`,
|
||||
|
||||
// Input checkbox
|
||||
`<input type="hidden">`, ``,
|
||||
`<input type="checkbox">`, `<input type="checkbox">`,
|
||||
`<input checked disabled autofocus>`, `<input checked="" disabled="">`,
|
||||
|
||||
// Code highlight injection
|
||||
`<code class="language-random ui tab active menu attached animating sidebar following bar center"></code>`, `<code></code>`,
|
||||
`<code class="language-lol ui tab active menu attached animating sidebar following bar center">
|
||||
<code class="language-lol ui container input huge basic segment center"> </code>
|
||||
<img src="https://try.gogs.io/img/favicon.png" width="200" height="200">
|
||||
<code class="language-lol ui container input massive basic segment">Hello there! Something has gone wrong, we are working on it.</code>
|
||||
<code class="language-lol ui container input huge basic segment">In the meantime, play a game with us at <a href="http://example.com/">example.com</a>.</code>
|
||||
</code>`, "<code>\n<code>\u00a0</code>\n<img src=\"https://try.gogs.io/img/favicon.png\" width=\"200\" height=\"200\">\n<code>Hello there! Something has gone wrong, we are working on it.</code>\n<code>In the meantime, play a game with us at\u00a0<a href=\"http://example.com/\" rel=\"nofollow\">example.com</a>.</code>\n</code>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
assert.Equal(t, testCases[i+1], Sanitize(testCases[i]))
|
||||
assert.Equal(t, testCases[i+1], string(SanitizeBytes([]byte(testCases[i]))))
|
||||
}
|
||||
}
|
||||
72
modules/markup/markup.go
Normal file
72
modules/markup/markup.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 markup
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parser defines an interface for parsering markup file to HTML
|
||||
type Parser interface {
|
||||
Name() string // markup format name
|
||||
Extensions() []string
|
||||
Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte
|
||||
}
|
||||
|
||||
var (
|
||||
parsers = make(map[string]Parser)
|
||||
)
|
||||
|
||||
// RegisterParser registers a new markup file parser
|
||||
func RegisterParser(parser Parser) {
|
||||
for _, ext := range parser.Extensions() {
|
||||
parsers[strings.ToLower(ext)] = parser
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders markup file to HTML with all specific handling stuff.
|
||||
func Render(filename string, rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
return render(filename, rawBytes, urlPrefix, metas, false)
|
||||
}
|
||||
|
||||
func render(filename string, rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
|
||||
extension := strings.ToLower(filepath.Ext(filename))
|
||||
if parser, ok := parsers[extension]; ok {
|
||||
return parser.Render(rawBytes, urlPrefix, metas, isWiki)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderString renders Markdown to HTML with special links and returns string type.
|
||||
func RenderString(filename string, raw, urlPrefix string, metas map[string]string) string {
|
||||
return string(render(filename, []byte(raw), urlPrefix, metas, false))
|
||||
}
|
||||
|
||||
// RenderWiki renders markdown wiki page to HTML and return HTML string
|
||||
func RenderWiki(filename string, rawBytes []byte, urlPrefix string, metas map[string]string) string {
|
||||
return string(render(filename, rawBytes, urlPrefix, metas, true))
|
||||
}
|
||||
|
||||
// Type returns if markup format via the filename
|
||||
func Type(filename string) string {
|
||||
extension := strings.ToLower(filepath.Ext(filename))
|
||||
if parser, ok := parsers[extension]; ok {
|
||||
return parser.Name()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsReadmeFile reports whether name looks like a README file
|
||||
// based on its name.
|
||||
func IsReadmeFile(name string) bool {
|
||||
name = strings.ToLower(name)
|
||||
if len(name) < 6 {
|
||||
return false
|
||||
} else if len(name) == 6 {
|
||||
return name == "readme"
|
||||
}
|
||||
return name[:7] == "readme."
|
||||
}
|
||||
36
modules/markup/markup_test.go
Normal file
36
modules/markup/markup_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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 markup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMisc_IsReadmeFile(t *testing.T) {
|
||||
trueTestCases := []string{
|
||||
"readme",
|
||||
"README",
|
||||
"readME.mdown",
|
||||
"README.md",
|
||||
}
|
||||
falseTestCases := []string{
|
||||
"test.md",
|
||||
"wow.MARKDOWN",
|
||||
"LOL.mDoWn",
|
||||
"test",
|
||||
"abcdefg",
|
||||
"abcdefghijklmnopqrstuvwxyz",
|
||||
"test.md.test",
|
||||
}
|
||||
|
||||
for _, testCase := range trueTestCases {
|
||||
assert.True(t, IsReadmeFile(testCase))
|
||||
}
|
||||
for _, testCase := range falseTestCases {
|
||||
assert.False(t, IsReadmeFile(testCase))
|
||||
}
|
||||
}
|
||||
53
modules/private/internal.go
Normal file
53
modules/private/internal.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package private
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
func newRequest(url, method string) *httplib.Request {
|
||||
return httplib.NewRequest(url, method).Header("Authorization",
|
||||
fmt.Sprintf("Bearer %s", setting.InternalToken))
|
||||
}
|
||||
|
||||
// Response internal request response
|
||||
type Response struct {
|
||||
Err string `json:"err"`
|
||||
}
|
||||
|
||||
func decodeJSONError(resp *http.Response) *Response {
|
||||
var res Response
|
||||
err := json.NewDecoder(resp.Body).Decode(&res)
|
||||
if err != nil {
|
||||
res.Err = err.Error()
|
||||
}
|
||||
return &res
|
||||
}
|
||||
|
||||
// UpdatePublicKeyUpdated update publick key updates
|
||||
func UpdatePublicKeyUpdated(keyID int64) error {
|
||||
// Ask for running deliver hook and test pull request tasks.
|
||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update", keyID)
|
||||
log.GitLogger.Trace("UpdatePublicKeyUpdated: %s", reqURL)
|
||||
|
||||
resp, err := newRequest(reqURL, "POST").SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).Response()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// All 2XX status codes are accepted and others will return an error
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/user"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
_ "github.com/go-macaron/cache/memcache" // memcache plugin for cache
|
||||
_ "github.com/go-macaron/cache/redis"
|
||||
"github.com/go-macaron/session"
|
||||
@@ -121,12 +122,6 @@ var (
|
||||
MinPasswordLength int
|
||||
ImportLocalPaths bool
|
||||
|
||||
// OpenID settings
|
||||
EnableOpenIDSignIn bool
|
||||
EnableOpenIDSignUp bool
|
||||
OpenIDWhitelist []*regexp.Regexp
|
||||
OpenIDBlacklist []*regexp.Regexp
|
||||
|
||||
// Database settings
|
||||
UseSQLite3 bool
|
||||
UseMySQL bool
|
||||
@@ -232,6 +227,11 @@ var (
|
||||
User struct {
|
||||
RepoPagingNum int
|
||||
} `ini:"ui.user"`
|
||||
Meta struct {
|
||||
Author string
|
||||
Description string
|
||||
Keywords string
|
||||
} `ini:"ui.meta"`
|
||||
}{
|
||||
ExplorePagingNum: 20,
|
||||
IssuePagingNum: 10,
|
||||
@@ -254,6 +254,15 @@ var (
|
||||
}{
|
||||
RepoPagingNum: 15,
|
||||
},
|
||||
Meta: struct {
|
||||
Author string
|
||||
Description string
|
||||
Keywords string
|
||||
}{
|
||||
Author: "Gitea - Git with a cup of tea",
|
||||
Description: "Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go",
|
||||
Keywords: "go,git,self-hosted,gitea",
|
||||
},
|
||||
}
|
||||
|
||||
// Markdown settings
|
||||
@@ -409,10 +418,9 @@ var (
|
||||
}
|
||||
|
||||
// Mirror settings
|
||||
Mirror = struct {
|
||||
DefaultInterval int
|
||||
}{
|
||||
DefaultInterval: 8,
|
||||
Mirror struct {
|
||||
DefaultInterval time.Duration
|
||||
MinInterval time.Duration
|
||||
}
|
||||
|
||||
// API settings
|
||||
@@ -435,14 +443,15 @@ var (
|
||||
ShowFooterTemplateLoadTime bool
|
||||
|
||||
// Global setting objects
|
||||
Cfg *ini.File
|
||||
CustomPath string // Custom directory path
|
||||
CustomConf string
|
||||
CustomPID string
|
||||
ProdMode bool
|
||||
RunUser string
|
||||
IsWindows bool
|
||||
HasRobotsTxt bool
|
||||
Cfg *ini.File
|
||||
CustomPath string // Custom directory path
|
||||
CustomConf string
|
||||
CustomPID string
|
||||
ProdMode bool
|
||||
RunUser string
|
||||
IsWindows bool
|
||||
HasRobotsTxt bool
|
||||
InternalToken string // internal access token
|
||||
)
|
||||
|
||||
// DateLang transforms standard language locale name to corresponding value in datetime plugin.
|
||||
@@ -757,22 +766,41 @@ please consider changing to GITEA_CUSTOM`)
|
||||
ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
|
||||
MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6)
|
||||
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
|
||||
|
||||
sec = Cfg.Section("openid")
|
||||
EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(true)
|
||||
EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(true)
|
||||
pats := sec.Key("WHITELISTED_URIS").Strings(" ")
|
||||
if len(pats) != 0 {
|
||||
OpenIDWhitelist = make([]*regexp.Regexp, len(pats))
|
||||
for i, p := range pats {
|
||||
OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)
|
||||
InternalToken = sec.Key("INTERNAL_TOKEN").String()
|
||||
if len(InternalToken) == 0 {
|
||||
secretBytes := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, secretBytes)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Error reading random bytes: %v", err)
|
||||
}
|
||||
}
|
||||
pats = sec.Key("BLACKLISTED_URIS").Strings(" ")
|
||||
if len(pats) != 0 {
|
||||
OpenIDBlacklist = make([]*regexp.Regexp, len(pats))
|
||||
for i, p := range pats {
|
||||
OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)
|
||||
|
||||
secretKey := base64.RawURLEncoding.EncodeToString(secretBytes)
|
||||
|
||||
now := time.Now()
|
||||
InternalToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"nbf": now.Unix(),
|
||||
}).SignedString([]byte(secretKey))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(4, "Error generate internal token: %v", err)
|
||||
}
|
||||
|
||||
// Save secret
|
||||
cfgSave := ini.Empty()
|
||||
if com.IsFile(CustomConf) {
|
||||
// Keeps custom settings if there is already something.
|
||||
if err := cfgSave.Append(CustomConf); err != nil {
|
||||
log.Error(4, "Failed to load custom conf '%s': %v", CustomConf, err)
|
||||
}
|
||||
}
|
||||
|
||||
cfgSave.Section("security").Key("INTERNAL_TOKEN").SetValue(InternalToken)
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
|
||||
log.Fatal(4, "Failed to create '%s': %v", CustomConf, err)
|
||||
}
|
||||
if err := cfgSave.SaveTo(CustomConf); err != nil {
|
||||
log.Fatal(4, "Error saving generated JWT Secret to custom config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,14 +924,20 @@ please consider changing to GITEA_CUSTOM`)
|
||||
log.Fatal(4, "Failed to map Cron settings: %v", err)
|
||||
} else if err = Cfg.Section("git").MapTo(&Git); err != nil {
|
||||
log.Fatal(4, "Failed to map Git settings: %v", err)
|
||||
} else if err = Cfg.Section("mirror").MapTo(&Mirror); err != nil {
|
||||
log.Fatal(4, "Failed to map Mirror settings: %v", err)
|
||||
} else if err = Cfg.Section("api").MapTo(&API); err != nil {
|
||||
log.Fatal(4, "Failed to map API settings: %v", err)
|
||||
}
|
||||
|
||||
if Mirror.DefaultInterval <= 0 {
|
||||
Mirror.DefaultInterval = 24
|
||||
sec = Cfg.Section("mirror")
|
||||
Mirror.MinInterval = sec.Key("MIN_INTERVAL").MustDuration(10 * time.Minute)
|
||||
Mirror.DefaultInterval = sec.Key("DEFAULT_INTERVAL").MustDuration(8 * time.Hour)
|
||||
if Mirror.MinInterval.Minutes() < 1 {
|
||||
log.Warn("Mirror.MinInterval is too low")
|
||||
Mirror.MinInterval = 1 * time.Minute
|
||||
}
|
||||
if Mirror.DefaultInterval < Mirror.MinInterval {
|
||||
log.Warn("Mirror.DefaultInterval is less than Mirror.MinInterval")
|
||||
Mirror.DefaultInterval = time.Hour * 8
|
||||
}
|
||||
|
||||
Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
|
||||
@@ -939,6 +973,12 @@ var Service struct {
|
||||
EnableCaptcha bool
|
||||
DefaultKeepEmailPrivate bool
|
||||
NoReplyAddress string
|
||||
|
||||
// OpenID settings
|
||||
EnableOpenIDSignIn bool
|
||||
EnableOpenIDSignUp bool
|
||||
OpenIDWhitelist []*regexp.Regexp
|
||||
OpenIDBlacklist []*regexp.Regexp
|
||||
}
|
||||
|
||||
func newService() {
|
||||
@@ -953,6 +993,25 @@ func newService() {
|
||||
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool()
|
||||
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
||||
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")
|
||||
|
||||
sec = Cfg.Section("openid")
|
||||
Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(true)
|
||||
Service.EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(!Service.DisableRegistration)
|
||||
pats := sec.Key("WHITELISTED_URIS").Strings(" ")
|
||||
if len(pats) != 0 {
|
||||
Service.OpenIDWhitelist = make([]*regexp.Regexp, len(pats))
|
||||
for i, p := range pats {
|
||||
Service.OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)
|
||||
}
|
||||
}
|
||||
pats = sec.Key("BLACKLISTED_URIS").Strings(" ")
|
||||
if len(pats) != 0 {
|
||||
Service.OpenIDBlacklist = make([]*regexp.Regexp, len(pats))
|
||||
for i, p := range pats {
|
||||
Service.OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user