Compare commits

...

74 Commits

Author SHA1 Message Date
Kim "BKC" Carlbäcker
b0c7a3bb5c Fix stuff 2017-04-28 21:36:35 +02:00
Kim "BKC" Carlbäcker
77ef39112d Update changelog 2017-04-28 20:11:34 +02:00
Kim "BKC" Carlbäcker
8c708202b6 Add changelog 2017-04-28 20:09:41 +02:00
Kim "BKC" Carlbäcker
3e61f49da4 Restore-command added
- cleanup cmd.Dump, with deprication notice
- don't prefic cmd-commands
2017-04-28 20:09:13 +02:00
Lunny Xiao
fca7ddc6ea improve integration test to resue models/fixtures and store git repos with tests (#1627)
* improve integration test to resue models/fixtures and store git repos with source

* use gitea-integration-meta dir instead .tar.gz and cleanup every before every test

* fix import package name
2017-04-28 21:23:28 +08:00
Antoine GIRARD
431b26f6d7 Handle display of GPG key without end date (#1628) 2017-04-28 08:44:58 +08:00
Lunny Xiao
6853bf323a fix #1521 (#1621) 2017-04-27 20:15:56 +08:00
Lunny Xiao
e3c2963222 remove unused vendor packages (#1620) 2017-04-27 18:10:34 +08:00
Jonas Östanbäck
e4a0a04829 Add create_at and updated_at in PR json (#1616)
* Move new fields to object creation (use reference)
 * Vendor: Update SDK
2017-04-27 17:29:46 +08:00
Antoine GIRARD
8371f94d06 Rework SSH key management UI to add GPG (#1293)
* Rework SSH key management UI to add GPG

* Add more detail to gpg key display

* Update CHANGELOG.md

* Implement deletion UI

* Implement adding gpg UI

* Various fixes

- Fix duplicate entry in locale
- Re-generate hash before verification since they are consumed

* Add missing translation

* Split template

* Catch not found/verified email error
2017-04-26 21:10:43 +08:00
Sandro Santilli
b7da5a6cb7 Fix service description in Debian init file (#1538) 2017-04-25 19:30:42 +02:00
Ethan Koenig
c58708d3ee Integration test framework (#1290)
* Integration test framework

* udpate drone sign

* Formatting fixes and move router.go to routers/

* update sign for drone
2017-04-25 15:24:51 +08:00
Lunny Xiao
3012971e92 fix bug on status API (#1533) 2017-04-24 22:33:54 +08:00
Antoine GIRARD
eb1075dd4c Check if missing/modified/unused deps in vendor and fix errors (#1468)
* Check if missing deps in vendor

This will catch import from other repos. (maybe by auto-import)

* Remove github.com/smartystreets unused deps

* Remove github.com/boltdb/bolt unused dep

* Fetch github.com/go-xorm/core + sync gopkg.in/ldap.v2/ldap.go

* Auto-install govendor + reinstall github.com/boltdb/bolt needed by vendor/github.com/blevesearch/bleve/index/store/boltdb

* Update go-xorm/xorm to a5cb21 in vendor.json

* Use a custom repo for implementing change to bolt dep.

* Switching bolt to github.com/go-gitea/bolt

* Switching bolt to github.com/go-gitea/bolt (fork version)

* change the drone sign
2017-04-24 22:31:46 +08:00
Morlinest
fcc7cdab11 Fix markdown rendering (#1530) 2017-04-24 12:18:36 +08:00
Morlinest
5b8fe1e181 Do not show empty collaborators segment (#1531) 2017-04-23 08:55:02 +08:00
Kim "BKC" Carlbäcker
4bea219128 Status-API (#1332) 2017-04-21 19:32:31 +08:00
Lunny Xiao
52627032bc Add markup package to prepare for org markup format (#1493) 2017-04-21 09:01:08 +02:00
Lunny Xiao
f0db3da713 fix go get sub package and add domain on installation to let go get work defaultly (#1518)
* fix go get sub package and add domain on installation to let go get work defaultly

* fix import sequence

* fix .git problem
2017-04-21 10:43:29 +08:00
Lunny Xiao
42072783c9 support health check via / and fix #969 (#1520) 2017-04-20 20:14:21 +08:00
Lauris BH
bb14c97d40 Fix empty file download (#1506) 2017-04-20 10:38:56 +08:00
Bo-Yi Wu
fa2a513c62 feat: add download count field and unit testing for attachment. (#1512)
* feat: add download count field and unit testing.

* fix: unit testing

* refactor: improve testing.

* fix: update comment

* add default value.

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2017-04-20 10:31:31 +08:00
Lunny Xiao
a2d365c81f fix #1501 ssh hangs caused by #1461 (#1513) 2017-04-19 19:16:36 +08:00
Jonas
f995bcc87a Add repo mirror sync API endpoint (#1508)
* API: Add repo mirror sync

* Correct error message

* Change http status to 200
2017-04-19 19:09:49 +08:00
Lunny Xiao
2eeae84cbd Add internal routes for ssh hook comands (#1471)
* add internal routes for ssh hook comands

* fix lint

* add comment on why package named private not internal but the route name is internal

* add comment above package private why package named private not internal but the route name is internal

* remove exp time on internal access

* move routes from /internal to /api/internal

* add comment and defer on UpdatePublicKeyUpdated
2017-04-19 11:45:01 +08:00
Lauris BH
f42ec6120e Better URL validation (#1507)
* Add correct git branch name validation

* Change git refname validation error constant name

* Implement URL validation based on GoLang url.Parse method

* Backward compatibility with older Go compiler

* Add git reference name validation unit tests

* Remove unused variable in unit test

* Implement URL validation based on GoLang url.Parse method

* Backward compatibility with older Go compiler

* Add url validation unit tests
2017-04-19 11:02:20 +08:00
Antoine GIRARD
941281ae12 Use MAINTAINERS file in repository in CONTRIBUTING (#1489)
* Use MAINTAINERS file in repository because teams are not publicly available

* Use relative link
2017-04-18 08:56:28 +08:00
Antoine GIRARD
c764a542c0 Adding @sapk to MAINTAINERS (#1497) 2017-04-16 20:51:38 +08:00
Antoine GIRARD
cf91cfb993 Migrate WatchInfo struct to api (#1492)
* Remove WatchInfo and link to api struct

* Add vendor temporary update

* Return to gitea vendor source
2017-04-16 20:51:04 +08:00
silverwind
237270ef50 Orgs: fix org page title when full name is not defined (#1495) 2017-04-15 10:02:46 +08:00
Kim "BKC" Carlbäcker
d409d3ab57 Sanitation fix from Gogs (#1461)
* Santiation fix from Gogs

* Linting

* Fix build-errors

* still not working

* Fix all the things!

* gofmt

* Add code-injection checks
2017-04-13 10:52:24 +08:00
Michael de Wit
21290d4e80 Optimize and fix autolink function (#1442) (#1444)
* Optimize and fix autolink function (#1442)

* Fix bug in autolink recursive fallback function
2017-04-12 16:01:08 +08:00
Bo-Yi Wu
22295944df refactor: show command help message. (#1486) 2017-04-12 15:44:54 +08:00
Jonas
be6edaddcb Repo size in admin panel (#1482)
* Implementation of the feature to view repository size in admin panel
 * Move GetRepoSize to git module
 * Repository.RepoSize -> Repository.Size
 * RepoSize -> Size in template
 * Redo a few bits and pieces
 * Update size when syncing mirror or forking
 * Remove GetRepoSize
 * Changed fatal errors to error message

* Copy migration code from Gogs

* make fmt
2017-04-11 21:30:15 +08:00
Jonas
54f0293f0a Mirror sync interval specified as duration string (#1407)
* Sync interval specifed as duration string

* Changed mirror interval text

* make fmt

* Add MinInterval for mirror sync

* Use duration internally

* Changed min default to 10m

* make fmt

* Incorrect default

* Removed defaults in MustDuration()

* Add Mirror interval migration

* Default values corrected

* Use transaction during migration

* Change http 500 to page with error message

* Cleanup session.commit()
2017-04-08 23:27:26 +08:00
Mura Li
edbb9eefd6 Fix race when running commands with timeout (#1465)
Update vendored module code.gitea.io/git
2017-04-07 19:23:39 -07:00
Lunny Xiao
5c0bee9b20 refactor update ssh key use time (#1466) 2017-04-07 17:40:38 -07:00
Antoine GIRARD
d9db188274 Fix #1418 (#1456) 2017-04-07 22:50:40 +08:00
Jonas
e0df611cbc Autofocus on 2fa passcode fields (#1460)
* Autofocus on 2fa passcode field

* Autofocus on 2fa scratch code field
2017-04-07 06:07:37 -07:00
Lunny Xiao
5acfc7c4bc fix migrate failed and org dashboard failed on MSSQL database (#1448) 2017-04-06 18:47:25 -07:00
Lunny Xiao
cf6699fb4f fix gpg API panic when no verification (#1451)
* fix gpg API panic when no verification

* fix if condition
2017-04-06 18:40:05 -07:00
Morlinest
cbeeaa1b11 Fix and simplify repo branches UI (#1435) 2017-04-06 23:33:57 +08:00
Sandro Santilli
410af6971b Set VERSION from git once, in a variable (#1447) 2017-04-06 23:32:28 +08:00
Damien Gaignon
a78a0266c4 Correct broken unaligned load/store in armv5 (#1355)
Update vendor github.com/boltdb/bolt to take care of the issue #1354.
2017-04-05 18:50:33 -07:00
Patrick G
d800305b34 Commit search appearence fixes (#1254)
* Fix button text to be consisstent with rest of UI

* Make all commits checkbox look nicer
2017-04-05 18:32:59 -07:00
Bo-Yi Wu
0cee52e0d3 fix: remove str2html from org full name (#1360)
* fix: remove str2html for security issue.

* fix: update
2017-04-05 18:31:31 -07:00
Jonas
e7493e953f Corrected Mirror.NextUpdate not set (#1388) 2017-04-05 17:56:45 -07:00
Mura Li
2d1efcc270 Add GOFLAGS and EXTRA_GOFLAGS (#1438) 2017-04-05 16:10:32 +08:00
Mura Li
b746757209 Remove unused mutex field (#1440) 2017-04-04 01:54:02 -07:00
Mura Li
93c25c9a35 Fix races in the log module by using syncmap (#1421) 2017-04-03 10:22:26 +08:00
Kuba Tyszko
5d6b71fdbb fixes pull request hanging indefinitely when it contains normal and LFS file pointers (#1425) 2017-04-03 10:21:09 +08:00
Morlinest
21fd3da6f5 Fix disabled fields in repo settings UI (#1431) 2017-04-03 10:19:51 +08:00
Andrey Nering
37a34c1a28 Merge pull request #1410 from andreynering/notification/issue-watch
[Notifications Step 6] Per issue/PR watch/unwatch
2017-04-01 15:12:24 -03:00
Andrey Nering
f6e5ce65b2 Improve tests a little 2017-04-01 10:05:58 -03:00
Andrey Nering
e5c56fe30d Code style fixes 2017-04-01 09:58:20 -03:00
agrn
88112a5324 Fix "Error: No issue number specified" when pushing (#1393)
* Fix "Error: No issue number specified"  when pushing

* update pr
2017-03-31 18:05:39 -07:00
Jonas
095e1f5155 Support for custom html meta (#1423)
* Support for custom html meta

* Changed html meta description default
2017-03-31 18:03:01 -07:00
Mura Li
6a39250579 Add length check for the return string (#1420)
* Add length check for the return string

* Differentiate error paths and logging messages

* Rectify error message
2017-03-31 16:42:23 +08:00
Andrey Nering
fac7a6fecf Merge pull request #1390 from Morlinest/commit-graph-UI
Use whole button (commit graph) as link
2017-03-30 20:29:57 -03:00
Andrey Nering
e6781d5488 Add unit tests for issue_watch 2017-03-30 20:20:08 -03:00
Andrey Nering
a90ffffb1a Use variables for times 2017-03-30 19:14:16 -03:00
Andrey Nering
18952c40f8 Add copyright headers 2017-03-30 19:11:58 -03:00
Andrey Nering
4b284f814c UI and translation improvements 2017-03-30 19:10:30 -03:00
Morlinest
4fa691c4dd Make issue in commit graph "clickable" (#1392) 2017-03-30 18:05:38 +08:00
Antoine GIRARD
912b340d0d Simplify settings pages with item list (#1389)
* Remove point column on ssh key setting page

* Remove un-used css

* Some cleaning

* Use octicon-key
2017-03-30 09:02:37 +08:00
Andrey Nering
caed86fc6e Fix lint 2017-03-29 21:18:28 -03:00
Andrey Nering
e4a33ed4d0 Add octicons to watch/unwatch buttons 2017-03-29 21:08:46 -03:00
Andrey Nering
cb362513f0 Add updated_unix column on issue_watch 2017-03-29 20:59:28 -03:00
Andrey Nering
aa6e949b3d Consider issue_watchers while sending notifications 2017-03-29 20:54:57 -03:00
Andrey Nering
b674460748 Add watch button on issue 2017-03-29 20:31:47 -03:00
Andrey Nering
a0d0de7233 Create issue_watch table 2017-03-29 19:55:40 -03:00
Sandro Santilli
129b0d6a4b Allow ENABLE_OPENID_SIGNUP to depend on DISABLE_REGISTRATION (#1369)
* Allow ENABLE_OPENID_SIGNUP to depend on DISABLE_REGISTRATION

Omit the configuration variable (the default) to be dependent.
Fixes #1363

* Move OpenID settings under Service object

* Show OpenID SignUp and SignIn status in admin panel / configuration
2017-03-29 18:57:43 +08:00
Bo-Yi Wu
08f7fded3c refactor: remove workaround after the golang 1.7 release. (#1349)
* refactor: remove workaround after the golang 1.7 release.

* remove unused import.

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* refactor: Add remove function.

* refactor: rename

* refactor: update build flag
2017-03-29 10:05:23 +08:00
Peter Žeby
d0298ea2fb Use whole button as link 2017-03-27 12:46:46 +02:00
244 changed files with 7844 additions and 5024 deletions

View File

@@ -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 ]

View File

@@ -1 +1 @@
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QKICAgICAgLSBtYWtlIGJ1aWxkCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHRlc3QtbXlzcWw6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHRlc3QtbXlzcWwKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1wZ3NxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1wZ3NxbAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBzdGF0aWM6CiAgICBpbWFnZToga2FyYWxhYmUveGdvLWxhdGVzdDpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhIHNxbGl0ZQogICAgICBHT1BBVEg6IC9zcnYvYXBwCiAgICBjb21tYW5kczoKICAgICAgLSBtYWtlIHJlbGVhc2UKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgY292ZXJhZ2U6CiAgICBpbWFnZTogcGx1Z2lucy9jb3ZlcmFnZQogICAgc2VydmVyOiBodHRwczovL2NvdmVyYWdlLmdpdGVhLmlvCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIGRvY2tlcjoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZ2l0ZWEvZ2l0ZWEKICAgIHRhZ3M6IFsgJyR7RFJPTkVfVEFHIyN2fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9CUkFOQ0gjI3JlbGVhc2Uvdn0nIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgcmVsZWFzZS8qIF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnbGF0ZXN0JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX1RBRyMjdn0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHRhZyBdCiAgICAgIGJyYW5jaDogWyByZWZzL3RhZ3MvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS8ke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICByZWxlYXNlOgogICAgaW1hZ2U6IHBsdWdpbnMvczMKICAgIHBhdGhfc3R5bGU6IHRydWUKICAgIHN0cmlwX3ByZWZpeDogZGlzdC9yZWxlYXNlLwogICAgc291cmNlOiBkaXN0L3JlbGVhc2UvKgogICAgdGFyZ2V0OiAvZ2l0ZWEvbWFzdGVyCiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIG1hc3RlciBdCgogIGdpdGh1YjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdGh1Yi1yZWxlYXNlCiAgICBmaWxlczoKICAgICAgLSBkaXN0L3JlbGVhc2UvKgogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgZ2l0dGVyOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0dGVyCgpzZXJ2aWNlczoKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgcGdzcWw6CiAgICBpbWFnZTogcG9zdGdyZXM6OS41CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19EQj10ZXN0CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCg.hp6IsxbFIQOaxJdmGv32Vf34-Nra3KqVIWzH52W687I
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9zcnYvYXBwCiAgcGF0aDogc3JjL2NvZGUuZ2l0ZWEuaW8vZ2l0ZWEKCnBpcGVsaW5lOgogIGNsb25lOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0CiAgICB0YWdzOiB0cnVlCgogIHRlc3Q6CiAgICBpbWFnZTogd2ViaGlwcGllL2dvbGFuZzplZGdlCiAgICBwdWxsOiB0cnVlCiAgICBlbnZpcm9ubWVudDoKICAgICAgVEFHUzogYmluZGF0YSBzcWxpdGUKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gYXBrIC1VIGFkZCBvcGVuc3NoLWNsaWVudAogICAgICAtIG1ha2UgY2xlYW4KICAgICAgLSBtYWtlIGdlbmVyYXRlCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGxpbnQKICAgICAgLSBtYWtlIHRlc3QtdmVuZG9yCiAgICAgIC0gbWFrZSB0ZXN0CiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LXNxbGl0ZToKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgdGVzdC1zcWxpdGUKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgdGVzdC1teXNxbDoKICAgIGltYWdlOiB3ZWJoaXBwaWUvZ29sYW5nOmVkZ2UKICAgIHB1bGw6IHRydWUKICAgIGVudmlyb25tZW50OgogICAgICBUQUdTOiBiaW5kYXRhCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIGVjaG8gbWFrZSB0ZXN0LW15c3FsICMgTm90IHJlYWR5IHlldAogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICB0ZXN0LXBnc3FsOgogICAgaW1hZ2U6IHdlYmhpcHBpZS9nb2xhbmc6ZWRnZQogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEKICAgICAgR09QQVRIOiAvc3J2L2FwcAogICAgY29tbWFuZHM6CiAgICAgIC0gZWNobyBtYWtlIHRlc3QtcHFzcWwgIyBOb3QgcmVhZHkgeWV0CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoLCB0YWcsIHB1bGxfcmVxdWVzdCBdCgogIHN0YXRpYzoKICAgIGltYWdlOiBrYXJhbGFiZS94Z28tbGF0ZXN0OmxhdGVzdAogICAgcHVsbDogdHJ1ZQogICAgZW52aXJvbm1lbnQ6CiAgICAgIFRBR1M6IGJpbmRhdGEgc3FsaXRlCiAgICAgIEdPUEFUSDogL3Nydi9hcHAKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgcmVsZWFzZQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBjb3ZlcmFnZToKICAgIGltYWdlOiBwbHVnaW5zL2NvdmVyYWdlCiAgICBzZXJ2ZXI6IGh0dHBzOi8vY292ZXJhZ2UuZ2l0ZWEuaW8KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0KCiAgZG9ja2VyOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBnaXRlYS9naXRlYQogICAgdGFnczogWyAnJHtEUk9ORV9UQUcjI3Z9JyBdCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICcke0RST05FX0JSQU5DSCMjcmVsZWFzZS92fScgXQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCBdCiAgICAgIGJyYW5jaDogWyByZWxlYXNlLyogXQoKICBkb2NrZXI6CiAgICBpbWFnZTogcGx1Z2lucy9kb2NrZXIKICAgIHJlcG86IGdpdGVhL2dpdGVhCiAgICB0YWdzOiBbICdsYXRlc3QnIF0KICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgbWFzdGVyIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhLyR7RFJPTkVfVEFHIyN2fQogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgdGFnIF0KICAgICAgYnJhbmNoOiBbIHJlZnMvdGFncy8qIF0KCiAgcmVsZWFzZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBwYXRoX3N0eWxlOiB0cnVlCiAgICBzdHJpcF9wcmVmaXg6IGRpc3QvcmVsZWFzZS8KICAgIHNvdXJjZTogZGlzdC9yZWxlYXNlLyoKICAgIHRhcmdldDogL2dpdGVhLyR7RFJPTkVfQlJBTkNIIyNyZWxlYXNlL3Z9CiAgICB3aGVuOgogICAgICBldmVudDogWyBwdXNoIF0KICAgICAgYnJhbmNoOiBbIHJlbGVhc2UvKiBdCgogIHJlbGVhc2U6CiAgICBpbWFnZTogcGx1Z2lucy9zMwogICAgcGF0aF9zdHlsZTogdHJ1ZQogICAgc3RyaXBfcHJlZml4OiBkaXN0L3JlbGVhc2UvCiAgICBzb3VyY2U6IGRpc3QvcmVsZWFzZS8qCiAgICB0YXJnZXQ6IC9naXRlYS9tYXN0ZXIKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2ggXQogICAgICBicmFuY2g6IFsgbWFzdGVyIF0KCiAgZ2l0aHViOgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0aHViLXJlbGVhc2UKICAgIGZpbGVzOgogICAgICAtIGRpc3QvcmVsZWFzZS8qCiAgICB3aGVuOgogICAgICBldmVudDogWyB0YWcgXQogICAgICBicmFuY2g6IFsgcmVmcy90YWdzLyogXQoKICBnaXR0ZXI6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKCnNlcnZpY2VzOgogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwogICAgd2hlbjoKICAgICAgZXZlbnQ6IFsgcHVzaCwgdGFnLCBwdWxsX3JlcXVlc3QgXQoKICBwZ3NxbDoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX0RCPXRlc3QKICAgIHdoZW46CiAgICAgIGV2ZW50OiBbIHB1c2gsIHRhZywgcHVsbF9yZXF1ZXN0IF0K.dA2VK6LdoPXvBTYAUywWervhOZmgOjU32uiiPrBbVdQ

2
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -0,0 +1,3 @@
title: Implement proper `gitea restore` & `gitea backup`. Deprecating `gitea dump`
kind: feature
pull_request: 1637

View File

@@ -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
View 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
}

View File

@@ -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
View 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
}

View File

@@ -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)
}
}

View File

@@ -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") {

View File

@@ -11,6 +11,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/log"
"github.com/facebookgo/grace/gracehttp"
)

14
conf/app.ini vendored
View File

@@ -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

View File

@@ -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

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

View File

@@ -0,0 +1,2 @@
# gitea-integration
Gitea integration test environment

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' post-receive

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' pre-receive

View File

@@ -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"

View File

@@ -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

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config='integrations/app.ini' update $1 $2 $3

View File

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

View File

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

View File

@@ -0,0 +1 @@
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master

View File

@@ -0,0 +1 @@
65f1bf27bc3bf70f64657658635e66094edbcb4d

View File

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

View File

@@ -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(),
}
}

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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

View File

@@ -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
View 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)
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
View 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)
}

View File

@@ -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

View 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

View 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

View 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

View File

@@ -287,7 +287,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
curFile.IsBin = true
curFile.IsLFSFile = true
curSection.Lines = nil
break
}
}
}

View File

@@ -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
View 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
}

View 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))
}

View File

@@ -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
View 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
View 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
View 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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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),

View File

@@ -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) {

View File

@@ -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
View 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
View 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
View 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()
}

View File

@@ -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{

View File

@@ -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
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")),
})))
}

View File

@@ -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")

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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

View 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)
}

View 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&#32;ui&#32;tab&#32;active&#32;menu&#32;attached&#32;animating&#32;sidebar&#32;following&#32;bar&#32;center"></code>`, `<code></code>`,
`<code class="language-lol&#32;ui&#32;tab&#32;active&#32;menu&#32;attached&#32;animating&#32;sidebar&#32;following&#32;bar&#32;center">
<code class="language-lol&#32;ui&#32;container&#32;input&#32;huge&#32;basic&#32;segment&#32;center">&nbsp;</code>
<img src="https://try.gogs.io/img/favicon.png" width="200" height="200">
<code class="language-lol&#32;ui&#32;container&#32;input&#32;massive&#32;basic&#32;segment">Hello there! Something has gone wrong, we are working on it.</code>
<code class="language-lol&#32;ui&#32;container&#32;input&#32;huge&#32;basic&#32;segment">In the meantime, play a game with us at&nbsp;<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
View 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."
}

View 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))
}
}

View 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
}

View File

@@ -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