1
0
mirror of https://github.com/go-gitea/gitea.git synced 2024-10-15 06:04:32 -04:00

Merge branch 'main' into create-user-system-defaults

This commit is contained in:
Jimmy Praet 2022-04-10 08:43:49 +02:00 committed by GitHub
commit a8dc6ec0f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
150 changed files with 2427 additions and 1094 deletions

View File

@ -296,6 +296,15 @@ func runServ(c *cli.Context) error {
gitcmd = exec.CommandContext(ctx, verb, repoPath) gitcmd = exec.CommandContext(ctx, verb, repoPath)
} }
// Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
// `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
if _, err := os.Stat(setting.RepoRootPath); err != nil {
if os.IsNotExist(err) {
return fail("Incorrect configuration.",
"Directory `[repository]` `ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository]` `ROOT` an absolute value.")
}
}
gitcmd.Dir = setting.RepoRootPath gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin gitcmd.Stdin = os.Stdin

View File

@ -237,7 +237,7 @@ RUN_MODE = ; prod
;; PPROF_DATA_PATH, use an absolute path when you start gitea as service ;; PPROF_DATA_PATH, use an absolute path when you start gitea as service
;PPROF_DATA_PATH = data/tmp/pprof ;PPROF_DATA_PATH = data/tmp/pprof
;; ;;
;; Landing page, can be "home", "explore", "organizations" or "login" ;; Landing page, can be "home", "explore", "organizations", "login", or any URL such as "/org/repo" or even "https://anotherwebsite.com"
;; The "login" choice is not a security measure but just a UI flow change, use REQUIRE_SIGNIN_VIEW to force users to log in. ;; The "login" choice is not a security measure but just a UI flow change, use REQUIRE_SIGNIN_VIEW to force users to log in.
;LANDING_PAGE = home ;LANDING_PAGE = home
;; ;;
@ -879,7 +879,7 @@ PATH =
;DISABLE_STARS = false ;DISABLE_STARS = false
;; ;;
;; The default branch name of new repositories ;; The default branch name of new repositories
;DEFAULT_BRANCH = master ;DEFAULT_BRANCH = main
;; ;;
;; Allow adoption of unadopted repositories ;; Allow adoption of unadopted repositories
;ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES = false ;ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES = false

View File

@ -75,7 +75,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `PREFIX_ARCHIVE_FILES`: **true**: Prefix archive files by placing them in a directory named after the repository. - `PREFIX_ARCHIVE_FILES`: **true**: Prefix archive files by placing them in a directory named after the repository.
- `DISABLE_MIGRATIONS`: **false**: Disable migrating feature. - `DISABLE_MIGRATIONS`: **false**: Disable migrating feature.
- `DISABLE_STARS`: **false**: Disable stars feature. - `DISABLE_STARS`: **false**: Disable stars feature.
- `DEFAULT_BRANCH`: **master**: Default branch name of all repositories. - `DEFAULT_BRANCH`: **main**: Default branch name of all repositories.
- `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories - `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories
- `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories - `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories
@ -300,8 +300,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `ENABLE_GZIP`: **false**: Enable gzip compression for runtime-generated content, static resources excluded. - `ENABLE_GZIP`: **false**: Enable gzip compression for runtime-generated content, static resources excluded.
- `ENABLE_PPROF`: **false**: Application profiling (memory and cpu). For "web" command it listens on localhost:6060. For "serv" command it dumps to disk at `PPROF_DATA_PATH` as `(cpuprofile|memprofile)_<username>_<temporary id>` - `ENABLE_PPROF`: **false**: Application profiling (memory and cpu). For "web" command it listens on localhost:6060. For "serv" command it dumps to disk at `PPROF_DATA_PATH` as `(cpuprofile|memprofile)_<username>_<temporary id>`
- `PPROF_DATA_PATH`: **data/tmp/pprof**: `PPROF_DATA_PATH`, use an absolute path when you start Gitea as service - `PPROF_DATA_PATH`: **data/tmp/pprof**: `PPROF_DATA_PATH`, use an absolute path when you start Gitea as service
- `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\]. - `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login, **custom**\]. Where custom would instead be any URL such as "/org/repo" or even `https://anotherwebsite.com`
- `LFS_START_SERVER`: **false**: Enables Git LFS support. - `LFS_START_SERVER`: **false**: Enables Git LFS support.
- `LFS_CONTENT_PATH`: **%(APP_DATA_PATH)/lfs**: Default LFS content path. (if it is on local storage.) **DEPRECATED** use settings in `[lfs]`. - `LFS_CONTENT_PATH`: **%(APP_DATA_PATH)/lfs**: Default LFS content path. (if it is on local storage.) **DEPRECATED** use settings in `[lfs]`.
- `LFS_JWT_SECRET`: **\<empty\>**: LFS authentication secret, change this a unique string. - `LFS_JWT_SECRET`: **\<empty\>**: LFS authentication secret, change this a unique string.

View File

@ -287,6 +287,7 @@ MODE = console
LEVEL = debug ; please set the level to debug when we are debugging a problem LEVEL = debug ; please set the level to debug when we are debugging a problem
ROUTER = console ROUTER = console
COLORIZE = false ; this can be true if you can strip out the ansi coloring COLORIZE = false ; this can be true if you can strip out the ansi coloring
ENABLE_SSH_LOG = true ; shows logs related to git over SSH.
``` ```
Sometimes it will be helpful get some specific `TRACE` level logging restricted Sometimes it will be helpful get some specific `TRACE` level logging restricted
@ -445,7 +446,7 @@ Gitea includes built-in log rotation, which should be enough for most deployment
- Disable built-in log rotation by setting `LOG_ROTATE` to `false` in your `app.ini`. - Disable built-in log rotation by setting `LOG_ROTATE` to `false` in your `app.ini`.
- Install `logrotate`. - Install `logrotate`.
- Configure `logrotate` to match your deployment requirements, see `man 8 logrotate` for configuration syntax details. In the `postrotate/endscript` block send Gitea a `USR1` signal via `kill -USR1` or `kill -10` to the `gitea` process itself, or run `gitea manager logging release-and-reopen` (with the appropriate environment). Ensure that your configurations apply to all files emitted by Gitea loggers as described in the above sections. - Configure `logrotate` to match your deployment requirements, see `man 8 logrotate` for configuration syntax details. In the `postrotate/endscript` block send Gitea a `USR1` signal via `kill -USR1` or `kill -10` to the `gitea` process itself, or run `gitea manager logging release-and-reopen` (with the appropriate environment). Ensure that your configurations apply to all files emitted by Gitea loggers as described in the above sections.
- Always do `logrotate /etc/logrotate.conf --debug` to test your configurations. - Always do `logrotate /etc/logrotate.conf --debug` to test your configurations.
- If you are using docker and are running from outside of the container you can use `docker exec -u $OS_USER $CONTAINER_NAME sh -c 'gitea manager logging release-and-reopen'` or `docker exec $CONTAINER_NAME sh -c '/bin/s6-svc -1 /etc/s6/gitea/'` or send `USR1` directly to the Gitea process itself. - If you are using docker and are running from outside of the container you can use `docker exec -u $OS_USER $CONTAINER_NAME sh -c 'gitea manager logging release-and-reopen'` or `docker exec $CONTAINER_NAME sh -c '/bin/s6-svc -1 /etc/s6/gitea/'` or send `USR1` directly to the Gitea process itself.
The next `logrotate` jobs will include your configurations, so no restart is needed. You can also immediately reload `logrotate` with `logrotate /etc/logrotate.conf --force`. The next `logrotate` jobs will include your configurations, so no restart is needed. You can also immediately reload `logrotate` with `logrotate /etc/logrotate.conf --force`.

View File

@ -9,7 +9,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"code.gitea.io/gitea/models" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -21,7 +21,7 @@ import (
func TestAPIIssuesMilestone(t *testing.T) { func TestAPIIssuesMilestone(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
milestone := unittest.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: milestone.RepoID}).(*repo_model.Repository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: milestone.RepoID}).(*repo_model.Repository)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
assert.Equal(t, int64(1), int64(milestone.NumIssues)) assert.Equal(t, int64(1), int64(milestone.NumIssues))

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
@ -23,7 +24,7 @@ func TestAPIIssuesReactions(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
_ = issue.LoadRepo() _ = issue.LoadRepo(db.DefaultContext)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
session := loginUser(t, owner.Name) session := loginUser(t, owner.Name)
@ -82,7 +83,7 @@ func TestAPICommentReactions(t *testing.T) {
comment := unittest.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment) comment := unittest.AssertExistsAndLoadBean(t, &models.Comment{ID: 2}).(*models.Comment)
_ = comment.LoadIssue() _ = comment.LoadIssue()
issue := comment.Issue issue := comment.Issue
_ = issue.LoadRepo() _ = issue.LoadRepo(db.DefaultContext)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
session := loginUser(t, owner.Name) session := loginUser(t, owner.Name)

View File

@ -9,6 +9,7 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -45,7 +46,7 @@ func TestAPIStopStopWatches(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue)
_ = issue.LoadRepo() _ = issue.LoadRepo(db.DefaultContext)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
@ -61,7 +62,7 @@ func TestAPICancelStopWatches(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue) issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 1}).(*models.Issue)
_ = issue.LoadRepo() _ = issue.LoadRepo(db.DefaultContext)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
@ -77,7 +78,7 @@ func TestAPIStartStopWatches(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue) issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue)
_ = issue.LoadRepo() _ = issue.LoadRepo(db.DefaultContext)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)

View File

@ -168,12 +168,11 @@ func TestAPIEditIssue(t *testing.T) {
func TestAPISearchIssues(t *testing.T) { func TestAPISearchIssues(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
session := loginUser(t, "user2") token := getUserToken(t, "user2")
token := getTokenForLoggedInUser(t, session)
link, _ := url.Parse("/api/v1/repos/issues/search") link, _ := url.Parse("/api/v1/repos/issues/search")
req := NewRequest(t, "GET", link.String()) req := NewRequest(t, "GET", link.String()+"?token="+token)
resp := session.MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
var apiIssues []*api.Issue var apiIssues []*api.Issue
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10) assert.Len(t, apiIssues, 10)
@ -181,7 +180,7 @@ func TestAPISearchIssues(t *testing.T) {
query := url.Values{"token": {token}} query := url.Values{"token": {token}}
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10) assert.Len(t, apiIssues, 10)
@ -189,9 +188,10 @@ func TestAPISearchIssues(t *testing.T) {
before := time.Unix(999307200, 0).Format(time.RFC3339) before := time.Unix(999307200, 0).Format(time.RFC3339)
query.Add("since", since) query.Add("since", since)
query.Add("before", before) query.Add("before", before)
query.Add("token", token)
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 8) assert.Len(t, apiIssues, 8)
query.Del("since") query.Del("since")
@ -200,14 +200,14 @@ func TestAPISearchIssues(t *testing.T) {
query.Add("state", "closed") query.Add("state", "closed")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
query.Set("state", "all") query.Set("state", "all")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.EqualValues(t, "15", resp.Header().Get("X-Total-Count")) assert.EqualValues(t, "15", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 10) // there are more but 10 is page item limit assert.Len(t, apiIssues, 10) // there are more but 10 is page item limit
@ -215,49 +215,49 @@ func TestAPISearchIssues(t *testing.T) {
query.Add("limit", "20") query.Add("limit", "20")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 15) assert.Len(t, apiIssues, 15)
query = url.Values{"assigned": {"true"}, "state": {"all"}} query = url.Values{"assigned": {"true"}, "state": {"all"}, "token": {token}}
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 1) assert.Len(t, apiIssues, 1)
query = url.Values{"milestones": {"milestone1"}, "state": {"all"}} query = url.Values{"milestones": {"milestone1"}, "state": {"all"}, "token": {token}}
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 1) assert.Len(t, apiIssues, 1)
query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}} query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}, "token": {token}}
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
query = url.Values{"owner": {"user2"}} // user query = url.Values{"owner": {"user2"}, "token": {token}} // user
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 6) assert.Len(t, apiIssues, 6)
query = url.Values{"owner": {"user3"}} // organization query = url.Values{"owner": {"user3"}, "token": {token}} // organization
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 3) assert.Len(t, apiIssues, 3)
query = url.Values{"owner": {"user3"}, "team": {"team1"}} // organization + team query = url.Values{"owner": {"user3"}, "team": {"team1"}, "token": {token}} // organization + team
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
} }
@ -265,12 +265,11 @@ func TestAPISearchIssues(t *testing.T) {
func TestAPISearchIssuesWithLabels(t *testing.T) { func TestAPISearchIssuesWithLabels(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
session := loginUser(t, "user1") token := getUserToken(t, "user1")
token := getTokenForLoggedInUser(t, session)
link, _ := url.Parse("/api/v1/repos/issues/search") link, _ := url.Parse("/api/v1/repos/issues/search")
req := NewRequest(t, "GET", link.String()) req := NewRequest(t, "GET", link.String()+"?token="+token)
resp := session.MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
var apiIssues []*api.Issue var apiIssues []*api.Issue
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
@ -280,14 +279,14 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
query.Add("token", token) query.Add("token", token)
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10) assert.Len(t, apiIssues, 10)
query.Add("labels", "label1") query.Add("labels", "label1")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
@ -295,7 +294,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
query.Set("labels", "label1,label2") query.Set("labels", "label1,label2")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
@ -303,7 +302,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
query.Set("labels", "orglabel4") query.Set("labels", "orglabel4")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 1) assert.Len(t, apiIssues, 1)
@ -312,7 +311,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
query.Add("state", "all") query.Add("state", "all")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
@ -320,7 +319,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
query.Set("labels", "label1,orglabel4") query.Set("labels", "label1,orglabel4")
link.RawQuery = query.Encode() link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String()) req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues) DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
} }

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -23,7 +24,7 @@ func TestAPIGetTrackedTimes(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
issue2 := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) issue2 := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue)
assert.NoError(t, issue2.LoadRepo()) assert.NoError(t, issue2.LoadRepo(db.DefaultContext))
session := loginUser(t, user2.Name) session := loginUser(t, user2.Name)
token := getTokenForLoggedInUser(t, session) token := getTokenForLoggedInUser(t, session)
@ -65,7 +66,7 @@ func TestAPIDeleteTrackedTime(t *testing.T) {
time6 := unittest.AssertExistsAndLoadBean(t, &models.TrackedTime{ID: 6}).(*models.TrackedTime) time6 := unittest.AssertExistsAndLoadBean(t, &models.TrackedTime{ID: 6}).(*models.TrackedTime)
issue2 := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) issue2 := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue)
assert.NoError(t, issue2.LoadRepo()) assert.NoError(t, issue2.LoadRepo(db.DefaultContext))
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
session := loginUser(t, user2.Name) session := loginUser(t, user2.Name)
@ -99,7 +100,7 @@ func TestAPIAddTrackedTimes(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
issue2 := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) issue2 := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue)
assert.NoError(t, issue2.LoadRepo()) assert.NoError(t, issue2.LoadRepo(db.DefaultContext))
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)

View File

@ -20,9 +20,8 @@ import (
func TestAPIOrgCreate(t *testing.T) { func TestAPIOrgCreate(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) { onGiteaRun(t, func(*testing.T, *url.URL) {
session := loginUser(t, "user1") token := getUserToken(t, "user1")
token := getTokenForLoggedInUser(t, session)
org := api.CreateOrgOption{ org := api.CreateOrgOption{
UserName: "user1_org", UserName: "user1_org",
FullName: "User1's organization", FullName: "User1's organization",
@ -32,7 +31,7 @@ func TestAPIOrgCreate(t *testing.T) {
Visibility: "limited", Visibility: "limited",
} }
req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &org) req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &org)
resp := session.MakeRequest(t, req, http.StatusCreated) resp := MakeRequest(t, req, http.StatusCreated)
var apiOrg api.Organization var apiOrg api.Organization
DecodeJSON(t, resp, &apiOrg) DecodeJSON(t, resp, &apiOrg)
@ -50,13 +49,13 @@ func TestAPIOrgCreate(t *testing.T) {
FullName: org.FullName, FullName: org.FullName,
}) })
req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName) req = NewRequestf(t, "GET", "/api/v1/orgs/%s?token=%s", org.UserName, token)
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiOrg) DecodeJSON(t, resp, &apiOrg)
assert.EqualValues(t, org.UserName, apiOrg.UserName) assert.EqualValues(t, org.UserName, apiOrg.UserName)
req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org.UserName) req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token=%s", org.UserName, token)
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
var repos []*api.Repository var repos []*api.Repository
DecodeJSON(t, resp, &repos) DecodeJSON(t, resp, &repos)
@ -64,8 +63,8 @@ func TestAPIOrgCreate(t *testing.T) {
assert.False(t, repo.Private) assert.False(t, repo.Private)
} }
req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", org.UserName) req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members?token=%s", org.UserName, token)
resp = session.MakeRequest(t, req, http.StatusOK) resp = MakeRequest(t, req, http.StatusOK)
// user1 on this org is public // user1 on this org is public
var users []*api.User var users []*api.User

View File

@ -9,11 +9,15 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"testing" "testing"
"time"
"code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -43,7 +47,7 @@ func TestPackageAPI(t *testing.T) {
DecodeJSON(t, resp, &apiPackages) DecodeJSON(t, resp, &apiPackages)
assert.Len(t, apiPackages, 1) assert.Len(t, apiPackages, 1)
assert.Equal(t, string(packages.TypeGeneric), apiPackages[0].Type) assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type)
assert.Equal(t, packageName, apiPackages[0].Name) assert.Equal(t, packageName, apiPackages[0].Name)
assert.Equal(t, packageVersion, apiPackages[0].Version) assert.Equal(t, packageVersion, apiPackages[0].Version)
assert.NotNil(t, apiPackages[0].Creator) assert.NotNil(t, apiPackages[0].Creator)
@ -62,7 +66,7 @@ func TestPackageAPI(t *testing.T) {
var p *api.Package var p *api.Package
DecodeJSON(t, resp, &p) DecodeJSON(t, resp, &p)
assert.Equal(t, string(packages.TypeGeneric), p.Type) assert.Equal(t, string(packages_model.TypeGeneric), p.Type)
assert.Equal(t, packageName, p.Name) assert.Equal(t, packageName, p.Name)
assert.Equal(t, packageVersion, p.Version) assert.Equal(t, packageVersion, p.Version)
assert.NotNil(t, p.Creator) assert.NotNil(t, p.Creator)
@ -100,3 +104,26 @@ func TestPackageAPI(t *testing.T) {
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
}) })
} }
func TestPackageCleanup(t *testing.T) {
defer prepareTestEnv(t)()
time.Sleep(time.Second)
pbs, err := packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, time.Duration(0))
assert.NoError(t, err)
assert.NotEmpty(t, pbs)
_, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion)
assert.NoError(t, err)
err = packages_service.Cleanup(nil, time.Duration(0))
assert.NoError(t, err)
pbs, err = packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, time.Duration(0))
assert.NoError(t, err)
assert.Empty(t, pbs)
_, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion)
assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
}

View File

@ -25,12 +25,11 @@ func TestAPIListReleases(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
session := loginUser(t, user2.LowerName) token := getUserToken(t, user2.LowerName)
token := getTokenForLoggedInUser(t, session)
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name)) link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
link.RawQuery = url.Values{"token": {token}}.Encode() link.RawQuery = url.Values{"token": {token}}.Encode()
resp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
var apiReleases []*api.Release var apiReleases []*api.Release
DecodeJSON(t, resp, &apiReleases) DecodeJSON(t, resp, &apiReleases)
if assert.Len(t, apiReleases, 3) { if assert.Len(t, apiReleases, 3) {
@ -53,13 +52,11 @@ func TestAPIListReleases(t *testing.T) {
// test filter // test filter
testFilterByLen := func(auth bool, query url.Values, expectedLength int, msgAndArgs ...string) { testFilterByLen := func(auth bool, query url.Values, expectedLength int, msgAndArgs ...string) {
link.RawQuery = query.Encode()
if auth { if auth {
query.Set("token", token) query.Set("token", token)
resp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
} else {
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
} }
link.RawQuery = query.Encode()
resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
DecodeJSON(t, resp, &apiReleases) DecodeJSON(t, resp, &apiReleases)
assert.Len(t, apiReleases, expectedLength, msgAndArgs) assert.Len(t, apiReleases, expectedLength, msgAndArgs)
} }

View File

@ -59,36 +59,34 @@ func TestAPIRepoTopic(t *testing.T) {
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
// Get user2's token // Get user2's token
session := loginUser(t, user2.Name) token2 := getUserToken(t, user2.Name)
token2 := getTokenForLoggedInUser(t, session)
// Test read topics using login // Test read topics using login
url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name)
req := NewRequest(t, "GET", url) req := NewRequest(t, "GET", url+"?token="+token2)
res := session.MakeRequest(t, req, http.StatusOK) res := MakeRequest(t, req, http.StatusOK)
var topics *api.TopicName var topics *api.TopicName
DecodeJSON(t, res, &topics) DecodeJSON(t, res, &topics)
assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames)
// Log out user2 // Log out user2
session = emptyTestSession(t)
url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user2.Name, repo2.Name, token2) url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user2.Name, repo2.Name, token2)
// Test delete a topic // Test delete a topic
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2) req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2)
session.MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
// Test add an existing topic // Test add an existing topic
req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Golang", token2) req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Golang", token2)
session.MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
// Test add a topic // Test add a topic
req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "topicName3", token2) req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "topicName3", token2)
session.MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
// Test read topics using token // Test read topics using token
req = NewRequest(t, "GET", url) req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusOK) res = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, res, &topics) DecodeJSON(t, res, &topics)
assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames)
@ -97,9 +95,9 @@ func TestAPIRepoTopic(t *testing.T) {
req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{
Topics: newTopics, Topics: newTopics,
}) })
session.MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
req = NewRequest(t, "GET", url) req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusOK) res = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, res, &topics) DecodeJSON(t, res, &topics)
assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames)
@ -108,9 +106,9 @@ func TestAPIRepoTopic(t *testing.T) {
req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{
Topics: newTopics, Topics: newTopics,
}) })
session.MakeRequest(t, req, http.StatusUnprocessableEntity) MakeRequest(t, req, http.StatusUnprocessableEntity)
req = NewRequest(t, "GET", url) req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusOK) res = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, res, &topics) DecodeJSON(t, res, &topics)
assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames)
@ -119,9 +117,9 @@ func TestAPIRepoTopic(t *testing.T) {
req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{
Topics: newTopics, Topics: newTopics,
}) })
session.MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
req = NewRequest(t, "GET", url) req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusOK) res = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, res, &topics) DecodeJSON(t, res, &topics)
assert.Len(t, topics.TopicNames, 25) assert.Len(t, topics.TopicNames, 25)
@ -130,29 +128,27 @@ func TestAPIRepoTopic(t *testing.T) {
req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{
Topics: newTopics, Topics: newTopics,
}) })
session.MakeRequest(t, req, http.StatusUnprocessableEntity) MakeRequest(t, req, http.StatusUnprocessableEntity)
// Test add a topic when there is already maximum // Test add a topic when there is already maximum
req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "t26", token2) req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "t26", token2)
session.MakeRequest(t, req, http.StatusUnprocessableEntity) MakeRequest(t, req, http.StatusUnprocessableEntity)
// Test delete a topic that repo doesn't have // Test delete a topic that repo doesn't have
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2) req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s?token=%s", user2.Name, repo2.Name, "Topicname1", token2)
session.MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
// Get user4's token // Get user4's token
session = loginUser(t, user4.Name) token4 := getUserToken(t, user4.Name)
token4 := getTokenForLoggedInUser(t, session)
session = emptyTestSession(t)
// Test read topics with write access // Test read topics with write access
url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user3.Name, repo3.Name, token4) url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user3.Name, repo3.Name, token4)
req = NewRequest(t, "GET", url) req = NewRequest(t, "GET", url)
res = session.MakeRequest(t, req, http.StatusOK) res = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, res, &topics) DecodeJSON(t, res, &topics)
assert.Empty(t, topics.TopicNames) assert.Empty(t, topics.TopicNames)
// Test add a topic to repo with write access (requires repo admin access) // Test add a topic to repo with write access (requires repo admin access)
req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user3.Name, repo3.Name, "topicName", token4) req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s?token=%s", user3.Name, repo3.Name, "topicName", token4)
session.MakeRequest(t, req, http.StatusForbidden) MakeRequest(t, req, http.StatusForbidden)
} }

View File

@ -224,11 +224,9 @@ func TestAPITeamSearch(t *testing.T) {
var results TeamSearchResults var results TeamSearchResults
session := loginUser(t, user.Name) token := getUserToken(t, user.Name)
csrf := GetCSRF(t, session, "/"+org.Name) req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "_team", token)
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team") resp := MakeRequest(t, req, http.StatusOK)
req.Header.Add("X-Csrf-Token", csrf)
resp := session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &results) DecodeJSON(t, resp, &results)
assert.NotEmpty(t, results.Data) assert.NotEmpty(t, results.Data)
assert.Len(t, results.Data, 1) assert.Len(t, results.Data, 1)
@ -236,9 +234,8 @@ func TestAPITeamSearch(t *testing.T) {
// no access if not organization member // no access if not organization member
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
session = loginUser(t, user5.Name) token5 := getUserToken(t, user5.Name)
csrf = GetCSRF(t, session, "/"+org.Name)
req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team") req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "team", token5)
req.Header.Add("X-Csrf-Token", csrf) MakeRequest(t, req, http.StatusForbidden)
session.MakeRequest(t, req, http.StatusForbidden)
} }

View File

@ -20,15 +20,15 @@ func TestUserHeatmap(t *testing.T) {
defer prepareTestEnv(t)() defer prepareTestEnv(t)()
adminUsername := "user1" adminUsername := "user1"
normalUsername := "user2" normalUsername := "user2"
session := loginUser(t, adminUsername) token := getUserToken(t, adminUsername)
fakeNow := time.Date(2011, 10, 20, 0, 0, 0, 0, time.Local) fakeNow := time.Date(2011, 10, 20, 0, 0, 0, 0, time.Local)
timeutil.Set(fakeNow) timeutil.Set(fakeNow)
defer timeutil.Unset() defer timeutil.Unset()
urlStr := fmt.Sprintf("/api/v1/users/%s/heatmap", normalUsername) urlStr := fmt.Sprintf("/api/v1/users/%s/heatmap?token=%s", normalUsername, token)
req := NewRequest(t, "GET", urlStr) req := NewRequest(t, "GET", urlStr)
resp := session.MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
var heatmap []*models.UserHeatmapData var heatmap []*models.UserHeatmapData
DecodeJSON(t, resp, &heatmap) DecodeJSON(t, resp, &heatmap)
var dummyheatmap []*models.UserHeatmapData var dummyheatmap []*models.UserHeatmapData

52
integrations/csrf_test.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"net/http"
"strings"
"testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestCsrfProtection(t *testing.T) {
defer prepareTestEnv(t)()
// test web form csrf via form
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
session := loginUser(t, user.Name)
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
"_csrf": "fake_csrf",
})
session.MakeRequest(t, req, http.StatusSeeOther)
resp := session.MakeRequest(t, req, http.StatusSeeOther)
loc := resp.Header().Get("Location")
assert.Equal(t, setting.AppSubURL+"/", loc)
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Equal(t, "Bad Request: invalid CSRF token",
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
)
// test web form csrf via header. TODO: should use an UI api to test
req = NewRequest(t, "POST", "/user/settings")
req.Header.Add("X-Csrf-Token", "fake_csrf")
session.MakeRequest(t, req, http.StatusSeeOther)
resp = session.MakeRequest(t, req, http.StatusSeeOther)
loc = resp.Header().Get("Location")
assert.Equal(t, setting.AppSubURL+"/", loc)
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
assert.Equal(t, "Bad Request: invalid CSRF token",
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
)
}

View File

@ -165,6 +165,7 @@ func initIntegrationTest() {
setting.SetCustomPathAndConf("", "", "") setting.SetCustomPathAndConf("", "", "")
setting.LoadForTest() setting.LoadForTest()
setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
_ = util.RemoveAll(models.LocalCopyPath()) _ = util.RemoveAll(models.LocalCopyPath())
git.CheckLFSVersion() git.CheckLFSVersion()
setting.InitDBConfig() setting.InitDBConfig()
@ -359,6 +360,10 @@ func emptyTestSession(t testing.TB) *TestSession {
return &TestSession{jar: jar} return &TestSession{jar: jar}
} }
func getUserToken(t testing.TB, userName string) string {
return getTokenForLoggedInUser(t, loginUser(t, userName))
}
func loginUser(t testing.TB, userName string) *TestSession { func loginUser(t testing.TB, userName string) *TestSession {
t.Helper() t.Helper()
if session, ok := loginSessionCache[userName]; ok { if session, ok := loginSessionCache[userName]; ok {

View File

@ -7,6 +7,7 @@ package integrations
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -20,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
@ -347,3 +349,211 @@ func TestIssueRedirect(t *testing.T) {
resp = session.MakeRequest(t, req, http.StatusSeeOther) resp = session.MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp)) assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp))
} }
func TestSearchIssues(t *testing.T) {
defer prepareTestEnv(t)()
session := loginUser(t, "user2")
link, _ := url.Parse("/issues/search")
req := NewRequest(t, "GET", link.String())
resp := session.MakeRequest(t, req, http.StatusOK)
var apiIssues []*api.Issue
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10)
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10)
since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801
before := time.Unix(999307200, 0).Format(time.RFC3339)
query := url.Values{}
query.Add("since", since)
query.Add("before", before)
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 8)
query.Del("since")
query.Del("before")
query.Add("state", "closed")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
query.Set("state", "all")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.EqualValues(t, "15", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 10) // there are more but 10 is page item limit
query.Add("limit", "20")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 15)
query = url.Values{"assigned": {"true"}, "state": {"all"}}
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 1)
query = url.Values{"milestones": {"milestone1"}, "state": {"all"}}
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 1)
query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}}
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
query = url.Values{"owner": {"user2"}} // user
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 6)
query = url.Values{"owner": {"user3"}} // organization
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 3)
query = url.Values{"owner": {"user3"}, "team": {"team1"}} // organization + team
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
}
func TestSearchIssuesWithLabels(t *testing.T) {
defer prepareTestEnv(t)()
token := getUserToken(t, "user1")
link, _ := url.Parse("/api/v1/repos/issues/search?token=" + token)
req := NewRequest(t, "GET", link.String())
resp := MakeRequest(t, req, http.StatusOK)
var apiIssues []*api.Issue
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10)
query := url.Values{
"token": []string{token},
}
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10)
query.Add("labels", "label1")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
// multiple labels
query.Set("labels", "label1,label2")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
// an org label
query.Set("labels", "orglabel4")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 1)
// org and repo label
query.Set("labels", "label2,orglabel4")
query.Add("state", "all")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
// org and repo label which share the same issue
query.Set("labels", "label1,orglabel4")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
}
func TestGetIssueInfo(t *testing.T) {
defer prepareTestEnv(t)()
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
assert.NoError(t, issue.LoadAttributes())
assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix))
assert.Equal(t, api.StateOpen, issue.State())
session := loginUser(t, owner.Name)
urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index)
req := NewRequest(t, "GET", urlStr)
resp := session.MakeRequest(t, req, http.StatusOK)
var apiIssue api.Issue
DecodeJSON(t, resp, &apiIssue)
assert.EqualValues(t, issue.ID, apiIssue.ID)
}
func TestUpdateIssueDeadline(t *testing.T) {
defer prepareTestEnv(t)()
issueBefore := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue)
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}).(*repo_model.Repository)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}).(*user_model.User)
assert.NoError(t, issueBefore.LoadAttributes())
assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
assert.Equal(t, api.StateOpen, issueBefore.State())
session := loginUser(t, owner.Name)
issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
req := NewRequest(t, "GET", issueURL)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF()
req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{
"due_date": "2022-04-06T00:00:00.000Z",
})
resp = session.MakeRequest(t, req, http.StatusCreated)
var apiIssue api.IssueDeadline
DecodeJSON(t, resp, &apiIssue)
assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
}

