Compare commits

..

60 Commits

Author SHA1 Message Date
AJ ONeal 76fbfde40c Merge branch 'v1.5.1-coolaj86' of ssh://git.coolaj86.com:22042/coolaj86/gitroast into gitroast 2018-10-09 00:44:49 -06:00
AJ ONeal 20717e6139 add github signin to navbar 2018-10-09 06:33:19 +00:00
AJ ONeal fa737a887b Add Google Analytics config option 2018-10-09 06:24:29 +00:00
AJ ONeal e09a6248f1 Merge branch 'v1.5.1-coolaj86' into gitroast 2018-10-08 22:58:43 -06:00
AJ ONeal 1ff523881d use 'info' since password may or may not be allowed 2018-10-09 04:55:09 +00:00
AJ ONeal 0ae89454e1 Merge branch 'release/v1.5' into gitroast 2018-10-08 22:40:19 -06:00
AJ ONeal ce1739bd16 merge go fmt + bugfix allow current user password reset 2018-10-07 05:46:15 +00:00
AJ ONeal 4c7727c3ed allow current user to reset their own password 2018-10-07 05:36:05 +00:00
AJ ONeal 8718156545 treat register like sign in, even oauth 2018-10-07 05:33:29 +00:00
AJ ONeal eda51c2079 treat register like sign in, even oauth 2018-10-07 03:28:11 +00:00
SagePtr 812c225223 Remove links from topics in edit mode (#5030) 2018-10-06 20:11:13 -04:00
AJ ONeal e64ff60c13 do not require password for external registration 2018-10-06 21:21:37 +00:00
AJ ONeal ee0fa3e58f handle specific error; fix missing err typo 2018-10-06 20:05:16 +00:00
AJ ONeal 5a42829dbd changes as per discussion 2018-10-06 16:37:27 +00:00
AJ ONeal 996c9cbab3 add active to tab body as well 2018-10-05 04:56:44 +00:00
AJ ONeal 95df7779ef use tabs to switch between account link flows 2018-10-05 04:45:24 +00:00
AJ ONeal 20eec9abd3 disambiguate fresh start from adding recovery options 2018-10-03 06:52:40 +00:00
AJ ONeal fa7fc43a13 Show either sign up OR sign in 2018-10-03 05:07:49 +00:00
Lauris BH 33c3cbc968 Detect charset and convert non UTF-8 files for display (#4950) (#4994)
* 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-30 09:02:16 +08:00
Iwasa Kazmi 8f29f61a6b Backport: Fix layout of the topics editing form (#4971) (#4993) 2018-09-29 13:06:11 +03:00
SagePtr 93dcc6caef Fix null pointer dereference in ParseCommitWithSignature (#4964) 2018-09-20 22:09:01 +03:00
crito 4176e33148 fix url in discord webhook (#4951)
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-18 08:27:02 +03:00
Toni Villena 177b46fe77 Backport: fix crippled diff (#4726) (#4929)
* fix: Crippled diff (#4726)

* ci

* fix: rebuild styles with v1.5's node_modules
2018-09-14 13:06:38 +02:00
linweijie2012 1e51307466 fix bug forget to remove Stopwatch when remove repository (#4933)
fix bug forget to remove Stopwatch when remove repository
2018-09-14 16:09:25 +08:00
SagePtr c145cb745b Fix bug when repo remained bare if multiple branches pushed (#4927) 2018-09-13 13:36:41 +03:00
techknowlogick 1a68b3962f
backport "Enforce token on api routes (#4840)" (#4905) 2018-09-11 10:39:32 -04:00
SagePtr d918e63bc5 Fix redirect with non-ascii branch names (#4764) (#4887) 2018-09-07 16:32:46 -04:00
Lunny Xiao 1901f35980 issues api allow pulls and fix #4832 (#4852) (#4862) 2018-09-04 22:13:56 -04:00
Nicolas Lenz 745c898561 Fix trimming of markup section names (#4864)
Signed-off-by: Nicolas Lenz <nicolas@eisfunke.com>
2018-09-03 21:43:16 -04:00
techknowlogick 38d8b8cf49 1.5.1 Changelog (#4851)
As title
2018-09-03 08:53:34 +08:00
SagePtr 0358a40625 Fix missing release title in webhook (#4783) (#4800) 2018-08-26 15:07:44 -04:00
techknowlogick 99ce0bfcd7
Don't disclose emails of all users when sending out emails (#4784)
Backport (#4664)
2018-08-24 14:37:30 -04:00
Lanre Adelowo 3fbcdd9e25 Make sure to reset commit count in the cache on mirror syncing (#4720) (#4770)
* Make sure to reset commit count in the cache on mirror syncing

* reset count of commits in all branches
2018-08-23 22:23:21 +08:00
Lanre Adelowo e9def84bf2 Fixed bug where team with admin privelege type doesn't get any unit attached to the team (#4719) (#4759) 2018-08-21 15:29:25 -04:00
Lauris BH 066515429f Improve URL validation for external wiki and external issues (#4710) (#4740)
* Improve URL validation for external wiki  and external issues

* Do not allow also localhost address for external URLs
2018-08-17 20:21:20 -04:00
SagePtr 12c04a85f2 Fix failure on creating pull request with assignees (#4419) (#4727) 2018-08-16 13:46:06 -04:00
SagePtr a345023d0a Fix incorrect caption of webhook setting (#4701) (#4718) 2018-08-15 19:06:56 +03:00
SagePtr 052aa54b2b Make cookies HttpOnly and obey COOKIE_SECURE flag (#4707) 2018-08-14 16:19:20 -04:00
SagePtr cbe8a1f0e6 Hide org/create menu item in Dashboard if user has no rights (#4678) (#4686) 2018-08-13 14:22:15 +03:00
techknowlogick cfe6941905
1.5.0 changelog 2018-08-10 13:16:53 -04:00
Lunny Xiao eb8c611b1d Site admin could create repos even MAX_CREATION_LIMIT=0 (#4645) (#4650)
* site admin could create repos even MAX_CREATION_LIMIT=0

* Optimize if structure
2018-08-09 06:31:57 +03:00
techknowlogick b1eaeeb0cd
Backport Remove link to GitHub issues in 404 template #4639 (#4644) 2018-08-08 16:20:05 -04:00
SagePtr 15a403bf97 Push whitelist now doesn't apply to branch deletion (#4601) (#4640) 2018-08-08 11:19:13 +03:00
Lunny Xiao 099028681e fix bugs when too many IN variables (#4594) (#4597) 2018-08-02 14:48:39 -04:00
Dingjun 940e30bcd4 fix panic issue on update avatar email (#4580) (#4590)
fix #4580

back port PR for release/v1.5,  refer to #4581
2018-08-01 21:34:57 +08:00
SagePtr 5a7830e0e8 Fix incorrect MergeWhitelistTeamIDs check in CanUserMerge function (#4519) (#4526) 2018-07-27 22:11:53 +03:00
SagePtr dae065ea68 Fix out-of-transaction query in removeOrgUser (#4521) (#4524) 2018-07-27 08:57:49 -04:00
Lauris BH 40bbc7320c
Add 1.5.0-rc2 changelog (#4488) 2018-07-21 20:06:06 +03:00
Lauris BH 5da301bb70 Prevent html entity escaping (#4471) (#4485) 2018-07-20 18:39:40 -04:00
Lauris BH 3e191935c8 Fix column droping for MSSQL that need new transaction for that (#4440) (#4484) 2018-07-20 15:27:30 -04:00
Lauris BH 8a639ade58
Update xorm to latest version and fix correct `user` table referencing in sql (#4473) (#4483) 2018-07-20 21:48:15 +03:00
Lunny Xiao 88d791013b add valid for lfs oid (#4461) (#4477) 2018-07-20 19:36:56 +03:00
techknowlogick b37ca4a6ff
backport: Redirect to correct page after using scratch token (#4472) 2018-07-19 17:15:12 -04:00
Lauris BH 678834883e Fix drone git@next plugin Gitea version display when building tag (#4380) (#4430) 2018-07-12 11:40:50 -04:00
Lauris BH 1965eaf96e Disable swagger validation while it is not fixed in upstream (#4423) (#4431) 2018-07-12 19:49:40 +08:00
Jonas Franz c784ac53ba
Replace src with raw to fix image paths (#4386)
Signed-off-by: Jonas Franz <info@jonasfranz.software>
2018-07-07 00:44:16 +02:00
Nicolas Da Mutten 85f3966338 Fixes repo membership check in API (#4341) (#4379)
Untested, since I can't compile (yet).
2018-07-05 23:34:31 +03:00
Lauris BH f096e69e0a Add default merge options when adding new repository (#4369) (#4373) 2018-07-05 16:12:09 +02:00
Lunny Xiao 768b41adba fix repository last updated time update when delete a user who watched the repo (#4363) (#4371) 2018-07-05 08:46:13 +02:00
Lauris BH 155caa8e0a Check that repositories can only be migrated to own user or organizations (#4366) (#4370)
* 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:18:06 +02:00
757 changed files with 170577 additions and 25490 deletions

View File

@ -22,7 +22,7 @@ pipeline:
branch: [ master ] branch: [ master ]
update-translations: update-translations:
image: alpine:3.7 image: alpine:3.6
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.9 image: golang:1.8
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.11 image: golang:1.10
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.11 image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
@ -95,7 +95,7 @@ pipeline:
branch: [ master ] branch: [ master ]
test: test:
image: golang:1.11 image: golang:1.10
pull: true pull: true
group: test group: test
environment: environment:
@ -107,7 +107,7 @@ pipeline:
branch: [ release/* ] branch: [ release/* ]
test: test:
image: golang:1.11 image: golang:1.10
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.11 image: golang:1.10
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.11 image: golang:1.10
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.11 image: golang:1.10
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.11 image: golang:1.10
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,12 +212,26 @@ pipeline:
- make clean - make clean
- make build - make build
publish-docs: docker_docs:
image: lucap/drone-netlify:latest image: plugins/docker:17.05
pull: true pull: true
secrets: [ netlify_token ] secrets: [ docker_username, docker_password ]
site_id: d2260bae-7861-4c02-8646-8f6440b12672 repo: gitea/docs
path: docs/public/ context: docs
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 ]

View File

@ -1,51 +0,0 @@
# 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,4 +58,3 @@ 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 = "12dd70caea0268ac0d6c2707d0611ef601e7c64e" revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
[[constraint]] [[constraint]]
@ -30,15 +30,16 @@ 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,10 +42,6 @@ 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
@ -61,9 +57,6 @@ else
EXECUTABLE := gitea EXECUTABLE := gitea
endif endif
# $(call strip-suffix,filename)
strip-suffix = $(firstword $(subst ., ,$(1)))
.PHONY: all .PHONY: all
all: build all: build
@ -98,12 +91,11 @@ 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 './$(SWAGGER_SPEC)' swagger generate spec -o ./public/swagger.v1.json
$(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 '$(SWAGGER_SPEC)'); \ @diff=$$(git diff public/swagger.v1.json); \
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}"; \
@ -115,9 +107,7 @@ 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
$(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)' swagger validate ./public/swagger.v1.json
swagger validate './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
.PHONY: errcheck .PHONY: errcheck
errcheck: errcheck:
@ -313,7 +303,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/*); \ @diff=$$(git diff public/css/index.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}"; \
@ -323,7 +313,6 @@ 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:
TAGS="bindata" make generate all 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,10 +90,6 @@ 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,6 +11,13 @@
[![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。
@ -40,12 +47,3 @@ 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,12 +7,9 @@ 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"
@ -29,7 +26,6 @@ var (
subcmdChangePassword, subcmdChangePassword,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate, subcmdRegenerate,
subcmdAuth,
}, },
} }
@ -125,121 +121,6 @@ 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 {
@ -381,170 +262,3 @@ 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,7 +16,6 @@ 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"
@ -43,9 +42,6 @@ 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",
},
}, },
} }
@ -147,18 +143,6 @@ 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,7 +5,6 @@
package cmd package cmd
import ( import (
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -23,7 +22,6 @@ 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"
) )
@ -73,33 +71,6 @@ 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")
@ -172,10 +143,6 @@ 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,10 +60,6 @@ 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
@ -71,10 +67,6 @@ 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
@ -83,15 +75,13 @@ 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 one page ; Number of notices that are displayed on in 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
@ -191,12 +181,6 @@ 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.
@ -223,10 +207,9 @@ 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, either "disable" (default), "require", or "verify-full" ; For "postgres" only, either "disable", "require" or "verify-full"
; For MySQL, either "false" (default), "true", or "skip-verify"
SSL_MODE = disable SSL_MODE = disable
; For "sqlite3" and "tidb", use an absolute path when you start gitea as service ; For "sqlite3" and "tidb", use 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
@ -318,22 +301,13 @@ 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 = false ENABLE_CAPTCHA = true
; 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
@ -594,8 +568,8 @@ DEFAULT_INTERVAL = 8h
MIN_INTERVAL = 10m MIN_INTERVAL = 10m
[api] [api]
; Enables Swagger. True or false; default is true. ; Enables /api/swagger, /api/v1/swagger etc. endpoints. True or false; default is true.
ENABLE_SWAGGER = true ENABLE_SWAGGER_ENDPOINT = 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,3 +29,4 @@ 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,9 +4,6 @@ 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
@ -17,7 +14,6 @@ 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
@ -27,9 +23,6 @@ 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,3 +1,2 @@
public/ public/
templates/swagger/v1_json.tmpl
themes/ themes/

22
docs/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
# 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: 45 weight: 25
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: 45 weight: 25
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: 45 weight: 25
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: 45 weight: 25
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: 45 weight: 25
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: https://gitea.io/en-us/ url: /fr-fr/
weight: 10 weight: 10
pre: home pre: home
post: active post: active
- name: Documentation - name: Documentation
url: /fr-fr/ url: https://docs.gitea.io/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: 45 weight: 25
pre: plug pre: plug
- name: Blog - name: Blog
url: https://blog.gitea.io/ url: https://blog.gitea.io/

View File

@ -73,7 +73,3 @@ 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,17 +63,11 @@ 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`)
@ -125,11 +119,6 @@ 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`)
@ -138,7 +127,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 and MySQL only. - `SSL_MODE`: **disable**: For PostgreSQL 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.
@ -188,11 +177,7 @@ 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`: **false**: Enable this to use captcha validation for registration. - `ENABLE_CAPTCHA`: **true**: 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`)
@ -217,8 +202,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`)
@ -242,7 +227,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`)
@ -294,17 +279,10 @@ 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,13 +187,6 @@ 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 template syntax and Any statement contained inside `{{` and `}}` are Gitea's templete 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,7 +91,3 @@ 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,3 +66,4 @@ 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,9 +37,6 @@ _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 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
@ -56,7 +53,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) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
@ -77,7 +74,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 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
@ -87,7 +84,6 @@ _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
@ -96,8 +92,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,18 +21,10 @@ 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.5.0/gitea-1.5.0-linux-amd64 wget -O gitea https://dl.gitea.io/gitea/1.4.2/gitea-1.4.2-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
@ -90,20 +82,6 @@ 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
@ -121,25 +99,3 @@ 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,16 +64,3 @@ 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,8 +30,6 @@ 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"
@ -105,11 +103,6 @@ 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
@ -153,11 +146,6 @@ 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,15 +33,19 @@ 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:
FROM: ### 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.
```ini FROM:
```
[database] [database]
PATH = /home/:USER/gogs/data/:DATABASE.db PATH = /home/:USER/gogs/data/:DATABASE.db
[attachment] [attachment]
@ -53,8 +57,7 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
``` ```
TO: TO:
```
```ini
[database] [database]
PATH = /home/:USER/gitea/data/:DATABASE.db PATH = /home/:USER/gitea/data/:DATABASE.db
[attachment] [attachment]
@ -67,19 +70,13 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
* Verify by starting Gitea with `gitea web` * Verify by starting Gitea with `gitea web`
## Upgrading to most recent `gitea` version ### Troubleshooting
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,15 +29,14 @@ 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]
@ -49,8 +48,7 @@ Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécute
``` ```
VERS : VERS :
```
```ini
[database] [database]
PATH = /home/:USER/gitea/data/:DATABASE.db PATH = /home/:USER/gitea/data/:DATABASE.db
[attachment] [attachment]
@ -63,11 +61,11 @@ 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`.
## 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,50 +72,6 @@ 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,24 +32,6 @@ 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

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

44
docs/docker/caddy.conf Normal file
View File

@ -0,0 +1,44 @@
: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"
}

0
docs/static/.gitkeep vendored Normal file
View File

View File

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

View File

@ -1,8 +0,0 @@
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,8 +11,6 @@ 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) {
@ -49,8 +47,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)
} }
@ -77,32 +75,3 @@ 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,13 +5,10 @@
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"
@ -32,27 +29,3 @@ 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: 19}, nil: {count: 17},
user: {count: 19}, user: {count: 17},
user2: {count: 19}}, user2: {count: 17}},
}, },
{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)
token = getTokenForLoggedInUser(t, session)
userID = userToLogin.ID userID = userToLogin.ID
token = getTokenForLoggedInUser(t, session)
} else { } else {
testName = "AnonymousUser" testName = "AnonymousUser"
session = emptyTestSession(t) session = emptyTestSession(t)
@ -216,6 +216,7 @@ 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)
@ -265,26 +266,3 @@ 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

@ -1,50 +0,0 @@
// 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,11 +272,6 @@ 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,7 +14,6 @@ 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"
) )
@ -124,23 +123,3 @@ 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,7 +14,6 @@ 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,9 +47,6 @@ const (
ActionReopenPullRequest // 15 ActionReopenPullRequest // 15
ActionDeleteTag // 16 ActionDeleteTag // 16
ActionDeleteBranch // 17 ActionDeleteBranch // 17
ActionMirrorSyncPush // 18
ActionMirrorSyncCreate // 19
ActionMirrorSyncDelete // 20
) )
var ( var (
@ -125,12 +122,6 @@ 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()
@ -480,10 +471,6 @@ 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
} }
} }
@ -741,71 +728,6 @@ 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,110 +1259,3 @@ 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,35 +20,3 @@
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,11 +9,3 @@
- -
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,25 +407,3 @@
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

@ -1,32 +0,0 @@
-
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: 2 repo_count: 1
- -
id: 2 id: 2
@ -11,7 +11,3 @@
- id: 3 - id: 3
name: SQL name: SQL
repo_count: 1 repo_count: 1
- id: 4
name: graphql
repo_count: 1

View File

@ -314,18 +314,3 @@
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,8 +14,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
@ -59,7 +57,6 @@ 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.
@ -67,19 +64,6 @@ 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
@ -241,171 +225,11 @@ 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
@ -483,6 +307,7 @@ 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}
@ -637,13 +462,6 @@ 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
@ -655,21 +473,17 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID
} }
var cmd *exec.Cmd var cmd *exec.Cmd
if len(beforeCommitID) == 0 && commit.ParentCount() == 0 { // if "after" commit given
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 {
actualBeforeCommitID := beforeCommitID c, _ := commit.Parent(0)
if len(actualBeforeCommitID) == 0 { cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitID)
parentCommit, _ := commit.Parent(0)
actualBeforeCommitID = parentCommit.ID.String()
} }
diffArgs := []string{"diff", "-M"} } else {
if len(whitespaceBehavior) != 0 { cmd = exec.Command("git", "diff", "-M", beforeCommitID, afterCommitID)
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
@ -710,46 +524,32 @@ 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(endCommit) commit, err := repo.GetCommit(commitID)
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 len(startCommit) != 0 { if commit.ParentCount() == 0 {
cmd = exec.Command("git", append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...) cmd = exec.Command("git", "show", commitID)
} 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", append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...) cmd = exec.Command("git", "diff", "-M", c.ID.String(), commitID)
} }
case RawDiffPatch: case RawDiffPatch:
if len(startCommit) != 0 { if commit.ParentCount() == 0 {
query := fmt.Sprintf("%s...%s", endCommit, startCommit) cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", "--root", commitID)
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", endCommit, c.ID.String()) query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...) cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", query)
} }
default: default:
return fmt.Errorf("invalid diffType: %s", diffType) return fmt.Errorf("invalid diffType: %s", diffType)
@ -760,6 +560,7 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff
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,11 +2,9 @@ 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) {
@ -36,106 +34,3 @@ 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,7 +9,6 @@ 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
@ -42,7 +41,7 @@ func GetCommitGraph(r *git.Repository) (GraphItems, error) {
"--all", "--all",
"-C", "-C",
"-M", "-M",
fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum), "-n 100",
"--date=iso", "--date=iso",
fmt.Sprintf("--pretty=format:%s", format), fmt.Sprintf("--pretty=format:%s", format),
) )
@ -67,7 +66,7 @@ func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
var ascii string var ascii string
var data = "|||||||" var data = "|||||||"
lines := strings.SplitN(s, "DATA:", 2) lines := strings.Split(s, "DATA:")
switch len(lines) { switch len(lines) {
case 1: case 1:

View File

@ -5,7 +5,6 @@
package models package models
import ( import (
"fmt"
"testing" "testing"
"code.gitea.io/git" "code.gitea.io/git"
@ -44,32 +43,3 @@ 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,20 +649,6 @@ 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()
@ -1612,33 +1598,3 @@ 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,19 +1,13 @@
// Copyright 2018 The Gitea Authors. // Copyright 2016 The Gogs Authors. All rights reserved.
// 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"
@ -72,14 +66,6 @@ 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
@ -112,18 +98,12 @@ 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 // - previous line / + proposed line Line int64
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"`
@ -135,10 +115,6 @@ 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
@ -189,20 +165,6 @@ 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())
} }
@ -319,15 +281,6 @@ 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) {
@ -374,95 +327,11 @@ 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,
@ -479,10 +348,6 @@ 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
@ -492,14 +357,6 @@ 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{
@ -512,25 +369,14 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
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 err return nil, err
} }
// Check attachments // Check attachments
@ -541,7 +387,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
if IsErrAttachmentNotExist(err) { if IsErrAttachmentNotExist(err) {
continue continue
} }
return fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err) return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
} }
attachments = append(attachments, attach) attachments = append(attachments, attach)
} }
@ -551,7 +397,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
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 fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err) return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
} }
} }
@ -567,7 +413,7 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
_, 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 err return nil, err
} }
case CommentTypeClose: case CommentTypeClose:
@ -582,13 +428,15 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
_, 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 err return nil, 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 err return nil, 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 {
@ -598,7 +446,8 @@ func sendCreateCommentAction(e *xorm.Session, opts *CreateCommentOptions, commen
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) {
@ -700,39 +549,6 @@ 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
@ -741,7 +557,6 @@ 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
@ -750,10 +565,7 @@ 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
} }
@ -810,58 +622,6 @@ 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 {
@ -907,7 +667,6 @@ 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
} }
@ -920,9 +679,6 @@ 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})
} }
@ -1063,75 +819,3 @@ 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,21 +39,3 @@ 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)
}

View File

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

@ -1,57 +0,0 @@
// 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).Asc("deadline_unix").Find(&miles) return miles, x.Where("repo_id = ?", repoID).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,14 +192,6 @@ 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").Cols("id", "name", "owner_id").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil { if err := x.Table("repository").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 {

View File

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

View File

@ -1,88 +0,0 @@
// 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)
}

View File

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

View File

@ -1,19 +0,0 @@
// 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,13 +118,11 @@ 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"}
@ -155,7 +153,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").MustString("disable") DbCfg.SSLMode = sec.Key("SSL_MODE").String()
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)
@ -222,16 +220,13 @@ 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
connType = "unix" connStr = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8&parseTime=true",
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":

18
models/models_tidb.go Normal file
View File

@ -0,0 +1,18 @@
// +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,6 +182,10 @@ 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 && !pr.IsWorkInProgress() mergeable := pr.Status != PullRequestStatusConflict
apiPullRequest.Mergeable = mergeable apiPullRequest.Mergeable = mergeable
} }
if pr.HasMerged { if pr.HasMerged {
@ -1075,7 +1075,10 @@ func (prs PullRequestList) loadAttributes(e Engine) error {
} }
// Load issues. // Load issues.
issueIDs := prs.getIssueIDs() issueIDs := make([]int64, 0, len(prs))
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").
@ -1094,44 +1097,11 @@ 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)
@ -1158,13 +1128,10 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
} }
if isSync { if isSync {
requests := PullRequestList(prs) if err = PullRequestList(prs).LoadAttributes(); err != nil {
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
@ -1185,7 +1152,6 @@ 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)
@ -1201,24 +1167,6 @@ 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{
@ -1247,37 +1195,6 @@ 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,34 +237,3 @@ 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 noreferrer">%[1]s</a>`, s) return fmt.Sprintf(`<a href="%[1]s" target="_blank" rel="noopener">%[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,11 +1345,7 @@ 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{ Config: &IssuesConfig{EnableTimetracker: setting.Service.DefaultEnableTimetracking, AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime},
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,8 +131,6 @@ 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
@ -186,7 +184,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("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID), builder.Expr("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))
@ -204,14 +202,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
} }
if opts.Keyword != "" { if opts.Keyword != "" {
var keywordCond = builder.NewCond() cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
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 {
@ -233,15 +224,9 @@ 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)
} }
@ -251,23 +236,11 @@ 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: 19}, count: 17},
{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: 23}, count: 21},
{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: 19}, count: 17},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@ -222,28 +222,3 @@ 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,5 +1,4 @@
// 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.
@ -7,7 +6,6 @@ package models
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"code.gitea.io/git" "code.gitea.io/git"
@ -121,68 +119,8 @@ 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() ([]*mirrorSyncResult, bool) { func (m *Mirror) runSync() 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
@ -192,30 +130,28 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
gitArgs = append(gitArgs, "--prune") gitArgs = append(gitArgs, "--prune")
} }
_, stderr, err := process.GetManager().ExecDir( if _, stderr, err := process.GetManager().ExecDir(
timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath), timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath),
"git", gitArgs...) "git", gitArgs...); err != nil {
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 nil, false return 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 nil, false return 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 nil, false return 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)
@ -234,21 +170,21 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, 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 nil, false return 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 nil, false return 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 nil, false return false
} }
for i := range branches { for i := range branches {
@ -256,7 +192,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
} }
m.UpdatedUnix = util.TimeStampNow() m.UpdatedUnix = util.TimeStampNow()
return parseRemoteUpdateOutput(output), true return true
} }
func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) { func getMirrorByRepoID(e Engine, repoID int64) (*Mirror, error) {
@ -332,8 +268,7 @@ func SyncMirrors() {
continue continue
} }
results, ok := m.runSync() if !m.runSync() {
if !ok {
continue continue
} }
@ -343,66 +278,6 @@ 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,7 +73,6 @@ 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.
@ -166,6 +165,7 @@ 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)
} }

View File

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

View File

@ -1,107 +0,0 @@
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, err return nil, fmt.Errorf("addDeployKey: %v", 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, 4, len(topics)) assert.EqualValues(t, 3, 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, 4, len(topics)) assert.EqualValues(t, 3, 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, 5, len(topics)) assert.EqualValues(t, 4, len(topics))
topics, err = FindTopics(&FindTopicOptions{ topics, err = FindTopics(&FindTopicOptions{
RepoID: 2, RepoID: 2,

View File

@ -9,15 +9,12 @@ 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"
@ -29,27 +26,20 @@ 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
ScratchSalt string ScratchToken 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() (string, error) { func (t *TwoFactor) GenerateScratchToken() error {
token, err := generate.GetRandomString(8) token, err := generate.GetRandomString(8)
if err != nil { if err != nil {
return "", err return err
} }
t.ScratchSalt, _ = generate.GetRandomString(10) t.ScratchToken = token
t.ScratchHash = hashToken(token, t.ScratchSalt) return nil
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.
@ -57,8 +47,7 @@ func (t *TwoFactor) VerifyScratchToken(token string) bool {
if len(token) == 0 { if len(token) == 0 {
return false return false
} }
tempHash := hashToken(token, t.ScratchSalt) return subtle.ConstantTimeCompare([]byte(token), []byte(t.ScratchToken)) == 1
return subtle.ConstantTimeCompare([]byte(t.ScratchHash), []byte(tempHash)) == 1
} }
func (t *TwoFactor) getEncryptionKey() []byte { func (t *TwoFactor) getEncryptionKey() []byte {
@ -129,7 +118,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,7 +7,6 @@ package models
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -24,7 +23,7 @@ import (
) )
// NonexistentID an ID that will never exist // NonexistentID an ID that will never exist
const NonexistentID = int64(math.MaxInt64) const NonexistentID = 9223372036854775807
// giteaRoot a path to the gitea root // giteaRoot a path to the gitea root
var giteaRoot string var giteaRoot string

View File

@ -23,7 +23,6 @@ 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,11 +83,6 @@ 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
@ -688,35 +683,7 @@ func NewGhostUser() *User {
} }
var ( var (
reservedUsernames = []string{ 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", ".", ".."}
"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"}
) )
@ -803,6 +770,8 @@ 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()
@ -901,12 +870,7 @@ 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)
} }
// Do not fail if directory does not exist return os.Rename(UserPath(u.Name), UserPath(newUserName))
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, 21}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20})
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, 21}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20})
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,7 +18,6 @@ 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,8 +10,6 @@ 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"
@ -115,7 +113,6 @@ 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
@ -227,11 +224,6 @@ 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"`
@ -370,53 +362,6 @@ 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

@ -1,41 +0,0 @@
// 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:"Required;MaxSize(255)"` Password string `binding:"MaxSize(255)"`
Retype string Retype string
GRecaptchaResponse string `form:"g-recaptcha-response"` Remember bool
} }
// Validate valideates the fields // Validate valideates the fields
@ -84,21 +84,10 @@ 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,7 +24,6 @@ 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,33 +31,12 @@ func Toggle(options *ToggleOptions) macaron.Handler {
} }
// Check prohibit login users. // Check prohibit login users.
if ctx.IsSigned { if ctx.IsSigned && ctx.User.ProhibitLogin {
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,15 +261,10 @@ 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,11 +104,6 @@ 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

View File

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

@ -1,25 +0,0 @@
// 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))
}
}

View File

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

@ -1,47 +0,0 @@
// 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,5 +7,4 @@ 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