Compare commits
25 Commits
v1.13.0-rc2
...
v1.13.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d551152582 | ||
|
|
f677ed628b | ||
|
|
07629bd55c | ||
|
|
d475b656b1 | ||
|
|
6e14773c44 | ||
|
|
25421f08c0 | ||
|
|
bdb491e764 | ||
|
|
a82c7d4323 | ||
|
|
7ec1c13f53 | ||
|
|
4c9d00cf78 | ||
|
|
33431fcbd3 | ||
|
|
f2a3a9117e | ||
|
|
ef7a52826d | ||
|
|
e0d28e2026 | ||
|
|
2f6dad2e34 | ||
|
|
bcde51f4c2 | ||
|
|
ed3a4cd103 | ||
|
|
c6ab79ee3c | ||
|
|
48fca01b0d | ||
|
|
9a8e02ce30 | ||
|
|
159a4db30a | ||
|
|
b4d18dae19 | ||
|
|
ee0097f97d | ||
|
|
122f8f86d5 | ||
|
|
1f72656892 |
77
CHANGELOG.md
77
CHANGELOG.md
@@ -4,46 +4,17 @@ This changelog goes through all the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
|
||||
## [1.13.0-rc2](https://github.com/go-gitea/gitea/releases/tag/v1.13.0-rc2) - 2020-11-10
|
||||
* ENHANCEMENTS
|
||||
* Return the full rejection message and errors in flash errors (#13221) (#13237)
|
||||
* Remove PAM from auth dropdown when unavailable (#13276) (#13281)
|
||||
* BUGFIXES
|
||||
* Fix Italian language file parsing error (#13156)
|
||||
* Show outdated comments in pull request (#13148) (#13162)
|
||||
* Fix parsing of pre-release git version (#13169) (#13172)
|
||||
* Fix diff skipping lines (#13154) (#13155)
|
||||
* When handling errors in storageHandler check underlying error (#13178) (#13193)
|
||||
* Fix size and clickable area on file table back link (#13205) (#13207)
|
||||
* Add better error checking for inline html diff code (#13251)
|
||||
* Fix initial commit page & binary munching problem (#13249) (#13258)
|
||||
* Fix migrations from remote Gitea instances when configuration not set (#13229) (#13273)
|
||||
* Store task errors following migrations and display them (#13246) (#13287)
|
||||
* Fix bug isEnd detection on getIssues/getPullRequests (#13299) (#13301)
|
||||
* When the git ref is unable to be found return broken pr (#13218) (#13303)
|
||||
* Ensure topics added using the API are added to the repository (#13285) (#13302)
|
||||
* Fix avatar autogeneration (#13233) (#13282)
|
||||
* Add migrated pulls to pull request task queue (#13331) (#13334)
|
||||
* Issue comment reactions should also check pull type on API (#13349) (#13350)
|
||||
* Fix links to repositories in /user/setting/repos (#13360) (#13362)
|
||||
* Remove obsolete change of email on profile page (#13341) (#13347)
|
||||
* Fix scrolling to resolved comment anchors (#13343) (#13371)
|
||||
* Storage configuration support `[storage]` (#13314) (#13379)
|
||||
* When creating line diffs do not split within an html entity (#13357) (#13375) (#13425) (#13427)
|
||||
* Fix reactions on code comments (#13390) (#13401)
|
||||
* Add missing full names when DEFAULT_SHOW_FULL_NAME is enabled (#13424)
|
||||
* Replies to outdated code comments should also be outdated (#13217) (#13433)
|
||||
* Fix panic bug in handling multiple references in commit (#13486) (#13487)
|
||||
* Prevent panic on git blame by limiting lines to 4096 bytes at most (#13470) (#13491)
|
||||
|
||||
## [1.13.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.13.0-rc1) - 2020-10-14
|
||||
|
||||
## [1.13.0](https://github.com/go-gitea/gitea/releases/tag/v1.13.0) - 2020-12-01
|
||||
* SECURITY
|
||||
* Add Allow-/Block-List for Migrate & Mirrors (#13610) (#13776)
|
||||
* Prevent git operations for inactive users (#13527) (#13536)
|
||||
* Disallow urlencoded new lines in git protocol paths if there is a port (#13521) (#13524)
|
||||
* Mitigate Security vulnerability in the git hook feature (#13058)
|
||||
* Disable DSA ssh keys by default (#13056)
|
||||
* Set TLS minimum version to 1.2 (#12689)
|
||||
* Use argon as default password hash algorithm (#12688)
|
||||
* BREAKING
|
||||
* Set RUN_MODE prod by default (#13765) (#13767)
|
||||
* Don't replace underscores in auto-generated IDs in goldmark (#12805)
|
||||
* Add Primary Key to Topic and RepoTopic tables (#12639)
|
||||
* Disable password complexity check default (#12557)
|
||||
@@ -103,6 +74,40 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Add endpoint for Branch Creation (#11607)
|
||||
* Add pagination headers on endpoints that support total count from database (#11145)
|
||||
* BUGFIXES
|
||||
* Fix bogus http requests on diffs (#13760) (#13761)
|
||||
* Show 'owner' tag for real owner (#13689) (#13743)
|
||||
* Validate email before inserting/updating (#13475) (#13666)
|
||||
* Fix issue/pull request list assignee filter (#13647) (#13651)
|
||||
* Gitlab migration support for subdirectories (#13563) (#13591)
|
||||
* Fix logic for preferred license setting (#13550) (#13557)
|
||||
* Add missed sync branch/tag webhook (#13538) (#13556)
|
||||
* Migration won't fail on non-migrated reactions (#13507)
|
||||
* Fix Italian language file parsing error (#13156)
|
||||
* Show outdated comments in pull request (#13148) (#13162)
|
||||
* Fix parsing of pre-release git version (#13169) (#13172)
|
||||
* Fix diff skipping lines (#13154) (#13155)
|
||||
* When handling errors in storageHandler check underlying error (#13178) (#13193)
|
||||
* Fix size and clickable area on file table back link (#13205) (#13207)
|
||||
* Add better error checking for inline html diff code (#13251)
|
||||
* Fix initial commit page & binary munching problem (#13249) (#13258)
|
||||
* Fix migrations from remote Gitea instances when configuration not set (#13229) (#13273)
|
||||
* Store task errors following migrations and display them (#13246) (#13287)
|
||||
* Fix bug isEnd detection on getIssues/getPullRequests (#13299) (#13301)
|
||||
* When the git ref is unable to be found return broken pr (#13218) (#13303)
|
||||
* Ensure topics added using the API are added to the repository (#13285) (#13302)
|
||||
* Fix avatar autogeneration (#13233) (#13282)
|
||||
* Add migrated pulls to pull request task queue (#13331) (#13334)
|
||||
* Issue comment reactions should also check pull type on API (#13349) (#13350)
|
||||
* Fix links to repositories in /user/setting/repos (#13360) (#13362)
|
||||
* Remove obsolete change of email on profile page (#13341) (#13347)
|
||||
* Fix scrolling to resolved comment anchors (#13343) (#13371)
|
||||
* Storage configuration support `[storage]` (#13314) (#13379)
|
||||
* When creating line diffs do not split within an html entity (#13357) (#13375) (#13425) (#13427)
|
||||
* Fix reactions on code comments (#13390) (#13401)
|
||||
* Add missing full names when DEFAULT_SHOW_FULL_NAME is enabled (#13424)
|
||||
* Replies to outdated code comments should also be outdated (#13217) (#13433)
|
||||
* Fix panic bug in handling multiple references in commit (#13486) (#13487)
|
||||
* Prevent panic on git blame by limiting lines to 4096 bytes at most (#13470) (#13491)
|
||||
* Show original author's reviews on pull summary box (#13127)
|
||||
* Update golangci-lint to version 1.31.0 (#13102)
|
||||
* Fix line break for MS teams webhook (#13081)
|
||||
@@ -172,6 +177,10 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Fix Enter not working in SimpleMDE (#11564)
|
||||
* Fix bug about can't skip commits base on base branch (#11555)
|
||||
* ENHANCEMENTS
|
||||
* Only Return JSON for responses (#13511) (#13565)
|
||||
* Use existing analyzer module for language detection for highlighting (#13522) (#13551)
|
||||
* Return the full rejection message and errors in flash errors (#13221) (#13237)
|
||||
* Remove PAM from auth dropdown when unavailable (#13276) (#13281)
|
||||
* Add HostCertificate to sshd_config in Docker image (#13143)
|
||||
* Save TimeStamps for Star, Label, Follow, Watch and Collaboration to Database (#13124)
|
||||
* Improve error feedback for duplicate deploy keys (#13112)
|
||||
|
||||
4
Makefile
4
Makefile
@@ -638,8 +638,8 @@ fomantic: $(FOMANTIC_DEST)
|
||||
|
||||
$(FOMANTIC_DEST): $(FOMANTIC_CONFIGS) | node_modules
|
||||
rm -rf $(FOMANTIC_DEST_DIR)
|
||||
cp web_src/fomantic/theme.config.less node_modules/fomantic-ui/src/theme.config
|
||||
cp -r web_src/fomantic/_site/* node_modules/fomantic-ui/src/_site/
|
||||
cp -f web_src/fomantic/theme.config.less node_modules/fomantic-ui/src/theme.config
|
||||
cp -fr web_src/fomantic/_site/* node_modules/fomantic-ui/src/_site/
|
||||
npx gulp -f node_modules/fomantic-ui/gulpfile.js build
|
||||
@touch $(FOMANTIC_DEST)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
APP_NAME = Gitea: Git with a cup of tea
|
||||
; Change it if you run locally
|
||||
RUN_USER = git
|
||||
; Either "dev", "prod" or "test", default is "dev"
|
||||
RUN_MODE = dev
|
||||
; Application run mode, affects performance and debugging. Either "dev", "prod" or "test", default is "prod"
|
||||
RUN_MODE = prod
|
||||
|
||||
[project]
|
||||
; Default templates for project boards
|
||||
@@ -1188,6 +1188,14 @@ QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"
|
||||
MAX_ATTEMPTS = 3
|
||||
; Backoff time per http/https request retry (seconds)
|
||||
RETRY_BACKOFF = 3
|
||||
; Allowed domains for migrating, default is blank. Blank means everything will be allowed.
|
||||
; Multiple domains could be separated by commas.
|
||||
ALLOWED_DOMAINS =
|
||||
; Blocklist for migrating, default is blank. Multiple domains could be separated by commas.
|
||||
; When ALLOWED_DOMAINS is not blank, this option will be ignored.
|
||||
BLOCKED_DOMAINS =
|
||||
; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291 (false by default)
|
||||
ALLOW_LOCALNETWORKS = false
|
||||
|
||||
; default storage for attachments, lfs and avatars
|
||||
[storage]
|
||||
|
||||
@@ -25,7 +25,7 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
|
||||
|
||||
# Substitude the environment variables in the template
|
||||
APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \
|
||||
RUN_MODE=${RUN_MODE:-"dev"} \
|
||||
RUN_MODE=${RUN_MODE:-"prod"} \
|
||||
DOMAIN=${DOMAIN:-"localhost"} \
|
||||
SSH_DOMAIN=${SSH_DOMAIN:-"localhost"} \
|
||||
HTTP_PORT=${HTTP_PORT:-"3000"} \
|
||||
|
||||
@@ -36,9 +36,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||
- `APP_NAME`: **Gitea: Git with a cup of tea**: Application name, used in the page title.
|
||||
- `RUN_USER`: **git**: The user Gitea will run as. This should be a dedicated system
|
||||
(non-user) account. Setting this incorrectly will cause Gitea to not start.
|
||||
- `RUN_MODE`: **dev**: For performance and other purposes, change this to `prod` when
|
||||
deployed to a production environment. The installation process will set this to `prod`
|
||||
automatically. \[prod, dev, test\]
|
||||
- `RUN_MODE`: **prod**: Application run mode, affects performance and debugging. Either "dev", "prod" or "test".
|
||||
|
||||
## Repository (`repository`)
|
||||
|
||||
@@ -813,6 +811,9 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
|
||||
|
||||
- `MAX_ATTEMPTS`: **3**: Max attempts per http/https request on migrations.
|
||||
- `RETRY_BACKOFF`: **3**: Backoff time per http/https request retry (seconds)
|
||||
- `ALLOWED_DOMAINS`: **\<empty\>**: Domains allowlist for migrating repositories, default is blank. It means everything will be allowed. Multiple domains could be separated by commas.
|
||||
- `BLOCKED_DOMAINS`: **\<empty\>**: Domains blocklist for migrating repositories, default is blank. Multiple domains could be separated by commas. When `ALLOWED_DOMAINS` is not blank, this option will be ignored.
|
||||
- `ALLOW_LOCALNETWORKS`: **false**: Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291
|
||||
|
||||
## Mirror (`mirror`)
|
||||
|
||||
|
||||
@@ -313,6 +313,9 @@ IS_INPUT_FILE = false
|
||||
|
||||
- `MAX_ATTEMPTS`: **3**: 在迁移过程中的 http/https 请求重试次数。
|
||||
- `RETRY_BACKOFF`: **3**: 等待下一次重试的时间,单位秒。
|
||||
- `ALLOWED_DOMAINS`: **\<empty\>**: 迁移仓库的域名白名单,默认为空,表示允许从任意域名迁移仓库,多个域名用逗号分隔。
|
||||
- `BLOCKED_DOMAINS`: **\<empty\>**: 迁移仓库的域名黑名单,默认为空,多个域名用逗号分隔。如果 `ALLOWED_DOMAINS` 不为空,此选项将会被忽略。
|
||||
- `ALLOW_LOCALNETWORKS`: **false**: Allow private addresses defined by RFC 1918
|
||||
|
||||
## LFS (`lfs`)
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ You can configure some of Gitea's settings via environment variables:
|
||||
(Default values are provided in **bold**)
|
||||
|
||||
* `APP_NAME`: **"Gitea: Git with a cup of tea"**: Application name, used in the page title.
|
||||
* `RUN_MODE`: **dev**: For performance and other purposes, change this to `prod` when deployed to a production environment.
|
||||
* `RUN_MODE`: **prod**: Application run mode, affects performance and debugging. Either "dev", "prod" or "test".
|
||||
* `DOMAIN`: **localhost**: Domain name of this server, used for the displayed http clone URL in Gitea's UI.
|
||||
* `SSH_DOMAIN`: **localhost**: Domain name of this server, used for the displayed ssh clone URL in Gitea's UI. If the install page is enabled, SSH Domain Server takes DOMAIN value in the form (which overwrite this setting on save).
|
||||
* `SSH_PORT`: **22**: SSH port displayed in clone URL.
|
||||
|
||||
@@ -144,3 +144,22 @@ func TestAPIListUsersNonAdmin(t *testing.T) {
|
||||
req := NewRequestf(t, "GET", "/api/v1/admin/users?token=%s", token)
|
||||
session.MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestAPICreateUserInvalidEmail(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
adminUsername := "user1"
|
||||
session := loginUser(t, adminUsername)
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token)
|
||||
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
|
||||
"email": "invalid_email@domain.com\r\n",
|
||||
"full_name": "invalid user",
|
||||
"login_name": "invalidUser",
|
||||
"must_change_password": "true",
|
||||
"password": "password",
|
||||
"send_notify": "true",
|
||||
"source_id": "0",
|
||||
"username": "invalidUser",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
@@ -309,6 +309,8 @@ func TestAPIRepoMigrate(t *testing.T) {
|
||||
{ctxUserID: 2, userID: 1, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad", expectedStatus: http.StatusForbidden},
|
||||
{ctxUserID: 2, userID: 3, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-org", expectedStatus: http.StatusCreated},
|
||||
{ctxUserID: 2, userID: 6, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad-org", expectedStatus: http.StatusForbidden},
|
||||
{ctxUserID: 2, userID: 3, cloneURL: "https://localhost:3000/user/test_repo.git", repoName: "local-ip", expectedStatus: http.StatusUnprocessableEntity},
|
||||
{ctxUserID: 2, userID: 3, cloneURL: "https://10.0.0.1/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity},
|
||||
}
|
||||
|
||||
defer prepareTestEnv(t)()
|
||||
@@ -325,8 +327,16 @@ func TestAPIRepoMigrate(t *testing.T) {
|
||||
if resp.Code == http.StatusUnprocessableEntity {
|
||||
respJSON := map[string]string{}
|
||||
DecodeJSON(t, resp, &respJSON)
|
||||
if assert.Equal(t, respJSON["message"], "Remote visit addressed rate limitation.") {
|
||||
switch respJSON["message"] {
|
||||
case "Remote visit addressed rate limitation.":
|
||||
t.Log("test hit github rate limitation")
|
||||
case "migrate from '10.0.0.1' is not allowed: the host resolve to a private ip address '10.0.0.1'":
|
||||
assert.EqualValues(t, "private-ip", testCase.repoName)
|
||||
case "migrate from 'localhost:3000' is not allowed: the host resolve to a private ip address '::1'",
|
||||
"migrate from 'localhost:3000' is not allowed: the host resolve to a private ip address '127.0.0.1'":
|
||||
assert.EqualValues(t, "local-ip", testCase.repoName)
|
||||
default:
|
||||
t.Errorf("unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL)
|
||||
}
|
||||
} else {
|
||||
assert.EqualValues(t, testCase.expectedStatus, resp.Code)
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/unknwon/i18n"
|
||||
)
|
||||
|
||||
func TestSignup(t *testing.T) {
|
||||
@@ -28,3 +32,37 @@ func TestSignup(t *testing.T) {
|
||||
req = NewRequest(t, "GET", "/exampleUser")
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestSignupEmail(t *testing.T) {
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
setting.Service.EnableCaptcha = false
|
||||
|
||||
tests := []struct {
|
||||
email string
|
||||
wantStatus int
|
||||
wantMsg string
|
||||
}{
|
||||
{"exampleUser@example.com\r\n", http.StatusOK, i18n.Tr("en", "form.email_invalid", nil)},
|
||||
{"exampleUser@example.com\r", http.StatusOK, i18n.Tr("en", "form.email_invalid", nil)},
|
||||
{"exampleUser@example.com\n", http.StatusOK, i18n.Tr("en", "form.email_invalid", nil)},
|
||||
{"exampleUser@example.com", http.StatusFound, ""},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
|
||||
"user_name": fmt.Sprintf("exampleUser%d", i),
|
||||
"email": test.email,
|
||||
"password": "examplePassword!1",
|
||||
"retype": "examplePassword!1",
|
||||
})
|
||||
resp := MakeRequest(t, req, test.wantStatus)
|
||||
if test.wantMsg != "" {
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t,
|
||||
test.wantMsg,
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,21 @@ func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
|
||||
type ErrEmailInvalid struct {
|
||||
Email string
|
||||
}
|
||||
|
||||
// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
|
||||
func IsErrEmailInvalid(err error) bool {
|
||||
_, ok := err.(ErrEmailInvalid)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrEmailInvalid) Error() string {
|
||||
return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
||||
type ErrOpenIDAlreadyUsed struct {
|
||||
OpenID string
|
||||
@@ -1004,6 +1019,29 @@ func IsErrWontSign(err error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrMigrationNotAllowed explains why a migration from an url is not allowed
|
||||
type ErrMigrationNotAllowed struct {
|
||||
Host string
|
||||
NotResolvedIP bool
|
||||
PrivateNet string
|
||||
}
|
||||
|
||||
func (e *ErrMigrationNotAllowed) Error() string {
|
||||
if e.NotResolvedIP {
|
||||
return fmt.Sprintf("migrate from '%s' is not allowed: unknown hostname", e.Host)
|
||||
}
|
||||
if len(e.PrivateNet) != 0 {
|
||||
return fmt.Sprintf("migrate from '%s' is not allowed: the host resolve to a private ip address '%s'", e.Host, e.PrivateNet)
|
||||
}
|
||||
return fmt.Sprintf("migrate from '%s is not allowed'", e.Host)
|
||||
}
|
||||
|
||||
// IsErrMigrationNotAllowed checks if an error is a ErrMigrationNotAllowed
|
||||
func IsErrMigrationNotAllowed(err error) bool {
|
||||
_, ok := err.(*ErrMigrationNotAllowed)
|
||||
return ok
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \____________ ____ ____ | |__
|
||||
// | | _/\_ __ \__ \ / \_/ ___\| | \
|
||||
|
||||
@@ -271,6 +271,27 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
|
||||
return
|
||||
}
|
||||
|
||||
// IsUserRealRepoAdmin check if this user is real repo admin
|
||||
func IsUserRealRepoAdmin(repo *Repository, user *User) (bool, error) {
|
||||
if repo.OwnerID == user.ID {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
if err := repo.getOwner(sess); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
accessMode, err := accessLevel(sess, user, repo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return accessMode >= AccessModeAdmin, nil
|
||||
}
|
||||
|
||||
// IsUserRepoAdmin return true if user has admin right of a repo
|
||||
func IsUserRepoAdmin(repo *Repository, user *User) (bool, error) {
|
||||
return isUserRepoAdmin(x, repo, user)
|
||||
|
||||
@@ -821,6 +821,10 @@ func CreateUser(u *User) (err error) {
|
||||
return ErrEmailAlreadyUsed{u.Email}
|
||||
}
|
||||
|
||||
if err = ValidateEmail(u.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isExist, err = isEmailUsed(sess, u.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -963,8 +967,12 @@ func checkDupEmail(e Engine, u *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateUser(e Engine, u *User) error {
|
||||
_, err := e.ID(u.ID).AllCols().Update(u)
|
||||
func updateUser(e Engine, u *User) (err error) {
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
if err = ValidateEmail(u.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = e.ID(u.ID).AllCols().Update(u)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -984,13 +992,21 @@ func updateUserCols(e Engine, u *User, cols ...string) error {
|
||||
}
|
||||
|
||||
// UpdateUserSetting updates user's settings.
|
||||
func UpdateUserSetting(u *User) error {
|
||||
func UpdateUserSetting(u *User) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !u.IsOrganization() {
|
||||
if err := checkDupEmail(x, u); err != nil {
|
||||
if err = checkDupEmail(sess, u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return updateUser(x, u)
|
||||
if err = updateUser(sess, u); err != nil {
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// deleteBeans deletes all given beans, beans should contain delete conditions.
|
||||
|
||||
@@ -8,6 +8,7 @@ package models
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -32,6 +33,19 @@ type EmailAddress struct {
|
||||
IsPrimary bool `xorm:"-"`
|
||||
}
|
||||
|
||||
// ValidateEmail check if email is a allowed address
|
||||
func ValidateEmail(email string) error {
|
||||
if len(email) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := mail.ParseAddress(email); err != nil {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEmailAddresses returns all email addresses belongs to given user.
|
||||
func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
|
||||
emails := make([]*EmailAddress, 0, 5)
|
||||
@@ -143,6 +157,10 @@ func addEmailAddress(e Engine, email *EmailAddress) error {
|
||||
return ErrEmailAlreadyUsed{email.Email}
|
||||
}
|
||||
|
||||
if err = ValidateEmail(email.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = e.Insert(email)
|
||||
return err
|
||||
}
|
||||
@@ -167,6 +185,9 @@ func AddEmailAddresses(emails []*EmailAddress) error {
|
||||
} else if used {
|
||||
return ErrEmailAlreadyUsed{emails[i].Email}
|
||||
}
|
||||
if err = ValidateEmail(emails[i].Email); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := x.Insert(emails); err != nil {
|
||||
|
||||
@@ -346,6 +346,21 @@ func TestCreateUser(t *testing.T) {
|
||||
assert.NoError(t, DeleteUser(user))
|
||||
}
|
||||
|
||||
func TestCreateUserInvalidEmail(t *testing.T) {
|
||||
user := &User{
|
||||
Name: "GiteaBot",
|
||||
Email: "GiteaBot@gitea.io\r\n",
|
||||
Passwd: ";p['////..-++']",
|
||||
IsAdmin: false,
|
||||
Theme: setting.UI.DefaultTheme,
|
||||
MustChangePassword: false,
|
||||
}
|
||||
|
||||
err := CreateUser(user)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrEmailInvalid(err))
|
||||
}
|
||||
|
||||
func TestCreateUser_Issue5882(t *testing.T) {
|
||||
|
||||
// Init settings
|
||||
|
||||
@@ -102,6 +102,9 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string, user *models
|
||||
u.User = url.UserPassword(authUsername, authPassword)
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
if u.Scheme == "git" && u.Port() != "" && (strings.Contains(remoteAddr, "%0d") || strings.Contains(remoteAddr, "%0a")) {
|
||||
return "", models.ErrInvalidCloneAddr{IsURLError: true}
|
||||
}
|
||||
} else if !user.CanImportLocal() {
|
||||
return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
|
||||
} else if !com.IsDir(remoteAddr) {
|
||||
|
||||
@@ -255,3 +255,61 @@ func (ctx *APIContext) NotFound(objs ...interface{}) {
|
||||
"errors": errors,
|
||||
})
|
||||
}
|
||||
|
||||
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
|
||||
func RepoRefForAPI() macaron.Handler {
|
||||
return func(ctx *APIContext) {
|
||||
// Empty repository does not have reference information.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
refName := getRefName(ctx.Context, RepoRefAny)
|
||||
|
||||
if ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) == 40 {
|
||||
ctx.Repo.CommitID = refName
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.NotFound("GetCommit", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.NotFound(fmt.Errorf("not exist: '%s'", ctx.Params("*")))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +704,6 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
err error
|
||||
)
|
||||
|
||||
// For API calls.
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
|
||||
@@ -773,7 +772,7 @@ func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.NotFound("GetCommit", nil)
|
||||
ctx.NotFound("GetCommit", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
@@ -117,9 +118,11 @@ func File(numLines int, fileName string, code []byte) map[int]string {
|
||||
fileName = "test." + val
|
||||
}
|
||||
|
||||
lexer := lexers.Match(fileName)
|
||||
language := analyze.GetCodeLanguage(fileName, code)
|
||||
|
||||
lexer := lexers.Get(language)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(string(code))
|
||||
lexer = lexers.Match(fileName)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
46
modules/matchlist/matchlist.go
Normal file
46
modules/matchlist/matchlist.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 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 matchlist
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// Matchlist represents a block or allow list
|
||||
type Matchlist struct {
|
||||
ruleGlobs []glob.Glob
|
||||
}
|
||||
|
||||
// NewMatchlist creates a new block or allow list
|
||||
func NewMatchlist(rules ...string) (*Matchlist, error) {
|
||||
for i := range rules {
|
||||
rules[i] = strings.ToLower(rules[i])
|
||||
}
|
||||
list := Matchlist{
|
||||
ruleGlobs: make([]glob.Glob, 0, len(rules)),
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
rg, err := glob.Compile(rule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list.ruleGlobs = append(list.ruleGlobs, rg)
|
||||
}
|
||||
|
||||
return &list, nil
|
||||
}
|
||||
|
||||
// Match will matches
|
||||
func (b *Matchlist) Match(u string) bool {
|
||||
for _, r := range b.ruleGlobs {
|
||||
if r.Match(u) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -326,45 +326,44 @@ func (g *GiteaDownloader) GetAsset(_ string, relID, id int64) (io.ReadCloser, er
|
||||
}
|
||||
|
||||
func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) {
|
||||
var reactions []*base.Reaction
|
||||
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
|
||||
log.Info("GiteaDownloader: instance to old, skip getIssueReactions")
|
||||
return reactions, nil
|
||||
return []*base.Reaction{}, nil
|
||||
}
|
||||
rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, reaction := range rl {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.ID,
|
||||
UserName: reaction.User.UserName,
|
||||
Content: reaction.Reaction,
|
||||
})
|
||||
}
|
||||
return reactions, nil
|
||||
return g.convertReactions(rl), nil
|
||||
}
|
||||
|
||||
func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) {
|
||||
var reactions []*base.Reaction
|
||||
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil {
|
||||
log.Info("GiteaDownloader: instance to old, skip getCommentReactions")
|
||||
return reactions, nil
|
||||
return []*base.Reaction{}, nil
|
||||
}
|
||||
rl, _, err := g.client.GetIssueCommentReactions(g.repoOwner, g.repoName, commentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g.convertReactions(rl), nil
|
||||
}
|
||||
|
||||
func (g *GiteaDownloader) convertReactions(rl []*gitea_sdk.Reaction) []*base.Reaction {
|
||||
var reactions []*base.Reaction
|
||||
for i := range rl {
|
||||
if rl[i].User.ID <= 0 {
|
||||
continue
|
||||
}
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: rl[i].User.ID,
|
||||
UserName: rl[i].User.UserName,
|
||||
Content: rl[i].Reaction,
|
||||
})
|
||||
}
|
||||
return reactions, nil
|
||||
return reactions
|
||||
}
|
||||
|
||||
// GetIssues returns issues according start and limit
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -87,6 +88,30 @@ func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, username, passw
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// split namespace and subdirectory
|
||||
pathParts := strings.Split(strings.Trim(repoPath, "/"), "/")
|
||||
var resp *gitlab.Response
|
||||
u, _ := url.Parse(baseURL)
|
||||
for len(pathParts) >= 2 {
|
||||
_, resp, err = gitlabClient.Version.GetVersion()
|
||||
if err == nil || resp != nil && resp.StatusCode == 401 {
|
||||
err = nil // if no authentication given, this still should work
|
||||
break
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, pathParts[0])
|
||||
baseURL = u.String()
|
||||
pathParts = pathParts[1:]
|
||||
_ = gitlab.WithBaseURL(baseURL)(gitlabClient)
|
||||
repoPath = strings.Join(pathParts, "/")
|
||||
}
|
||||
if err != nil {
|
||||
log.Trace("Error could not get gitlab version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Trace("gitlab downloader: use BaseURL: '%s' and RepoPath: '%s'", baseURL, repoPath)
|
||||
|
||||
// Grab and store project/repo ID here, due to issues using the URL escaped path
|
||||
gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil, gitlab.WithContext(ctx))
|
||||
if err != nil {
|
||||
@@ -584,8 +609,12 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
|
||||
|
||||
// GetReviews returns pull requests review
|
||||
func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
|
||||
state, _, err := g.client.MergeRequestApprovals.GetApprovalState(g.repoID, int(pullRequestNumber), gitlab.WithContext(g.ctx))
|
||||
state, resp, err := g.client.MergeRequestApprovals.GetApprovalState(g.repoID, int(pullRequestNumber), gitlab.WithContext(g.ctx))
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == 404 {
|
||||
log.Error(fmt.Sprintf("GitlabDownloader: while migrating a error occurred: '%s'", err.Error()))
|
||||
return []*base.Review{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,13 @@ package migrations
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/matchlist"
|
||||
"code.gitea.io/gitea/modules/migrations/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@@ -20,6 +24,9 @@ type MigrateOptions = base.MigrateOptions
|
||||
|
||||
var (
|
||||
factories []base.DownloaderFactory
|
||||
|
||||
allowList *matchlist.Matchlist
|
||||
blockList *matchlist.Matchlist
|
||||
)
|
||||
|
||||
// RegisterDownloaderFactory registers a downloader factory
|
||||
@@ -27,12 +34,49 @@ func RegisterDownloaderFactory(factory base.DownloaderFactory) {
|
||||
factories = append(factories, factory)
|
||||
}
|
||||
|
||||
func isMigrateURLAllowed(remoteURL string) error {
|
||||
u, err := url.Parse(strings.ToLower(remoteURL))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") {
|
||||
if len(setting.Migrations.AllowedDomains) > 0 {
|
||||
if !allowList.Match(u.Host) {
|
||||
return &models.ErrMigrationNotAllowed{Host: u.Host}
|
||||
}
|
||||
} else {
|
||||
if blockList.Match(u.Host) {
|
||||
return &models.ErrMigrationNotAllowed{Host: u.Host}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !setting.Migrations.AllowLocalNetworks {
|
||||
addrList, err := net.LookupIP(strings.Split(u.Host, ":")[0])
|
||||
if err != nil {
|
||||
return &models.ErrMigrationNotAllowed{Host: u.Host, NotResolvedIP: true}
|
||||
}
|
||||
for _, addr := range addrList {
|
||||
if isIPPrivate(addr) || !addr.IsGlobalUnicast() {
|
||||
return &models.ErrMigrationNotAllowed{Host: u.Host, PrivateNet: addr.String()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateRepository migrate repository according MigrateOptions
|
||||
func MigrateRepository(ctx context.Context, doer *models.User, ownerName string, opts base.MigrateOptions) (*models.Repository, error) {
|
||||
err := isMigrateURLAllowed(opts.CloneAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
downloader base.Downloader
|
||||
uploader = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
|
||||
err error
|
||||
)
|
||||
|
||||
for _, factory := range factories {
|
||||
@@ -308,3 +352,32 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init migrations service
|
||||
func Init() error {
|
||||
var err error
|
||||
allowList, err = matchlist.NewMatchlist(setting.Migrations.AllowedDomains...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init migration allowList domains failed: %v", err)
|
||||
}
|
||||
|
||||
blockList, err = matchlist.NewMatchlist(setting.Migrations.BlockedDomains...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init migration blockList domains failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isIPPrivate reports whether ip is a private address, according to
|
||||
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
|
||||
// from https://github.com/golang/go/pull/42793
|
||||
// TODO remove if https://github.com/golang/go/issues/29146 got resolved
|
||||
func isIPPrivate(ip net.IP) bool {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4[0] == 10 ||
|
||||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
|
||||
(ip4[0] == 192 && ip4[1] == 168)
|
||||
}
|
||||
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
|
||||
}
|
||||
|
||||
34
modules/migrations/migrate_test.go
Normal file
34
modules/migrations/migrate_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMigrateWhiteBlocklist(t *testing.T) {
|
||||
setting.Migrations.AllowedDomains = []string{"github.com"}
|
||||
assert.NoError(t, Init())
|
||||
|
||||
err := isMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git")
|
||||
assert.Error(t, err)
|
||||
|
||||
err = isMigrateURLAllowed("https://github.com/go-gitea/gitea.git")
|
||||
assert.NoError(t, err)
|
||||
|
||||
setting.Migrations.AllowedDomains = []string{}
|
||||
setting.Migrations.BlockedDomains = []string{"github.com"}
|
||||
assert.NoError(t, Init())
|
||||
|
||||
err = isMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = isMigrateURLAllowed("https://github.com/go-gitea/gitea.git")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -797,3 +797,11 @@ func (m *webhookNotifier) NotifySyncPushCommits(pusher *models.User, repo *model
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifySyncCreateRef(pusher *models.User, repo *models.Repository, refType, refFullName string) {
|
||||
m.NotifyCreateRef(pusher, repo, refType, refFullName)
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifySyncDeleteRef(pusher *models.User, repo *models.Repository, refType, refFullName string) {
|
||||
m.NotifyDeleteRef(pusher, repo, refType, refFullName)
|
||||
}
|
||||
|
||||
@@ -162,10 +162,10 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def
|
||||
defaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
if stdout, err := git.NewCommand("push", "origin", "master:"+defaultBranch).
|
||||
if stdout, err := git.NewCommand("push", "origin", "HEAD:"+defaultBranch).
|
||||
SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
|
||||
RunInDirWithEnv(tmpPath, models.InternalPushingEnvironment(u, repo)); err != nil {
|
||||
log.Error("Failed to push back to master: Stdout: %s\nError: %v", stdout, err)
|
||||
log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err)
|
||||
return fmt.Errorf("git push: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// Migrations settings
|
||||
Migrations = struct {
|
||||
MaxAttempts int
|
||||
RetryBackoff int
|
||||
MaxAttempts int
|
||||
RetryBackoff int
|
||||
AllowedDomains []string
|
||||
BlockedDomains []string
|
||||
AllowLocalNetworks bool
|
||||
}{
|
||||
MaxAttempts: 3,
|
||||
RetryBackoff: 3,
|
||||
@@ -19,4 +26,15 @@ func newMigrationsService() {
|
||||
sec := Cfg.Section("migrations")
|
||||
Migrations.MaxAttempts = sec.Key("MAX_ATTEMPTS").MustInt(Migrations.MaxAttempts)
|
||||
Migrations.RetryBackoff = sec.Key("RETRY_BACKOFF").MustInt(Migrations.RetryBackoff)
|
||||
|
||||
Migrations.AllowedDomains = sec.Key("ALLOWED_DOMAINS").Strings(",")
|
||||
for i := range Migrations.AllowedDomains {
|
||||
Migrations.AllowedDomains[i] = strings.ToLower(Migrations.AllowedDomains[i])
|
||||
}
|
||||
Migrations.BlockedDomains = sec.Key("BLOCKED_DOMAINS").Strings(",")
|
||||
for i := range Migrations.BlockedDomains {
|
||||
Migrations.BlockedDomains[i] = strings.ToLower(Migrations.BlockedDomains[i])
|
||||
}
|
||||
|
||||
Migrations.AllowLocalNetworks = sec.Key("ALLOW_LOCALNETWORKS").MustBool(false)
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ var (
|
||||
MaxCreationLimit: -1,
|
||||
MirrorQueueLength: 1000,
|
||||
PullRequestQueueLength: 1000,
|
||||
PreferredLicenses: []string{"Apache License 2.0,MIT License"},
|
||||
PreferredLicenses: []string{"Apache License 2.0", "MIT License"},
|
||||
DisableHTTPGit: false,
|
||||
AccessControlAllowOrigin: "",
|
||||
UseCompatSSHURI: false,
|
||||
|
||||
@@ -366,6 +366,7 @@ org_name_been_taken = The organization name is already taken.
|
||||
team_name_been_taken = The team name is already taken.
|
||||
team_no_units_error = Allow access to at least one repository section.
|
||||
email_been_used = The email address is already used.
|
||||
email_invalid = The email address is invalid.
|
||||
openid_been_used = The OpenID address '%s' is already used.
|
||||
username_password_incorrect = Username or password is incorrect.
|
||||
password_complexity = Password does not pass complexity requirements:
|
||||
|
||||
@@ -129,6 +129,9 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
|
||||
case models.IsErrEmailAlreadyUsed(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
|
||||
case models.IsErrEmailInvalid(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
|
||||
case models.IsErrNameReserved(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplUserNew, &form)
|
||||
@@ -277,6 +280,9 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
|
||||
if models.IsErrEmailAlreadyUsed(err) {
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form)
|
||||
} else if models.IsErrEmailInvalid(err) {
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
|
||||
} else {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
}
|
||||
|
||||
@@ -87,3 +87,33 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
|
||||
assert.Equal(t, email, u.Email)
|
||||
assert.False(t, u.MustChangePassword)
|
||||
}
|
||||
|
||||
func TestNewUserPost_InvalidEmail(t *testing.T) {
|
||||
|
||||
models.PrepareTestEnv(t)
|
||||
ctx := test.MockContext(t, "admin/users/new")
|
||||
|
||||
u := models.AssertExistsAndLoadBean(t, &models.User{
|
||||
IsAdmin: true,
|
||||
ID: 2,
|
||||
}).(*models.User)
|
||||
|
||||
ctx.User = u
|
||||
|
||||
username := "gitea"
|
||||
email := "gitea@gitea.io\r\n"
|
||||
|
||||
form := auth.AdminCreateUserForm{
|
||||
LoginType: "local",
|
||||
LoginName: "local",
|
||||
UserName: username,
|
||||
Email: email,
|
||||
Password: "abc123ABC!=$",
|
||||
SendNotify: false,
|
||||
MustChangePassword: false,
|
||||
}
|
||||
|
||||
NewUserPost(ctx, form)
|
||||
|
||||
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
|
||||
models.IsErrEmailAlreadyUsed(err) ||
|
||||
models.IsErrNameReserved(err) ||
|
||||
models.IsErrNameCharsNotAllowed(err) ||
|
||||
models.IsErrEmailInvalid(err) ||
|
||||
models.IsErrNamePatternNotAllowed(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
@@ -208,7 +209,7 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
|
||||
}
|
||||
|
||||
if err := models.UpdateUser(u); err != nil {
|
||||
if models.IsErrEmailAlreadyUsed(err) {
|
||||
if models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailInvalid(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
|
||||
|
||||
@@ -191,14 +191,14 @@ func reqToken() macaron.Handler {
|
||||
ctx.RequireCSRF()
|
||||
return
|
||||
}
|
||||
ctx.Context.Error(http.StatusUnauthorized)
|
||||
ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
|
||||
}
|
||||
}
|
||||
|
||||
func reqBasicAuth() macaron.Handler {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.Context.IsBasicAuth {
|
||||
ctx.Context.Error(http.StatusUnauthorized)
|
||||
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "basic auth required")
|
||||
return
|
||||
}
|
||||
ctx.CheckForOTP()
|
||||
@@ -207,9 +207,9 @@ func reqBasicAuth() macaron.Handler {
|
||||
|
||||
// reqSiteAdmin user should be the site admin
|
||||
func reqSiteAdmin() macaron.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.IsUserSiteAdmin() {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -217,9 +217,9 @@ func reqSiteAdmin() macaron.Handler {
|
||||
|
||||
// reqOwner user should be the owner of the repo or site admin.
|
||||
func reqOwner() macaron.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -227,9 +227,9 @@ func reqOwner() macaron.Handler {
|
||||
|
||||
// reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
|
||||
func reqAdmin() macaron.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -237,9 +237,9 @@ func reqAdmin() macaron.Handler {
|
||||
|
||||
// reqRepoWriter user should have a permission to write to a repo, or be a site admin
|
||||
func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -247,9 +247,9 @@ func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
|
||||
|
||||
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
|
||||
func reqRepoReader(unitType models.UnitType) macaron.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -257,9 +257,9 @@ func reqRepoReader(unitType models.UnitType) macaron.Handler {
|
||||
|
||||
// reqAnyRepoReader user should have any permission to read repository or permissions of site admin
|
||||
func reqAnyRepoReader() macaron.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -502,7 +502,6 @@ func mustNotBeArchived(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all v1 APIs routes to web application.
|
||||
// FIXME: custom form error response
|
||||
func RegisterRoutes(m *macaron.Macaron) {
|
||||
bind := binding.Bind
|
||||
|
||||
@@ -641,7 +640,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
|
||||
Delete(reqToken(), reqOwner(), repo.Delete).
|
||||
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), context.RepoRef(), repo.Edit)
|
||||
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), context.RepoRefForAPI(), repo.Edit)
|
||||
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
|
||||
m.Combo("/notifications").
|
||||
Get(reqToken(), notify.ListRepoNotifications).
|
||||
@@ -653,7 +652,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Combo("").Get(repo.GetHook).
|
||||
Patch(bind(api.EditHookOption{}), repo.EditHook).
|
||||
Delete(repo.DeleteHook)
|
||||
m.Post("/tests", context.RepoRef(), repo.TestHook)
|
||||
m.Post("/tests", context.RepoRefForAPI(), repo.TestHook)
|
||||
})
|
||||
m.Group("/git", func() {
|
||||
m.Combo("").Get(repo.ListGitHooks)
|
||||
@@ -670,14 +669,14 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
|
||||
Delete(reqAdmin(), repo.DeleteCollaborator)
|
||||
}, reqToken())
|
||||
m.Get("/raw/*", context.RepoRefByType(context.RepoRefAny), reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
|
||||
m.Get("/raw/*", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
|
||||
m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive)
|
||||
m.Combo("/forks").Get(repo.ListForks).
|
||||
Post(reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
||||
m.Group("/branches", func() {
|
||||
m.Get("", repo.ListBranches)
|
||||
m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
|
||||
m.Delete("/*", reqRepoWriter(models.UnitTypeCode), context.RepoRefByType(context.RepoRefBranch), repo.DeleteBranch)
|
||||
m.Get("/*", repo.GetBranch)
|
||||
m.Delete("/*", context.ReferencesGitRepo(false), reqRepoWriter(models.UnitTypeCode), repo.DeleteBranch)
|
||||
m.Post("", reqRepoWriter(models.UnitTypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
||||
}, reqRepoReader(models.UnitTypeCode))
|
||||
m.Group("/branch_protections", func() {
|
||||
@@ -802,7 +801,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
})
|
||||
}, reqRepoReader(models.UnitTypeReleases))
|
||||
m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)
|
||||
m.Get("/editorconfig/:filename", context.RepoRef(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig)
|
||||
m.Get("/editorconfig/:filename", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig)
|
||||
m.Group("/pulls", func() {
|
||||
m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
|
||||
@@ -847,9 +846,9 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
})
|
||||
m.Get("/refs", repo.GetGitAllRefs)
|
||||
m.Get("/refs/*", repo.GetGitRefs)
|
||||
m.Get("/trees/:sha", context.RepoRef(), repo.GetTree)
|
||||
m.Get("/blobs/:sha", context.RepoRef(), repo.GetBlob)
|
||||
m.Get("/tags/:sha", context.RepoRef(), repo.GetTag)
|
||||
m.Get("/trees/:sha", context.RepoRefForAPI(), repo.GetTree)
|
||||
m.Get("/blobs/:sha", context.RepoRefForAPI(), repo.GetBlob)
|
||||
m.Get("/tags/:sha", context.RepoRefForAPI(), repo.GetTag)
|
||||
}, reqRepoReader(models.UnitTypeCode))
|
||||
m.Group("/contents", func() {
|
||||
m.Get("", repo.GetContentsList)
|
||||
|
||||
@@ -101,7 +101,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
opts := models.FindNotificationOptions{
|
||||
|
||||
@@ -63,7 +63,7 @@ func ListNotifications(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
opts := models.FindNotificationOptions{
|
||||
|
||||
@@ -46,15 +46,12 @@ func GetBranch(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Branch"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if ctx.Repo.TreePath != "" {
|
||||
// if TreePath != "", then URL contained extra slashes
|
||||
// (i.e. "master/subbranch" instead of "master"), so branch does
|
||||
// not exist
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
branch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.BranchName)
|
||||
branchName := ctx.Params("*")
|
||||
|
||||
branch, err := repo_module.GetBranch(ctx.Repo.Repository, branchName)
|
||||
if err != nil {
|
||||
if git.IsErrBranchNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
@@ -70,7 +67,7 @@ func GetBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
branchProtection, err := ctx.Repo.Repository.GetBranchProtection(ctx.Repo.BranchName)
|
||||
branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branchName)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
|
||||
return
|
||||
@@ -113,21 +110,17 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if ctx.Repo.TreePath != "" {
|
||||
// if TreePath != "", then URL contained extra slashes
|
||||
// (i.e. "master/subbranch" instead of "master"), so branch does
|
||||
// not exist
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
branchName := ctx.Params("*")
|
||||
|
||||
if ctx.Repo.Repository.DefaultBranch == ctx.Repo.BranchName {
|
||||
if ctx.Repo.Repository.DefaultBranch == branchName {
|
||||
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
||||
return
|
||||
}
|
||||
|
||||
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(ctx.Repo.BranchName, ctx.User)
|
||||
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.User)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
@@ -137,7 +130,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
branch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.BranchName)
|
||||
branch, err := repo_module.GetBranch(ctx.Repo.Repository, branchName)
|
||||
if err != nil {
|
||||
if git.IsErrBranchNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
@@ -153,7 +146,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctx.Repo.GitRepo.DeleteBranch(ctx.Repo.BranchName, git.DeleteBranchOptions{
|
||||
if err := ctx.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||
Force: true,
|
||||
}); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
||||
@@ -163,7 +156,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
// Don't return error below this
|
||||
if err := repo_service.PushUpdate(
|
||||
&repo_service.PushUpdateOptions{
|
||||
RefFullName: git.BranchPrefix + ctx.Repo.BranchName,
|
||||
RefFullName: git.BranchPrefix + branchName,
|
||||
OldCommitID: c.ID.String(),
|
||||
NewCommitID: git.EmptySHA,
|
||||
PusherID: ctx.User.ID,
|
||||
@@ -174,7 +167,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
log.Error("Update: %v", err)
|
||||
}
|
||||
|
||||
if err := ctx.Repo.Repository.AddDeletedBranch(ctx.Repo.BranchName, c.ID.String(), ctx.User.ID); err != nil {
|
||||
if err := ctx.Repo.Repository.AddDeletedBranch(branchName, c.ID.String(), ctx.User.ID); err != nil {
|
||||
log.Warn("AddDeletedBranch: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetQueryBeforeSince", err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||
@@ -132,7 +132,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetQueryBeforeSince", err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||
|
||||
var err error
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
|
||||
|
||||
var err error
|
||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,8 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))
|
||||
case models.IsErrNamePatternNotAllowed(err):
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
|
||||
case models.IsErrMigrationNotAllowed(err):
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||
default:
|
||||
err = util.URLSanitizedError(err, remoteAddr)
|
||||
if strings.Contains(err.Error(), "Authentication failed") ||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
@@ -78,6 +79,9 @@ func AddEmail(ctx *context.APIContext, form api.CreateEmailOption) {
|
||||
if err := models.AddEmailAddresses(emails); err != nil {
|
||||
if models.IsErrEmailAlreadyUsed(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
|
||||
} else if models.IsErrEmailInvalid(err) {
|
||||
errMsg := fmt.Sprintf("Email address %s invalid", err.(models.ErrEmailInvalid).Email)
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", errMsg)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,30 +16,49 @@ import (
|
||||
|
||||
// GetQueryBeforeSince return parsed time (unix format) from URL query's before and since
|
||||
func GetQueryBeforeSince(ctx *context.APIContext) (before, since int64, err error) {
|
||||
qCreatedBefore := strings.Trim(ctx.Query("before"), " ")
|
||||
if qCreatedBefore != "" {
|
||||
createdBefore, err := time.Parse(time.RFC3339, qCreatedBefore)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if !createdBefore.IsZero() {
|
||||
before = createdBefore.Unix()
|
||||
}
|
||||
qCreatedBefore, err := prepareQueryArg(ctx, "before")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
qCreatedAfter := strings.Trim(ctx.Query("since"), " ")
|
||||
if qCreatedAfter != "" {
|
||||
createdAfter, err := time.Parse(time.RFC3339, qCreatedAfter)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if !createdAfter.IsZero() {
|
||||
since = createdAfter.Unix()
|
||||
}
|
||||
qCreatedSince, err := prepareQueryArg(ctx, "since")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
before, err = parseTime(qCreatedBefore)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
since, err = parseTime(qCreatedSince)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return before, since, nil
|
||||
}
|
||||
|
||||
// parseTime parse time and return unix timestamp
|
||||
func parseTime(value string) (int64, error) {
|
||||
if len(value) != 0 {
|
||||
t, err := time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !t.IsZero() {
|
||||
return t.Unix(), nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// prepareQueryArg unescape and trim a query arg
|
||||
func prepareQueryArg(ctx *context.APIContext, name string) (value string, err error) {
|
||||
value, err = url.PathUnescape(ctx.Query(name))
|
||||
value = strings.Trim(value, " ")
|
||||
return
|
||||
}
|
||||
|
||||
// GetListOptions returns list options using the page and limit parameters
|
||||
func GetListOptions(ctx *context.APIContext) models.ListOptions {
|
||||
return models.ListOptions{
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/external"
|
||||
repo_migrations "code.gitea.io/gitea/modules/migrations"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -43,12 +44,14 @@ import (
|
||||
|
||||
func checkRunMode() {
|
||||
switch setting.Cfg.Section("").Key("RUN_MODE").String() {
|
||||
case "prod":
|
||||
case "dev":
|
||||
git.Debug = true
|
||||
case "test":
|
||||
git.Debug = true
|
||||
default:
|
||||
macaron.Env = macaron.PROD
|
||||
macaron.ColorLog = false
|
||||
setting.ProdMode = true
|
||||
default:
|
||||
git.Debug = true
|
||||
}
|
||||
log.Info("Run Mode: %s", strings.Title(macaron.Env))
|
||||
}
|
||||
@@ -172,6 +175,10 @@ func GlobalInit(ctx context.Context) {
|
||||
}
|
||||
checkRunMode()
|
||||
|
||||
if err := repo_migrations.Init(); err != nil {
|
||||
log.Fatal("Failed to initialize repository migrations: %v", err)
|
||||
}
|
||||
|
||||
// Now because Install will re-run GlobalInit once it has set InstallLock
|
||||
// we can't tell if the ssh port will remain unused until that's done.
|
||||
// However, see FIXME comment in install.go
|
||||
|
||||
@@ -61,6 +61,12 @@ func ServNoCommand(ctx *macaron.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
if !user.IsActive || user.ProhibitLogin {
|
||||
ctx.JSON(http.StatusForbidden, map[string]interface{}{
|
||||
"err": "Your account is disabled.",
|
||||
})
|
||||
return
|
||||
}
|
||||
results.Owner = user
|
||||
}
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
@@ -98,9 +104,28 @@ func ServCommand(ctx *macaron.Context) {
|
||||
results.RepoName = repoName[:len(repoName)-5]
|
||||
}
|
||||
|
||||
owner, err := models.GetUserByName(results.OwnerName)
|
||||
if err != nil {
|
||||
log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"results": results,
|
||||
"type": "InternalServerError",
|
||||
"err": fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !owner.IsOrganization() && !owner.IsActive {
|
||||
ctx.JSON(http.StatusForbidden, map[string]interface{}{
|
||||
"results": results,
|
||||
"type": "ForbiddenError",
|
||||
"err": "Repository cannot be accessed, you could retry it later",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Now get the Repository and set the results section
|
||||
repoExist := true
|
||||
repo, err := models.GetRepositoryByOwnerAndName(results.OwnerName, results.RepoName)
|
||||
repo, err := models.GetRepositoryByName(owner.ID, results.RepoName)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
repoExist = false
|
||||
@@ -127,6 +152,7 @@ func ServCommand(ctx *macaron.Context) {
|
||||
}
|
||||
|
||||
if repoExist {
|
||||
repo.Owner = owner
|
||||
repo.OwnerName = ownerName
|
||||
results.RepoID = repo.ID
|
||||
|
||||
@@ -217,15 +243,6 @@ func ServCommand(ctx *macaron.Context) {
|
||||
// so for now use the owner of the repository
|
||||
results.UserName = results.OwnerName
|
||||
results.UserID = repo.OwnerID
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
log.Error("Unable to get owner for repo %-v. Error: %v", repo, err)
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"results": results,
|
||||
"type": "InternalServerError",
|
||||
"err": fmt.Sprintf("Unable to get owner for repo: %s/%s.", results.OwnerName, results.RepoName),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !repo.Owner.KeepEmailPrivate {
|
||||
results.UserEmail = repo.Owner.Email
|
||||
}
|
||||
@@ -250,6 +267,14 @@ func ServCommand(ctx *macaron.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsActive || user.ProhibitLogin {
|
||||
ctx.JSON(http.StatusForbidden, map[string]interface{}{
|
||||
"err": "Your account is disabled.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
results.UserName = user.Name
|
||||
if !user.KeepEmailPrivate {
|
||||
results.UserEmail = user.Email
|
||||
|
||||
@@ -105,6 +105,10 @@ func HTTP(ctx *context.Context) {
|
||||
ctx.NotFoundOrServerError("GetUserByName", models.IsErrUserNotExist, err)
|
||||
return
|
||||
}
|
||||
if !owner.IsOrganization() && !owner.IsActive {
|
||||
ctx.HandleText(http.StatusForbidden, "Repository cannot be accessed. You cannot push or open issues/pull-requests.")
|
||||
return
|
||||
}
|
||||
|
||||
repoExist := true
|
||||
repo, err := models.GetRepositoryByName(owner.ID, reponame)
|
||||
@@ -244,6 +248,11 @@ func HTTP(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if !authUser.IsActive || authUser.ProhibitLogin {
|
||||
ctx.HandleText(http.StatusForbidden, "Your account is disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
if repoExist {
|
||||
perm, err := models.GetUserRepoPermission(repo, authUser)
|
||||
if err != nil {
|
||||
|
||||
@@ -130,6 +130,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
|
||||
posterID = ctx.User.ID
|
||||
case "mentioned":
|
||||
mentionedID = ctx.User.ID
|
||||
case "assigned":
|
||||
assigneeID = ctx.User.ID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,8 +979,27 @@ func commentTag(repo *models.Repository, poster *models.User, issue *models.Issu
|
||||
return models.CommentTagNone, err
|
||||
}
|
||||
if perm.IsOwner() {
|
||||
return models.CommentTagOwner, nil
|
||||
} else if perm.CanWrite(models.UnitTypeCode) {
|
||||
if !poster.IsAdmin {
|
||||
return models.CommentTagOwner, nil
|
||||
}
|
||||
|
||||
ok, err := models.IsUserRealRepoAdmin(repo, poster)
|
||||
if err != nil {
|
||||
return models.CommentTagNone, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return models.CommentTagOwner, nil
|
||||
}
|
||||
|
||||
if ok, err = repo.IsCollaborator(poster.ID); ok && err == nil {
|
||||
return models.CommentTagWriter, nil
|
||||
}
|
||||
|
||||
return models.CommentTagNone, err
|
||||
}
|
||||
|
||||
if perm.CanWrite(models.UnitTypeCode) {
|
||||
return models.CommentTagWriter, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -964,6 +964,9 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
|
||||
case models.IsErrEmailAlreadyUsed(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplLinkAccount, &form)
|
||||
case models.IsErrEmailInvalid(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSignUp, &form)
|
||||
case models.IsErrNameReserved(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplLinkAccount, &form)
|
||||
@@ -1151,6 +1154,9 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
|
||||
case models.IsErrEmailAlreadyUsed(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignUp, &form)
|
||||
case models.IsErrEmailInvalid(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSignUp, &form)
|
||||
case models.IsErrNameReserved(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplSignUp, &form)
|
||||
|
||||
@@ -179,6 +179,11 @@ func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
|
||||
return
|
||||
} else if models.IsErrEmailInvalid(err) {
|
||||
loadAccountData(ctx)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("AddEmailAddress", err)
|
||||
return
|
||||
|
||||
@@ -676,6 +676,15 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
|
||||
leftLine, rightLine := 1, 1
|
||||
|
||||
for {
|
||||
for isFragment {
|
||||
curFile.IsIncomplete = true
|
||||
_, isFragment, err = input.ReadLine()
|
||||
if err != nil {
|
||||
// Now by the definition of ReadLine this cannot be io.EOF
|
||||
err = fmt.Errorf("Unable to ReadLine: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
sb.Reset()
|
||||
lineBytes, isFragment, err = input.ReadLine()
|
||||
if err != nil {
|
||||
@@ -790,6 +799,10 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(line) > maxLineCharacters {
|
||||
curFile.IsIncomplete = true
|
||||
line = line[:maxLineCharacters]
|
||||
}
|
||||
curSection.Lines[len(curSection.Lines)-1].Content = line
|
||||
|
||||
// handle LFS
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -153,11 +154,11 @@ func TestParsePatch_singlefile(t *testing.T) {
|
||||
name: "really weird filename",
|
||||
gitdiff: `diff --git "\\a/a b/file b/a a/file" "\\b/a b/file b/a a/file"
|
||||
index d2186f1..f5c8ed2 100644
|
||||
--- "\\a/a b/file b/a a/file"
|
||||
+++ "\\b/a b/file b/a a/file"
|
||||
--- "\\a/a b/file b/a a/file" ` + `
|
||||
+++ "\\b/a b/file b/a a/file" ` + `
|
||||
@@ -1,3 +1,2 @@
|
||||
Create a weird file.
|
||||
|
||||
` + `
|
||||
-and what does diff do here?
|
||||
\ No newline at end of file`,
|
||||
addition: 0,
|
||||
@@ -170,7 +171,7 @@ index d2186f1..f5c8ed2 100644
|
||||
gitdiff: `diff --git "\\a/file with blanks" "\\b/file with blanks"
|
||||
deleted file mode 100644
|
||||
index 898651a..0000000
|
||||
--- "\\a/file with blanks"
|
||||
--- "\\a/file with blanks" ` + `
|
||||
+++ /dev/null
|
||||
@@ -1,5 +0,0 @@
|
||||
-a blank file
|
||||
@@ -263,7 +264,83 @@ index 6961180..9ba1a00 100644
|
||||
})
|
||||
}
|
||||
|
||||
var diff = `diff --git "a/README.md" "b/README.md"
|
||||
// Test max lines
|
||||
diffBuilder := &strings.Builder{}
|
||||
|
||||
var diff = `diff --git a/newfile2 b/newfile2
|
||||
new file mode 100644
|
||||
index 0000000..6bb8f39
|
||||
--- /dev/null
|
||||
+++ b/newfile2
|
||||
@@ -0,0 +1,35 @@
|
||||
`
|
||||
diffBuilder.WriteString(diff)
|
||||
|
||||
for i := 0; i < 35; i++ {
|
||||
diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n")
|
||||
}
|
||||
diff = diffBuilder.String()
|
||||
result, err := ParsePatch(20, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
if err != nil {
|
||||
t.Errorf("There should not be an error: %v", err)
|
||||
}
|
||||
if !result.Files[0].IsIncomplete {
|
||||
t.Errorf("Files should be incomplete! %v", result.Files[0])
|
||||
}
|
||||
result, err = ParsePatch(40, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
if err != nil {
|
||||
t.Errorf("There should not be an error: %v", err)
|
||||
}
|
||||
if result.Files[0].IsIncomplete {
|
||||
t.Errorf("Files should not be incomplete! %v", result.Files[0])
|
||||
}
|
||||
result, err = ParsePatch(40, 5, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
if err != nil {
|
||||
t.Errorf("There should not be an error: %v", err)
|
||||
}
|
||||
if !result.Files[0].IsIncomplete {
|
||||
t.Errorf("Files should be incomplete! %v", result.Files[0])
|
||||
}
|
||||
|
||||
// Test max characters
|
||||
diff = `diff --git a/newfile2 b/newfile2
|
||||
new file mode 100644
|
||||
index 0000000..6bb8f39
|
||||
--- /dev/null
|
||||
+++ b/newfile2
|
||||
@@ -0,0 +1,35 @@
|
||||
`
|
||||
diffBuilder.Reset()
|
||||
diffBuilder.WriteString(diff)
|
||||
|
||||
for i := 0; i < 33; i++ {
|
||||
diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n")
|
||||
}
|
||||
diffBuilder.WriteString("+line33")
|
||||
for i := 0; i < 512; i++ {
|
||||
diffBuilder.WriteString("0123456789ABCDEF")
|
||||
}
|
||||
diffBuilder.WriteByte('\n')
|
||||
diffBuilder.WriteString("+line" + strconv.Itoa(34) + "\n")
|
||||
diffBuilder.WriteString("+line" + strconv.Itoa(35) + "\n")
|
||||
diff = diffBuilder.String()
|
||||
|
||||
result, err = ParsePatch(20, 4096, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
if err != nil {
|
||||
t.Errorf("There should not be an error: %v", err)
|
||||
}
|
||||
if !result.Files[0].IsIncomplete {
|
||||
t.Errorf("Files should be incomplete! %v", result.Files[0])
|
||||
}
|
||||
result, err = ParsePatch(40, 4096, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
if err != nil {
|
||||
t.Errorf("There should not be an error: %v", err)
|
||||
}
|
||||
if !result.Files[0].IsIncomplete {
|
||||
t.Errorf("Files should be incomplete! %v", result.Files[0])
|
||||
}
|
||||
|
||||
diff = `diff --git "a/README.md" "b/README.md"
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -1,3 +1,6 @@
|
||||
@@ -274,7 +351,7 @@ index 6961180..9ba1a00 100644
|
||||
Docker Pulls
|
||||
+ cut off
|
||||
+ cut off`
|
||||
result, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
result, err = ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff))
|
||||
if err != nil {
|
||||
t.Errorf("ParsePatch failed: %s", err)
|
||||
}
|
||||
|
||||
@@ -609,7 +609,7 @@ func GetCommitMessages(pr *models.PullRequest) string {
|
||||
}
|
||||
element = element.Next()
|
||||
}
|
||||
|
||||
skip += limit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ architectures:
|
||||
environment:
|
||||
GITEA_CUSTOM: "$SNAP_COMMON"
|
||||
GITEA_WORK_DIR: "$SNAP_DATA"
|
||||
GIT_TEMPLATE_DIR: "$SNAP/usr/share/git-core/templates"
|
||||
GIT_EXEC_PATH: "$SNAP/usr/lib/git-core"
|
||||
|
||||
apps:
|
||||
gitea:
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</span>
|
||||
<div class="menu">
|
||||
<a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a>
|
||||
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{.SignedUser.ID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
|
||||
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
|
||||
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a>
|
||||
<a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a>
|
||||
</div>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</span>
|
||||
<div class="menu">
|
||||
<a class="{{if eq .ViewType "all"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.all_issues"}}</a>
|
||||
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{.SignedUser.ID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
|
||||
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
|
||||
<a class="{{if eq .ViewType "created_by"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.created_by_you"}}</a>
|
||||
<a class="{{if eq .ViewType "mentioned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}</a>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
<div class="header-right actions df ac">
|
||||
{{if not $.Repository.IsArchived}}
|
||||
{{if eq .PosterID .Issue.PosterID }}
|
||||
{{if or (and (eq .PosterID .Issue.PosterID) (eq .Issue.OriginalAuthorID 0)) (eq .Issue.OriginalAuthorID .OriginalAuthorID) }}
|
||||
<div class="item tag">
|
||||
{{$.i18n.Tr "repo.issues.poster"}}
|
||||
</div>
|
||||
|
||||
@@ -2541,6 +2541,9 @@
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/Branch"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2582,6 +2585,9 @@
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/error"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2089,6 +2089,7 @@ function initCodeView() {
|
||||
});
|
||||
$(document).on('click', '.blob-excerpt', async ({currentTarget}) => {
|
||||
const {url, query, anchor} = currentTarget.dataset;
|
||||
if (!url) return;
|
||||
const blob = await $.get(`${url}?${query}&anchor=${anchor}`);
|
||||
currentTarget.closest('tr').outerHTML = blob;
|
||||
});
|
||||
|
||||
@@ -893,6 +893,11 @@ a.ui.basic.green.label:hover {
|
||||
color: #dbdbdb !important;
|
||||
}
|
||||
|
||||
.ui.ui.ui.ui.table tr.grey:not(.marked),
|
||||
.ui.ui.table td.grey:not(.marked) {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.repository.file.list #repo-files-table tr {
|
||||
background: #2a2e3a;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user