View File

@ -10,6 +10,8 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -173,3 +175,30 @@ func TestOrgRestrictedUser(t *testing.T) {
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
restrictedSession.MakeRequest(t, req, http.StatusOK) restrictedSession.MakeRequest(t, req, http.StatusOK)
} }
func TestTeamSearch(t *testing.T) {
defer prepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
var results TeamSearchResults
session := loginUser(t, user.Name)
csrf := GetCSRF(t, session, "/"+org.Name)
req := NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "_team")
req.Header.Add("X-Csrf-Token", csrf)
resp := session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &results)
assert.NotEmpty(t, results.Data)
assert.Len(t, results.Data, 1)
assert.Equal(t, "test_team", results.Data[0].Name)
// no access if not organization member
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
session = loginUser(t, user5.Name)
csrf = GetCSRF(t, session, "/"+org.Name)
req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team")
req.Header.Add("X-Csrf-Token", csrf)
session.MakeRequest(t, req, http.StatusNotFound)
}

View File

@ -0,0 +1,47 @@
// Copyright 2022 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"
"net/url"
"testing"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestTopicSearch(t *testing.T) {
defer prepareTestEnv(t)()
searchURL, _ := url.Parse("/explore/topics/search")
var topics struct {
TopicNames []*api.TopicResponse `json:"topics"`
}
query := url.Values{"page": []string{"1"}, "limit": []string{"4"}}
searchURL.RawQuery = query.Encode()
res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
DecodeJSON(t, res, &topics)
assert.Len(t, topics.TopicNames, 4)
assert.EqualValues(t, "6", res.Header().Get("x-total-count"))
query.Add("q", "topic")
searchURL.RawQuery = query.Encode()
res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
DecodeJSON(t, res, &topics)
assert.Len(t, topics.TopicNames, 2)
query.Set("q", "database")
searchURL.RawQuery = query.Encode()
res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
DecodeJSON(t, res, &topics)
if assert.Len(t, topics.TopicNames, 1) {
assert.EqualValues(t, 2, topics.TopicNames[0].ID)
assert.EqualValues(t, "database", topics.TopicNames[0].Name)
assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount)
}
}

View File

@ -8,8 +8,11 @@ import (
"net/http" "net/http"
"testing" "testing"
"code.gitea.io/gitea/models"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation/i18n"
@ -222,3 +225,26 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) {
// t.Log(resp.Body.String()) // t.Log(resp.Body.String())
assert.Equal(t, expected, resp.Body.String()) assert.Equal(t, expected, resp.Body.String())
} }
func TestListStopWatches(t *testing.T) {
defer prepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
session := loginUser(t, owner.Name)
req := NewRequestf(t, "GET", "/user/stopwatches")
resp := session.MakeRequest(t, req, http.StatusOK)
var apiWatches []*api.StopWatch
DecodeJSON(t, resp, &apiWatches)
stopwatch := unittest.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch)
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue)
if assert.Len(t, apiWatches, 1) {
assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix())
assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex)
assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle)
assert.EqualValues(t, repo.Name, apiWatches[0].RepoName)
assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName)
assert.Greater(t, int64(apiWatches[0].Seconds), int64(0))
}
}

View File

@ -243,7 +243,7 @@ func (a *Action) getCommentLink(ctx context.Context) string {
return "#" return "#"
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return "#" return "#"
} }

View File

@ -8,7 +8,10 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -33,3 +36,70 @@ func TestDeleteOrphanedObjects(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, countBefore, countAfter) assert.EqualValues(t, countBefore, countAfter)
} }
func TestNewMilestone(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := &issues_model.Milestone{
RepoID: 1,
Name: "milestoneName",
Content: "milestoneContent",
}
assert.NoError(t, issues_model.NewMilestone(milestone))
unittest.AssertExistsAndLoadBean(t, milestone)
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{})
}
func TestChangeMilestoneStatus(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
assert.NoError(t, issues_model.ChangeMilestoneStatus(milestone, true))
unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=1")
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{})
assert.NoError(t, issues_model.ChangeMilestoneStatus(milestone, false))
unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=0")
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{})
}
func TestDeleteMilestoneByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
assert.NoError(t, issues_model.DeleteMilestoneByRepoID(1, 1))
unittest.AssertNotExistsBean(t, &issues_model.Milestone{ID: 1})
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: 1})
assert.NoError(t, issues_model.DeleteMilestoneByRepoID(unittest.NonexistentID, unittest.NonexistentID))
}
func TestUpdateMilestone(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
milestone.Name = " newMilestoneName "
milestone.Content = "newMilestoneContent"
assert.NoError(t, issues_model.UpdateMilestone(milestone, milestone.IsClosed))
milestone = unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
assert.EqualValues(t, "newMilestoneName", milestone.Name)
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
}
func TestUpdateMilestoneCounters(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1},
"is_closed=0").(*Issue)
issue.IsClosed = true
issue.ClosedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
assert.NoError(t, err)
assert.NoError(t, issues_model.UpdateMilestoneCounters(db.DefaultContext, issue.MilestoneID))
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
issue.IsClosed = false
issue.ClosedUnix = 0
_, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
assert.NoError(t, err)
assert.NoError(t, issues_model.UpdateMilestoneCounters(db.DefaultContext, issue.MilestoneID))
unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
}

View File

@ -1048,33 +1048,6 @@ func (err ErrLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID) return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
} }
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
// \/ \/ \/ \/ \/
// ErrMilestoneNotExist represents a "MilestoneNotExist" kind of error.
type ErrMilestoneNotExist struct {
ID int64
RepoID int64
Name string
}
// IsErrMilestoneNotExist checks if an error is a ErrMilestoneNotExist.
func IsErrMilestoneNotExist(err error) bool {
_, ok := err.(ErrMilestoneNotExist)
return ok
}
func (err ErrMilestoneNotExist) Error() string {
if len(err.Name) > 0 {
return fmt.Sprintf("milestone does not exist [name: %s, repo_id: %d]", err.Name, err.RepoID)
}
return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
}
// ____ ___ .__ .___ // ____ ___ .__ .___
// | | \______ | | _________ __| _/ // | | \______ | | _________ __| _/
// | | /\____ \| | / _ \__ \ / __ | // | | /\____ \| | / _ \__ \ / __ |

View File

@ -16,7 +16,7 @@ import (
admin_model "code.gitea.io/gitea/models/admin" admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference" "code.gitea.io/gitea/models/foreignreference"
"code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
@ -46,14 +46,14 @@ type Issue struct {
PosterID int64 `xorm:"INDEX"` PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"` Poster *user_model.User `xorm:"-"`
OriginalAuthor string OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"` OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"` Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"` Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"` Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"` MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"` Milestone *issues_model.Milestone `xorm:"-"`
Project *project_model.Project `xorm:"-"` Project *project_model.Project `xorm:"-"`
Priority int Priority int
AssigneeID int64 `xorm:"-"` AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"` Assignee *user_model.User `xorm:"-"`
@ -72,7 +72,7 @@ type Issue struct {
Attachments []*repo_model.Attachment `xorm:"-"` Attachments []*repo_model.Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"` Comments []*Comment `xorm:"-"`
Reactions issues.ReactionList `xorm:"-"` Reactions issues_model.ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"` TotalTrackedTime int64 `xorm:"-"`
Assignees []*user_model.User `xorm:"-"` Assignees []*user_model.User `xorm:"-"`
ForeignReference *foreignreference.ForeignReference `xorm:"-"` ForeignReference *foreignreference.ForeignReference `xorm:"-"`
@ -124,11 +124,7 @@ func (issue *Issue) IsOverdue() bool {
} }
// LoadRepo loads issue's repository // LoadRepo loads issue's repository
func (issue *Issue) LoadRepo() error { func (issue *Issue) LoadRepo(ctx context.Context) (err error) {
return issue.loadRepo(db.DefaultContext)
}
func (issue *Issue) loadRepo(ctx context.Context) (err error) {
if issue.Repo == nil { if issue.Repo == nil {
issue.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, issue.RepoID) issue.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, issue.RepoID)
if err != nil { if err != nil {
@ -144,7 +140,7 @@ func (issue *Issue) IsTimetrackerEnabled() bool {
} }
func (issue *Issue) isTimetrackerEnabled(ctx context.Context) bool { func (issue *Issue) isTimetrackerEnabled(ctx context.Context) bool {
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
log.Error(fmt.Sprintf("loadRepo: %v", err)) log.Error(fmt.Sprintf("loadRepo: %v", err))
return false return false
} }
@ -244,17 +240,17 @@ func (issue *Issue) loadReactions(ctx context.Context) (err error) {
if issue.Reactions != nil { if issue.Reactions != nil {
return nil return nil
} }
reactions, _, err := issues.FindReactions(ctx, issues.FindReactionsOptions{ reactions, _, err := issues_model.FindReactions(ctx, issues_model.FindReactionsOptions{
IssueID: issue.ID, IssueID: issue.ID,
}) })
if err != nil { if err != nil {
return err return err
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return err return err
} }
// Load reaction user data // Load reaction user data
if _, err := issues.ReactionList(reactions).LoadUsers(ctx, issue.Repo); err != nil { if _, err := issues_model.ReactionList(reactions).LoadUsers(ctx, issue.Repo); err != nil {
return err return err
} }
@ -297,10 +293,10 @@ func (issue *Issue) loadForeignReference(ctx context.Context) (err error) {
return nil return nil
} }
func (issue *Issue) loadMilestone(e db.Engine) (err error) { func (issue *Issue) loadMilestone(ctx context.Context) (err error) {
if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 { if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 {
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, issue.RepoID, issue.MilestoneID)
if err != nil && !IsErrMilestoneNotExist(err) { if err != nil && !issues_model.IsErrMilestoneNotExist(err) {
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err) return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
} }
} }
@ -309,7 +305,7 @@ func (issue *Issue) loadMilestone(e db.Engine) (err error) {
func (issue *Issue) loadAttributes(ctx context.Context) (err error) { func (issue *Issue) loadAttributes(ctx context.Context) (err error) {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return return
} }
@ -321,7 +317,7 @@ func (issue *Issue) loadAttributes(ctx context.Context) (err error) {
return return
} }
if err = issue.loadMilestone(e); err != nil { if err = issue.loadMilestone(ctx); err != nil {
return return
} }
@ -372,7 +368,7 @@ func (issue *Issue) LoadAttributes() error {
// LoadMilestone load milestone of this issue. // LoadMilestone load milestone of this issue.
func (issue *Issue) LoadMilestone() error { func (issue *Issue) LoadMilestone() error {
return issue.loadMilestone(db.GetEngine(db.DefaultContext)) return issue.loadMilestone(db.DefaultContext)
} }
// GetIsRead load the `IsRead` field of the issue // GetIsRead load the `IsRead` field of the issue
@ -391,7 +387,7 @@ func (issue *Issue) GetIsRead(userID int64) error {
// APIURL returns the absolute APIURL to this issue. // APIURL returns the absolute APIURL to this issue.
func (issue *Issue) APIURL() string { func (issue *Issue) APIURL() string {
if issue.Repo == nil { if issue.Repo == nil {
err := issue.LoadRepo() err := issue.LoadRepo(db.DefaultContext)
if err != nil { if err != nil {
log.Error("Issue[%d].APIURL(): %v", issue.ID, err) log.Error("Issue[%d].APIURL(): %v", issue.ID, err)
return "" return ""
@ -491,7 +487,7 @@ func ClearIssueLabels(issue *Issue, doer *user_model.User) (err error) {
} }
defer committer.Close() defer committer.Close()
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} else if err = issue.loadPullRequest(db.GetEngine(ctx)); err != nil { } else if err = issue.loadPullRequest(db.GetEngine(ctx)); err != nil {
return err return err
@ -539,7 +535,7 @@ func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (e
} }
defer committer.Close() defer committer.Close()
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return err return err
} }
@ -607,7 +603,8 @@ func (issue *Issue) ReadBy(userID int64) error {
return setIssueNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, issue.ID) return setIssueNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, issue.ID)
} }
func updateIssueCols(ctx context.Context, issue *Issue, cols ...string) error { // UpdateIssueCols updates cols of issue
func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue); err != nil { if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue); err != nil {
return err return err
} }
@ -658,7 +655,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
issue.ClosedUnix = 0 issue.ClosedUnix = 0
} }
if err := updateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil { if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil {
return nil, err return nil, err
} }
@ -674,7 +671,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
// Update issue count of milestone // Update issue count of milestone
if issue.MilestoneID > 0 { if issue.MilestoneID > 0 {
if err := updateMilestoneCounters(ctx, issue.MilestoneID); err != nil { if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
return nil, err return nil, err
} }
} }
@ -691,7 +688,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
cmtType = CommentTypeMergePull cmtType = CommentTypeMergePull
} }
return createComment(ctx, &CreateCommentOptions{ return CreateCommentCtx(ctx, &CreateCommentOptions{
Type: cmtType, Type: cmtType,
Doer: doer, Doer: doer,
Repo: issue.Repo, Repo: issue.Repo,
@ -707,7 +704,7 @@ func ChangeIssueStatus(issue *Issue, doer *user_model.User, isClosed bool) (*Com
} }
defer committer.Close() defer committer.Close()
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return nil, err return nil, err
} }
if err := issue.loadPoster(db.GetEngine(ctx)); err != nil { if err := issue.loadPoster(db.GetEngine(ctx)); err != nil {
@ -734,11 +731,11 @@ func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err
} }
defer committer.Close() defer committer.Close()
if err = updateIssueCols(ctx, issue, "name"); err != nil { if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
return fmt.Errorf("updateIssueCols: %v", err) return fmt.Errorf("updateIssueCols: %v", err)
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return fmt.Errorf("loadRepo: %v", err) return fmt.Errorf("loadRepo: %v", err)
} }
@ -750,7 +747,7 @@ func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err
OldTitle: oldTitle, OldTitle: oldTitle,
NewTitle: issue.Title, NewTitle: issue.Title,
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return fmt.Errorf("createComment: %v", err) return fmt.Errorf("createComment: %v", err)
} }
if err = issue.addCrossReferences(ctx, doer, true); err != nil { if err = issue.addCrossReferences(ctx, doer, true); err != nil {
@ -768,11 +765,11 @@ func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err err
} }
defer committer.Close() defer committer.Close()
if err = updateIssueCols(ctx, issue, "ref"); err != nil { if err = UpdateIssueCols(ctx, issue, "ref"); err != nil {
return fmt.Errorf("updateIssueCols: %v", err) return fmt.Errorf("updateIssueCols: %v", err)
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return fmt.Errorf("loadRepo: %v", err) return fmt.Errorf("loadRepo: %v", err)
} }
oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix) oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix)
@ -786,7 +783,7 @@ func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err err
OldRef: oldRefFriendly, OldRef: oldRefFriendly,
NewRef: newRefFriendly, NewRef: newRefFriendly,
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return fmt.Errorf("createComment: %v", err) return fmt.Errorf("createComment: %v", err)
} }
@ -811,7 +808,7 @@ func AddDeletePRBranchComment(doer *user_model.User, repo *repo_model.Repository
Issue: issue, Issue: issue,
OldRef: branchName, OldRef: branchName,
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return err return err
} }
@ -846,12 +843,12 @@ func ChangeIssueContent(issue *Issue, doer *user_model.User, content string) (er
} }
defer committer.Close() defer committer.Close()
hasContentHistory, err := issues.HasIssueContentHistory(ctx, issue.ID, 0) hasContentHistory, err := issues_model.HasIssueContentHistory(ctx, issue.ID, 0)
if err != nil { if err != nil {
return fmt.Errorf("HasIssueContentHistory: %v", err) return fmt.Errorf("HasIssueContentHistory: %v", err)
} }
if !hasContentHistory { if !hasContentHistory {
if err = issues.SaveIssueContentHistory(db.GetEngine(ctx), issue.PosterID, issue.ID, 0, if err = issues_model.SaveIssueContentHistory(db.GetEngine(ctx), issue.PosterID, issue.ID, 0,
issue.CreatedUnix, issue.Content, true); err != nil { issue.CreatedUnix, issue.Content, true); err != nil {
return fmt.Errorf("SaveIssueContentHistory: %v", err) return fmt.Errorf("SaveIssueContentHistory: %v", err)
} }
@ -859,11 +856,11 @@ func ChangeIssueContent(issue *Issue, doer *user_model.User, content string) (er
issue.Content = content issue.Content = content
if err = updateIssueCols(ctx, issue, "content"); err != nil { if err = UpdateIssueCols(ctx, issue, "content"); err != nil {
return fmt.Errorf("UpdateIssueCols: %v", err) return fmt.Errorf("UpdateIssueCols: %v", err)
} }
if err = issues.SaveIssueContentHistory(db.GetEngine(ctx), doer.ID, issue.ID, 0, if err = issues_model.SaveIssueContentHistory(db.GetEngine(ctx), doer.ID, issue.ID, 0,
timeutil.TimeStampNow(), issue.Content, false); err != nil { timeutil.TimeStampNow(), issue.Content, false); err != nil {
return fmt.Errorf("SaveIssueContentHistory: %v", err) return fmt.Errorf("SaveIssueContentHistory: %v", err)
} }
@ -943,8 +940,8 @@ func newIssue(ctx context.Context, doer *user_model.User, opts NewIssueOptions)
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title) opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
if opts.Issue.MilestoneID > 0 { if opts.Issue.MilestoneID > 0 {
milestone, err := getMilestoneByRepoID(e, opts.Issue.RepoID, opts.Issue.MilestoneID) milestone, err := issues_model.GetMilestoneByRepoID(ctx, opts.Issue.RepoID, opts.Issue.MilestoneID)
if err != nil && !IsErrMilestoneNotExist(err) { if err != nil && !issues_model.IsErrMilestoneNotExist(err) {
return fmt.Errorf("getMilestoneByID: %v", err) return fmt.Errorf("getMilestoneByID: %v", err)
} }
@ -968,7 +965,7 @@ func newIssue(ctx context.Context, doer *user_model.User, opts NewIssueOptions)
} }
if opts.Issue.MilestoneID > 0 { if opts.Issue.MilestoneID > 0 {
if err := updateMilestoneCounters(ctx, opts.Issue.MilestoneID); err != nil { if err := issues_model.UpdateMilestoneCounters(ctx, opts.Issue.MilestoneID); err != nil {
return err return err
} }
@ -980,7 +977,7 @@ func newIssue(ctx context.Context, doer *user_model.User, opts NewIssueOptions)
OldMilestoneID: 0, OldMilestoneID: 0,
MilestoneID: opts.Issue.MilestoneID, MilestoneID: opts.Issue.MilestoneID,
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return err return err
} }
} }
@ -1270,7 +1267,7 @@ func sortIssuesSession(sess *xorm.Session, sortType string, priorityRepoID int64
} }
} }
func (opts *IssuesOptions) setupSession(sess *xorm.Session) { func (opts *IssuesOptions) setupSessionWithLimit(sess *xorm.Session) {
if opts.Page >= 0 && opts.PageSize > 0 { if opts.Page >= 0 && opts.PageSize > 0 {
var start int var start int
if opts.Page == 0 { if opts.Page == 0 {
@ -1280,7 +1277,10 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
} }
sess.Limit(opts.PageSize, start) sess.Limit(opts.PageSize, start)
} }
opts.setupSessionNoLimit(sess)
}
func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
if len(opts.IssueIDs) > 0 { if len(opts.IssueIDs) > 0 {
sess.In("issue.id", opts.IssueIDs) sess.In("issue.id", opts.IssueIDs)
} }
@ -1446,7 +1446,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
opts.setupSession(sess) opts.setupSessionNoLimit(sess)
countsSlice := make([]*struct { countsSlice := make([]*struct {
RepoID int64 RepoID int64
@ -1456,7 +1456,7 @@ func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
Select("issue.repo_id AS repo_id, COUNT(*) AS count"). Select("issue.repo_id AS repo_id, COUNT(*) AS count").
Table("issue"). Table("issue").
Find(&countsSlice); err != nil { Find(&countsSlice); err != nil {
return nil, err return nil, fmt.Errorf("unable to CountIssuesByRepo: %w", err)
} }
countMap := make(map[int64]int64, len(countsSlice)) countMap := make(map[int64]int64, len(countsSlice))
@ -1473,14 +1473,14 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]i
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
opts.setupSession(sess) opts.setupSessionNoLimit(sess)
accessCond := accessibleRepositoryCondition(user) accessCond := accessibleRepositoryCondition(user)
if err := sess.Where(accessCond). if err := sess.Where(accessCond).
Distinct("issue.repo_id"). Distinct("issue.repo_id").
Table("issue"). Table("issue").
Find(&repoIDs); err != nil { Find(&repoIDs); err != nil {
return nil, err return nil, fmt.Errorf("unable to GetRepoIDsForIssuesOptions: %w", err)
} }
return repoIDs, nil return repoIDs, nil
@ -1491,17 +1491,16 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
e := db.GetEngine(db.DefaultContext) e := db.GetEngine(db.DefaultContext)
sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") sess := e.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
opts.setupSession(sess) opts.setupSessionWithLimit(sess)
sortIssuesSession(sess, opts.SortType, opts.PriorityRepoID) sortIssuesSession(sess, opts.SortType, opts.PriorityRepoID)
issues := make([]*Issue, 0, opts.ListOptions.PageSize) issues := make([]*Issue, 0, opts.ListOptions.PageSize)
if err := sess.Find(&issues); err != nil { if err := sess.Find(&issues); err != nil {
return nil, fmt.Errorf("Find: %v", err) return nil, fmt.Errorf("unable to query Issues: %w", err)
} }
sess.Close()
if err := IssueList(issues).LoadAttributes(); err != nil { if err := IssueList(issues).LoadAttributes(); err != nil {
return nil, fmt.Errorf("LoadAttributes: %v", err) return nil, fmt.Errorf("unable to LoadAttributes for Issues: %w", err)
} }
return issues, nil return issues, nil
@ -1512,18 +1511,17 @@ func CountIssues(opts *IssuesOptions) (int64, error) {
e := db.GetEngine(db.DefaultContext) e := db.GetEngine(db.DefaultContext)
countsSlice := make([]*struct { countsSlice := make([]*struct {
RepoID int64 Count int64
Count int64
}, 0, 1) }, 0, 1)
sess := e.Select("COUNT(issue.id) AS count").Table("issue") sess := e.Select("COUNT(issue.id) AS count").Table("issue")
sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id") sess.Join("INNER", "repository", "`issue`.repo_id = `repository`.id")
opts.setupSession(sess) opts.setupSessionNoLimit(sess)
if err := sess.Find(&countsSlice); err != nil { if err := sess.Find(&countsSlice); err != nil {
return 0, fmt.Errorf("Find: %v", err) return 0, fmt.Errorf("unable to CountIssues: %w", err)
} }
if len(countsSlice) < 1 { if len(countsSlice) != 1 {
return 0, fmt.Errorf("there is less than one result sql record") return 0, fmt.Errorf("unable to get one row result when CountIssues, row count=%d", len(countsSlice))
} }
return countsSlice[0].Count, nil return countsSlice[0].Count, nil
} }
@ -1955,7 +1953,7 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment
defer committer.Close() defer committer.Close()
sess := db.GetEngine(ctx) sess := db.GetEngine(ctx)
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return nil, false, fmt.Errorf("loadRepo: %v", err) return nil, false, fmt.Errorf("loadRepo: %v", err)
} }
@ -1982,7 +1980,7 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment
OldTitle: currentIssue.Title, OldTitle: currentIssue.Title,
NewTitle: issue.Title, NewTitle: issue.Title,
} }
_, err := createComment(ctx, opts) _, err := CreateCommentCtx(ctx, opts)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("createComment: %v", err) return nil, false, fmt.Errorf("createComment: %v", err)
} }
@ -2014,7 +2012,7 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us
defer committer.Close() defer committer.Close()
// Update the deadline // Update the deadline
if err = updateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil { if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil {
return err return err
} }
@ -2103,14 +2101,14 @@ func deleteIssue(ctx context.Context, issue *Issue) error {
// delete all database data still assigned to this issue // delete all database data still assigned to this issue
if err := deleteInIssue(e, issue.ID, if err := deleteInIssue(e, issue.ID,
&issues.ContentHistory{}, &issues_model.ContentHistory{},
&Comment{}, &Comment{},
&IssueLabel{}, &IssueLabel{},
&IssueDependency{}, &IssueDependency{},
&IssueAssignees{}, &IssueAssignees{},
&IssueUser{}, &IssueUser{},
&Notification{}, &Notification{},
&issues.Reaction{}, &issues_model.Reaction{},
&IssueWatch{}, &IssueWatch{},
&Stopwatch{}, &Stopwatch{},
&TrackedTime{}, &TrackedTime{},
@ -2241,7 +2239,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
if len(mentions) == 0 { if len(mentions) == 0 {
return return
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return return
} }
@ -2400,7 +2398,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
// Delete content histories // Delete content histories
if _, err = sess.In("issue_id", deleteCond). if _, err = sess.In("issue_id", deleteCond).
Delete(&issues.ContentHistory{}); err != nil { Delete(&issues_model.ContentHistory{}); err != nil {
return return
} }
@ -2428,7 +2426,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
} }
if _, err = sess.In("issue_id", deleteCond). if _, err = sess.In("issue_id", deleteCond).
Delete(&issues.Reaction{}); err != nil { Delete(&issues_model.Reaction{}); err != nil {
return return
} }

View File

@ -120,7 +120,7 @@ func toggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.Use
} }
// Repo infos // Repo infos
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return false, nil, fmt.Errorf("loadRepo: %v", err) return false, nil, fmt.Errorf("loadRepo: %v", err)
} }
@ -133,7 +133,7 @@ func toggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.Use
AssigneeID: assigneeID, AssigneeID: assigneeID,
} }
// Comment // Comment
comment, err = createComment(ctx, opts) comment, err = CreateCommentCtx(ctx, opts)
if err != nil { if err != nil {
return false, nil, fmt.Errorf("createComment: %v", err) return false, nil, fmt.Errorf("createComment: %v", err)
} }

View File

