You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pull_review.go 25KB

Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
3 년 전
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
1 년 전
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "fmt"
  6. "net/http"
  7. "strings"
  8. issues_model "code.gitea.io/gitea/models/issues"
  9. "code.gitea.io/gitea/models/organization"
  10. access_model "code.gitea.io/gitea/models/perm/access"
  11. user_model "code.gitea.io/gitea/models/user"
  12. "code.gitea.io/gitea/modules/gitrepo"
  13. api "code.gitea.io/gitea/modules/structs"
  14. "code.gitea.io/gitea/modules/web"
  15. "code.gitea.io/gitea/routers/api/v1/utils"
  16. "code.gitea.io/gitea/services/context"
  17. "code.gitea.io/gitea/services/convert"
  18. issue_service "code.gitea.io/gitea/services/issue"
  19. pull_service "code.gitea.io/gitea/services/pull"
  20. )
  21. // ListPullReviews lists all reviews of a pull request
  22. func ListPullReviews(ctx *context.APIContext) {
  23. // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/reviews repository repoListPullReviews
  24. // ---
  25. // summary: List all reviews for a pull request
  26. // produces:
  27. // - application/json
  28. // parameters:
  29. // - name: owner
  30. // in: path
  31. // description: owner of the repo
  32. // type: string
  33. // required: true
  34. // - name: repo
  35. // in: path
  36. // description: name of the repo
  37. // type: string
  38. // required: true
  39. // - name: index
  40. // in: path
  41. // description: index of the pull request
  42. // type: integer
  43. // format: int64
  44. // required: true
  45. // - name: page
  46. // in: query
  47. // description: page number of results to return (1-based)
  48. // type: integer
  49. // - name: limit
  50. // in: query
  51. // description: page size of results
  52. // type: integer
  53. // responses:
  54. // "200":
  55. // "$ref": "#/responses/PullReviewList"
  56. // "404":
  57. // "$ref": "#/responses/notFound"
  58. pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  59. if err != nil {
  60. if issues_model.IsErrPullRequestNotExist(err) {
  61. ctx.NotFound("GetPullRequestByIndex", err)
  62. } else {
  63. ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  64. }
  65. return
  66. }
  67. if err = pr.LoadIssue(ctx); err != nil {
  68. ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  69. return
  70. }
  71. if err = pr.Issue.LoadRepo(ctx); err != nil {
  72. ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
  73. return
  74. }
  75. opts := issues_model.FindReviewOptions{
  76. ListOptions: utils.GetListOptions(ctx),
  77. Type: issues_model.ReviewTypeUnknown,
  78. IssueID: pr.IssueID,
  79. }
  80. allReviews, err := issues_model.FindReviews(ctx, opts)
  81. if err != nil {
  82. ctx.InternalServerError(err)
  83. return
  84. }
  85. count, err := issues_model.CountReviews(ctx, opts)
  86. if err != nil {
  87. ctx.InternalServerError(err)
  88. return
  89. }
  90. apiReviews, err := convert.ToPullReviewList(ctx, allReviews, ctx.Doer)
  91. if err != nil {
  92. ctx.Error(http.StatusInternalServerError, "convertToPullReviewList", err)
  93. return
  94. }
  95. ctx.SetTotalCountHeader(count)
  96. ctx.JSON(http.StatusOK, &apiReviews)
  97. }
  98. // GetPullReview gets a specific review of a pull request
  99. func GetPullReview(ctx *context.APIContext) {
  100. // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoGetPullReview
  101. // ---
  102. // summary: Get a specific review for a pull request
  103. // produces:
  104. // - application/json
  105. // parameters:
  106. // - name: owner
  107. // in: path
  108. // description: owner of the repo
  109. // type: string
  110. // required: true
  111. // - name: repo
  112. // in: path
  113. // description: name of the repo
  114. // type: string
  115. // required: true
  116. // - name: index
  117. // in: path
  118. // description: index of the pull request
  119. // type: integer
  120. // format: int64
  121. // required: true
  122. // - name: id
  123. // in: path
  124. // description: id of the review
  125. // type: integer
  126. // format: int64
  127. // required: true
  128. // responses:
  129. // "200":
  130. // "$ref": "#/responses/PullReview"
  131. // "404":
  132. // "$ref": "#/responses/notFound"
  133. review, _, statusSet := prepareSingleReview(ctx)
  134. if statusSet {
  135. return
  136. }
  137. apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
  138. if err != nil {
  139. ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
  140. return
  141. }
  142. ctx.JSON(http.StatusOK, apiReview)
  143. }
  144. // GetPullReviewComments lists all comments of a pull request review
  145. func GetPullReviewComments(ctx *context.APIContext) {
  146. // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments repository repoGetPullReviewComments
  147. // ---
  148. // summary: Get a specific review for a pull request
  149. // produces:
  150. // - application/json
  151. // parameters:
  152. // - name: owner
  153. // in: path
  154. // description: owner of the repo
  155. // type: string
  156. // required: true
  157. // - name: repo
  158. // in: path
  159. // description: name of the repo
  160. // type: string
  161. // required: true
  162. // - name: index
  163. // in: path
  164. // description: index of the pull request
  165. // type: integer
  166. // format: int64
  167. // required: true
  168. // - name: id
  169. // in: path
  170. // description: id of the review
  171. // type: integer
  172. // format: int64
  173. // required: true
  174. // responses:
  175. // "200":
  176. // "$ref": "#/responses/PullReviewCommentList"
  177. // "404":
  178. // "$ref": "#/responses/notFound"
  179. review, _, statusSet := prepareSingleReview(ctx)
  180. if statusSet {
  181. return
  182. }
  183. apiComments, err := convert.ToPullReviewCommentList(ctx, review, ctx.Doer)
  184. if err != nil {
  185. ctx.Error(http.StatusInternalServerError, "convertToPullReviewCommentList", err)
  186. return
  187. }
  188. ctx.JSON(http.StatusOK, apiComments)
  189. }
  190. // DeletePullReview delete a specific review from a pull request
  191. func DeletePullReview(ctx *context.APIContext) {
  192. // swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoDeletePullReview
  193. // ---
  194. // summary: Delete a specific review from a pull request
  195. // produces:
  196. // - application/json
  197. // parameters:
  198. // - name: owner
  199. // in: path
  200. // description: owner of the repo
  201. // type: string
  202. // required: true
  203. // - name: repo
  204. // in: path
  205. // description: name of the repo
  206. // type: string
  207. // required: true
  208. // - name: index
  209. // in: path
  210. // description: index of the pull request
  211. // type: integer
  212. // format: int64
  213. // required: true
  214. // - name: id
  215. // in: path
  216. // description: id of the review
  217. // type: integer
  218. // format: int64
  219. // required: true
  220. // responses:
  221. // "204":
  222. // "$ref": "#/responses/empty"
  223. // "403":
  224. // "$ref": "#/responses/forbidden"
  225. // "404":
  226. // "$ref": "#/responses/notFound"
  227. review, _, statusSet := prepareSingleReview(ctx)
  228. if statusSet {
  229. return
  230. }
  231. if ctx.Doer == nil {
  232. ctx.NotFound()
  233. return
  234. }
  235. if !ctx.Doer.IsAdmin && ctx.Doer.ID != review.ReviewerID {
  236. ctx.Error(http.StatusForbidden, "only admin and user itself can delete a review", nil)
  237. return
  238. }
  239. if err := issues_model.DeleteReview(ctx, review); err != nil {
  240. ctx.Error(http.StatusInternalServerError, "DeleteReview", fmt.Errorf("can not delete ReviewID: %d", review.ID))
  241. return
  242. }
  243. ctx.Status(http.StatusNoContent)
  244. }
  245. // CreatePullReview create a review to a pull request
  246. func CreatePullReview(ctx *context.APIContext) {
  247. // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews repository repoCreatePullReview
  248. // ---
  249. // summary: Create a review to an pull request
  250. // produces:
  251. // - application/json
  252. // parameters:
  253. // - name: owner
  254. // in: path
  255. // description: owner of the repo
  256. // type: string
  257. // required: true
  258. // - name: repo
  259. // in: path
  260. // description: name of the repo
  261. // type: string
  262. // required: true
  263. // - name: index
  264. // in: path
  265. // description: index of the pull request
  266. // type: integer
  267. // format: int64
  268. // required: true
  269. // - name: body
  270. // in: body
  271. // required: true
  272. // schema:
  273. // "$ref": "#/definitions/CreatePullReviewOptions"
  274. // responses:
  275. // "200":
  276. // "$ref": "#/responses/PullReview"
  277. // "404":
  278. // "$ref": "#/responses/notFound"
  279. // "422":
  280. // "$ref": "#/responses/validationError"
  281. opts := web.GetForm(ctx).(*api.CreatePullReviewOptions)
  282. pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  283. if err != nil {
  284. if issues_model.IsErrPullRequestNotExist(err) {
  285. ctx.NotFound("GetPullRequestByIndex", err)
  286. } else {
  287. ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  288. }
  289. return
  290. }
  291. // determine review type
  292. reviewType, isWrong := preparePullReviewType(ctx, pr, opts.Event, opts.Body, len(opts.Comments) > 0)
  293. if isWrong {
  294. return
  295. }
  296. if err := pr.Issue.LoadRepo(ctx); err != nil {
  297. ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
  298. return
  299. }
  300. // if CommitID is empty, set it as lastCommitID
  301. if opts.CommitID == "" {
  302. gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.Issue.Repo)
  303. if err != nil {
  304. ctx.Error(http.StatusInternalServerError, "git.OpenRepository", err)
  305. return
  306. }
  307. defer closer.Close()
  308. headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
  309. if err != nil {
  310. ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err)
  311. return
  312. }
  313. opts.CommitID = headCommitID
  314. }
  315. // create review comments
  316. for _, c := range opts.Comments {
  317. line := c.NewLineNum
  318. if c.OldLineNum > 0 {
  319. line = c.OldLineNum * -1
  320. }
  321. if _, err := pull_service.CreateCodeComment(ctx,
  322. ctx.Doer,
  323. ctx.Repo.GitRepo,
  324. pr.Issue,
  325. line,
  326. c.Body,
  327. c.Path,
  328. true, // pending review
  329. 0, // no reply
  330. opts.CommitID,
  331. nil,
  332. ); err != nil {
  333. ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err)
  334. return
  335. }
  336. }
  337. // create review and associate all pending review comments
  338. review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil)
  339. if err != nil {
  340. ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
  341. return
  342. }
  343. // convert response
  344. apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
  345. if err != nil {
  346. ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
  347. return
  348. }
  349. ctx.JSON(http.StatusOK, apiReview)
  350. }
  351. // SubmitPullReview submit a pending review to an pull request
  352. func SubmitPullReview(ctx *context.APIContext) {
  353. // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoSubmitPullReview
  354. // ---
  355. // summary: Submit a pending review to an pull request
  356. // produces:
  357. // - application/json
  358. // parameters:
  359. // - name: owner
  360. // in: path
  361. // description: owner of the repo
  362. // type: string
  363. // required: true
  364. // - name: repo
  365. // in: path
  366. // description: name of the repo
  367. // type: string
  368. // required: true
  369. // - name: index
  370. // in: path
  371. // description: index of the pull request
  372. // type: integer
  373. // format: int64
  374. // required: true
  375. // - name: id
  376. // in: path
  377. // description: id of the review
  378. // type: integer
  379. // format: int64
  380. // required: true
  381. // - name: body
  382. // in: body
  383. // required: true
  384. // schema:
  385. // "$ref": "#/definitions/SubmitPullReviewOptions"
  386. // responses:
  387. // "200":
  388. // "$ref": "#/responses/PullReview"
  389. // "404":
  390. // "$ref": "#/responses/notFound"
  391. // "422":
  392. // "$ref": "#/responses/validationError"
  393. opts := web.GetForm(ctx).(*api.SubmitPullReviewOptions)
  394. review, pr, isWrong := prepareSingleReview(ctx)
  395. if isWrong {
  396. return
  397. }
  398. if review.Type != issues_model.ReviewTypePending {
  399. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("only a pending review can be submitted"))
  400. return
  401. }
  402. // determine review type
  403. reviewType, isWrong := preparePullReviewType(ctx, pr, opts.Event, opts.Body, len(review.Comments) > 0)
  404. if isWrong {
  405. return
  406. }
  407. // if review stay pending return
  408. if reviewType == issues_model.ReviewTypePending {
  409. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("review stay pending"))
  410. return
  411. }
  412. headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pr.GetGitRefName())
  413. if err != nil {
  414. ctx.Error(http.StatusInternalServerError, "GitRepo: GetRefCommitID", err)
  415. return
  416. }
  417. // create review and associate all pending review comments
  418. review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil)
  419. if err != nil {
  420. ctx.Error(http.StatusInternalServerError, "SubmitReview", err)
  421. return
  422. }
  423. // convert response
  424. apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
  425. if err != nil {
  426. ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
  427. return
  428. }
  429. ctx.JSON(http.StatusOK, apiReview)
  430. }
  431. // preparePullReviewType return ReviewType and false or nil and true if an error happen
  432. func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest, event api.ReviewStateType, body string, hasComments bool) (issues_model.ReviewType, bool) {
  433. if err := pr.LoadIssue(ctx); err != nil {
  434. ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  435. return -1, true
  436. }
  437. needsBody := true
  438. hasBody := len(strings.TrimSpace(body)) > 0
  439. var reviewType issues_model.ReviewType
  440. switch event {
  441. case api.ReviewStateApproved:
  442. // can not approve your own PR
  443. if pr.Issue.IsPoster(ctx.Doer.ID) {
  444. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("approve your own pull is not allowed"))
  445. return -1, true
  446. }
  447. reviewType = issues_model.ReviewTypeApprove
  448. needsBody = false
  449. case api.ReviewStateRequestChanges:
  450. // can not reject your own PR
  451. if pr.Issue.IsPoster(ctx.Doer.ID) {
  452. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("reject your own pull is not allowed"))
  453. return -1, true
  454. }
  455. reviewType = issues_model.ReviewTypeReject
  456. case api.ReviewStateComment:
  457. reviewType = issues_model.ReviewTypeComment
  458. needsBody = false
  459. // if there is no body we need to ensure that there are comments
  460. if !hasBody && !hasComments {
  461. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("review event %s requires a body or a comment", event))
  462. return -1, true
  463. }
  464. default:
  465. reviewType = issues_model.ReviewTypePending
  466. }
  467. // reject reviews with empty body if a body is required for this call
  468. if needsBody && !hasBody {
  469. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("review event %s requires a body", event))
  470. return -1, true
  471. }
  472. return reviewType, false
  473. }
  474. // prepareSingleReview return review, related pull and false or nil, nil and true if an error happen
  475. func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues_model.PullRequest, bool) {
  476. pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  477. if err != nil {
  478. if issues_model.IsErrPullRequestNotExist(err) {
  479. ctx.NotFound("GetPullRequestByIndex", err)
  480. } else {
  481. ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  482. }
  483. return nil, nil, true
  484. }
  485. review, err := issues_model.GetReviewByID(ctx, ctx.ParamsInt64(":id"))
  486. if err != nil {
  487. if issues_model.IsErrReviewNotExist(err) {
  488. ctx.NotFound("GetReviewByID", err)
  489. } else {
  490. ctx.Error(http.StatusInternalServerError, "GetReviewByID", err)
  491. }
  492. return nil, nil, true
  493. }
  494. // validate the review is for the given PR
  495. if review.IssueID != pr.IssueID {
  496. ctx.NotFound("ReviewNotInPR")
  497. return nil, nil, true
  498. }
  499. // make sure that the user has access to this review if it is pending
  500. if review.Type == issues_model.ReviewTypePending && review.ReviewerID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
  501. ctx.NotFound("GetReviewByID")
  502. return nil, nil, true
  503. }
  504. if err := review.LoadAttributes(ctx); err != nil && !user_model.IsErrUserNotExist(err) {
  505. ctx.Error(http.StatusInternalServerError, "ReviewLoadAttributes", err)
  506. return nil, nil, true
  507. }
  508. return review, pr, false
  509. }
  510. // CreateReviewRequests create review requests to an pull request
  511. func CreateReviewRequests(ctx *context.APIContext) {
  512. // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoCreatePullReviewRequests
  513. // ---
  514. // summary: create review requests for a pull request
  515. // produces:
  516. // - application/json
  517. // parameters:
  518. // - name: owner
  519. // in: path
  520. // description: owner of the repo
  521. // type: string
  522. // required: true
  523. // - name: repo
  524. // in: path
  525. // description: name of the repo
  526. // type: string
  527. // required: true
  528. // - name: index
  529. // in: path
  530. // description: index of the pull request
  531. // type: integer
  532. // format: int64
  533. // required: true
  534. // - name: body
  535. // in: body
  536. // required: true
  537. // schema:
  538. // "$ref": "#/definitions/PullReviewRequestOptions"
  539. // responses:
  540. // "201":
  541. // "$ref": "#/responses/PullReviewList"
  542. // "422":
  543. // "$ref": "#/responses/validationError"
  544. // "404":
  545. // "$ref": "#/responses/notFound"
  546. opts := web.GetForm(ctx).(*api.PullReviewRequestOptions)
  547. apiReviewRequest(ctx, *opts, true)
  548. }
  549. // DeleteReviewRequests delete review requests to an pull request
  550. func DeleteReviewRequests(ctx *context.APIContext) {
  551. // swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoDeletePullReviewRequests
  552. // ---
  553. // summary: cancel review requests for a pull request
  554. // produces:
  555. // - application/json
  556. // parameters:
  557. // - name: owner
  558. // in: path
  559. // description: owner of the repo
  560. // type: string
  561. // required: true
  562. // - name: repo
  563. // in: path
  564. // description: name of the repo
  565. // type: string
  566. // required: true
  567. // - name: index
  568. // in: path
  569. // description: index of the pull request
  570. // type: integer
  571. // format: int64
  572. // required: true
  573. // - name: body
  574. // in: body
  575. // required: true
  576. // schema:
  577. // "$ref": "#/definitions/PullReviewRequestOptions"
  578. // responses:
  579. // "204":
  580. // "$ref": "#/responses/empty"
  581. // "422":
  582. // "$ref": "#/responses/validationError"
  583. // "403":
  584. // "$ref": "#/responses/forbidden"
  585. // "404":
  586. // "$ref": "#/responses/notFound"
  587. opts := web.GetForm(ctx).(*api.PullReviewRequestOptions)
  588. apiReviewRequest(ctx, *opts, false)
  589. }
  590. func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) {
  591. pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  592. if err != nil {
  593. if issues_model.IsErrPullRequestNotExist(err) {
  594. ctx.NotFound("GetPullRequestByIndex", err)
  595. } else {
  596. ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  597. }
  598. return
  599. }
  600. if err := pr.Issue.LoadRepo(ctx); err != nil {
  601. ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
  602. return
  603. }
  604. reviewers := make([]*user_model.User, 0, len(opts.Reviewers))
  605. permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
  606. if err != nil {
  607. ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
  608. return
  609. }
  610. for _, r := range opts.Reviewers {
  611. var reviewer *user_model.User
  612. if strings.Contains(r, "@") {
  613. reviewer, err = user_model.GetUserByEmail(ctx, r)
  614. } else {
  615. reviewer, err = user_model.GetUserByName(ctx, r)
  616. }
  617. if err != nil {
  618. if user_model.IsErrUserNotExist(err) {
  619. ctx.NotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r))
  620. return
  621. }
  622. ctx.Error(http.StatusInternalServerError, "GetUser", err)
  623. return
  624. }
  625. err = issue_service.IsValidReviewRequest(ctx, reviewer, ctx.Doer, isAdd, pr.Issue, &permDoer)
  626. if err != nil {
  627. if issues_model.IsErrNotValidReviewRequest(err) {
  628. ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err)
  629. return
  630. }
  631. ctx.Error(http.StatusInternalServerError, "IsValidReviewRequest", err)
  632. return
  633. }
  634. reviewers = append(reviewers, reviewer)
  635. }
  636. var reviews []*issues_model.Review
  637. if isAdd {
  638. reviews = make([]*issues_model.Review, 0, len(reviewers))
  639. }
  640. for _, reviewer := range reviewers {
  641. comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, reviewer, isAdd)
  642. if err != nil {
  643. if issues_model.IsErrReviewRequestOnClosedPR(err) {
  644. ctx.Error(http.StatusForbidden, "", err)
  645. return
  646. }
  647. ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
  648. return
  649. }
  650. if comment != nil && isAdd {
  651. if err = comment.LoadReview(ctx); err != nil {
  652. ctx.ServerError("ReviewRequest", err)
  653. return
  654. }
  655. reviews = append(reviews, comment.Review)
  656. }
  657. }
  658. if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 {
  659. teamReviewers := make([]*organization.Team, 0, len(opts.TeamReviewers))
  660. for _, t := range opts.TeamReviewers {
  661. var teamReviewer *organization.Team
  662. teamReviewer, err = organization.GetTeam(ctx, ctx.Repo.Owner.ID, t)
  663. if err != nil {
  664. if organization.IsErrTeamNotExist(err) {
  665. ctx.NotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t))
  666. return
  667. }
  668. ctx.Error(http.StatusInternalServerError, "ReviewRequest", err)
  669. return
  670. }
  671. err = issue_service.IsValidTeamReviewRequest(ctx, teamReviewer, ctx.Doer, isAdd, pr.Issue)
  672. if err != nil {
  673. if issues_model.IsErrNotValidReviewRequest(err) {
  674. ctx.Error(http.StatusUnprocessableEntity, "NotValidReviewRequest", err)
  675. return
  676. }
  677. ctx.Error(http.StatusInternalServerError, "IsValidTeamReviewRequest", err)
  678. return
  679. }
  680. teamReviewers = append(teamReviewers, teamReviewer)
  681. }
  682. for _, teamReviewer := range teamReviewers {
  683. comment, err := issue_service.TeamReviewRequest(ctx, pr.Issue, ctx.Doer, teamReviewer, isAdd)
  684. if err != nil {
  685. ctx.ServerError("TeamReviewRequest", err)
  686. return
  687. }
  688. if comment != nil && isAdd {
  689. if err = comment.LoadReview(ctx); err != nil {
  690. ctx.ServerError("ReviewRequest", err)
  691. return
  692. }
  693. reviews = append(reviews, comment.Review)
  694. }
  695. }
  696. }
  697. if isAdd {
  698. apiReviews, err := convert.ToPullReviewList(ctx, reviews, ctx.Doer)
  699. if err != nil {
  700. ctx.Error(http.StatusInternalServerError, "convertToPullReviewList", err)
  701. return
  702. }
  703. ctx.JSON(http.StatusCreated, apiReviews)
  704. } else {
  705. ctx.Status(http.StatusNoContent)
  706. return
  707. }
  708. }
  709. // DismissPullReview dismiss a review for a pull request
  710. func DismissPullReview(ctx *context.APIContext) {
  711. // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals repository repoDismissPullReview
  712. // ---
  713. // summary: Dismiss a review for a pull request
  714. // produces:
  715. // - application/json
  716. // parameters:
  717. // - name: owner
  718. // in: path
  719. // description: owner of the repo
  720. // type: string
  721. // required: true
  722. // - name: repo
  723. // in: path
  724. // description: name of the repo
  725. // type: string
  726. // required: true
  727. // - name: index
  728. // in: path
  729. // description: index of the pull request
  730. // type: integer
  731. // format: int64
  732. // required: true
  733. // - name: id
  734. // in: path
  735. // description: id of the review
  736. // type: integer
  737. // format: int64
  738. // required: true
  739. // - name: body
  740. // in: body
  741. // required: true
  742. // schema:
  743. // "$ref": "#/definitions/DismissPullReviewOptions"
  744. // responses:
  745. // "200":
  746. // "$ref": "#/responses/PullReview"
  747. // "403":
  748. // "$ref": "#/responses/forbidden"
  749. // "404":
  750. // "$ref": "#/responses/notFound"
  751. // "422":
  752. // "$ref": "#/responses/validationError"
  753. opts := web.GetForm(ctx).(*api.DismissPullReviewOptions)
  754. dismissReview(ctx, opts.Message, true, opts.Priors)
  755. }
  756. // UnDismissPullReview cancel to dismiss a review for a pull request
  757. func UnDismissPullReview(ctx *context.APIContext) {
  758. // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/undismissals repository repoUnDismissPullReview
  759. // ---
  760. // summary: Cancel to dismiss a review for a pull request
  761. // produces:
  762. // - application/json
  763. // parameters:
  764. // - name: owner
  765. // in: path
  766. // description: owner of the repo
  767. // type: string
  768. // required: true
  769. // - name: repo
  770. // in: path
  771. // description: name of the repo
  772. // type: string
  773. // required: true
  774. // - name: index
  775. // in: path
  776. // description: index of the pull request
  777. // type: integer
  778. // format: int64
  779. // required: true
  780. // - name: id
  781. // in: path
  782. // description: id of the review
  783. // type: integer
  784. // format: int64
  785. // required: true
  786. // responses:
  787. // "200":
  788. // "$ref": "#/responses/PullReview"
  789. // "403":
  790. // "$ref": "#/responses/forbidden"
  791. // "404":
  792. // "$ref": "#/responses/notFound"
  793. // "422":
  794. // "$ref": "#/responses/validationError"
  795. dismissReview(ctx, "", false, false)
  796. }
  797. func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors bool) {
  798. if !ctx.Repo.IsAdmin() {
  799. ctx.Error(http.StatusForbidden, "", "Must be repo admin")
  800. return
  801. }
  802. review, _, isWrong := prepareSingleReview(ctx)
  803. if isWrong {
  804. return
  805. }
  806. if review.Type != issues_model.ReviewTypeApprove && review.Type != issues_model.ReviewTypeReject {
  807. ctx.Error(http.StatusForbidden, "", "not need to dismiss this review because it's type is not Approve or change request")
  808. return
  809. }
  810. _, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors)
  811. if err != nil {
  812. if pull_service.IsErrDismissRequestOnClosedPR(err) {
  813. ctx.Error(http.StatusForbidden, "", err)
  814. return
  815. }
  816. ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
  817. return
  818. }
  819. if review, err = issues_model.GetReviewByID(ctx, review.ID); err != nil {
  820. ctx.Error(http.StatusInternalServerError, "GetReviewByID", err)
  821. return
  822. }
  823. // convert response
  824. apiReview, err := convert.ToPullReview(ctx, review, ctx.Doer)
  825. if err != nil {
  826. ctx.Error(http.StatusInternalServerError, "convertToPullReview", err)
  827. return
  828. }
  829. ctx.JSON(http.StatusOK, apiReview)
  830. }