Compare commits

..

242 Commits

Author SHA1 Message Date
AJ ONeal f8cf890b41 handle reset password edge cases properly and consistently 2018-10-07 20:18:21 +00:00
AJ ONeal d3a4d76d0e allow current user to reset their own password 2018-10-07 05:37:02 +00:00
SagePtr 378af8ea88 Fix missing AppSubUrl in few more templates (#5021) 2018-10-05 19:41:09 -04:00
SagePtr c6daee6da6 Fix missing AppSubUrl in some templates (#5020) 2018-10-05 20:22:33 +03:00
Jonas Franz 94cd7bb25b Hide outdated comments in file view (#5017)
* Hide outdated comments in file view

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add unit test by adding "invalidated" comment to fixtures

Signed-off-by: Jonas Franz <info@jonasfranz.software>
2018-10-05 11:49:30 -04:00
Chih-Hsuan Yen d7ca839c67 Fix a typo in app.ini.sample (#5015) 2018-10-05 11:06:47 +02:00
Dane 513db27377 Add missing envionment vars for DB with Docker (#5007)
Current docs demonstrate how to configure a database container but don't
explicitly specify that you should add additional environment variables
to the Gitea container to make it use the database. This just
demonstrates the changes required.

Signed-off-by: Dane Elwell <dane.elwell@ukfast.co.uk>
2018-10-03 17:16:48 +03:00
AJ ONeal 2b8dc17db7 README: update make command to build full release (#5004)
* update make command to build full release

* use quoted TAGS

`TAGS=bindata` => `TAGS="bindata"`
2018-10-03 08:33:46 +02:00
GiteaBot 2e2eacf62a [skip ci] Updated translations via Crowdin 2018-10-02 19:22:42 +00:00
Mura Li dba955be7c Upgrade gopkg.in/testfixtures.v2 (#4999) 2018-10-02 15:20:02 -04:00
GiteaBot b8d048fa0d [skip ci] Updated translations via Crowdin 2018-09-29 22:45:10 +00:00
Lauris BH ab5b245182 Disable debug routes unless PPROF is enabled in configuration (#4995) 2018-09-29 18:44:06 -04:00
SagePtr fc0001caa1 Fix #rrggbbaa color to rgba for better browser compatibility (#4990) 2018-09-29 12:57:32 +03:00
Lauris BH 81702e6ec9 Detect charset and convert non UTF-8 files for display (#4950)
* Detect charset and convert non UTF-8 files for display

* Refactor and move function to correct module

* Revert unrelated changes

* More unrelated changes

* Duplicate content for small text to have better encoding detection

* Check if original content is valid before duplicating it
2018-09-29 16:33:54 +08:00
Iwasa Kazmi 6780661192 Fix layout of the topics editing form (#4971)
* Fix layout of the topic edit.

- made right-hand column wider so that it has enough space for "Done" button.

- fixed issue that jQuery's .show() breaks functionality of the Semantic UI Grid.

* Improve switching visibility of the topic edit

Changes to support old browsers that doesn't support 'flex' keyword.

- Removed style "display: none" from index.css so that
  the grid can be displayed without specifying new "display" style.

- Added style "display:none" to the grid element in HTML template
  as the initial style.

- In index.js, visibility of the grid element is changed by
  set "display:none" style to the element or removing it from the element.
2018-09-29 09:28:47 +03:00
Lanre Adelowo e6d54d511d make sure milestone listing is consistent across board (#4987) 2018-09-28 06:59:46 +03:00
silverwind 31d5488059 Fix user menu item styling (#4985) 2018-09-27 16:58:38 -04:00
GiteaBot 39735723f5 [skip ci] Updated translations via Crowdin 2018-09-27 00:51:27 +00:00
flufmonster b3393b5ca7 Adjust maintainers (#4979) 2018-09-27 08:50:10 +08:00
GiteaBot 79b4d4729c [skip ci] Updated translations via Crowdin 2018-09-25 12:37:54 +00:00
Lauris BH 4092b32bad Fix preview when adding new code review comment (#4975) 2018-09-25 14:36:45 +02:00
zeripath 36e7cb9755 Update Swagger API to match the return of /users/search (#4847)
Signed-off-by: Andrew Thornton <art27@cantab.net>
2018-09-21 16:56:26 +08:00
Wyall dab02b80fd ADD: Hint to function of docker version tags (#4967)
I wasn't sure about how tags work, maybe this is interesting for people with less knowledge of docker.
2018-09-21 09:43:31 +08:00
OvermindDL1 07af31d004 Fix #4877 to follow the OpenID Connect Audiences spec (#4878)
Signed-off-by: Gabriel Robertson <overminddl1@gmail.com>
2018-09-20 22:17:34 +03:00
GiteaBot 364c029246 [skip ci] Updated translations via Crowdin 2018-09-20 05:50:04 +00:00
SagePtr 043ab2cd59 Fix null pointer dereference in ParseCommitWithSignature (#4962) 2018-09-20 13:49:07 +08:00
GiteaBot 8b113cd8b6 [skip ci] Updated translations via Crowdin 2018-09-19 09:03:30 +00:00
bugreport0 91b164c778 Fix interpunction in English translation. (#4958) 2018-09-19 12:02:08 +03:00
GiteaBot 552d8d3a4e [skip ci] Updated translations via Crowdin 2018-09-18 07:05:46 +00:00
crito d6b97c8557 fix url in discord webhook (#4953)
opening issues generates a webhook to discord that contains
a url to the gitea api. the message title in discord is therefore
referencing to the api instead of the issue itself.
2018-09-17 21:04:46 -10:00
Lauris BH 4befec242a
Code review UI improvements and bugfixes (#4682)
* Code review UI improvements

* More fixes to dark theme

* Style fix

* Fix to allow add code review comments only on review files tab

* More readability dark style fixes

* Fix commenting on deleted files. Fixes #4752

* Fix line blame getting for multiple corner cases
2018-09-17 17:59:49 +03:00
nubenum 756eafaaf6 Fix some issues with special chars in branch names (#3767)
Signed-off-by: Robin Durner <github@nubenum.de>
2018-09-17 01:28:23 +03:00
Lanre Adelowo acb6f8a518 make sure to catch the right error so it is displayed as an error on the ui not a 500 (#4945) 2018-09-16 18:27:43 +03:00
Bo-Yi Wu f6eb669b51 fix(topics): don't redirect to expole page. (#4938) 2018-09-15 06:32:59 -04:00
GiteaBot 3e76e7826e [skip ci] Updated translations via Crowdin 2018-09-15 06:30:00 +00:00
Girish Ramakrishnan 661fd901bd doc: add Cloudron package (#4937)
Fixes #4936
2018-09-15 14:28:53 +08:00
Lanre Adelowo 2a6d3ba058 Allow admin toggle forcing a password change for newly created users (#4563) 2018-09-13 12:34:36 -04:00
linweijie2012 f98040ad50 fix bug forget to remove Stopwatch when remove repository (#4928) 2018-09-13 22:58:01 +08:00
Lanre Adelowo 126ba796dc Force user to change password (#4489)
* redirect to login page after successfully activating account

* force users to change password if account was created by an admin

* force users to change password if account was created by an admin

* fixed build

* fixed build

* fix pending issues with translation and wrong routes

* make sure path check is safe

* remove unneccessary newline

* make sure users that don't have to view the form get redirected

* move route to use /settings prefix so as to make sure unauthenticated users can't view the page

* update as per @lafriks review

* add necessary comment

* remove unrelated changes

* support redirecting to location the user actually want to go to before being forced to change his/her password

* run make fmt

* added tests

* improve assertions

* add assertion

* fix copyright year

Signed-off-by: Lanre Adelowo <yo@lanre.wtf>
2018-09-13 15:04:25 +03:00
SagePtr 10a2a904d7 Fix bug when repo remained bare if multiple branches pushed (#4923) 2018-09-13 11:40:35 +08:00
Bo-Yi Wu ea20adaa84
feat(repo): support search repository by topic name (#4505)
* feat(repo): support search repository by topic name
2018-09-13 10:33:48 +08:00
GiteaBot 7dd93b2441 [skip ci] Updated translations via Crowdin 2018-09-12 14:47:24 +00:00
techknowlogick e91a2cf2ea
Add/remove/list oauth providers via cli (#4591) 2018-09-12 10:46:02 -04:00
Drew Bowering 8e3e59fdb8 Fix: Let's Encrypt configuration settings (#4911)
ENABLE_LETSENCRYPT and LETSENCRYPT_ACCEPTTOS were not being properly loaded from the config file, always resulting in the default settings being in place.
2018-09-12 00:06:44 -04:00
GiteaBot b5b39a56ad [skip ci] Updated translations via Crowdin 2018-09-11 18:55:32 +00:00
Fluf 08ed515b62 Remove zookeeper documentation (#4910)
Fix #3763
2018-09-11 21:54:23 +03:00
GiteaBot c64c595acc [skip ci] Updated translations via Crowdin 2018-09-11 14:44:00 +00:00
techknowlogick f1ad4bb3d5
Remove traces of embedded TiDB (#4906)
Fix #3357
2018-09-11 10:42:40 -04:00
Lanre Adelowo 8ee9922fe6 Add file name and branch to page title (#4902) 2018-09-10 13:01:49 -04:00
B-OnTheGo e47df0b301 Enforce token on api routes [fixed critical security issue #4357] (#4840) 2018-09-10 12:15:52 -04:00
Bobonium 387a4b09c1 allow api to create tags for releases (#4890) 2018-09-10 10:57:41 -04:00
GiteaBot 15ebe4b853 [skip ci] Updated translations via Crowdin 2018-09-10 14:32:36 +00:00
Lanre Adelowo be48397945 Slack webhook channel name cannot be empty or just contain an hashtag (#4786) 2018-09-10 10:31:08 -04:00
GiteaBot 6e03390aa8 [skip ci] Updated translations via Crowdin 2018-09-09 18:12:41 +00:00
Toni Villena eea76a5241 fix: Crippled diff (#4726) (#4900)
* fix: Crippled diff (#4726)

* Regenerate css
2018-09-09 20:11:49 +02:00
vz e9dbfc70b7 Move README_ZH.md Screenshots to Bottom (#4895)
- Move Screenshots to Bottom
2018-09-09 12:13:48 +08:00
GiteaBot 1dee960b45 [skip ci] Updated translations via Crowdin 2018-09-09 03:37:12 +00:00
Lewis Cowles c43399cad8 Fixes repo branch endpoint summary (#4893)
in browser saw `/repos/{owner}/{repo}/branches/{branch} List a repository's branches` fixed

Addresses https://github.com/go-gitea/debian-packaging/pull/1

Fixes https://github.com/go-gitea/gitea#4892
2018-09-09 11:36:08 +08:00
zeripath d293a2b9d6 Add sudo functionality to the API (#4809) 2018-09-06 23:31:29 -04:00
Lukas Bestle e6a03813d4 Extract header and footer content to separate tmpl (#4797)
This change makes it easier to customize the header and footer content. Before this change, the whole header and footer had to be overridden, including the meta, style and script tags.

Signed-off-by: Lukas Bestle <mail@lukasbestle.com>
2018-09-07 10:59:06 +08:00
Lauris BH fab7937c62 Improve dependency UI (#4503) 2018-09-07 10:32:46 +08:00
Lauris BH fa4663e61e Add push webhook support for mirrored repositories (#4127) 2018-09-06 22:06:09 -04:00
SagePtr bf55276189 Fix redirect with non-ascii branch names (#4764) (#4810)
* Fix redirect with non-ascii branch names (#4764)

* Add integration tests for non-ascii branch redirect

* Fix mysql test and coverage test
2018-09-06 21:37:02 -04:00
techknowlogick 13e8a0fe56
offline use of fonts (#4872) 2018-09-06 21:15:25 -04:00
SagePtr 303d7f7e9c Do not autocreate directory for new user/orgs (#4828) (#4849) 2018-09-06 20:40:58 -04:00
Max Wittig 3c6cc56143 docs(comparison): correct GitLab CE multiple assignees for issues (#4885)
GitLab CE doesn't support multiple assignees for issues.  
That's an enterprise feature.  See: https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html
2018-09-06 20:17:31 -04:00
Matthias Kappeller bd1bf2a072 Remove feature `global code search` from GitLab CE (#4875) 2018-09-05 11:12:09 -04:00
maiki a5cc3a9baf Punctuation changes in documentation (#4866) 2018-09-04 22:47:45 -04:00
Lunny Xiao 061b1aa2d4 update comparsion docs to add file viewer items (#4865) 2018-09-04 22:07:24 -04:00
Nicolas Lenz 668a477c69 Fix trimming of markup section names (#4863)
Signed-off-by: Nicolas Lenz <nicolas@eisfunke.com>
2018-09-03 20:59:02 -04:00
Lunny Xiao e48df3ee47 issues api allow pulls and fix #4832 (#4852) 2018-09-03 13:20:54 -04:00
techknowlogick a4ee5627ed
Backport 1.5.1 changelog (#4854) 2018-09-03 01:17:10 -04:00
GiteaBot 01fd05a5cb [skip ci] Updated translations via Crowdin 2018-09-03 03:25:34 +00:00
Bernhard Fröhlich 83d1173634 Add missing History link to directory listings (#4829)
The feature to list commits from a subdirectory is already there but
so far the history link to it was missing. There is a History button
in the view_file.tmpl already so avoid showing two history buttons in
that case.

The GitHub webinterface has the same History button in the same place
so this makes gitea a little bit more compatible.

Signed-off-by: Bernhard Froehlich <decke@bluelife.at>
2018-09-02 23:24:19 -04:00
GiteaBot aad5cccec8 [skip ci] Updated translations via Crowdin 2018-08-31 21:23:17 +00:00
SagePtr 9500d394ec Minor fix to TRANSLATORS (#4836)
* Minor fix to TRANSLATORS

Replaced @ with AT and removed " accidentially put in few lines

* Update TRANSLATORS
2018-09-01 00:22:11 +03:00
Najib Idrissi b9ae16d15e Make reverse proxy auth optional (#4643)
* Make reverse proxy auth optional

If the option ENABLE_REVERSE_PROXY_AUTHENTICATION is enabled, make
reverse proxy auth optional, instead of failing if the authentication
did not succeed.

Fixes #3973

Signed-off-by: Najib Idrissi <najib.idrissi.kaitouni@gmail.com>

* Update http.go
2018-08-29 10:39:16 -04:00
techknowlogick fcea86877f
update image used for build-without-gcc step (#4818)
As we've dropped support for go 1.8 (see 1.5.0 release post),
now we need to ensure that a minimum of go 1.9 builds correctly.
2018-08-29 10:01:58 -04:00
GiteaBot a938ddf704 [skip ci] Updated translations via Crowdin 2018-08-29 13:46:42 +00:00
techknowlogick 080428b2bf
Upgrade images in .drone.yml to more recent versions (#4819) 2018-08-29 09:43:58 -04:00
GiteaBot 179123de35 [skip ci] Updated translations via Crowdin 2018-08-28 15:50:29 +00:00
SagePtr 74d65b5b5b Update legacy branch and tag URLs in dashboard to new format (#4812) 2018-08-28 11:48:15 -04:00
Joel da Rosa 34831afaa7 Locale for Edit and Remove due date issue (#4802) 2018-08-27 21:34:12 +03:00
Piotr Orzechowski 51c3b4b4bf Add Orzech to translator list (#4801) 2018-08-26 23:31:10 -04:00
Piotr Orzechowski 56d931aeac Hide home button when landing page is not set to home (#4651) 2018-08-26 22:23:27 -04:00
SagePtr 4ae5a54c1f Fix missing release title in webhook (#4783) (#4796) 2018-08-26 11:24:33 -04:00
Deoren Moor b63e2e0ded Update en-us and fr-fr versions of the "Upgrade from Gogs" doc (#4618)
* Update "Upgrade from Gogs" en-us doc

- Move "Change gogs specific information" FROM/TO steps up
  to just beneath matching section header, adjust indention
  and explicitly specify syntax of code sample to provide
  highlighting

- Adjust header levels to match what appears to be the
  intended level, remove trailing decoration (see below)

- Move "Upgrading to most recent ..." section down, remove
  decoration (section header formatting likely covers this
  well enough already)

refs go-gitea/gitea#4286, go-gitea/gitea#3558

* Update "Upgrade from Gogs" fr-fr doc

- Adjust indention and explicitly specify syntax of code
  sample to provide highlighting

- Adjust header levels to match what appears to be the
  intended level, remove trailing decoration since
  section header formatting likely covers this
  well enough already

refs go-gitea/gitea#4286, go-gitea/gitea#3558
2018-08-25 02:47:47 -04:00
EnricoFerro 0a24f5cac8 Disable 'May Import Local Repository' when is disabled by setting (Issue #4779) (#4780) 2018-08-24 01:00:22 -04:00
techknowlogick 194a11eb11
Don't disclose emails of all users when sending out emails (#4664) 2018-08-24 00:41:26 -04:00
EnricoFerro 912953e82a API /admin/users/{username} missing parameter (#4775) 2018-08-23 19:59:47 -04:00
Russell Aunger 127f477056 MySQL TLS (#4642) 2018-08-23 18:42:02 -04:00
GiteaBot 0dac1ff677 [skip ci] Updated translations via Crowdin 2018-08-23 18:13:57 +00:00
Lanre Adelowo f766e9713b Add myself to maintainers (#4777) 2018-08-23 14:12:57 -04:00
GiteaBot 3c39b6351c [skip ci] Updated translations via Crowdin 2018-08-23 06:16:16 +00:00
Lanre Adelowo 33bc2ebdfa Make sure to reset commit count in the cache on mirror syncing (#4720)
* Make sure to reset commit count in the cache on mirror syncing

* reset count of commits in all branches
2018-08-23 14:15:07 +08:00
Lanre Adelowo 6ca8fbd2f9 Fixed bug where team with admin privelege type doesn't get any unit attached to the team (#4719) 2018-08-21 13:02:32 -04:00
GiteaBot 05dcfcfc33 [skip ci] Updated translations via Crowdin 2018-08-21 13:58:07 +00:00
Fluf b82c14b3d2 add letsencrypt to Gitea (#4189) 2018-08-21 09:56:50 -04:00
Lanre Adelowo 6c1a31ffaa User shouldn't be able to approve or reject his/her own PR (#4729)
* Make sure author cannot reject/approve their own PR

* Disable buttons in templates too

* Remove unneccessary if check since the switch below catches it

* Fix IsOwner check

* Update template and remove new template variable

* Add alert template and redirect to diff page on review failure

* Redirect to files diff as a little update to #4632
2018-08-20 07:04:01 +02:00
Julien Tant fa93857117 allow WIP marker to contains < or > (#4709) 2018-08-19 15:00:10 -04:00
Lanre Adelowo a6cdda115d Display error when adding a user to a team twice (#4746) 2018-08-19 21:49:19 +03:00
Tosone b1ad5734c6 Add whitespace between chinese characters and english characters. (#4731)
Signed-off-by: Tosone <i@tosiney.com>
2018-08-17 09:26:00 +08:00
GiteaBot 1b2aff02a2 [skip ci] Updated translations via Crowdin 2018-08-16 11:53:51 +00:00
SagePtr 50ce19eff7 Fix failure on creating pull request with assignees (#4419) (#4583) 2018-08-16 19:52:51 +08:00
GiteaBot 8273479b41 [skip ci] Updated translations via Crowdin 2018-08-15 22:59:13 +00:00
Andrew Phillips b30f6b4099 Remove UsePrivilegeSeparation from the Docker sshd_config, see #2876 (#4722)
Signed-off-by: Andrew Phillips <theasp@gmail.com>
2018-08-16 01:58:12 +03:00
GiteaBot f24ba27d79 [skip ci] Updated translations via Crowdin 2018-08-15 11:56:09 +00:00
SagePtr 8f86c43b90 Fix incorrect caption of webhook setting (#4701) (#4717) 2018-08-15 14:55:17 +03:00
GiteaBot bc06ab4a31 [skip ci] Updated translations via Crowdin 2018-08-15 06:30:05 +00:00
Lauris BH 92466129ec
Improve URL validation for external wiki and external issues (#4710)
* Improve URL validation for external wiki  and external issues

* Do not allow also localhost address for external URLs
2018-08-15 09:29:37 +03:00
SagePtr 0449330dbc Make cookies HttpOnly and obey COOKIE_SECURE flag (#4706) 2018-08-14 23:16:37 +03:00
nemoinho ca112f0a04 Add whitespace handling to PR-comparsion (#4683)
* Add whitespace handling to PR-comparsion

In a PR we have to keep an eye on a lot of different things. But sometimes the
bare code is the key-thing we want to care about and just don't want to care
about fixed indention on some places. Especially if we follow the pathfinder
rule we face a lot of these situations because these changes don't break the
code in many languages but improve the readability a lot.

So this change introduce a fine graned button to adjust the way how the
reviewer want to see whitespace-changes within the code.

The possibilities reflect the possibilities from git itself except of the
`--ignore-blank-lines` flag because that one is also handled by `-b` and is
really rare.

Signed-off-by: Felix Nehrke <felix@nehrke.info>
2018-08-14 13:49:33 -04:00
GiteaBot 03e558c29b [skip ci] Updated translations via Crowdin 2018-08-14 06:11:45 +00:00
Julien Tant e6777f7b9d Focus title input when clicking helper link (#4696) 2018-08-14 09:10:26 +03:00
GiteaBot df0eb7372c [skip ci] Updated translations via Crowdin 2018-08-13 19:05:41 +00:00
Julien Tant 7781e8cef2 Disable merging a WIP Pull request (#4529)
* prevent pull request to be merged when PR is a WIP

* add tests

* add helper to prepend WIP: in PR title

* move default wip prefixes into settings

* use configurable WIP prefixes in javascript and default to first one in templates

* add documentation

* add unit test on pull model

Signed-off-by: Julien Tant <julien@craftyx.fr>
2018-08-13 22:04:39 +03:00
Lunny Xiao 52c2cb15db add vendor to user reserved words and format words list according alphabet (#4685) 2018-08-13 08:02:18 +03:00
SagePtr a4fa6bbc89 Hide org/create menu item in Dashboard if user has no rights (#4678) (#4680) 2018-08-12 20:16:10 +03:00
Lauris BH 6e68b61479 Fix dark theme diff box header backround color (#4674) 2018-08-12 21:55:48 +08:00
techknowlogick ce7c64c7e1
Use updated docs link on install page (#4668)
Fix #4665
2018-08-11 21:05:52 -04:00
Lanre Adelowo 3422077a89 make sure to set pr split view (#4617)
Signed-off-by: Lanre Adelowo <yo@lanre.wtf>
2018-08-11 20:20:33 -04:00
Lauris BH 7fbdd4f2ac Enable swagger validation back as issue with it has been fixed upstream (#4673) 2018-08-11 20:08:17 -04:00
SagePtr 3ad5399a31 Update gitea version in install-from-binary docs (#4670) 2018-08-11 15:47:14 -04:00
Lanre Adelowo 69a855f3d4 log user in after a successful sign up (#4615) 2018-08-11 15:33:19 -04:00
SagePtr 4a7de87071 Add gitea/issues link to 500 page (#4654) 2018-08-11 13:04:43 -04:00
techknowlogick 5dfe8b4340
Add 1.5.0 changelog (#4661) 2018-08-11 04:38:44 -04:00
Lunny Xiao 8da45ae2b3 improve comparison to add issue dependency (#4658) 2018-08-10 08:22:07 +03:00
nemoinho 0e04a2091a Fix Split-View line adjustment (#4622)
The $-function is unreachable in the previous implementation because jQuery is
not loaded yet. I fix this by executing the function after the content is
loaded, so jQuery is loaded at the time of execution and the call will not fail
anymore.

Signed-off-by: Felix Nehrke <felix@nehrke.info>
2018-08-09 16:28:55 +08:00
Lunny Xiao 578cf52ce5
Site admin could create repos even MAX_CREATION_LIMIT=0 (#4645)
* site admin could create repos even MAX_CREATION_LIMIT=0

* Optimize if structure
2018-08-09 09:04:16 +08:00
techknowlogick 0e464995ce
Remove link to GitHub issues (#4639) 2018-08-08 11:07:34 -04:00
Piotr Orzechowski 152c6af97e Fix custom templates being ignored (#4638) 2018-08-08 14:15:48 +03:00
GiteaBot 00bd6277a8 [skip ci] Updated translations via Crowdin 2018-08-08 05:21:16 +00:00
Lanre Adelowo 3b51c4f3fb Show review comment box only on a pull request page (#4636)
* Show review comment box only on a pull request page

* Fixed template check
2018-08-08 08:20:04 +03:00
SagePtr 32145b6de8 Push whitelist now doesn't apply to branch deletion (#4601) (#4607) 2018-08-08 11:17:10 +08:00
GiteaBot 67a8688538 [skip ci] Updated translations via Crowdin 2018-08-07 18:49:38 +00:00
Jerry Jacobs b1bc08e268 cmd/serve: pprof cpu and memory profile dumps to disk (#4560) 2018-08-07 14:49:18 -04:00
GiteaBot ed3589f429 [skip ci] Updated translations via Crowdin 2018-08-07 18:37:15 +00:00
Lauris BH 5ae8408725 Fix starring icon after semantic ui update (#4628) 2018-08-07 14:36:21 -04:00
GiteaBot a42900c17f [skip ci] Updated translations via Crowdin 2018-08-07 17:16:52 +00:00
Lanre Adelowo 0df7cab4fb prevent empty review comment (#4632)
* prevent empty review comment

This would only require a comment for rejection and comment

* add tests

* add comment
2018-08-07 20:15:41 +03:00
Lanre Adelowo 59b10e66f7 An inactive user shouldn't be able to be added as a collaborator (#4535)
* an inactive user shouldn't be able to be a collaborator

* use translated error message

* add active user check when adding a new collaborator via the api

* fix translation text

* added collaborator test

* improvee testcases
2018-08-07 13:01:06 +03:00
Lanre Adelowo c7a6ee5c0b Don't fail silently if trying to add a collaborator twice (#4533)
* don't fail silently if trying to add a collaborator twice

* fix translation text

* added collaborator test

* improvee testcases

* Added tests to make sure a collaborator cannot be added twice
2018-08-07 09:59:42 +08:00
GiteaBot 7cb1c1cf20 [skip ci] Updated translations via Crowdin 2018-08-06 11:54:16 +00:00
Dennis Menschel 9c0a374f3f Fix integer constant overflows in tests (#4616)
* Use integer limit value [1] instead of hard-coded magic constant for
  NonexistentID.

* Explicitly use int64 in order to avoid the following errors on 32 bit
  architectures:

  # code.gitea.io/gitea/integrations
  ./api_admin_test.go:50:34: constant 9223372036854775807 overflows int
  ./api_token_test.go:47:34: constant 9223372036854775807 overflows int
  [...]
  # code.gitea.io/gitea/models
  ./action_test.go:179:15: constant 9223372036854775807 overflows int

[1] https://golang.org/pkg/math/#pkg-constants

Signed-off-by: Dennis Menschel <menschel-d@posteo.de>
2018-08-06 14:52:53 +03:00
GiteaBot 9ea327f1f7 [skip ci] Updated translations via Crowdin 2018-08-06 04:44:17 +00:00
Lauris BH 6e64f9db8e Pull request review/approval and comment on code (#3748)
* Initial ui components for pull request review

* Add Review
Add IssueComment types

Signed-off-by: Jonas Franz <info@jonasfranz.software>

(cherry picked from commit 2b4daab)
Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Replace ReviewComment with Content

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add load functions
Add ReviewID to findComments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add create review comment implementation
Add migration for review
Other small changes

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Simplified create and find functions for review

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Moved "Pending" to first position

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add GetCurrentReview to simplify fetching current review

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Preview for listing comments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Move new comment form to its own file

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Implement Review form
Show Review comments on comment stream

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add support for single comments
Showing buttons in context

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add pending tag to pending review comments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add unit tests for Review

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fetch all review ids at once
Add unit tests

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* gofmt

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Improved comment rendering in "Files" view by adding Comments to DiffLine

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add support for invalidating comments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Switched back to code.gitea.io/git

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Moved review migration from v64 to v65

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Rebuild css

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* gofmt

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Improve translations

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix unit tests by updating fixtures and updating outdated test

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Comments will be shown at the right place now

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add support for deleting CodeComments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix problems caused by files in subdirectories

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add support for showing code comments of reviews in conversation

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add support for "Show/Hide outdated"

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Update code.gitea.io/git

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add support for new webhooks

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Update comparison

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Resolve conflicts

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Minor UI improvements

* update code.gitea.io/git

* Fix ui bug reported by @lunny causing wrong position of add button
Add functionality to "Cancel" button
Add scale effects to add button
Hide "Cancel" button for existing comments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Prepare solving conflicts

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Show add button only if no comments already exist for the line

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add missing vendor files

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Check if reviewer is nil

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Show forms only to users who are logged in

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Revert "Show forms only to users who are logged in"

This reverts commit c083682

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Save patch in comment
Render patch for code comments

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add link to comment in code

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add reply form to comment list
Show forms only to signed in users

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add 'Reply' as translatable
Add CODE_COMMENT_LINES setting

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* gofmt

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix problems introduced by checking for singed in user

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add v70

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Update generated stylesheet

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix preview
Beginn with new review comment patch system

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add new algo to generate diff for line range
Remove old algo used for cutting big diffs (it was very buggy)

* Add documentation and example for CutDiffAroundLine

* Fix example of CutDiffAroundLine

* Fix some comment UI rendering bugs

* Add code comment edit mode

* Send notifications / actions to users until review gets published
Fix diff generation bug
Fix wrong hashtag

* Fix vet errors

* Send notifications also for single comments

* Fix some notification bugs, fix link

* Fix: add comment icon is only shown on code lines

* Add lint comment

* Add unit tests for git diff

* Add more error messages

* Regenerated css

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* fmt

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Regenerated CSS with latest less version

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix test by updating comment type to new ID

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Introducing CodeComments as type for map[string]map[int64][]*Comment
Other minor code improvements

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix data-tab issues

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Remove unnecessary change

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* refactored checkForInvalidation

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Append comments instead of setting

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Use HeadRepo instead of BaseRepo

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Update migration

Signed-off-by: Jonas Franz <info@jonasfranz.de>

* Regenerated CSS

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add copyright

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Update index.css

Signed-off-by: Jonas Franz <info@jonasfranz.software>
2018-08-06 06:43:21 +02:00
Lunny Xiao 9c354a539a
fix bugs when too many IN variables (#4594) 2018-08-02 21:49:05 +08:00
Allen Wild c40f5d2d4c api: repos/search: add parameters to control the sort order (#3964)
The backend SearchRepositoryByName function supports several sort order
options, hook these up to the /repos/search API.

New parameters for /repos/search:
    'sort':  alpha, created, updated, size, or id
    'order': asc or desc

'sort' defaults to alpha.
'order' defaults to ascending, and is ignored if 'sort' is not specified.
The overall default behavior is unchanged (alphabetically ascending).

This partially implements issue #3963

Signed-off-by: Allen Wild <allenwild93@gmail.com>
2018-08-02 16:10:02 +08:00
Dingjun 819f50ccd5 fix panic issue on update avatar email (#4580) (#4581)
fix #4580
2018-08-01 17:38:56 +08:00
Julien Tant ced08d18a4 fix IsPullReuqestBroken->IsPullRequestBroken (#4578)
Signed-off-by: Julien Tant <julien@craftyx.fr>
2018-08-01 11:00:35 +08:00
Benedikt Kroll d57233680b Add documentation on how to update Gitea to new version (#4500) 2018-07-30 11:35:05 -04:00
Lanre Adelowo b11ddd8d4c fix status code label for a successful webhook (#4540) 2018-07-30 10:27:31 +03:00
GiteaBot e277b3e0e4 [skip ci] Updated translations via Crowdin 2018-07-30 06:05:23 +00:00
techknowlogick 895e538836
Update jQuery to v1.12.4 (#4551)
Fix #4384
2018-07-30 02:04:24 -04:00
Piotr Orzechowski a74426d631 Swagger.v1.json template (#3572)
* Turn swagger.v1.json into template

* Rename ENABLE_SWAGGER_ENDPOINT option to ENABLE_SWAGGER
2018-07-28 03:19:01 +03:00
GiteaBot 412583a3f2 [skip ci] Updated translations via Crowdin 2018-07-27 19:12:26 +00:00
SagePtr c1224124ea Fix incorrect MergeWhitelistTeamIDs check in CanUserMerge function (#4519) (#4525) 2018-07-27 22:11:24 +03:00
GiteaBot 5927599e43 [skip ci] Updated translations via Crowdin 2018-07-27 12:55:56 +00:00
techknowlogick adf3f004b6
Switch plaintext scratch tokens to use hash instead (#4331) 2018-07-27 08:54:50 -04:00
SagePtr ac968c3c6f Fix out-of-transaction query in removeOrgUser (#4521) (#4522) 2018-07-27 02:41:36 +03:00
Lukas Treyer be4ec0cbba env var GITEA_PUSHER_EMAIL (#4516)
* env var GITEA_PUSHER_EMAIL

* set pusher email only if email address is not private
2018-07-26 19:38:55 +03:00
GiteaBot a847d16f5b [skip ci] Updated translations via Crowdin 2018-07-26 15:26:44 +00:00
kolaente 8d1ad55598 Responsive design fixes (#4508)
* reset to master

* build css

* Fixed spacing
2018-07-26 18:25:41 +03:00
William Le Pommelet f847884d16 Fix doc typo (#4517)
* fix language list

Standardized the languages' names within the list displayed when picking up a language at the bottom of the webpages

* typo

fix a typo into documentation (template rather than templete)

* fix languages list
2018-07-26 12:57:45 +02:00
Lauris BH 1b9b894731
Fix uk-UA locale. Fixes #4518 2018-07-26 13:30:56 +03:00
GiteaBot 7a30208e05 [skip ci] Updated translations via Crowdin 2018-07-25 17:55:49 +00:00
Lanre Adelowo 630f234223 Add flash message after an account has been successfully activated (#4510)
* added new locale text
2018-07-25 20:54:56 +03:00
GiteaBot 32f25598b3 [skip ci] Updated translations via Crowdin 2018-07-25 12:12:34 +00:00
Lanre Adelowo 4bf0cae1f5 Respect email privacy option in user search via API (#4512)
* respect user's email privacy option

* make email visible to admin irrespective of privacy option
2018-07-25 15:11:22 +03:00
GiteaBot d0fef4395f [skip ci] Updated translations via Crowdin 2018-07-24 10:22:04 +00:00
Michael Kuhn 344dc07239 Add shortcut to save wiki page (#4452)
This allows saving the wiki page with Ctrl-Enter.
2018-07-24 12:20:52 +02:00
Kjell Kvinge ba358ecbf5 Make max commits in graph configurable (#4498) 2018-07-23 17:12:06 +03:00
Lauris BH ae9dd239fb Fix migration from older releases (#4495) 2018-07-21 23:15:11 -04:00
GiteaBot ec43e5619b [skip ci] Updated translations via Crowdin 2018-07-21 18:18:18 +00:00
Kjell Kvinge bed623600d Accept 'Data:' in commit graph (#4487) 2018-07-21 14:17:09 -04:00
GiteaBot 5fa403c874 [skip ci] Updated translations via Crowdin 2018-07-20 21:09:20 +00:00
Lunny Xiao 0bb1c84208 Add csv file render support defaultly (#4105)
* add csv file render support defaultly

* escaping csv column content
2018-07-21 00:08:15 +03:00
bugreport0 b174817b6a Improve English translation for new features. (#4481) 2018-07-20 12:40:08 -04:00
GiteaBot cc9fa062e6 [skip ci] Updated translations via Crowdin 2018-07-20 02:11:33 +00:00
Lauris BH 0c59edaafa Update xorm to latest version and fix correct `user` table referencing in sql (#4473) 2018-07-20 10:10:17 +08:00
GiteaBot 1e2da5d396 [skip ci] Updated translations via Crowdin 2018-07-19 17:59:34 +00:00
techknowlogick 492ec97a46
Redirect to correct page after using scratch token (#4458) 2018-07-19 13:58:33 -04:00
Lunny Xiao d1337299e2 add valid for lfs oid (#4461) 2018-07-19 11:39:19 -04:00
GiteaBot 9ca8aaecb4 [skip ci] Updated translations via Crowdin 2018-07-19 15:26:27 +00:00
Joel da Rosa 91373901f6 Prevent html entity escaping (#4471) 2018-07-19 18:25:17 +03:00
techknowlogick fe78154895 relative URLs for LibreJS page (#4460)
Fix #4449
2018-07-18 17:23:40 +08:00
kolaente 1bff02de55 Added dependencies for issues (#2196) (#2531) 2018-07-17 17:23:58 -04:00
Mahmoud Al-Qudsi 7be5935c55 Add BSDmakefile to prevent errors when `make` is called under FreeBSD (#4446)
The syntax of the gitea Makefile is not platform-agnostic and is
specific to the GNU version of `make`. BSD platforms such as FreeBSD
ship with bmake (BSD make) as their default `make` program; attempting
to compile gitea by simply executing `make` causes a wall of errors to
show as a result of syntax incompatible with BSD make.

If a file named `BSDmakefile` is present, `bmake` will give it
preference over a generic `Makefile`. This `BSDmakefile` is taken from
the BSD-licensed `gmake-proxy` project [0], which transparently proxies
all `make` commands to `gmake` (GNU make) on systems where `bmake` is
the default, and if `gmake` is not installed an error message is
displayed.

[0]: https://github.com/neosmart/gmake-proxy
2018-07-16 20:45:51 +02:00
kolaente ef6813abc9 Issue due date api (#3890)
* Implemented basic api endpoint to manage deadlines

* Fixed checking for permissions

* Updating a deadline from the ui is now entirely done via the api

* cleanup

* Cosmetics

* fixed lint + fmt

* Added swagger model definition for deadline response

* Updated gitea-sdk

* Updated gitea-sdk

* More cleanup

* Generate swagger json

* Merge branch 'master' of https://github.com/go-gitea/gitea into issue-due-date-api

# Conflicts:
#	public/swagger.v1.json

* Fixed permission to update a deadline via api

* Re-added form to change a deadline

* Added client-side validation + not ignore error messages from the api

* Added locale for error message

* Merge branch 'master' of https://github.com/go-gitea/gitea

# Conflicts:
#	models/issue_comment.go

* Proper date validation

* Fixed indention

* moved css to css file

* added documentation for error codes

* after merge cleanup

* Added swagger description

* DO NOTHING BUT TRIGGER THAT F*CKIN CI SO IT PICKS UP THE LATEST COMMIT AS IT SHOULD

* DO NOTHING BUT TRIGGER THAT F*CKIN CI SO IT PICKS UP THE LATEST COMMIT AS IT SHOULD

* regenerated stylesheets
2018-07-16 14:43:00 +02:00
GiteaBot 55d9ddf24a [skip ci] Updated translations via Crowdin 2018-07-15 00:44:24 +00:00
Alexey Terentyev ca474af3c6 Added front-end topics validation (#4316) 2018-07-14 20:43:32 -04:00
GiteaBot 8e103d3e76 [skip ci] Updated translations via Crowdin 2018-07-14 06:08:34 +00:00
techknowlogick a7c5e58635 Clean up arc-green theme (#4443)
* Resolve some issues with Arc Green

* Fix editor styles

* zeebra stripes

* generate CSS file
2018-07-14 09:07:30 +03:00
GiteaBot ca8c7bb2b5 [skip ci] Updated translations via Crowdin 2018-07-13 20:58:18 +00:00
Joel da Rosa aa27cbf229 Locale for button Edit on protected branch (#4442) 2018-07-13 16:57:20 -04:00
GiteaBot 6813640d2f [skip ci] Updated translations via Crowdin 2018-07-13 15:22:00 +00:00
Lauris BH e95417ea2c Fix column droping for MSSQL that need new transaction for that (#4440) 2018-07-13 11:20:40 -04:00
Lauris BH 659bc727bd
Fix query parameter name comment in issue API (#4421) 2018-07-12 16:40:41 +03:00
Thomas Boerger e6b51200ab Final CSP header fix (#4432) 2018-07-12 19:48:33 +08:00
Lauris BH a7f90905df
Fix drone git@next plugin Gitea version display when building tag (#4380) 2018-07-12 06:32:01 +03:00
Thomas Boerger 8afd500c48 Add missing font hosts to CSP header (#4429) 2018-07-11 19:24:06 -04:00
Thomas Boerger 8b21cdba78
Fix CSP header for docs (#4428) 2018-07-12 00:27:48 +02:00
Thomas Boerger 3e950ef112 Use correct site id for netlify (#4427) 2018-07-11 23:45:03 +02:00
silverwind c55caeaf0c Update npm dependencies, regenerate CSS (#4415) 2018-07-11 10:54:28 -04:00
Thomas Boerger d84490a0df Replaced docker docs with netlify deployment (#4420) 2018-07-11 16:43:33 +02:00
Lauris BH c2ec38f9b7 Disable swagger validation while it is not fixed in upstream (#4423) 2018-07-11 10:03:52 -04:00
GiteaBot 61f4ad2fc5 [skip ci] Updated translations via Crowdin 2018-07-11 11:37:10 +00:00
Niclas Kroon c9687c036d update TRANSLATORS (#4406) 2018-07-11 14:36:04 +03:00
GiteaBot 3e445cce06 [skip ci] Updated translations via Crowdin 2018-07-08 20:42:36 +00:00
BNolet 80169460ec Added question for Gitea hosted Gitea (#4397)
Currently some issues need to be resolved and features need to be implemented before we can move the Gitea development workflow to a Gitea instance. Many ask why Gitea isn't hosted on Gitea, now that's in the FAQ.
2018-07-08 22:41:31 +02:00
GiteaBot efd202f40f [skip ci] Updated translations via Crowdin 2018-07-07 01:55:43 +00:00
techknowlogick ab55ca7ebd
Add ability to delete a token (#4235)
Fix #4234
2018-07-06 21:54:30 -04:00
Lunny Xiao 1675fc4301 add git timeout settings docs (#4383) 2018-07-06 08:49:37 +03:00
techknowlogick 61b40520ba GPG verification docs (#4381)
* GPG verification docs

* update URL
2018-07-06 09:36:54 +08:00
techknowlogick f1d6a1fffc
Add the ability to have built in themes in Gitea (#4198)
This makes it easier for user who want to theme but
don't have the ability to know how to customize templates
all that is required is a change in a config option

The reason why I chose the DEFAULT_THEME as variable,
as perhaps in the future we will allow users to chose their
theme whon logged in just like we do with languages
2018-07-05 17:25:04 -04:00
GiteaBot 28c1c90230 [skip ci] Updated translations via Crowdin 2018-07-05 20:42:15 +00:00
Jonas Franz 2a60e72fcd
Replace src with raw to fix image paths (#4377)
Signed-off-by: Jonas Franz <info@jonasfranz.software>
2018-07-05 22:36:45 +02:00
GiteaBot cfb76cd99e [skip ci] Updated translations via Crowdin 2018-07-05 17:49:07 +00:00
Guido Diepen 7c943b1cad Implemented hover text showing user FullName (#4261)
For each action that is displayed in either the public activity overview
of a user, or in the dashboard overview, the link to the username is now
extended with a title attribute to show the FullName as hover text

Signed-off-by: Guido Diepen <site-github@guidodiepen.nl>
2018-07-05 19:48:18 +02:00
Lanre Adelowo 5bc8782d33 Don't display buttons if there are no system notifications (#4280)
* Don't display buttons if there are no notices

* remove redundant gt check
2018-07-05 17:35:42 +02:00
Nicolas Da Mutten e07d3ad0fc Fixes repo membership check in API (#4341)
Untested, since I can't compile (yet).
2018-07-05 15:14:56 +08:00
Fluf f035dcd4f2 Add Recaptcha functionality to Gitea (#4044) 2018-07-05 00:13:05 -04:00
Lauris BH 54fedd4070 Add default merge options when adding new repository (#4369) 2018-07-05 11:02:54 +08:00
GiteaBot 07063e3e11 [skip ci] Updated translations via Crowdin 2018-07-05 00:05:18 +00:00
Lauris BH 69e2ab1611 Allow administrator to create repository for any organization (#4368) 2018-07-05 01:51:02 +02:00
GiteaBot 4eae810d63 [skip ci] Updated translations via Crowdin 2018-07-04 22:53:06 +00:00
Lauris BH 4a8ee0b5cc
Check that repositories can only be migrated to own user or organizations (#4366)
* Repositories can only migrated to own user or organizations

* Add check for organization that user does not belong to

* Allow admin to migrate repositories for other users
2018-07-05 01:45:15 +03:00
Lunny Xiao b46066f17c fix repository last updated time update when delete a user who watched the repo (#4363) 2018-07-05 00:47:05 +03:00
GiteaBot 95f0f62ea4 [skip ci] Updated translations via Crowdin 2018-07-04 20:24:23 +00:00
Antoine GIRARD 74f9f98f78 Simply remove tidb and deps (#3993) 2018-07-04 16:23:23 -04:00
Matthew Richardson 2e3475f02c Fix typos in i18n variable names. (#4080) 2018-07-04 19:43:21 +08:00
GiteaBot df77ad31d5 [skip ci] Updated translations via Crowdin 2018-07-04 09:28:27 +00:00
techknowlogick c3bbf43970 Fix docker build (#4358)
/app/gitea/gitea is the file, and /usr/local/bin is where the symlink will be located
2018-07-04 12:27:30 +03:00
GiteaBot fc53f95f23 [skip ci] Updated translations via Crowdin 2018-07-04 00:45:44 +00:00
Tao Wang 823318bfbe Add missing path in the Docker app.ini template (#2181) 2018-07-03 20:44:46 -04:00
tarelda 5676f60cba Keep preseeded database password (#4284) 2018-07-03 20:09:55 -04:00
GiteaBot 3fed13b0ee [skip ci] Updated translations via Crowdin 2018-07-03 23:53:28 +00:00
cezar97 51ba3df5ff Add `noreferrer` to rel='noopener` for <a> tags (#4328) 2018-07-03 19:52:36 -04:00
ucodi 4b654ad17f Update notification icon (#4343) 2018-07-03 19:16:46 -04:00
Pofilo 5d1a6382b6 #4354 Fix translation (#4355) 2018-07-03 19:03:31 -04:00
Clar Charr c71ee33057 Increase default TOTP secret size to 320 bits (#4287) 2018-07-03 18:10:35 -04:00
techknowlogick 9d4c1ddfa1
Dep upgrade mysql lib (#4161)
*  update gopkg file to add sql dep
2018-07-03 17:58:31 -04:00
758 changed files with 25577 additions and 170632 deletions

View File

@ -22,7 +22,7 @@ pipeline:
branch: [ master ] branch: [ master ]
update-translations: update-translations:
image: alpine:3.6 image: alpine:3.7
commands: commands:
- mv ./options/locale/locale_en-US.ini ./options/ - mv ./options/locale/locale_en-US.ini ./options/
- sed -i -e 's/="/=/g' -e 's/"$$//g' ./options/locale/*.ini - sed -i -e 's/="/=/g' -e 's/"$$//g' ./options/locale/*.ini
@ -56,7 +56,7 @@ pipeline:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
build-without-gcc: build-without-gcc:
image: golang:1.8 image: golang:1.9
pull: true pull: true
commands: commands:
- go build -o gitea_no_gcc # test if build succeeds without the sqlite tag - go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
@ -64,7 +64,7 @@ pipeline:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
build: build:
image: golang:1.10 image: golang:1.11
pull: true pull: true
environment: environment:
TAGS: bindata sqlite TAGS: bindata sqlite
@ -75,7 +75,7 @@ pipeline:
- make lint - make lint
- make fmt-check - make fmt-check
- make swagger-check - make swagger-check
# - make swagger-validate - make swagger-validate
- make misspell-check - make misspell-check
- make test-vendor - make test-vendor
- make build - make build
@ -83,7 +83,7 @@ pipeline:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
test: test:
image: golang:1.10 image: golang:1.11
pull: true pull: true
group: test group: test
environment: environment:
@ -95,7 +95,7 @@ pipeline:
branch: [ master ] branch: [ master ]
test: test:
image: golang:1.10 image: golang:1.11
pull: true pull: true
group: test group: test
environment: environment:
@ -107,7 +107,7 @@ pipeline:
branch: [ release/* ] branch: [ release/* ]
test: test:
image: golang:1.10 image: golang:1.11
pull: true pull: true
group: test group: test
environment: environment:
@ -130,7 +130,7 @@ pipeline:
# event: [ push, tag, pull_request ] # event: [ push, tag, pull_request ]
test-mysql: test-mysql:
image: golang:1.10 image: golang:1.11
pull: true pull: true
group: test group: test
environment: environment:
@ -145,7 +145,7 @@ pipeline:
branch: [ master ] branch: [ master ]
test-mysql: test-mysql:
image: golang:1.10 image: golang:1.11
pull: true pull: true
group: test group: test
environment: environment:
@ -159,7 +159,7 @@ pipeline:
event: [ tag ] event: [ tag ]
test-pgsql: test-pgsql:
image: golang:1.10 image: golang:1.11
pull: true pull: true
group: test group: test
environment: environment:
@ -173,7 +173,7 @@ pipeline:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
generate-coverage: generate-coverage:
image: golang:1.10 image: golang:1.11
pull: true pull: true
environment: environment:
TAGS: bindata TAGS: bindata
@ -203,7 +203,7 @@ pipeline:
when: when:
event: [ push, tag ] event: [ push, tag ]
build_docs: build-docs:
image: webhippie/hugo:latest image: webhippie/hugo:latest
pull: true pull: true
commands: commands:
@ -212,26 +212,12 @@ pipeline:
- make clean - make clean
- make build - make build
docker_docs: publish-docs:
image: plugins/docker:17.05 image: lucap/drone-netlify:latest
pull: true pull: true
secrets: [ docker_username, docker_password ] secrets: [ netlify_token ]
repo: gitea/docs site_id: d2260bae-7861-4c02-8646-8f6440b12672
context: docs path: docs/public/
dockerfile: docs/Dockerfile
tags: [ '${DRONE_BRANCH##release/v}' ]
when:
event: [ push ]
branch: [ release/* ]
docker_docs:
image: plugins/docker:17.05
pull: true
secrets: [ docker_username, docker_password ]
repo: gitea/docs
context: docs
dockerfile: docs/Dockerfile
tags: [ 'latest' ]
when: when:
event: [ push ] event: [ push ]
branch: [ master ] branch: [ master ]

51
BSDmakefile Normal file
View File

@ -0,0 +1,51 @@
# GNU makefile proxy script for BSD make
# Written and maintained by Mahmoud Al-Qudsi <mqudsi@neosmart.net>
# Copyright NeoSmart Technologies <https://neosmart.net/> 2014-2018
# Obtain updates from <https://github.com/neosmart/gmake-proxy>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
JARG =
GMAKE = "gmake"
#When gmake is called from another make instance, -w is automatically added
#which causes extraneous messages about directory changes to be emitted.
#--no-print-directory silences these messages.
GARGS = "--no-print-directory"
.if "$(.MAKE.JOBS)" != ""
JARG = -j$(.MAKE.JOBS)
.endif
#by default bmake will cd into ./obj first
.OBJDIR: ./
.PHONY: FRC
$(.TARGETS): FRC
$(GMAKE) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
.DONE .DEFAULT: .SILENT
$(GMAKE) $(GARGS) $(.TARGETS:S,.DONE,,) $(JARG)
.ERROR: .SILENT
if ! which $(GMAKE) > /dev/null; then \
echo "GNU Make is required!"; \
fi

View File

@ -58,3 +58,4 @@ CMD ["/bin/s6-svscan", "/etc/s6"]
COPY docker / COPY docker /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea

526
Gopkg.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ ignored = ["google.golang.org/appengine*"]
name = "code.gitea.io/sdk" name = "code.gitea.io/sdk"
[[constraint]] [[constraint]]
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94" revision = "12dd70caea0268ac0d6c2707d0611ef601e7c64e"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
[[constraint]] [[constraint]]
@ -30,16 +30,15 @@ ignored = ["google.golang.org/appengine*"]
revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d" revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
name = "golang.org/x/net" name = "golang.org/x/net"
[[constraint]]
#version = "v1.0.0"
revision = "33197485abe227dcb254644cf5081c9a3c281669"
name = "github.com/pingcap/tidb"
[[override]] [[override]]
name = "github.com/go-xorm/xorm" name = "github.com/go-xorm/xorm"
#version = "0.6.5" #version = "0.6.5"
revision = "ad69f7d8f0861a29438154bb0a20b60501298480" revision = "ad69f7d8f0861a29438154bb0a20b60501298480"
[[override]]
name = "github.com/go-sql-driver/mysql"
revision = "d523deb1b23d913de5bdada721a6071e71283618"
[[override]] [[override]]
name = "github.com/gorilla/mux" name = "github.com/gorilla/mux"
revision = "757bef944d0f21880861c2dd9c871ca543023cba" revision = "757bef944d0f21880861c2dd9c871ca543023cba"

View File

@ -22,5 +22,5 @@ Peter Žeby <morlinest@gmail.com> (@morlinest)
Matti Ranta <matti@mdranta.net> (@techknowlogick) Matti Ranta <matti@mdranta.net> (@techknowlogick)
Michael Lustfield <mtecknology@debian.org> (@MTecknology) Michael Lustfield <mtecknology@debian.org> (@MTecknology)
Jonas Franz <info@jonasfranz.software> (@JonasFranzDEV) Jonas Franz <info@jonasfranz.software> (@JonasFranzDEV)
Flynn Lufmons <fluf@warpmail.net> (@flufmonster)
Alexey Terentyev <axifnx@gmail.com> (@axifive) Alexey Terentyev <axifnx@gmail.com> (@axifive)
Lanre Adelowo <yo@lanre.wtf> (@adelowo)

View File

@ -42,6 +42,10 @@ TAGS ?=
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'gitea-temp') TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'gitea-temp')
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_S_TMPL := s|"basePath":\s*"/api/v1"|"basePath": "{{AppSubUrl}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath":\s*"{{AppSubUrl}}/api/v1"|"basePath": "/api/v1"|g
TEST_MYSQL_HOST ?= mysql:3306 TEST_MYSQL_HOST ?= mysql:3306
TEST_MYSQL_DBNAME ?= testgitea TEST_MYSQL_DBNAME ?= testgitea
TEST_MYSQL_USERNAME ?= root TEST_MYSQL_USERNAME ?= root
@ -57,6 +61,9 @@ else
EXECUTABLE := gitea EXECUTABLE := gitea
endif endif
# $(call strip-suffix,filename)
strip-suffix = $(firstword $(subst ., ,$(1)))
.PHONY: all .PHONY: all
all: build all: build
@ -91,11 +98,12 @@ generate-swagger:
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \ $(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \
fi fi
swagger generate spec -o ./public/swagger.v1.json swagger generate spec -o './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
.PHONY: swagger-check .PHONY: swagger-check
swagger-check: generate-swagger swagger-check: generate-swagger
@diff=$$(git diff public/swagger.v1.json); \ @diff=$$(git diff '$(SWAGGER_SPEC)'); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make generate-swagger' and commit the result:"; \ echo "Please run 'make generate-swagger' and commit the result:"; \
echo "$${diff}"; \ echo "$${diff}"; \
@ -107,7 +115,9 @@ swagger-validate:
@hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \ $(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \
fi fi
swagger validate ./public/swagger.v1.json $(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
swagger validate './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
.PHONY: errcheck .PHONY: errcheck
errcheck: errcheck:
@ -303,7 +313,7 @@ public/js/index.js: $(JAVASCRIPTS)
.PHONY: stylesheets-check .PHONY: stylesheets-check
stylesheets-check: generate-stylesheets stylesheets-check: generate-stylesheets
@diff=$$(git diff public/css/index.css); \ @diff=$$(git diff public/css/*); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make generate-stylesheets' and commit the result:"; \ echo "Please run 'make generate-stylesheets' and commit the result:"; \
echo "$${diff}"; \ echo "$${diff}"; \
@ -313,6 +323,7 @@ stylesheets-check: generate-stylesheets
.PHONY: generate-stylesheets .PHONY: generate-stylesheets
generate-stylesheets: generate-stylesheets:
node_modules/.bin/lessc --clean-css public/less/index.less public/css/index.css node_modules/.bin/lessc --clean-css public/less/index.less public/css/index.css
$(foreach file, $(filter-out public/less/themes/_base.less, $(wildcard public/less/themes/*)),node_modules/.bin/lessc --clean-css public/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;)
.PHONY: swagger-ui .PHONY: swagger-ui
swagger-ui: swagger-ui:

View File

@ -29,7 +29,7 @@ This project has been
From the root of the source tree, run: From the root of the source tree, run:
make generate all TAGS="bindata" make generate all
More info: https://docs.gitea.io/en-us/install-from-source/ More info: https://docs.gitea.io/en-us/install-from-source/
@ -90,6 +90,10 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
Gitea is pronounced [/ɡɪti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g. Gitea is pronounced [/ɡɪti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
**Why is this not hosted on a Gitea instance?**
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
## License ## License
This project is licensed under the MIT License. This project is licensed under the MIT License.

View File

@ -11,13 +11,6 @@
[![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest) [![GitHub release](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest)
[![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea) [![Become a backer/sponsor of gitea](https://opencollective.com/gitea/tiers/backer/badge.svg?label=backer&color=brightgreen)](https://opencollective.com/gitea)
| | | |
|:---:|:---:|:---:|
|![Dashboard](https://image.ibb.co/dms6DG/1.png)|![Repository](https://image.ibb.co/m6MSLw/2.png)|![Commits History](https://image.ibb.co/cjrSLw/3.png)|
|![Branches](https://image.ibb.co/e6vbDG/4.png)|![Issues](https://image.ibb.co/bJTJSb/5.png)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)|
|![Releases](https://image.ibb.co/cUzgfw/7.png)|![Activity](https://image.ibb.co/eZgGDG/8.png)|![Wiki](https://image.ibb.co/dYV9YG/9.png)|
|![Diff](https://image.ibb.co/ewA9YG/10.png)|![Organization](https://image.ibb.co/ceOwDG/11.png)|![Profile](https://image.ibb.co/c44Q7b/12.png)|
## 目标 ## 目标
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86amd64还包括 ARM 和 PowerPC。 Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86amd64还包括 ARM 和 PowerPC。
@ -47,3 +40,12 @@ Fork -> Patch -> Push -> Pull Request
## 授权许可 ## 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) 文件中。 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/master/LICENSE) 文件中。
## 截图
| | | |
|:---:|:---:|:---:|
|![Dashboard](https://image.ibb.co/dms6DG/1.png)|![Repository](https://image.ibb.co/m6MSLw/2.png)|![Commits History](https://image.ibb.co/cjrSLw/3.png)|
|![Branches](https://image.ibb.co/e6vbDG/4.png)|![Issues](https://image.ibb.co/bJTJSb/5.png)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)|
|![Releases](https://image.ibb.co/cUzgfw/7.png)|![Activity](https://image.ibb.co/eZgGDG/8.png)|![Wiki](https://image.ibb.co/dYV9YG/9.png)|
|![Diff](https://image.ibb.co/ewA9YG/10.png)|![Organization](https://image.ibb.co/ceOwDG/11.png)|![Profile](https://image.ibb.co/c44Q7b/12.png)|

View File

@ -7,9 +7,12 @@ package cmd
import ( import (
"fmt" "fmt"
"os"
"text/tabwriter"
"code.gitea.io/git" "code.gitea.io/git"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -26,6 +29,7 @@ var (
subcmdChangePassword, subcmdChangePassword,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate, subcmdRegenerate,
subcmdAuth,
}, },
} }
@ -121,6 +125,121 @@ var (
}, },
}, },
} }
subcmdAuth = cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
Subcommands: []cli.Command{
microcmdAuthAddOauth,
microcmdAuthUpdateOauth,
microcmdAuthList,
microcmdAuthDelete,
},
}
microcmdAuthList = cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
},
}
idFlag = cli.Int64Flag{
Name: "id",
Usage: "ID of OAuth authentication source",
}
microcmdAuthDelete = cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Action: runDeleteAuth,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
idFlag,
},
}
oauthCLIFlags = []cli.Flag{
cli.StringFlag{
Name: "config, c",
Value: "custom/conf/app.ini",
Usage: "Custom configuration file path",
},
cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
cli.StringFlag{
Name: "provider",
Value: "",
Usage: "OAuth2 Provider",
},
cli.StringFlag{
Name: "key",
Value: "",
Usage: "Client ID (Key)",
},
cli.StringFlag{
Name: "secret",
Value: "",
Usage: "Client Secret",
},
cli.StringFlag{
Name: "auto-discover-url",
Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
},
cli.StringFlag{
Name: "use-custom-urls",
Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
},
cli.StringFlag{
Name: "custom-auth-url",
Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
},
cli.StringFlag{
Name: "custom-token-url",
Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)",
},
cli.StringFlag{
Name: "custom-profile-url",
Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
},
cli.StringFlag{
Name: "custom-email-url",
Value: "",
Usage: "Use a custom Email URL (option for GitHub)",
},
}
microcmdAuthUpdateOauth = cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth,
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
}
microcmdAuthAddOauth = cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: runAddOauth,
Flags: oauthCLIFlags,
}
) )
func runChangePassword(c *cli.Context) error { func runChangePassword(c *cli.Context) error {
@ -262,3 +381,170 @@ func runRegenerateKeys(c *cli.Context) error {
} }
return models.RewriteAllPublicKeys() return models.RewriteAllPublicKeys()
} }
func parseOAuth2Config(c *cli.Context) *models.OAuth2Config {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
TokenURL: c.String("custom-token-url"),
AuthURL: c.String("custom-auth-url"),
ProfileURL: c.String("custom-profile-url"),
EmailURL: c.String("custom-email-url"),
}
} else {
customURLMapping = nil
}
return &models.OAuth2Config{
Provider: c.String("provider"),
ClientID: c.String("key"),
ClientSecret: c.String("secret"),
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping,
}
}
func runAddOauth(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if err := initDB(); err != nil {
return err
}
if err := models.CreateLoginSource(&models.LoginSource{
Type: models.LoginOAuth2,
Name: c.String("name"),
IsActived: true,
Cfg: parseOAuth2Config(c),
}); err != nil {
return err
}
return nil
}
func runUpdateOauth(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if !c.IsSet("id") {
return fmt.Errorf("--id flag is missing")
}
if err := initDB(); err != nil {
return err
}
source, err := models.GetLoginSourceByID(c.Int64("id"))
if err != nil {
return err
}
oAuth2Config := source.OAuth2()
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("provider") {
oAuth2Config.Provider = c.String("provider")
}
if c.IsSet("key") {
oAuth2Config.ClientID = c.String("key")
}
if c.IsSet("secret") {
oAuth2Config.ClientSecret = c.String("secret")
}
if c.IsSet("auto-discover-url") {
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
}
// update custom URL mapping
var customURLMapping *oauth2.CustomURLMapping
if oAuth2Config.CustomURLMapping != nil {
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
customURLMapping.TokenURL = c.String("custom-token-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
customURLMapping.AuthURL = c.String("custom-auth-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
customURLMapping.ProfileURL = c.String("custom-profile-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
customURLMapping.EmailURL = c.String("custom-email-url")
}
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config
if err := models.UpdateSource(source); err != nil {
return err
}
return nil
}
func runListAuth(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if err := initDB(); err != nil {
return err
}
loginSources, err := models.LoginSources()
if err != nil {
return err
}
// loop through each source and print
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
fmt.Fprintf(w, "ID\tName\tType\tEnabled")
for _, source := range loginSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived)
}
w.Flush()
return nil
}
func runDeleteAuth(c *cli.Context) error {
if c.IsSet("config") {
setting.CustomConf = c.String("config")
}
if !c.IsSet("id") {
return fmt.Errorf("--id flag is missing")
}
if err := initDB(); err != nil {
return err
}
source, err := models.GetLoginSourceByID(c.Int64("id"))
if err != nil {
return err
}
if err = models.DeleteSource(source); err != nil {
return err
}
return nil
}

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -42,6 +43,9 @@ var CmdServ = cli.Command{
Value: "custom/conf/app.ini", Value: "custom/conf/app.ini",
Usage: "Custom configuration file path", Usage: "Custom configuration file path",
}, },
cli.BoolFlag{
Name: "enable-pprof",
},
}, },
} }
@ -143,6 +147,18 @@ func runServ(c *cli.Context) error {
username := strings.ToLower(rr[0]) username := strings.ToLower(rr[0])
reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
if setting.EnablePprof || c.Bool("enable-pprof") {
if err := os.MkdirAll(setting.PprofDataPath, os.ModePerm); err != nil {
fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err)
}
stopCPUProfiler := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username)
defer func() {
stopCPUProfiler()
pprof.DumpMemProfileForUsername(setting.PprofDataPath, username)
}()
}
isWiki := false isWiki := false
unitType := models.UnitTypeCode unitType := models.UnitTypeCode
if strings.HasSuffix(reponame, ".wiki") { if strings.HasSuffix(reponame, ".wiki") {

View File

@ -5,6 +5,7 @@
package cmd package cmd
import ( import (
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -22,6 +23,7 @@ import (
"github.com/Unknwon/com" "github.com/Unknwon/com"
context2 "github.com/gorilla/context" context2 "github.com/gorilla/context"
"github.com/urfave/cli" "github.com/urfave/cli"
"golang.org/x/crypto/acme/autocert"
ini "gopkg.in/ini.v1" ini "gopkg.in/ini.v1"
) )
@ -71,6 +73,33 @@ func runHTTPRedirector() {
} }
} }
func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) error {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(domain),
Cache: autocert.DirCache(directory),
Email: email,
}
go http.ListenAndServe(listenAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validatio happens here)
server := &http.Server{
Addr: listenAddr,
Handler: m,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
return server.ListenAndServeTLS("", "")
}
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Use HTTPS", http.StatusBadRequest)
return
}
target := setting.AppURL + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusFound)
}
func runWeb(ctx *cli.Context) error { func runWeb(ctx *cli.Context) error {
if ctx.IsSet("config") { if ctx.IsSet("config") {
setting.CustomConf = ctx.String("config") setting.CustomConf = ctx.String("config")
@ -143,6 +172,10 @@ func runWeb(ctx *cli.Context) error {
case setting.HTTP: case setting.HTTP:
err = runHTTP(listenAddr, context2.ClearHandler(m)) err = runHTTP(listenAddr, context2.ClearHandler(m))
case setting.HTTPS: case setting.HTTPS:
if setting.EnableLetsEncrypt {
err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
break
}
if setting.RedirectOtherPort { if setting.RedirectOtherPort {
go runHTTPRedirector() go runHTTPRedirector()
} }

View File

@ -60,6 +60,10 @@ FILE_MAX_SIZE = 3
; Max number of files per upload. Defaults to 5 ; Max number of files per upload. Defaults to 5
MAX_FILES = 5 MAX_FILES = 5
[repository.pull-request]
; List of prefixes used in Pull Request title to mark them as Work In Progress
WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP]
[ui] [ui]
; Number of repositories that are displayed on one explore page ; Number of repositories that are displayed on one explore page
EXPLORE_PAGING_NUM = 20 EXPLORE_PAGING_NUM = 20
@ -67,6 +71,10 @@ EXPLORE_PAGING_NUM = 20
ISSUE_PAGING_NUM = 10 ISSUE_PAGING_NUM = 10
; Number of maximum commits displayed in one activity feed ; Number of maximum commits displayed in one activity feed
FEED_MAX_COMMIT_NUM = 5 FEED_MAX_COMMIT_NUM = 5
; Number of maximum commits displayed in commit graph.
GRAPH_MAX_COMMIT_NUM = 100
; Number of line of codes shown for a code comment
CODE_COMMENT_LINES = 4
; Value of `theme-color` meta tag, used by Android >= 5.0 ; Value of `theme-color` meta tag, used by Android >= 5.0
; An invalid color like "none" or "disable" will have the default style ; An invalid color like "none" or "disable" will have the default style
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android ; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
@ -75,13 +83,15 @@ THEME_COLOR_META_TAG = `#6cc644`
MAX_DISPLAY_FILE_SIZE = 8388608 MAX_DISPLAY_FILE_SIZE = 8388608
; Whether the email of the user should be shown in the Explore Users page ; Whether the email of the user should be shown in the Explore Users page
SHOW_USER_EMAIL = true SHOW_USER_EMAIL = true
; Set the default theme for the Gitea install
DEFAULT_THEME = gitea
[ui.admin] [ui.admin]
; Number of users that are displayed on one page ; Number of users that are displayed on one page
USER_PAGING_NUM = 50 USER_PAGING_NUM = 50
; Number of repos that are displayed on one page ; Number of repos that are displayed on one page
REPO_PAGING_NUM = 50 REPO_PAGING_NUM = 50
; Number of notices that are displayed on in one page ; Number of notices that are displayed on one page
NOTICE_PAGING_NUM = 25 NOTICE_PAGING_NUM = 25
; Number of organizations that are displayed on one page ; Number of organizations that are displayed on one page
ORG_PAGING_NUM = 50 ORG_PAGING_NUM = 50
@ -181,6 +191,12 @@ STATIC_ROOT_PATH =
APP_DATA_PATH = data APP_DATA_PATH = data
; Application level GZIP support ; Application level GZIP support
ENABLE_GZIP = false ENABLE_GZIP = false
; Application profiling (memory and cpu)
; For "web" command it listens on localhost:6060
; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)_<username>_<temporary id>
ENABLE_PPROF = false
; PPROF_DATA_PATH, use an absolute path when you start gitea as service
PPROF_DATA_PATH = data/tmp/pprof
; Landing page, can be "home", "explore", or "organizations" ; Landing page, can be "home", "explore", or "organizations"
LANDING_PAGE = home LANDING_PAGE = home
; Enables git-lfs support. true or false, default is false. ; Enables git-lfs support. true or false, default is false.
@ -207,9 +223,10 @@ NAME = gitea
USER = root USER = root
; Use PASSWD = `your password` for quoting if you use special characters in the password. ; Use PASSWD = `your password` for quoting if you use special characters in the password.
PASSWD = PASSWD =
; For "postgres" only, either "disable", "require" or "verify-full" ; For Postgres, either "disable" (default), "require", or "verify-full"
; For MySQL, either "false" (default), "true", or "skip-verify"
SSL_MODE = disable SSL_MODE = disable
; For "sqlite3" and "tidb", use absolute path when you start gitea as service ; For "sqlite3" and "tidb", use an absolute path when you start gitea as service
PATH = data/gitea.db PATH = data/gitea.db
; For "sqlite3" only. Query timeout ; For "sqlite3" only. Query timeout
SQLITE_TIMEOUT = 500 SQLITE_TIMEOUT = 500
@ -301,13 +318,22 @@ ENABLE_NOTIFY_MAIL = false
ENABLE_REVERSE_PROXY_AUTHENTICATION = false ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; Enable captcha validation for registration ; Enable captcha validation for registration
ENABLE_CAPTCHA = true ENABLE_CAPTCHA = false
; Type of captcha you want to use. Options: image, recaptcha
CAPTCHA_TYPE = image
; Enable recaptcha to use Google's recaptcha service
; Go to https://www.google.com/recaptcha/admin to sign up for a key
RECAPTCHA_SECRET =
RECAPTCHA_SITEKEY =
; Default value for KeepEmailPrivate ; Default value for KeepEmailPrivate
; Each new user will get the value of this setting copied into their profile ; Each new user will get the value of this setting copied into their profile
DEFAULT_KEEP_EMAIL_PRIVATE = false DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization ; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting ; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true DEFAULT_ALLOW_CREATE_ORGANIZATION = true
; Default value for EnableDependencies
; Repositories will use depencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
; Enable Timetracking ; Enable Timetracking
ENABLE_TIMETRACKING = true ENABLE_TIMETRACKING = true
; Default value for EnableTimetracking ; Default value for EnableTimetracking
@ -568,8 +594,8 @@ DEFAULT_INTERVAL = 8h
MIN_INTERVAL = 10m MIN_INTERVAL = 10m
[api] [api]
; Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true. ; Enables Swagger. True or false; default is true.
ENABLE_SWAGGER_ENDPOINT = true ENABLE_SWAGGER = true
; Max number of items in a page ; Max number of items in a page
MAX_RESPONSE_ITEMS = 50 MAX_RESPONSE_ITEMS = 50

View File

@ -29,4 +29,3 @@ AllowUsers git
Banner none Banner none
Subsystem sftp /usr/lib/ssh/sftp-server Subsystem sftp /usr/lib/ssh/sftp-server
UsePrivilegeSeparation no

View File

@ -4,6 +4,9 @@ RUN_MODE = $RUN_MODE
[repository] [repository]
ROOT = /data/git/repositories ROOT = /data/git/repositories
[repository.local]
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
[repository.upload] [repository.upload]
TEMP_PATH = /data/gitea/uploads TEMP_PATH = /data/gitea/uploads
@ -14,6 +17,7 @@ HTTP_PORT = $HTTP_PORT
ROOT_URL = $ROOT_URL ROOT_URL = $ROOT_URL
DISABLE_SSH = $DISABLE_SSH DISABLE_SSH = $DISABLE_SSH
SSH_PORT = $SSH_PORT SSH_PORT = $SSH_PORT
LFS_CONTENT_PATH = /data/git/lfs
[database] [database]
PATH = /data/gitea/gitea.db PATH = /data/gitea/gitea.db
@ -23,6 +27,9 @@ NAME = $DB_NAME
USER = $DB_USER USER = $DB_USER
PASSWD = $DB_PASSWD PASSWD = $DB_PASSWD
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session] [session]
PROVIDER_CONFIG = /data/gitea/sessions PROVIDER_CONFIG = /data/gitea/sessions

1
docs/.gitignore vendored
View File

@ -1,2 +1,3 @@
public/ public/
templates/swagger/v1_json.tmpl
themes/ themes/

View File

@ -1,22 +0,0 @@
# build stage
FROM golang:alpine AS build-env
RUN apk add --no-cache git
RUN go get -d -v github.com/mholt/caddy/caddy github.com/pedronasser/caddy-search github.com/simia-tech/caddy-locale
WORKDIR /go/src/github.com/mholt/caddy/caddy
RUN sed -i '/This is where other plugins get plugged in (imported)/a _ "github.com/pedronasser/caddy-search"' caddymain/run.go \
&& sed -i '/This is where other plugins get plugged in (imported)/a _ "github.com/simia-tech/caddy-locale"' caddymain/run.go \
&& go install -v . \
&& /go/bin/caddy -version
FROM alpine:edge
EXPOSE 80
RUN apk add --no-cache wget mailcap ca-certificates
COPY --from=build-env /go/bin/caddy /usr/sbin/caddy
COPY docker/caddy.conf /etc/caddy.conf
COPY public /srv/www
CMD ["/usr/sbin/caddy", "-conf", "/etc/caddy.conf"]

View File

@ -31,7 +31,7 @@ menu:
post: active post: active
- name: API - name: API
url: https://try.gitea.io/api/swagger url: https://try.gitea.io/api/swagger
weight: 25 weight: 45
pre: plug pre: plug
- name: Blog - name: Blog
url: https://blog.gitea.io/ url: https://blog.gitea.io/
@ -79,7 +79,7 @@ languages:
post: active post: active
- name: API - name: API
url: https://try.gitea.io/api/swagger url: https://try.gitea.io/api/swagger
weight: 25 weight: 45
pre: plug pre: plug
- name: 博客 - name: 博客
url: https://blog.gitea.io/ url: https://blog.gitea.io/
@ -122,7 +122,7 @@ languages:
post: active post: active
- name: API - name: API
url: https://try.gitea.io/api/swagger url: https://try.gitea.io/api/swagger
weight: 25 weight: 45
pre: plug pre: plug
- name: 部落格 - name: 部落格
url: https://blog.gitea.io/ url: https://blog.gitea.io/
@ -165,7 +165,7 @@ languages:
post: active post: active
- name: API - name: API
url: https://try.gitea.io/api/swagger url: https://try.gitea.io/api/swagger
weight: 25 weight: 45
pre: plug pre: plug
- name: Blog - name: Blog
url: https://blog.gitea.io/ url: https://blog.gitea.io/
@ -208,7 +208,7 @@ languages:
post: active post: active
- name: API - name: API
url: https://try.gitea.io/api/swagger url: https://try.gitea.io/api/swagger
weight: 25 weight: 45
pre: plug pre: plug
- name: Blog - name: Blog
url: https://blog.gitea.io/ url: https://blog.gitea.io/
@ -241,17 +241,17 @@ languages:
menu: menu:
page: page:
- name: Site - name: Site
url: /fr-fr/ url: https://gitea.io/en-us/
weight: 10 weight: 10
pre: home pre: home
post: active post: active
- name: Documentation - name: Documentation
url: https://docs.gitea.io/fr-fr/ url: /fr-fr/
weight: 20 weight: 20
pre: question pre: question
- name: API - name: API
url: https://try.gitea.io/api/swagger url: https://try.gitea.io/api/swagger
weight: 25 weight: 45
pre: plug pre: plug
- name: Blog - name: Blog
url: https://blog.gitea.io/ url: https://blog.gitea.io/

View File

@ -73,3 +73,7 @@ using BasicAuth, as follows:
$ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens $ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens
[{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}] [{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}]
``` ```
## Sudo
The API allows admin users to sudo API requests as another user. Simply add either a `sudo=` parameter or `Sudo:` request header with the username of the user to sudo.

View File

@ -63,11 +63,17 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `USE_COMPAT_SSH_URI`: **false**: Force ssh:// clone url instead of scp-style uri when - `USE_COMPAT_SSH_URI`: **false**: Force ssh:// clone url instead of scp-style uri when
default SSH port is used. default SSH port is used.
### Repository - Pull Request (`repository.pull-request`)
- `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request
title to mark them as Work In Progress
## UI (`ui`) ## UI (`ui`)
- `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page. - `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page.
- `ISSUE_PAGING_NUM`: **10**: Number of issues that are shown in one page (for all pages that list issues). - `ISSUE_PAGING_NUM`: **10**: Number of issues that are shown in one page (for all pages that list issues).
- `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed. - `FEED_MAX_COMMIT_NUM`: **5**: Number of maximum commits shown in one activity feed.
- `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph.
- `DEFAULT_THEME`: **gitea**: \[gitea, arc-green\]: Set the default theme for the Gitea install.
### UI - Admin (`ui.admin`) ### UI - Admin (`ui.admin`)
@ -119,6 +125,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, redirects http requests - `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, redirects http requests
on another (https) port. on another (https) port.
- `PORT_TO_REDIRECT`: **80**: Port used when `REDIRECT_OTHER_PORT` is true. - `PORT_TO_REDIRECT`: **80**: Port used when `REDIRECT_OTHER_PORT` is true.
- `ENABLE_LETSENCRYPT`: **false**: If enabled you must set `DOMAIN` to valid internet facing domain (ensure DNS is set and port 80 is accessible by letsencrypt validation server).
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf).
- `LETSENCRYPT_ACCEPTTOS`: **false**: This is an explicit check that you accept the terms of service for Let's Encrypt.
- `LETSENCRYPT_DIRECTORY`: **https**: Directory that Letsencrypt will use to cache information such as certs and private keys.
- `LETSENCRYPT_EMAIL`: **email@example.com**: Email used by Letsencrypt to notify about problems with issued certificates. (No default)
## Database (`database`) ## Database (`database`)
@ -127,7 +138,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `NAME`: **gitea**: Database name. - `NAME`: **gitea**: Database name.
- `USER`: **root**: Database username. - `USER`: **root**: Database username.
- `PASSWD`: **\<empty\>**: Database user password. Use \`your password\` for quoting if you use special characters in the password. - `PASSWD`: **\<empty\>**: Database user password. Use \`your password\` for quoting if you use special characters in the password.
- `SSL_MODE`: **disable**: For PostgreSQL only. - `SSL_MODE`: **disable**: For PostgreSQL and MySQL only.
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. - `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
- `LOG_SQL`: **true**: Log the executed SQL. - `LOG_SQL`: **true**: Log the executed SQL.
@ -177,7 +188,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication. - `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication.
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration - `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration
for reverse authentication. for reverse authentication.
- `ENABLE_CAPTCHA`: **true**: Enable this to use captcha validation for registration. - `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha.
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
## Webhook (`webhook`) ## Webhook (`webhook`)
@ -202,8 +217,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
This is common on linux systems. This is common on linux systems.
- Note that enabling sendmail will ignore all other `mailer` settings except `ENABLED`, - Note that enabling sendmail will ignore all other `mailer` settings except `ENABLED`,
`FROM` and `SENDMAIL_PATH`. `FROM` and `SENDMAIL_PATH`.
- `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system. (can be - `SENDMAIL_PATH`: **sendmail**: The location of sendmail on the operating system (can be
command or full path) command or full path).
## Cache (`cache`) ## Cache (`cache`)
@ -227,7 +242,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
`http://cn.gravatar.com/avatar/`. `http://cn.gravatar.com/avatar/`.
- `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only. - `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only.
- `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see - `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see
http://www.libravatar.org) [http://www.libravatar.org](http://www.libravatar.org)).
- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store local and cached files. - `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store local and cached files.
## Attachment (`attachment`) ## Attachment (`attachment`)
@ -279,10 +294,17 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view. - `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`. - `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`.
## Git - Timeout settings (`git.timeout`)
- `MIGRATE`: **600**: Migrate external repositories timeout seconds.
- `MIRROR`: **300**: Mirror external repositories timeout seconds.
- `CLONE`: **300**: Git clone from internal repositories timeout seconds.
- `PULL`: **300**: Git pull from internal repositories timeout seconds.
- `GC`: **60**: Git repository GC timeout seconds.
## API (`api`) ## API (`api`)
- `ENABLE_SWAGGER_ENDPOINT`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true. - `ENABLE_SWAGGER_ENDPOINT`: **true**: Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
- `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page - `MAX_RESPONSE_ITEMS`: **50**: Max number of items in a page.
## i18n (`i18n`) ## i18n (`i18n`)

View File

@ -187,6 +187,13 @@ menu:
- `MAX_GIT_DIFF_FILES`: 比较视图中的最大现实文件数目。 - `MAX_GIT_DIFF_FILES`: 比较视图中的最大现实文件数目。
- `GC_ARGS`: 执行 `git gc` 命令的参数, 比如: `--aggressive --auto` - `GC_ARGS`: 执行 `git gc` 命令的参数, 比如: `--aggressive --auto`
## Git - 超时设置 (`git.timeout`)
- `MIGRATE`: **600**: 迁移外部仓库时的超时时间,单位秒
- `MIRROR`: **300**: 镜像外部仓库的超时时间,单位秒
- `CLONE`: **300**: 内部仓库间克隆的超时时间,单位秒
- `PULL`: **300**: 内部仓库间拉取的超时时间,单位秒
- `GC`: **60**: git仓库GC的超时时间单位秒
## markup (`markup`) ## markup (`markup`)
外部渲染工具支持,你可以用你熟悉的文档渲染工具. 比如一下将新增一个名字为 `asciidoc` 的渲染工具which is followed `markup.` ini section. And there are some config items below. 外部渲染工具支持,你可以用你熟悉的文档渲染工具. 比如一下将新增一个名字为 `asciidoc` 的渲染工具which is followed `markup.` ini section. And there are some config items below.

View File

@ -59,7 +59,7 @@ to override can be found in the `templates` directory of Gitea source. Override
making a copy of the file under `custom/templates` using a full path structure making a copy of the file under `custom/templates` using a full path structure
matching source. matching source.
Any statement contained inside `{{` and `}}` are Gitea's templete syntax and Any statement contained inside `{{` and `}}` are Gitea's template syntax and
shouldn't be touched without fully understanding these components. shouldn't be touched without fully understanding these components.
### Adding links and tabs ### Adding links and tabs
@ -91,3 +91,7 @@ Apart from `extra_links.tmpl` and `extra_tabs.tmpl`, there are other useful temp
## Customizing gitignores, labels, licenses, locales, and readmes. ## Customizing gitignores, labels, licenses, locales, and readmes.
Place custom files in corresponding sub-folder under `custom/options`. Place custom files in corresponding sub-folder under `custom/options`.
## Customizing the look of Gitea
Gitea has two built-in themes, the default theme `gitea`, and a dark theme `arc-green`. To change the look of your Gitea install change the value of `DEFAULT_THEME` in the [ui](https://docs.gitea.io/en-us/config-cheat-sheet/#ui-ui) section of `app.ini` to another one of the available options.

View File

@ -66,4 +66,3 @@ For documentation about each of the variables available, refer to the
## Miscellaneous ## Miscellaneous
* `SKIP_MINWINSVC`: If set to 1, do not run as a service on Windows. * `SKIP_MINWINSVC`: If set to 1, do not run as a service on Windows.
* `ZOOKEEPER_PATH`: [Zookeeper](http://zookeeper.apache.org/) jar file path

View File

@ -37,6 +37,9 @@ _Symbols used in table:_
| Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | | Multiple OS support | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ |
| Easy upgrade process | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | | Easy upgrade process | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ |
| Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Markdown support | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Orgmode support | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ? |
| CSV support | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? |
| Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? |
| Static Git-powered pages | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Static Git-powered pages | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | | Integrated Git-powered wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Deploy Tokens | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
@ -53,7 +56,7 @@ _Symbols used in table:_
|---------|-------|------|-----------|-----------|-----------|-----------|--------------| |---------|-------|------|-----------|-----------|-----------|-----------|--------------|
| Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Repository topics | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Repository code search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Global code search | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ | | Global code search | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ |
| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | | ✓ | | Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | | ✓ |
| Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Group Milestones | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
@ -74,7 +77,7 @@ _Symbols used in table:_
| Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Issue templates | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Labels | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Time tracking | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Multiple assignees for issues | ✓ | ✘ | ✓ | | ✓ | ✘ | ✘ | | Multiple assignees for issues | ✓ | ✘ | ✓ | | ✓ | ✘ | ✘ |
| Related issues | ✘ | ✘ | | ✘ | ✓ | ✘ | ✘ | | Related issues | ✘ | ✘ | | ✘ | ✓ | ✘ | ✘ |
| Confidential issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Confidential issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | Comment reactions | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
@ -84,6 +87,7 @@ _Symbols used in table:_
| Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | Create new branches from issues | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Issue search | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Global issue search | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Global issue search | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Issue dependency | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
#### Pull/Merge requests #### Pull/Merge requests
@ -92,8 +96,8 @@ _Symbols used in table:_
| Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull/Merge requests | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Squash merging | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | | Squash merging | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ |
| Rebase merging | ✓ | ✓ | ✓ | ✘ | | ✘ | ✓ | | Rebase merging | ✓ | ✓ | ✓ | ✘ | | ✘ | ✓ |
| Pull/Merge request inline comments | | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull/Merge request inline comments | | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull/Merge request approval | | ✘ | | ✓ | ✓ | ✓ | ✓ | | Pull/Merge request approval | | ✘ | | ✓ | ✓ | ✓ | ✓ |
| Merge conflict resolution | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Merge conflict resolution | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Restrict push and merge access to certain users | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ | | Restrict push and merge access to certain users | ✓ | ✘ | ✓ | | ✓ | ✓ | ✓ |
| Revert specific commits or a merge request | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | Revert specific commits or a merge request | ✘ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |

View File

@ -21,10 +21,18 @@ the destination platform from the [downloads page](https://dl.gitea.io/gitea), c
the URL and replace the URL within the commands below: the URL and replace the URL within the commands below:
```sh ```sh
wget -O gitea https://dl.gitea.io/gitea/1.4.2/gitea-1.4.2-linux-amd64 wget -O gitea https://dl.gitea.io/gitea/1.5.0/gitea-1.5.0-linux-amd64
chmod +x gitea chmod +x gitea
``` ```
## Verify GPG signature
Gitea signs all binaries with a [GPG key](https://pgp.mit.edu/pks/lookup?op=vindex&fingerprint=on&search=0x2D9AE806EC1592E2) to prevent against unwanted modification of binaries. To validate the binary download the signature file which ends in `.asc` for the binary you downloaded and use the gpg command line tool.
```sh
gpg --keyserver pgp.mit.edu --recv 0x2D9AE806EC1592E2
gpg --verify gitea-1.5.0-linux-amd64.asc gitea-1.5.0-linux-amd64
```
## Test ## Test
After getting a binary, it can be tested with `./gitea web` or moved to a permanent After getting a binary, it can be tested with `./gitea web` or moved to a permanent
@ -82,6 +90,20 @@ cp gitea /usr/local/bin/gitea
See how to create [Linux service]({{< relref "run-as-service-in-ubuntu.en-us.md" >}}) See how to create [Linux service]({{< relref "run-as-service-in-ubuntu.en-us.md" >}})
## Updating to a new version
You can update to a new version of gitea by stopping gitea, replacing the binary at `/usr/local/bin/gitea` and restarting the instance.
The binary file name should not be changed during the update to avoid problems
in existing repositories.
It is recommended you do a [backup]({{< relref "doc/usage/backup-and-restore.en-us.md" >}}) before updating your installation.
If you have carried out the installation steps as described above, the binary should
have the generic name `gitea`. Do not change this, i.e. to include the version number.
See below for troubleshooting instructions to repair broken repositories after
an update of your gitea version.
## Troubleshooting ## Troubleshooting
### Old glibc versions ### Old glibc versions
@ -99,3 +121,25 @@ For errors like `702 runWeb()] [E] Failed to start server: listen tcp 0.0.0.0:30
bind: address already in use` gitea needs to be started on another free port. This bind: address already in use` gitea needs to be started on another free port. This
is possible using `./gitea web -p $PORT`. It's possible another instance of gitea is possible using `./gitea web -p $PORT`. It's possible another instance of gitea
is already running. is already running.
### Git error after updating to a new version of gitea
If the binary file name has been changed during the update to a new version of gitea,
git hooks in existing repositories will not work any more. In that case, a git
error will be displayed when pushing to the repository.
```
remote: ./hooks/pre-receive.d/gitea: line 2: [...]: No such file or directory
```
The `[...]` part of the error message will contain the path to your previous gitea
binary.
To solve this, go to the admin options and run the task `Resynchronize pre-receive,
update and post-receive hooks of all repositories` to update all hooks to contain
the new binary path. Please note that this overwrite all git hooks including ones
with customizations made.
If you aren't using the built-in to Gitea ssh server you will also need to re-write
the authorized key file by running the `Update the '.ssh/authorized_keys' file with
Gitea SSH keys.` task in the admin options.

View File

@ -64,3 +64,16 @@ bundled templates, options, plugins and themes are in `/usr/local/share/gitea`,
is in `/usr/local/etc/rc.d/gitea`. is in `/usr/local/etc/rc.d/gitea`.
To enable Gitea to run as a service, run `sysrc gitea_enable=YES` and start it with `service gitea start`. To enable Gitea to run as a service, run `sysrc gitea_enable=YES` and start it with `service gitea start`.
## Cloudron
Gitea is available as a 1-click install on [Cloudron](https://cloudron.io). For those unaware,
Cloudron makes it easy to run apps like Gitea on your server and keep them up-to-date and secure.
[![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=io.gitea.cloudronapp)
The Gitea package is maintained [here](https://git.cloudron.io/cloudron/gitea-app).
There is a [demo instance](https://my-demo.cloudron.me) (username: cloudron password: cloudron) where
you can experiment with running Gitea.

View File

@ -30,6 +30,8 @@ image as a service. Since there is no database available one can be initialized
Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`. Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`.
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. Note that the volume should be owned by the user/group with the UID/GID specified in the config file.
If you don't give the volume correct permissions, the container may not start. If you don't give the volume correct permissions, the container may not start.
Also be aware that the tag `:latest` will install the current development version.
For a stable release you can use `:1` or specify a certain release like `:1.5.1`.
```yaml ```yaml
version: "2" version: "2"
@ -103,6 +105,11 @@ services:
environment: environment:
- USER_UID=1000 - USER_UID=1000
- USER_GID=1000 - USER_GID=1000
+ - DB_TYPE=mysql
+ - DB_HOST=db:3306
+ - DB_NAME=gitea
+ - DB_USER=gitea
+ - DB_PASSWD=gitea
restart: always restart: always
networks: networks:
- gitea - gitea
@ -146,6 +153,11 @@ services:
environment: environment:
- USER_UID=1000 - USER_UID=1000
- USER_GID=1000 - USER_GID=1000
+ - DB_TYPE=postgres
+ - DB_HOST=db:5432
+ - DB_NAME=gitea
+ - DB_USER=gitea
+ - DB_PASSWD=gitea
restart: always restart: always
networks: networks:
- gitea - gitea

View File

@ -33,19 +33,15 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
* Enter Gitea admin panel on the UI, run `Rewrite '.ssh/authorized_keys' file`. * Enter Gitea admin panel on the UI, run `Rewrite '.ssh/authorized_keys' file`.
* If custom or config path was changed, run `Rewrite all update hook of repositories`. * If custom or config path was changed, run `Rewrite all update hook of repositories`.
### Change gogs specific information: ## Change gogs specific information
* Rename `gogs-repositories/` to `gitea-repositories/` * Rename `gogs-repositories/` to `gitea-repositories/`
* Rename `gogs-data/` to `gitea-data/` * Rename `gogs-data/` to `gitea-data/`
* In `gitea/custom/conf/app.ini` change: * In `gitea/custom/conf/app.ini` change:
### Upgrading to most recent `gitea` version:
After successful migration from `gogs` to `gitea 1.0.x` it is possible to upgrade to the recent `gitea` version.
Simply download the file matching the destination platform from the [downloads page](https://dl.gitea.io/gitea)
and replace the binary.
FROM: FROM:
```
```ini
[database] [database]
PATH = /home/:USER/gogs/data/:DATABASE.db PATH = /home/:USER/gogs/data/:DATABASE.db
[attachment] [attachment]
@ -57,7 +53,8 @@ ROOT_PATH = /home/:USER/gogs/log
``` ```
TO: TO:
```
```ini
[database] [database]
PATH = /home/:USER/gitea/data/:DATABASE.db PATH = /home/:USER/gitea/data/:DATABASE.db
[attachment] [attachment]
@ -70,13 +67,19 @@ ROOT_PATH = /home/:USER/gitea/log
* Verify by starting Gitea with `gitea web` * Verify by starting Gitea with `gitea web`
### Troubleshooting ## Upgrading to most recent `gitea` version
After successful migration from `gogs` to `gitea 1.0.x` it is possible to upgrade to the recent `gitea` version.
Simply download the file matching the destination platform from the [downloads page](https://dl.gitea.io/gitea)
and replace the binary.
## Troubleshooting
* If errors are encountered relating to custom templates in the `gitea/custom/templates` * If errors are encountered relating to custom templates in the `gitea/custom/templates`
folder, try moving the templates causing the errors away one by one. They may not be folder, try moving the templates causing the errors away one by one. They may not be
compatible with Gitea or an update. compatible with Gitea or an update.
### Add Gitea to startup on Unix ## Add Gitea to startup on Unix
Update the appropriate file from [gitea/contrib](https://github.com/go-gitea/gitea/tree/master/contrib) Update the appropriate file from [gitea/contrib](https://github.com/go-gitea/gitea/tree/master/contrib)
with the right environment variables. with the right environment variables.

View File

@ -29,14 +29,15 @@ Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécute
* Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`. * Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`.
* Connectez vous au panel d'administration de Gitea et exécutez l'action `Rewrite '.ssh/authorized_keys' file`, puis l'action `Rewrite all update hook of repositories` (obligatoire si le chemin menant à votre configuration personnalisée à changé). * Connectez vous au panel d'administration de Gitea et exécutez l'action `Rewrite '.ssh/authorized_keys' file`, puis l'action `Rewrite all update hook of repositories` (obligatoire si le chemin menant à votre configuration personnalisée à changé).
### Modifier les informations spécifiques de gogs ## Modifier les informations spécifiques de gogs
* Renommez `gogs-repositories/` vers `gitea-repositories/` * Renommez `gogs-repositories/` vers `gitea-repositories/`
* Renommez `gogs-data/` to `gitea-data/` * Renommez `gogs-data/` to `gitea-data/`
* Dans votre fichier `gitea/custom/conf/app.ini`, modifiez les éléments suivants: * Dans votre fichier `gitea/custom/conf/app.ini`, modifiez les éléments suivants:
DE : DE :
```
```ini
[database] [database]
PATH = /home/:USER/gogs/data/:DATABASE.db PATH = /home/:USER/gogs/data/:DATABASE.db
[attachment] [attachment]
@ -48,7 +49,8 @@ ROOT_PATH = /home/:USER/gogs/log
``` ```
VERS : VERS :
```
```ini
[database] [database]
PATH = /home/:USER/gitea/data/:DATABASE.db PATH = /home/:USER/gitea/data/:DATABASE.db
[attachment] [attachment]
@ -61,11 +63,11 @@ ROOT_PATH = /home/:USER/gitea/log
* Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`. * Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`.
### Dépannage ## Dépannage
* Si vous rencontrez des erreurs relatives à des modèles personnalisés dans le dossier `gitea/custom/templates`, essayez de déplacer un par un les modèles provoquant les erreurs. Il est possible qu'ils ne soient pas compatibles avec Gitea. * Si vous rencontrez des erreurs relatives à des modèles personnalisés dans le dossier `gitea/custom/templates`, essayez de déplacer un par un les modèles provoquant les erreurs. Il est possible qu'ils ne soient pas compatibles avec Gitea.
### Démarrer automatiquement Gitea (Unix) ## Démarrer automatiquement Gitea (Unix)
Distributions utilisant systemd: Distributions utilisant systemd:

View File

@ -72,6 +72,50 @@ Admin operations:
- Examples: - Examples:
- `gitea admin regenerate hooks` - `gitea admin regenerate hooks`
- `gitea admin regenerate keys` - `gitea admin regenerate keys`
- `auth`:
- `list`:
- Description: lists all external authentication sources that exist
- Options:
- `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini).
- Examples:
- `gitea auth list`
- `delete`:
- Options:
- `--id`: ID of source to be deleted. Required.
- `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini).
- Examples:
- `gitea auth delete --id 1`
- `add-oauth`:
- Options:
- `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini).
- `--name`: Application Name.
- `--provider`: OAuth2 Provider.
- `--key`: Client ID (Key).
- `--secret`: Client Secret.
- `--auto-discover-url`: OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider).
- `--use-custom-urls`: Use custom URLs for GitLab/GitHub OAuth endpoints.
- `--custom-auth-url`: Use a custom Authorization URL (option for GitLab/GitHub).
- `--custom-token-url`: Use a custom Token URL (option for GitLab/GitHub).
- `--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
- `--custom-email-url`: Use a custom Email URL (option for GitHub).
- Examples:
- `gitea auth add-oauth --name external-github --provider github --key OBTAIN_FROM_SOURCE --secret OBTAIN_FROM_SOURCE`
- `update-oauth`:
- Options:
- `--id`: ID of source to be updated. Required.
- `--config path`: Gitea configuration file path. Optional. (default: custom/conf/app.ini).
- `--name`: Application Name.
- `--provider`: OAuth2 Provider.
- `--key`: Client ID (Key).
- `--secret`: Client Secret.
- `--auto-discover-url`: OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider).
- `--use-custom-urls`: Use custom URLs for GitLab/GitHub OAuth endpoints.
- `--custom-auth-url`: Use a custom Authorization URL (option for GitLab/GitHub).
- `--custom-token-url`: Use a custom Token URL (option for GitLab/GitHub).
- `--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
- `--custom-email-url`: Use a custom Email URL (option for GitHub).
- Examples:
- `gitea auth update-oauth --id 1 --name external-github-updated`
#### cert #### cert

View File

@ -32,6 +32,24 @@ KEY_FILE = key.pem
``` ```
To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server). To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server).
## Using Let's Encrypt
[Let's Encrypt](https://letsencrypt.org/) is a Certificate Authority that allows you to automatically request and renew SSL/TLS certificates. In addition to starting Gitea on your configured port, to request HTTPS certificates Gitea will also need to listed on port 80, and will set up an autoredirect to HTTPS for you. Let's Encrypt will need to be able to access Gitea via the Internet to verify your ownership of the domain.
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf)
```ini
[server]
PROTOCOL=https
DOMAIN=git.example.com
ENABLE_LETSENCRYPT=true
LETSENCRYPT_ACCEPTTOS=true
LETSENCRYPT_DIRECTORY=https
LETSENCRYPT_EMAIL=email@example.com
```
To learn more about the config values, please checkout the [Config Cheat Sheet](../config-cheat-sheet#server).
## Using reverse proxy ## Using reverse proxy
Setup up your reverse proxy like shown in the [reverse proxy guide](../reverse-proxies). Setup up your reverse proxy like shown in the [reverse proxy guide](../reverse-proxies).

View File

@ -0,0 +1,31 @@
---
date: "2018-06-01T19:00:00+02:00"
title: "Usage: Pull Request"
slug: "pull-request"
weight: 13
toc: true
draft: false
menu:
sidebar:
parent: "usage"
name: "Pull Request"
weight: 13
identifier: "pull-request"
---
# Pull Request
## "Work In Progress" pull requests
Marking a pull request as being a work in progress will prevent that pull request from being accidentally merged. To mark a pull request as being a work in progress, you must prefix its title by `WIP:` or `[WIP]` (case insensitive). Those values are configurable in your `app.ini` file :
```
[repository.pull-request]
WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP]
```
The first value of the list will be used in helpers.
## Pull Request Templates
You can find more information about pull request templates in the dedicated page : [Issue and Pull Request templates](../issue-pull-request-templates)

View File

@ -1,44 +0,0 @@
:80 {
root /srv/www
locale en-US zh-CN zh-TW pt-BR nl-NL fr-FR {
detect header
}
redir 301 {
if {path} match ^/$
/ /{>Detected-Locale}/
}
rewrite /en-US/ {
regexp (.*)
to /en-us/{1}
}
rewrite /zh-CN/ {
regexp (.*)
to /zh-cn/{1}
}
rewrite /zh-TW/ {
regexp (.*)
to /zh-tw/{1}
}
rewrite /pt-BR/ {
regexp (.*)
to /pt-br/{1}
}
rewrite /nl-NL/ {
regexp (.*)
to /nl-nl/{1}
}
rewrite /fr-FR/ {
regexp (.*)
to /fr-fr/{1}
}
header / Vary "Accept-Language"
}

View File

6
docs/static/_headers vendored Normal file
View File

@ -0,0 +1,6 @@
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com https://cdnjs.cloudflare.com; font-src 'self' data: https://cdnjs.cloudflare.com https://fonts.gstatic.com
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

8
docs/static/_redirects vendored Normal file
View File

@ -0,0 +1,8 @@
https://gitea-docs.netlify.com/* https://docs.gitea.io/:splat 302!
/ /fr-fr/ 302! Language=fr
/ /nl-nl/ 302! Language=nl
/ /pt-br/ 302! Language=pt-br
/ /zh-cn/ 302! Language=zh-cn
/ /zh-tw/ 302! Language=zh-tw
/ /en-us/ 302!

View File

@ -11,6 +11,8 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
) )
func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
@ -47,8 +49,8 @@ func TestAPIAdminDeleteMissingSSHKey(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
// user1 is an admin user // user1 is an admin user
session := loginUser(t, "user1") session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session)
token := getTokenForLoggedInUser(t, session)
req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token="+token, models.NonexistentID) req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token="+token, models.NonexistentID)
session.MakeRequest(t, req, http.StatusNotFound) session.MakeRequest(t, req, http.StatusNotFound)
} }
@ -75,3 +77,32 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) {
adminUsername, newPublicKey.ID) adminUsername, newPublicKey.ID)
session.MakeRequest(t, req, http.StatusForbidden) session.MakeRequest(t, req, http.StatusForbidden)
} }
func TestAPISudoUser(t *testing.T) {
prepareTestEnv(t)
adminUsername := "user1"
normalUsername := "user2"
session := loginUser(t, adminUsername)
token := getTokenForLoggedInUser(t, session)
urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", normalUsername, token)
req := NewRequest(t, "GET", urlStr)
resp := session.MakeRequest(t, req, http.StatusOK)
var user api.User
DecodeJSON(t, resp, &user)
assert.Equal(t, normalUsername, user.UserName)
}
func TestAPISudoUserForbidden(t *testing.T) {
prepareTestEnv(t)
adminUsername := "user1"
normalUsername := "user2"
session := loginUser(t, normalUsername)
token := getTokenForLoggedInUser(t, session)
urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", adminUsername, token)
req := NewRequest(t, "GET", urlStr)
session.MakeRequest(t, req, http.StatusForbidden)
}

View File

@ -5,13 +5,13 @@
package integrations package integrations
import ( import (
"fmt"
"net/http" "net/http"
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
"fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -5,10 +5,13 @@
package integrations package integrations
import ( import (
"fmt"
"net/http" "net/http"
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -29,3 +32,27 @@ func TestAPIViewPulls(t *testing.T) {
expectedLen := models.GetCount(t, &models.Issue{RepoID: repo.ID}, models.Cond("is_pull = ?", true)) expectedLen := models.GetCount(t, &models.Issue{RepoID: repo.ID}, models.Cond("is_pull = ?", true))
assert.Len(t, pulls, expectedLen) assert.Len(t, pulls, expectedLen)
} }
// TestAPIMergePullWIP ensures that we can't merge a WIP pull request
func TestAPIMergePullWIP(t *testing.T) {
prepareTestEnv(t)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{Status: models.PullRequestStatusMergeable}, models.Cond("has_merged = ?", false)).(*models.PullRequest)
pr.LoadIssue()
pr.Issue.ChangeTitle(owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title)
// force reload
pr.LoadAttributes()
assert.Contains(t, pr.Issue.Title, setting.Repository.PullRequest.WorkInProgressPrefixes[0])
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session)
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", owner.Name, repo.Name, pr.Index, token), &auth.MergePullRequestForm{
MergeMessageField: pr.Issue.Title,
Do: string(models.MergeStyleMerge),
})
session.MakeRequest(t, req, http.StatusMethodNotAllowed)
}

View File

@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
expectedResults expectedResults
}{ }{
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{ {name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
nil: {count: 17}, nil: {count: 19},
user: {count: 17}, user: {count: 19},
user2: {count: 17}}, user2: {count: 19}},
}, },
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{ {name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
nil: {count: 10}, nil: {count: 10},
@ -147,8 +147,8 @@ func TestAPISearchRepo(t *testing.T) {
if userToLogin != nil && userToLogin.ID > 0 { if userToLogin != nil && userToLogin.ID > 0 {
testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID)
session = loginUser(t, userToLogin.Name) session = loginUser(t, userToLogin.Name)
userID = userToLogin.ID
token = getTokenForLoggedInUser(t, session) token = getTokenForLoggedInUser(t, session)
userID = userToLogin.ID
} else { } else {
testName = "AnonymousUser" testName = "AnonymousUser"
session = emptyTestSession(t) session = emptyTestSession(t)
@ -216,7 +216,6 @@ func TestAPIOrgRepos(t *testing.T) {
sourceOrg := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) sourceOrg := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User)
// Login as User2. // Login as User2.
session := loginUser(t, user.Name) session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session) token := getTokenForLoggedInUser(t, session)
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token="+token, sourceOrg.Name) req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token="+token, sourceOrg.Name)
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
@ -266,3 +265,26 @@ func TestAPIRepoMigrate(t *testing.T) {
session.MakeRequest(t, req, testCase.expectedStatus) session.MakeRequest(t, req, testCase.expectedStatus)
} }
} }
func TestAPIOrgRepoCreate(t *testing.T) {
testCases := []struct {
ctxUserID int64
orgName, repoName string
expectedStatus int
}{
{ctxUserID: 1, orgName: "user3", repoName: "repo-admin", expectedStatus: http.StatusCreated},
{ctxUserID: 2, orgName: "user3", repoName: "repo-own", expectedStatus: http.StatusCreated},
{ctxUserID: 2, orgName: "user6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden},
}
prepareTestEnv(t)
for _, testCase := range testCases {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: testCase.ctxUserID}).(*models.User)
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session)
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos?token="+token, testCase.orgName), &api.CreateRepoOption{
Name: testCase.repoName,
})
session.MakeRequest(t, req, testCase.expectedStatus)
}
}

View File

@ -0,0 +1,50 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"net/http"
"testing"
"code.gitea.io/gitea/models"
api "code.gitea.io/sdk/gitea"
)
// TestAPICreateAndDeleteToken tests that token that was just created can be deleted
func TestAPICreateAndDeleteToken(t *testing.T) {
prepareTestEnv(t)
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{
"name": "test-key-1",
})
req = AddBasicAuthHeader(req, user.Name)
resp := MakeRequest(t, req, http.StatusCreated)
var newAccessToken api.AccessToken
DecodeJSON(t, resp, &newAccessToken)
models.AssertExistsAndLoadBean(t, &models.AccessToken{
ID: newAccessToken.ID,
Name: newAccessToken.Name,
Sha1: newAccessToken.Sha1,
UID: user.ID,
})
req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID)
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)
models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID})
}
// TestAPIDeleteMissingToken ensures that error is thrown when token not found
func TestAPIDeleteMissingToken(t *testing.T) {
prepareTestEnv(t)
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", models.NonexistentID)
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNotFound)
}

View File

@ -272,6 +272,11 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *ht
return request return request
} }
func AddBasicAuthHeader(request *http.Request, username string) *http.Request {
request.SetBasicAuth(username, userPassword)
return request
}
const NoExpectedStatus = -1 const NoExpectedStatus = -1
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder {

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/Unknwon/i18n"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -123,3 +124,23 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg) assert.EqualValues(t, "Branch 'user1/feature/test' has been deleted.", resultMsg)
} }
func TestCantMergeWorkInProgress(t *testing.T) {
prepareTestEnv(t)
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master", "[wip] This is a pull title")
req := NewRequest(t, "GET", resp.Header().Get("Location"))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
text := strings.TrimSpace(htmlDoc.doc.Find(".merge.segment > .text.grey").Text())
assert.NotEmpty(t, text, "Can't find WIP text")
// remove <strong /> from lang
expected := i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress", "[wip]")
replacer := strings.NewReplacer("<strong>", "", "</strong>", "")
assert.Equal(t, replacer.Replace(expected), text, "Unable to find WIP text")
}

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
// register supported doc types // register supported doc types
_ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode" _ "code.gitea.io/gitea/modules/markup/orgmode"

View File

@ -47,6 +47,9 @@ const (
ActionReopenPullRequest // 15 ActionReopenPullRequest // 15
ActionDeleteTag // 16 ActionDeleteTag // 16
ActionDeleteBranch // 17 ActionDeleteBranch // 17
ActionMirrorSyncPush // 18
ActionMirrorSyncCreate // 19
ActionMirrorSyncDelete // 20
) )
var ( var (
@ -122,6 +125,12 @@ func (a *Action) loadRepo() {
} }
} }
// GetActFullName gets the action's user full name.
func (a *Action) GetActFullName() string {
a.loadActUser()
return a.ActUser.FullName
}
// GetActUserName gets the action's user name. // GetActUserName gets the action's user name.
func (a *Action) GetActUserName() string { func (a *Action) GetActUserName() string {
a.loadActUser() a.loadActUser()
@ -471,6 +480,10 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
} }
if err = issue.ChangeStatus(doer, repo, true); err != nil { if err = issue.ChangeStatus(doer, repo, true); err != nil {
// Don't return an error when dependencies are open as this would let the push fail
if IsErrDependenciesLeft(err) {
return nil
}
return err return err
} }
} }
@ -728,6 +741,71 @@ func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error
return mergePullRequestAction(x, actUser, repo, pull) return mergePullRequestAction(x, actUser, repo, pull)
} }
func mirrorSyncAction(e Engine, opType ActionType, repo *Repository, refName string, data []byte) error {
if err := notifyWatchers(e, &Action{
ActUserID: repo.OwnerID,
ActUser: repo.MustOwner(),
OpType: opType,
RepoID: repo.ID,
Repo: repo,
IsPrivate: repo.IsPrivate,
RefName: refName,
Content: string(data),
}); err != nil {
return fmt.Errorf("notifyWatchers: %v", err)
}
return nil
}
// MirrorSyncPushActionOptions mirror synchronization action options.
type MirrorSyncPushActionOptions struct {
RefName string
OldCommitID string
NewCommitID string
Commits *PushCommits
}
// MirrorSyncPushAction adds new action for mirror synchronization of pushed commits.
func MirrorSyncPushAction(repo *Repository, opts MirrorSyncPushActionOptions) error {
if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum {
opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum]
}
apiCommits := opts.Commits.ToAPIPayloadCommits(repo.HTMLURL())
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
apiPusher := repo.MustOwner().APIFormat()
if err := PrepareWebhooks(repo, HookEventPush, &api.PushPayload{
Ref: opts.RefName,
Before: opts.OldCommitID,
After: opts.NewCommitID,
CompareURL: setting.AppURL + opts.Commits.CompareURL,
Commits: apiCommits,
Repo: repo.APIFormat(AccessModeOwner),
Pusher: apiPusher,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
}
data, err := json.Marshal(opts.Commits)
if err != nil {
return err
}
return mirrorSyncAction(x, ActionMirrorSyncPush, repo, opts.RefName, data)
}
// MirrorSyncCreateAction adds new action for mirror synchronization of new reference.
func MirrorSyncCreateAction(repo *Repository, refName string) error {
return mirrorSyncAction(x, ActionMirrorSyncCreate, repo, refName, nil)
}
// MirrorSyncDeleteAction adds new action for mirror synchronization of delete reference.
func MirrorSyncDeleteAction(repo *Repository, refName string) error {
return mirrorSyncAction(x, ActionMirrorSyncDelete, repo, refName, nil)
}
// GetFeedsOptions options for retrieving feeds // GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct { type GetFeedsOptions struct {
RequestedUser *User RequestedUser *User

View File

@ -1259,3 +1259,110 @@ func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist) _, ok := err.(ErrU2FRegistrationNotExist)
return ok return ok
} }
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
// | |\___ \ \___ \| | /\ ___/ | ` \ ___/| |_> > ___/| | \/ /_/ \ ___/| | \ \___| \ ___/ \___ \
// |___/____ >____ >____/ \___ >_______ /\___ > __/ \___ >___| /\____ |\___ >___| /\___ >__|\___ >____ >
// \/ \/ \/ \/ \/|__| \/ \/ \/ \/ \/ \/ \/ \/
// ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyExists checks if an error is a ErrDependencyExists.
func IsErrDependencyExists(err error) bool {
_, ok := err.(ErrDependencyExists)
return ok
}
func (err ErrDependencyExists) Error() string {
return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
type ErrDependencyNotExists struct {
IssueID int64
DependencyID int64
}
// IsErrDependencyNotExists checks if an error is a ErrDependencyExists.
func IsErrDependencyNotExists(err error) bool {
_, ok := err.(ErrDependencyNotExists)
return ok
}
func (err ErrDependencyNotExists) Error() string {
return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrCircularDependency represents a "DependencyCircular" kind of error.
type ErrCircularDependency struct {
IssueID int64
DependencyID int64
}
// IsErrCircularDependency checks if an error is a ErrCircularDependency.
func IsErrCircularDependency(err error) bool {
_, ok := err.(ErrCircularDependency)
return ok
}
func (err ErrCircularDependency) Error() string {
return fmt.Sprintf("circular dependencies exists (two issues blocking each other) [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
}
// ErrDependenciesLeft represents an error where the issue you're trying to close still has dependencies left.
type ErrDependenciesLeft struct {
IssueID int64
}
// IsErrDependenciesLeft checks if an error is a ErrDependenciesLeft.
func IsErrDependenciesLeft(err error) bool {
_, ok := err.(ErrDependenciesLeft)
return ok
}
func (err ErrDependenciesLeft) Error() string {
return fmt.Sprintf("issue has open dependencies [issue id: %d]", err.IssueID)
}
// ErrUnknownDependencyType represents an error where an unknown dependency type was passed
type ErrUnknownDependencyType struct {
Type DependencyType
}
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
func IsErrUnknownDependencyType(err error) bool {
_, ok := err.(ErrUnknownDependencyType)
return ok
}
func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
}
// __________ .__
// \______ \ _______ _|__| ______ _ __
// | _// __ \ \/ / |/ __ \ \/ \/ /
// | | \ ___/\ /| \ ___/\ /
// |____|_ /\___ >\_/ |__|\___ >\/\_/
// \/ \/ \/
// ErrReviewNotExist represents a "ReviewNotExist" kind of error.
type ErrReviewNotExist struct {
ID int64
}
// IsErrReviewNotExist checks if an error is a ErrReviewNotExist.
func IsErrReviewNotExist(err error) bool {
_, ok := err.(ErrReviewNotExist)
return ok
}
func (err ErrReviewNotExist) Error() string {
return fmt.Sprintf("review does not exist [id: %d]", err.ID)
}

View File

@ -20,3 +20,35 @@
issue_id: 1 # in repo_id 1 issue_id: 1 # in repo_id 1
content: "meh..." content: "meh..."
created_unix: 946684812 created_unix: 946684812
-
id: 4
type: 21 # code comment
poster_id: 1
issue_id: 2
content: "meh..."
review_id: 4
line: 4
tree_path: "README.md"
created_unix: 946684812
invalidated: false
-
id: 5
type: 21 # code comment
poster_id: 1
issue_id: 2
content: "meh..."
line: -4
tree_path: "README.md"
created_unix: 946684812
invalidated: false
-
id: 6
type: 21 # code comment
poster_id: 1
issue_id: 2
content: "it's already invalidated. boring..."
line: -4
tree_path: "README.md"
created_unix: 946684812
invalidated: true

View File

@ -9,3 +9,11 @@
- -
repo_id: 1 repo_id: 1
topic_id: 3 topic_id: 3
-
repo_id: 33
topic_id: 1
-
repo_id: 33
topic_id: 4

View File

@ -407,3 +407,25 @@
lower_name: utf8 lower_name: utf8
name: utf8 name: utf8
is_private: false is_private: false
-
id: 34
owner_id: 21
lower_name: golang
name: golang
is_private: false
num_stars: 0
num_forks: 0
num_issues: 0
is_mirror: false
-
id: 35
owner_id: 21
lower_name: graphql
name: graphql
is_private: false
num_stars: 0
num_forks: 0
num_issues: 0
is_mirror: false

View File

@ -0,0 +1,32 @@
-
id: 1
type: 1
reviewer_id: 1
issue_id: 2
content: "Demo Review"
updated_unix: 946684810
created_unix: 946684810
-
id: 2
type: 1
reviewer_id: 534543
issue_id: 534543
content: "Invalid Review #1"
updated_unix: 946684810
created_unix: 946684810
-
id: 3
type: 1
reviewer_id: 1
issue_id: 343545
content: "Invalid Review #2"
updated_unix: 946684810
created_unix: 946684810
-
id: 4
type: 0 # Pending review
reviewer_id: 1
issue_id: 2
content: "Pending Review"
updated_unix: 946684810
created_unix: 946684810

View File

@ -1,7 +1,7 @@
- -
id: 1 id: 1
name: golang name: golang
repo_count: 1 repo_count: 2
- -
id: 2 id: 2
@ -11,3 +11,7 @@
- id: 3 - id: 3
name: SQL name: SQL
repo_count: 1 repo_count: 1
- id: 4
name: graphql
repo_count: 1

View File

@ -314,3 +314,18 @@
avatar_email: user20@example.com avatar_email: user20@example.com
num_repos: 4 num_repos: 4
is_active: true is_active: true
-
id: 21
lower_name: user21
name: user21
full_name: User 21
email: user21@example.com
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
type: 0 # individual
salt: ZogKvWdyEx
is_admin: false
avatar: avatar21
avatar_email: user21@example.com
num_repos: 2
is_active: true

View File

@ -14,6 +14,8 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
@ -57,6 +59,7 @@ type DiffLine struct {
RightIdx int RightIdx int
Type DiffLineType Type DiffLineType
Content string Content string
Comments []*Comment
} }
// GetType returns the type of a DiffLine. // GetType returns the type of a DiffLine.
@ -64,6 +67,19 @@ func (d *DiffLine) GetType() int {
return int(d.Type) return int(d.Type)
} }
// CanComment returns whether or not a line can get commented
func (d *DiffLine) CanComment() bool {
return len(d.Comments) == 0 && d.Type != DiffLineSection
}
// GetCommentSide returns the comment side of the first comment, if not set returns empty string
func (d *DiffLine) GetCommentSide() string {
if len(d.Comments) == 0 {
return ""
}
return d.Comments[0].DiffSide()
}
// DiffSection represents a section of a DiffFile. // DiffSection represents a section of a DiffFile.
type DiffSection struct { type DiffSection struct {
Name string Name string
@ -225,11 +241,171 @@ type Diff struct {
IsIncomplete bool IsIncomplete bool
} }
// LoadComments loads comments into each line
func (diff *Diff) LoadComments(issue *Issue, currentUser *User) error {
allComments, err := FetchCodeComments(issue, currentUser)
if err != nil {
return err
}
for _, file := range diff.Files {
if lineCommits, ok := allComments[file.Name]; ok {
for _, section := range file.Sections {
for _, line := range section.Lines {
if comments, ok := lineCommits[int64(line.LeftIdx*-1)]; ok {
line.Comments = append(line.Comments, comments...)
}
if comments, ok := lineCommits[int64(line.RightIdx)]; ok {
line.Comments = append(line.Comments, comments...)
}
sort.SliceStable(line.Comments, func(i, j int) bool {
return line.Comments[i].CreatedUnix < line.Comments[j].CreatedUnix
})
}
}
}
}
return nil
}
// NumFiles returns number of files changes in a diff. // NumFiles returns number of files changes in a diff.
func (diff *Diff) NumFiles() int { func (diff *Diff) NumFiles() int {
return len(diff.Files) return len(diff.Files)
} }
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
var hunkRegex = regexp.MustCompile(`^@@ -([0-9]+),([0-9]+) \+([0-9]+)(,([0-9]+))? @@`)
func isHeader(lof string) bool {
return strings.HasPrefix(lof, cmdDiffHead) || strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")
}
// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
// it also recalculates hunks and adds the appropriate headers to the new diff.
// Warning: Only one-file diffs are allowed.
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) string {
if line == 0 || numbersOfLine == 0 {
// no line or num of lines => no diff
return ""
}
scanner := bufio.NewScanner(originalDiff)
hunk := make([]string, 0)
// begin is the start of the hunk containing searched line
// end is the end of the hunk ...
// currentLine is the line number on the side of the searched line (differentiated by old)
// otherLine is the line number on the opposite side of the searched line (differentiated by old)
var begin, end, currentLine, otherLine int64
var headerLines int
for scanner.Scan() {
lof := scanner.Text()
// Add header to enable parsing
if isHeader(lof) {
hunk = append(hunk, lof)
headerLines++
}
if currentLine > line {
break
}
// Detect "hunk" with contains commented lof
if strings.HasPrefix(lof, "@@") {
// Already got our hunk. End of hunk detected!
if len(hunk) > headerLines {
break
}
groups := hunkRegex.FindStringSubmatch(lof)
if old {
begin = com.StrTo(groups[1]).MustInt64()
end = com.StrTo(groups[2]).MustInt64()
// init otherLine with begin of opposite side
otherLine = com.StrTo(groups[3]).MustInt64()
} else {
begin = com.StrTo(groups[3]).MustInt64()
if groups[5] != "" {
end = com.StrTo(groups[5]).MustInt64()
} else {
end = 0
}
// init otherLine with begin of opposite side
otherLine = com.StrTo(groups[1]).MustInt64()
}
end += begin // end is for real only the number of lines in hunk
// lof is between begin and end
if begin <= line && end >= line {
hunk = append(hunk, lof)
currentLine = begin
continue
}
} else if len(hunk) > headerLines {
hunk = append(hunk, lof)
// Count lines in context
switch lof[0] {
case '+':
if !old {
currentLine++
} else {
otherLine++
}
case '-':
if old {
currentLine++
} else {
otherLine++
}
default:
currentLine++
otherLine++
}
}
}
// No hunk found
if currentLine == 0 {
return ""
}
// headerLines + hunkLine (1) = totalNonCodeLines
if len(hunk)-headerLines-1 <= numbersOfLine {
// No need to cut the hunk => return existing hunk
return strings.Join(hunk, "\n")
}
var oldBegin, oldNumOfLines, newBegin, newNumOfLines int64
if old {
oldBegin = currentLine
newBegin = otherLine
} else {
oldBegin = otherLine
newBegin = currentLine
}
// headers + hunk header
newHunk := make([]string, headerLines)
// transfer existing headers
for idx, lof := range hunk[:headerLines] {
newHunk[idx] = lof
}
// transfer last n lines
for _, lof := range hunk[len(hunk)-numbersOfLine-1:] {
newHunk = append(newHunk, lof)
}
// calculate newBegin, ... by counting lines
for i := len(hunk) - 1; i >= len(hunk)-numbersOfLine; i-- {
switch hunk[i][0] {
case '+':
newBegin--
newNumOfLines++
case '-':
oldBegin--
oldNumOfLines++
default:
oldBegin--
newBegin--
newNumOfLines++
oldNumOfLines++
}
}
// construct the new hunk header
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
return strings.Join(newHunk, "\n")
}
const cmdDiffHead = "diff --git " const cmdDiffHead = "diff --git "
// ParsePatch builds a Diff object from a io.Reader and some // ParsePatch builds a Diff object from a io.Reader and some
@ -307,7 +483,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
if curFileLinesCount >= maxLines { if curFileLinesCount >= maxLines {
curFile.IsIncomplete = true curFile.IsIncomplete = true
} }
switch { switch {
case line[0] == ' ': case line[0] == ' ':
diffLine := &DiffLine{Type: DiffLinePlain, Content: line, LeftIdx: leftLine, RightIdx: rightLine} diffLine := &DiffLine{Type: DiffLinePlain, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
@ -462,6 +637,13 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
// passing the empty string as beforeCommitID returns a diff from the // passing the empty string as beforeCommitID returns a diff from the
// parent commit. // parent commit.
func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) { func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
return GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID, maxLines, maxLineCharacters, maxFiles, "")
}
// GetDiffRangeWithWhitespaceBehavior builds a Diff between two commits of a repository.
// Passing the empty string as beforeCommitID returns a diff from the parent commit.
// The whitespaceBehavior is either an empty string or a git flag
func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int, whitespaceBehavior string) (*Diff, error) {
gitRepo, err := git.OpenRepository(repoPath) gitRepo, err := git.OpenRepository(repoPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -473,17 +655,21 @@ func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxL
} }
var cmd *exec.Cmd var cmd *exec.Cmd
// if "after" commit given if len(beforeCommitID) == 0 && commit.ParentCount() == 0 {
if len(beforeCommitID) == 0 {
// First commit of repository.
if commit.ParentCount() == 0 {
cmd = exec.Command("git", "show", afterCommitID) cmd = exec.Command("git", "show", afterCommitID)
} else { } else {
c, _ := commit.Parent(0) actualBeforeCommitID := beforeCommitID
cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitID) if len(actualBeforeCommitID) == 0 {
parentCommit, _ := commit.Parent(0)
actualBeforeCommitID = parentCommit.ID.String()
} }
} else { diffArgs := []string{"diff", "-M"}
cmd = exec.Command("git", "diff", "-M", beforeCommitID, afterCommitID) if len(whitespaceBehavior) != 0 {
diffArgs = append(diffArgs, whitespaceBehavior)
}
diffArgs = append(diffArgs, actualBeforeCommitID)
diffArgs = append(diffArgs, afterCommitID)
cmd = exec.Command("git", diffArgs...)
} }
cmd.Dir = repoPath cmd.Dir = repoPath
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@ -524,32 +710,46 @@ const (
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer. // GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
// TODO: move this function to gogits/git-module // TODO: move this function to gogits/git-module
func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error { func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
return GetRawDiffForFile(repoPath, "", commitID, diffType, "", writer)
}
// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
// TODO: move this function to gogits/git-module
func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
repo, err := git.OpenRepository(repoPath) repo, err := git.OpenRepository(repoPath)
if err != nil { if err != nil {
return fmt.Errorf("OpenRepository: %v", err) return fmt.Errorf("OpenRepository: %v", err)
} }
commit, err := repo.GetCommit(commitID) commit, err := repo.GetCommit(endCommit)
if err != nil { if err != nil {
return fmt.Errorf("GetCommit: %v", err) return fmt.Errorf("GetCommit: %v", err)
} }
fileArgs := make([]string, 0)
if len(file) > 0 {
fileArgs = append(fileArgs, "--", file)
}
var cmd *exec.Cmd var cmd *exec.Cmd
switch diffType { switch diffType {
case RawDiffNormal: case RawDiffNormal:
if commit.ParentCount() == 0 { if len(startCommit) != 0 {
cmd = exec.Command("git", "show", commitID) cmd = exec.Command("git", append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
} else if commit.ParentCount() == 0 {
cmd = exec.Command("git", append([]string{"show", endCommit}, fileArgs...)...)
} else { } else {
c, _ := commit.Parent(0) c, _ := commit.Parent(0)
cmd = exec.Command("git", "diff", "-M", c.ID.String(), commitID) cmd = exec.Command("git", append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
} }
case RawDiffPatch: case RawDiffPatch:
if commit.ParentCount() == 0 { if len(startCommit) != 0 {
cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", "--root", commitID) query := fmt.Sprintf("%s...%s", endCommit, startCommit)
cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
} else if commit.ParentCount() == 0 {
cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
} else { } else {
c, _ := commit.Parent(0) c, _ := commit.Parent(0)
query := fmt.Sprintf("%s...%s", commitID, c.ID.String()) query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", query) cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
} }
default: default:
return fmt.Errorf("invalid diffType: %s", diffType) return fmt.Errorf("invalid diffType: %s", diffType)
@ -560,7 +760,6 @@ func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Write
cmd.Dir = repoPath cmd.Dir = repoPath
cmd.Stdout = writer cmd.Stdout = writer
cmd.Stderr = stderr cmd.Stderr = stderr
if err = cmd.Run(); err != nil { if err = cmd.Run(); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr) return fmt.Errorf("Run: %v - %s", err, stderr)
} }

View File

@ -2,9 +2,11 @@ package models
import ( import (
"html/template" "html/template"
"strings"
"testing" "testing"
dmp "github.com/sergi/go-diff/diffmatchpatch" dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/stretchr/testify/assert"
) )
func assertEqual(t *testing.T, s1 string, s2 template.HTML) { func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
@ -34,3 +36,106 @@ func TestDiffToHTML(t *testing.T) {
{Type: dmp.DiffEqual, Text: " biz"}, {Type: dmp.DiffEqual, Text: " biz"},
}, DiffLineDel)) }, DiffLineDel))
} }
const exampleDiff = `diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
# gitea-github-migrator
+
+ Build Status
- Latest Release
Docker Pulls
+ cut off
+ cut off`
func TestCutDiffAroundLine(t *testing.T) {
result := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
resultByLine := strings.Split(result, "\n")
assert.Len(t, resultByLine, 7)
// Check if headers got transferred
assert.Equal(t, "diff --git a/README.md b/README.md", resultByLine[0])
assert.Equal(t, "--- a/README.md", resultByLine[1])
assert.Equal(t, "+++ b/README.md", resultByLine[2])
// Check if hunk header is calculated correctly
assert.Equal(t, "@@ -2,2 +3,2 @@", resultByLine[3])
// Check if line got transferred
assert.Equal(t, "+ Build Status", resultByLine[4])
// Must be same result as before since old line 3 == new line 5
newResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
assert.Equal(t, result, newResult, "Must be same result as before since old line 3 == new line 5")
newResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
assert.Equal(t, exampleDiff, newResult)
emptyResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
assert.Empty(t, emptyResult)
// Line is out of scope
emptyResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
assert.Empty(t, emptyResult)
}
func BenchmarkCutDiffAroundLine(b *testing.B) {
for n := 0; n < b.N; n++ {
CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
}
}
func ExampleCutDiffAroundLine() {
const diff = `diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
# gitea-github-migrator
+
+ Build Status
- Latest Release
Docker Pulls
+ cut off
+ cut off`
result := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
println(result)
}
func setupDefaultDiff() *Diff {
return &Diff{
Files: []*DiffFile{
{
Name: "README.md",
Sections: []*DiffSection{
{
Lines: []*DiffLine{
{
LeftIdx: 4,
RightIdx: 4,
},
},
},
},
},
},
}
}
func TestDiff_LoadComments(t *testing.T) {
issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
diff := setupDefaultDiff()
assert.NoError(t, PrepareTestDatabase())
assert.NoError(t, diff.LoadComments(issue, user))
assert.Len(t, diff.Files[0].Sections[0].Lines[0].Comments, 2)
}
func TestDiffLine_CanComment(t *testing.T) {
assert.False(t, (&DiffLine{Type: DiffLineSection}).CanComment())
assert.False(t, (&DiffLine{Type: DiffLineAdd, Comments: []*Comment{{Content: "bla"}}}).CanComment())
assert.True(t, (&DiffLine{Type: DiffLineAdd}).CanComment())
assert.True(t, (&DiffLine{Type: DiffLineDel}).CanComment())
assert.True(t, (&DiffLine{Type: DiffLinePlain}).CanComment())
}
func TestDiffLine_GetCommentSide(t *testing.T) {
assert.Equal(t, "previous", (&DiffLine{Comments: []*Comment{{Line: -3}}}).GetCommentSide())
assert.Equal(t, "proposed", (&DiffLine{Comments: []*Comment{{Line: 3}}}).GetCommentSide())
}

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"code.gitea.io/git" "code.gitea.io/git"
"code.gitea.io/gitea/modules/setting"
) )
// GraphItem represent one commit, or one relation in timeline // GraphItem represent one commit, or one relation in timeline
@ -41,7 +42,7 @@ func GetCommitGraph(r *git.Repository) (GraphItems, error) {
"--all", "--all",
"-C", "-C",
"-M", "-M",
"-n 100", fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum),
"--date=iso", "--date=iso",
fmt.Sprintf("--pretty=format:%s", format), fmt.Sprintf("--pretty=format:%s", format),
) )
@ -66,7 +67,7 @@ func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
var ascii string var ascii string
var data = "|||||||" var data = "|||||||"
lines := strings.Split(s, "DATA:") lines := strings.SplitN(s, "DATA:", 2)
switch len(lines) { switch len(lines) {
case 1: case 1:

View File

@ -5,6 +5,7 @@
package models package models
import ( import (
"fmt"
"testing" "testing"
"code.gitea.io/git" "code.gitea.io/git"
@ -43,3 +44,32 @@ func BenchmarkParseCommitString(b *testing.B) {
} }
} }
} }
func TestCommitStringParsing(t *testing.T) {
dataFirstPart := "* DATA:||4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Author|user@mail.something|4e61bac|"
tests := []struct {
shouldPass bool
testName string
commitMessage string
}{
{true, "normal", "not a fancy message"},
{true, "extra pipe", "An extra pipe: |"},
{true, "extra 'Data:'", "DATA: might be trouble"},
}
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
testString := fmt.Sprintf("%s%s", dataFirstPart, test.commitMessage)
graphItem, err := graphItemFromString(testString, nil)
if err != nil && test.shouldPass {
t.Errorf("Could not parse %s", testString)
return
}
if test.commitMessage != graphItem.Subject {
t.Errorf("%s does not match %s", test.commitMessage, graphItem.Subject)
}
})
}
}

View File

@ -649,6 +649,20 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
if issue.IsClosed == isClosed { if issue.IsClosed == isClosed {
return nil return nil
} }
// Check for open dependencies
if isClosed && issue.Repo.IsDependenciesEnabled() {
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
noDeps, err := IssueNoDependenciesLeft(issue)
if err != nil {
return err
}
if !noDeps {
return ErrDependenciesLeft{issue.ID}
}
}
issue.IsClosed = isClosed issue.IsClosed = isClosed
if isClosed { if isClosed {
issue.ClosedUnix = util.TimeStampNow() issue.ClosedUnix = util.TimeStampNow()
@ -1598,3 +1612,33 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User)
return sess.Commit() return sess.Commit()
} }
// Get Blocked By Dependencies, aka all issues this issue is blocked by.
func (issue *Issue) getBlockedByDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_id = ?", issue.ID).
Find(&issueDeps)
}
// Get Blocking Dependencies, aka all issues this issue blocks.
func (issue *Issue) getBlockingDependencies(e Engine) (issueDeps []*Issue, err error) {
return issueDeps, e.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.issue_id").
Where("dependency_id = ?", issue.ID).
Find(&issueDeps)
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies() ([]*Issue, error) {
return issue.getBlockedByDependencies(x)
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
func (issue *Issue) BlockingDependencies() ([]*Issue, error) {
return issue.getBlockingDependencies(x)
}

View File

@ -1,13 +1,19 @@
// Copyright 2016 The Gogs Authors. All rights reserved. // Copyright 2018 The Gitea Authors.
// Copyright 2016 The Gogs Authors.
// All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package models
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/git"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-xorm/builder" "github.com/go-xorm/builder"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
@ -66,6 +72,14 @@ const (
CommentTypeModifiedDeadline CommentTypeModifiedDeadline
// Removed a due date // Removed a due date
CommentTypeRemovedDeadline CommentTypeRemovedDeadline
// Dependency added
CommentTypeAddDependency
//Dependency removed
CommentTypeRemoveDependency
// Comment a line of code
CommentTypeCode
// Reviews a pull request by giving general feedback
CommentTypeReview
) )
// CommentTag defines comment tag type // CommentTag defines comment tag type
@ -98,12 +112,18 @@ type Comment struct {
Assignee *User `xorm:"-"` Assignee *User `xorm:"-"`
OldTitle string OldTitle string
NewTitle string NewTitle string
DependentIssueID int64
DependentIssue *Issue `xorm:"-"`
CommitID int64 CommitID int64
Line int64 Line int64 // - previous line / + proposed line
TreePath string
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`
// Path represents the 4 lines of code cemented by this comment
Patch string `xorm:"TEXT"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"` CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
@ -115,6 +135,10 @@ type Comment struct {
// For view issue page. // For view issue page.
ShowTag CommentTag `xorm:"-"` ShowTag CommentTag `xorm:"-"`
Review *Review `xorm:"-"`
ReviewID int64
Invalidated bool
} }
// LoadIssue loads issue from database // LoadIssue loads issue from database
@ -165,6 +189,20 @@ func (c *Comment) HTMLURL() string {
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err) log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
if c.Type == CommentTypeCode {
if c.ReviewID == 0 {
return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag())
}
if c.Review == nil {
if err := c.LoadReview(); err != nil {
log.Warn("LoadReview(%d): %v", c.ReviewID, err)
return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag())
}
}
if c.Review.Type <= ReviewTypePending {
return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag())
}
}
return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag()) return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
} }
@ -281,6 +319,15 @@ func (c *Comment) LoadAssigneeUser() error {
return nil return nil
} }
// LoadDepIssueDetails loads Dependent Issue Details
func (c *Comment) LoadDepIssueDetails() (err error) {
if c.DependentIssueID <= 0 || c.DependentIssue != nil {
return nil
}
c.DependentIssue, err = getIssueByID(x, c.DependentIssueID)
return err
}
// MailParticipants sends new comment emails to repository watchers // MailParticipants sends new comment emails to repository watchers
// and mentioned people. // and mentioned people.
func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
@ -327,11 +374,95 @@ func (c *Comment) LoadReactions() error {
return c.loadReactions(x) return c.loadReactions(x)
} }
func (c *Comment) loadReview(e Engine) (err error) {
if c.Review, err = getReviewByID(e, c.ReviewID); err != nil {
return err
}
return nil
}
// LoadReview loads the associated review
func (c *Comment) LoadReview() error {
return c.loadReview(x)
}
func (c *Comment) checkInvalidation(e Engine, doer *User, repo *git.Repository, branch string) error {
// FIXME differentiate between previous and proposed line
commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
if err != nil {
return err
}
if c.CommitSHA != "" && c.CommitSHA != commit.ID.String() {
c.Invalidated = true
return UpdateComment(doer, c, "")
}
return nil
}
// CheckInvalidation checks if the line of code comment got changed by another commit.
// If the line got changed the comment is going to be invalidated.
func (c *Comment) CheckInvalidation(repo *git.Repository, doer *User, branch string) error {
return c.checkInvalidation(x, doer, repo, branch)
}
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
func (c *Comment) DiffSide() string {
if c.Line < 0 {
return "previous"
}
return "proposed"
}
// UnsignedLine returns the LOC of the code comment without + or -
func (c *Comment) UnsignedLine() uint64 {
if c.Line < 0 {
return uint64(c.Line * -1)
}
return uint64(c.Line)
}
// AsDiff returns c.Patch as *Diff
func (c *Comment) AsDiff() (*Diff, error) {
diff, err := ParsePatch(setting.Git.MaxGitDiffLines,
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch))
if err != nil {
return nil, err
}
if len(diff.Files) == 0 {
return nil, fmt.Errorf("no file found for comment ID: %d", c.ID)
}
secs := diff.Files[0].Sections
if len(secs) == 0 {
return nil, fmt.Errorf("no sections found for comment ID: %d", c.ID)
}
return diff, nil
}
// MustAsDiff executes AsDiff and logs the error instead of returning
func (c *Comment) MustAsDiff() *Diff {
diff, err := c.AsDiff()
if err != nil {
log.Warn("MustAsDiff: %v", err)
}
return diff
}
// CodeCommentURL returns the url to a comment in code
func (c *Comment) CodeCommentURL() string {
err := c.LoadIssue()
if err != nil { // Silently dropping errors :unamused:
log.Error(4, "LoadIssue(%d): %v", c.IssueID, err)
return ""
}
return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag())
}
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
var LabelID int64 var LabelID int64
if opts.Label != nil { if opts.Label != nil {
LabelID = opts.Label.ID LabelID = opts.Label.ID
} }
comment := &Comment{ comment := &Comment{
Type: opts.Type, Type: opts.Type,
PosterID: opts.Doer.ID, PosterID: opts.Doer.ID,
@ -348,6 +479,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
Content: opts.Content, Content: opts.Content,
OldTitle: opts.OldTitle, OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle, NewTitle: opts.NewTitle,
DependentIssueID: opts.DependentIssueID,
TreePath: opts.TreePath,
ReviewID: opts.ReviewID,
Patch: opts.Patch,
} }
if _, err = e.Insert(comment); err != nil { if _, err = e.Insert(comment); err != nil {
return nil, err return nil, err
@ -357,6 +492,14 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
return nil, err return nil, err
} }
if err = sendCreateCommentAction(e, opts, comment); err != nil {
return nil, err
}
return comment, nil
}
func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, comment *Comment) (err error) {
// Compose comment action, could be plain comment, close or reopen issue/pull request. // Compose comment action, could be plain comment, close or reopen issue/pull request.
// This object will be used to notify watchers in the end of function. // This object will be used to notify watchers in the end of function.
act := &Action{ act := &Action{
@ -369,14 +512,25 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
CommentID: comment.ID, CommentID: comment.ID,
IsPrivate: opts.Repo.IsPrivate, IsPrivate: opts.Repo.IsPrivate,
} }
// Check comment type. // Check comment type.
switch opts.Type { switch opts.Type {
case CommentTypeCode:
if comment.ReviewID != 0 {
if comment.Review == nil {
if err := comment.LoadReview(); err != nil {
return err
}
}
if comment.Review.Type <= ReviewTypePending {
return nil
}
}
fallthrough
case CommentTypeComment: case CommentTypeComment:
act.OpType = ActionCommentIssue act.OpType = ActionCommentIssue
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
return nil, err return err
} }
// Check attachments // Check attachments
@ -387,7 +541,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if IsErrAttachmentNotExist(err) { if IsErrAttachmentNotExist(err) {
continue continue
} }
return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err) return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
} }
attachments = append(attachments, attach) attachments = append(attachments, attach)
} }
@ -397,7 +551,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
attachments[i].CommentID = comment.ID attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols(). // No assign value could be 0, so ignore AllCols().
if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil { if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err) return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
} }
} }
@ -413,7 +567,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID) _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID)
} }
if err != nil { if err != nil {
return nil, err return err
} }
case CommentTypeClose: case CommentTypeClose:
@ -428,15 +582,13 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID) _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID)
} }
if err != nil { if err != nil {
return nil, err return err
} }
} }
// update the issue's updated_unix column // update the issue's updated_unix column
if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil { if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil {
return nil, err return err
} }
// Notify watchers for whatever action comes in, ignore if no action type. // Notify watchers for whatever action comes in, ignore if no action type.
if act.OpType > 0 { if act.OpType > 0 {
if err = notifyWatchers(e, act); err != nil { if err = notifyWatchers(e, act); err != nil {
@ -446,8 +598,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
log.Error(4, "MailParticipants: %v", err) log.Error(4, "MailParticipants: %v", err)
} }
} }
return nil
return comment, nil
} }
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) { func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
@ -549,6 +700,39 @@ func createDeleteBranchComment(e *xorm.Session, doer *User, repo *Repository, is
}) })
} }
// Creates issue dependency comment
func createIssueDependencyComment(e *xorm.Session, doer *User, issue *Issue, dependentIssue *Issue, add bool) (err error) {
cType := CommentTypeAddDependency
if !add {
cType = CommentTypeRemoveDependency
}
// Make two comments, one in each issue
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
DependentIssueID: dependentIssue.ID,
})
if err != nil {
return
}
_, err = createComment(e, &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: dependentIssue,
DependentIssueID: issue.ID,
})
if err != nil {
return
}
return
}
// CreateCommentOptions defines options for creating comment // CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct { type CreateCommentOptions struct {
Type CommentType Type CommentType
@ -557,6 +741,7 @@ type CreateCommentOptions struct {
Issue *Issue Issue *Issue
Label *Label Label *Label
DependentIssueID int64
OldMilestoneID int64 OldMilestoneID int64
MilestoneID int64 MilestoneID int64
AssigneeID int64 AssigneeID int64
@ -565,7 +750,10 @@ type CreateCommentOptions struct {
NewTitle string NewTitle string
CommitID int64 CommitID int64
CommitSHA string CommitSHA string
Patch string
LineNum int64 LineNum int64
TreePath string
ReviewID int64
Content string Content string
Attachments []string // UUIDs of attachments Attachments []string // UUIDs of attachments
} }
@ -622,6 +810,58 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
return comment, nil return comment, nil
} }
// CreateCodeComment creates a plain code comment at the specified line / path
func CreateCodeComment(doer *User, repo *Repository, issue *Issue, content, treePath string, line, reviewID int64) (*Comment, error) {
var commitID, patch string
pr, err := GetPullRequestByIssueID(issue.ID)
if err != nil {
return nil, fmt.Errorf("GetPullRequestByIssueID: %v", err)
}
if err := pr.GetBaseRepo(); err != nil {
return nil, fmt.Errorf("GetHeadRepo: %v", err)
}
gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
if err != nil {
return nil, fmt.Errorf("OpenRepository: %v", err)
}
// FIXME validate treePath
// Get latest commit referencing the commented line
// No need for get commit for base branch changes
if line > 0 {
commit, err := gitRepo.LineBlame(pr.GetGitRefName(), gitRepo.Path, treePath, uint(line))
if err != nil {
return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitRefName(), gitRepo.Path, treePath, line, err)
}
commitID = commit.ID.String()
}
// Only fetch diff if comment is review comment
if reviewID != 0 {
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err)
}
patchBuf := new(bytes.Buffer)
if err := GetRawDiffForFile(gitRepo.Path, pr.MergeBase, headCommitID, RawDiffNormal, treePath, patchBuf); err != nil {
return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", err, gitRepo.Path, pr.MergeBase, headCommitID, treePath)
}
patch = CutDiffAroundLine(strings.NewReader(patchBuf.String()), int64((&Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
}
return CreateComment(&CreateCommentOptions{
Type: CommentTypeCode,
Doer: doer,
Repo: repo,
Issue: issue,
Content: content,
LineNum: line,
TreePath: treePath,
CommitSHA: commitID,
ReviewID: reviewID,
Patch: patch,
})
}
// CreateRefComment creates a commit reference comment to issue. // CreateRefComment creates a commit reference comment to issue.
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
if len(commitSHA) == 0 { if len(commitSHA) == 0 {
@ -667,6 +907,7 @@ func GetCommentByID(id int64) (*Comment, error) {
type FindCommentsOptions struct { type FindCommentsOptions struct {
RepoID int64 RepoID int64
IssueID int64 IssueID int64
ReviewID int64
Since int64 Since int64
Type CommentType Type CommentType
} }
@ -679,6 +920,9 @@ func (opts *FindCommentsOptions) toConds() builder.Cond {
if opts.IssueID > 0 { if opts.IssueID > 0 {
cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID}) cond = cond.And(builder.Eq{"comment.issue_id": opts.IssueID})
} }
if opts.ReviewID > 0 {
cond = cond.And(builder.Eq{"comment.review_id": opts.ReviewID})
}
if opts.Since > 0 { if opts.Since > 0 {
cond = cond.And(builder.Gte{"comment.updated_unix": opts.Since}) cond = cond.And(builder.Gte{"comment.updated_unix": opts.Since})
} }
@ -819,3 +1063,75 @@ func DeleteComment(doer *User, comment *Comment) error {
return nil return nil
} }
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
type CodeComments map[string]map[int64][]*Comment
func fetchCodeComments(e Engine, issue *Issue, currentUser *User) (CodeComments, error) {
return fetchCodeCommentsByReview(e, issue, currentUser, nil)
}
func fetchCodeCommentsByReview(e Engine, issue *Issue, currentUser *User, review *Review) (CodeComments, error) {
pathToLineToComment := make(CodeComments)
if review == nil {
review = &Review{ID: 0}
}
//Find comments
opts := FindCommentsOptions{
Type: CommentTypeCode,
IssueID: issue.ID,
ReviewID: review.ID,
}
conds := opts.toConds()
if review.ID == 0 {
conds = conds.And(builder.Eq{"invalidated": false})
}
var comments []*Comment
if err := e.Where(conds).
Asc("comment.created_unix").
Asc("comment.id").
Find(&comments); err != nil {
return nil, err
}
if err := issue.loadRepo(e); err != nil {
return nil, err
}
// Find all reviews by ReviewID
reviews := make(map[int64]*Review)
var ids = make([]int64, 0, len(comments))
for _, comment := range comments {
if comment.ReviewID != 0 {
ids = append(ids, comment.ReviewID)
}
}
if err := e.In("id", ids).Find(&reviews); err != nil {
return nil, err
}
for _, comment := range comments {
if re, ok := reviews[comment.ReviewID]; ok && re != nil {
// If the review is pending only the author can see the comments (except the review is set)
if review.ID == 0 {
if re.Type == ReviewTypePending &&
(currentUser == nil || currentUser.ID != re.ReviewerID) {
continue
}
}
comment.Review = re
}
comment.RenderedContent = string(markdown.Render([]byte(comment.Content), issue.Repo.Link(),
issue.Repo.ComposeMetas()))
if pathToLineToComment[comment.TreePath] == nil {
pathToLineToComment[comment.TreePath] = make(map[int64][]*Comment)
}
pathToLineToComment[comment.TreePath][comment.Line] = append(pathToLineToComment[comment.TreePath][comment.Line], comment)
}
return pathToLineToComment, nil
}
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
func FetchCodeComments(issue *Issue, currentUser *User) (CodeComments, error) {
return fetchCodeComments(x, issue, currentUser)
}

View File

@ -39,3 +39,21 @@ func TestCreateComment(t *testing.T) {
updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue) updatedIssue := AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue)
AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
} }
func TestFetchCodeComments(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
res, err := FetchCodeComments(issue, user)
assert.NoError(t, err)
assert.Contains(t, res, "README.md")
assert.Contains(t, res["README.md"], int64(4))
assert.Len(t, res["README.md"][4], 1)
assert.Equal(t, int64(4), res["README.md"][4][0].ID)
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
res, err = FetchCodeComments(issue, user2)
assert.NoError(t, err)
assert.Len(t, res, 1)
}

137
models/issue_dependency.go Normal file
View File

@ -0,0 +1,137 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// IssueDependency represents an issue dependency
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
DependencyID int64 `xorm:"UNIQUE(issue_dependency) NOT NULL"`
CreatedUnix util.TimeStamp `xorm:"created"`
UpdatedUnix util.TimeStamp `xorm:"updated"`
}
// DependencyType Defines Dependency Type Constants
type DependencyType int
// Define Dependency Types
const (
DependencyTypeBlockedBy DependencyType = iota
DependencyTypeBlocking
)
// CreateIssueDependency creates a new dependency for an issue
func CreateIssueDependency(user *User, issue, dep *Issue) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// Check if it aleready exists
exists, err := issueDepExists(sess, issue.ID, dep.ID)
if err != nil {
return err
}
if exists {
return ErrDependencyExists{issue.ID, dep.ID}
}
// And if it would be circular
circular, err := issueDepExists(sess, dep.ID, issue.ID)
if err != nil {
return err
}
if circular {
return ErrCircularDependency{issue.ID, dep.ID}
}
if _, err := sess.Insert(&IssueDependency{
UserID: user.ID,
IssueID: issue.ID,
DependencyID: dep.ID,
}); err != nil {
return err
}
// Add comment referencing the new dependency
if err = createIssueDependencyComment(sess, user, issue, dep, true); err != nil {
return err
}
return sess.Commit()
}
// RemoveIssueDependency removes a dependency from an issue
func RemoveIssueDependency(user *User, issue *Issue, dep *Issue, depType DependencyType) (err error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
var issueDepToDelete IssueDependency
switch depType {
case DependencyTypeBlockedBy:
issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
case DependencyTypeBlocking:
issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
default:
return ErrUnknownDependencyType{depType}
}
affected, err := sess.Delete(&issueDepToDelete)
if err != nil {
return err
}
// If we deleted nothing, the dependency did not exist
if affected <= 0 {
return ErrDependencyNotExists{issue.ID, dep.ID}
}
// Add comment referencing the removed dependency
if err = createIssueDependencyComment(sess, user, issue, dep, false); err != nil {
return err
}
return sess.Commit()
}
// Check if the dependency already exists
func issueDepExists(e Engine, issueID int64, depID int64) (bool, error) {
return e.Where("(issue_id = ? AND dependency_id = ?)", issueID, depID).Exist(&IssueDependency{})
}
// IssueNoDependenciesLeft checks if issue can be closed
func IssueNoDependenciesLeft(issue *Issue) (bool, error) {
exists, err := x.
Table("issue_dependency").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_dependency.dependency_id").
Where("issue_dependency.issue_id = ?", issue.ID).
And("issue.is_closed = ?", "0").
Exist(&Issue{})
return !exists, err
}
// IsDependenciesEnabled returns if dependecies are enabled and returns the default setting if not set.
func (repo *Repository) IsDependenciesEnabled() bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnit(UnitTypeIssues); err != nil {
log.Trace("%s", err)
return setting.Service.DefaultEnableDependencies
}
return u.IssuesConfig().EnableDependencies
}

View File

@ -0,0 +1,57 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateIssueDependency(t *testing.T) {
// Prepare
assert.NoError(t, PrepareTestDatabase())
user1, err := GetUserByID(1)
assert.NoError(t, err)
issue1, err := GetIssueByID(1)
assert.NoError(t, err)
issue2, err := GetIssueByID(2)
assert.NoError(t, err)
// Create a dependency and check if it was successful
err = CreateIssueDependency(user1, issue1, issue2)
assert.NoError(t, err)
// Do it again to see if it will check if the dependency already exists
err = CreateIssueDependency(user1, issue1, issue2)
assert.Error(t, err)
assert.True(t, IsErrDependencyExists(err))
// Check for circular dependencies
err = CreateIssueDependency(user1, issue2, issue1)
assert.Error(t, err)
assert.True(t, IsErrCircularDependency(err))
_ = AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddDependency, PosterID: user1.ID, IssueID: issue1.ID})
// Check if dependencies left is correct
left, err := IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.False(t, left)
// Close #2 and check again
err = issue2.ChangeStatus(user1, issue2.Repo, true)
assert.NoError(t, err)
left, err = IssueNoDependenciesLeft(issue1)
assert.NoError(t, err)
assert.True(t, left)
// Test removing the dependency
err = RemoveIssueDependency(user1, issue1, issue2, DependencyTypeBlockedBy)
assert.NoError(t, err)
}

View File

@ -181,7 +181,7 @@ func (milestones MilestoneList) getMilestoneIDs() []int64 {
// GetMilestonesByRepoID returns all milestones of a repository. // GetMilestonesByRepoID returns all milestones of a repository.
func GetMilestonesByRepoID(repoID int64) (MilestoneList, error) { func GetMilestonesByRepoID(repoID int64) (MilestoneList, error) {
miles := make([]*Milestone, 0, 10) miles := make([]*Milestone, 0, 10)
return miles, x.Where("repo_id = ?", repoID).Find(&miles) return miles, x.Where("repo_id = ?", repoID).Asc("deadline_unix").Find(&miles)
} }
// GetMilestones returns a list of milestones of given repository and status. // GetMilestones returns a list of milestones of given repository and status.

View File

@ -192,6 +192,14 @@ var migrations = []Migration{
NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics), NewMigration("Reformat and remove incorrect topics", reformatAndRemoveIncorrectTopics),
// v69 -> v70 // v69 -> v70
NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable),
// v70 -> v71
NewMigration("add issue_dependencies", addIssueDependencies),
// v71 -> v72
NewMigration("protect each scratch token", addScratchHash),
// v72 -> v73
NewMigration("add review", addReview),
// v73 -> v74
NewMigration("add must_change_password column for users table", addMustChangePassword),
} }
// Migrate database to current version // Migrate database to current version

View File

@ -34,7 +34,7 @@ func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
pageSize := models.RepositoryListDefaultPageSize pageSize := models.RepositoryListDefaultPageSize
for { for {
repos := make([]*models.Repository, 0, pageSize) repos := make([]*models.Repository, 0, pageSize)
if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil { if err := x.Table("repository").Cols("id", "name", "owner_id").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
return fmt.Errorf("select repos [offset: %d]: %v", offset, err) return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
} }
for _, repo := range repos { for _, repo := range repos {

100
models/migrations/v70.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addIssueDependencies(x *xorm.Engine) (err error) {
type IssueDependency struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"NOT NULL"`
DependencyID int64 `xorm:"NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"created"`
Updated time.Time `xorm:"-"`
UpdatedUnix int64 `xorm:"updated"`
}
if err = x.Sync(new(IssueDependency)); err != nil {
return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
}
// Update Comment definition
// This (copied) struct does only contain fields used by xorm as the only use here is to update the database
// CommentType defines the comment type
type CommentType int
// TimeStamp defines a timestamp
type TimeStamp int64
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
IssueID int64 `xorm:"INDEX"`
LabelID int64
OldMilestoneID int64
MilestoneID int64
OldAssigneeID int64
AssigneeID int64
OldTitle string
NewTitle string
DependentIssueID int64
CommitID int64
Line int64
Content string `xorm:"TEXT"`
CreatedUnix TimeStamp `xorm:"INDEX created"`
UpdatedUnix TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"`
}
if err = x.Sync(new(Comment)); err != nil {
return fmt.Errorf("Error updating issue_comment table column definition: %v", err)
}
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64 `xorm:"INDEX(s)"`
Type int `xorm:"INDEX(s)"`
Config map[string]interface{} `xorm:"JSON"`
CreatedUnix int64 `xorm:"INDEX CREATED"`
Created time.Time `xorm:"-"`
}
//Updating existing issue units
units := make([]*RepoUnit, 0, 100)
err = x.Where("`type` = ?", V16UnitTypeIssues).Find(&units)
if err != nil {
return fmt.Errorf("Query repo units: %v", err)
}
for _, unit := range units {
if unit.Config == nil {
unit.Config = make(map[string]interface{})
}
if _, ok := unit.Config["EnableDependencies"]; !ok {
unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}
}
return err
}

88
models/migrations/v71.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"crypto/sha256"
"fmt"
"github.com/go-xorm/xorm"
"golang.org/x/crypto/pbkdf2"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/util"
)
func addScratchHash(x *xorm.Engine) error {
// TwoFactor see models/twofactor.go
type TwoFactor struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE"`
Secret string
ScratchToken string
ScratchSalt string
ScratchHash string
LastUsedPasscode string `xorm:"VARCHAR(10)"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(new(TwoFactor)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
// transform all tokens to hashes
const batchSize = 100
for start := 0; ; start += batchSize {
tfas := make([]*TwoFactor, 0, batchSize)
if err := x.Limit(batchSize, start).Find(&tfas); err != nil {
return err
}
if len(tfas) == 0 {
break
}
for _, tfa := range tfas {
// generate salt
salt, err := generate.GetRandomString(10)
if err != nil {
return err
}
tfa.ScratchSalt = salt
tfa.ScratchHash = hashToken(tfa.ScratchToken, salt)
if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil {
return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %v", err)
}
}
}
// Commit and begin new transaction for dropping columns
if err := sess.Commit(); err != nil {
return err
}
if err := sess.Begin(); err != nil {
return err
}
if err := dropTableColumns(sess, "two_factor", "scratch_token"); err != nil {
return err
}
return sess.Commit()
}
func hashToken(token, salt string) string {
tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
return fmt.Sprintf("%x", tempHash)
}

31
models/migrations/v72.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/xorm"
)
func addReview(x *xorm.Engine) error {
// Review see models/review.go
type Review struct {
ID int64 `xorm:"pk autoincr"`
Type string
ReviewerID int64 `xorm:"index"`
IssueID int64 `xorm:"index"`
Content string
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(new(Review)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}

19
models/migrations/v73.go Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"github.com/go-xorm/xorm"
)
func addMustChangePassword(x *xorm.Engine) error {
// User see models/user.go
type User struct {
ID int64 `xorm:"pk autoincr"`
MustChangePassword bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync2(new(User))
}

View File

@ -118,11 +118,13 @@ func init() {
new(TrackedTime), new(TrackedTime),
new(DeletedBranch), new(DeletedBranch),
new(RepoIndexerStatus), new(RepoIndexerStatus),
new(IssueDependency),
new(LFSLock), new(LFSLock),
new(Reaction), new(Reaction),
new(IssueAssignees), new(IssueAssignees),
new(U2FRegistration), new(U2FRegistration),
new(TeamUnit), new(TeamUnit),
new(Review),
) )
gonicNames := []string{"SSL", "UID"} gonicNames := []string{"SSL", "UID"}
@ -153,7 +155,7 @@ func LoadConfigs() {
if len(DbCfg.Passwd) == 0 { if len(DbCfg.Passwd) == 0 {
DbCfg.Passwd = sec.Key("PASSWD").String() DbCfg.Passwd = sec.Key("PASSWD").String()
} }
DbCfg.SSLMode = sec.Key("SSL_MODE").String() DbCfg.SSLMode = sec.Key("SSL_MODE").MustString("disable")
DbCfg.Path = sec.Key("PATH").MustString("data/gitea.db") DbCfg.Path = sec.Key("PATH").MustString("data/gitea.db")
DbCfg.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500) DbCfg.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
@ -220,13 +222,16 @@ func getEngine() (*xorm.Engine, error) {
} }
switch DbCfg.Type { switch DbCfg.Type {
case "mysql": case "mysql":
connType := "tcp"
if DbCfg.Host[0] == '/' { // looks like a unix socket if DbCfg.Host[0] == '/' { // looks like a unix socket
connStr = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8&parseTime=true", connType = "unix"
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
} else {
connStr = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8&parseTime=true",
DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name, Param)
} }
tls := DbCfg.SSLMode
if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL
tls = "false"
}
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=utf8&parseTime=true&tls=%s",
DbCfg.User, DbCfg.Passwd, connType, DbCfg.Host, DbCfg.Name, Param, tls)
case "postgres": case "postgres":
connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode) connStr = getPostgreSQLConnectionString(DbCfg.Host, DbCfg.User, DbCfg.Passwd, DbCfg.Name, Param, DbCfg.SSLMode)
case "mssql": case "mssql":

View File

@ -1,18 +0,0 @@
// +build tidb
// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
_ "github.com/go-xorm/tidb"
"github.com/ngaut/log"
_ "github.com/pingcap/tidb"
)
func init() {
EnableTiDB = true
log.SetLevelByString("error")
}

View File

@ -182,10 +182,6 @@ func CreateOrganization(org, owner *User) (err error) {
return fmt.Errorf("insert team-user relation: %v", err) return fmt.Errorf("insert team-user relation: %v", err)
} }
if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil {
return fmt.Errorf("create directory: %v", err)
}
return sess.Commit() return sess.Commit()
} }

View File

@ -214,7 +214,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
} }
if pr.Status != PullRequestStatusChecking { if pr.Status != PullRequestStatusChecking {
mergeable := pr.Status != PullRequestStatusConflict mergeable := pr.Status != PullRequestStatusConflict && !pr.IsWorkInProgress()
apiPullRequest.Mergeable = mergeable apiPullRequest.Mergeable = mergeable
} }
if pr.HasMerged { if pr.HasMerged {
@ -1075,10 +1075,7 @@ func (prs PullRequestList) loadAttributes(e Engine) error {
} }
// Load issues. // Load issues.
issueIDs := make([]int64, 0, len(prs)) issueIDs := prs.getIssueIDs()
for i := range prs {
issueIDs = append(issueIDs, prs[i].IssueID)
}
issues := make([]*Issue, 0, len(issueIDs)) issues := make([]*Issue, 0, len(issueIDs))
if err := e. if err := e.
Where("id > 0"). Where("id > 0").
@ -1097,11 +1094,44 @@ func (prs PullRequestList) loadAttributes(e Engine) error {
return nil return nil
} }
func (prs PullRequestList) getIssueIDs() []int64 {
issueIDs := make([]int64, 0, len(prs))
for i := range prs {
issueIDs = append(issueIDs, prs[i].IssueID)
}
return issueIDs
}
// LoadAttributes load all the prs attributes // LoadAttributes load all the prs attributes
func (prs PullRequestList) LoadAttributes() error { func (prs PullRequestList) LoadAttributes() error {
return prs.loadAttributes(x) return prs.loadAttributes(x)
} }
func (prs PullRequestList) invalidateCodeComments(e Engine, doer *User, repo *git.Repository, branch string) error {
if len(prs) == 0 {
return nil
}
issueIDs := prs.getIssueIDs()
var codeComments []*Comment
if err := e.
Where("type = ? and invalidated = ?", CommentTypeCode, false).
In("issue_id", issueIDs).
Find(&codeComments); err != nil {
return fmt.Errorf("find code comments: %v", err)
}
for _, comment := range codeComments {
if err := comment.CheckInvalidation(repo, doer, branch); err != nil {
return err
}
}
return nil
}
// InvalidateCodeComments will lookup the prs for code comments which got invalidated by change
func (prs PullRequestList) InvalidateCodeComments(doer *User, repo *git.Repository, branch string) error {
return prs.invalidateCodeComments(x, doer, repo, branch)
}
func addHeadRepoTasks(prs []*PullRequest) { func addHeadRepoTasks(prs []*PullRequest) {
for _, pr := range prs { for _, pr := range prs {
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID) log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
@ -1128,10 +1158,13 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
} }
if isSync { if isSync {
if err = PullRequestList(prs).LoadAttributes(); err != nil { requests := PullRequestList(prs)
if err = requests.LoadAttributes(); err != nil {
log.Error(4, "PullRequestList.LoadAttributes: %v", err) log.Error(4, "PullRequestList.LoadAttributes: %v", err)
} }
if invalidationErr := checkForInvalidation(requests, repoID, doer, branch); invalidationErr != nil {
log.Error(4, "checkForInvalidation: %v", invalidationErr)
}
if err == nil { if err == nil {
for _, pr := range prs { for _, pr := range prs {
pr.Issue.PullRequest = pr pr.Issue.PullRequest = pr
@ -1152,6 +1185,7 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
go HookQueue.Add(pr.Issue.Repo.ID) go HookQueue.Add(pr.Issue.Repo.ID)
} }
} }
} }
addHeadRepoTasks(prs) addHeadRepoTasks(prs)
@ -1167,6 +1201,24 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
} }
} }
func checkForInvalidation(requests PullRequestList, repoID int64, doer *User, branch string) error {
repo, err := GetRepositoryByID(repoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil {
return fmt.Errorf("git.OpenRepository: %v", err)
}
go func() {
err := requests.InvalidateCodeComments(doer, gitRepo, branch)
if err != nil {
log.Error(4, "PullRequestList.InvalidateCodeComments: %v", err)
}
}()
return nil
}
// ChangeUsernameInPullRequests changes the name of head_user_name // ChangeUsernameInPullRequests changes the name of head_user_name
func ChangeUsernameInPullRequests(oldUserName, newUserName string) error { func ChangeUsernameInPullRequests(oldUserName, newUserName string) error {
pr := PullRequest{ pr := PullRequest{
@ -1195,6 +1247,37 @@ func (pr *PullRequest) checkAndUpdateStatus() {
} }
} }
// IsWorkInProgress determine if the Pull Request is a Work In Progress by its title
func (pr *PullRequest) IsWorkInProgress() bool {
if err := pr.LoadIssue(); err != nil {
log.Error(4, "LoadIssue: %v", err)
return false
}
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), prefix) {
return true
}
}
return false
}
// GetWorkInProgressPrefix returns the prefix used to mark the pull request as a work in progress.
// It returns an empty string when none were found
func (pr *PullRequest) GetWorkInProgressPrefix() string {
if err := pr.LoadIssue(); err != nil {
log.Error(4, "LoadIssue: %v", err)
return ""
}
for _, prefix := range setting.Repository.PullRequest.WorkInProgressPrefixes {
if strings.HasPrefix(strings.ToUpper(pr.Issue.Title), prefix) {
return pr.Issue.Title[0:len(prefix)]
}
}
return ""
}
// TestPullRequests checks and tests untested patches of pull requests. // TestPullRequests checks and tests untested patches of pull requests.
// TODO: test more pull requests at same time. // TODO: test more pull requests at same time.
func TestPullRequests() { func TestPullRequests() {

View File

@ -237,3 +237,34 @@ func TestChangeUsernameInPullRequests(t *testing.T) {
} }
CheckConsistencyFor(t, &PullRequest{}) CheckConsistencyFor(t, &PullRequest{})
} }
func TestPullRequest_IsWorkInProgress(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
pr.LoadIssue()
assert.False(t, pr.IsWorkInProgress())
pr.Issue.Title = "WIP: " + pr.Issue.Title
assert.True(t, pr.IsWorkInProgress())
pr.Issue.Title = "[wip]: " + pr.Issue.Title
assert.True(t, pr.IsWorkInProgress())
}
func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
pr.LoadIssue()
assert.Empty(t, pr.GetWorkInProgressPrefix())
original := pr.Issue.Title
pr.Issue.Title = "WIP: " + original
assert.Equal(t, "WIP:", pr.GetWorkInProgressPrefix())
pr.Issue.Title = "[wip] " + original
assert.Equal(t, "[wip]", pr.GetWorkInProgressPrefix())
}

View File

@ -781,7 +781,7 @@ var (
// DescriptionHTML does special handles to description and return HTML string. // DescriptionHTML does special handles to description and return HTML string.
func (repo *Repository) DescriptionHTML() template.HTML { func (repo *Repository) DescriptionHTML() template.HTML {
sanitize := func(s string) string { sanitize := func(s string) string {
return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener">%[1]s</a>`, s) return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener noreferrer">%[1]s</a>`, s)
} }
return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize)) return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize))
} }
@ -1345,7 +1345,11 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
units = append(units, RepoUnit{ units = append(units, RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: tp, Type: tp,
Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking, AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime}, Config: &IssuesConfig{
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
EnableDependencies: setting.Service.DefaultEnableDependencies,
},
}) })
} else if tp == UnitTypePullRequests { } else if tp == UnitTypePullRequests {
units = append(units, RepoUnit{ units = append(units, RepoUnit{

View File

@ -131,6 +131,8 @@ type SearchRepoOptions struct {
// True -> include just mirrors // True -> include just mirrors
// False -> include just non-mirrors // False -> include just non-mirrors
Mirror util.OptionalBool Mirror util.OptionalBool
// only search topic name
TopicOnly bool
} }
//SearchOrderBy is used to sort the result //SearchOrderBy is used to sort the result
@ -184,7 +186,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
if opts.Collaborate != util.OptionalBoolFalse { if opts.Collaborate != util.OptionalBoolFalse {
collaborateCond := builder.And( collaborateCond := builder.And(
builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID), builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
builder.Neq{"owner_id": opts.OwnerID}) builder.Neq{"owner_id": opts.OwnerID})
if !opts.Private { if !opts.Private {
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false)) collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
@ -202,7 +204,14 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
} }
if opts.Keyword != "" { if opts.Keyword != "" {
cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)}) var keywordCond = builder.NewCond()
if opts.TopicOnly {
keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
} else {
keywordCond = keywordCond.Or(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
}
cond = cond.And(keywordCond)
} }
if opts.Fork != util.OptionalBoolNone { if opts.Fork != util.OptionalBoolNone {
@ -224,9 +233,15 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
sess.Join("INNER", "star", "star.repo_id = repository.id") sess.Join("INNER", "star", "star.repo_id = repository.id")
} }
if opts.Keyword != "" {
sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
}
count, err := sess. count, err := sess.
Where(cond). Where(cond).
Count(new(Repository)) Count(new(Repository))
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err) return nil, 0, fmt.Errorf("Count: %v", err)
} }
@ -236,11 +251,23 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
sess.Join("INNER", "star", "star.repo_id = repository.id") sess.Join("INNER", "star", "star.repo_id = repository.id")
} }
if opts.Keyword != "" {
sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
}
if opts.Keyword != "" {
sess.Select("repository.*")
sess.GroupBy("repository.id")
sess.OrderBy("repository." + opts.OrderBy.String())
} else {
sess.OrderBy(opts.OrderBy.String())
}
repos := make(RepositoryList, 0, opts.PageSize) repos := make(RepositoryList, 0, opts.PageSize)
if err = sess. if err = sess.
Where(cond). Where(cond).
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
OrderBy(opts.OrderBy.String()).
Find(&repos); err != nil { Find(&repos); err != nil {
return nil, 0, fmt.Errorf("Repo: %v", err) return nil, 0, fmt.Errorf("Repo: %v", err)
} }

View File

@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 14}, count: 14},
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", {name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
count: 17}, count: 19},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 21}, count: 23},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 13}, count: 13},
@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
count: 11}, count: 11},
{name: "AllPublic/PublicRepositoriesOfOrganization", {name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse}, opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
count: 17}, count: 19},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@ -222,3 +222,28 @@ func TestSearchRepositoryByName(t *testing.T) {
}) })
} }
} }
func TestSearchRepositoryByTopicName(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testCases := []struct {
name string
opts *SearchRepoOptions
count int
}{
{name: "AllPublic/SearchPublicRepositoriesFromTopicAndName",
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"},
count: 2},
{name: "AllPublic/OnlySearchPublicRepositoriesFromTopic",
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true},
count: 1},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
_, count, err := SearchRepositoryByName(testCase.opts)
assert.NoError(t, err)
assert.Equal(t, int64(testCase.count), count)
})
}
}

View File

@ -1,4 +1,5 @@
// Copyright 2016 The Gogs Authors. All rights reserved. // Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -6,6 +7,7 @@ package models
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"code.gitea.io/git" "code.gitea.io/git"
@ -119,8 +121,68 @@ func (m *Mirror) SaveAddress(addr string) error {
return cfg.SaveToIndent(configPath, "\t") return cfg.SaveToIndent(configPath, "\t")
} }
// gitShortEmptySha Git short empty SHA
const gitShortEmptySha = "0000000"
// mirrorSyncResult contains information of a updated reference.
// If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty.
// If the newCommitID is "0000000", it means the reference is deleted, the value of oldCommitID is empty.
type mirrorSyncResult struct {
refName string
oldCommitID string
newCommitID string
}
// parseRemoteUpdateOutput detects create, update and delete operations of references from upstream.
func parseRemoteUpdateOutput(output string) []*mirrorSyncResult {
results := make([]*mirrorSyncResult, 0, 3)
lines := strings.Split(output, "\n")
for i := range lines {
// Make sure reference name is presented before continue
idx := strings.Index(lines[i], "-> ")
if idx == -1 {
continue
}
refName := lines[i][idx+3:]
switch {
case strings.HasPrefix(lines[i], " * "): // New reference
results = append(results, &mirrorSyncResult{
refName: refName,
oldCommitID: gitShortEmptySha,
})
case strings.HasPrefix(lines[i], " - "): // Delete reference
results = append(results, &mirrorSyncResult{
refName: refName,
newCommitID: gitShortEmptySha,
})
case strings.HasPrefix(lines[i], " "): // New commits of a reference
delimIdx := strings.Index(lines[i][3:], " ")
if delimIdx == -1 {
log.Error(2, "SHA delimiter not found: %q", lines[i])
continue
}
shas := strings.Split(lines[i][3:delimIdx+3], "..")
if len(shas) != 2 {
log.Error(2, "Expect two SHAs but not what found: %q", lines[i])
continue
}
results = append(results, &mirrorSyncResult{
refName: refName,
oldCommitID: shas[0],
newCommitID: shas[1],
})
default:
log.Warn("parseRemoteUpdateOutput: unexpected update line %q", lines[i])
}
}
return results
}
// runSync returns true if sync finished without error. // runSync returns true if sync finished without error.
func (m *Mirror) runSync() bool { func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
repoPath := m.Repo.RepoPath() repoPath := m.Repo.RepoPath()
wikiPath := m.Repo.WikiPath() wikiPath := m.Repo.WikiPath()
timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second timeout := time.Duration(setting.Git.Timeout.Mirror) * time.Second
@ -130,28 +192,30 @@ func (m *Mirror) runSync() bool {
gitArgs = append(gitArgs, "--prune") gitArgs = append(gitArgs, "--prune")
} }
if _, stderr, err := process.GetManager().ExecDir( _, stderr, err := process.GetManager().ExecDir(
timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath),
"git", gitArgs...); err != nil { "git", gitArgs...)
if err != nil {
// sanitize the output, since it may contain the remote address, which may // sanitize the output, since it may contain the remote address, which may
// contain a password // contain a password
message, err := sanitizeOutput(stderr, repoPath) message, err := sanitizeOutput(stderr, repoPath)
if err != nil { if err != nil {
log.Error(4, "sanitizeOutput: %v", err) log.Error(4, "sanitizeOutput: %v", err)
return false return nil, false
} }
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message) desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message)
log.Error(4, desc) log.Error(4, desc)
if err = CreateRepositoryNotice(desc); err != nil { if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "CreateRepositoryNotice: %v", err) log.Error(4, "CreateRepositoryNotice: %v", err)
} }
return false return nil, false
} }
output := stderr
gitRepo, err := git.OpenRepository(repoPath) gitRepo, err := git.OpenRepository(repoPath)
if err != nil { if err != nil {
log.Error(4, "OpenRepository: %v", err) log.Error(4, "OpenRepository: %v", err)
return false return nil, false
} }
if err = SyncReleasesWithTags(m.Repo, gitRepo); err != nil { if err = SyncReleasesWithTags(m.Repo, gitRepo); err != nil {
log.Error(4, "Failed to synchronize tags to releases for repository: %v", err) log.Error(4, "Failed to synchronize tags to releases for repository: %v", err)
@ -170,21 +234,21 @@ func (m *Mirror) runSync() bool {
message, err := sanitizeOutput(stderr, wikiPath) message, err := sanitizeOutput(stderr, wikiPath)
if err != nil { if err != nil {
log.Error(4, "sanitizeOutput: %v", err) log.Error(4, "sanitizeOutput: %v", err)
return false return nil, false
} }
desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message) desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message)
log.Error(4, desc) log.Error(4, desc)
if err = CreateRepositoryNotice(desc); err != nil { if err = CreateRepositoryNotice(desc); err != nil {
log.Error(4, "CreateRepositoryNotice: %v", err) log.Error(4, "CreateRepositoryNotice: %v", err)
} }
return false return nil, false
} }
} }
branches, err := m.Repo.GetBranches() branches, err := m.Repo.GetBranches()
if err != nil { if err != nil {
log.Error(4, "GetBranches: %v", err) log.Error(4, "GetBranches: %v", err)
return false return nil, false
} }
for i := range branches { for i := range branches {
@ -192,7 +256,7 @@ func (m *Mirror) runSync() bool {
} }
m.UpdatedUnix = util.TimeStampNow() m.UpdatedUnix = util.TimeStampNow()
return true return parseRemoteUpdateOutput(output), true
} }
func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) {
@ -268,7 +332,8 @@ func SyncMirrors() {
continue continue
} }
if !m.runSync() { results, ok := m.runSync()
if !ok {
continue continue
} }
@ -278,6 +343,66 @@ func SyncMirrors() {
continue continue
} }
var gitRepo *git.Repository
if len(results) == 0 {
log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID)
} else {
gitRepo, err = git.OpenRepository(m.Repo.RepoPath())
if err != nil {
log.Error(2, "OpenRepository [%d]: %v", m.RepoID, err)
continue
}
}
for _, result := range results {
// Discard GitHub pull requests, i.e. refs/pull/*
if strings.HasPrefix(result.refName, "refs/pull/") {
continue
}
// Create reference
if result.oldCommitID == gitShortEmptySha {
if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil {
log.Error(2, "MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err)
}
continue
}
// Delete reference
if result.newCommitID == gitShortEmptySha {
if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil {
log.Error(2, "MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err)
}
continue
}
// Push commits
oldCommitID, err := git.GetFullCommitID(gitRepo.Path, result.oldCommitID)
if err != nil {
log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err)
continue
}
newCommitID, err := git.GetFullCommitID(gitRepo.Path, result.newCommitID)
if err != nil {
log.Error(2, "GetFullCommitID [%d]: %v", m.RepoID, err)
continue
}
commits, err := gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID)
if err != nil {
log.Error(2, "CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err)
continue
}
if err = MirrorSyncPushAction(m.Repo, MirrorSyncPushActionOptions{
RefName: result.refName,
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
Commits: ListToPushCommits(commits),
}); err != nil {
log.Error(2, "MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err)
continue
}
}
// Get latest commit date and update to current repository updated time // Get latest commit date and update to current repository updated time
commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath()) commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath())
if err != nil { if err != nil {

View File

@ -73,6 +73,7 @@ func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
type IssuesConfig struct { type IssuesConfig struct {
EnableTimetracker bool EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool AllowOnlyContributorsToTrackTime bool
EnableDependencies bool
} }
// FromDB fills up a IssuesConfig from serialized format. // FromDB fills up a IssuesConfig from serialized format.
@ -165,7 +166,6 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig {
func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
return r.Config.(*ExternalTrackerConfig) return r.Config.(*ExternalTrackerConfig)
} }
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
return units, e.Where("repo_id = ?", repoID).Find(&units) return units, e.Where("repo_id = ?", repoID).Find(&units)
} }

256
models/review.go Normal file
View File

@ -0,0 +1,256 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/xorm"
"github.com/go-xorm/builder"
)
// ReviewType defines the sort of feedback a review gives
type ReviewType int
// ReviewTypeUnknown unknown review type
const ReviewTypeUnknown ReviewType = -1
const (
// ReviewTypePending is a review which is not published yet
ReviewTypePending ReviewType = iota
// ReviewTypeApprove approves changes
ReviewTypeApprove
// ReviewTypeComment gives general feedback
ReviewTypeComment
// ReviewTypeReject gives feedback blocking merge
ReviewTypeReject
)
// Icon returns the corresponding icon for the review type
func (rt ReviewType) Icon() string {
switch rt {
case ReviewTypeApprove:
return "eye"
case ReviewTypeReject:
return "x"
default:
case ReviewTypeComment:
case ReviewTypeUnknown:
return "comment"
}
return "comment"
}
// Review represents collection of code comments giving feedback for a PR
type Review struct {
ID int64 `xorm:"pk autoincr"`
Type ReviewType
Reviewer *User `xorm:"-"`
ReviewerID int64 `xorm:"index"`
Issue *Issue `xorm:"-"`
IssueID int64 `xorm:"index"`
Content string
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
// CodeComments are the initial code comments of the review
CodeComments CodeComments `xorm:"-"`
}
func (r *Review) loadCodeComments(e Engine) (err error) {
r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
return
}
// LoadCodeComments loads CodeComments
func (r *Review) LoadCodeComments() error {
return r.loadCodeComments(x)
}
func (r *Review) loadIssue(e Engine) (err error) {
r.Issue, err = getIssueByID(e, r.IssueID)
return
}
func (r *Review) loadReviewer(e Engine) (err error) {
if r.ReviewerID == 0 {
return nil
}
r.Reviewer, err = getUserByID(e, r.ReviewerID)
return
}
func (r *Review) loadAttributes(e Engine) (err error) {
if err = r.loadReviewer(e); err != nil {
return
}
if err = r.loadIssue(e); err != nil {
return
}
return
}
// LoadAttributes loads all attributes except CodeComments
func (r *Review) LoadAttributes() error {
return r.loadAttributes(x)
}
// Publish will send notifications / actions to participants for all code comments; parts are concurrent
func (r *Review) Publish() error {
return r.publish(x)
}
func (r *Review) publish(e *xorm.Engine) error {
if r.Type == ReviewTypePending || r.Type == ReviewTypeUnknown {
return fmt.Errorf("review cannot be published if type is pending or unknown")
}
if r.Issue == nil {
if err := r.loadIssue(e); err != nil {
return err
}
}
if err := r.Issue.loadRepo(e); err != nil {
return err
}
if len(r.CodeComments) == 0 {
if err := r.loadCodeComments(e); err != nil {
return err
}
}
for _, lines := range r.CodeComments {
for _, comments := range lines {
for _, comment := range comments {
go func(en *xorm.Engine, review *Review, comm *Comment) {
sess := en.NewSession()
defer sess.Close()
if err := sendCreateCommentAction(sess, &CreateCommentOptions{
Doer: comm.Poster,
Issue: review.Issue,
Repo: review.Issue.Repo,
Type: comm.Type,
Content: comm.Content,
}, comm); err != nil {
log.Warn("sendCreateCommentAction: %v", err)
}
}(e, r, comment)
}
}
}
return nil
}
func getReviewByID(e Engine, id int64) (*Review, error) {
review := new(Review)
if has, err := e.ID(id).Get(review); err != nil {
return nil, err
} else if !has {
return nil, ErrReviewNotExist{ID: id}
} else {
return review, nil
}
}
// GetReviewByID returns the review by the given ID
func GetReviewByID(id int64) (*Review, error) {
return getReviewByID(x, id)
}
// FindReviewOptions represent possible filters to find reviews
type FindReviewOptions struct {
Type ReviewType
IssueID int64
ReviewerID int64
}
func (opts *FindReviewOptions) toCond() builder.Cond {
var cond = builder.NewCond()
if opts.IssueID > 0 {
cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
}
if opts.ReviewerID > 0 {
cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
}
if opts.Type != ReviewTypeUnknown {
cond = cond.And(builder.Eq{"type": opts.Type})
}
return cond
}
func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) {
reviews := make([]*Review, 0, 10)
sess := e.Where(opts.toCond())
return reviews, sess.
Asc("created_unix").
Asc("id").
Find(&reviews)
}
// FindReviews returns reviews passing FindReviewOptions
func FindReviews(opts FindReviewOptions) ([]*Review, error) {
return findReviews(x, opts)
}
// CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required.
type CreateReviewOptions struct {
Content string
Type ReviewType
Issue *Issue
Reviewer *User
}
func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
review := &Review{
Type: opts.Type,
Issue: opts.Issue,
IssueID: opts.Issue.ID,
Reviewer: opts.Reviewer,
ReviewerID: opts.Reviewer.ID,
Content: opts.Content,
}
if _, err := e.Insert(review); err != nil {
return nil, err
}
return review, nil
}
// CreateReview creates a new review based on opts
func CreateReview(opts CreateReviewOptions) (*Review, error) {
return createReview(x, opts)
}
func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) {
if reviewer == nil {
return nil, nil
}
reviews, err := findReviews(e, FindReviewOptions{
Type: ReviewTypePending,
IssueID: issue.ID,
ReviewerID: reviewer.ID,
})
if err != nil {
return nil, err
}
if len(reviews) == 0 {
return nil, ErrReviewNotExist{}
}
return reviews[0], nil
}
// GetCurrentReview returns the current pending review of reviewer for given issue
func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) {
return getCurrentReview(x, reviewer, issue)
}
// UpdateReview will update all cols of the given review in db
func UpdateReview(r *Review) error {
if _, err := x.ID(r.ID).AllCols().Update(r); err != nil {
return err
}
return nil
}

107
models/review_test.go Normal file
View File

@ -0,0 +1,107 @@
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetReviewByID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
review, err := GetReviewByID(1)
assert.NoError(t, err)
assert.Equal(t, "Demo Review", review.Content)
assert.Equal(t, ReviewTypeApprove, review.Type)
_, err = GetReviewByID(23892)
assert.Error(t, err)
assert.True(t, IsErrReviewNotExist(err), "IsErrReviewNotExist")
}
func TestReview_LoadAttributes(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
review := AssertExistsAndLoadBean(t, &Review{ID: 1}).(*Review)
assert.NoError(t, review.LoadAttributes())
assert.NotNil(t, review.Issue)
assert.NotNil(t, review.Reviewer)
invalidReview1 := AssertExistsAndLoadBean(t, &Review{ID: 2}).(*Review)
assert.Error(t, invalidReview1.LoadAttributes())
invalidReview2 := AssertExistsAndLoadBean(t, &Review{ID: 3}).(*Review)
assert.Error(t, invalidReview2.LoadAttributes())
}
func TestReview_LoadCodeComments(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
review := AssertExistsAndLoadBean(t, &Review{ID: 4}).(*Review)
assert.NoError(t, review.LoadAttributes())
assert.NoError(t, review.LoadCodeComments())
assert.Len(t, review.CodeComments, 1)
assert.Equal(t, int64(4), review.CodeComments["README.md"][int64(4)][0].Line)
}
func TestReviewType_Icon(t *testing.T) {
assert.Equal(t, "eye", ReviewTypeApprove.Icon())
assert.Equal(t, "x", ReviewTypeReject.Icon())
assert.Equal(t, "comment", ReviewTypeComment.Icon())
assert.Equal(t, "comment", ReviewTypeUnknown.Icon())
assert.Equal(t, "comment", ReviewType(4).Icon())
}
func TestFindReviews(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
reviews, err := FindReviews(FindReviewOptions{
Type: ReviewTypeApprove,
IssueID: 2,
ReviewerID: 1,
})
assert.NoError(t, err)
assert.Len(t, reviews, 1)
assert.Equal(t, "Demo Review", reviews[0].Content)
}
func TestGetCurrentReview(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
review, err := GetCurrentReview(user, issue)
assert.NoError(t, err)
assert.NotNil(t, review)
assert.Equal(t, ReviewTypePending, review.Type)
assert.Equal(t, "Pending Review", review.Content)
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
review2, err := GetCurrentReview(user2, issue)
assert.Error(t, err)
assert.True(t, IsErrReviewNotExist(err))
assert.Nil(t, review2)
}
func TestCreateReview(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue)
user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User)
review, err := CreateReview(CreateReviewOptions{
Content: "New Review",
Type: ReviewTypePending,
Issue: issue,
Reviewer: user,
})
assert.NoError(t, err)
assert.Equal(t, "New Review", review.Content)
AssertExistsAndLoadBean(t, &Review{Content: "New Review"})
}
func TestUpdateReview(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
review := AssertExistsAndLoadBean(t, &Review{ID: 1}).(*Review)
review.Content = "Updated Review"
assert.NoError(t, UpdateReview(review))
AssertExistsAndLoadBean(t, &Review{ID: 1, Content: "Updated Review"})
}

View File

@ -18,14 +18,14 @@ import (
"sync" "sync"
"time" "time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"golang.org/x/crypto/ssh"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
"golang.org/x/crypto/ssh"
) )
const ( const (
@ -732,7 +732,7 @@ func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey
key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint, accessMode) key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint, accessMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("addDeployKey: %v", err) return nil, err
} }
return key, sess.Commit() return key, sess.Commit()

View File

@ -15,7 +15,7 @@ func TestAddTopic(t *testing.T) {
topics, err := FindTopics(&FindTopicOptions{}) topics, err := FindTopics(&FindTopicOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3, len(topics)) assert.EqualValues(t, 4, len(topics))
topics, err = FindTopics(&FindTopicOptions{ topics, err = FindTopics(&FindTopicOptions{
Limit: 2, Limit: 2,
@ -32,7 +32,7 @@ func TestAddTopic(t *testing.T) {
assert.NoError(t, SaveTopics(2, "golang")) assert.NoError(t, SaveTopics(2, "golang"))
topics, err = FindTopics(&FindTopicOptions{}) topics, err = FindTopics(&FindTopicOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 3, len(topics)) assert.EqualValues(t, 4, len(topics))
topics, err = FindTopics(&FindTopicOptions{ topics, err = FindTopics(&FindTopicOptions{
RepoID: 2, RepoID: 2,
@ -47,7 +47,7 @@ func TestAddTopic(t *testing.T) {
topics, err = FindTopics(&FindTopicOptions{}) topics, err = FindTopics(&FindTopicOptions{})
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 4, len(topics)) assert.EqualValues(t, 5, len(topics))
topics, err = FindTopics(&FindTopicOptions{ topics, err = FindTopics(&FindTopicOptions{
RepoID: 2, RepoID: 2,

View File

@ -9,12 +9,15 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt"
"io" "io"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -26,20 +29,27 @@ type TwoFactor struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"UNIQUE"` UID int64 `xorm:"UNIQUE"`
Secret string Secret string
ScratchToken string ScratchSalt string
ScratchHash string
LastUsedPasscode string `xorm:"VARCHAR(10)"` LastUsedPasscode string `xorm:"VARCHAR(10)"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"` CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
} }
// GenerateScratchToken recreates the scratch token the user is using. // GenerateScratchToken recreates the scratch token the user is using.
func (t *TwoFactor) GenerateScratchToken() error { func (t *TwoFactor) GenerateScratchToken() (string, error) {
token, err := generate.GetRandomString(8) token, err := generate.GetRandomString(8)
if err != nil { if err != nil {
return err return "", err
} }
t.ScratchToken = token t.ScratchSalt, _ = generate.GetRandomString(10)
return nil t.ScratchHash = hashToken(token, t.ScratchSalt)
return token, nil
}
func hashToken(token, salt string) string {
tempHash := pbkdf2.Key([]byte(token), []byte(salt), 10000, 50, sha256.New)
return fmt.Sprintf("%x", tempHash)
} }
// VerifyScratchToken verifies if the specified scratch token is valid. // VerifyScratchToken verifies if the specified scratch token is valid.
@ -47,7 +57,8 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool {
if len(token) == 0 { if len(token) == 0 {
return false return false
} }
return subtle.ConstantTimeCompare([]byte(token), []byte(t.ScratchToken)) == 1 tempHash := hashToken(token, t.ScratchSalt)
return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
} }
func (t *TwoFactor) getEncryptionKey() []byte { func (t *TwoFactor) getEncryptionKey() []byte {
@ -118,7 +129,7 @@ func aesDecrypt(key, text []byte) ([]byte, error) {
// NewTwoFactor creates a new two-factor authentication token. // NewTwoFactor creates a new two-factor authentication token.
func NewTwoFactor(t *TwoFactor) error { func NewTwoFactor(t *TwoFactor) error {
err := t.GenerateScratchToken() _, err := t.GenerateScratchToken()
if err != nil { if err != nil {
return err return err
} }

View File

@ -7,6 +7,7 @@ package models
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -23,7 +24,7 @@ import (
) )
// NonexistentID an ID that will never exist // NonexistentID an ID that will never exist
const NonexistentID = 9223372036854775807 const NonexistentID = int64(math.MaxInt64)
// giteaRoot a path to the gitea root // giteaRoot a path to the gitea root
var giteaRoot string var giteaRoot string

View File

@ -23,6 +23,7 @@ const (
EnvRepoUsername = "GITEA_REPO_USER_NAME" EnvRepoUsername = "GITEA_REPO_USER_NAME"
EnvRepoIsWiki = "GITEA_REPO_IS_WIKI" EnvRepoIsWiki = "GITEA_REPO_IS_WIKI"
EnvPusherName = "GITEA_PUSHER_NAME" EnvPusherName = "GITEA_PUSHER_NAME"
EnvPusherEmail = "GITEA_PUSHER_EMAIL"
EnvPusherID = "GITEA_PUSHER_ID" EnvPusherID = "GITEA_PUSHER_ID"
) )

View File

@ -83,6 +83,11 @@ type User struct {
Email string `xorm:"NOT NULL"` Email string `xorm:"NOT NULL"`
KeepEmailPrivate bool KeepEmailPrivate bool
Passwd string `xorm:"NOT NULL"` Passwd string `xorm:"NOT NULL"`
// MustChangePassword is an attribute that determines if a user
// is to change his/her password after registration.
MustChangePassword bool `xorm:"NOT NULL DEFAULT false"`
LoginType LoginType LoginType LoginType
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
LoginName string LoginName string
@ -683,7 +688,35 @@ func NewGhostUser() *User {
} }
var ( var (
reservedUsernames = []string{"assets", "css", "explore", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatars", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "error", "new", ".", ".."} reservedUsernames = []string{
"admin",
"api",
"assets",
"avatars",
"commits",
"css",
"debug",
"error",
"explore",
"help",
"img",
"install",
"issues",
"js",
"less",
"new",
"org",
"plugins",
"pulls",
"raw",
"repo",
"stars",
"template",
"user",
"vendor",
".",
"..",
}
reservedUserPatterns = []string{"*.keys"} reservedUserPatterns = []string{"*.keys"}
) )
@ -770,8 +803,6 @@ func CreateUser(u *User) (err error) {
if _, err = sess.Insert(u); err != nil { if _, err = sess.Insert(u); err != nil {
return err return err
} else if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil {
return err
} }
return sess.Commit() return sess.Commit()
@ -870,7 +901,12 @@ func ChangeUserName(u *User, newUserName string) (err error) {
return fmt.Errorf("Delete repository wiki local copy: %v", err) return fmt.Errorf("Delete repository wiki local copy: %v", err)
} }
return os.Rename(UserPath(u.Name), UserPath(newUserName)) // Do not fail if directory does not exist
if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("Rename user directory: %v", err)
}
return nil
} }
// checkDupEmail checks whether there are the same email with the user // checkDupEmail checks whether there are the same email with the user

View File

@ -77,13 +77,13 @@ func TestSearchUsers(t *testing.T) {
} }
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1}, testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse}, testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
[]int64{9}) []int64{9})
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue}, testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue}, testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})

View File

@ -18,6 +18,7 @@ type AdminCreateUserForm struct {
Email string `binding:"Required;Email;MaxSize(254)"` Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"MaxSize(255)"` Password string `binding:"MaxSize(255)"`
SendNotify bool SendNotify bool
MustChangePassword bool
} }
// Validate validates form fields // Validate validates form fields

View File

@ -10,6 +10,8 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/routers/utils"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-macaron/binding" "github.com/go-macaron/binding"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
@ -113,6 +115,7 @@ type RepoSettingForm struct {
PullsAllowSquash bool PullsAllowSquash bool
EnableTimetracker bool EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
// Admin settings // Admin settings
EnableHealthCheck bool EnableHealthCheck bool
@ -224,6 +227,11 @@ func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) b
return validate(errs, ctx.Data, f, ctx.Locale) return validate(errs, ctx.Data, f, ctx.Locale)
} }
// HasInvalidChannel validates the channel name is in the right format
func (f NewSlackHookForm) HasInvalidChannel() bool {
return !utils.IsValidSlackChannel(f.Channel)
}
// NewDiscordHookForm form for creating discord hook // NewDiscordHookForm form for creating discord hook
type NewDiscordHookForm struct { type NewDiscordHookForm struct {
PayloadURL string `binding:"Required;ValidUrl"` PayloadURL string `binding:"Required;ValidUrl"`
@ -362,6 +370,53 @@ func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Error
return validate(errs, ctx.Data, f, ctx.Locale) return validate(errs, ctx.Data, f, ctx.Locale)
} }
// CodeCommentForm form for adding code comments for PRs
type CodeCommentForm struct {
Content string `binding:"Required"`
Side string `binding:"Required;In(previous,proposed)"`
Line int64
TreePath string `form:"path" binding:"Required"`
IsReview bool `form:"is_review"`
}
// Validate validates the fields
func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// SubmitReviewForm for submitting a finished code review
type SubmitReviewForm struct {
Content string
Type string `binding:"Required;In(approve,comment,reject)"`
}
// Validate validates the fields
func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// ReviewType will return the corresponding reviewtype for type
func (f SubmitReviewForm) ReviewType() models.ReviewType {
switch f.Type {
case "approve":
return models.ReviewTypeApprove
case "comment":
return models.ReviewTypeComment
case "reject":
return models.ReviewTypeReject
default:
return models.ReviewTypeUnknown
}
}
// HasEmptyContent checks if the content of the review form is empty.
func (f SubmitReviewForm) HasEmptyContent() bool {
reviewType := f.ReviewType()
return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) &&
len(strings.TrimSpace(f.Content)) == 0
}
// __________ .__ // __________ .__
// \______ \ ____ | | ____ _____ ______ ____ // \______ \ ____ | | ____ _____ ______ ____
// | _// __ \| | _/ __ \\__ \ / ___// __ \ // | _// __ \| | _/ __ \\__ \ / ___// __ \

View File

@ -0,0 +1,41 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSubmitReviewForm_IsEmpty(t *testing.T) {
cases := []struct {
form SubmitReviewForm
expected bool
}{
// Approved PR with a comment shouldn't count as empty
{SubmitReviewForm{Type: "approve", Content: "Awesome"}, false},
// Approved PR without a comment shouldn't count as empty
{SubmitReviewForm{Type: "approve", Content: ""}, false},
// Rejected PR without a comment should count as empty
{SubmitReviewForm{Type: "reject", Content: ""}, true},
// Rejected PR with a comment shouldn't count as empty
{SubmitReviewForm{Type: "reject", Content: "Awesome"}, false},
// Comment review on a PR with a comment shouldn't count as empty
{SubmitReviewForm{Type: "comment", Content: "Awesome"}, false},
// Comment review on a PR without a comment should count as empty
{SubmitReviewForm{Type: "comment", Content: ""}, true},
}
for _, v := range cases {
assert.Equal(t, v.expected, v.form.HasEmptyContent())
}
}

View File

@ -74,9 +74,9 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
type RegisterForm struct { type RegisterForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"` UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"` Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"MaxSize(255)"` Password string `binding:"Required;MaxSize(255)"`
Retype string Retype string
Remember bool GRecaptchaResponse string `form:"g-recaptcha-response"`
} }
// Validate valideates the fields // Validate valideates the fields
@ -84,10 +84,21 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
return validate(errs, ctx.Data, f, ctx.Locale) return validate(errs, ctx.Data, f, ctx.Locale)
} }
// MustChangePasswordForm form for updating your password after account creation
// by an admin
type MustChangePasswordForm struct {
Password string `binding:"Required;MaxSize(255)"`
Retype string
}
// Validate valideates the fields
func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// SignInForm form for signing in with user/password // SignInForm form for signing in with user/password
type SignInForm struct { type SignInForm struct {
UserName string `binding:"Required;MaxSize(254)"` UserName string `binding:"Required;MaxSize(254)"`
// TODO remove required from password for SecondFactorAuthentication
Password string `binding:"Required;MaxSize(255)"` Password string `binding:"Required;MaxSize(255)"`
Remember bool Remember bool
} }

View File

@ -24,6 +24,7 @@ func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) b
type SignUpOpenIDForm struct { type SignUpOpenIDForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"` UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"` Email string `binding:"Required;Email;MaxSize(254)"`
GRecaptchaResponse string `form:"g-recaptcha-response"`
} }
// Validate valideates the fields // Validate valideates the fields

View File

@ -31,12 +31,33 @@ func Toggle(options *ToggleOptions) macaron.Handler {
} }
// Check prohibit login users. // Check prohibit login users.
if ctx.IsSigned && ctx.User.ProhibitLogin { if ctx.IsSigned {
if ctx.User.ProhibitLogin {
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(200, "user/auth/prohibit_login") ctx.HTML(200, "user/auth/prohibit_login")
return return
} }
// prevent infinite redirection
// also make sure that the form cannot be accessed by
// users who don't need this
if ctx.Req.URL.Path == setting.AppSubURL+"/user/settings/change_password" {
if !ctx.User.MustChangePassword {
ctx.Redirect(setting.AppSubURL + "/")
}
return
}
if ctx.User.MustChangePassword {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+ctx.Req.RequestURI), 0, setting.AppSubURL)
ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
return
}
}
// Redirect to dashboard if user tries to visit any non-login page. // Redirect to dashboard if user tries to visit any non-login page.
if options.SignOutRequired && ctx.IsSigned && ctx.Req.RequestURI != "/" { if options.SignOutRequired && ctx.IsSigned && ctx.Req.RequestURI != "/" {
ctx.Redirect(setting.AppSubURL + "/") ctx.Redirect(setting.AppSubURL + "/")

View File

@ -261,10 +261,15 @@ func Contexter() macaron.Handler {
log.Debug("Session ID: %s", sess.ID()) log.Debug("Session ID: %s", sess.ID())
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"]) log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
ctx.Data["EnableSwaggerEndpoint"] = setting.API.EnableSwaggerEndpoint
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
c.Map(ctx) c.Map(ctx)

View File

@ -104,6 +104,11 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
r.IsWriter() || issue.IsPoster(user.ID) || isAssigned) r.IsWriter() || issue.IsPoster(user.ID) || isAssigned)
} }
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
return r.Repository.IsDependenciesEnabled() && r.IsWriter()
}
// GetCommitsCount returns cached commit count for current view // GetCommitsCount returns cached commit count for current view
func (r *Repository) GetCommitsCount() (int64, error) { func (r *Repository) GetCommitsCount() (int64, error) {
var contextName string var contextName string

58
modules/markup/csv/csv.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package markup
import (
"bytes"
"encoding/csv"
"html"
"io"
"code.gitea.io/gitea/modules/markup"
)
func init() {
markup.RegisterParser(Parser{})
}
// Parser implements markup.Parser for orgmode
type Parser struct {
}
// Name implements markup.Parser
func (Parser) Name() string {
return "csv"
}
// Extensions implements markup.Parser
func (Parser) Extensions() []string {
return []string{".csv"}
}
// Render implements markup.Parser
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
rd := csv.NewReader(bytes.NewReader(rawBytes))
var tmpBlock bytes.Buffer
tmpBlock.WriteString(`<table class="table">`)
for {
fields, err := rd.Read()
if err == io.EOF {
break
}
if err != nil {
continue
}
tmpBlock.WriteString("<tr>")
for _, field := range fields {
tmpBlock.WriteString("<td>")
tmpBlock.WriteString(html.EscapeString(field))
tmpBlock.WriteString("</td>")
}
tmpBlock.WriteString("<tr>")
}
tmpBlock.WriteString("</table>")
return tmpBlock.Bytes()
}

View File

@ -0,0 +1,25 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package markup
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderCSV(t *testing.T) {
var parser Parser
var kases = map[string]string{
"a": "<table class=\"table\"><tr><td>a</td><tr></table>",
"1,2": "<table class=\"table\"><tr><td>1</td><td>2</td><tr></table>",
"<br/>": "<table class=\"table\"><tr><td>&lt;br/&gt;</td><tr></table>",
}
for k, v := range kases {
res := parser.Render([]byte(k), "", nil, false)
assert.EqualValues(t, v, string(res))
}
}

42
modules/pprof/pprof.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package pprof
import (
"fmt"
"io/ioutil"
"runtime"
"runtime/pprof"
"code.gitea.io/gitea/modules/log"
)
// DumpMemProfileForUsername dumps a memory profile at pprofDataPath as memprofile_<username>_<temporary id>
func DumpMemProfileForUsername(pprofDataPath, username string) {
f, err := ioutil.TempFile(pprofDataPath, fmt.Sprintf("memprofile_%s_", username))
if err != nil {
log.GitLogger.Fatal(4, "Could not create memory profile: %v", err)
}
defer f.Close()
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.GitLogger.Fatal(4, "Could not write memory profile: %v", err)
}
}
// DumpCPUProfileForUsername dumps a CPU profile at pprofDataPath as cpuprofile_<username>_<temporary id>
// it returns the stop function which stops, writes and closes the CPU profile file
func DumpCPUProfileForUsername(pprofDataPath, username string) func() {
f, err := ioutil.TempFile(pprofDataPath, fmt.Sprintf("cpuprofile_%s_", username))
if err != nil {
log.GitLogger.Fatal(4, "Could not create cpu profile: %v", err)
}
pprof.StartCPUProfile(f)
return func() {
pprof.StopCPUProfile()
f.Close()
}
}

View File

@ -0,0 +1,47 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package recaptcha
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"code.gitea.io/gitea/modules/setting"
)
// Response is the structure of JSON returned from API
type Response struct {
Success bool `json:"success"`
ChallengeTS time.Time `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes []string `json:"error-codes"`
}
const apiURL = "https://www.google.com/recaptcha/api/siteverify"
// Verify calls Google Recaptcha API to verify token
func Verify(response string) (bool, error) {
resp, err := http.PostForm(apiURL,
url.Values{"secret": {setting.Service.RecaptchaSecret}, "response": {response}})
if err != nil {
return false, fmt.Errorf("Failed to send CAPTCHA response: %s", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("Failed to read CAPTCHA response: %s", err)
}
var jsonResponse Response
err = json.Unmarshal(body, &jsonResponse)
if err != nil {
return false, fmt.Errorf("Failed to parse CAPTCHA response: %s", err)
}
return jsonResponse.Success, nil
}

View File

@ -7,4 +7,5 @@ import (
var ( var (
defaultLangs = strings.Split("en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR", ",") defaultLangs = strings.Split("en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR", ",")
defaultLangNames = strings.Split("English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어", ",") defaultLangNames = strings.Split("English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어", ",")
defaultPullRequestWorkInProgressPrefixes = strings.Split("WIP:,[WIP]", ",")
) )

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