@ -15,7 +15,7 @@ import (
"unicode/utf8" "unicode/utf8"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -209,8 +209,8 @@ type Comment struct {
Project *project_model.Project `xorm:"-"` Project *project_model.Project `xorm:"-"`
OldMilestoneID int64 OldMilestoneID int64
MilestoneID int64 MilestoneID int64
OldMilestone *Milestone `xorm:"-"` OldMilestone *issues_model.Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"` Milestone *issues_model.Milestone `xorm:"-"`
TimeID int64 TimeID int64
Time *TrackedTime `xorm:"-"` Time *TrackedTime `xorm:"-"`
AssigneeID int64 AssigneeID int64
@ -243,8 +243,8 @@ type Comment struct {
// Reference issue in commit message // Reference issue in commit message
CommitSHA string `xorm:"VARCHAR(40)"` CommitSHA string `xorm:"VARCHAR(40)"`
Attachments []*repo_model.Attachment `xorm:"-"` Attachments []*repo_model.Attachment `xorm:"-"`
Reactions issues.ReactionList `xorm:"-"` Reactions issues_model.ReactionList `xorm:"-"`
// For view issue page. // For view issue page.
ShowRole RoleDescriptor `xorm:"-"` ShowRole RoleDescriptor `xorm:"-"`
@ -358,7 +358,7 @@ func (c *Comment) HTMLURL() string {
log.Error("LoadIssue(%d): %v", c.IssueID, err) log.Error("LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
err = c.Issue.loadRepo(db.DefaultContext) err = c.Issue.LoadRepo(db.DefaultContext)
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
return "" return ""
@ -387,7 +387,7 @@ func (c *Comment) APIURL() string {
log.Error("LoadIssue(%d): %v", c.IssueID, err) log.Error("LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
err = c.Issue.loadRepo(db.DefaultContext) err = c.Issue.LoadRepo(db.DefaultContext)
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
return "" return ""
@ -408,7 +408,7 @@ func (c *Comment) IssueURL() string {
return "" return ""
} }
err = c.Issue.loadRepo(db.DefaultContext) err = c.Issue.LoadRepo(db.DefaultContext)
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
return "" return ""
@ -424,7 +424,7 @@ func (c *Comment) PRURL() string {
return "" return ""
} }
err = c.Issue.loadRepo(db.DefaultContext) err = c.Issue.LoadRepo(db.DefaultContext)
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
return "" return ""
@ -495,7 +495,7 @@ func (c *Comment) LoadProject() error {
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone // LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func (c *Comment) LoadMilestone() error { func (c *Comment) LoadMilestone() error {
if c.OldMilestoneID > 0 { if c.OldMilestoneID > 0 {
var oldMilestone Milestone var oldMilestone issues_model.Milestone
has, err := db.GetEngine(db.DefaultContext).ID(c.OldMilestoneID).Get(&oldMilestone) has, err := db.GetEngine(db.DefaultContext).ID(c.OldMilestoneID).Get(&oldMilestone)
if err != nil { if err != nil {
return err return err
@ -505,7 +505,7 @@ func (c *Comment) LoadMilestone() error {
} }
if c.MilestoneID > 0 { if c.MilestoneID > 0 {
var milestone Milestone var milestone issues_model.Milestone
has, err := db.GetEngine(db.DefaultContext).ID(c.MilestoneID).Get(&milestone) has, err := db.GetEngine(db.DefaultContext).ID(c.MilestoneID).Get(&milestone)
if err != nil { if err != nil {
return err return err
@ -574,7 +574,7 @@ func (c *Comment) LoadAssigneeUserAndTeam() error {
return err return err
} }
if err = c.Issue.LoadRepo(); err != nil { if err = c.Issue.LoadRepo(db.DefaultContext); err != nil {
return err return err
} }
@ -635,7 +635,7 @@ func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository
if c.Reactions != nil { if c.Reactions != nil {
return nil return nil
} }
c.Reactions, _, err = issues.FindReactions(ctx, issues.FindReactionsOptions{ c.Reactions, _, err = issues_model.FindReactions(ctx, issues_model.FindReactionsOptions{
IssueID: c.IssueID, IssueID: c.IssueID,
CommentID: c.ID, CommentID: c.ID,
}) })
@ -717,7 +717,7 @@ func (c *Comment) CodeCommentURL() string {
log.Error("LoadIssue(%d): %v", c.IssueID, err) log.Error("LoadIssue(%d): %v", c.IssueID, err)
return "" return ""
} }
err = c.Issue.loadRepo(db.DefaultContext) err = c.Issue.LoadRepo(db.DefaultContext)
if err != nil { // Silently dropping errors :unamused: if err != nil { // Silently dropping errors :unamused:
log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
return "" return ""
@ -761,7 +761,8 @@ func (c *Comment) LoadPushCommits(ctx context.Context) (err error) {
return err return err
} }
func createComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, err error) { // CreateCommentCtx creates comment with context
func CreateCommentCtx(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, err error) {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
var LabelID int64 var LabelID int64
if opts.Label != nil { if opts.Label != nil {
@ -863,7 +864,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
} }
} }
// update the issue's updated_unix column // update the issue's updated_unix column
return updateIssueCols(ctx, opts.Issue, "updated_unix") return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
} }
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) { func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
@ -884,7 +885,7 @@ func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Is
content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02") content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02")
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return nil, err return nil, err
} }
@ -895,7 +896,7 @@ func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Is
Issue: issue, Issue: issue,
Content: content, Content: content,
} }
comment, err := createComment(ctx, opts) comment, err := CreateCommentCtx(ctx, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -908,7 +909,7 @@ func createIssueDependencyComment(ctx context.Context, doer *user_model.User, is
if !add { if !add {
cType = CommentTypeRemoveDependency cType = CommentTypeRemoveDependency
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return return
} }
@ -920,7 +921,7 @@ func createIssueDependencyComment(ctx context.Context, doer *user_model.User, is
Issue: issue, Issue: issue,
DependentIssueID: dependentIssue.ID, DependentIssueID: dependentIssue.ID,
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return return
} }
@ -931,7 +932,7 @@ func createIssueDependencyComment(ctx context.Context, doer *user_model.User, is
Issue: dependentIssue, Issue: dependentIssue,
DependentIssueID: issue.ID, DependentIssueID: issue.ID,
} }
_, err = createComment(ctx, opts) _, err = CreateCommentCtx(ctx, opts)
return return
} }
@ -981,7 +982,7 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
} }
defer committer.Close() defer committer.Close()
comment, err = createComment(ctx, opts) comment, err = CreateCommentCtx(ctx, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1159,7 +1160,7 @@ func deleteComment(ctx context.Context, comment *Comment) error {
return err return err
} }
if _, err := e.Delete(&issues.ContentHistory{ if _, err := e.Delete(&issues_model.ContentHistory{
CommentID: comment.ID, CommentID: comment.ID,
}); err != nil { }); err != nil {
return err return err
@ -1178,7 +1179,7 @@ func deleteComment(ctx context.Context, comment *Comment) error {
return err return err
} }
return issues.DeleteReaction(ctx, &issues.ReactionOptions{CommentID: comment.ID}) return issues_model.DeleteReaction(ctx, &issues_model.ReactionOptions{CommentID: comment.ID})
} }
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS // CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS
@ -1230,7 +1231,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
return nil, err return nil, err
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return nil, err return nil, err
} }

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
@ -139,7 +140,7 @@ func (comments CommentList) loadMilestones(e db.Engine) error {
return nil return nil
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*issues_model.Milestone, len(milestoneIDs))
left := len(milestoneIDs) left := len(milestoneIDs)
for left > 0 { for left > 0 {
limit := defaultMaxInSize limit := defaultMaxInSize
@ -182,7 +183,7 @@ func (comments CommentList) loadOldMilestones(e db.Engine) error {
return nil return nil
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*issues_model.Milestone, len(milestoneIDs))
left := len(milestoneIDs) left := len(milestoneIDs)
for left > 0 { for left > 0 {
limit := defaultMaxInSize limit := defaultMaxInSize
@ -387,7 +388,7 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
if comment.DependentIssue == nil { if comment.DependentIssue == nil {
comment.DependentIssue = issues[comment.DependentIssueID] comment.DependentIssue = issues[comment.DependentIssueID]
if comment.DependentIssue != nil { if comment.DependentIssue != nil {
if err := comment.DependentIssue.loadRepo(ctx); err != nil { if err := comment.DependentIssue.LoadRepo(ctx); err != nil {
return err return err
} }
} }

View File

@ -586,7 +586,7 @@ func newIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m
return err return err
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return return
} }
@ -598,7 +598,7 @@ func newIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m
Label: label, Label: label,
Content: "1", Content: "1",
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return err return err
} }
@ -618,7 +618,7 @@ func NewIssueLabel(issue *Issue, label *Label, doer *user_model.User) (err error
defer committer.Close() defer committer.Close()
sess := db.GetEngine(ctx) sess := db.GetEngine(ctx)
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return err return err
} }
@ -642,7 +642,7 @@ func NewIssueLabel(issue *Issue, label *Label, doer *user_model.User) (err error
// newIssueLabels add labels to an issue. It will check if the labels are valid for the issue // newIssueLabels add labels to an issue. It will check if the labels are valid for the issue
func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) { func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return err return err
} }
for _, label := range labels { for _, label := range labels {
@ -691,7 +691,7 @@ func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use
return nil return nil
} }
if err = issue.loadRepo(ctx); err != nil { if err = issue.LoadRepo(ctx); err != nil {
return return
} }
@ -702,7 +702,7 @@ func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use
Issue: issue, Issue: issue,
Label: label, Label: label,
} }
if _, err = createComment(ctx, opts); err != nil { if _, err = CreateCommentCtx(ctx, opts); err != nil {
return err return err
} }

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
@ -199,7 +200,7 @@ func (issues IssueList) loadMilestones(e db.Engine) error {
return nil return nil
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*issues_model.Milestone, len(milestoneIDs))
left := len(milestoneIDs) left := len(milestoneIDs)
for left > 0 { for left > 0 {
limit := defaultMaxInSize limit := defaultMaxInSize

View File

@ -46,7 +46,7 @@ func updateIssueLock(opts *IssueLockOptions, lock bool) error {
} }
defer committer.Close() defer committer.Close()
if err := updateIssueCols(ctx, opts.Issue, "is_locked"); err != nil { if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); err != nil {
return err return err
} }
@ -57,7 +57,7 @@ func updateIssueLock(opts *IssueLockOptions, lock bool) error {
Type: commentType, Type: commentType,
Content: opts.Reason, Content: opts.Reason,
} }
if _, err := createComment(ctx, opt); err != nil { if _, err := CreateCommentCtx(ctx, opt); err != nil {
return err return err
} }

View File

@ -129,12 +129,12 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
return err return err
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} }
if oldProjectID > 0 || newProjectID > 0 { if oldProjectID > 0 || newProjectID > 0 {
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Type: CommentTypeProject, Type: CommentTypeProject,
Doer: doer, Doer: doer,
Repo: issue.Repo, Repo: issue.Repo,

View File

@ -157,11 +157,11 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
return err return err
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} }
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Doer: user, Doer: user,
Issue: issue, Issue: issue,
Repo: issue.Repo, Repo: issue.Repo,
@ -178,7 +178,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
// CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error // CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error { func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} }
@ -208,11 +208,11 @@ func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
return err return err
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} }
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Doer: user, Doer: user,
Issue: issue, Issue: issue,
Repo: issue.Repo, Repo: issue.Repo,
@ -249,11 +249,11 @@ func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) e
return err return err
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} }
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Doer: user, Doer: user,
Issue: issue, Issue: issue,
Repo: issue.Repo, Repo: issue.Repo,

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference" "code.gitea.io/gitea/models/foreignreference"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -132,7 +133,7 @@ func TestUpdateIssueCols(t *testing.T) {
issue.Content = "This should have no effect" issue.Content = "This should have no effect"
now := time.Now().Unix() now := time.Now().Unix()
assert.NoError(t, updateIssueCols(db.DefaultContext, issue, "name")) assert.NoError(t, UpdateIssueCols(db.DefaultContext, issue, "name"))
then := time.Now().Unix() then := time.Now().Unix()
updatedIssue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue) updatedIssue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: issue.ID}).(*Issue)
@ -568,3 +569,23 @@ func TestIssueForeignReference(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, found.Index, issue.Index) assert.EqualValues(t, found.Index, issue.Index)
} }
func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
miles := issues_model.MilestoneList{
unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone),
}
assert.NoError(t, miles.LoadTotalTrackedTimes())
assert.Equal(t, int64(3682), miles[0].TotalTrackedTime)
}
func TestLoadTotalTrackedTime(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
assert.NoError(t, milestone.LoadTotalTrackedTime())
assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
}

View File

@ -53,7 +53,7 @@ func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
if err != nil { if err != nil {
return return
} }
err = t.Issue.loadRepo(ctx) err = t.Issue.LoadRepo(ctx)
if err != nil { if err != nil {
return return
} }
@ -170,11 +170,11 @@ func AddTime(user *user_model.User, issue *Issue, amount int64, created time.Tim
return nil, err return nil, err
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return nil, err return nil, err
} }
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Issue: issue, Issue: issue,
Repo: issue.Repo, Repo: issue.Repo,
Doer: user, Doer: user,
@ -254,10 +254,10 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error {
return ErrNotExist{} return ErrNotExist{}
} }
if err := issue.loadRepo(ctx); err != nil { if err := issue.LoadRepo(ctx); err != nil {
return err return err
} }
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Issue: issue, Issue: issue,
Repo: issue.Repo, Repo: issue.Repo,
Doer: user, Doer: user,
@ -286,7 +286,7 @@ func DeleteTime(t *TrackedTime) error {
return err return err
} }
if _, err := createComment(ctx, &CreateCommentOptions{ if _, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Issue: t.Issue, Issue: t.Issue,
Repo: t.Issue.Repo, Repo: t.Issue.Repo,
Doer: t.User, Doer: t.User,

View File

@ -129,7 +129,7 @@ func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossRefe
RefAction: xref.Action, RefAction: xref.Action,
RefIsPull: ctx.OrigIssue.IsPull, RefIsPull: ctx.OrigIssue.IsPull,
} }
_, err := createComment(stdCtx, opts) _, err := CreateCommentCtx(stdCtx, opts)
if err != nil { if err != nil {
return err return err
} }
@ -150,7 +150,7 @@ func (issue *Issue) getCrossReferences(stdCtx context.Context, ctx *crossReferen
for _, ref := range allrefs { for _, ref := range allrefs {
if ref.Owner == "" && ref.Name == "" { if ref.Owner == "" && ref.Name == "" {
// Issues in the same repository // Issues in the same repository
if err := ctx.OrigIssue.loadRepo(stdCtx); err != nil { if err := ctx.OrigIssue.LoadRepo(stdCtx); err != nil {
return nil, err return nil, err
} }
refRepo = ctx.OrigIssue.Repo refRepo = ctx.OrigIssue.Repo
@ -204,7 +204,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
if has, _ := e.Get(refIssue); !has { if has, _ := e.Get(refIssue); !has {
return nil, references.XRefActionNone, nil return nil, references.XRefActionNone, nil
} }
if err := refIssue.loadRepo(stdCtx); err != nil { if err := refIssue.LoadRepo(stdCtx); err != nil {
return nil, references.XRefActionNone, err return nil, references.XRefActionNone, err
} }
@ -282,7 +282,7 @@ func (comment *Comment) LoadRefIssue() (err error) {
} }
comment.RefIssue, err = GetIssueByID(comment.RefIssueID) comment.RefIssue, err = GetIssueByID(comment.RefIssueID)
if err == nil { if err == nil {
err = comment.RefIssue.loadRepo(db.DefaultContext) err = comment.RefIssue.LoadRepo(db.DefaultContext)
} }
return return
} }

View File

@ -22,5 +22,6 @@ func TestMain(m *testing.M) {
"reaction.yml", "reaction.yml",
"user.yml", "user.yml",
"repository.yml", "repository.yml",
"milestone.yml",
) )
} }

View File

@ -2,7 +2,7 @@
// 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 issues
import ( import (
"context" "context"
@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -20,6 +19,26 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// ErrMilestoneNotExist represents a "MilestoneNotExist" kind of error.
type ErrMilestoneNotExist struct {
ID int64
RepoID int64
Name string
}
// IsErrMilestoneNotExist checks if an error is a ErrMilestoneNotExist.
func IsErrMilestoneNotExist(err error) bool {
_, ok := err.(ErrMilestoneNotExist)
return ok
}
func (err ErrMilestoneNotExist) Error() string {
if len(err.Name) > 0 {
return fmt.Sprintf("milestone does not exist [name: %s, repo_id: %d]", err.Name, err.RepoID)
}
return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
}
// Milestone represents a milestone of repository. // Milestone represents a milestone of repository.
type Milestone struct { type Milestone struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -105,9 +124,10 @@ func NewMilestone(m *Milestone) (err error) {
return committer.Commit() return committer.Commit()
} }
func getMilestoneByRepoID(e db.Engine, repoID, id int64) (*Milestone, error) { // GetMilestoneByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, error) {
m := new(Milestone) m := new(Milestone)
has, err := e.ID(id).Where("repo_id=?", repoID).Get(m) has, err := db.GetEngine(ctx).ID(id).Where("repo_id=?", repoID).Get(m)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
@ -116,11 +136,6 @@ func getMilestoneByRepoID(e db.Engine, repoID, id int64) (*Milestone, error) {
return m, nil return m, nil
} }
// GetMilestoneByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
return getMilestoneByRepoID(db.GetEngine(db.DefaultContext), repoID, id)
}
// GetMilestoneByRepoIDANDName return a milestone if one exist by name and repo // GetMilestoneByRepoIDANDName return a milestone if one exist by name and repo
func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) { func GetMilestoneByRepoIDANDName(repoID int64, name string) (*Milestone, error) {
var mile Milestone var mile Milestone
@ -166,11 +181,11 @@ func updateMilestone(ctx context.Context, m *Milestone) error {
if err != nil { if err != nil {
return err return err
} }
return updateMilestoneCounters(ctx, m.ID) return UpdateMilestoneCounters(ctx, m.ID)
} }
// updateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness // UpdateMilestoneCounters calculates NumIssues, NumClosesIssues and Completeness
func updateMilestoneCounters(ctx context.Context, id int64) error { func UpdateMilestoneCounters(ctx context.Context, id int64) error {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
_, err := e.ID(id). _, err := e.ID(id).
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
@ -250,65 +265,9 @@ func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) err
return updateRepoMilestoneNum(ctx, m.RepoID) return updateRepoMilestoneNum(ctx, m.RepoID)
} }
func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *Issue, oldMilestoneID int64) error {
if err := updateIssueCols(ctx, issue, "milestone_id"); err != nil {
return err
}
if oldMilestoneID > 0 {
if err := updateMilestoneCounters(ctx, oldMilestoneID); err != nil {
return err
}
}
if issue.MilestoneID > 0 {
if err := updateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
return err
}
}
if oldMilestoneID > 0 || issue.MilestoneID > 0 {
if err := issue.loadRepo(ctx); err != nil {
return err
}
opts := &CreateCommentOptions{
Type: CommentTypeMilestone,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldMilestoneID: oldMilestoneID,
MilestoneID: issue.MilestoneID,
}
if _, err := createComment(ctx, opts); err != nil {
return err
}
}
return nil
}
// ChangeMilestoneAssign changes assignment of milestone for issue.
func ChangeMilestoneAssign(issue *Issue, doer *user_model.User, oldMilestoneID int64) (err error) {
ctx, committer, err := db.TxContext()
if err != nil {
return err
}
defer committer.Close()
if err = changeMilestoneAssign(ctx, doer, issue, oldMilestoneID); err != nil {
return err
}
if err = committer.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
return nil
}
// DeleteMilestoneByRepoID deletes a milestone from a repository. // DeleteMilestoneByRepoID deletes a milestone from a repository.
func DeleteMilestoneByRepoID(repoID, id int64) error { func DeleteMilestoneByRepoID(repoID, id int64) error {
m, err := GetMilestoneByRepoID(repoID, id) m, err := GetMilestoneByRepoID(db.DefaultContext, repoID, id)
if err != nil { if err != nil {
if IsErrMilestoneNotExist(err) { if IsErrMilestoneNotExist(err) {
return nil return nil

View File

@ -2,7 +2,7 @@
// 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 issues
import ( import (
"sort" "sort"
@ -11,10 +11,8 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"xorm.io/builder" "xorm.io/builder"
@ -25,28 +23,15 @@ func TestMilestone_State(t *testing.T) {
assert.Equal(t, api.StateClosed, (&Milestone{IsClosed: true}).State()) assert.Equal(t, api.StateClosed, (&Milestone{IsClosed: true}).State())
} }
func TestNewMilestone(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := &Milestone{
RepoID: 1,
Name: "milestoneName",
Content: "milestoneContent",
}
assert.NoError(t, NewMilestone(milestone))
unittest.AssertExistsAndLoadBean(t, milestone)
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &Milestone{})
}
func TestGetMilestoneByRepoID(t *testing.T) { func TestGetMilestoneByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
milestone, err := GetMilestoneByRepoID(1, 1) milestone, err := GetMilestoneByRepoID(db.DefaultContext, 1, 1)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, milestone.ID) assert.EqualValues(t, 1, milestone.ID)
assert.EqualValues(t, 1, milestone.RepoID) assert.EqualValues(t, 1, milestone.RepoID)
_, err = GetMilestoneByRepoID(unittest.NonexistentID, unittest.NonexistentID) _, err = GetMilestoneByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
assert.True(t, IsErrMilestoneNotExist(err)) assert.True(t, IsErrMilestoneNotExist(err))
} }
@ -160,18 +145,6 @@ func TestGetMilestones(t *testing.T) {
}) })
} }
func TestUpdateMilestone(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
milestone.Name = " newMilestoneName "
milestone.Content = "newMilestoneContent"
assert.NoError(t, UpdateMilestone(milestone, milestone.IsClosed))
milestone = unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.EqualValues(t, "newMilestoneName", milestone.Name)
unittest.CheckConsistencyFor(t, &Milestone{})
}
func TestCountRepoMilestones(t *testing.T) { func TestCountRepoMilestones(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
test := func(repoID int64) { test := func(repoID int64) {
@ -206,78 +179,6 @@ func TestCountRepoClosedMilestones(t *testing.T) {
assert.EqualValues(t, 0, count) assert.EqualValues(t, 0, count)
} }
func TestChangeMilestoneStatus(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.NoError(t, ChangeMilestoneStatus(milestone, true))
unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=1")
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &Milestone{})
assert.NoError(t, ChangeMilestoneStatus(milestone, false))
unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=0")
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &Milestone{})
}
func TestUpdateMilestoneCounters(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1},
"is_closed=0").(*Issue)
issue.IsClosed = true
issue.ClosedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
assert.NoError(t, err)
assert.NoError(t, updateMilestoneCounters(db.DefaultContext, issue.MilestoneID))
unittest.CheckConsistencyFor(t, &Milestone{})
issue.IsClosed = false
issue.ClosedUnix = 0
_, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Cols("is_closed", "closed_unix").Update(issue)
assert.NoError(t, err)
assert.NoError(t, updateMilestoneCounters(db.DefaultContext, issue.MilestoneID))
unittest.CheckConsistencyFor(t, &Milestone{})
}
func TestChangeMilestoneAssign(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue)
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
assert.NotNil(t, issue)
assert.NotNil(t, doer)
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = 2
assert.NoError(t, ChangeMilestoneAssign(issue, doer, oldMilestoneID))
unittest.AssertExistsAndLoadBean(t, &Comment{
IssueID: issue.ID,
Type: CommentTypeMilestone,
MilestoneID: issue.MilestoneID,
OldMilestoneID: oldMilestoneID,
})
unittest.CheckConsistencyFor(t, &Milestone{}, &Issue{})
}
func TestDeleteMilestoneByRepoID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
assert.NoError(t, DeleteMilestoneByRepoID(1, 1))
unittest.AssertNotExistsBean(t, &Milestone{ID: 1})
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: 1})
assert.NoError(t, DeleteMilestoneByRepoID(unittest.NonexistentID, unittest.NonexistentID))
}
func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
miles := MilestoneList{
unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone),
}
assert.NoError(t, miles.LoadTotalTrackedTimes())
assert.Equal(t, int64(3682), miles[0].TotalTrackedTime)
}
func TestCountMilestonesByRepoIDs(t *testing.T) { func TestCountMilestonesByRepoIDs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
milestonesCount := func(repoID int64) (int, int) { milestonesCount := func(repoID int64) (int, int) {
@ -343,15 +244,6 @@ func TestGetMilestonesByRepoIDs(t *testing.T) {
}) })
} }
func TestLoadTotalTrackedTime(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
milestone := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.NoError(t, milestone.LoadTotalTrackedTime())
assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
}
func TestGetMilestonesStats(t *testing.T) { func TestGetMilestonesStats(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -7,6 +7,7 @@ package models
import ( import (
"testing" "testing"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
@ -29,7 +30,7 @@ func TestFixturesAreConsistent(t *testing.T) {
&repo_model.Repository{}, &repo_model.Repository{},
&Issue{}, &Issue{},
&PullRequest{}, &PullRequest{},
&Milestone{}, &issues_model.Milestone{},
&Label{}, &Label{},
&organization.Team{}, &organization.Team{},
&Action{}) &Action{})

View File

@ -8,13 +8,14 @@ import (
"context" "context"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"xorm.io/builder" "xorm.io/builder"
) )
// InsertMilestones creates milestones of repository. // InsertMilestones creates milestones of repository.
func InsertMilestones(ms ...*Milestone) (err error) { func InsertMilestones(ms ...*issues_model.Milestone) (err error) {
if len(ms) == 0 { if len(ms) == 0 {
return nil return nil
} }

View File

@ -8,6 +8,7 @@ import (
"strconv" "strconv"
"testing" "testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference" "code.gitea.io/gitea/models/foreignreference"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -22,7 +23,7 @@ func TestMigrate_InsertMilestones(t *testing.T) {
reponame := "repo1" reponame := "repo1"
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
name := "milestonetest1" name := "milestonetest1"
ms := &Milestone{ ms := &issues_model.Milestone{
RepoID: repo.ID, RepoID: repo.ID,
Name: name, Name: name,
} }
@ -32,7 +33,7 @@ func TestMigrate_InsertMilestones(t *testing.T) {
repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository) repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones) assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones)
unittest.CheckConsistencyFor(t, &Milestone{}) unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
} }
func assertCreateIssues(t *testing.T, isPull bool) { func assertCreateIssues(t *testing.T, isPull bool) {
@ -41,7 +42,7 @@ func assertCreateIssues(t *testing.T, isPull bool) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label)
milestone := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
assert.EqualValues(t, milestone.ID, 1) assert.EqualValues(t, milestone.ID, 1)
reaction := &issues_model.Reaction{ reaction := &issues_model.Reaction{
Type: "heart", Type: "heart",
@ -90,7 +91,7 @@ func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
func TestMigrate_InsertIssueComments(t *testing.T) { func TestMigrate_InsertIssueComments(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) issue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
_ = issue.LoadRepo() _ = issue.LoadRepo(db.DefaultContext)
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
reaction := &issues_model.Reaction{ reaction := &issues_model.Reaction{
Type: "heart", Type: "heart",

View File

@ -255,7 +255,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
} }
} }
err = issue.loadRepo(ctx) err = issue.LoadRepo(ctx)
if err != nil { if err != nil {
return err return err
} }

View File

@ -65,14 +65,14 @@ func findPropertyValues(ctx context.Context, propertyName string, ownerID int64,
in2 := builder. in2 := builder.
Select("package_file.id"). Select("package_file.id").
From("package_file"). From("package_file").
Join("INNER", "package_version", "package_version.id = package_file.version_id"). InnerJoin("package_version", "package_version.id = package_file.version_id").
Join("INNER", "package", "package.id = package_version.package_id"). InnerJoin("package", "package.id = package_version.package_id").
Where(cond) Where(cond)
query := builder. query := builder.
Select("package_property.value, MAX(package_file.created_unix) AS created_unix"). Select("package_property.value, MAX(package_file.created_unix) AS created_unix").
From("package_property"). From("package_property").
Join("INNER", "package_file", "package_file.id = package_property.ref_id"). InnerJoin("package_file", "package_file.id = package_property.ref_id").
Where(builder.Eq{"package_property.name": propertyName}.And(builder.In("package_property.ref_id", in2))). Where(builder.Eq{"package_property.name": propertyName}.And(builder.In("package_property.ref_id", in2))).
GroupBy("package_property.value"). GroupBy("package_property.value").
OrderBy("created_unix DESC") OrderBy("created_unix DESC")

View File

@ -74,8 +74,8 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
query := builder. query := builder.
Select("package.name, package_version.version, package_file.id"). Select("package.name, package_version.version, package_file.id").
From("package_file"). From("package_file").
Join("INNER", "package_version", "package_version.id = package_file.version_id"). InnerJoin("package_version", "package_version.id = package_file.version_id").
Join("INNER", "package", "package.id = package_version.package_id"). InnerJoin("package", "package.id = package_version.package_id").
Where(cond) Where(cond)
results := make([]struct { results := make([]struct {

View File

@ -190,13 +190,15 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]
// DeletePackagesIfUnreferenced deletes a package if there are no associated versions // DeletePackagesIfUnreferenced deletes a package if there are no associated versions
func DeletePackagesIfUnreferenced(ctx context.Context) error { func DeletePackagesIfUnreferenced(ctx context.Context) error {
in := builder. in := builder.
Select("package_version.package_id"). Select("package.id").
From("package"). From("package").
Join("LEFT", "package_version", "package_version.package_id = package.id"). LeftJoin("package_version", "package_version.package_id = package.id").
Where(builder.Expr("package_version.id IS NULL")) Where(builder.Expr("package_version.id IS NULL"))
_, err := db.GetEngine(ctx). _, err := db.GetEngine(ctx).
Where(builder.In("package.id", in)). // double select workaround for MySQL
// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
Delete(&Package{}) Delete(&Package{})
return err return err

View File

@ -67,7 +67,7 @@ func FindExpiredUnreferencedBlobs(ctx context.Context, olderThan time.Duration)
pbs := make([]*PackageBlob, 0, 10) pbs := make([]*PackageBlob, 0, 10)
return pbs, db.GetEngine(ctx). return pbs, db.GetEngine(ctx).
Table("package_blob"). Table("package_blob").
Join("LEFT OUTER", "package_file", "package_file.blob_id = package_blob.id"). Join("LEFT", "package_file", "package_file.blob_id = package_blob.id").
Where("package_file.id IS NULL AND package_blob.created_unix < ?", time.Now().Add(-olderThan).Unix()). Where("package_file.id IS NULL AND package_blob.created_unix < ?", time.Now().Add(-olderThan).Unix()).
Find(&pbs) Find(&pbs)
} }

View File

@ -147,7 +147,7 @@ func (opts *PackageFileSearchOptions) toConds() builder.Cond {
in := builder. in := builder.
Select("package_version.id"). Select("package_version.id").
From("package_version"). From("package_version").
Join("INNER", "package", "package.id = package_version.package_id"). InnerJoin("package", "package.id = package_version.package_id").
Where(versionCond) Where(versionCond)
cond = cond.And(builder.In("package_file.version_id", in)) cond = cond.And(builder.In("package_file.version_id", in))

View File

@ -12,16 +12,13 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
) )
var ( // ErrDuplicatePackageVersion indicates a duplicated package version error
// ErrDuplicatePackageVersion indicates a duplicated package version error var ErrDuplicatePackageVersion = errors.New("Package version already exists")
ErrDuplicatePackageVersion = errors.New("Package version does exist already")
// ErrPackageVersionNotExist indicates a package version not exist error
ErrPackageVersionNotExist = errors.New("Package version does not exist")
)
func init() { func init() {
db.RegisterModel(new(PackageVersion)) db.RegisterModel(new(PackageVersion))
@ -99,75 +96,49 @@ func GetInternalVersionByNameAndVersion(ctx context.Context, ownerID int64, pack
} }
func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string, isInternal bool) (*PackageVersion, error) { func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string, isInternal bool) (*PackageVersion, error) {
var cond builder.Cond = builder.Eq{ pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
"package.owner_id": ownerID, OwnerID: ownerID,
"package.type": packageType, Type: packageType,
"package.lower_name": strings.ToLower(name), Name: SearchValue{
"package_version.is_internal": isInternal, ExactMatch: true,
} Value: name,
pv := &PackageVersion{ },
LowerVersion: strings.ToLower(version), Version: SearchValue{
} ExactMatch: true,
has, err := db.GetEngine(ctx). Value: version,
Join("INNER", "package", "package.id = package_version.package_id"). },
Where(cond). IsInternal: isInternal,
Get(pv) Paginator: db.NewAbsoluteListOptions(0, 1),
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !has { if len(pvs) == 0 {
return nil, ErrPackageNotExist return nil, ErrPackageNotExist
} }
return pvs[0], nil
return pv, nil
} }
// GetVersionsByPackageType gets all versions of a specific type // GetVersionsByPackageType gets all versions of a specific type
func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Type) ([]*PackageVersion, error) { func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Type) ([]*PackageVersion, error) {
var cond builder.Cond = builder.Eq{ pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
"package.owner_id": ownerID, OwnerID: ownerID,
"package.type": packageType, Type: packageType,
"package_version.is_internal": false, })
} return pvs, err
pvs := make([]*PackageVersion, 0, 10)
return pvs, db.GetEngine(ctx).
Where(cond).
Join("INNER", "package", "package.id = package_version.package_id").
Find(&pvs)
} }
// GetVersionsByPackageName gets all versions of a specific package // GetVersionsByPackageName gets all versions of a specific package
func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Type, name string) ([]*PackageVersion, error) { func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Type, name string) ([]*PackageVersion, error) {
var cond builder.Cond = builder.Eq{ pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
"package.owner_id": ownerID, OwnerID: ownerID,
"package.type": packageType, Type: packageType,
"package.lower_name": strings.ToLower(name), Name: SearchValue{
"package_version.is_internal": false, ExactMatch: true,
} Value: name,
},
pvs := make([]*PackageVersion, 0, 10) })
return pvs, db.GetEngine(ctx). return pvs, err
Where(cond).
Join("INNER", "package", "package.id = package_version.package_id").
Find(&pvs)
}
// GetVersionsByFilename gets all versions which are linked to a filename
func GetVersionsByFilename(ctx context.Context, ownerID int64, packageType Type, filename string) ([]*PackageVersion, error) {
var cond builder.Cond = builder.Eq{
"package.owner_id": ownerID,
"package.type": packageType,
"package_file.lower_name": strings.ToLower(filename),
"package_version.is_internal": false,
}
pvs := make([]*PackageVersion, 0, 10)
return pvs, db.GetEngine(ctx).
Where(cond).
Join("INNER", "package_file", "package_file.version_id = package_version.id").
Join("INNER", "package", "package.id = package_version.package_id").
Find(&pvs)
} }
// DeleteVersionByID deletes a version by id // DeleteVersionByID deletes a version by id
@ -183,21 +154,32 @@ func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error
}) })
} }
// SearchValue describes a value to search
// If ExactMatch is true, the field must match the value otherwise a LIKE search is performed.
type SearchValue struct {
Value string
ExactMatch bool
}
// PackageSearchOptions are options for SearchXXX methods // PackageSearchOptions are options for SearchXXX methods
// Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0)
type PackageSearchOptions struct { type PackageSearchOptions struct {
OwnerID int64 OwnerID int64
RepoID int64 RepoID int64
Type string Type Type
PackageID int64 PackageID int64
QueryName string Name SearchValue // only results with the specific name are found
QueryVersion string Version SearchValue // only results with the specific version are found
Properties map[string]string Properties map[string]string // only results are found which contain all listed version properties with the specific value
Sort string IsInternal bool
HasFileWithName string // only results are found which are associated with a file with the specific name
HasFiles util.OptionalBool // only results are found which have associated files
Sort string
db.Paginator db.Paginator
} }
func (opts *PackageSearchOptions) toConds() builder.Cond { func (opts *PackageSearchOptions) toConds() builder.Cond {
var cond builder.Cond = builder.Eq{"package_version.is_internal": false} var cond builder.Cond = builder.Eq{"package_version.is_internal": opts.IsInternal}
if opts.OwnerID != 0 { if opts.OwnerID != 0 {
cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID}) cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID})
@ -211,11 +193,19 @@ func (opts *PackageSearchOptions) toConds() builder.Cond {
if opts.PackageID != 0 { if opts.PackageID != 0 {
cond = cond.And(builder.Eq{"package.id": opts.PackageID}) cond = cond.And(builder.Eq{"package.id": opts.PackageID})
} }
if opts.QueryName != "" { if opts.Name.Value != "" {
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.QueryName)}) if opts.Name.ExactMatch {
cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
} else {
cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
}
} }
if opts.QueryVersion != "" { if opts.Version.Value != "" {
cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.QueryVersion)}) if opts.Version.ExactMatch {
cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Version.Value)})
} else {
cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.Version.Value)})
}
} }
if len(opts.Properties) != 0 { if len(opts.Properties) != 0 {
@ -238,6 +228,22 @@ func (opts *PackageSearchOptions) toConds() builder.Cond {
}) })
} }
if opts.HasFileWithName != "" {
fileCond := builder.Expr("package_file.version_id = package_version.id").And(builder.Eq{"package_file.lower_name": strings.ToLower(opts.HasFileWithName)})
cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond)))
}
if !opts.HasFiles.IsNone() {
var filesCond builder.Cond = builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
if opts.HasFiles.IsFalse() {
filesCond = builder.Not{filesCond}
}
cond = cond.And(filesCond)
}
return cond return cond
} }
@ -297,20 +303,3 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
count, err := sess.FindAndCount(&pvs) count, err := sess.FindAndCount(&pvs)
return pvs, count, err return pvs, count, err
} }
// FindVersionsByPropertyNameAndValue gets all package versions which are associated with a specific property + value
func FindVersionsByPropertyNameAndValue(ctx context.Context, packageID int64, name, value string) ([]*PackageVersion, error) {
var cond builder.Cond = builder.Eq{
"package_property.ref_type": PropertyTypeVersion,
"package_property.name": name,
"package_property.value": value,
"package_version.package_id": packageID,
"package_version.is_internal": false,
}
pvs := make([]*PackageVersion, 0, 5)
return pvs, db.GetEngine(ctx).
Where(cond).
Join("INNER", "package_property", "package_property.ref_id = package_version.id").
Find(&pvs)
}

View File

@ -407,7 +407,7 @@ func (pr *PullRequest) SetMerged() (bool, error) {
return false, fmt.Errorf("PullRequest[%d] already closed", pr.Index) return false, fmt.Errorf("PullRequest[%d] already closed", pr.Index)
} }
if err := pr.Issue.loadRepo(ctx); err != nil { if err := pr.Issue.LoadRepo(ctx); err != nil {
return false, err return false, err
} }

View File

@ -19,6 +19,7 @@ import (
admin_model "code.gitea.io/gitea/models/admin" admin_model "code.gitea.io/gitea/models/admin"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
@ -698,7 +699,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
&webhook.HookTask{RepoID: repoID}, &webhook.HookTask{RepoID: repoID},
&LFSLock{RepoID: repoID}, &LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID}, &repo_model.LanguageStat{RepoID: repoID},
&Milestone{RepoID: repoID}, &issues_model.Milestone{RepoID: repoID},
&repo_model.Mirror{RepoID: repoID}, &repo_model.Mirror{RepoID: repoID},
&Notification{RepoID: repoID}, &Notification{RepoID: repoID},
&ProtectedBranch{RepoID: repoID}, &ProtectedBranch{RepoID: repoID},
@ -945,10 +946,6 @@ func labelStatsCorrectNumClosedIssuesRepo(ctx context.Context, id int64) error {
var milestoneStatsQueryNumIssues = "SELECT `milestone`.id FROM `milestone` WHERE `milestone`.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id AND `issue`.is_closed=?) OR `milestone`.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id)" var milestoneStatsQueryNumIssues = "SELECT `milestone`.id FROM `milestone` WHERE `milestone`.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id AND `issue`.is_closed=?) OR `milestone`.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id)"
func milestoneStatsCorrectNumIssues(ctx context.Context, id int64) error {
return updateMilestoneCounters(ctx, id)
}
func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error { func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id) results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id)
@ -957,7 +954,7 @@ func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
} }
for _, result := range results { for _, result := range results {
id, _ := strconv.ParseInt(string(result["id"]), 10, 64) id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
err = milestoneStatsCorrectNumIssues(ctx, id) err = issues_model.UpdateMilestoneCounters(ctx, id)
if err != nil { if err != nil {
return err return err
} }
@ -1049,7 +1046,7 @@ func CheckRepoStats(ctx context.Context) error {
// Milestone.Num{,Closed}Issues // Milestone.Num{,Closed}Issues
{ {
statsQuery(milestoneStatsQueryNumIssues, true), statsQuery(milestoneStatsQueryNumIssues, true),
milestoneStatsCorrectNumIssues, issues_model.UpdateMilestoneCounters,
"milestone count 'num_closed_issues' and 'num_issues'", "milestone count 'num_closed_issues' and 'num_issues'",
}, },
// User.NumRepos // User.NumRepos

View File

@ -427,7 +427,7 @@ func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, co
} }
} }
comm, err := createComment(ctx, &CreateCommentOptions{ comm, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Type: CommentTypeReview, Type: CommentTypeReview,
Doer: doer, Doer: doer,
Content: review.Content, Content: review.Content,
@ -662,7 +662,7 @@ func AddReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Comment,
return nil, err return nil, err
} }
comment, err := createComment(ctx, &CreateCommentOptions{ comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Type: CommentTypeReviewRequest, Type: CommentTypeReviewRequest,
Doer: doer, Doer: doer,
Repo: issue.Repo, Repo: issue.Repo,
@ -717,7 +717,7 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *user_model.User) (*Commen
} }
} }
comment, err := createComment(ctx, &CreateCommentOptions{ comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Type: CommentTypeReviewRequest, Type: CommentTypeReviewRequest,
Doer: doer, Doer: doer,
Repo: issue.Repo, Repo: issue.Repo,
@ -776,7 +776,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
} }
} }
comment, err := createComment(ctx, &CreateCommentOptions{ comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Type: CommentTypeReviewRequest, Type: CommentTypeReviewRequest,
Doer: doer, Doer: doer,
Repo: issue.Repo, Repo: issue.Repo,
@ -786,7 +786,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
ReviewID: review.ID, ReviewID: review.ID,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("createComment(): %v", err) return nil, fmt.Errorf("CreateCommentCtx(): %v", err)
} }
return comment, committer.Commit() return comment, committer.Commit()
@ -837,7 +837,7 @@ func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *us
return nil, committer.Commit() return nil, committer.Commit()
} }
comment, err := createComment(ctx, &CreateCommentOptions{ comment, err := CreateCommentCtx(ctx, &CreateCommentOptions{
Type: CommentTypeReviewRequest, Type: CommentTypeReviewRequest,
Doer: doer, Doer: doer,
Repo: issue.Repo, Repo: issue.Repo,
@ -846,7 +846,7 @@ func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *us
AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("createComment(): %v", err) return nil, fmt.Errorf("CreateCommentCtx(): %v", err)
} }
return comment, committer.Commit() return comment, committer.Commit()
@ -887,7 +887,7 @@ func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool,
} }
if doer.ID != issue.PosterID { if doer.ID != issue.PosterID {
if err = issue.LoadRepo(); err != nil { if err = issue.LoadRepo(db.DefaultContext); err != nil {
return false, err return false, err
} }

View File

@ -8,6 +8,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -102,7 +103,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.Release, _ = e.Count(new(Release)) stats.Counter.Release, _ = e.Count(new(Release))
stats.Counter.AuthSource = auth.CountSources() stats.Counter.AuthSource = auth.CountSources()
stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook)) stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook))
stats.Counter.Milestone, _ = e.Count(new(Milestone)) stats.Counter.Milestone, _ = e.Count(new(issues_model.Milestone))
stats.Counter.Label, _ = e.Count(new(Label)) stats.Counter.Label, _ = e.Count(new(Label))
stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask)) stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
stats.Counter.Team, _ = e.Count(new(organization.Team)) stats.Counter.Team, _ = e.Count(new(organization.Team))

View File

@ -69,6 +69,7 @@ func MainTest(m *testing.M, pathToGiteaRoot string, fixtureFiles ...string) {
setting.SSH.Port = 3000 setting.SSH.Port = 3000
setting.SSH.Domain = "try.gitea.io" setting.SSH.Domain = "try.gitea.io"
setting.Database.UseSQLite3 = true setting.Database.UseSQLite3 = true
setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos") repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
if err != nil { if err != nil {
fatalTestError("TempDir: %v\n", err) fatalTestError("TempDir: %v\n", err)

View File

@ -8,7 +8,6 @@ package context
import ( import (
"context" "context"
"fmt" "fmt"
"html"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -20,8 +19,6 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
auth_service "code.gitea.io/gitea/services/auth" auth_service "code.gitea.io/gitea/services/auth"
"gitea.com/go-chi/session"
) )
// APIContext is a specific context for API service // APIContext is a specific context for API service
@ -191,33 +188,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
} }
} }
// SetTotalCountHeader set "X-Total-Count" header
func (ctx *APIContext) SetTotalCountHeader(total int64) {
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
}
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) {
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
if len(val) != 0 {
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
} else {
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
}
}
// RequireCSRF requires a validated a CSRF token
func (ctx *APIContext) RequireCSRF() {
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
if len(headerToken) > 0 || len(formValueToken) > 0 {
Validate(ctx.Context, ctx.csrf)
} else {
ctx.Context.Error(http.StatusUnauthorized, "Missing CSRF token.")
}
}
// CheckForOTP validates OTP // CheckForOTP validates OTP
func (ctx *APIContext) CheckForOTP() { func (ctx *APIContext) CheckForOTP() {
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) {
@ -269,17 +239,14 @@ func APIAuth(authMethod auth_service.Method) func(*APIContext) {
// APIContexter returns apicontext as middleware // APIContexter returns apicontext as middleware
func APIContexter() func(http.Handler) http.Handler { func APIContexter() func(http.Handler) http.Handler {
csrfOpts := getCsrfOpts()
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
locale := middleware.Locale(w, req) locale := middleware.Locale(w, req)
ctx := APIContext{ ctx := APIContext{
Context: &Context{ Context: &Context{
Resp: NewResponse(w), Resp: NewResponse(w),
Data: map[string]interface{}{}, Data: map[string]interface{}{},
Locale: locale, Locale: locale,
Session: session.GetSession(req),
Repo: &Repository{ Repo: &Repository{
PullRequest: &PullRequest{}, PullRequest: &PullRequest{},
}, },
@ -289,7 +256,6 @@ func APIContexter() func(http.Handler) http.Handler {
} }
ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx) ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
ctx.csrf = Csrfer(csrfOpts, ctx.Context)
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
@ -301,7 +267,6 @@ func APIContexter() func(http.Handler) http.Handler {
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
ctx.Data["Context"] = &ctx ctx.Data["Context"] = &ctx
next.ServeHTTP(ctx.Resp, ctx.Req) next.ServeHTTP(ctx.Resp, ctx.Req)

View File

@ -63,7 +63,7 @@ func Toggle(options *ToggleOptions) func(ctx *Context) {
} }
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" { if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
Validate(ctx, ctx.csrf) ctx.csrf.Validate(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }

View File

@ -10,6 +10,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"html" "html"
"html/template" "html/template"
"io" "io"
@ -21,6 +22,7 @@ import (
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -57,7 +59,7 @@ type Context struct {
Render Render Render Render
translation.Locale translation.Locale
Cache cache.Cache Cache cache.Cache
csrf CSRF csrf CSRFProtector
Flash *middleware.Flash Flash *middleware.Flash
Session session.Store Session session.Store
@ -577,6 +579,22 @@ func (ctx *Context) Value(key interface{}) interface{} {
return ctx.Req.Context().Value(key) return ctx.Req.Context().Value(key)
} }
// SetTotalCountHeader set "X-Total-Count" header
func (ctx *Context) SetTotalCountHeader(total int64) {
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
}
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) {
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
if len(val) != 0 {
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
} else {
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
}
}
// Handler represents a custom handler // Handler represents a custom handler
type Handler func(*Context) type Handler func(*Context)
@ -648,7 +666,9 @@ func Auth(authMethod auth.Method) func(*Context) {
func Contexter() func(next http.Handler) http.Handler { func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer() rnd := templates.HTMLRenderer()
csrfOpts := getCsrfOpts() csrfOpts := getCsrfOpts()
if !setting.IsProd {
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
}
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
locale := middleware.Locale(resp, req) locale := middleware.Locale(resp, req)
@ -679,7 +699,7 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Data["Context"] = &ctx ctx.Data["Context"] = &ctx
ctx.Req = WithContext(req, &ctx) ctx.Req = WithContext(req, &ctx)
ctx.csrf = Csrfer(csrfOpts, &ctx) ctx.csrf = PrepareCSRFProtector(csrfOpts, &ctx)
// Get flash. // Get flash.
flashCookie := ctx.GetCookie("macaron_flash") flashCookie := ctx.GetCookie("macaron_flash")
@ -737,7 +757,7 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken()) ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`) ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
@ -780,3 +800,21 @@ func Contexter() func(next http.Handler) http.Handler {
}) })
} }
} }
// SearchOrderByMap represents all possible search order
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
"asc": {
"alpha": db.SearchOrderByAlphabetically,
"created": db.SearchOrderByOldest,
"updated": db.SearchOrderByLeastUpdated,
"size": db.SearchOrderBySize,
"id": db.SearchOrderByID,
},
"desc": {
"alpha": db.SearchOrderByAlphabeticallyReverse,
"created": db.SearchOrderByNewest,
"updated": db.SearchOrderByRecentUpdated,
"size": db.SearchOrderBySizeReverse,
"id": db.SearchOrderByIDReverse,
},
}

View File

@ -31,29 +31,19 @@ import (
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
) )
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token. // CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
type CSRF interface { type CSRFProtector interface {
// Return HTTP header to search for token. // GetHeaderName returns HTTP header to search for token.
GetHeaderName() string GetHeaderName() string
// Return form value to search for token. // GetFormName returns form value to search for token.
GetFormName() string GetFormName() string
// Return cookie name to search for token. // GetToken returns the token.
GetCookieName() string
// Return cookie path
GetCookiePath() string
// Return the flag value used for the csrf token.
GetCookieHTTPOnly() bool
// Return cookie domain
GetCookieDomain() string
// Return the token.
GetToken() string GetToken() string
// Validate by token. // Validate validates the token in http context.
ValidToken(t string) bool Validate(ctx *Context)
// Error replies to the request with a custom function when ValidToken fails.
Error(w http.ResponseWriter)
} }
type csrf struct { type csrfProtector struct {
// Header name value for setting and getting csrf token. // Header name value for setting and getting csrf token.
Header string Header string
// Form name value for setting and getting csrf token. // Form name value for setting and getting csrf token.
@ -72,56 +62,24 @@ type csrf struct {
ID string ID string
// Secret used along with the unique id above to generate the Token. // Secret used along with the unique id above to generate the Token.
Secret string Secret string
// ErrorFunc is the custom function that replies to the request when ValidToken fails.
ErrorFunc func(w http.ResponseWriter)
} }
// GetHeaderName returns the name of the HTTP header for csrf token. // GetHeaderName returns the name of the HTTP header for csrf token.
func (c *csrf) GetHeaderName() string { func (c *csrfProtector) GetHeaderName() string {
return c.Header return c.Header
} }
// GetFormName returns the name of the form value for csrf token. // GetFormName returns the name of the form value for csrf token.
func (c *csrf) GetFormName() string { func (c *csrfProtector) GetFormName() string {
return c.Form return c.Form
} }
// GetCookieName returns the name of the cookie for csrf token.
func (c *csrf) GetCookieName() string {
return c.Cookie
}
// GetCookiePath returns the path of the cookie for csrf token.
func (c *csrf) GetCookiePath() string {
return c.CookiePath
}
// GetCookieHTTPOnly returns the flag value used for the csrf token.
func (c *csrf) GetCookieHTTPOnly() bool {
return c.CookieHTTPOnly
}
// GetCookieDomain returns the flag value used for the csrf token.
func (c *csrf) GetCookieDomain() string {
return c.CookieDomain
}
// GetToken returns the current token. This is typically used // GetToken returns the current token. This is typically used
// to populate a hidden form in an HTML template. // to populate a hidden form in an HTML template.
func (c *csrf) GetToken() string { func (c *csrfProtector) GetToken() string {
return c.Token return c.Token
} }
// ValidToken validates the passed token against the existing Secret and ID.
func (c *csrf) ValidToken(t string) bool {
return ValidToken(t, c.Secret, c.ID, "POST")
}
// Error replies to the request when ValidToken fails.
func (c *csrf) Error(w http.ResponseWriter) {
c.ErrorFunc(w)
}
// CsrfOptions maintains options to manage behavior of Generate. // CsrfOptions maintains options to manage behavior of Generate.
type CsrfOptions struct { type CsrfOptions struct {
// The global secret value used to generate Tokens. // The global secret value used to generate Tokens.
@ -143,7 +101,7 @@ type CsrfOptions struct {
SessionKey string SessionKey string
// oldSessionKey saves old value corresponding to SessionKey. // oldSessionKey saves old value corresponding to SessionKey.
oldSessionKey string oldSessionKey string
// If true, send token via X-CSRFToken header. // If true, send token via X-Csrf-Token header.
SetHeader bool SetHeader bool
// If true, send token via _csrf cookie. // If true, send token via _csrf cookie.
SetCookie bool SetCookie bool
@ -151,20 +109,12 @@ type CsrfOptions struct {
Secure bool Secure bool
// Disallow Origin appear in request header. // Disallow Origin appear in request header.
Origin bool Origin bool
// The function called when Validate fails. // Cookie lifetime. Default is 0
ErrorFunc func(w http.ResponseWriter)
// Cookie life time. Default is 0
CookieLifeTime int CookieLifeTime int
} }
func prepareOptions(options []CsrfOptions) CsrfOptions { func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions {
var opt CsrfOptions if opt.Secret == "" {
if len(options) > 0 {
opt = options[0]
}
// Defaults.
if len(opt.Secret) == 0 {
randBytes, err := util.CryptoRandomBytes(8) randBytes, err := util.CryptoRandomBytes(8)
if err != nil { if err != nil {
// this panic can be handled by the recover() in http handlers // this panic can be handled by the recover() in http handlers
@ -172,36 +122,30 @@ func prepareOptions(options []CsrfOptions) CsrfOptions {
} }
opt.Secret = base32.StdEncoding.EncodeToString(randBytes) opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
} }
if len(opt.Header) == 0 { if opt.Header == "" {
opt.Header = "X-CSRFToken" opt.Header = "X-Csrf-Token"
} }
if len(opt.Form) == 0 { if opt.Form == "" {
opt.Form = "_csrf" opt.Form = "_csrf"
} }
if len(opt.Cookie) == 0 { if opt.Cookie == "" {
opt.Cookie = "_csrf" opt.Cookie = "_csrf"
} }
if len(opt.CookiePath) == 0 { if opt.CookiePath == "" {
opt.CookiePath = "/" opt.CookiePath = "/"
} }
if len(opt.SessionKey) == 0 { if opt.SessionKey == "" {
opt.SessionKey = "uid" opt.SessionKey = "uid"
} }
opt.oldSessionKey = "_old_" + opt.SessionKey opt.oldSessionKey = "_old_" + opt.SessionKey
if opt.ErrorFunc == nil {
opt.ErrorFunc = func(w http.ResponseWriter) {
http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
}
}
return opt return opt
} }
// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token. // PrepareCSRFProtector returns a CSRFProtector to be used for every request.
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie. // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
func Csrfer(opt CsrfOptions, ctx *Context) CSRF { func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
opt = prepareOptions([]CsrfOptions{opt}) opt = prepareDefaultCsrfOptions(opt)
x := &csrf{ x := &csrfProtector{
Secret: opt.Secret, Secret: opt.Secret,
Header: opt.Header, Header: opt.Header,
Form: opt.Form, Form: opt.Form,
@ -209,7 +153,6 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
CookieDomain: opt.CookieDomain, CookieDomain: opt.CookieDomain,
CookiePath: opt.CookiePath, CookiePath: opt.CookiePath,
CookieHTTPOnly: opt.CookieHTTPOnly, CookieHTTPOnly: opt.CookieHTTPOnly,
ErrorFunc: opt.ErrorFunc,
} }
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 { if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
@ -229,28 +172,31 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
} }
} }
needsNew := false
oldUID := ctx.Session.Get(opt.oldSessionKey) oldUID := ctx.Session.Get(opt.oldSessionKey)
if oldUID == nil || oldUID.(string) != x.ID { uidChanged := oldUID == nil || oldUID.(string) != x.ID
needsNew = true cookieToken := ctx.GetCookie(opt.Cookie)
needsNew := true
if uidChanged {
_ = ctx.Session.Set(opt.oldSessionKey, x.ID) _ = ctx.Session.Set(opt.oldSessionKey, x.ID)
} else { } else if cookieToken != "" {
// If cookie present, map existing token, else generate a new one. // If cookie token presents, re-use existing unexpired token, else generate a new one.
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 { if issueTime, ok := ParseCsrfToken(cookieToken); ok {
// FIXME: test coverage. dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
x.Token = val if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
} else { x.Token = cookieToken
needsNew = true needsNew = false
}
} }
} }
if needsNew { if needsNew {
// FIXME: actionId. // FIXME: actionId.
x.Token = GenerateToken(x.Secret, x.ID, "POST") x.Token = GenerateCsrfToken(x.Secret, x.ID, "POST", time.Now())
if opt.SetCookie { if opt.SetCookie {
var expires interface{} var expires interface{}
if opt.CookieLifeTime == 0 { if opt.CookieLifeTime == 0 {
expires = time.Now().AddDate(0, 0, 1) expires = time.Now().Add(CsrfTokenTimeout)
} }
middleware.SetCookie(ctx.Resp, opt.Cookie, x.Token, middleware.SetCookie(ctx.Resp, opt.Cookie, x.Token,
opt.CookieLifeTime, opt.CookieLifeTime,
@ -270,47 +216,31 @@ func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
return x return x
} }
// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken" func (c *csrfProtector) validateToken(ctx *Context, token string) {
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated if !ValidCsrfToken(token, c.Secret, c.ID, "POST", time.Now()) {
// using ValidToken. If this validation fails, custom Error is sent in the reply. middleware.DeleteCSRFCookie(ctx.Resp)
// If neither a header or form value is found, http.StatusBadRequest is sent. if middleware.IsAPIPath(ctx.Req) {
func Validate(ctx *Context, x CSRF) { // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 { http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
if !x.ValidToken(token) { } else {
// Delete the cookie
middleware.SetCookie(ctx.Resp, x.GetCookieName(), "",
-1,
x.GetCookiePath(),
x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
if middleware.IsAPIPath(ctx.Req) {
x.Error(ctx.Resp)
return
}
ctx.Flash.Error(ctx.Tr("error.invalid_csrf")) ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
ctx.Redirect(setting.AppSubURL + "/") ctx.Redirect(setting.AppSubURL + "/")
} }
return
} }
if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 { }
if !x.ValidToken(token) {
// Delete the cookie // Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
middleware.SetCookie(ctx.Resp, x.GetCookieName(), "", // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
-1, // If this validation fails, custom Error is sent in the reply.
x.GetCookiePath(), // If neither a header nor form value is found, http.StatusBadRequest is sent.
x.GetCookieDomain()) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too? func (c *csrfProtector) Validate(ctx *Context) {
if middleware.IsAPIPath(ctx.Req) { if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" {
x.Error(ctx.Resp) c.validateToken(ctx, token)
return return
} }
ctx.Flash.Error(ctx.Tr("error.invalid_csrf")) if token := ctx.Req.FormValue(c.GetFormName()); token != "" {
ctx.Redirect(setting.AppSubURL + "/") c.validateToken(ctx, token)
} return
return }
} c.validateToken(ctx, "") // no csrf token, use an empty token to respond error
if middleware.IsAPIPath(ctx.Req) {
http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
return
}
ctx.Flash.Error(ctx.Tr("error.missing_csrf"))
ctx.Redirect(setting.AppSubURL + "/")
} }

View File

@ -2,20 +2,16 @@
// 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 utils package context
import ( import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
) )
// GetQueryBeforeSince return parsed time (unix format) from URL query's before and since // GetQueryBeforeSince return parsed time (unix format) from URL query's before and since
func GetQueryBeforeSince(ctx *context.APIContext) (before, since int64, err error) { func GetQueryBeforeSince(ctx *Context) (before, since int64, err error) {
qCreatedBefore, err := prepareQueryArg(ctx, "before") qCreatedBefore, err := prepareQueryArg(ctx, "before")
if err != nil { if err != nil {
return 0, 0, err return 0, 0, err
@ -53,16 +49,8 @@ func parseTime(value string) (int64, error) {
} }
// prepareQueryArg unescape and trim a query arg // prepareQueryArg unescape and trim a query arg
func prepareQueryArg(ctx *context.APIContext, name string) (value string, err error) { func prepareQueryArg(ctx *Context, name string) (value string, err error) {
value, err = url.PathUnescape(ctx.FormString(name)) value, err = url.PathUnescape(ctx.FormString(name))
value = strings.TrimSpace(value) value = strings.TrimSpace(value)
return return
} }
// GetListOptions returns list options using the page and limit parameters
func GetListOptions(ctx *context.APIContext) db.ListOptions {
return db.ListOptions{
Page: ctx.FormInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
}
}

View File

@ -28,69 +28,69 @@ import (
"time" "time"
) )
// Timeout represents the duration that XSRF tokens are valid. // CsrfTokenTimeout represents the duration that XSRF tokens are valid.
// It is exported so clients may set cookie timeouts that match generated tokens. // It is exported so clients may set cookie timeouts that match generated tokens.
const Timeout = 24 * time.Hour const CsrfTokenTimeout = 24 * time.Hour
// clean sanitizes a string for inclusion in a token by replacing all ":"s. // CsrfTokenRegenerationInterval is the interval between token generations, old tokens are still valid before CsrfTokenTimeout
func clean(s string) string { var CsrfTokenRegenerationInterval = 10 * time.Minute
return strings.ReplaceAll(s, ":", "_")
}
// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours. var csrfTokenSep = []byte(":")
//
// GenerateCsrfToken returns a URL-safe secure XSRF token that expires in CsrfTokenTimeout hours.
// key is a secret key for your application. // key is a secret key for your application.
// userID is a unique identifier for the user. // userID is a unique identifier for the user.
// actionID is the action the user is taking (e.g. POSTing to a particular path). // actionID is the action the user is taking (e.g. POSTing to a particular path).
func GenerateToken(key, userID, actionID string) string { func GenerateCsrfToken(key, userID, actionID string, now time.Time) string {
return generateTokenAtTime(key, userID, actionID, time.Now()) nowUnixNano := now.UnixNano()
} nowUnixNanoStr := strconv.FormatInt(nowUnixNano, 10)
// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
h := hmac.New(sha1.New, []byte(key)) h := hmac.New(sha1.New, []byte(key))
fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano()) h.Write([]byte(strings.ReplaceAll(userID, ":", "_")))
tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano()) h.Write(csrfTokenSep)
h.Write([]byte(strings.ReplaceAll(actionID, ":", "_")))
h.Write(csrfTokenSep)
h.Write([]byte(nowUnixNanoStr))
tok := fmt.Sprintf("%s:%s", h.Sum(nil), nowUnixNanoStr)
return base64.RawURLEncoding.EncodeToString([]byte(tok)) return base64.RawURLEncoding.EncodeToString([]byte(tok))
} }
// ValidToken returns true if token is a valid, unexpired token returned by Generate. func ParseCsrfToken(token string) (issueTime time.Time, ok bool) {
func ValidToken(token, key, userID, actionID string) bool {
return validTokenAtTime(token, key, userID, actionID, time.Now())
}
// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
// Decode the token.
data, err := base64.RawURLEncoding.DecodeString(token) data, err := base64.RawURLEncoding.DecodeString(token)
if err != nil { if err != nil {
return false return time.Time{}, false
} }
// Extract the issue time of the token. pos := bytes.LastIndex(data, csrfTokenSep)
sep := bytes.LastIndex(data, []byte{':'}) if pos == -1 {
if sep < 0 { return time.Time{}, false
return false
} }
nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64) nanos, err := strconv.ParseInt(string(data[pos+1:]), 10, 64)
if err != nil { if err != nil {
return time.Time{}, false
}
return time.Unix(0, nanos), true
}
// ValidCsrfToken returns true if token is a valid and unexpired token returned by Generate.
func ValidCsrfToken(token, key, userID, actionID string, now time.Time) bool {
issueTime, ok := ParseCsrfToken(token)
if !ok {
return false return false
} }
issueTime := time.Unix(0, nanos)
// Check that the token is not expired. // Check that the token is not expired.
if now.Sub(issueTime) >= Timeout { if now.Sub(issueTime) >= CsrfTokenTimeout {
return false return false
} }
// Check that the token is not from the future. // Check that the token is not from the future.
// Allow 1 minute grace period in case the token is being verified on a // Allow 1-minute grace period in case the token is being verified on a
// machine whose clock is behind the machine that issued the token. // machine whose clock is behind the machine that issued the token.
if issueTime.After(now.Add(1 * time.Minute)) { if issueTime.After(now.Add(1 * time.Minute)) {
return false return false
} }
expected := generateTokenAtTime(key, userID, actionID, issueTime) expected := GenerateCsrfToken(key, userID, actionID, issueTime)
// Check that the token matches the expected value. // Check that the token matches the expected value.
// Use constant time comparison to avoid timing attacks. // Use constant time comparison to avoid timing attacks.

View File

@ -37,18 +37,18 @@ var (
func Test_ValidToken(t *testing.T) { func Test_ValidToken(t *testing.T) {
t.Run("Validate token", func(t *testing.T) { t.Run("Validate token", func(t *testing.T) {
tok := generateTokenAtTime(key, userID, actionID, now) tok := GenerateCsrfToken(key, userID, actionID, now)
assert.True(t, validTokenAtTime(tok, key, userID, actionID, oneMinuteFromNow)) assert.True(t, ValidCsrfToken(tok, key, userID, actionID, oneMinuteFromNow))
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(Timeout-1*time.Nanosecond))) assert.True(t, ValidCsrfToken(tok, key, userID, actionID, now.Add(CsrfTokenTimeout-1*time.Nanosecond)))
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(-1*time.Minute))) assert.True(t, ValidCsrfToken(tok, key, userID, actionID, now.Add(-1*time.Minute)))
}) })
} }
// Test_SeparatorReplacement tests that separators are being correctly substituted // Test_SeparatorReplacement tests that separators are being correctly substituted
func Test_SeparatorReplacement(t *testing.T) { func Test_SeparatorReplacement(t *testing.T) {
t.Run("Test two separator replacements", func(t *testing.T) { t.Run("Test two separator replacements", func(t *testing.T) {
assert.NotEqual(t, generateTokenAtTime("foo:bar", "baz", "wah", now), assert.NotEqual(t, GenerateCsrfToken("foo:bar", "baz", "wah", now),
generateTokenAtTime("foo", "bar:baz", "wah", now)) GenerateCsrfToken("foo", "bar:baz", "wah", now))
}) })
} }
@ -61,13 +61,13 @@ func Test_InvalidToken(t *testing.T) {
{"Bad key", "foobar", userID, actionID, oneMinuteFromNow}, {"Bad key", "foobar", userID, actionID, oneMinuteFromNow},
{"Bad userID", key, "foobar", actionID, oneMinuteFromNow}, {"Bad userID", key, "foobar", actionID, oneMinuteFromNow},
{"Bad actionID", key, userID, "foobar", oneMinuteFromNow}, {"Bad actionID", key, userID, "foobar", oneMinuteFromNow},
{"Expired", key, userID, actionID, now.Add(Timeout)}, {"Expired", key, userID, actionID, now.Add(CsrfTokenTimeout)},
{"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)}, {"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)},
} }
tok := generateTokenAtTime(key, userID, actionID, now) tok := GenerateCsrfToken(key, userID, actionID, now)
for _, itt := range invalidTokenTests { for _, itt := range invalidTokenTests {
assert.False(t, validTokenAtTime(tok, itt.key, itt.userID, itt.actionID, itt.t)) assert.False(t, ValidCsrfToken(tok, itt.key, itt.userID, itt.actionID, itt.t))
} }
}) })
} }
@ -84,7 +84,7 @@ func Test_ValidateBadData(t *testing.T) {
} }
for _, bdt := range badDataTests { for _, bdt := range badDataTests {
assert.False(t, validTokenAtTime(bdt.tok, key, userID, actionID, oneMinuteFromNow)) assert.False(t, ValidCsrfToken(bdt.tok, key, userID, actionID, oneMinuteFromNow))
} }
}) })
} }

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -29,7 +30,7 @@ func ToAPIIssue(issue *models.Issue) *api.Issue {
if err := issue.LoadPoster(); err != nil { if err := issue.LoadPoster(); err != nil {
return &api.Issue{} return &api.Issue{}
} }
if err := issue.LoadRepo(); err != nil { if err := issue.LoadRepo(db.DefaultContext); err != nil {
return &api.Issue{} return &api.Issue{}
} }
if err := issue.Repo.GetOwner(db.DefaultContext); err != nil { if err := issue.Repo.GetOwner(db.DefaultContext); err != nil {
@ -214,7 +215,7 @@ func ToLabelList(labels []*models.Label, repo *repo_model.Repository, org *user_
} }
// ToAPIMilestone converts Milestone into API Format // ToAPIMilestone converts Milestone into API Format
func ToAPIMilestone(m *models.Milestone) *api.Milestone { func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone {
apiMilestone := &api.Milestone{ apiMilestone := &api.Milestone{
ID: m.ID, ID: m.ID,
State: m.State(), State: m.State(),

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -32,7 +33,7 @@ func TestLabel_ToLabel(t *testing.T) {
} }
func TestMilestone_APIFormat(t *testing.T) { func TestMilestone_APIFormat(t *testing.T) {
milestone := &models.Milestone{ milestone := &issues_model.Milestone{
ID: 3, ID: 3,
RepoID: 4, RepoID: 4,
Name: "milestoneName", Name: "milestoneName",

View File

@ -27,7 +27,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
err error err error
) )
if err = pr.Issue.LoadRepo(); err != nil { if err = pr.Issue.LoadRepo(ctx); err != nil {
log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err) log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err)
return nil return nil
} }

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
@ -37,7 +38,7 @@ func (a *actionNotifier) NotifyNewIssue(issue *models.Issue, mentions []*user_mo
log.Error("issue.LoadPoster: %v", err) log.Error("issue.LoadPoster: %v", err)
return return
} }
if err := issue.LoadRepo(); err != nil { if err := issue.LoadRepo(db.DefaultContext); err != nil {
log.Error("issue.LoadRepo: %v", err) log.Error("issue.LoadRepo: %v", err)
return return
} }
@ -130,7 +131,7 @@ func (a *actionNotifier) NotifyNewPullRequest(pull *models.PullRequest, mentions
log.Error("pull.LoadIssue: %v", err) log.Error("pull.LoadIssue: %v", err)
return return
} }
if err := pull.Issue.LoadRepo(); err != nil { if err := pull.Issue.LoadRepo(db.DefaultContext); err != nil {
log.Error("pull.Issue.LoadRepo: %v", err) log.Error("pull.Issue.LoadRepo: %v", err)
return return
} }

View File

@ -161,7 +161,7 @@ func (m *mailNotifier) NotifyPullRequestPushCommits(doer *user_model.User, pr *m
log.Error("comment.LoadIssue: %v", err) log.Error("comment.LoadIssue: %v", err)
return return
} }
if err = comment.Issue.LoadRepo(); err != nil { if err = comment.Issue.LoadRepo(ctx); err != nil {
log.Error("comment.Issue.LoadRepo: %v", err) log.Error("comment.Issue.LoadRepo: %v", err)
return return
} }

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -46,7 +47,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *user_model.User, issue *m
return return
} }
if err := issue.LoadRepo(); err != nil { if err := issue.LoadRepo(ctx); err != nil {
log.Error("LoadRepo: %v", err) log.Error("LoadRepo: %v", err)
return return
} }
@ -282,7 +283,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *user_model.User, issue *
} }
func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue, mentions []*user_model.User) { func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue, mentions []*user_model.User) {
if err := issue.LoadRepo(); err != nil { if err := issue.LoadRepo(db.DefaultContext); err != nil {
log.Error("issue.LoadRepo: %v", err) log.Error("issue.LoadRepo: %v", err)
return return
} }
@ -311,7 +312,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest, mention
log.Error("pull.LoadIssue: %v", err) log.Error("pull.LoadIssue: %v", err)
return return
} }
if err := pull.Issue.LoadRepo(); err != nil { if err := pull.Issue.LoadRepo(ctx); err != nil {
log.Error("pull.Issue.LoadRepo: %v", err) log.Error("pull.Issue.LoadRepo: %v", err)
return return
} }
@ -507,7 +508,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *user_model.User, issue *
var err error var err error
if err = issue.LoadRepo(); err != nil { if err = issue.LoadRepo(ctx); err != nil {
log.Error("LoadRepo: %v", err) log.Error("LoadRepo: %v", err)
return return
} }
@ -634,7 +635,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *use
return return
} }
if err := pr.Issue.LoadRepo(); err != nil { if err := pr.Issue.LoadRepo(ctx); err != nil {
log.Error("pr.Issue.LoadRepo: %v", err) log.Error("pr.Issue.LoadRepo: %v", err)
return return
} }

View File

@ -154,7 +154,7 @@ var (
PrefixArchiveFiles: true, PrefixArchiveFiles: true,
DisableMigrations: false, DisableMigrations: false,
DisableStars: false, DisableStars: false,
DefaultBranch: "master", DefaultBranch: "main",
// Repository editor settings // Repository editor settings
Editor: struct { Editor: struct {

View File

@ -106,6 +106,7 @@ var (
StaticCacheTime time.Duration StaticCacheTime time.Duration
EnableGzip bool EnableGzip bool
LandingPageURL LandingPage LandingPageURL LandingPage
LandingPageCustom string
UnixSocketPermission uint32 UnixSocketPermission uint32
EnablePprof bool EnablePprof bool
PprofDataPath string PprofDataPath string
@ -776,15 +777,19 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath) PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
} }
switch sec.Key("LANDING_PAGE").MustString("home") { landingPage := sec.Key("LANDING_PAGE").MustString("home")
switch landingPage {
case "explore": case "explore":
LandingPageURL = LandingPageExplore LandingPageURL = LandingPageExplore
case "organizations": case "organizations":
LandingPageURL = LandingPageOrganizations LandingPageURL = LandingPageOrganizations
case "login": case "login":
LandingPageURL = LandingPageLogin LandingPageURL = LandingPageLogin
default: case "":
case "home":
LandingPageURL = LandingPageHome LandingPageURL = LandingPageHome
default:
LandingPageURL = LandingPage(landingPage)
} }
if len(SSH.Domain) == 0 { if len(SSH.Domain) == 0 {

View File

@ -98,17 +98,6 @@ func DeleteRedirectToCookie(resp http.ResponseWriter) {
SameSite(setting.SessionConfig.SameSite)) SameSite(setting.SessionConfig.SameSite))
} }
// DeleteSesionConfigPathCookie convenience function to delete SessionConfigPath cookies consistently
func DeleteSesionConfigPathCookie(resp http.ResponseWriter, name string) {
SetCookie(resp, name, "",
-1,
setting.SessionConfig.CookiePath,
setting.SessionConfig.Domain,
setting.SessionConfig.Secure,
true,
SameSite(setting.SessionConfig.SameSite))
}
// DeleteCSRFCookie convenience function to delete SessionConfigPath cookies consistently // DeleteCSRFCookie convenience function to delete SessionConfigPath cookies consistently
func DeleteCSRFCookie(resp http.ResponseWriter) { func DeleteCSRFCookie(resp http.ResponseWriter) {
SetCookie(resp, setting.CSRFCookieName, "", SetCookie(resp, setting.CSRFCookieName, "",
@ -117,7 +106,7 @@ func DeleteCSRFCookie(resp http.ResponseWriter) {
setting.SessionConfig.Domain) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too? setting.SessionConfig.Domain) // FIXME: Do we need to set the Secure, httpOnly and SameSite values too?
} }
// SetCookie set the cookies // SetCookie set the cookies. (name, value, lifetime, path, domain, secure, httponly, expires, {sameSite, ...})
// TODO: Copied from gitea.com/macaron/macaron and should be improved after macaron removed. // TODO: Copied from gitea.com/macaron/macaron and should be improved after macaron removed.
func SetCookie(resp http.ResponseWriter, name, value string, others ...interface{}) { func SetCookie(resp http.ResponseWriter, name, value string, others ...interface{}) {
cookie := http.Cookie{} cookie := http.Cookie{}

View File

@ -653,7 +653,6 @@ ssh_invalid_token_signature=Der gegebene SSH-Schlüssel, Signatur oder Token sti
ssh_token_required=Du musst eine Signatur für den Token unten angeben ssh_token_required=Du musst eine Signatur für den Token unten angeben
ssh_token=Token ssh_token=Token
ssh_token_help=Du kannst eine Signatur wie folgt generieren: ssh_token_help=Du kannst eine Signatur wie folgt generieren:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /pfad_zu_deinem_oeffentlichen_Schluessel
ssh_token_signature=SSH Textsignatur (armored signature) ssh_token_signature=SSH Textsignatur (armored signature)
key_signature_ssh_placeholder=Beginnt mit „-----BEGIN PGP SIGNATURE-----“ key_signature_ssh_placeholder=Beginnt mit „-----BEGIN PGP SIGNATURE-----“
verify_ssh_key_success=Der SSH-Key "%s" wurde verifiziert. verify_ssh_key_success=Der SSH-Key "%s" wurde verifiziert.

View File

@ -658,7 +658,6 @@ ssh_invalid_token_signature=Το παρεχόμενο κλειδί SSH, υπογ
ssh_token_required=Πρέπει να δώσετε μια υπογραφή για το παρακάτω διακριτικό ssh_token_required=Πρέπει να δώσετε μια υπογραφή για το παρακάτω διακριτικό
ssh_token=Διακριτικό ssh_token=Διακριτικό
ssh_token_help=Μπορείτε να δημιουργήσετε μια υπογραφή χρησιμοποιώντας: ssh_token_help=Μπορείτε να δημιουργήσετε μια υπογραφή χρησιμοποιώντας:
ssh_token_code=echo -n "%s" "ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey
ssh_token_signature=Θωρακισμένη υπογραφή SSH ssh_token_signature=Θωρακισμένη υπογραφή SSH
key_signature_ssh_placeholder=Ξεκινά με '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Ξεκινά με '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=Το SSH κλειδί '%s' επαληθεύτηκε. verify_ssh_key_success=Το SSH κλειδί '%s' επαληθεύτηκε.

View File

@ -664,7 +664,6 @@ ssh_invalid_token_signature = The provided SSH key, signature or token do not ma
ssh_token_required = You must provide a signature for the below token ssh_token_required = You must provide a signature for the below token
ssh_token = Token ssh_token = Token
ssh_token_help = You can generate a signature using: ssh_token_help = You can generate a signature using:
ssh_token_code = echo -n "%s" | ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey
ssh_token_signature = Armored SSH signature ssh_token_signature = Armored SSH signature
key_signature_ssh_placeholder = Begins with '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder = Begins with '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success = SSH key '%s' has been verified. verify_ssh_key_success = SSH key '%s' has been verified.

View File

@ -656,7 +656,6 @@ ssh_invalid_token_signature=La clave SSH proporcionada, la firma o el token no c
ssh_token_required=Debe proporcionar una firma para el token de abajo ssh_token_required=Debe proporcionar una firma para el token de abajo
ssh_token=Token ssh_token=Token
ssh_token_help=Puede generar una firma de la siguiente manera: ssh_token_help=Puede generar una firma de la siguiente manera:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /ruta_a_su_clave_publico
ssh_token_signature=Firma SSH armadura ssh_token_signature=Firma SSH armadura
key_signature_ssh_placeholder=Comienza con '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Comienza con '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=La clave SSH '%s' ha sido verificada. verify_ssh_key_success=La clave SSH '%s' ha sido verificada.

View File

@ -488,7 +488,9 @@ auth_failed=認証に失敗しました: %v
still_own_repo=あなたのアカウントは1つ以上のリポジトリを所有しています。 先にそれらを削除するか移転してください。 still_own_repo=あなたのアカウントは1つ以上のリポジトリを所有しています。 先にそれらを削除するか移転してください。
still_has_org=あなたのアカウントは1つ以上の組織に参加しています。 先にそれらから脱退してください。 still_has_org=あなたのアカウントは1つ以上の組織に参加しています。 先にそれらから脱退してください。
still_own_packages=あなたのアカウントは1つ以上のパッケージを所有しています。 先にそれらを削除してください。
org_still_own_repo=組織はまだ1つ以上のリポジトリを所有しています。 先にそれらを削除するか移転してください。 org_still_own_repo=組織はまだ1つ以上のリポジトリを所有しています。 先にそれらを削除するか移転してください。
org_still_own_packages=組織はまだ1つ以上のパッケージを所有しています。 先にそれらを削除してください。
target_branch_not_exist=ターゲットのブランチが存在していません。 target_branch_not_exist=ターゲットのブランチが存在していません。
@ -662,7 +664,6 @@ ssh_invalid_token_signature=入力されたSSH 鍵、署名、トークンが合
ssh_token_required=以下のトークンの署名を入力する必要があります ssh_token_required=以下のトークンの署名を入力する必要があります
ssh_token=トークン ssh_token=トークン
ssh_token_help=署名はこの方法で生成できます: ssh_token_help=署名はこの方法で生成できます:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey
ssh_token_signature=Armor形式のSSH署名 ssh_token_signature=Armor形式のSSH署名
key_signature_ssh_placeholder=先頭は '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=先頭は '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=SSH 鍵 '%s' を確認しました。 verify_ssh_key_success=SSH 鍵 '%s' を確認しました。
@ -1793,6 +1794,7 @@ settings.pulls.allow_manual_merge=プルリクエストを手動マージ済み
settings.pulls.enable_autodetect_manual_merge=手動マージの自動検出を有効にする (注意: 特殊なケースでは判定ミスが発生する場合があります) settings.pulls.enable_autodetect_manual_merge=手動マージの自動検出を有効にする (注意: 特殊なケースでは判定ミスが発生する場合があります)
settings.pulls.allow_rebase_update=リベースでプルリクエストのブランチの更新を可能にする settings.pulls.allow_rebase_update=リベースでプルリクエストのブランチの更新を可能にする
settings.pulls.default_delete_branch_after_merge=デフォルトでプルリクエストのブランチをマージ後に削除する settings.pulls.default_delete_branch_after_merge=デフォルトでプルリクエストのブランチをマージ後に削除する
settings.packages_desc=リポジトリパッケージレジストリを有効にする
settings.projects_desc=リポジトリプロジェクトを有効にする settings.projects_desc=リポジトリプロジェクトを有効にする
settings.admin_settings=管理者用設定 settings.admin_settings=管理者用設定
settings.admin_enable_health_check=リポジトリのヘルスチェックを有効にする (git fsck) settings.admin_enable_health_check=リポジトリのヘルスチェックを有効にする (git fsck)
@ -1950,6 +1952,8 @@ settings.event_pull_request_review=プルリクエストのレビュー
settings.event_pull_request_review_desc=プルリクエストの承認・拒否、またはレビューコメントが付いたとき。 settings.event_pull_request_review_desc=プルリクエストの承認・拒否、またはレビューコメントが付いたとき。
settings.event_pull_request_sync=プルリクエストの同期 settings.event_pull_request_sync=プルリクエストの同期
settings.event_pull_request_sync_desc=プルリクエストが同期されたとき。 settings.event_pull_request_sync_desc=プルリクエストが同期されたとき。
settings.event_package=パッケージ
settings.event_package_desc=リポジトリにパッケージが作成または削除されたとき。
settings.branch_filter=ブランチ フィルター settings.branch_filter=ブランチ フィルター
settings.branch_filter_desc=プッシュ、ブランチ作成、ブランチ削除のイベントを通知するブランチを、globパターンで指定するホワイトリストです。 空か<code>*</code>のときは、すべてのブランチのイベントを通知します。 文法については <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> を参照してください。 例: <code>master</code> 、 <code>{master,release*}</code> settings.branch_filter_desc=プッシュ、ブランチ作成、ブランチ削除のイベントを通知するブランチを、globパターンで指定するホワイトリストです。 空か<code>*</code>のときは、すべてのブランチのイベントを通知します。 文法については <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> を参照してください。 例: <code>master</code> 、 <code>{master,release*}</code>
settings.active=有効 settings.active=有効
@ -2431,6 +2435,7 @@ dashboard.resync_all_hooks=すべてのリポジトリの pre-receive, update, p
dashboard.reinit_missing_repos=レコードが存在するが見当たらないすべてのGitリポジトリを再初期化する dashboard.reinit_missing_repos=レコードが存在するが見当たらないすべてのGitリポジトリを再初期化する
dashboard.sync_external_users=外部ユーザーデータの同期 dashboard.sync_external_users=外部ユーザーデータの同期
dashboard.cleanup_hook_task_table=hook_taskテーブルのクリーンアップ dashboard.cleanup_hook_task_table=hook_taskテーブルのクリーンアップ
dashboard.cleanup_packages=期限切れパッケージのクリーンアップ
dashboard.server_uptime=サーバーの稼働時間 dashboard.server_uptime=サーバーの稼働時間
dashboard.current_goroutine=現在のGoroutine数 dashboard.current_goroutine=現在のGoroutine数
dashboard.current_memory_usage=現在のメモリ使用量 dashboard.current_memory_usage=現在のメモリ使用量
@ -2500,6 +2505,7 @@ users.update_profile=ユーザーアカウントを更新
users.delete_account=ユーザーアカウントを削除 users.delete_account=ユーザーアカウントを削除
users.still_own_repo=このユーザーはまだ1つ以上のリポジトリを所有しています。 先にそれらのリポジトリを削除するか移転してください。 users.still_own_repo=このユーザーはまだ1つ以上のリポジトリを所有しています。 先にそれらのリポジトリを削除するか移転してください。
users.still_has_org=このユーザーは組織のメンバーになっています。 先に組織からこのユーザーを削除してください。 users.still_has_org=このユーザーは組織のメンバーになっています。 先に組織からこのユーザーを削除してください。
users.still_own_packages=このユーザーはまだ1つ以上のパッケージを所有しています。最初にそれらのパッケージを削除してください。
users.deletion_success=ユーザーアカウントを削除しました。 users.deletion_success=ユーザーアカウントを削除しました。
users.reset_2fa=2要素認証をリセット users.reset_2fa=2要素認証をリセット
users.list_status_filter.menu_text=フィルター users.list_status_filter.menu_text=フィルター
@ -2546,6 +2552,16 @@ repos.forks=フォーク
repos.issues=イシュー repos.issues=イシュー
repos.size=サイズ repos.size=サイズ
packages.package_manage_panel=パッケージ管理
packages.total_size=合計サイズ: %s
packages.owner=オーナー
packages.creator=作成者
packages.name=名前
packages.version=バージョン
packages.type=タイプ
packages.repository=リポジトリ
packages.size=サイズ
packages.published=配布
defaulthooks=デフォルトWebhook defaulthooks=デフォルトWebhook
defaulthooks.desc=Webhookは、特定のGiteaイベントトリガーが発生した際に、自動的にHTTP POSTリクエストをサーバーへ送信するものです。 ここで定義されたWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくは<a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">webhooks guide</a>をご覧下さい。 defaulthooks.desc=Webhookは、特定のGiteaイベントトリガーが発生した際に、自動的にHTTP POSTリクエストをサーバーへ送信するものです。 ここで定義されたWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくは<a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">webhooks guide</a>をご覧下さい。
@ -2814,6 +2830,8 @@ monitor.next=次回
monitor.previous=前回 monitor.previous=前回
monitor.execute_times=実行回数 monitor.execute_times=実行回数
monitor.process=実行中のプロセス monitor.process=実行中のプロセス
monitor.stacktrace=スタックトレース
monitor.goroutines=%d 件のGoroutines
monitor.desc=説明 monitor.desc=説明
monitor.start=開始日時 monitor.start=開始日時
monitor.execute_time=実行時間 monitor.execute_time=実行時間
@ -2985,4 +3003,91 @@ error.no_unit_allowed_repo=このリポジトリのどのセクションにも
error.unit_not_allowed=このセクションへのアクセスが許可されていません。 error.unit_not_allowed=このセクションへのアクセスが許可されていません。
[packages] [packages]
title=パッケージ
desc=リポジトリ パッケージを管理します。
empty=パッケージはまだありません。
empty.documentation=パッケージレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/overview">ドキュメント</a> を参照してください。
filter.type=タイプ
filter.type.all=すべて
filter.no_result=フィルタの結果、空になりました。
filter.container.tagged=タグあり
filter.container.untagged=タグなし
published_by=%[1]sに<a href="%[2]s">%[3]s</a>が配布
published_by_in=%[1]sに<a href="%[2]s">%[3]s</a>が<a href="%[4]s"><strong>%[5]s</strong></a>で配布
installation=インストール方法
about=このパッケージについて
requirements=要求事項
dependencies=依存関係
keywords=キーワード
details=詳細
details.author=著作者
details.project_site=プロジェクトサイト
details.license=ライセンス
assets=アセット
versions=バージョン
versions.on=on
versions.view_all=すべて表示
dependency.id=ID
dependency.version=バージョン
composer.registry=あなたの <code>~/.composer/config.json</code> ファイルに、このレジストリをセットアップします:
composer.install=Composer を使用してパッケージをインストールするには、次のコマンドを実行します:
composer.documentation=Composer レジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">ドキュメント</a> を参照してください。
composer.dependencies=依存関係
composer.dependencies.development=開発用依存関係
conan.details.repository=リポジトリ
conan.registry=このレジストリをコマンドラインからセットアップします:
conan.install=Conan を使用してパッケージをインストールするには、次のコマンドを実行します:
conan.documentation=Conan レジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">ドキュメント</a> を参照してください。
container.details.type=イメージタイプ
container.details.platform=プラットフォーム
container.details.repository_site=リポジトリサイト
container.details.documentation_site=ドキュメントサイト
container.pull=コマンドラインでイメージを取得します:
container.documentation=Container レジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">ドキュメント</a> を参照してください。
container.multi_arch=OS / アーキテクチャ
container.layers=イメージレイヤー
container.labels=ラベル
container.labels.key=キー
container.labels.value=
generic.download=コマンドラインでパッケージをダウンロードします:
generic.documentation=汎用 レジストリの詳細については、<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">ドキュメント</a> を参照してください。
maven.registry=あなたのプロジェクトの <code>pom.xml</code> ファイルに、このレジストリをセットアップします:
maven.install=パッケージを使用するため <code>pom.xml</code> ファイル内の <code>dependencies</code> ブロックに以下を含めます:
maven.install2=コマンドラインで実行します:
maven.download=依存関係をダウンロードするには、コマンドラインでこれを実行します:
maven.documentation=Mavenレジストリの詳細については、<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">ドキュメント</a> を参照してください。
nuget.registry=このレジストリをコマンドラインからセットアップします:
nuget.install=NuGet を使用してパッケージをインストールするには、次のコマンドを実行します:
nuget.documentation=NuGetレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">ドキュメント</a> を参照してください。
nuget.dependency.framework=ターゲットフレームワーク
npm.registry=あなたのプロジェクトの <code>.npmrc</code> ファイルに、このレジストリをセットアップします:
npm.install=npm を使用してパッケージをインストールするには、次のコマンドを実行します:
npm.install2=または package.json ファイルに追加します:
npm.documentation=Npm レジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">ドキュメント</a> を参照してください。
npm.dependencies=依存関係
npm.dependencies.development=開発用依存関係
npm.dependencies.peer=Peer依存関係
npm.dependencies.optional=オプションの依存関係
npm.details.tag=タグ
pypi.requires=必要なPython
pypi.install=pip を使用してパッケージをインストールするには、次のコマンドを実行します:
pypi.documentation=PyPI レジストリの詳細については、<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">ドキュメント</a> を参照してください。
rubygems.install=gem を使用してパッケージをインストールするには、次のコマンドを実行します:
rubygems.install2=または Gemfile に追加します:
rubygems.dependencies.runtime=実行用依存関係
rubygems.dependencies.development=開発用依存関係
rubygems.required.ruby=必要なRubyバージョン
rubygems.required.rubygems=必要なRubyGemバージョン
rubygems.documentation=RubyGemsレジストリの詳細については、<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">ドキュメント</a> を参照してください。
settings.link=このパッケージをリポジトリにリンク
settings.link.description=パッケージをリポジトリにリンクすると、リポジトリのパッケージリストに表示されるようになります。
settings.link.select=リポジトリを選択
settings.link.button=リポジトリのリンクを更新
settings.link.success=リポジトリのリンクが正常に更新されました。
settings.link.error=リポジトリのリンクの更新に失敗しました。
settings.delete=パッケージ削除
settings.delete.description=パッケージの削除は恒久的で元に戻すことはできません。
settings.delete.notice=%s (%s) を削除しようとしています。この操作は元に戻せません。よろしいですか?
settings.delete.success=パッケージを削除しました。
settings.delete.error=パッケージの削除に失敗しました。

View File

@ -644,7 +644,6 @@ ssh_invalid_token_signature=Norādītā SSH atslēga, paraksts un talons neatbil
ssh_token_required=Jānorāda paraksts zemāk esošajam talonam ssh_token_required=Jānorāda paraksts zemāk esošajam talonam
ssh_token=Talons ssh_token=Talons
ssh_token_help=Parakstu ir iespējams uzģenerēt izmantojot komandu: ssh_token_help=Parakstu ir iespējams uzģenerēt izmantojot komandu:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /ceļš/uz/atslēgu
ssh_token_signature=Aizsargāts SSH paraksts ssh_token_signature=Aizsargāts SSH paraksts
key_signature_ssh_placeholder=Sākas ar '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Sākas ar '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=SSH atslēga '%s' veiksmīgi pārbaudīta. verify_ssh_key_success=SSH atslēga '%s' veiksmīgi pārbaudīta.

View File

@ -625,7 +625,6 @@ ssh_key_verify=Weryfikuj
ssh_token_required=Musisz podać podpis poniższego tokenu ssh_token_required=Musisz podać podpis poniższego tokenu
ssh_token=Token ssh_token=Token
ssh_token_help=Możesz wygenerować podpis używając: ssh_token_help=Możesz wygenerować podpis używając:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /ścieżka_do_twojego_klucza_publicznego
ssh_token_signature=Wzmocniony podpis SSH ssh_token_signature=Wzmocniony podpis SSH
key_signature_ssh_placeholder=Zaczyna się od '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Zaczyna się od '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=Klucz SSH '%s' został zweryfikowany. verify_ssh_key_success=Klucz SSH '%s' został zweryfikowany.

View File

@ -144,6 +144,9 @@ path=Caminho
sqlite_helper=Caminho do arquivo do banco de dados SQLite3.<br>Informe um caminho absoluto se você executar o Gitea como um serviço. sqlite_helper=Caminho do arquivo do banco de dados SQLite3.<br>Informe um caminho absoluto se você executar o Gitea como um serviço.
reinstall_error=Você está tentando instalar em um banco de dados existente do Gitea reinstall_error=Você está tentando instalar em um banco de dados existente do Gitea
reinstall_confirm_message=Reinstalar com um banco de dados Gitea existente pode causar vários problemas. Na maioria dos casos, você deve usar seu "app.ini" existente para executar o Gitea. Se você sabe o que está fazendo, confirme o seguinte: reinstall_confirm_message=Reinstalar com um banco de dados Gitea existente pode causar vários problemas. Na maioria dos casos, você deve usar seu "app.ini" existente para executar o Gitea. Se você sabe o que está fazendo, confirme o seguinte:
reinstall_confirm_check_1=Os dados criptografados pelo SECRET_KEY no app.ini poderão ser perdidos: os usuários podem não conseguir fazer login com 2FA/OTP & espelhos podem não funcionar corretamente. Ao marcar esta caixa você confirma que o atual arquivo app.ini contém o SECRET_KEY correto.
reinstall_confirm_check_2=Os repositórios e configurações podem precisar ser re-sincronizados. Marcando esta caixa você confirma que irá sincronizar novamente os hooks para os repositórios e o arquivo authorized_keys manualmente. Você confirma que irá garantir que as configurações de repositório e espelhamento estão corretas.
reinstall_confirm_check_3=Você confirma que este Gitea está realmente executando com a localização correta do app.ini e que você tem certeza de que precisa reinstalar. Você confirma que tomou conhecimento dos riscos acima descritos.
err_empty_db_path=O caminho do banco de dados SQLite3 não pode ser em branco. err_empty_db_path=O caminho do banco de dados SQLite3 não pode ser em branco.
no_admin_and_disable_registration=Você não pode desabilitar o auto-cadastro do usuário sem criar uma conta de administrador. no_admin_and_disable_registration=Você não pode desabilitar o auto-cadastro do usuário sem criar uma conta de administrador.
err_empty_admin_password=A senha do administrador não pode ser em branco. err_empty_admin_password=A senha do administrador não pode ser em branco.
@ -243,6 +246,7 @@ view_home=Ver %s
search_repos=Encontre um repositório… search_repos=Encontre um repositório…
filter=Outros filtros filter=Outros filtros
filter_by_team_repositories=Filtrar por repositórios da equipe filter_by_team_repositories=Filtrar por repositórios da equipe
feed_of=Feed de "%s"
show_archived=Arquivado show_archived=Arquivado
show_both_archived_unarchived=Mostrando arquivados e não arquivados show_both_archived_unarchived=Mostrando arquivados e não arquivados
@ -374,6 +378,7 @@ issue.action.push_1=<b>@%[1]s</b> fez o push de %[3]d commit para %[2]s
issue.action.push_n=<b>@%[1]s</b> fez o push de %[3]d commits para %[2]s issue.action.push_n=<b>@%[1]s</b> fez o push de %[3]d commits para %[2]s
issue.action.close=<b>@%[1]s</b> fechou #%[2]d. issue.action.close=<b>@%[1]s</b> fechou #%[2]d.
issue.action.reopen=<b>@%[1]s</b> reabriu #%[2]d. issue.action.reopen=<b>@%[1]s</b> reabriu #%[2]d.
issue.action.merge=<b>@%[1]s</b> aplicou o merge #%[2]d em %[3]s.
issue.action.approve=<b>@%[1]s</b> aprovou este pull request. issue.action.approve=<b>@%[1]s</b> aprovou este pull request.
issue.action.reject=<b>@%[1]s</b> solicitou alterações neste pull request. issue.action.reject=<b>@%[1]s</b> solicitou alterações neste pull request.
issue.action.review=<b>@%[1]s</b> fez um comentário neste pull request. issue.action.review=<b>@%[1]s</b> fez um comentário neste pull request.
@ -549,9 +554,12 @@ ui=Tema
hidden_comment_types=Tipos de comentários ocultos hidden_comment_types=Tipos de comentários ocultos
comment_type_group_reference=Referência comment_type_group_reference=Referência
comment_type_group_label=Rótulo comment_type_group_label=Rótulo
comment_type_group_milestone=Marco
comment_type_group_title=Título comment_type_group_title=Título
comment_type_group_branch=Branch comment_type_group_branch=Branch
comment_type_group_deadline=Prazo final comment_type_group_deadline=Prazo final
comment_type_group_dependency=Dependência
comment_type_group_lock=Status de Bloqueio
comment_type_group_review_request=Revisar solicitação comment_type_group_review_request=Revisar solicitação
comment_type_group_pull_request_push=Commits adicionados comment_type_group_pull_request_push=Commits adicionados
comment_type_group_project=Projeto comment_type_group_project=Projeto
@ -647,10 +655,10 @@ verify_gpg_key_success=A chave GPG '%s' foi validada.
ssh_key_verified=Chave validada ssh_key_verified=Chave validada
ssh_key_verified_long=A chave foi validada com um token e pode ser usada para validar commits que correspondam a qualquer dos endereços de e-mail ativados deste usuário. ssh_key_verified_long=A chave foi validada com um token e pode ser usada para validar commits que correspondam a qualquer dos endereços de e-mail ativados deste usuário.
ssh_key_verify=Validar ssh_key_verify=Validar
ssh_invalid_token_signature=A chave, assinatura ou token SSH fornecidos não coincidem, ou então o token expirou.
ssh_token_required=Você tem que fornecer uma assinatura para o token abaixo ssh_token_required=Você tem que fornecer uma assinatura para o token abaixo
ssh_token=Token ssh_token=Token
ssh_token_help=Você pode gerar uma assinatura usando: ssh_token_help=Você pode gerar uma assinatura usando:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /caminho_para_a_sua_chave_pública
ssh_token_signature=Assinatura SSH blindada ssh_token_signature=Assinatura SSH blindada
key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=A chave SSH '%s' foi validada. verify_ssh_key_success=A chave SSH '%s' foi validada.
@ -835,6 +843,7 @@ auto_init=Inicializar o repositório (adicionando .gitignore, licença e LEIA-ME
trust_model_helper=Selecione o modelo de confiança para verificação de assinatura. As opções possíveis são: trust_model_helper=Selecione o modelo de confiança para verificação de assinatura. As opções possíveis são:
trust_model_helper_collaborator=Colaborador: Confiar em assinaturas de colaboradores trust_model_helper_collaborator=Colaborador: Confiar em assinaturas de colaboradores
trust_model_helper_committer=Committer: Confiar em assinaturas que correspondem aos committers trust_model_helper_committer=Committer: Confiar em assinaturas que correspondem aos committers
trust_model_helper_collaborator_committer=Colaborador+Committer: Confiar em assinaturas dos colaboradores que correspondem ao committer
trust_model_helper_default=Padrão: Usar o modelo de confiança padrão para esta instalação trust_model_helper_default=Padrão: Usar o modelo de confiança padrão para esta instalação
create_repo=Criar repositório create_repo=Criar repositório
default_branch=Branch Padrão default_branch=Branch Padrão
@ -891,6 +900,7 @@ desc.archived=Arquivado
template.items=Itens do modelo template.items=Itens do modelo
template.git_content=Conteúdo Git (Branch padrão) template.git_content=Conteúdo Git (Branch padrão)
template.git_hooks=Hooks do Git template.git_hooks=Hooks do Git
template.git_hooks_tooltip=Atualmente você não pode modificar ou remover os Git Hooks adicionados. Selecione isso apenas se você confia no repositório modelo.
template.webhooks=Webhooks template.webhooks=Webhooks
template.topics=Tópicos template.topics=Tópicos
template.avatar=Avatar template.avatar=Avatar
@ -1022,6 +1032,7 @@ line_unicode=`Esta linha possui caracteres unicode ocultos`
escape_control_characters=Escapar escape_control_characters=Escapar
unescape_control_characters=Desescapar unescape_control_characters=Desescapar
file_copy_permalink=Copiar Link Permanente
video_not_supported_in_browser=Seu navegador não suporta a tag 'video' do HTML5. video_not_supported_in_browser=Seu navegador não suporta a tag 'video' do HTML5.
audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5. audio_not_supported_in_browser=Seu navegador não suporta a tag 'audio' do HTML5.
stored_lfs=Armazenado com Git LFS stored_lfs=Armazenado com Git LFS
@ -1127,6 +1138,7 @@ commit.revert-header=Reverter: %s
commit.revert-content=Selecione a branch para reverter para: commit.revert-content=Selecione a branch para reverter para:
commit.cherry-pick=Cherry-pick commit.cherry-pick=Cherry-pick
commit.cherry-pick-header=Cherry-pick: %s commit.cherry-pick-header=Cherry-pick: %s
commit.cherry-pick-content=Selecione o branch para receber o cherry-pick:
ext_issues=Acesso a Issues Externos ext_issues=Acesso a Issues Externos
ext_issues.desc=Link para o issue tracker externo. ext_issues.desc=Link para o issue tracker externo.
@ -1271,6 +1283,11 @@ issues.action_milestone_no_select=Sem marco
issues.action_assignee=Responsável issues.action_assignee=Responsável
issues.action_assignee_no_select=Sem responsável issues.action_assignee_no_select=Sem responsável
issues.opened_by=aberto por <a href="%[2]s">%[3]s</a> %[1]s issues.opened_by=aberto por <a href="%[2]s">%[3]s</a> %[1]s
pulls.merged_by=por <a href="%[2]s">%[3]s</a> foi aplicado em %[1]s
pulls.merged_by_fake=por %[2]s foi aplicado %[1]s
issues.closed_by=por <a href="%[2]s">%[3]s</a> foi fechada %[1]s
issues.opened_by_fake=%[1]s abertas por %[2]s
issues.closed_by_fake=por %[2]s foi fechada %[1]s
issues.previous=Anterior issues.previous=Anterior
issues.next=Próximo issues.next=Próximo
issues.open_title=Aberto issues.open_title=Aberto
@ -1285,10 +1302,14 @@ issues.context.edit=Editar
issues.context.delete=Excluir issues.context.delete=Excluir
issues.no_content=Ainda não há conteúdo. issues.no_content=Ainda não há conteúdo.
issues.close_issue=Fechar issues.close_issue=Fechar
issues.pull_merged_at=`aplicou o merge do commit <a class="ui sha" href="%[1]s"><code>%[2]s</code></a> em <b>%[3]s</b> %[4]s`
issues.manually_pull_merged_at=`aplicou o merge do commit <a class="ui sha" href="%[1]s"><code>%[2]s</code></a> em <b>%[3]s</b> manualmente %[4]s`
issues.close_comment_issue=Comentar e fechar issues.close_comment_issue=Comentar e fechar
issues.reopen_issue=Reabrir issues.reopen_issue=Reabrir
issues.reopen_comment_issue=Comentar e reabrir issues.reopen_comment_issue=Comentar e reabrir
issues.create_comment=Comentar issues.create_comment=Comentar
issues.closed_at=`fechou esta issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at=`reabriu esta issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at=`citou esta issue em um commit <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.commit_ref_at=`citou esta issue em um commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from=`<a href="%[3]s">referenciado esta issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_issue_from=`<a href="%[3]s">referenciado esta issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_pull_from=`<a href="%[3]s">referenciado este pull request %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_pull_from=`<a href="%[3]s">referenciado este pull request %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
@ -1399,6 +1420,8 @@ issues.dependency.remove=Remover
issues.dependency.remove_info=Remover esta dependência issues.dependency.remove_info=Remover esta dependência
issues.dependency.added_dependency=`adicionou uma nova dependência %s` issues.dependency.added_dependency=`adicionou uma nova dependência %s`
issues.dependency.removed_dependency=`removeu uma dependência %s` issues.dependency.removed_dependency=`removeu uma dependência %s`
issues.dependency.pr_closing_blockedby=Fechamento deste pull request está bloqueado pelas seguintes issues
issues.dependency.issue_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues
issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues
issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues
issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la. issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la.
@ -1424,6 +1447,10 @@ issues.review.dismissed_label=Rejeitada
issues.review.left_comment=deixou um comentário issues.review.left_comment=deixou um comentário
issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas. issues.review.content.empty=Você precisa deixar um comentário indicando as alterações solicitadas.
issues.review.reject=alterações solicitadas %s issues.review.reject=alterações solicitadas %s
issues.review.wait=foi solicitada para revisão %s
issues.review.add_review_request=solicitou revisão de %s %s
issues.review.remove_review_request=removeu a solicitação de revisão para %s %s
issues.review.remove_review_request_self=recusou revisar %s
issues.review.pending=Pendente issues.review.pending=Pendente
issues.review.review=Revisão issues.review.review=Revisão
issues.review.reviewers=Revisores issues.review.reviewers=Revisores
@ -1433,6 +1460,7 @@ issues.review.hide_outdated=Ocultar desatualizado
issues.review.show_resolved=Mostrar resolvidas issues.review.show_resolved=Mostrar resolvidas
issues.review.hide_resolved=Ocultar resolvidas issues.review.hide_resolved=Ocultar resolvidas
issues.review.resolve_conversation=Resolver conversa issues.review.resolve_conversation=Resolver conversa
issues.review.un_resolve_conversation=Conversa não resolvida
issues.review.resolved_by=marcou esta conversa como resolvida issues.review.resolved_by=marcou esta conversa como resolvida
issues.assignee.error=Nem todos os responsáveis foram adicionados devido a um erro inesperado. issues.assignee.error=Nem todos os responsáveis foram adicionados devido a um erro inesperado.
issues.reference_issue.body=Conteúdo issues.reference_issue.body=Conteúdo
@ -1450,7 +1478,7 @@ pulls.desc=Habilitar pull requests e revisões de código.
pulls.new=Novo pull request pulls.new=Novo pull request
pulls.view=Ver Pull Request pulls.view=Ver Pull Request
pulls.compare_changes=Novo pull request pulls.compare_changes=Novo pull request
pulls.compare_changes_desc=Selecione a branch de destino (push) e a branch de origem (pull) para o merge. pulls.compare_changes_desc=Selecione o branch de destino (push) e o branch de origem (pull) para o merge.
pulls.compare_base=merge em pulls.compare_base=merge em
pulls.compare_compare=pull de pulls.compare_compare=pull de
pulls.switch_comparison_type=Mudar tipo de comparação pulls.switch_comparison_type=Mudar tipo de comparação
@ -1472,6 +1500,7 @@ pulls.cant_reopen_deleted_branch=Este pull request não pode ser reaberto porque
pulls.merged=Merge aplicado pulls.merged=Merge aplicado
pulls.merged_as=O pull request teve merge aplicado como <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a>. pulls.merged_as=O pull request teve merge aplicado como <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a>.
pulls.manually_merged=Merge aplicado manualmente pulls.manually_merged=Merge aplicado manualmente
pulls.manually_merged_as=O pull request foi aplicado manualmente como <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a>.
pulls.is_closed=O pull request foi fechado. pulls.is_closed=O pull request foi fechado.
pulls.has_merged=O merge deste pull request foi aplicado. pulls.has_merged=O merge deste pull request foi aplicado.
pulls.title_wip_desc=`<a href="#">Inicie o título com o prefixo <strong>%s</strong></a> para prevenir o merge do pull request até que o mesmo esteja pronto.` pulls.title_wip_desc=`<a href="#">Inicie o título com o prefixo <strong>%s</strong></a> para prevenir o merge do pull request até que o mesmo esteja pronto.`
@ -1482,6 +1511,7 @@ pulls.remove_prefix=Remover o prefixo <strong>%s</strong>
pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork. pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork.
pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino. pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino.
pulls.is_checking=Verificação de conflitos do merge está em andamento. Tente novamente em alguns momentos. pulls.is_checking=Verificação de conflitos do merge está em andamento. Tente novamente em alguns momentos.
pulls.is_empty=Este branch é igual ao branch de destino.
pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas. pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas.
pulls.required_status_check_missing=Estão faltando algumas verificações necessárias. pulls.required_status_check_missing=Estão faltando algumas verificações necessárias.
pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request. pulls.required_status_check_administrator=Como administrador, você ainda pode aplicar o merge deste pull request.
@ -1510,17 +1540,24 @@ pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque est
pulls.no_merge_not_ready=Este pull request não está pronto para ser realizado o merge, verifique o status da revisão e as verificações de status. pulls.no_merge_not_ready=Este pull request não está pronto para ser realizado o merge, verifique o status da revisão e as verificações de status.
pulls.no_merge_access=Você não está autorizado para realizar o merge deste pull request. pulls.no_merge_access=Você não está autorizado para realizar o merge deste pull request.
pulls.merge_pull_request=Criar commit de merge pulls.merge_pull_request=Criar commit de merge
pulls.rebase_merge_pull_request=Rebase e fast-forward
pulls.rebase_merge_commit_pull_request=Rebase e criar commit de merge
pulls.squash_merge_pull_request=Criar commit de squash pulls.squash_merge_pull_request=Criar commit de squash
pulls.merge_manually=Merge feito manualmente pulls.merge_manually=Merge feito manualmente
pulls.merge_commit_id=A ID de merge commit pulls.merge_commit_id=A ID de merge commit
pulls.require_signed_wont_sign=O branch requer commits assinados, mas este merge não será assinado pulls.require_signed_wont_sign=O branch requer commits assinados, mas este merge não será assinado
pulls.invalid_merge_option=Você não pode usar esta opção de merge neste pull request. pulls.invalid_merge_option=Você não pode usar esta opção de merge neste pull request.
pulls.merge_conflict=O merge falhou: Houve um conflito ao fazer merge. Dica: Tente uma estratégia diferente
pulls.merge_conflict_summary=Mensagem de erro pulls.merge_conflict_summary=Mensagem de erro
pulls.rebase_conflict=O merge falhou: Houve um conflito durante o rebase do commit %[1]s. Dica: Tente uma estratégia diferente
pulls.rebase_conflict_summary=Mensagem de Erro pulls.rebase_conflict_summary=Mensagem de Erro
; </summary><code>%[2]s<br>%[3]s</code></details> ; </summary><code>%[2]s<br>%[3]s</code></details>
pulls.unrelated_histories=Merge falhou: O merge do principal e da base não compartilham uma história comum. Dica: Tente uma estratégia diferente pulls.unrelated_histories=Merge falhou: O merge do principal e da base não compartilham uma história comum. Dica: Tente uma estratégia diferente
pulls.merge_out_of_date=Merge falhou: durante a geração do merge, a base não foi atualizada. Dica: Tente novamente. pulls.merge_out_of_date=Merge falhou: durante a geração do merge, a base não foi atualizada. Dica: Tente novamente.
pulls.head_out_of_date=O merge falhou: Enquanto gerava o merge, a head foi atualizada. Dica: Tente novamente.
pulls.push_rejected=O merge falhou: O push foi rejeitado. Revise os Git Hooks para este repositório.
pulls.push_rejected_summary=Mensagem completa da rejeição pulls.push_rejected_summary=Mensagem completa da rejeição
pulls.push_rejected_no_message=O merge falhou: O push foi rejeitado mas não houve mensagem remota.<br>Revise os Git Hooks para este repositório
pulls.open_unmerged_pull_exists=`Não é possível executar uma operação de reabertura pois há um pull request pendente (#%d) com propriedades idênticas.` pulls.open_unmerged_pull_exists=`Não é possível executar uma operação de reabertura pois há um pull request pendente (#%d) com propriedades idênticas.`
pulls.status_checking=Algumas verificações estão pendentes pulls.status_checking=Algumas verificações estão pendentes
pulls.status_checks_success=Todas as verificações foram bem sucedidas pulls.status_checks_success=Todas as verificações foram bem sucedidas
@ -1529,13 +1566,16 @@ pulls.status_checks_failure=Algumas verificações falharam
pulls.status_checks_error=Algumas verificações reportaram erros pulls.status_checks_error=Algumas verificações reportaram erros
pulls.status_checks_requested=Obrigatário pulls.status_checks_requested=Obrigatário
pulls.status_checks_details=Detalhes pulls.status_checks_details=Detalhes
pulls.update_branch_success=Atualização da branch foi bem-sucedida pulls.update_branch=Atualizar branch por merge
pulls.update_not_allowed=Você não tem permissão para atualizar a branch pulls.update_branch_rebase=Atualizar branch por rebase
pulls.outdated_with_base_branch=Esta branch está desatualizado com a branch base pulls.update_branch_success=Atualização do branch foi bem-sucedida
pulls.update_not_allowed=Você não tem permissão para atualizar o branch
pulls.outdated_with_base_branch=Este branch está desatualizado com o branch base
pulls.closed_at=`fechou este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>` pulls.closed_at=`fechou este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.reopened_at=`reabriu este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>` pulls.reopened_at=`reabriu este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.merge_instruction_hint=`Você também pode ver as <a class="show-instruction">instruções para a linha de comandos</a>.` pulls.merge_instruction_hint=`Você também pode ver as <a class="show-instruction">instruções para a linha de comandos</a>.`
pulls.merge_instruction_step1_desc=No repositório do seu projeto, crie um novo branch e teste as alterações.
pulls.merge_instruction_step2_desc=Faça merge das alterações e atualize no Gitea. pulls.merge_instruction_step2_desc=Faça merge das alterações e atualize no Gitea.
milestones.new=Novo marco milestones.new=Novo marco
@ -1582,6 +1622,7 @@ signing.wont_sign.basesigned=O merge não será assinada porque o commit base n
signing.wont_sign.headsigned=O merge não será assinado porque o commit principal não foi assinado signing.wont_sign.headsigned=O merge não será assinado porque o commit principal não foi assinado
signing.wont_sign.commitssigned=O merge não será assinado pois todos os commits associados não foram assinados signing.wont_sign.commitssigned=O merge não será assinado pois todos os commits associados não foram assinados
signing.wont_sign.approved=O merge não será assinado pois o PR não foi aprovado signing.wont_sign.approved=O merge não será assinado pois o PR não foi aprovado
signing.wont_sign.not_signed_in=Você não está logado
ext_wiki=Acesso a Wiki Externo ext_wiki=Acesso a Wiki Externo
ext_wiki.desc=Link para uma wiki externa. ext_wiki.desc=Link para uma wiki externa.
@ -1662,9 +1703,9 @@ activity.git_stats_pushed_1=realizou push de
activity.git_stats_pushed_n=realizaram push de activity.git_stats_pushed_n=realizaram push de
activity.git_stats_commit_1=%d commit activity.git_stats_commit_1=%d commit
activity.git_stats_commit_n=%d commits activity.git_stats_commit_n=%d commits
activity.git_stats_push_to_branch=para a %s e activity.git_stats_push_to_branch=para o %s e
activity.git_stats_push_to_all_branches=para todas as branches. activity.git_stats_push_to_all_branches=para todos os branches.
activity.git_stats_on_default_branch=Na %s, activity.git_stats_on_default_branch=No %s,
activity.git_stats_file_1=%d arquivo activity.git_stats_file_1=%d arquivo
activity.git_stats_file_n=%d arquivos activity.git_stats_file_n=%d arquivos
activity.git_stats_files_changed_1=foi modificado activity.git_stats_files_changed_1=foi modificado
@ -1702,6 +1743,9 @@ settings.mirror_settings.direction=Sentido
settings.mirror_settings.direction.pull=Pull settings.mirror_settings.direction.pull=Pull
settings.mirror_settings.direction.push=Push settings.mirror_settings.direction.push=Push
settings.mirror_settings.last_update=Última atualização settings.mirror_settings.last_update=Última atualização
settings.mirror_settings.push_mirror.none=Nenhum espelhamento de push configurado
settings.mirror_settings.push_mirror.remote_url=URL do repositório do Git remoto
settings.mirror_settings.push_mirror.add=Adicionar Espelho de Push
settings.sync_mirror=Sincronizar agora settings.sync_mirror=Sincronizar agora
settings.mirror_sync_in_progress=Sincronização do espelhamento está em andamento. Verifique novamente em um minuto. settings.mirror_sync_in_progress=Sincronização do espelhamento está em andamento. Verifique novamente em um minuto.
settings.email_notifications.enable=Habilitar notificações de e-mail settings.email_notifications.enable=Habilitar notificações de e-mail
@ -1738,6 +1782,9 @@ settings.pulls.allow_merge_commits=Habilitar commit no merge
settings.pulls.allow_rebase_merge=Habilitar Rebasing em commits via merge settings.pulls.allow_rebase_merge=Habilitar Rebasing em commits via merge
settings.pulls.allow_rebase_merge_commit=Habilitar Rebasing com commits explícitos no merge (--no-ff) settings.pulls.allow_rebase_merge_commit=Habilitar Rebasing com commits explícitos no merge (--no-ff)
settings.pulls.allow_squash_commits=Habilitar Squashing em commits via merge settings.pulls.allow_squash_commits=Habilitar Squashing em commits via merge
settings.pulls.allow_manual_merge=Habilitar Marcar PR como aplicado manualmente
settings.pulls.enable_autodetect_manual_merge=Habilitar a detecção automática de merge manual (Nota: Em alguns casos especiais, podem ocorrer julgamentos errados)
settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull request após o merge por padrão
settings.packages_desc=Habilitar Registro de Pacotes de Repositório settings.packages_desc=Habilitar Registro de Pacotes de Repositório
settings.projects_desc=Habilitar Projetos do Repositório settings.projects_desc=Habilitar Projetos do Repositório
settings.admin_settings=Configurações do administrador settings.admin_settings=Configurações do administrador
@ -1780,8 +1827,16 @@ settings.transfer_succeed=O repositório foi transferido.
settings.signing_settings=Configurações de Verificação de Assinatura settings.signing_settings=Configurações de Verificação de Assinatura
settings.trust_model=Modelo de Confiança na Assinatura settings.trust_model=Modelo de Confiança na Assinatura
settings.trust_model.default=Modelo Padrão de Confiança settings.trust_model.default=Modelo Padrão de Confiança
settings.trust_model.default.desc=Use o modelo de confiança de repositório padrão para esta instalação.
settings.trust_model.collaborator=Colaborador settings.trust_model.collaborator=Colaborador
settings.trust_model.collaborator.long=Colaborador: Confiar em assinaturas feitas por colaboradores settings.trust_model.collaborator.long=Colaborador: Confiar em assinaturas feitas por colaboradores
settings.trust_model.collaborator.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "confiáveis" - (quer correspondam ao autor do commit ou não). Caso contrário, assinaturas válidas serão marcadas como "não confiáveis" se a assinatura corresponder ao autor do submissão e "não corresponde" se não corresponder.
settings.trust_model.committer=Committer
settings.trust_model.committer.long=Committer: Confiar nas assinaturas que correspondam aos committers (isso corresponde ao GitHub e forçará commits assinados pelo Gitea a ter o Gitea como o committer)
settings.trust_model.committer.desc=Assinaturas válidas só serão marcadas como "confiáveis" se corresponderem ao committer, caso contrário serão marcadas como "não correspondidas". Isso forçará o Gitea a ser o commiter nos commits assinados, com o autor real marcado como Co-authored-by: e Co-commited-by: no final do commit. A chave padrão do Gitea tem que corresponder a um usuário no banco de dados.
settings.trust_model.collaboratorcommitter=Colaborador+Commiter
settings.trust_model.collaboratorcommitter.long=Colaborador+Committer: Confiar na assinatura dos colaboradores que correspondem ao autor do commit
settings.trust_model.collaboratorcommitter.desc=Assinaturas válidas dos colaboradores deste repositório serão marcadas como "confiáveis" se corresponderem ao autor do commit. Caso contrário, as assinaturas válidas serão marcadas como "não confiáveis" se a assinatura corresponder ao autor do commit e "não corresponde" caso contrário. Isso forçará o Gitea a ser marcado como o autor do commit nos commits assinados com o autor marcado como Co-Authored-By: e o Committed-By: resumo do commit. A chave padrão do Gitea tem que corresponder a um usuário no banco de dados.
settings.wiki_delete=Excluir dados da wiki settings.wiki_delete=Excluir dados da wiki
settings.wiki_delete_desc=A exclusão de dados da wiki é permanente e não pode ser desfeita. settings.wiki_delete_desc=A exclusão de dados da wiki é permanente e não pode ser desfeita.
settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente a wiki do repositório %s. settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente a wiki do repositório %s.
@ -1829,6 +1884,8 @@ settings.webhook.headers=Cabeçalhos
settings.webhook.payload=Conteúdo settings.webhook.payload=Conteúdo
settings.webhook.body=Corpo settings.webhook.body=Corpo
settings.webhook.replay.description=Executar novamente esse webhook. settings.webhook.replay.description=Executar novamente esse webhook.
settings.webhook.delivery.success=Um evento foi adicionado à fila de envio. Pode levar alguns segundos até que ele apareça no histórico de envio.
settings.githooks_desc=Hooks do Git são executados pelo próprio Git. Você pode editar arquivos de hook abaixo para configurar operações personalizadas.
settings.githook_edit_desc=Se o hook não estiver ativo, o conteúdo de exemplo será apresentado. Deixar o conteúdo em branco irá desabilitar esse hook. settings.githook_edit_desc=Se o hook não estiver ativo, o conteúdo de exemplo será apresentado. Deixar o conteúdo em branco irá desabilitar esse hook.
settings.githook_name=Nome do Hook settings.githook_name=Nome do Hook
settings.githook_content=Conteúdo do Hook settings.githook_content=Conteúdo do Hook
@ -1853,6 +1910,7 @@ settings.event_create_desc=Branch ou tag criado.
settings.event_delete=Excluir settings.event_delete=Excluir
settings.event_delete_desc=Branch ou tag deletado. settings.event_delete_desc=Branch ou tag deletado.
settings.event_fork=Fork settings.event_fork=Fork
settings.event_fork_desc=Feito fork do repositório.
settings.event_release=Versão settings.event_release=Versão
settings.event_release_desc=Versão publicada, atualizada ou excluída em um repositório. settings.event_release_desc=Versão publicada, atualizada ou excluída em um repositório.
settings.event_push=Push settings.event_push=Push
@ -1863,14 +1921,31 @@ settings.event_header_issue=Eventos da Issue
settings.event_issues=Issues settings.event_issues=Issues
settings.event_issues_desc=Issue aberta, fechada, reaberta ou editada. settings.event_issues_desc=Issue aberta, fechada, reaberta ou editada.
settings.event_issue_assign=Issue Atribuída settings.event_issue_assign=Issue Atribuída
settings.event_issue_assign_desc=Issue atribuída ou não atribuída.
settings.event_issue_label=Issue Rotulada settings.event_issue_label=Issue Rotulada
settings.event_issue_label_desc=Rótulos da issue atualizados ou removidos. settings.event_issue_label_desc=Rótulos da issue atualizados ou removidos.
settings.event_issue_milestone=Marco Atribuído à Issue
settings.event_issue_milestone_desc=Marco atribuído ou desatribuído à Issue.
settings.event_issue_comment=Comentário da issue settings.event_issue_comment=Comentário da issue
settings.event_issue_comment_desc=Comentário da issue criado, editado ou excluído. settings.event_issue_comment_desc=Comentário da issue criado, editado ou excluído.
settings.event_header_pull_request=Eventos de Pull Request settings.event_header_pull_request=Eventos de Pull Request
settings.event_pull_request=Pull request settings.event_pull_request=Pull request
settings.event_pull_request_desc=Pull request aberto, fechado, reaberto ou editado.
settings.event_pull_request_assign=Pull Request Atribuído
settings.event_pull_request_assign_desc=Pull request atribuído ou desatribuído.
settings.event_pull_request_label=Pull Request Rotulado
settings.event_pull_request_label_desc=Rótulos do pull request atualizados ou limpos.
settings.event_pull_request_milestone=Marco Atribuído ao Pull Request
settings.event_pull_request_milestone_desc=Marco atribuído ou desatribuído ao pull request.
settings.event_pull_request_comment=Comentário no Pull Request
settings.event_pull_request_comment_desc=Comentário criado, editado ou excluído no pull request.
settings.event_pull_request_review=Pull Request Revisado
settings.event_pull_request_review_desc=Pull request aprovado, rejeitado ou revisão comentada.
settings.event_pull_request_sync=Pull Request Sincronizado
settings.event_pull_request_sync_desc=Pull request sincronizado.
settings.event_package=Pacote settings.event_package=Pacote
settings.branch_filter=Filtro de branch settings.branch_filter=Filtro de branch
settings.branch_filter_desc=Lista dos branches a serem considerados nos eventos push, criação de branch e exclusão de branch, especificados como padrão glob. Se estiver vazio ou for <code>*</code>, eventos para todos os branches serão relatados. Veja <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentação da sintaxe. Exemplos: <code>master</code>, <code>{master,release*}</code>.
settings.active=Ativo settings.active=Ativo
settings.active_helper=Informações sobre eventos disparados serão enviadas para esta URL do webhook. settings.active_helper=Informações sobre eventos disparados serão enviadas para esta URL do webhook.
settings.add_hook_success=O webhook foi adicionado. settings.add_hook_success=O webhook foi adicionado.
@ -1882,6 +1957,7 @@ settings.hook_type=Tipo de Hook
settings.slack_token=Token settings.slack_token=Token
settings.slack_domain=Domínio settings.slack_domain=Domínio
settings.slack_channel=Canal settings.slack_channel=Canal
settings.add_web_hook_desc=Integrar <a target="_blank" rel="noreferrer" href="%s">%s</a> no seu repositório.
settings.web_hook_name_gitea=Gitea settings.web_hook_name_gitea=Gitea
settings.web_hook_name_gogs=Gogs settings.web_hook_name_gogs=Gogs
settings.web_hook_name_slack=Slack settings.web_hook_name_slack=Slack
@ -1917,8 +1993,8 @@ settings.protected_branch=Proteção de Branch
settings.protected_branch_can_push=Permitir push? settings.protected_branch_can_push=Permitir push?
settings.protected_branch_can_push_yes=Você pode fazer push settings.protected_branch_can_push_yes=Você pode fazer push
settings.protected_branch_can_push_no=Você não pode fazer push settings.protected_branch_can_push_no=Você não pode fazer push
settings.branch_protection=Proteção de branch para '<b>%s</b>' settings.branch_protection=Proteção de Branch para '<b>%s</b>'
settings.protect_this_branch=Habilitar proteção de branch settings.protect_this_branch=Habilitar Proteção de Branch
settings.protect_this_branch_desc=Previne a exclusão e restringe o merge e push para o branch. settings.protect_this_branch_desc=Previne a exclusão e restringe o merge e push para o branch.
settings.protect_disable_push=Desabilitar push settings.protect_disable_push=Desabilitar push
settings.protect_disable_push_desc=Nenhum push será permitido neste branch. settings.protect_disable_push_desc=Nenhum push será permitido neste branch.
@ -1926,6 +2002,7 @@ settings.protect_enable_push=Habilitar push
settings.protect_enable_push_desc=Qualquer pessoa com acesso de escrita terá permissão para realizar push neste branch (mas não forçar o push). settings.protect_enable_push_desc=Qualquer pessoa com acesso de escrita terá permissão para realizar push neste branch (mas não forçar o push).
settings.protect_whitelist_committers=Lista permitida para push settings.protect_whitelist_committers=Lista permitida para push
settings.protect_whitelist_committers_desc=Somente usuários ou equipes da lista permitida serão autorizados realizar push neste branch (mas não forçar o push). settings.protect_whitelist_committers_desc=Somente usuários ou equipes da lista permitida serão autorizados realizar push neste branch (mas não forçar o push).
settings.protect_whitelist_deploy_keys=Dar permissão às chaves de deploy com acesso de gravação para push.
settings.protect_whitelist_users=Usuários com permissão para realizar push: settings.protect_whitelist_users=Usuários com permissão para realizar push:
settings.protect_whitelist_search_users=Pesquisar usuários... settings.protect_whitelist_search_users=Pesquisar usuários...
settings.protect_whitelist_teams=Equipes com permissão para realizar push: settings.protect_whitelist_teams=Equipes com permissão para realizar push:
@ -1935,6 +2012,7 @@ settings.protect_merge_whitelist_committers_desc=Permitir que determinados usuá
settings.protect_merge_whitelist_users=Usuários com permissão para aplicar merge: settings.protect_merge_whitelist_users=Usuários com permissão para aplicar merge:
settings.protect_merge_whitelist_teams=Equipes com permissão para aplicar merge: settings.protect_merge_whitelist_teams=Equipes com permissão para aplicar merge:
settings.protect_check_status_contexts=Habilitar verificação de status settings.protect_check_status_contexts=Habilitar verificação de status
settings.protect_check_status_contexts_desc=Exigir que as verificações de status passem antes de fazer merge. Escolha quais verificações de status devem passar antes que os branches possam ter o merge aplicado em um branch que corresponda a esta regra. Quando habilitado, os commits devem primeiro ser enviados para outro branch, então faça merge ou push diretamente para um branch que corresponde a esta regra após a verificação de status ter passado. Se nenhum contexto for selecionado, o último commit deve ser bem sucedido, independentemente do contexto.
settings.protect_check_status_contexts_list=Verificações de status encontradas na última semana para este repositório settings.protect_check_status_contexts_list=Verificações de status encontradas na última semana para este repositório
settings.protect_required_approvals=Aprovações necessárias: settings.protect_required_approvals=Aprovações necessárias:
settings.protect_required_approvals_desc=Permite apenas realizar merge do pull request com avaliações positivas suficientes. settings.protect_required_approvals_desc=Permite apenas realizar merge do pull request com avaliações positivas suficientes.
@ -1945,6 +2023,8 @@ settings.protect_approvals_whitelist_teams=Equipes com permissão de revisão:
settings.dismiss_stale_approvals=Descartar aprovações obsoletas settings.dismiss_stale_approvals=Descartar aprovações obsoletas
settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas. settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo do pull request são enviados para o branch, as antigas aprovações serão descartadas.
settings.require_signed_commits=Exibir commits assinados settings.require_signed_commits=Exibir commits assinados
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
settings.protect_protected_file_patterns=Padrões de arquivos protegidos (separados usando ponto e vírgula '\;'):
settings.protect_unprotected_file_patterns=Padrões de arquivos desprotegidos (separados usando ponto e vírgula '\;'): settings.protect_unprotected_file_patterns=Padrões de arquivos desprotegidos (separados usando ponto e vírgula '\;'):
settings.protect_unprotected_file_patterns_desc=Arquivos não protegidos que podem ser alterados diretamente se o usuário tiver acesso de gravação, ignorando as restrições de push. Vários padrões podem ser separados usando ponto e vírgula ('\;'). Veja <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentação para sintaxe de padrões. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. settings.protect_unprotected_file_patterns_desc=Arquivos não protegidos que podem ser alterados diretamente se o usuário tiver acesso de gravação, ignorando as restrições de push. Vários padrões podem ser separados usando ponto e vírgula ('\;'). Veja <a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> documentação para sintaxe de padrões. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>.
settings.add_protected_branch=Habilitar proteção settings.add_protected_branch=Habilitar proteção
@ -1955,7 +2035,12 @@ settings.protected_branch_deletion=Desabilitar proteção de branch
settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar? settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar?
settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas
settings.block_rejected_reviews_desc=O merge não será possível quando são solicitadas alterações pelos revisores oficiais, mesmo que haja aprovação suficiente. settings.block_rejected_reviews_desc=O merge não será possível quando são solicitadas alterações pelos revisores oficiais, mesmo que haja aprovação suficiente.
settings.block_on_official_review_requests=Bloquear merge em solicitações de revisão oficiais
settings.block_on_official_review_requests_desc=O merge não será possível quando tiver pedidos de revisão oficiais, mesmo que haja aprovações suficientes.
settings.block_outdated_branch=Bloquear o merge se o pull request estiver desatualizado
settings.block_outdated_branch_desc=O merge não será possível quando o branch de topo estiver atrás do branch base.
settings.default_branch_desc=Selecione um branch padrão para pull requests e commits de código: settings.default_branch_desc=Selecione um branch padrão para pull requests e commits de código:
settings.default_merge_style_desc=Estilo de merge padrão para pull requests:
settings.choose_branch=Escolha um branch... settings.choose_branch=Escolha um branch...
settings.no_protected_branch=Não há branches protegidos. settings.no_protected_branch=Não há branches protegidos.
settings.edit_protected_branch=Editar settings.edit_protected_branch=Editar
@ -2075,6 +2160,7 @@ diff.has_escaped=Essa linha tem caracteres Unicode ocultos
releases.desc=Acompanhe as versões e downloads do projeto. releases.desc=Acompanhe as versões e downloads do projeto.
release.releases=Versões release.releases=Versões
release.detail=Detalhes da versão
release.tags=Tags release.tags=Tags
release.new_release=Nova versão release.new_release=Nova versão
release.draft=Rascunho release.draft=Rascunho
@ -2083,6 +2169,7 @@ release.stable=Estável
release.compare=Comparar release.compare=Comparar
release.edit=editar release.edit=editar
release.ahead.commits=<strong>%d</strong> commits release.ahead.commits=<strong>%d</strong> commits
release.ahead.target=para %s desde esta versão
release.source_code=Código fonte release.source_code=Código fonte
release.new_subheader=Lançamentos organizam versões do projeto. release.new_subheader=Lançamentos organizam versões do projeto.
release.edit_subheader=Lançamentos organizam versões do projeto. release.edit_subheader=Lançamentos organizam versões do projeto.
@ -2113,28 +2200,29 @@ release.download_count=Downloads: %s
release.add_tag_msg=Use o título e o conteúdo do lançamento como mensagem da tag. release.add_tag_msg=Use o título e o conteúdo do lançamento como mensagem da tag.
release.add_tag=Criar apenas a tag release.add_tag=Criar apenas a tag
branch.name=Nome da branch branch.name=Nome do Branch
branch.search=Pesquisar branches branch.search=Pesquisar branches
branch.already_exists=Uma branch com o nome %s já existe. branch.already_exists=Um branch com o nome %s já existe.
branch.delete_head=Excluir branch.delete_head=Excluir
branch.delete=Excluir branch '%s' branch.delete=Excluir Branch '%s'
branch.delete_html=Excluir Branch branch.delete_html=Excluir Branch
branch.delete_desc=A exclusão de uma branch é permanente. Isto <strong>NÃO PODERÁ</strong> ser desfeito. Continuar? branch.delete_desc=A exclusão de um branch é permanente. Isto <strong>NÃO PODERÁ</strong> ser desfeito. Continuar?
branch.deletion_success=A branch '%s' foi excluída. branch.deletion_success=O branch '%s' foi excluído.
branch.deletion_failed=Falha ao excluir a branch %s. branch.deletion_failed=Falha ao excluir o branch %s.
branch.delete_branch_has_new_commits=A branch %s não pode ser excluída porque há novos commits após o merge. branch.delete_branch_has_new_commits=O branch %s não pode ser excluído porque há novos commits após o merge.
branch.create_branch=Criar branch <strong>%s</strong> branch.create_branch=Criar branch <strong>%s</strong>
branch.create_from=de %s branch.create_from=de %s
branch.create_success=A branch '%s' foi criada. branch.create_success=O branch '%s' foi criado.
branch.branch_already_exists=Branch '%s' já existe neste repositório. branch.branch_already_exists=Branch '%s' já existe neste repositório.
branch.branch_name_conflict=O nome da branch '%s' está em conflito com a branch '%s'. branch.branch_name_conflict=O nome do branch '%s' está em conflito com o branch '%s'.
branch.tag_collision=A branch '%s' não pode ser criada como tag com o mesmo nome já existente neste repositório. branch.tag_collision=O branch '%s' não pode ser criado como tag com o mesmo nome já existente neste repositório.
branch.deleted_by=Excluído por %s branch.deleted_by=Excluído por %s
branch.restore_success=A branch '%s' foi restaurada. branch.restore_success=O branch '%s' foi restaurado.
branch.restore_failed=Falha ao restaurar a branch %s. branch.restore_failed=Falha ao restaurar o branch %s.
branch.protected_deletion_failed=A branch '%s' está protegida. Ela não pode ser excluída. branch.protected_deletion_failed=O branch '%s' está protegido. Ele não pode ser excluído.
branch.restore=Restaurar branch '%s' branch.default_deletion_failed=Branch '%s' é o branch padrão. Não pode ser excluído.
branch.download=Baixar branch '%s' branch.restore=Restaurar Branch '%s'
branch.download=Baixar Branch '%s'
branch.included_desc=Este branch faz parte do branch padrão branch.included_desc=Este branch faz parte do branch padrão
branch.included=Incluído branch.included=Incluído
branch.create_new_branch=Criar branch a partir do branch: branch.create_new_branch=Criar branch a partir do branch:
@ -2156,6 +2244,9 @@ topic.done=Feito
topic.count_prompt=Você não pode selecionar mais de 25 tópicos topic.count_prompt=Você não pode selecionar mais de 25 tópicos
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres. topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
error.csv.too_large=Não é possível renderizar este arquivo porque ele é muito grande.
error.csv.unexpected=Não é possível renderizar este arquivo porque ele contém um caractere inesperado na linha %d e coluna %d.
error.csv.invalid_field_count=Não é possível renderizar este arquivo porque ele tem um número errado de campos na linha %d.
[org] [org]
org_name_holder=Nome da organização org_name_holder=Nome da organização
@ -2193,6 +2284,7 @@ settings.repoadminchangeteam=O administrador do repositório pode adicionar e re
settings.visibility=Visibilidade settings.visibility=Visibilidade
settings.visibility.public=Pública settings.visibility.public=Pública
settings.visibility.limited=Limitada (Visível apenas para usuários registrados) settings.visibility.limited=Limitada (Visível apenas para usuários registrados)
settings.visibility.limited_shortname=Limitado
settings.visibility.private=Privada (Visível apenas para membros da organização) settings.visibility.private=Privada (Visível apenas para membros da organização)
settings.visibility.private_shortname=Privado settings.visibility.private_shortname=Privado
@ -2288,6 +2380,7 @@ first_page=Primeira
last_page=Última last_page=Última
total=Total: %d total=Total: %d
dashboard.new_version_hint=Gitea %s está disponível, você está executando %s. Verifique o <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blog</a> para mais detalhes.
dashboard.statistic=Resumo dashboard.statistic=Resumo
dashboard.operations=Operações de manutenção dashboard.operations=Operações de manutenção
dashboard.system_status=Status do sistema dashboard.system_status=Status do sistema
@ -2297,13 +2390,28 @@ dashboard.operation_switch=Trocar
dashboard.operation_run=Executar dashboard.operation_run=Executar
dashboard.clean_unbind_oauth=Limpar conexões OAuth não vinculadas dashboard.clean_unbind_oauth=Limpar conexões OAuth não vinculadas
dashboard.clean_unbind_oauth_success=Todas as conexões de OAuth não vinculadas foram excluídas. dashboard.clean_unbind_oauth_success=Todas as conexões de OAuth não vinculadas foram excluídas.
dashboard.task.started=Tarefa Iniciada: %[1]s
dashboard.task.process=Tarefa: %[1]s dashboard.task.process=Tarefa: %[1]s
dashboard.task.cancelled=Tarefa: %[1]s cancelada: %[3]s
dashboard.task.error=Erro na Tarefa: %[1]: %[3]s
dashboard.task.finished=Tarefa: %[1]s iniciada por %[2]s foi finalizada
dashboard.task.unknown=Tarefa desconhecida: %[1]s dashboard.task.unknown=Tarefa desconhecida: %[1]s
dashboard.cron.started=Cron Iniciado: %[1]s
dashboard.cron.process=Cron: %[1]s
dashboard.cron.cancelled=Cron: %s cancelado: %[3]s
dashboard.cron.error=Erro no Cron: %s: %[3]s
dashboard.cron.finished=Cron: %[1]s terminou
dashboard.delete_inactive_accounts=Excluir todas as contas não ativadas dashboard.delete_inactive_accounts=Excluir todas as contas não ativadas
dashboard.delete_inactive_accounts.started=A tarefa de apagar todas as contas não ativadas foi iniciada. dashboard.delete_inactive_accounts.started=A tarefa de apagar todas as contas não ativadas foi iniciada.
dashboard.delete_repo_archives=Excluir todos os arquivos dos repositórios (ZIP, TAR.GZ, etc..)
dashboard.delete_repo_archives.started=A tarefa de remover todos os arquivos foi iniciada.
dashboard.delete_missing_repos=Excluir todos os repositórios que não possuem seus arquivos Git dashboard.delete_missing_repos=Excluir todos os repositórios que não possuem seus arquivos Git
dashboard.delete_missing_repos.started=Foi iniciada a tarefa de excluir todos os repositórios que não têm arquivos Git.
dashboard.delete_generated_repository_avatars=Excluir avatares gerados do repositório dashboard.delete_generated_repository_avatars=Excluir avatares gerados do repositório
dashboard.update_mirrors=Atualizar espelhamentos dashboard.update_mirrors=Atualizar espelhamentos
dashboard.repo_health_check=Verificar estado de saúde de todos os repositórios
dashboard.check_repo_stats=Verificar estatísticas de todos os repositórios
dashboard.archive_cleanup=Apagar arquivos antigos de repositório
dashboard.deleted_branches_cleanup=Realizar limpeza de branches apagados dashboard.deleted_branches_cleanup=Realizar limpeza de branches apagados
dashboard.git_gc_repos=Coleta de lixo em todos os repositórios dashboard.git_gc_repos=Coleta de lixo em todos os repositórios
dashboard.resync_all_sshkeys.desc=(Não necessário para o servidor SSH embutido.) dashboard.resync_all_sshkeys.desc=(Não necessário para o servidor SSH embutido.)
@ -2342,6 +2450,9 @@ dashboard.total_gc_time=Pausa total do GC
dashboard.total_gc_pause=Pausa total do GC dashboard.total_gc_pause=Pausa total do GC
dashboard.last_gc_pause=Última pausa do GC dashboard.last_gc_pause=Última pausa do GC
dashboard.gc_times=Nº de execuções do GC dashboard.gc_times=Nº de execuções do GC
dashboard.delete_old_actions=Excluir todas as ações antigas do banco de dados
dashboard.delete_old_actions.started=A exclusão de todas as ações antigas do banco de dados foi iniciada.
dashboard.update_checker=Verificador de atualização
dashboard.delete_old_system_notices=Excluir todos os avisos de sistema antigos do banco de dados dashboard.delete_old_system_notices=Excluir todos os avisos de sistema antigos do banco de dados
users.user_manage_panel=Gerenciamento de conta de usuário users.user_manage_panel=Gerenciamento de conta de usuário
@ -2413,6 +2524,7 @@ orgs.members=Membros
orgs.new_orga=Nova organização orgs.new_orga=Nova organização
repos.repo_manage_panel=Gerenciamento do repositório repos.repo_manage_panel=Gerenciamento do repositório
repos.unadopted=Repositórios Não Adotados
repos.owner=Proprietário repos.owner=Proprietário
repos.name=Nome repos.name=Nome
repos.private=Privado repos.private=Privado
@ -2432,7 +2544,15 @@ packages.type=Tipo
packages.repository=Repositório packages.repository=Repositório
packages.size=Tamanho packages.size=Tamanho
defaulthooks=Webhooks Padrões
defaulthooks.desc=Webhooks automaticamente fazem requisições HTTP POST para um servidor quando acionados por determinados eventos do Gitea. Webhooks definidos aqui são os padrões e serão copiados para todos os novos repositórios. Leia mais no <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">guia de webhooks</a>.
defaulthooks.add_webhook=Adicionar Webhook Padrão
defaulthooks.update_webhook=Atualizar Webhook Padrão
systemhooks=Webhooks do Sistema
systemhooks.desc=Webhooks automaticamente fazem requisições HTTP POST para um servidor quando acionados por determinados eventos do Gitea. Webhooks definidos aqui agirão em todos os repositórios do sistema, então, por favor, considere quaisquer implicações de desempenho que isso possa ter. Leia mais no <a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">guia de webhooks</a>.
systemhooks.add_webhook=Adicionar Webhook do Sistema
systemhooks.update_webhook=Atualizar Webhook do Sistema
auths.auth_manage_panel=Gerenciamento de fonte de autenticação auths.auth_manage_panel=Gerenciamento de fonte de autenticação
auths.new=Adicionar fonte de autenticação auths.new=Adicionar fonte de autenticação
@ -2480,6 +2600,7 @@ auths.disable_helo=Desativar HELO
auths.pam_service_name=Nome de Serviço PAM auths.pam_service_name=Nome de Serviço PAM
auths.pam_email_domain=Domínio de e-mail do PAM (opcional) auths.pam_email_domain=Domínio de e-mail do PAM (opcional)
auths.oauth2_provider=Provedor OAuth2 auths.oauth2_provider=Provedor OAuth2
auths.oauth2_icon_url=URL do Ícone
auths.oauth2_clientID=ID do cliente (chave) auths.oauth2_clientID=ID do cliente (chave)
auths.oauth2_clientSecret=Senha do cliente auths.oauth2_clientSecret=Senha do cliente
auths.openIdConnectAutoDiscoveryURL=URL do OpenID Connect Auto Discovery auths.openIdConnectAutoDiscoveryURL=URL do OpenID Connect Auto Discovery
@ -2506,6 +2627,7 @@ auths.tips.oauth2.general=Autenticação OAuth2
auths.tips.oauth2.general.tip=Ao cadastrar uma nova autenticação OAuth2, o retorno de chamada/redirecionamento URL deve ser: <host>/user/oauth2/<Nome da Autenticação>/callback auths.tips.oauth2.general.tip=Ao cadastrar uma nova autenticação OAuth2, o retorno de chamada/redirecionamento URL deve ser: <host>/user/oauth2/<Nome da Autenticação>/callback
auths.tip.oauth2_provider=Provedor OAuth2 auths.tip.oauth2_provider=Provedor OAuth2
auths.tip.bitbucket=Cadastrar um novo consumidor de OAuth em https://bitbucket.org/account/user/<seu nome de usuário> e adicionar a permissão 'Account' - 'Read' auths.tip.bitbucket=Cadastrar um novo consumidor de OAuth em https://bitbucket.org/account/user/<seu nome de usuário> e adicionar a permissão 'Account' - 'Read'
auths.tip.nextcloud=Registre um novo consumidor OAuth em sua instância usando o seguinte menu "Configurações -> Segurança -> Cliente OAuth 2.0"
auths.tip.dropbox=Criar um novo aplicativo em https://www.dropbox.com/developers/apps auths.tip.dropbox=Criar um novo aplicativo em https://www.dropbox.com/developers/apps
auths.tip.facebook=Cadastrar um novo aplicativo em https://developers.facebook.com/apps e adicionar o produto "Facebook Login" auths.tip.facebook=Cadastrar um novo aplicativo em https://developers.facebook.com/apps e adicionar o produto "Facebook Login"
auths.tip.github=Cadastrar um novo aplicativo de OAuth na https://github.com/settings/applications/new auths.tip.github=Cadastrar um novo aplicativo de OAuth na https://github.com/settings/applications/new
@ -2515,6 +2637,8 @@ auths.tip.openid_connect=Use o OpenID Connect Discovery URL (<servidor>/.well-kn
auths.tip.twitter=Vá em https://dev.twitter.com/apps, crie um aplicativo e certifique-se de que está habilitada a opção “Allow this application to be used to Sign in with Twitter“ auths.tip.twitter=Vá em https://dev.twitter.com/apps, crie um aplicativo e certifique-se de que está habilitada a opção “Allow this application to be used to Sign in with Twitter“
auths.tip.discord=Cadastrar um novo aplicativo em https://discordapp.com/developers/applications/me auths.tip.discord=Cadastrar um novo aplicativo em https://discordapp.com/developers/applications/me
auths.tip.gitea=Cadastrar um novo aplicativo OAuth2. Guia pode ser encontrado em https://docs.gitea.io/en-us/oauth2-provider/ auths.tip.gitea=Cadastrar um novo aplicativo OAuth2. Guia pode ser encontrado em https://docs.gitea.io/en-us/oauth2-provider/
auths.tip.yandex=Crie um novo aplicativo em https://oauth.yandex.com/client/new. Selecione as seguintes permissões da seção "Yandex.Passport API": "Access to email address", "Access to user avatar" and "Access to username, first name and surname, gender"
auths.tip.mastodon=Insira a URL da instância personalizada do mastodon que você deseja usar para autenticar (ou use o padrão)
auths.edit=Editar fonte de autenticação auths.edit=Editar fonte de autenticação
auths.activated=Esta fonte de autenticação está ativada auths.activated=Esta fonte de autenticação está ativada
auths.new_success=A autenticação '%s' foi adicionada. auths.new_success=A autenticação '%s' foi adicionada.
@ -2670,6 +2794,7 @@ monitor.next=Próxima vez
monitor.previous=Vez anterior monitor.previous=Vez anterior
monitor.execute_times=Execuções monitor.execute_times=Execuções
monitor.process=Processos em execução monitor.process=Processos em execução
monitor.stacktrace=Stacktraces
monitor.goroutines=%d Goroutines monitor.goroutines=%d Goroutines
monitor.desc=Descrição monitor.desc=Descrição
monitor.start=Hora de início monitor.start=Hora de início
@ -2783,6 +2908,7 @@ publish_release=`lançou a versão <a href="%[2]s"> "%[4]s" </a> em <a href="%[1
review_dismissed=`descartou a revisão de <b>%[4]s</b> para <a href="%[1]s">%[3]s#%[2]s</a>` review_dismissed=`descartou a revisão de <b>%[4]s</b> para <a href="%[1]s">%[3]s#%[2]s</a>`
review_dismissed_reason=Motivo: review_dismissed_reason=Motivo:
create_branch=criou o branch <a href="%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a> create_branch=criou o branch <a href="%[2]s">%[3]s</a> em <a href="%[1]s">%[4]s</a>
starred_repo=favoritou <a href="%[1]s">%[2]s</a>
watched_repo=começou a observar <a href="%[1]s">%[2]s</a> watched_repo=começou a observar <a href="%[1]s">%[2]s</a>
[tool] [tool]

View File

@ -663,7 +663,6 @@ ssh_invalid_token_signature=A chave SSH, assinatura ou código fornecidos não c
ssh_token_required=Tem que fornecer uma assinatura para o código abaixo ssh_token_required=Tem que fornecer uma assinatura para o código abaixo
ssh_token=Código ssh_token=Código
ssh_token_help=Pode gerar uma assinatura usando o seguinte comando: ssh_token_help=Pode gerar uma assinatura usando o seguinte comando:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /caminho_para_a_sua_chave_pública
ssh_token_signature=Assinatura SSH blindada (com armadura ASCII) ssh_token_signature=Assinatura SSH blindada (com armadura ASCII)
key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=A chave SSH '%s' foi validada. verify_ssh_key_success=A chave SSH '%s' foi validada.
@ -3024,44 +3023,44 @@ dependency.id=ID
dependency.version=Versão dependency.version=Versão
composer.registry=Configure este registo no seu ficheiro <code>~/.composer/config.json</code>: composer.registry=Configure este registo no seu ficheiro <code>~/.composer/config.json</code>:
composer.install=Para instalar o pacote usando o Composer, execute o seguinte comando: composer.install=Para instalar o pacote usando o Composer, execute o seguinte comando:
composer.documentation=Para obter mais informações sobre o registo do Composer, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/composer/">a documentação</a>. composer.documentation=Para obter mais informações sobre o registo do Composer, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">a documentação</a>.
composer.dependencies=Dependências composer.dependencies=Dependências
composer.dependencies.development=Dependências de desenvolvimento composer.dependencies.development=Dependências de desenvolvimento
conan.details.repository=Repositório conan.details.repository=Repositório
conan.registry=Configurar este registo usando a linha de comandos: conan.registry=Configurar este registo usando a linha de comandos:
conan.install=Para instalar o pacote usando o Conan, execute o seguinte comando: conan.install=Para instalar o pacote usando o Conan, execute o seguinte comando:
conan.documentation=Para obter mais informações sobre o registo do Conan, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/conan/">a documentação</a>. conan.documentation=Para obter mais informações sobre o registo do Conan, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">a documentação</a>.
container.details.type=Tipo de imagem container.details.type=Tipo de imagem
container.details.platform=Plataforma container.details.platform=Plataforma
container.details.repository_site=Página web do repositório container.details.repository_site=Página web do repositório
container.details.documentation_site=Página web da documentação container.details.documentation_site=Página web da documentação
container.pull=Puxar a imagem usando a linha de comandos: container.pull=Puxar a imagem usando a linha de comandos:
container.documentation=Para obter mais informações sobre o registo do Contentor, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/container/">a documentação</a>. container.documentation=Para obter mais informações sobre o registo do Container, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">a documentação</a>.
container.multi_arch=S.O. / Arquit. container.multi_arch=S.O. / Arquit.
container.labels=Rótulos container.labels=Rótulos
container.labels.key=Chave container.labels.key=Chave
container.labels.value=Valor container.labels.value=Valor
generic.download=Descarregar pacote usando a linha de comandos: generic.download=Descarregar pacote usando a linha de comandos:
generic.documentation=Para obter mais informações sobre o registo genérico, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/generic">a documentação</a>. generic.documentation=Para obter mais informações sobre o registo genérico, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">a documentação</a>.
maven.registry=Configure este registo no seu ficheiro <code>pom.xml</code> do projecto: maven.registry=Configure este registo no seu ficheiro <code>pom.xml</code> do projecto:
maven.install=Para usar este pacote, inclua no bloco <code>dependencies</code> do ficheiro <code>pom.xml</code> o seguinte: maven.install=Para usar este pacote, inclua no bloco <code>dependencies</code> do ficheiro <code>pom.xml</code> o seguinte:
maven.install2=Executar usando a linha de comandos: maven.install2=Executar usando a linha de comandos:
maven.download=Para descarregar a dependência, execute na linha de comandos: maven.download=Para descarregar a dependência, execute na linha de comandos:
maven.documentation=Para obter mais informações sobre o registo Maven, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/maven/">a documentação</a>. maven.documentation=Para obter mais informações sobre o registo do Maven, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">a documentação</a>.
nuget.registry=Configurar este registo usando a linha de comandos: nuget.registry=Configurar este registo usando a linha de comandos:
nuget.install=Para instalar o pacote usando NuGet, execute o seguinte comando: nuget.install=Para instalar o pacote usando NuGet, execute o seguinte comando:
nuget.documentation=Para obter mais informações sobre o registo Nuget, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/nuget/">a documentação</a>. nuget.documentation=Para obter mais informações sobre o registo do Nuget, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">a documentação</a>.
npm.install=Para instalar o pacote usando o npm, execute o seguinte comando: npm.install=Para instalar o pacote usando o npm, execute o seguinte comando:
npm.documentation=Para obter mais informações sobre o registo npm, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/npm/">a documentação</a>. npm.documentation=Para obter mais informações sobre o registo do npm, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">a documentação</a>.
npm.dependencies=Dependências npm.dependencies=Dependências
npm.dependencies.development=Dependências de desenvolvimento npm.dependencies.development=Dependências de desenvolvimento
npm.dependencies.optional=Dependências opcionais npm.dependencies.optional=Dependências opcionais
pypi.requires=Requer Python pypi.requires=Requer Python
pypi.install=Para instalar o pacote usando o pip, execute o seguinte comando: pypi.install=Para instalar o pacote usando o pip, execute o seguinte comando:
pypi.documentation=Para obter mais informações sobre o registo do PyPI, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/pypi/">a documentação</a>. pypi.documentation=Para obter mais informações sobre o registo do PyPI, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">a documentação</a>.
rubygems.install=Para instalar o pacote usando o gem, execute o seguinte comando: rubygems.install=Para instalar o pacote usando o gem, execute o seguinte comando:
rubygems.dependencies.development=Dependências de desenvolvimento rubygems.dependencies.development=Dependências de desenvolvimento
rubygems.documentation=Para obter mais informações sobre o registo do RubyGems, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/rubygems/">a documentação</a>. rubygems.documentation=Para obter mais informações sobre o registo do RubyGems, consulte <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">a documentação</a>.
settings.link=Vincular este pacote a um repositório settings.link=Vincular este pacote a um repositório
settings.link.description=Se você vincular um pacote a um repositório, o pacote será listado na lista de pacotes do repositório. settings.link.description=Se você vincular um pacote a um repositório, o pacote será listado na lista de pacotes do repositório.
settings.link.select=Escolha o repositório settings.link.select=Escolha o repositório

View File

@ -632,7 +632,6 @@ ssh_invalid_token_signature=Предоставленный SSH ключ, под
ssh_token_required=Вы должны предоставить подпись для токена ниже ssh_token_required=Вы должны предоставить подпись для токена ниже
ssh_token=Токен ssh_token=Токен
ssh_token_help=Вы можете сгенерировать подпись с помощью: ssh_token_help=Вы можете сгенерировать подпись с помощью:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey
ssh_token_signature=Бронированная SSH подпись ssh_token_signature=Бронированная SSH подпись
key_signature_ssh_placeholder=Начинается с '-----BEGIN SSH SIGNATURE-----' key_signature_ssh_placeholder=Начинается с '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=SSH ключ '%s' проверен. verify_ssh_key_success=SSH ключ '%s' проверен.

View File

@ -664,7 +664,6 @@ ssh_invalid_token_signature=提供的 SSH 密钥、签名或令牌不匹配或
ssh_token_required=您必须为下面的令牌提供签名 ssh_token_required=您必须为下面的令牌提供签名
ssh_token=令牌 ssh_token=令牌
ssh_token_help=您可以使用以下方式生成签名: ssh_token_help=您可以使用以下方式生成签名:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign-n gitea -f /path_to_your_pubkey
ssh_token_signature=增强 SSH 签名 ssh_token_signature=增强 SSH 签名
key_signature_ssh_placeholder=以 '-----BEGIN SSH SIGNATURE -----' 开头 key_signature_ssh_placeholder=以 '-----BEGIN SSH SIGNATURE -----' 开头
verify_ssh_key_success=SSH 密钥 '%s' 已被验证。 verify_ssh_key_success=SSH 密钥 '%s' 已被验证。
@ -3032,39 +3031,39 @@ dependency.id=ID
dependency.version=版本 dependency.version=版本
composer.registry=在您的 <code>~/.composer/config.json</code> 文件中设置此注册中心: composer.registry=在您的 <code>~/.composer/config.json</code> 文件中设置此注册中心:
composer.install=要使用 Composer 安装软件包,请运行以下命令: composer.install=要使用 Composer 安装软件包,请运行以下命令:
composer.documentation=关于 Composer 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/composer/">文档</a>。 composer.documentation=关于 Composer 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/"> 文档 </a>。
composer.dependencies=依赖 composer.dependencies=依赖
composer.dependencies.development=开发依赖 composer.dependencies.development=开发依赖
conan.details.repository=仓库 conan.details.repository=仓库
conan.registry=从命令行设置此注册中心: conan.registry=从命令行设置此注册中心:
conan.install=要使用 Conan 安装软件包,请运行以下命令: conan.install=要使用 Conan 安装软件包,请运行以下命令:
conan.documentation=关于 Conan 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/conan/">文档</a>。 conan.documentation=关于 Conan 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">文档</a>。
container.details.type=镜像类型 container.details.type=镜像类型
container.details.platform=平台 container.details.platform=平台
container.details.repository_site=仓库站点 container.details.repository_site=仓库站点
container.details.documentation_site=文档网站 container.details.documentation_site=文档网站
container.pull=从命令行拉取镜像: container.pull=从命令行拉取镜像:
container.documentation=关于 Container 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/container/"> 文档 </a>。 container.documentation=关于 Container 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/container/">文档</a>。
container.multi_arch=OS / Arch container.multi_arch=OS / Arch
container.layers=镜像层 container.layers=镜像层
container.labels=标签 container.labels=标签
container.labels.key= container.labels.key=
container.labels.value= container.labels.value=
generic.download=从命令行下载软件包: generic.download=从命令行下载软件包:
generic.documentation=关于通用注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/generic"> 文档 </a>。 generic.documentation=关于通用注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/generic">文档</a>。
maven.registry=在您项目的 <code>pom.xml</code> 文件中设置此注册中心: maven.registry=在您项目的 <code>pom.xml</code> 文件中设置此注册中心:
maven.install=要使用这个软件包,在 <code>pom.xml</code> 文件中的 <code>依赖项</code> 块中包含以下内容: maven.install=要使用这个软件包,在 <code>pom.xml</code> 文件中的 <code>依赖项</code> 块中包含以下内容:
maven.install2=通过命令行运行: maven.install2=通过命令行运行:
maven.download=要下载依赖项,请通过命令行运行: maven.download=要下载依赖项,请通过命令行运行:
maven.documentation=关于 Maven 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/maven/">文档</a>。 maven.documentation=关于 Maven 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/maven/">文档</a>。
nuget.registry=从命令行设置此注册中心: nuget.registry=从命令行设置此注册中心:
nuget.install=要使用 Nuget 安装软件包,请运行以下命令: nuget.install=要使用 Nuget 安装软件包,请运行以下命令:
nuget.documentation=关于 Nuget 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/nuget/">文档</a>。 nuget.documentation=关于 Nuget 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/nuget/">文档</a>。
nuget.dependency.framework=目标框架 nuget.dependency.framework=目标框架
npm.registry=在您项目的 <code>.npmrc</code> 文件中设置此注册中心: npm.registry=在您项目的 <code>.npmrc</code> 文件中设置此注册中心:
npm.install=要使用 npm 安装软件包,请运行以下命令: npm.install=要使用 npm 安装软件包,请运行以下命令:
npm.install2=或将其添加到 package.json 文件: npm.install2=或将其添加到 package.json 文件:
npm.documentation=关于 npm 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/npm/">文档</a>。 npm.documentation=关于 npm 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/npm/">文档</a>。
npm.dependencies=依赖项 npm.dependencies=依赖项
npm.dependencies.development=开发依赖 npm.dependencies.development=开发依赖
npm.dependencies.peer=Peer 依赖 npm.dependencies.peer=Peer 依赖
@ -3072,14 +3071,14 @@ npm.dependencies.optional=可选依赖
npm.details.tag=标签 npm.details.tag=标签
pypi.requires=需要 Python pypi.requires=需要 Python
pypi.install=要使用 pip 安装软件包,请运行以下命令: pypi.install=要使用 pip 安装软件包,请运行以下命令:
pypi.documentation=关于 PyPI 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/pypi/">文档</a>。 pypi.documentation=关于 PyPI 注册中心的信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/pypi/">文档</a>。
rubygems.install=要使用 gem 安装软件包,请运行以下命令: rubygems.install=要使用 gem 安装软件包,请运行以下命令:
rubygems.install2=或将它添加到 Gemfile rubygems.install2=或将它添加到 Gemfile
rubygems.dependencies.runtime=运行时依赖 rubygems.dependencies.runtime=运行时依赖
rubygems.dependencies.development=开发依赖 rubygems.dependencies.development=开发依赖
rubygems.required.ruby=需要 Ruby 版本 rubygems.required.ruby=需要 Ruby 版本
rubygems.required.rubygems=需要 RubyGem 版本 rubygems.required.rubygems=需要 RubyGem 版本
rubygems.documentation=关于 RubyGems 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/rubygems/"> 文档 </a>。 rubygems.documentation=关于 RubyGems 注册中心的更多信息,请参阅 <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/rubygems/">文档</a>。
settings.link=将此软件包链接到仓库 settings.link=将此软件包链接到仓库
settings.link.description=如果您将一个软件包与一个代码库链接起来,软件包将显示在代码库的软件包列表中。 settings.link.description=如果您将一个软件包与一个代码库链接起来,软件包将显示在代码库的软件包列表中。
settings.link.select=选择仓库 settings.link.select=选择仓库

View File

@ -662,7 +662,6 @@ ssh_invalid_token_signature=提供的 SSH 金鑰、簽署、Token 不符合或 T
ssh_token_required=您必須為下列的 Token 提供簽署 ssh_token_required=您必須為下列的 Token 提供簽署
ssh_token=Token ssh_token=Token
ssh_token_help=您可以使用以下方法產生簽署: ssh_token_help=您可以使用以下方法產生簽署:
ssh_token_code=echo -n "%s" | ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey
ssh_token_signature=Armored SSH 簽署 ssh_token_signature=Armored SSH 簽署
key_signature_ssh_placeholder=以「-----BEGIN SSH SIGNATURE-----」開頭 key_signature_ssh_placeholder=以「-----BEGIN SSH SIGNATURE-----」開頭
verify_ssh_key_success=已驗證 SSH 金鑰「%s」。 verify_ssh_key_success=已驗證 SSH 金鑰「%s」。

View File

@ -63,8 +63,8 @@ func SearchPackages(ctx *context.Context) {
opts := &packages_model.PackageSearchOptions{ opts := &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Type: string(packages_model.TypeComposer), Type: packages_model.TypeComposer,
QueryName: ctx.FormTrim("q"), Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
Paginator: &paginator, Paginator: &paginator,
} }
if ctx.FormTrim("type") != "" { if ctx.FormTrim("type") != "" {

View File

@ -256,7 +256,12 @@ func setPackageTag(tag string, pv *packages_model.PackageVersion, deleteOnly boo
} }
defer committer.Close() defer committer.Close()
pvs, err := packages_model.FindVersionsByPropertyNameAndValue(ctx, pv.PackageID, npm_module.TagProperty, tag) pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: pv.PackageID,
Properties: map[string]string{
npm_module.TagProperty: tag,
},
})
if err != nil { if err != nil {
return err return err
} }

View File

@ -39,9 +39,9 @@ func ServiceIndex(ctx *context.Context) {
// SearchService https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages // SearchService https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchService(ctx *context.Context) { func SearchService(ctx *context.Context) {
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Type: string(packages_model.TypeNuGet), Type: packages_model.TypeNuGet,
QueryName: ctx.FormTrim("q"), Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
Paginator: db.NewAbsoluteListOptions( Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("skip"), ctx.FormInt("skip"),
ctx.FormInt("take"), ctx.FormInt("take"),

View File

@ -41,7 +41,7 @@ func EnumeratePackages(ctx *context.Context) {
func EnumeratePackagesLatest(ctx *context.Context) { func EnumeratePackagesLatest(ctx *context.Context) {
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Type: string(packages_model.TypeRubyGems), Type: packages_model.TypeRubyGems,
}) })
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
@ -96,7 +96,7 @@ func ServePackageSpecification(ctx *context.Context) {
return return
} }
pvs, err := packages_model.GetVersionsByFilename(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, filename[:len(filename)-10]+"gem") pvs, err := getVersionsByFilename(ctx, filename[:len(filename)-10]+"gem")
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
@ -158,7 +158,7 @@ func ServePackageSpecification(ctx *context.Context) {
func DownloadPackageFile(ctx *context.Context) { func DownloadPackageFile(ctx *context.Context) {
filename := ctx.Params("filename") filename := ctx.Params("filename")
pvs, err := packages_model.GetVersionsByFilename(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, filename) pvs, err := getVersionsByFilename(ctx, filename)
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
@ -283,3 +283,12 @@ func DeletePackage(ctx *context.Context) {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
} }
} }
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeRubyGems,
HasFileWithName: filename,
})
return pvs, err
}

View File

@ -216,7 +216,6 @@ func reqToken() func(ctx *context.APIContext) {
return return
} }
if ctx.IsSigned { if ctx.IsSigned {
ctx.RequireCSRF()
return return
} }
ctx.Error(http.StatusUnauthorized, "reqToken", "token is required") ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
@ -584,8 +583,7 @@ func bind(obj interface{}) http.HandlerFunc {
func buildAuthGroup() *auth.Group { func buildAuthGroup() *auth.Group {
group := auth.NewGroup( group := auth.NewGroup(
&auth.OAuth2{}, &auth.OAuth2{},
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API &auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
auth.SharedSession, // FIXME: this should be removed once all UI don't reference API/v1, see https://github.com/go-gitea/gitea/pull/16052
) )
if setting.Service.EnableReverseProxyAuth { if setting.Service.EnableReverseProxyAuth {
group.Add(&auth.ReverseProxy{}) group.Add(&auth.ReverseProxy{})
@ -596,11 +594,9 @@ func buildAuthGroup() *auth.Group {
} }
// Routes registers all v1 APIs routes to web application. // Routes registers all v1 APIs routes to web application.
func Routes(sessioner func(http.Handler) http.Handler) *web.Route { func Routes() *web.Route {
m := web.NewRoute() m := web.NewRoute()
m.Use(sessioner)
m.Use(securityHeaders()) m.Use(securityHeaders())
if setting.CORSConfig.Enabled { if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{ m.Use(cors.Handler(cors.Options{
@ -609,7 +605,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option // setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
AllowedMethods: setting.CORSConfig.Methods, AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials, AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: []string{"Authorization", "X-CSRFToken", "X-Gitea-OTP"}, AllowedHeaders: []string{"Authorization", "X-Gitea-OTP"},
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()), MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
})) }))
} }

View File

@ -26,7 +26,7 @@ func NewAvailable(ctx *context.APIContext) {
} }
func getFindNotificationOptions(ctx *context.APIContext) *models.FindNotificationOptions { func getFindNotificationOptions(ctx *context.APIContext) *models.FindNotificationOptions {
before, since, err := utils.GetQueryBeforeSince(ctx) before, since, err := context.GetQueryBeforeSince(ctx.Context)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return nil return nil

View File

@ -56,8 +56,8 @@ func ListPackages(ctx *context.APIContext) {
pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{ pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Type: packageType, Type: packages.Type(packageType),
QueryName: query, Name: packages.SearchValue{Value: query},
Paginator: &listOptions, Paginator: &listOptions,
}) })
if err != nil { if err != nil {

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -111,7 +112,7 @@ func SearchIssues(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/IssueList" // "$ref": "#/responses/IssueList"
before, since, err := utils.GetQueryBeforeSince(ctx) before, since, err := context.GetQueryBeforeSince(ctx.Context)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
@ -359,7 +360,7 @@ func ListIssues(ctx *context.APIContext) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/IssueList" // "$ref": "#/responses/IssueList"
before, since, err := utils.GetQueryBeforeSince(ctx) before, since, err := context.GetQueryBeforeSince(ctx.Context)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
@ -405,12 +406,12 @@ func ListIssues(ctx *context.APIContext) {
for i := range part { for i := range part {
// uses names and fall back to ids // uses names and fall back to ids
// non existent milestones are discarded // non existent milestones are discarded
mile, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i]) mile, err := issues_model.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i])
if err == nil { if err == nil {
mileIDs = append(mileIDs, mile.ID) mileIDs = append(mileIDs, mile.ID)
continue continue
} }
if !models.IsErrMilestoneNotExist(err) { if !issues_model.IsErrMilestoneNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoIDANDName", err) ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoIDANDName", err)
return return
} }
@ -418,12 +419,12 @@ func ListIssues(ctx *context.APIContext) {
if err != nil { if err != nil {
continue continue
} }
mile, err = models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, id) mile, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, id)
if err == nil { if err == nil {
mileIDs = append(mileIDs, mile.ID) mileIDs = append(mileIDs, mile.ID)
continue continue
} }
if models.IsErrMilestoneNotExist(err) { if issues_model.IsErrMilestoneNotExist(err) {
continue continue
} }
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)

View File

@ -58,7 +58,7 @@ func ListIssueComments(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/CommentList" // "$ref": "#/responses/CommentList"
before, since, err := utils.GetQueryBeforeSince(ctx) before, since, err := context.GetQueryBeforeSince(ctx.Context)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
@ -150,7 +150,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/TimelineList" // "$ref": "#/responses/TimelineList"
before, since, err := utils.GetQueryBeforeSince(ctx) before, since, err := context.GetQueryBeforeSince(ctx.Context)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
@ -253,7 +253,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/CommentList" // "$ref": "#/responses/CommentList"
before, since, err := utils.GetQueryBeforeSince(ctx) before, since, err := context.GetQueryBeforeSince(ctx.Context)
if err != nil { if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return

View File

@ -103,7 +103,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
opts.UserID = user.ID opts.UserID = user.ID
} }
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil { if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
} }
@ -522,7 +522,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
} }
var err error var err error
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil { if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
} }
@ -597,7 +597,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
} }
var err error var err error
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil { if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
} }

View File

@ -10,7 +10,7 @@ import (
"strconv" "strconv"
"time" "time"
"code.gitea.io/gitea/models" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -57,7 +57,7 @@ func ListMilestones(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/MilestoneList" // "$ref": "#/responses/MilestoneList"
milestones, total, err := models.GetMilestones(models.GetMilestonesOption{ milestones, total, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
ListOptions: utils.GetListOptions(ctx), ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
State: api.StateType(ctx.FormString("state")), State: api.StateType(ctx.FormString("state")),
@ -146,7 +146,7 @@ func CreateMilestone(ctx *context.APIContext) {
form.Deadline = &defaultDeadline form.Deadline = &defaultDeadline
} }
milestone := &models.Milestone{ milestone := &issues_model.Milestone{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Name: form.Title, Name: form.Title,
Content: form.Description, Content: form.Description,
@ -158,7 +158,7 @@ func CreateMilestone(ctx *context.APIContext) {
milestone.ClosedDateUnix = timeutil.TimeStampNow() milestone.ClosedDateUnix = timeutil.TimeStampNow()
} }
if err := models.NewMilestone(milestone); err != nil { if err := issues_model.NewMilestone(milestone); err != nil {
ctx.Error(http.StatusInternalServerError, "NewMilestone", err) ctx.Error(http.StatusInternalServerError, "NewMilestone", err)
return return
} }
@ -218,7 +218,7 @@ func EditMilestone(ctx *context.APIContext) {
milestone.IsClosed = *form.State == string(api.StateClosed) milestone.IsClosed = *form.State == string(api.StateClosed)
} }
if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil { if err := issues_model.UpdateMilestone(milestone, oldIsClosed); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err) ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
return return
} }
@ -255,7 +255,7 @@ func DeleteMilestone(ctx *context.APIContext) {
return return
} }
if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, m.ID); err != nil { if err := issues_model.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, m.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err) ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err)
return return
} }
@ -263,23 +263,23 @@ func DeleteMilestone(ctx *context.APIContext) {
} }
// getMilestoneByIDOrName get milestone by ID and if not available by name // getMilestoneByIDOrName get milestone by ID and if not available by name
func getMilestoneByIDOrName(ctx *context.APIContext) *models.Milestone { func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
mile := ctx.Params(":id") mile := ctx.Params(":id")
mileID, _ := strconv.ParseInt(mile, 0, 64) mileID, _ := strconv.ParseInt(mile, 0, 64)
if mileID != 0 { if mileID != 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, mileID) milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, mileID)
if err == nil { if err == nil {
return milestone return milestone
} else if !models.IsErrMilestoneNotExist(err) { } else if !issues_model.IsErrMilestoneNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
return nil return nil
} }
} }
milestone, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, mile) milestone, err := issues_model.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, mile)
if err != nil { if err != nil {
if models.IsErrMilestoneNotExist(err) { if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound() ctx.NotFound()
return nil return nil
} }

View File

@ -14,6 +14,7 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -342,9 +343,9 @@ func CreatePullRequest(ctx *context.APIContext) {
} }
if form.Milestone > 0 { if form.Milestone > 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, form.Milestone) milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
if err != nil { if err != nil {
if models.IsErrMilestoneNotExist(err) { if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err) ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)

View File

@ -75,7 +75,7 @@ func ListPullReviews(ctx *context.APIContext) {
return return
} }
if err = pr.Issue.LoadRepo(); err != nil { if err = pr.Issue.LoadRepo(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadRepo", err) ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
return return
} }
@ -322,7 +322,7 @@ func CreatePullReview(ctx *context.APIContext) {
return return
} }
if err := pr.Issue.LoadRepo(); err != nil { if err := pr.Issue.LoadRepo(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err) ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
return return
} }
@ -657,7 +657,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
return return
} }
if err := pr.Issue.LoadRepo(); err != nil { if err := pr.Issue.LoadRepo(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err) ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
return return
} }

View File

@ -31,23 +31,6 @@ import (
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
) )
var searchOrderByMap = map[string]map[string]db.SearchOrderBy{
"asc": {
"alpha": db.SearchOrderByAlphabetically,
"created": db.SearchOrderByOldest,
"updated": db.SearchOrderByLeastUpdated,
"size": db.SearchOrderBySize,
"id": db.SearchOrderByID,
},
"desc": {
"alpha": db.SearchOrderByAlphabeticallyReverse,
"created": db.SearchOrderByNewest,
"updated": db.SearchOrderByRecentUpdated,
"size": db.SearchOrderBySizeReverse,
"id": db.SearchOrderByIDReverse,
},
}
// Search repositories via options // Search repositories via options
func Search(ctx *context.APIContext) { func Search(ctx *context.APIContext) {
// swagger:operation GET /repos/search repository repoSearch // swagger:operation GET /repos/search repository repoSearch
@ -193,7 +176,7 @@ func Search(ctx *context.APIContext) {
if len(sortOrder) == 0 { if len(sortOrder) == 0 {
sortOrder = "asc" sortOrder = "asc"
} }
if searchModeMap, ok := searchOrderByMap[sortOrder]; ok { if searchModeMap, ok := context.SearchOrderByMap[sortOrder]; ok {
if orderBy, ok := searchModeMap[sortMode]; ok { if orderBy, ok := searchModeMap[sortMode]; ok {
opts.OrderBy = orderBy opts.OrderBy = orderBy
} else { } else {

View File

@ -0,0 +1,19 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package utils
import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
)
// GetListOptions returns list options using the page and limit parameters
func GetListOptions(ctx *context.APIContext) db.ListOptions {
return db.ListOptions{
Page: ctx.FormInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
}
}

View File

@ -48,8 +48,6 @@ import (
"code.gitea.io/gitea/services/repository/archiver" "code.gitea.io/gitea/services/repository/archiver"
"code.gitea.io/gitea/services/task" "code.gitea.io/gitea/services/task"
"code.gitea.io/gitea/services/webhook" "code.gitea.io/gitea/services/webhook"
"gitea.com/go-chi/session"
) )
func mustInit(fn func() error) { func mustInit(fn func() error) {
@ -174,20 +172,8 @@ func NormalRoutes() *web.Route {
r.Use(middle) r.Use(middle)
} }
sessioner := session.Sessioner(session.Options{ r.Mount("/", web_routers.Routes())
Provider: setting.SessionConfig.Provider, r.Mount("/api/v1", apiv1.Routes())
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
CookiePath: setting.SessionConfig.CookiePath,
Gclifetime: setting.SessionConfig.Gclifetime,
Maxlifetime: setting.SessionConfig.Maxlifetime,
Secure: setting.SessionConfig.Secure,
SameSite: setting.SessionConfig.SameSite,
Domain: setting.SessionConfig.Domain,
})
r.Mount("/", web_routers.Routes(sessioner))
r.Mount("/api/v1", apiv1.Routes(sessioner))
r.Mount("/api/internal", private.Routes()) r.Mount("/api/internal", private.Routes())
if setting.Packages.Enabled { if setting.Packages.Enabled {
r.Mount("/api/packages", packages_router.Routes()) r.Mount("/api/packages", packages_router.Routes())

View File

@ -31,9 +31,9 @@ func Packages(ctx *context.Context) {
sort := ctx.FormTrim("sort") sort := ctx.FormTrim("sort")
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
QueryName: query, Type: packages_model.Type(packageType),
Type: packageType, Name: packages_model.SearchValue{Value: query},
Sort: sort, Sort: sort,
Paginator: &db.ListOptions{ Paginator: &db.ListOptions{
PageSize: setting.UI.PackagesPagingNum, PageSize: setting.UI.PackagesPagingNum,
Page: page, Page: page,

View File

@ -345,7 +345,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
} }
// Clear whatever CSRF has right now, force to generate a new one // Clear whatever CSRF cookie has right now, force to generate a new one
middleware.DeleteCSRFCookie(ctx.Resp) middleware.DeleteCSRFCookie(ctx.Resp)
// Register last login // Register last login

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