mirror of
https://github.com/go-gitea/gitea
synced 2026-02-07 14:20:57 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d876ab1c8 | ||
|
|
f58715d903 | ||
|
|
81072af8ce | ||
|
|
1d7a855d66 | ||
|
|
3c8842d58c | ||
|
|
e786f098d5 | ||
|
|
22dec1cea6 | ||
|
|
0c5e2e2e4c | ||
|
|
158b716387 | ||
|
|
e611dbbe86 | ||
|
|
68bca621cd | ||
|
|
c4e0f717e7 | ||
|
|
e3e024876e | ||
|
|
cebc125f7f | ||
|
|
c975287149 | ||
|
|
b31068652a | ||
|
|
918e640590 | ||
|
|
9809fe27c4 | ||
|
|
ed6a2f2e2e | ||
|
|
a0435fcd63 | ||
|
|
a2e2045a96 | ||
|
|
b67a023bec | ||
|
|
b1a90f7286 | ||
|
|
45c8a3aeeb | ||
|
|
0e5126da22 | ||
|
|
cd3e52d91b | ||
|
|
319c92163f | ||
|
|
a15a644c05 | ||
|
|
2ccd92407a | ||
|
|
17c691f8aa | ||
|
|
cb3fe4cbf1 | ||
|
|
c63a80138a | ||
|
|
b4b8c9679f | ||
|
|
c0bb5ebc15 | ||
|
|
9409ac9030 | ||
|
|
a3928fd820 | ||
|
|
f0bda12c49 | ||
|
|
58c38ab4b6 | ||
|
|
a276aaf61e | ||
|
|
e03934f035 | ||
|
|
3a14a69e8a | ||
|
|
f0f48e0fff | ||
|
|
1aeeaa8e89 | ||
|
|
eb8d5f6aff | ||
|
|
9ef148abeb | ||
|
|
f11df80058 | ||
|
|
59913f405c | ||
|
|
42dae399eb | ||
|
|
f36efe0b54 | ||
|
|
38664d7f39 | ||
|
|
c4e52d232e | ||
|
|
2b257a91de | ||
|
|
c01afd584d | ||
|
|
1270e2ad85 | ||
|
|
29fa3a0f68 | ||
|
|
65a573f3c7 | ||
|
|
ade5ec5aa7 | ||
|
|
38ce87a61a | ||
|
|
261b19ced7 | ||
|
|
6ef0ab4d96 | ||
|
|
ecdb4c1750 | ||
|
|
a0e76de75a | ||
|
|
880f26c7f0 | ||
|
|
fd461ca555 | ||
|
|
3487fb66a1 | ||
|
|
1122230d0e |
+12
-12
@@ -1,44 +1,44 @@
|
||||
repo: go-gitea/gitea
|
||||
groups:
|
||||
-
|
||||
-
|
||||
name: BREAKING
|
||||
labels:
|
||||
- kind/breaking
|
||||
-
|
||||
-
|
||||
name: FEATURE
|
||||
labels:
|
||||
- kind/feature
|
||||
-
|
||||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
-
|
||||
-
|
||||
name: ENHANCEMENT
|
||||
labels:
|
||||
- kind/enhancement
|
||||
- kind/refactor
|
||||
- kind/ui
|
||||
-
|
||||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
- kind/testing
|
||||
-
|
||||
-
|
||||
name: TRANSLATION
|
||||
labels:
|
||||
- kind/translation
|
||||
-
|
||||
-
|
||||
name: BUILD
|
||||
labels:
|
||||
- kind/build
|
||||
- kind/lint
|
||||
-
|
||||
-
|
||||
name: DOCS
|
||||
labels:
|
||||
- kind/docs
|
||||
-
|
||||
-
|
||||
name: MISC
|
||||
default: true
|
||||
default: true
|
||||
|
||||
+3
-2
@@ -30,6 +30,7 @@ services:
|
||||
image: postgres:9.5
|
||||
environment:
|
||||
POSTGRES_DB: test
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
- name: mssql
|
||||
pull: default
|
||||
@@ -388,7 +389,7 @@ steps:
|
||||
|
||||
- name: static
|
||||
pull: always
|
||||
image: techknowlogick/xgo:latest
|
||||
image: techknowlogick/xgo:go-1.13.x
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make generate
|
||||
@@ -490,7 +491,7 @@ steps:
|
||||
|
||||
- name: static
|
||||
pull: always
|
||||
image: techknowlogick/xgo:latest
|
||||
image: techknowlogick/xgo:go-1.13.x
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make generate
|
||||
|
||||
+87
-4
@@ -4,6 +4,89 @@ 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.10.6](https://github.com/go-gitea/gitea/releases/tag/v1.10.6) - 2020-03-10
|
||||
|
||||
This is a re-tag version of v1.10.5 and also explicitly built with Go 1.13.
|
||||
|
||||
WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should **not** be used.
|
||||
|
||||
## [1.10.5](https://github.com/go-gitea/gitea/releases/tag/v1.10.5) - 2020-03-06
|
||||
|
||||
* BUGFIXES
|
||||
* Fix release attachments being deleted while upgrading (#10572) (#10574)
|
||||
|
||||
## [1.10.4](https://github.com/go-gitea/gitea/releases/tag/v1.10.4) - 2020-02-16
|
||||
|
||||
* FEATURE
|
||||
* Prevent empty LDAP search from deactivating all users (#9879) (#9890)
|
||||
* BUGFIXES
|
||||
* Fix reply on code review (#10261) (#10227)
|
||||
* Fix branch page pull request title and link error (#10092) (#10098)
|
||||
* Fix milestone API state parameter unhandled (#10049) (#10053)
|
||||
* Fix wiki raw view on sub path (#10002) (#10041)
|
||||
* Fix RocketChat Webhook (#9908) (#9921) (#9925)
|
||||
* Fix bug about wrong dependencies permissions check and other wrong permissions check (#9884) (Partial backport #9842)
|
||||
* Ensure that 2fa is checked on reset-password (#9857) (#9877)
|
||||
|
||||
## [1.10.3](https://github.com/go-gitea/gitea/releases/tag/v1.10.3) - 2020-01-17
|
||||
* SECURITY
|
||||
* Hide credentials when submitting migration (#9102) (#9704)
|
||||
* Never allow an empty password to validate (#9682) (#9684)
|
||||
* Prevent redirect to Host (#9678) (#9680)
|
||||
* Hide public repos owned by private orgs (#9609) (#9616)
|
||||
* BUGFIXES
|
||||
* Allow assignee on Pull Creation when Issue Unit is deactivated (#9836) (#9838)
|
||||
* Fix download file wrong content-type (#9825) (#9835)
|
||||
* Fix wrong identify poster on a migrated pull request when submit review (#9827) (#9831)
|
||||
* Fix dump non-exist log directory (#9818) (#9820)
|
||||
* Fix compare (#9808) (#9815)
|
||||
* Fix missing msteam webhook on organization (#9781) (#9795)
|
||||
* Fix add team on collaborator page when same name as organization (#9783)
|
||||
* Fix cache problem on dashboard (#9358) (#9703)
|
||||
* Send tag create and push webhook when release created on UI (#8671) (#9702)
|
||||
* Branches not at ref commit ID should not be listed as Merged (#9614) (#9639)
|
||||
|
||||
## [1.10.2](https://github.com/go-gitea/gitea/releases/tag/v1.10.2) - 2020-01-02
|
||||
* BUGFIXES
|
||||
* Allow only specific Columns to be updated on Issue via API (#9539) (#9580)
|
||||
* Add ErrReactionAlreadyExist error (#9550) (#9564)
|
||||
* Fix bug when migrate from API (#8631) (#9563)
|
||||
* Use default avatar for ghost user (#9536) (#9537)
|
||||
* Fix repository issues pagination bug when there are more than one label filter (#9512) (#9528)
|
||||
* Fix deleted branch not removed when push the branch again (#9516) (#9524)
|
||||
* Fix missing repository status when migrating repository via API (#9511)
|
||||
* Trigger webhook when deleting a branch after merging a PR (#9510)
|
||||
* Fix paging on /repos/{owner}/{repo}/git/trees/{sha} API endpoint (#9482)
|
||||
* Fix NewCommitStatus (#9434) (#9435)
|
||||
* Use OriginalURL instead of CloneAddr in migration logging (#9418) (#9420)
|
||||
* Fix Slack webhook payload title generation to work with Mattermost (#9404)
|
||||
* DefaultBranch needs to be prefixed by BranchPrefix (#9356) (#9359)
|
||||
* Fix issue indexer not triggered when migrating a repository (#9333)
|
||||
* Fix bug that release attachment files not deleted when deleting repository (#9322) (#9329)
|
||||
* Fix migration releases (#9319) (#9326) (#9328)
|
||||
* Fix File Edit: Author/Committer interchanged (#9297) (#9300)
|
||||
|
||||
## [1.10.1](https://github.com/go-gitea/gitea/releases/tag/v1.10.1) - 2019-12-05
|
||||
* BUGFIXES
|
||||
* Fix max length check and limit in multiple repo forms (#9148) (#9204)
|
||||
* Properly fix displaying virtual session provider in admin panel (#9137) (#9203)
|
||||
* Upgrade levelqueue to 0.1.0 (#9192) (#9199)
|
||||
* Fix panic when diff (#9187) (#9193)
|
||||
* Smtp logger configuration sendTos should be an array (#9154) (#9157)
|
||||
* Always Show Password Field on Link Account Sign-in Page (#9150)
|
||||
* Create PR on Current Repository by Default (#8670) (#9141)
|
||||
* Fix race on indexer (#9136) (#9139)
|
||||
* Fix reCAPTCHA URL (#9119)
|
||||
* Hide migrated credentials (#9098)
|
||||
* Update golang.org/x/crypto vendor to use acme v2 (#9056) (#9085)
|
||||
* Fix password checks on admin create/edit user (#9076) (#9081)
|
||||
* Fix add search as a reserved username (#9063) (#9065)
|
||||
* Fix permission checks for close/reopen from commit (#8875) (#9033)
|
||||
* Ensure Written is set in GZIP ProxyResponseWriter (#9018) (#9025)
|
||||
* Fix broken link to branch from issue list (#9003) (#9021)
|
||||
* Fix wrong system notice when repository is empty (#9020)
|
||||
* Shadow password correctly for session config (#8984) (#9002)
|
||||
|
||||
## [1.10.0](https://github.com/go-gitea/gitea/releases/tag/v1.10.0) - 2019-11-13
|
||||
* BREAKING
|
||||
* Fix deadline on update issue or PR via API (#8698)
|
||||
@@ -13,8 +96,10 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Add pagination for admin api get orgs and fix only list public orgs bug (#7742)
|
||||
* Implement the ability to change the ssh port to match what is in the gitea config (#7286)
|
||||
* SECURITY
|
||||
* Fix issue with user.fullname (#8903)
|
||||
* Ignore mentions for users with no access (#8395)
|
||||
* Be more strict with git arguments (#7715)
|
||||
* Extract the username and password from the mirror url (#7651)
|
||||
* reserve .well-known username (#7637)
|
||||
* FEATURE
|
||||
* Org/Members: display 2FA members states + optimize sql requests (#7621)
|
||||
@@ -25,7 +110,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Add option to initialize repository with labels (#6061)
|
||||
* Add additional password hash algorithms (#6023)
|
||||
* BUGFIXES
|
||||
* Allow to merge if file path contains " or \ (#8629) (#8771)
|
||||
* Allow to merge if file path contains " or \ (#8629) (#8771)
|
||||
* On windows set core.longpaths true (#8776) (#8786)
|
||||
* Fix 500 when edit hook (#8782) (#8789)
|
||||
* Fix Checkbox at RepoSettings Protected Branch (#8799) (#8801)
|
||||
@@ -37,8 +122,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Fix require external registration password (#8885) (#8890)
|
||||
* Fix password complexity check on registration (#8887) (#8888)
|
||||
* Update Github Migration Tests (#8896) (#8938) (#8945)
|
||||
* Fix issue with user.fullname (#8903)
|
||||
* Enable punctuations ending mentions (#8889) (#8894)
|
||||
* Enable punctuations ending mentions (#8889) (#8894)
|
||||
* Add Close() method to gogitRepository (#8901) (#8956)
|
||||
* Hotfix for review actions and notifications (#8965)
|
||||
* Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) (#8618)
|
||||
@@ -288,7 +372,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
* Fix global search result CSS, misc CSS tweaks (#7789)
|
||||
* Tweak label border CSS (#7739)
|
||||
* Fix create menu item widths (#7708)
|
||||
* Extract the username and password from the mirror url (#7651)
|
||||
* [Branch View] Delete duplicate protection symbol (#7624)
|
||||
* [Branch View] Delete Table Header (#7622)
|
||||
* [Branch View] icons to buttons (#7602)
|
||||
|
||||
@@ -61,6 +61,10 @@ var (
|
||||
Name: "admin-filter",
|
||||
Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "allow-deactivate-all",
|
||||
Usage: "Allow empty search results to deactivate all users.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "username-attribute",
|
||||
Usage: "The attribute of the user’s LDAP record containing the user name.",
|
||||
@@ -231,6 +235,9 @@ func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
|
||||
if c.IsSet("admin-filter") {
|
||||
config.Source.AdminFilter = c.String("admin-filter")
|
||||
}
|
||||
if c.IsSet("allow-deactivate-all") {
|
||||
config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -145,8 +145,10 @@ func runDump(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := z.AddDir("log", setting.LogRootPath); err != nil {
|
||||
log.Fatalf("Failed to include log: %v", err)
|
||||
if com.IsExist(setting.LogRootPath) {
|
||||
if err := z.AddDir("log", setting.LogRootPath); err != nil {
|
||||
log.Fatalf("Failed to include log: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = z.Close(); err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ go 1.13
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.45.0 // indirect
|
||||
gitea.com/lunny/levelqueue v0.1.0
|
||||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
|
||||
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76
|
||||
gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae
|
||||
@@ -67,7 +68,6 @@ require (
|
||||
github.com/lafriks/xormstore v1.3.1
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e
|
||||
github.com/mailru/easyjson v0.7.0 // indirect
|
||||
github.com/markbates/goth v1.56.0
|
||||
github.com/mattn/go-isatty v0.0.7
|
||||
@@ -104,7 +104,7 @@ require (
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect
|
||||
github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53
|
||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad
|
||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba
|
||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b
|
||||
|
||||
@@ -12,6 +12,8 @@ cloud.google.com/go v0.45.0 h1:bALuGBSgE+BD4rxsopAYlqjcwqcQtye6pWG4bC3N/k0=
|
||||
cloud.google.com/go v0.45.0/go.mod h1:452BcPOeI9AZfbvDw0Tbo7D32wA+WX9WME8AZwMEDZU=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
gitea.com/lunny/levelqueue v0.1.0 h1:7wMk0VH6mvKN6vZEZCy9nUDgRmdPLgeNrm1NkW8EHNk=
|
||||
gitea.com/lunny/levelqueue v0.1.0/go.mod h1:G7hVb908t0Bl0uk7zGSg14fyzNtxgtD9Shf04wkMK7s=
|
||||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ=
|
||||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b/go.mod h1:Cxadig6POWpPYYSfg23E7jo35Yf0yvsdC1lifoKWmPo=
|
||||
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 h1:mMsMEg90c5KXQgRWsH8D6GHXfZIW1RAe5S9VYIb12lM=
|
||||
@@ -391,8 +393,6 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
|
||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
|
||||
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e h1:GSprKUrG9wNgwQgROvjPGXmcZrg4OLslOuZGB0uJjx8=
|
||||
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e/go.mod h1:rQZVENnBOiVakCs97XvclbwJRTAv77CRFWcYVNDkVf8=
|
||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk=
|
||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
|
||||
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af h1:UaWHNBdukWrSG3DRvHFR/hyfg681fceqQDYVTBncKfQ=
|
||||
@@ -627,6 +627,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49N
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
|
||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE=
|
||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2020 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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIIssuesMilestone(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
|
||||
milestone := models.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone)
|
||||
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: milestone.RepoID}).(*models.Repository)
|
||||
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
|
||||
assert.Equal(t, int64(1), int64(milestone.NumIssues))
|
||||
assert.Equal(t, structs.StateOpen, milestone.State())
|
||||
|
||||
session := loginUser(t, owner.Name)
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
|
||||
// update values of issue
|
||||
milestoneState := "closed"
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, milestone.ID, token)
|
||||
req := NewRequestWithJSON(t, "PATCH", urlStr, structs.EditMilestoneOption{
|
||||
State: &milestoneState,
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
var apiMilestone structs.Milestone
|
||||
DecodeJSON(t, resp, &apiMilestone)
|
||||
assert.EqualValues(t, "closed", apiMilestone.State)
|
||||
|
||||
req = NewRequest(t, "GET", urlStr)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
var apiMilestone2 structs.Milestone
|
||||
DecodeJSON(t, resp, &apiMilestone2)
|
||||
assert.EqualValues(t, "closed", apiMilestone2.State)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -62,3 +63,61 @@ func TestAPICreateIssue(t *testing.T) {
|
||||
Title: title,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEditIssue(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
|
||||
issueBefore := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 9}).(*models.Issue)
|
||||
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository)
|
||||
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.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)
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
|
||||
// update values of issue
|
||||
issueState := "closed"
|
||||
removeDeadline := time.Unix(0, 0)
|
||||
milestone := int64(4)
|
||||
body := "new content!"
|
||||
title := "new title from api set"
|
||||
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repo.Name, issueBefore.Index, token)
|
||||
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
|
||||
State: &issueState,
|
||||
Deadline: &removeDeadline,
|
||||
Milestone: &milestone,
|
||||
Body: &body,
|
||||
Title: title,
|
||||
|
||||
// ToDo change more
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusCreated)
|
||||
var apiIssue api.Issue
|
||||
DecodeJSON(t, resp, &apiIssue)
|
||||
|
||||
issueAfter := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 9}).(*models.Issue)
|
||||
|
||||
// check deleted user
|
||||
assert.Equal(t, int64(500), issueAfter.PosterID)
|
||||
assert.NoError(t, issueAfter.LoadAttributes())
|
||||
assert.Equal(t, int64(-1), issueAfter.PosterID)
|
||||
assert.Equal(t, int64(-1), issueBefore.PosterID)
|
||||
assert.Equal(t, int64(-1), apiIssue.Poster.ID)
|
||||
|
||||
// API response
|
||||
assert.Equal(t, api.StateClosed, apiIssue.State)
|
||||
assert.Equal(t, milestone, apiIssue.Milestone.ID)
|
||||
assert.Equal(t, body, apiIssue.Body)
|
||||
assert.True(t, apiIssue.Deadline == nil)
|
||||
assert.Equal(t, title, apiIssue.Title)
|
||||
|
||||
// in database
|
||||
assert.Equal(t, api.StateClosed, issueAfter.State())
|
||||
assert.Equal(t, milestone, issueAfter.MilestoneID)
|
||||
assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix))
|
||||
assert.Equal(t, body, issueAfter.Content)
|
||||
assert.Equal(t, title, issueAfter.Title)
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ func getCreateFileOptions() api.CreateFileOptions {
|
||||
NewBranchName: "master",
|
||||
Message: "Making this new file new/file.txt",
|
||||
Author: api.Identity{
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
Name: "Anne Doe",
|
||||
Email: "annedoe@example.com",
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: "Jane Doe",
|
||||
Email: "janedoe@example.com",
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
},
|
||||
Content: contentEncoded,
|
||||
@@ -77,8 +77,8 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon
|
||||
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
|
||||
Author: &api.CommitUser{
|
||||
Identity: api.Identity{
|
||||
Name: "Jane Doe",
|
||||
Email: "janedoe@example.com",
|
||||
Name: "Anne Doe",
|
||||
Email: "annedoe@example.com",
|
||||
},
|
||||
},
|
||||
Committer: &api.CommitUser{
|
||||
|
||||
@@ -35,8 +35,8 @@ func getUpdateFileOptions() *api.UpdateFileOptions {
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
Committer: api.Identity{
|
||||
Name: "Jane Doe",
|
||||
Email: "janedoe@example.com",
|
||||
Name: "Anne Doe",
|
||||
Email: "annedoe@example.com",
|
||||
},
|
||||
},
|
||||
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
|
||||
@@ -80,14 +80,14 @@ func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileRespon
|
||||
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
|
||||
Author: &api.CommitUser{
|
||||
Identity: api.Identity{
|
||||
Name: "Jane Doe",
|
||||
Email: "janedoe@example.com",
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
},
|
||||
Committer: &api.CommitUser{
|
||||
Identity: api.Identity{
|
||||
Name: "John Doe",
|
||||
Email: "johndoe@example.com",
|
||||
Name: "Anne Doe",
|
||||
Email: "annedoe@example.com",
|
||||
},
|
||||
},
|
||||
Message: "My update of README.md\n",
|
||||
|
||||
@@ -334,7 +334,7 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) {
|
||||
resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict)
|
||||
respJSON := map[string]string{}
|
||||
DecodeJSON(t, resp, &respJSON)
|
||||
assert.Equal(t, respJSON["message"], "The repository with the same name already exists.")
|
||||
assert.Equal(t, "The repository with the same name already exists.", respJSON["message"])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
4a357436d925b5c974181ff12a994538ddc5a269
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1 +1 @@
|
||||
0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3
|
||||
423313fbd38093bb10d0c8387db9105409c6f196
|
||||
|
||||
@@ -90,6 +90,7 @@ func TestRenameReservedUsername(t *testing.T) {
|
||||
"repo",
|
||||
"template",
|
||||
"user",
|
||||
"search",
|
||||
}
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
+30
-17
@@ -533,31 +533,44 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
|
||||
}
|
||||
refMarked[key] = true
|
||||
|
||||
// only create comments for issues if user has permission for it
|
||||
if perm.IsAdmin() || perm.IsOwner() || perm.CanWrite(UnitTypeIssues) {
|
||||
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, html.EscapeString(c.Message))
|
||||
if err = CreateRefComment(doer, refRepo, refIssue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: this kind of condition is all over the code, it should be consolidated in a single place
|
||||
canclose := perm.IsAdmin() || perm.IsOwner() || perm.CanWrite(UnitTypeIssues) || refIssue.PosterID == doer.ID
|
||||
cancomment := canclose || perm.CanRead(UnitTypeIssues)
|
||||
|
||||
// Don't proceed if the user can't comment
|
||||
if !cancomment {
|
||||
continue
|
||||
}
|
||||
|
||||
// Process closing/reopening keywords
|
||||
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, html.EscapeString(c.Message))
|
||||
if err = CreateRefComment(doer, refRepo, refIssue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only issues can be closed/reopened this way, and user needs the correct permissions
|
||||
if refIssue.IsPull || !canclose {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only process closing/reopening keywords
|
||||
if ref.Action != references.XRefActionCloses && ref.Action != references.XRefActionReopens {
|
||||
continue
|
||||
}
|
||||
|
||||
// Change issue status only if the commit has been pushed to the default branch.
|
||||
// and if the repo is configured to allow only that
|
||||
// FIXME: we should be using Issue.ref if set instead of repo.DefaultBranch
|
||||
if repo.DefaultBranch != branchName && !repo.CloseIssuesViaCommitInAnyBranch {
|
||||
continue
|
||||
if !repo.CloseIssuesViaCommitInAnyBranch {
|
||||
// If the issue was specified to be in a particular branch, don't allow commits in other branches to close it
|
||||
if refIssue.Ref != "" {
|
||||
if branchName != refIssue.Ref {
|
||||
continue
|
||||
}
|
||||
// Otherwise, only process commits to the default branch
|
||||
} else if branchName != repo.DefaultBranch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// only close issues in another repo if user has push access
|
||||
if perm.IsAdmin() || perm.IsOwner() || perm.CanWrite(UnitTypeCode) {
|
||||
if err := changeIssueStatus(refRepo, refIssue, doer, ref.Action == references.XRefActionCloses); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := changeIssueStatus(refRepo, refIssue, doer, ref.Action == references.XRefActionCloses); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ func TestUpdateIssuesCommit(t *testing.T) {
|
||||
PosterID: user.ID,
|
||||
IssueID: 1,
|
||||
}
|
||||
issueBean := &Issue{RepoID: repo.ID, Index: 2}
|
||||
issueBean := &Issue{RepoID: repo.ID, Index: 4}
|
||||
|
||||
AssertNotExistsBean(t, commentBean)
|
||||
AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
|
||||
@@ -273,7 +273,7 @@ func TestUpdateIssuesCommit_Colon(t *testing.T) {
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||
repo.Owner = user
|
||||
|
||||
issueBean := &Issue{RepoID: repo.ID, Index: 2}
|
||||
issueBean := &Issue{RepoID: repo.ID, Index: 4}
|
||||
|
||||
AssertNotExistsBean(t, &Issue{RepoID: repo.ID, Index: 2}, "is_closed=1")
|
||||
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, repo.DefaultBranch))
|
||||
|
||||
@@ -480,6 +480,12 @@ func (deletedBranch *DeletedBranch) LoadUser() {
|
||||
deletedBranch.DeletedBy = user
|
||||
}
|
||||
|
||||
// RemoveDeletedBranch removes all deleted branches
|
||||
func RemoveDeletedBranch(repoID int64, branch string) error {
|
||||
_, err := x.Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch))
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveOldDeletedBranches removes old deleted branches
|
||||
func RemoveOldDeletedBranches() {
|
||||
log.Trace("Doing: DeletedBranchesCleanup")
|
||||
|
||||
@@ -1104,6 +1104,21 @@ func (err ErrNewIssueInsert) Error() string {
|
||||
return err.OriginalError.Error()
|
||||
}
|
||||
|
||||
// ErrReactionAlreadyExist is used when a existing reaction was try to created
|
||||
type ErrReactionAlreadyExist struct {
|
||||
Reaction string
|
||||
}
|
||||
|
||||
// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist.
|
||||
func IsErrReactionAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrReactionAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReactionAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("reaction '%s' already exists", err.Reaction)
|
||||
}
|
||||
|
||||
// __________ .__ .__ __________ __
|
||||
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
|
||||
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
|
||||
|
||||
@@ -96,4 +96,17 @@
|
||||
is_closed: false
|
||||
is_pull: true
|
||||
created_unix: 946684820
|
||||
updated_unix: 978307180
|
||||
updated_unix: 978307180
|
||||
|
||||
-
|
||||
id: 9
|
||||
repo_id: 42
|
||||
index: 1
|
||||
poster_id: 500
|
||||
name: issue from deleted account
|
||||
content: content from deleted account
|
||||
is_closed: false
|
||||
is_pull: false
|
||||
created_unix: 946684830
|
||||
updated_unix: 999307200
|
||||
deadline_unix: 1019307200
|
||||
|
||||
@@ -21,3 +21,11 @@
|
||||
content: content3
|
||||
is_closed: true
|
||||
num_issues: 0
|
||||
|
||||
-
|
||||
id: 4
|
||||
repo_id: 42
|
||||
name: milestone of repo42
|
||||
content: content random
|
||||
is_closed: false
|
||||
num_issues: 0
|
||||
|
||||
@@ -547,7 +547,8 @@
|
||||
is_private: false
|
||||
num_stars: 0
|
||||
num_forks: 0
|
||||
num_issues: 0
|
||||
num_issues: 1
|
||||
num_milestones: 1
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
|
||||
+25
-17
@@ -1,4 +1,5 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 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.
|
||||
|
||||
@@ -238,6 +239,16 @@ func (issue *Issue) loadReactions(e Engine) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (issue *Issue) loadMilestone(e Engine) (err error) {
|
||||
if issue.Milestone == nil && issue.MilestoneID > 0 {
|
||||
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
|
||||
if err != nil && !IsErrMilestoneNotExist(err) {
|
||||
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (issue *Issue) loadAttributes(e Engine) (err error) {
|
||||
if err = issue.loadRepo(e); err != nil {
|
||||
return
|
||||
@@ -251,11 +262,8 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if issue.Milestone == nil && issue.MilestoneID > 0 {
|
||||
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
|
||||
if err != nil && !IsErrMilestoneNotExist(err) {
|
||||
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
|
||||
}
|
||||
if err = issue.loadMilestone(e); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = issue.loadAssignees(e); err != nil {
|
||||
@@ -295,6 +303,11 @@ func (issue *Issue) LoadAttributes() error {
|
||||
return issue.loadAttributes(x)
|
||||
}
|
||||
|
||||
// LoadMilestone load milestone of this issue.
|
||||
func (issue *Issue) LoadMilestone() error {
|
||||
return issue.loadMilestone(x)
|
||||
}
|
||||
|
||||
// GetIsRead load the `IsRead` field of the issue
|
||||
func (issue *Issue) GetIsRead(userID int64) error {
|
||||
issueUser := &IssueUser{IssueID: issue.ID, UID: userID}
|
||||
@@ -416,7 +429,7 @@ func (issue *Issue) HashTag() string {
|
||||
|
||||
// IsPoster returns true if given user by ID is the poster.
|
||||
func (issue *Issue) IsPoster(uid int64) bool {
|
||||
return issue.PosterID == uid
|
||||
return issue.OriginalAuthorID == 0 && issue.PosterID == uid
|
||||
}
|
||||
|
||||
func (issue *Issue) hasLabel(e Engine, labelID int64) bool {
|
||||
@@ -1730,22 +1743,17 @@ func SearchIssueIDsByKeyword(kw string, repoID int64, limit, start int) (int64,
|
||||
return total, ids, nil
|
||||
}
|
||||
|
||||
func updateIssue(e Engine, issue *Issue) error {
|
||||
_, err := e.ID(issue.ID).AllCols().Update(issue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIssue updates all fields of given issue.
|
||||
func UpdateIssue(issue *Issue) error {
|
||||
// UpdateIssueByAPI updates all allowed fields of given issue.
|
||||
func UpdateIssueByAPI(issue *Issue) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateIssue(sess, issue); err != nil {
|
||||
if _, err := sess.ID(issue.ID).Cols(
|
||||
"name", "is_closed", "content", "milestone_id", "priority",
|
||||
"deadline_unix", "updated_unix", "closed_unix", "is_locked").
|
||||
Update(issue); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := issue.neuterCrossReferences(sess); err != nil {
|
||||
|
||||
@@ -253,12 +253,33 @@ func updateMilestone(e Engine, m *Milestone) error {
|
||||
}
|
||||
|
||||
// UpdateMilestone updates information of given milestone.
|
||||
func UpdateMilestone(m *Milestone) error {
|
||||
if err := updateMilestone(x, m); err != nil {
|
||||
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updateMilestoneCompleteness(x, m.ID)
|
||||
if m.IsClosed && !oldIsClosed {
|
||||
m.ClosedDateUnix = timeutil.TimeStampNow()
|
||||
}
|
||||
|
||||
if err := updateMilestone(sess, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateMilestoneCompleteness(sess, m.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if IsClosed changed, update milestone numbers of repository
|
||||
if oldIsClosed != m.IsClosed {
|
||||
if err := updateRepoMilestoneNum(sess, m.RepoID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func updateMilestoneCompleteness(e Engine, milestoneID int64) error {
|
||||
|
||||
@@ -160,8 +160,9 @@ func TestUpdateMilestone(t *testing.T) {
|
||||
milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
|
||||
milestone.Name = "newMilestoneName"
|
||||
milestone.Content = "newMilestoneContent"
|
||||
assert.NoError(t, UpdateMilestone(milestone))
|
||||
AssertExistsAndLoadBean(t, milestone)
|
||||
assert.NoError(t, UpdateMilestone(milestone, milestone.IsClosed))
|
||||
milestone = AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
|
||||
assert.EqualValues(t, "newMilestoneName", milestone.Name)
|
||||
CheckConsistencyFor(t, &Milestone{})
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ type Reaction struct {
|
||||
type FindReactionsOptions struct {
|
||||
IssueID int64
|
||||
CommentID int64
|
||||
UserID int64
|
||||
Reaction string
|
||||
}
|
||||
|
||||
func (opts *FindReactionsOptions) toConds() builder.Cond {
|
||||
@@ -40,6 +42,13 @@ func (opts *FindReactionsOptions) toConds() builder.Cond {
|
||||
if opts.CommentID > 0 {
|
||||
cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
|
||||
}
|
||||
if opts.UserID > 0 {
|
||||
cond = cond.And(builder.Eq{"reaction.user_id": opts.UserID})
|
||||
}
|
||||
if opts.Reaction != "" {
|
||||
cond = cond.And(builder.Eq{"reaction.type": opts.Reaction})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
@@ -57,9 +66,25 @@ func createReaction(e *xorm.Session, opts *ReactionOptions) (*Reaction, error) {
|
||||
UserID: opts.Doer.ID,
|
||||
IssueID: opts.Issue.ID,
|
||||
}
|
||||
findOpts := FindReactionsOptions{
|
||||
IssueID: opts.Issue.ID,
|
||||
CommentID: -1, // reaction to issue only
|
||||
Reaction: opts.Type,
|
||||
UserID: opts.Doer.ID,
|
||||
}
|
||||
if opts.Comment != nil {
|
||||
reaction.CommentID = opts.Comment.ID
|
||||
findOpts.CommentID = opts.Comment.ID
|
||||
}
|
||||
|
||||
existingR, err := findReactions(e, findOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(existingR) > 0 {
|
||||
return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type}
|
||||
}
|
||||
|
||||
if _, err := e.Insert(reaction); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,19 +101,19 @@ type ReactionOptions struct {
|
||||
}
|
||||
|
||||
// CreateReaction creates reaction for issue or comment.
|
||||
func CreateReaction(opts *ReactionOptions) (reaction *Reaction, err error) {
|
||||
func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
if err := sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reaction, err = createReaction(sess, opts)
|
||||
reaction, err := createReaction(sess, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return reaction, err
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
if err := sess.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reaction, nil
|
||||
|
||||
@@ -50,9 +50,10 @@ func TestIssueAddDuplicateReaction(t *testing.T) {
|
||||
Type: "heart",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, reaction)
|
||||
assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err)
|
||||
|
||||
AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
|
||||
existingR := AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction)
|
||||
assert.Equal(t, existingR.ID, reaction.ID)
|
||||
}
|
||||
|
||||
func TestIssueDeleteReaction(t *testing.T) {
|
||||
@@ -129,7 +130,6 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
|
||||
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
|
||||
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
|
||||
ghost := NewGhostUser()
|
||||
|
||||
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
|
||||
|
||||
@@ -139,14 +139,13 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
|
||||
addReaction(t, user2, issue1, comment1, "heart")
|
||||
addReaction(t, user3, issue1, comment1, "heart")
|
||||
addReaction(t, user4, issue1, comment1, "+1")
|
||||
addReaction(t, ghost, issue1, comment1, "heart")
|
||||
|
||||
err := comment1.LoadReactions()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, comment1.Reactions, 5)
|
||||
assert.Len(t, comment1.Reactions, 4)
|
||||
|
||||
reactions := comment1.Reactions.GroupByType()
|
||||
assert.Len(t, reactions["heart"], 4)
|
||||
assert.Len(t, reactions["heart"], 3)
|
||||
assert.Len(t, reactions["+1"], 1)
|
||||
}
|
||||
|
||||
@@ -160,7 +159,7 @@ func TestIssueCommentReactionCount(t *testing.T) {
|
||||
comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
|
||||
|
||||
addReaction(t, user1, issue1, comment1, "heart")
|
||||
DeleteCommentReaction(user1, issue1, comment1, "heart")
|
||||
assert.NoError(t, DeleteCommentReaction(user1, issue1, comment1, "heart"))
|
||||
|
||||
AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID})
|
||||
}
|
||||
|
||||
+32
-17
@@ -26,23 +26,38 @@ func deleteOrphanedAttachments(x *xorm.Engine) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
err := sess.BufferSize(setting.Database.IterateBufferSize).
|
||||
Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").Cols("uuid").
|
||||
Iterate(new(Attachment),
|
||||
func(idx int, bean interface{}) error {
|
||||
attachment := bean.(*Attachment)
|
||||
|
||||
if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := sess.ID(attachment.ID).NoAutoCondition().Delete(attachment)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
var limit = setting.Database.IterateBufferSize
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
for {
|
||||
attachements := make([]Attachment, 0, limit)
|
||||
if err := sess.Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").
|
||||
Cols("id, uuid").Limit(limit).
|
||||
Asc("id").
|
||||
Find(&attachements); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(attachements) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ids = make([]int64, 0, limit)
|
||||
for _, attachment := range attachements {
|
||||
ids = append(ids, attachment.ID)
|
||||
}
|
||||
if _, err := sess.In("id", ids).Delete(new(Attachment)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, attachment := range attachements {
|
||||
if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(attachements) < limit {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ func (pr *PullRequest) LoadHeadRepo() error {
|
||||
if has, err := x.ID(pr.HeadRepoID).Get(&repo); err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrRepoNotExist{ID: pr.BaseRepoID}
|
||||
return ErrRepoNotExist{ID: pr.HeadRepoID}
|
||||
}
|
||||
pr.HeadRepo = &repo
|
||||
}
|
||||
|
||||
+40
-7
@@ -1781,6 +1781,12 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) {
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// UpdateRepositoryStatus updates a repository's status
|
||||
func UpdateRepositoryStatus(repoID int64, status RepositoryStatus) error {
|
||||
_, err := x.Exec("UPDATE repository SET status = ? WHERE id = ?", status, repoID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRepositoryUpdatedTime updates a repository's updated time
|
||||
func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error {
|
||||
_, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID)
|
||||
@@ -1860,6 +1866,17 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
}
|
||||
}
|
||||
|
||||
attachments := make([]*Attachment, 0, 20)
|
||||
if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
|
||||
Where("`release`.repo_id = ?", repoID).
|
||||
Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
releaseAttachments := make([]string, 0, len(attachments))
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
releaseAttachments = append(releaseAttachments, attachments[i].LocalPath())
|
||||
}
|
||||
|
||||
if err = deleteBeans(sess,
|
||||
&Access{RepoID: repo.ID},
|
||||
&Action{RepoID: repo.ID},
|
||||
@@ -1910,13 +1927,13 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
attachmentPaths := make([]string, 0, 20)
|
||||
attachments := make([]*Attachment, 0, len(attachmentPaths))
|
||||
attachments = attachments[:0]
|
||||
if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id").
|
||||
Where("issue.repo_id = ?", repoID).
|
||||
Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
attachmentPaths := make([]string, 0, len(attachments))
|
||||
for j := range attachments {
|
||||
attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
|
||||
}
|
||||
@@ -1953,11 +1970,6 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove attachment files.
|
||||
for i := range attachmentPaths {
|
||||
removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i])
|
||||
}
|
||||
|
||||
// Remove LFS objects
|
||||
var lfsObjects []*LFSMetaObject
|
||||
if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
|
||||
@@ -1997,6 +2009,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
}
|
||||
|
||||
sess.Close()
|
||||
|
||||
if org.IsOrganization() {
|
||||
if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{
|
||||
Action: api.HookRepoDeleted,
|
||||
@@ -2009,6 +2023,19 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
go HookQueue.Add(repo.ID)
|
||||
}
|
||||
|
||||
// We should always delete the files after the database transaction succeed. If
|
||||
// we delete the file but the database rollback, the repository will be borken.
|
||||
|
||||
// Remove issue attachment files.
|
||||
for i := range attachmentPaths {
|
||||
removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i])
|
||||
}
|
||||
|
||||
// Remove release attachment files.
|
||||
for i := range releaseAttachments {
|
||||
removeAllWithNotice(x, "Delete release attachment", releaseAttachments[i])
|
||||
}
|
||||
|
||||
if len(repo.Avatar) > 0 {
|
||||
avatarPath := repo.CustomAvatarPath()
|
||||
if com.IsExist(avatarPath) {
|
||||
@@ -2784,3 +2811,9 @@ func (repo *Repository) GetOriginalURLHostname() string {
|
||||
|
||||
return u.Host
|
||||
}
|
||||
|
||||
// UpdateRepositoryCols updates repository's columns
|
||||
func UpdateRepositoryCols(repo *Repository, cols ...string) error {
|
||||
_, err := x.ID(repo.ID).Cols(cols...).Update(repo)
|
||||
return err
|
||||
}
|
||||
|
||||
+13
-8
@@ -58,7 +58,7 @@ func (repo *Repository) updateIndexerStatus(sha string) error {
|
||||
}
|
||||
|
||||
type repoIndexerOperation struct {
|
||||
repo *Repository
|
||||
repoID int64
|
||||
deleted bool
|
||||
watchers []chan<- error
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func populateRepoIndexer(maxRepoID int64) {
|
||||
}
|
||||
for _, repo := range repos {
|
||||
repoIndexerOperationQueue <- repoIndexerOperation{
|
||||
repo: repo,
|
||||
repoID: repo.ID,
|
||||
deleted: false,
|
||||
}
|
||||
maxRepoID = repo.ID - 1
|
||||
@@ -131,7 +131,12 @@ func populateRepoIndexer(maxRepoID int64) {
|
||||
log.Info("Done populating the repo indexer with existing repositories")
|
||||
}
|
||||
|
||||
func updateRepoIndexer(repo *Repository) error {
|
||||
func updateRepoIndexer(repoID int64) error {
|
||||
repo, err := getRepositoryByID(x, repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sha, err := getDefaultBranchSha(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -172,7 +177,7 @@ type fileUpdate struct {
|
||||
}
|
||||
|
||||
func getDefaultBranchSha(repo *Repository) (string, error) {
|
||||
stdout, err := git.NewCommand("show-ref", "-s", repo.DefaultBranch).RunInDir(repo.RepoPath())
|
||||
stdout, err := git.NewCommand("show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunInDir(repo.RepoPath())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -339,11 +344,11 @@ func processRepoIndexerOperationQueue() {
|
||||
op := <-repoIndexerOperationQueue
|
||||
var err error
|
||||
if op.deleted {
|
||||
if err = indexer.DeleteRepoFromIndexer(op.repo.ID); err != nil {
|
||||
if err = indexer.DeleteRepoFromIndexer(op.repoID); err != nil {
|
||||
log.Error("DeleteRepoFromIndexer: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err = updateRepoIndexer(op.repo); err != nil {
|
||||
if err = updateRepoIndexer(op.repoID); err != nil {
|
||||
log.Error("updateRepoIndexer: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -355,12 +360,12 @@ func processRepoIndexerOperationQueue() {
|
||||
|
||||
// DeleteRepoFromIndexer remove all of a repository's entries from the indexer
|
||||
func DeleteRepoFromIndexer(repo *Repository, watchers ...chan<- error) {
|
||||
addOperationToQueue(repoIndexerOperation{repo: repo, deleted: true, watchers: watchers})
|
||||
addOperationToQueue(repoIndexerOperation{repoID: repo.ID, deleted: true, watchers: watchers})
|
||||
}
|
||||
|
||||
// UpdateRepoIndexer update a repository's entries in the indexer
|
||||
func UpdateRepoIndexer(repo *Repository, watchers ...chan<- error) {
|
||||
addOperationToQueue(repoIndexerOperation{repo: repo, deleted: false, watchers: watchers})
|
||||
addOperationToQueue(repoIndexerOperation{repoID: repo.ID, deleted: false, watchers: watchers})
|
||||
}
|
||||
|
||||
func addOperationToQueue(op repoIndexerOperation) {
|
||||
|
||||
+7
-2
@@ -120,7 +120,8 @@ type SearchRepoOptions struct {
|
||||
StarredByID int64
|
||||
Page int
|
||||
IsProfile bool
|
||||
AllPublic bool // Include also all public repositories
|
||||
AllPublic bool // Include also all public repositories of users and public organisations
|
||||
AllLimited bool // Include also all public repositories of limited organisations
|
||||
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
|
||||
// None -> include collaborative AND non-collaborative
|
||||
// True -> include just collaborative
|
||||
@@ -240,7 +241,11 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
accessCond = accessCond.Or(builder.Eq{"is_private": false})
|
||||
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}))))
|
||||
}
|
||||
|
||||
if opts.AllLimited {
|
||||
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited}))))
|
||||
}
|
||||
|
||||
cond = cond.And(accessCond)
|
||||
|
||||
@@ -177,8 +177,8 @@ func TestSearchRepository(t *testing.T) {
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
|
||||
count: 22},
|
||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||
count: 28},
|
||||
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true},
|
||||
count: 27},
|
||||
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
|
||||
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
|
||||
count: 15},
|
||||
|
||||
+1
-1
@@ -196,7 +196,7 @@ func CreateMigrateTask(doer, u *User, opts base.MigrateOptions) (*Task, error) {
|
||||
repo, err := CreateRepository(doer, u, CreateRepoOptions{
|
||||
Name: opts.RepoName,
|
||||
Description: opts.Description,
|
||||
OriginalURL: opts.CloneAddr,
|
||||
OriginalURL: opts.OriginalURL,
|
||||
IsPrivate: opts.Private,
|
||||
IsMirror: opts.Mirror,
|
||||
Status: RepositoryBeingMigrated,
|
||||
|
||||
+11
-1
@@ -502,7 +502,7 @@ func (u *User) ValidatePassword(passwd string) bool {
|
||||
|
||||
// IsPasswordSet checks if the password is set or left empty
|
||||
func (u *User) IsPasswordSet() bool {
|
||||
return len(u.Passwd) > 0
|
||||
return !u.ValidatePassword("")
|
||||
}
|
||||
|
||||
// UploadAvatar saves custom avatar for user.
|
||||
@@ -822,6 +822,7 @@ var (
|
||||
".",
|
||||
"..",
|
||||
".well-known",
|
||||
"search",
|
||||
}
|
||||
reservedUserPatterns = []string{"*.keys", "*.gpg"}
|
||||
)
|
||||
@@ -1714,6 +1715,15 @@ func SyncExternalUsers() {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sr) == 0 {
|
||||
if !s.LDAP().AllowDeactivateAll {
|
||||
log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
|
||||
continue
|
||||
} else {
|
||||
log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
|
||||
}
|
||||
}
|
||||
|
||||
for _, su := range sr {
|
||||
if len(su.Username) == 0 {
|
||||
continue
|
||||
|
||||
+94
-82
@@ -36,10 +36,11 @@ type SlackPayload struct {
|
||||
|
||||
// SlackAttachment contains the slack message
|
||||
type SlackAttachment struct {
|
||||
Fallback string `json:"fallback"`
|
||||
Color string `json:"color"`
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
Fallback string `json:"fallback"`
|
||||
Color string `json:"color"`
|
||||
Title string `json:"title"`
|
||||
TitleLink string `json:"title_link"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// SetSecret sets the slack secret
|
||||
@@ -133,70 +134,78 @@ func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, e
|
||||
|
||||
func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) {
|
||||
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
||||
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
|
||||
fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
|
||||
var text, title, attachmentText string
|
||||
title := SlackTextFormatter(fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
|
||||
titleLink := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index)
|
||||
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
|
||||
var text, attachmentText string
|
||||
|
||||
switch p.Action {
|
||||
case api.HookIssueOpened:
|
||||
text = fmt.Sprintf("[%s] Issue submitted by %s", p.Repository.FullName, senderLink)
|
||||
title = titleLink
|
||||
text = fmt.Sprintf("[%s] Issue opened by %s", repoLink, senderLink)
|
||||
attachmentText = SlackTextFormatter(p.Issue.Body)
|
||||
case api.HookIssueClosed:
|
||||
text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue closed: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueReOpened:
|
||||
text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue re-opened: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueEdited:
|
||||
text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue edited: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
attachmentText = SlackTextFormatter(p.Issue.Body)
|
||||
case api.HookIssueAssigned:
|
||||
text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName,
|
||||
text = fmt.Sprintf("[%s] Issue assigned to %s: [%s](%s) by %s", repoLink,
|
||||
SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
|
||||
titleLink, senderLink)
|
||||
title, titleLink, senderLink)
|
||||
case api.HookIssueUnassigned:
|
||||
text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue unassigned: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueLabelUpdated:
|
||||
text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue labels updated: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueLabelCleared:
|
||||
text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue labels cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueSynchronized:
|
||||
text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue synchronized: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueMilestoned:
|
||||
text = fmt.Sprintf("[%s] Issue milestoned: #%s %s", p.Repository.FullName, titleLink, senderLink)
|
||||
mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID)
|
||||
text = fmt.Sprintf("[%s] Issue milestoned to [%s](%s): [%s](%s) by %s", repoLink,
|
||||
p.Issue.Milestone.Title, mileStoneLink, title, titleLink, senderLink)
|
||||
case api.HookIssueDemilestoned:
|
||||
text = fmt.Sprintf("[%s] Issue milestone cleared: #%s %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Issue milestone cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
}
|
||||
|
||||
return &SlackPayload{
|
||||
pl := &SlackPayload{
|
||||
Channel: slack.Channel,
|
||||
Text: text,
|
||||
Username: slack.Username,
|
||||
IconURL: slack.IconURL,
|
||||
Attachments: []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
Text: attachmentText,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
if attachmentText != "" {
|
||||
pl.Attachments = []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
TitleLink: titleLink,
|
||||
Text: attachmentText,
|
||||
}}
|
||||
}
|
||||
|
||||
return pl, nil
|
||||
}
|
||||
|
||||
func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
|
||||
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
||||
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)),
|
||||
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
|
||||
var text, title, attachmentText string
|
||||
title := SlackTextFormatter(fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
|
||||
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
|
||||
var text, titleLink, attachmentText string
|
||||
|
||||
switch p.Action {
|
||||
case api.HookIssueCommentCreated:
|
||||
text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink)
|
||||
title = titleLink
|
||||
text = fmt.Sprintf("[%s] New comment created by %s", repoLink, senderLink)
|
||||
titleLink = fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
|
||||
attachmentText = SlackTextFormatter(p.Comment.Body)
|
||||
case api.HookIssueCommentEdited:
|
||||
text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink)
|
||||
title = titleLink
|
||||
text = fmt.Sprintf("[%s] Comment edited by %s", repoLink, senderLink)
|
||||
titleLink = fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
|
||||
attachmentText = SlackTextFormatter(p.Comment.Body)
|
||||
case api.HookIssueCommentDeleted:
|
||||
text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink)
|
||||
title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index),
|
||||
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
|
||||
text = fmt.Sprintf("[%s] Comment deleted by %s", repoLink, senderLink)
|
||||
titleLink = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
|
||||
attachmentText = SlackTextFormatter(p.Comment.Body)
|
||||
}
|
||||
|
||||
@@ -206,9 +215,10 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (
|
||||
Username: slack.Username,
|
||||
IconURL: slack.IconURL,
|
||||
Attachments: []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
Text: attachmentText,
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
TitleLink: titleLink,
|
||||
Text: attachmentText,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
@@ -273,73 +283,85 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
|
||||
Username: slack.Username,
|
||||
IconURL: slack.IconURL,
|
||||
Attachments: []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Text: attachmentText,
|
||||
Color: slack.Color,
|
||||
Title: p.Repo.HTMLURL,
|
||||
TitleLink: p.Repo.HTMLURL,
|
||||
Text: attachmentText,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
|
||||
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
||||
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
|
||||
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
|
||||
var text, title, attachmentText string
|
||||
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
|
||||
titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
|
||||
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
|
||||
var text, attachmentText string
|
||||
|
||||
switch p.Action {
|
||||
case api.HookIssueOpened:
|
||||
text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink)
|
||||
title = titleLink
|
||||
text = fmt.Sprintf("[%s] Pull request opened by %s", repoLink, senderLink)
|
||||
attachmentText = SlackTextFormatter(p.PullRequest.Body)
|
||||
case api.HookIssueClosed:
|
||||
if p.PullRequest.HasMerged {
|
||||
text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request merged: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
} else {
|
||||
text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request closed: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
}
|
||||
case api.HookIssueReOpened:
|
||||
text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request re-opened: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueEdited:
|
||||
text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request edited: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
attachmentText = SlackTextFormatter(p.PullRequest.Body)
|
||||
case api.HookIssueAssigned:
|
||||
list := make([]string, len(p.PullRequest.Assignees))
|
||||
for i, user := range p.PullRequest.Assignees {
|
||||
list[i] = SlackLinkFormatter(setting.AppURL+user.UserName, user.UserName)
|
||||
}
|
||||
text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
|
||||
text = fmt.Sprintf("[%s] Pull request assigned to %s: [%s](%s) by %s", repoLink,
|
||||
strings.Join(list, ", "),
|
||||
titleLink, senderLink)
|
||||
title, titleLink, senderLink)
|
||||
case api.HookIssueUnassigned:
|
||||
text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request unassigned: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueLabelUpdated:
|
||||
text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request labels updated: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueLabelCleared:
|
||||
text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request labels cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueSynchronized:
|
||||
text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request synchronized: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
|
||||
case api.HookIssueMilestoned:
|
||||
text = fmt.Sprintf("[%s] Pull request milestoned: #%s %s", p.Repository.FullName, titleLink, senderLink)
|
||||
mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID)
|
||||
text = fmt.Sprintf("[%s] Pull request milestoned to [%s](%s): [%s](%s) %s", repoLink,
|
||||
p.PullRequest.Milestone.Title, mileStoneLink, title, titleLink, senderLink)
|
||||
case api.HookIssueDemilestoned:
|
||||
text = fmt.Sprintf("[%s] Pull request milestone cleared: #%s %s", p.Repository.FullName, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request milestone cleared: [%s](%s) %s", repoLink, title, titleLink, senderLink)
|
||||
}
|
||||
|
||||
return &SlackPayload{
|
||||
pl := &SlackPayload{
|
||||
Channel: slack.Channel,
|
||||
Text: text,
|
||||
Username: slack.Username,
|
||||
IconURL: slack.IconURL,
|
||||
Attachments: []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
Text: attachmentText,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
if attachmentText != "" {
|
||||
pl.Attachments = []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
TitleLink: titleLink,
|
||||
Text: attachmentText,
|
||||
}}
|
||||
}
|
||||
|
||||
return pl, nil
|
||||
}
|
||||
|
||||
func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*SlackPayload, error) {
|
||||
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
||||
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
|
||||
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
|
||||
var text, title, attachmentText string
|
||||
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
|
||||
titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
|
||||
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
|
||||
var text string
|
||||
|
||||
switch p.Action {
|
||||
case api.HookIssueSynchronized:
|
||||
action, err := parseHookPullRequestEventType(event)
|
||||
@@ -347,7 +369,7 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text = fmt.Sprintf("[%s] Pull request review %s : %s by %s", p.Repository.FullName, action, titleLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink)
|
||||
}
|
||||
|
||||
return &SlackPayload{
|
||||
@@ -355,21 +377,16 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM
|
||||
Text: text,
|
||||
Username: slack.Username,
|
||||
IconURL: slack.IconURL,
|
||||
Attachments: []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
Text: attachmentText,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) {
|
||||
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
||||
var text, title, attachmentText string
|
||||
var text string
|
||||
|
||||
switch p.Action {
|
||||
case api.HookRepoCreated:
|
||||
text = fmt.Sprintf("[%s] Repository created by %s", p.Repository.FullName, senderLink)
|
||||
title = p.Repository.HTMLURL
|
||||
case api.HookRepoDeleted:
|
||||
text = fmt.Sprintf("[%s] Repository deleted by %s", p.Repository.FullName, senderLink)
|
||||
}
|
||||
@@ -379,11 +396,6 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla
|
||||
Text: text,
|
||||
Username: slack.Username,
|
||||
IconURL: slack.IconURL,
|
||||
Attachments: []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Title: title,
|
||||
Text: attachmentText,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ type AuthenticationForm struct {
|
||||
SearchPageSize int
|
||||
Filter string
|
||||
AdminFilter string
|
||||
AllowDeactivateAll bool
|
||||
IsActive bool
|
||||
IsSyncEnabled bool
|
||||
SMTPAuth string
|
||||
|
||||
@@ -47,6 +47,7 @@ type Source struct {
|
||||
Filter string // Query filter to validate entry
|
||||
AdminFilter string // Query filter to check if user is admin
|
||||
Enabled bool // if this source is disabled
|
||||
AllowDeactivateAll bool // Allow an empty search response to deactivate all users from this source
|
||||
}
|
||||
|
||||
// SearchResult : user data
|
||||
|
||||
@@ -499,9 +499,9 @@ func (f SubmitReviewForm) HasEmptyContent() bool {
|
||||
|
||||
// NewReleaseForm form for creating release
|
||||
type NewReleaseForm struct {
|
||||
TagName string `binding:"Required;GitRefName"`
|
||||
Target string `form:"tag_target" binding:"Required"`
|
||||
Title string `binding:"Required"`
|
||||
TagName string `binding:"Required;GitRefName;MaxSize(255)"`
|
||||
Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
|
||||
Title string `binding:"Required;MaxSize(255)"`
|
||||
Content string
|
||||
Draft string
|
||||
Prerelease bool
|
||||
@@ -515,7 +515,7 @@ func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) bin
|
||||
|
||||
// EditReleaseForm form for changing release
|
||||
type EditReleaseForm struct {
|
||||
Title string `form:"title" binding:"Required"`
|
||||
Title string `form:"title" binding:"Required;MaxSize(255)"`
|
||||
Content string `form:"content"`
|
||||
Draft string `form:"draft"`
|
||||
Prerelease bool `form:"prerelease"`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 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.
|
||||
|
||||
@@ -122,7 +123,7 @@ func (ctx *Context) RedirectToFirst(location ...string) {
|
||||
}
|
||||
|
||||
u, err := url.Parse(loc)
|
||||
if err != nil || (u.Scheme != "" && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
|
||||
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -91,12 +91,12 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
|
||||
// 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
|
||||
isAssigned, _ := models.IsUserAssignedToIssue(issue, user)
|
||||
return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() ||
|
||||
r.Permission.CanWrite(models.UnitTypeIssues) || issue.IsPoster(user.ID) || isAssigned)
|
||||
r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
|
||||
}
|
||||
|
||||
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
|
||||
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
|
||||
return r.Permission.CanWrite(models.UnitTypeIssues) && r.Repository.IsDependenciesEnabled()
|
||||
func (r *Repository) CanCreateIssueDependencies(user *models.User, isPull bool) bool {
|
||||
return r.Repository.IsDependenciesEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull)
|
||||
}
|
||||
|
||||
// GetCommitsCount returns cached commit count for current view
|
||||
|
||||
+43
-10
@@ -123,7 +123,7 @@ func Middleware(options ...Options) macaron.Handler {
|
||||
// OK we should proxy the response writer
|
||||
// We are still not necessarily going to compress...
|
||||
proxyWriter := &ProxyResponseWriter{
|
||||
ResponseWriter: ctx.Resp,
|
||||
internal: ctx.Resp,
|
||||
}
|
||||
defer proxyWriter.Close()
|
||||
|
||||
@@ -137,19 +137,52 @@ func Middleware(options ...Options) macaron.Handler {
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
ctx.Resp = proxyWriter.internal
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyResponseWriter is a wrapped macaron ResponseWriter that may compress its contents
|
||||
type ProxyResponseWriter struct {
|
||||
writer io.WriteCloser
|
||||
macaron.ResponseWriter
|
||||
stopped bool
|
||||
writer io.WriteCloser
|
||||
internal macaron.ResponseWriter
|
||||
stopped bool
|
||||
|
||||
code int
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// Header returns the header map
|
||||
func (proxy *ProxyResponseWriter) Header() http.Header {
|
||||
return proxy.internal.Header()
|
||||
}
|
||||
|
||||
// Status returns the status code of the response or 0 if the response has not been written.
|
||||
func (proxy *ProxyResponseWriter) Status() int {
|
||||
if proxy.code != 0 {
|
||||
return proxy.code
|
||||
}
|
||||
return proxy.internal.Status()
|
||||
}
|
||||
|
||||
// Written returns whether or not the ResponseWriter has been written.
|
||||
func (proxy *ProxyResponseWriter) Written() bool {
|
||||
if proxy.code != 0 {
|
||||
return true
|
||||
}
|
||||
return proxy.internal.Written()
|
||||
}
|
||||
|
||||
// Size returns the size of the response body.
|
||||
func (proxy *ProxyResponseWriter) Size() int {
|
||||
return proxy.internal.Size()
|
||||
}
|
||||
|
||||
// Before allows for a function to be called before the ResponseWriter has been written to. This is
|
||||
// useful for setting headers or any other operations that must happen before a response has been written.
|
||||
func (proxy *ProxyResponseWriter) Before(before macaron.BeforeFunc) {
|
||||
proxy.internal.Before(before)
|
||||
}
|
||||
|
||||
// Write appends data to the proxied gzip writer.
|
||||
func (proxy *ProxyResponseWriter) Write(b []byte) (int, error) {
|
||||
// if writer is initialized, use the writer
|
||||
@@ -210,7 +243,7 @@ func (proxy *ProxyResponseWriter) startGzip() error {
|
||||
|
||||
// Write the header to gzip response.
|
||||
if proxy.code != 0 {
|
||||
proxy.ResponseWriter.WriteHeader(proxy.code)
|
||||
proxy.internal.WriteHeader(proxy.code)
|
||||
// Ensure that no other WriteHeader's happen
|
||||
proxy.code = 0
|
||||
}
|
||||
@@ -220,7 +253,7 @@ func (proxy *ProxyResponseWriter) startGzip() error {
|
||||
// write the gzip header even if nothing was ever written.
|
||||
if len(proxy.buf) > 0 {
|
||||
// Initialize the GZIP response.
|
||||
proxy.writer = writerPool.Get(proxy.ResponseWriter)
|
||||
proxy.writer = writerPool.Get(proxy.internal)
|
||||
|
||||
return proxy.writeBuf()
|
||||
}
|
||||
@@ -229,11 +262,11 @@ func (proxy *ProxyResponseWriter) startGzip() error {
|
||||
|
||||
func (proxy *ProxyResponseWriter) startPlain() error {
|
||||
if proxy.code != 0 {
|
||||
proxy.ResponseWriter.WriteHeader(proxy.code)
|
||||
proxy.internal.WriteHeader(proxy.code)
|
||||
proxy.code = 0
|
||||
}
|
||||
proxy.stopped = true
|
||||
proxy.writer = noopCloser{proxy.ResponseWriter}
|
||||
proxy.writer = noopCloser{proxy.internal}
|
||||
return proxy.writeBuf()
|
||||
}
|
||||
|
||||
@@ -295,13 +328,13 @@ func (proxy *ProxyResponseWriter) Flush() {
|
||||
gw.Flush()
|
||||
}
|
||||
|
||||
proxy.ResponseWriter.Flush()
|
||||
proxy.internal.Flush()
|
||||
}
|
||||
|
||||
// Hijack implements http.Hijacker. If the underlying ResponseWriter is a
|
||||
// Hijacker, its Hijack method is returned. Otherwise an error is returned.
|
||||
func (proxy *ProxyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker, ok := proxy.ResponseWriter.(http.Hijacker)
|
||||
hijacker, ok := proxy.internal.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
|
||||
}
|
||||
|
||||
@@ -138,26 +138,31 @@ func populateIssueIndexer() {
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
is, err := models.Issues(&models.IssuesOptions{
|
||||
RepoIDs: []int64{repo.ID},
|
||||
IsClosed: util.OptionalBoolNone,
|
||||
IsPull: util.OptionalBoolNone,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Issues: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = models.IssueList(is).LoadDiscussComments(); err != nil {
|
||||
log.Error("LoadComments: %v", err)
|
||||
continue
|
||||
}
|
||||
for _, issue := range is {
|
||||
UpdateIssueIndexer(issue)
|
||||
}
|
||||
UpdateRepoIndexer(repo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRepoIndexer add/update all issues of the repositories
|
||||
func UpdateRepoIndexer(repo *models.Repository) {
|
||||
is, err := models.Issues(&models.IssuesOptions{
|
||||
RepoIDs: []int64{repo.ID},
|
||||
IsClosed: util.OptionalBoolNone,
|
||||
IsPull: util.OptionalBoolNone,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Issues: %v", err)
|
||||
return
|
||||
}
|
||||
if err = models.IssueList(is).LoadDiscussComments(); err != nil {
|
||||
log.Error("LoadComments: %v", err)
|
||||
return
|
||||
}
|
||||
for _, issue := range is {
|
||||
UpdateIssueIndexer(issue)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateIssueIndexer add/update an issue to the issue indexer
|
||||
func UpdateIssueIndexer(issue *models.Issue) {
|
||||
var comments []string
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/lunny/levelqueue"
|
||||
"gitea.com/lunny/levelqueue"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -12,6 +12,7 @@ type Uploader interface {
|
||||
CreateTopics(topic ...string) error
|
||||
CreateMilestones(milestones ...*Milestone) error
|
||||
CreateReleases(releases ...*Release) error
|
||||
SyncTags() error
|
||||
CreateLabels(labels ...*Label) error
|
||||
CreateIssues(issues ...*Issue) error
|
||||
CreateComments(comments ...*Comment) error
|
||||
|
||||
@@ -288,11 +288,12 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
|
||||
|
||||
rels = append(rels, &rel)
|
||||
}
|
||||
if err := models.InsertReleases(rels...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sync tags to releases in database
|
||||
return models.InsertReleases(rels...)
|
||||
}
|
||||
|
||||
// SyncTags syncs releases with tags in the database
|
||||
func (g *GiteaLocalUploader) SyncTags() error {
|
||||
return models.SyncReleasesWithTags(g.repo, g.gitRepo)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOpt
|
||||
opts.PullRequests = false
|
||||
opts.GitServiceType = structs.PlainGitService
|
||||
downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
|
||||
log.Trace("Will migrate from git: %s", opts.CloneAddr)
|
||||
log.Trace("Will migrate from git: %s", opts.OriginalURL)
|
||||
} else if opts.GitServiceType == structs.NotMigrated {
|
||||
opts.GitServiceType = theFactory.GitServiceType()
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOpt
|
||||
log.Error("rollback failed: %v", err1)
|
||||
}
|
||||
|
||||
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.CloneAddr, err)); err2 != nil {
|
||||
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
|
||||
log.Error("create respotiry notice failed: ", err2)
|
||||
}
|
||||
return nil, err
|
||||
@@ -165,6 +165,11 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
|
||||
}
|
||||
releases = releases[relBatchSize:]
|
||||
}
|
||||
|
||||
// Once all releases (if any) are inserted, sync any remaining non-release tags
|
||||
if err := uploader.SyncTags(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var commentBatchSize = uploader.MaxBatchInsertSize("comment")
|
||||
|
||||
@@ -107,3 +107,8 @@ func (r *indexerNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod
|
||||
func (r *indexerNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
|
||||
issue_indexer.UpdateIssueIndexer(issue)
|
||||
}
|
||||
|
||||
func (r *indexerNotifier) NotifyMigrateRepository(doer *models.User, u *models.User, repo *models.Repository) {
|
||||
issue_indexer.UpdateRepoIndexer(repo)
|
||||
models.UpdateRepoIndexer(repo)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type Response struct {
|
||||
ErrorCodes []string `json:"error-codes"`
|
||||
}
|
||||
|
||||
const apiURL = "/api/siteverify"
|
||||
const apiURL = "api/siteverify"
|
||||
|
||||
// Verify calls Google Recaptcha API to verify token
|
||||
func Verify(response string) (bool, error) {
|
||||
|
||||
@@ -69,7 +69,7 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo
|
||||
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Committer, opts.Author, doer)
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(repo)
|
||||
if err != nil {
|
||||
|
||||
@@ -80,7 +80,7 @@ func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.Fi
|
||||
}
|
||||
|
||||
// GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
|
||||
func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (committerUser, authorUser *models.User) {
|
||||
func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (authorUser, committerUser *models.User) {
|
||||
// Committer and author are optional. If they are not the doer (not same email address)
|
||||
// then we use bogus User objects for them to store their FullName and Email.
|
||||
// If only one of the two are provided, we set both of them to it.
|
||||
|
||||
@@ -79,11 +79,11 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
|
||||
for e := rangeStart; e < rangeEnd; e++ {
|
||||
i := e - rangeStart
|
||||
|
||||
tree.Entries[e].Path = entries[e].Name()
|
||||
tree.Entries[e].Mode = fmt.Sprintf("%06o", entries[e].Mode())
|
||||
tree.Entries[e].Type = entries[e].Type()
|
||||
tree.Entries[e].Size = entries[e].Size()
|
||||
tree.Entries[e].SHA = entries[e].ID.String()
|
||||
tree.Entries[i].Path = entries[e].Name()
|
||||
tree.Entries[i].Mode = fmt.Sprintf("%06o", entries[e].Mode())
|
||||
tree.Entries[i].Type = entries[e].Type()
|
||||
tree.Entries[i].Size = entries[e].Size()
|
||||
tree.Entries[i].SHA = entries[e].ID.String()
|
||||
|
||||
if entries[e].IsDir() {
|
||||
copy(treeURL[copyPos:], entries[e].ID.String())
|
||||
|
||||
@@ -167,7 +167,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
|
||||
|
||||
message := strings.TrimSpace(opts.Message)
|
||||
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Committer, opts.Author, doer)
|
||||
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
|
||||
|
||||
t, err := NewTemporaryUploadRepository(repo)
|
||||
if err != nil {
|
||||
@@ -453,10 +453,15 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp
|
||||
}
|
||||
}
|
||||
} else if !isDelRef {
|
||||
branchName := opts.RefFullName[len(git.BranchPrefix):]
|
||||
if err = models.RemoveDeletedBranch(repo.ID, branchName); err != nil {
|
||||
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branchName, err)
|
||||
}
|
||||
|
||||
// If is branch reference
|
||||
|
||||
// Clear cache for branch commit count
|
||||
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefFullName[len(git.BranchPrefix):], true))
|
||||
cache.Remove(repo.GetCommitsCountCacheKey(branchName, true))
|
||||
|
||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||
if err != nil {
|
||||
|
||||
@@ -128,7 +128,11 @@ func generateLogConfig(sec *ini.Section, name string, defaults defaultLogOptions
|
||||
logConfig["username"] = sec.Key("USER").MustString("example@example.com")
|
||||
logConfig["password"] = sec.Key("PASSWD").MustString("******")
|
||||
logConfig["host"] = sec.Key("HOST").MustString("127.0.0.1:25")
|
||||
logConfig["sendTos"] = sec.Key("RECEIVERS").MustString("[]")
|
||||
sendTos := strings.Split(sec.Key("RECEIVERS").MustString(""), ",")
|
||||
for i, address := range sendTos {
|
||||
sendTos[i] = strings.TrimSpace(address)
|
||||
}
|
||||
logConfig["sendTos"] = sendTos
|
||||
logConfig["subject"] = sec.Key("SUBJECT").MustString("Diagnostic message from Gitea")
|
||||
}
|
||||
|
||||
|
||||
@@ -97,8 +97,6 @@ func runMigrateTask(t *models.Task) (err error) {
|
||||
opts.MigrateToRepoID = t.RepoID
|
||||
repo, err := migrations.MigrateRepository(t.Doer, t.Owner.Name, *opts)
|
||||
if err == nil {
|
||||
notification.NotifyMigrateRepository(t.Doer, t.Owner, repo)
|
||||
|
||||
log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -239,6 +239,14 @@ func NewFuncMap() []template.FuncMap {
|
||||
"MirrorFullAddress": mirror_service.AddressNoCredentials,
|
||||
"MirrorUserName": mirror_service.Username,
|
||||
"MirrorPassword": mirror_service.Password,
|
||||
"contain": func(s []int64, id int64) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1700,6 +1700,7 @@ auths.attribute_surname = Surname Attribute
|
||||
auths.attribute_mail = Email Attribute
|
||||
auths.attribute_ssh_public_key = Public SSH Key Attribute
|
||||
auths.attributes_in_bind = Fetch Attributes in Bind DN Context
|
||||
auths.allow_deactivate_all = Allow an empty search result to deactivate all users
|
||||
auths.use_paged_search = Use Paged Search
|
||||
auths.search_page_size = Page Size
|
||||
auths.filter = User Filter
|
||||
|
||||
+11
-2
@@ -1,6 +1,6 @@
|
||||
/* globals wipPrefixes, issuesTribute, emojiTribute */
|
||||
/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */
|
||||
/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */
|
||||
/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, submitReply, cancelCodeComment, onOAuthLoginClick */
|
||||
'use strict';
|
||||
|
||||
function htmlEncode(text) {
|
||||
@@ -3134,10 +3134,11 @@ function deleteDependencyModal(id, type) {
|
||||
|
||||
function initIssueList() {
|
||||
const repolink = $('#repolink').val();
|
||||
const tp = $('#type').val();
|
||||
$('#new-dependency-drop-list')
|
||||
.dropdown({
|
||||
apiSettings: {
|
||||
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}',
|
||||
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}&type='+tp,
|
||||
onResponse: function(response) {
|
||||
const filteredResponse = {'success': true, 'results': []};
|
||||
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
|
||||
@@ -3170,6 +3171,14 @@ function cancelCodeComment(btn) {
|
||||
form.closest('.comment-code-cloud').remove()
|
||||
}
|
||||
}
|
||||
|
||||
function submitReply(btn) {
|
||||
const form = $(btn).closest('form');
|
||||
if (form.length > 0 && form.hasClass('comment-form')) {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
function onOAuthLoginClick() {
|
||||
const oauthLoader = $('#oauth2-login-loader');
|
||||
const oauthNav = $('#oauth2-login-navigator');
|
||||
|
||||
+19
-11
@@ -6,6 +6,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/macaron/session"
|
||||
"github.com/unknwon/com"
|
||||
)
|
||||
|
||||
@@ -207,7 +209,7 @@ func SendTestMail(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/config")
|
||||
}
|
||||
|
||||
func shadownPasswordKV(cfgItem, splitter string) string {
|
||||
func shadowPasswordKV(cfgItem, splitter string) string {
|
||||
fields := strings.Split(cfgItem, splitter)
|
||||
for i := 0; i < len(fields); i++ {
|
||||
if strings.HasPrefix(fields[i], "password=") {
|
||||
@@ -218,10 +220,10 @@ func shadownPasswordKV(cfgItem, splitter string) string {
|
||||
return strings.Join(fields, splitter)
|
||||
}
|
||||
|
||||
func shadownURL(provider, cfgItem string) string {
|
||||
func shadowURL(provider, cfgItem string) string {
|
||||
u, err := url.Parse(cfgItem)
|
||||
if err != nil {
|
||||
log.Error("shodowPassword %v failed: %v", provider, err)
|
||||
log.Error("Shadowing Password for %v failed: %v", provider, err)
|
||||
return cfgItem
|
||||
}
|
||||
if u.User != nil {
|
||||
@@ -239,7 +241,7 @@ func shadownURL(provider, cfgItem string) string {
|
||||
func shadowPassword(provider, cfgItem string) string {
|
||||
switch provider {
|
||||
case "redis":
|
||||
return shadownPasswordKV(cfgItem, ",")
|
||||
return shadowPasswordKV(cfgItem, ",")
|
||||
case "mysql":
|
||||
//root:@tcp(localhost:3306)/macaron?charset=utf8
|
||||
atIdx := strings.Index(cfgItem, "@")
|
||||
@@ -253,15 +255,15 @@ func shadowPassword(provider, cfgItem string) string {
|
||||
case "postgres":
|
||||
// user=jiahuachen dbname=macaron port=5432 sslmode=disable
|
||||
if !strings.HasPrefix(cfgItem, "postgres://") {
|
||||
return shadownPasswordKV(cfgItem, " ")
|
||||
return shadowPasswordKV(cfgItem, " ")
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case "couchbase":
|
||||
return shadowURL(provider, cfgItem)
|
||||
// postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full
|
||||
// Notice: use shadwonURL
|
||||
// Notice: use shadowURL
|
||||
}
|
||||
|
||||
// "couchbase"
|
||||
return shadownURL(provider, cfgItem)
|
||||
return cfgItem
|
||||
}
|
||||
|
||||
// Config show admin config page
|
||||
@@ -306,8 +308,14 @@ func Config(ctx *context.Context) {
|
||||
ctx.Data["CacheItemTTL"] = setting.CacheService.TTL
|
||||
|
||||
sessionCfg := setting.SessionConfig
|
||||
if sessionCfg.Provider == "VirtualSession" {
|
||||
var realSession session.Options
|
||||
if err := json.Unmarshal([]byte(sessionCfg.ProviderConfig), &realSession); err != nil {
|
||||
log.Error("Unable to unmarshall session config for virtualed provider config: %s\nError: %v", sessionCfg.ProviderConfig, err)
|
||||
}
|
||||
sessionCfg = realSession
|
||||
}
|
||||
sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig)
|
||||
|
||||
ctx.Data["SessionConfig"] = sessionCfg
|
||||
|
||||
ctx.Data["DisableGravatar"] = setting.DisableGravatar
|
||||
|
||||
@@ -115,6 +115,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig {
|
||||
SearchPageSize: pageSize,
|
||||
Filter: form.Filter,
|
||||
AdminFilter: form.AdminFilter,
|
||||
AllowDeactivateAll: form.AllowDeactivateAll,
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
+14
-3
@@ -94,8 +94,14 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
|
||||
u.LoginName = form.LoginName
|
||||
}
|
||||
}
|
||||
if u.LoginType == models.LoginPlain {
|
||||
if u.LoginType == models.LoginNoType || u.LoginType == models.LoginPlain {
|
||||
if len(form.Password) < setting.MinPasswordLength {
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserNew, &form)
|
||||
return
|
||||
}
|
||||
if !password.IsComplexEnough(form.Password) {
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form)
|
||||
return
|
||||
}
|
||||
@@ -203,14 +209,19 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
|
||||
|
||||
if len(form.Password) > 0 {
|
||||
var err error
|
||||
if u.Salt, err = models.GetUserSalt(); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
if len(form.Password) < setting.MinPasswordLength {
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form)
|
||||
return
|
||||
}
|
||||
if !password.IsComplexEnough(form.Password) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserEdit, &form)
|
||||
return
|
||||
}
|
||||
if u.Salt, err = models.GetUserSalt(); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
u.HashPassword(form.Password)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,10 @@ func ListIssues(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: search string
|
||||
// type: string
|
||||
// - name: type
|
||||
// in: query
|
||||
// description: filter by type (issues / pulls) if set
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/IssueList"
|
||||
@@ -91,6 +95,16 @@ func ListIssues(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
var isPull util.OptionalBool
|
||||
switch ctx.Query("type") {
|
||||
case "pulls":
|
||||
isPull = util.OptionalBoolTrue
|
||||
case "issues":
|
||||
isPull = util.OptionalBoolFalse
|
||||
default:
|
||||
isPull = util.OptionalBoolNone
|
||||
}
|
||||
|
||||
// Only fetch the issues if we either don't have a keyword or the search returned issues
|
||||
// This would otherwise return all issues if no issues were found by the search.
|
||||
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
|
||||
@@ -101,6 +115,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
IsClosed: isClosed,
|
||||
IssueIDs: issueIDs,
|
||||
LabelIDs: labelIDs,
|
||||
IsPull: isPull,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -292,6 +307,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
return
|
||||
}
|
||||
issue.Repo = ctx.Repo.Repository
|
||||
canWrite := ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
|
||||
err = issue.LoadAttributes()
|
||||
if err != nil {
|
||||
@@ -299,7 +315,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
return
|
||||
}
|
||||
|
||||
if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
if !issue.IsPoster(ctx.User.ID) && !canWrite {
|
||||
ctx.Status(403)
|
||||
return
|
||||
}
|
||||
@@ -312,7 +328,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
}
|
||||
|
||||
// Update the deadline
|
||||
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
if form.Deadline != nil && canWrite {
|
||||
deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix())
|
||||
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
|
||||
ctx.Error(500, "UpdateIssueDeadline", err)
|
||||
@@ -329,7 +345,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
// Pass one or more user logins to replace the set of assignees on this Issue.
|
||||
// Send an empty array ([]) to clear all assignees from the Issue.
|
||||
|
||||
if ctx.Repo.CanWrite(models.UnitTypeIssues) && (form.Assignees != nil || form.Assignee != nil) {
|
||||
if canWrite && (form.Assignees != nil || form.Assignee != nil) {
|
||||
oneAssignee := ""
|
||||
if form.Assignee != nil {
|
||||
oneAssignee = *form.Assignee
|
||||
@@ -342,7 +358,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Repo.CanWrite(models.UnitTypeIssues) && form.Milestone != nil &&
|
||||
if canWrite && form.Milestone != nil &&
|
||||
issue.MilestoneID != *form.Milestone {
|
||||
oldMilestoneID := issue.MilestoneID
|
||||
issue.MilestoneID = *form.Milestone
|
||||
@@ -352,8 +368,8 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = models.UpdateIssue(issue); err != nil {
|
||||
ctx.Error(500, "UpdateIssue", err)
|
||||
if err = models.UpdateIssueByAPI(issue); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
if form.State != nil {
|
||||
@@ -372,7 +388,11 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
|
||||
// Refetch from database to assign some automatic values
|
||||
issue, err = models.GetIssueByID(issue.ID)
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetIssueByID", err)
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
if err = issue.LoadMilestone(); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(201, issue.APIFormat())
|
||||
@@ -426,7 +446,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(403)
|
||||
return
|
||||
}
|
||||
@@ -493,7 +513,7 @@ func StartIssueStopwatch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(403)
|
||||
return
|
||||
}
|
||||
@@ -562,7 +582,7 @@ func StopIssueStopwatch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||
ctx.Status(403)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti
|
||||
return
|
||||
}
|
||||
|
||||
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
|
||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
|
||||
ctx.Error(403, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -189,7 +189,12 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) {
|
||||
milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
|
||||
}
|
||||
|
||||
if err := models.UpdateMilestone(milestone); err != nil {
|
||||
var oldIsClosed = milestone.IsClosed
|
||||
if form.State != nil {
|
||||
milestone.IsClosed = *form.State == string(api.StateClosed)
|
||||
}
|
||||
|
||||
if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil {
|
||||
ctx.ServerError("UpdateMilestone", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -420,8 +420,8 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = models.UpdateIssue(issue); err != nil {
|
||||
ctx.Error(500, "UpdateIssue", err)
|
||||
if err = models.UpdateIssueByAPI(issue); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
if form.State != nil {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -431,15 +433,53 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
|
||||
opts.Releases = false
|
||||
}
|
||||
|
||||
repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts)
|
||||
if err == nil {
|
||||
notification.NotifyCreateRepository(ctx.User, ctxUser, repo)
|
||||
|
||||
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
|
||||
ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
|
||||
repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
|
||||
Name: opts.RepoName,
|
||||
Description: opts.Description,
|
||||
OriginalURL: form.CloneAddr,
|
||||
IsPrivate: opts.Private,
|
||||
IsMirror: opts.Mirror,
|
||||
Status: models.RepositoryBeingMigrated,
|
||||
})
|
||||
if err != nil {
|
||||
handleMigrateError(ctx, ctxUser, remoteAddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
opts.MigrateToRepoID = repo.ID
|
||||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
|
||||
err = errors.New(buf.String())
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
repo.Status = models.RepositoryReady
|
||||
if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
|
||||
notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
|
||||
log.Error("DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err = migrations.MigrateRepository(ctx.User, ctxUser.Name, opts); err != nil {
|
||||
handleMigrateError(ctx, ctxUser, remoteAddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
|
||||
ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
|
||||
}
|
||||
|
||||
func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
|
||||
switch {
|
||||
case models.IsErrRepoAlreadyExist(err):
|
||||
ctx.Error(409, "", "The repository with the same name already exists.")
|
||||
@@ -448,7 +488,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
|
||||
case migrations.IsTwoFactorAuthError(err):
|
||||
ctx.Error(422, "", "Remote visit required two factors authentication.")
|
||||
case models.IsErrReachLimitOfRepo(err):
|
||||
ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", ctxUser.MaxCreationLimit()))
|
||||
ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
|
||||
case models.IsErrNameReserved(err):
|
||||
ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
|
||||
case models.IsErrNamePatternNotAllowed(err):
|
||||
|
||||
@@ -41,8 +41,8 @@ func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) {
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateStatusOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/StatusList"
|
||||
// "201":
|
||||
// "$ref": "#/responses/Status"
|
||||
sha := ctx.Params("sha")
|
||||
if len(sha) == 0 {
|
||||
ctx.Error(400, "sha not given", nil)
|
||||
|
||||
@@ -141,6 +141,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
||||
Keyword: keyword,
|
||||
OwnerID: opts.OwnerID,
|
||||
AllPublic: true,
|
||||
AllLimited: true,
|
||||
TopicOnly: topicOnly,
|
||||
IncludeDescription: setting.UI.SearchRepoDescription,
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/repofiles"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,6 +33,7 @@ type Branch struct {
|
||||
CommitsAhead int
|
||||
CommitsBehind int
|
||||
LatestPullRequest *models.PullRequest
|
||||
MergeMovedOn bool
|
||||
}
|
||||
|
||||
// Branches render repository branch page
|
||||
@@ -168,6 +170,12 @@ func loadBranches(ctx *context.Context) []*Branch {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoIDToRepo := map[int64]*models.Repository{}
|
||||
repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository
|
||||
|
||||
repoIDToGitRepo := map[int64]*git.Repository{}
|
||||
repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo
|
||||
|
||||
branches := make([]*Branch, len(rawBranches))
|
||||
for i := range rawBranches {
|
||||
commit, err := rawBranches[i].GetCommit()
|
||||
@@ -196,11 +204,46 @@ func loadBranches(ctx *context.Context) []*Branch {
|
||||
ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
|
||||
return nil
|
||||
}
|
||||
headCommit := commit.ID.String()
|
||||
|
||||
mergeMovedOn := false
|
||||
if pr != nil {
|
||||
pr.HeadRepo = ctx.Repo.Repository
|
||||
if err := pr.LoadIssue(); err != nil {
|
||||
ctx.ServerError("pr.LoadIssue", err)
|
||||
return nil
|
||||
}
|
||||
if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
|
||||
pr.BaseRepo = repo
|
||||
} else if err := pr.LoadBaseRepo(); err != nil {
|
||||
ctx.ServerError("pr.LoadBaseRepo", err)
|
||||
return nil
|
||||
} else {
|
||||
repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
|
||||
}
|
||||
pr.Issue.Repo = pr.BaseRepo
|
||||
|
||||
if pr.HasMerged {
|
||||
baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
|
||||
if !ok {
|
||||
baseGitRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath())
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return nil
|
||||
}
|
||||
defer baseGitRepo.Close()
|
||||
repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
|
||||
}
|
||||
pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
|
||||
if err != nil && err != plumbing.ErrReferenceNotFound {
|
||||
ctx.ServerError("GetBranchCommitID", err)
|
||||
return nil
|
||||
}
|
||||
if err == nil && headCommit != pullCommit {
|
||||
// the head has moved on from the merge - we shouldn't delete
|
||||
mergeMovedOn = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
branches[i] = &Branch{
|
||||
@@ -210,6 +253,7 @@ func loadBranches(ctx *context.Context) []*Branch {
|
||||
CommitsAhead: divergence.Ahead,
|
||||
CommitsBehind: divergence.Behind,
|
||||
LatestPullRequest: pr,
|
||||
MergeMovedOn: mergeMovedOn,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
-25
@@ -152,12 +152,12 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
}
|
||||
|
||||
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
|
||||
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
@@ -168,42 +168,40 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *
|
||||
baseRepo,
|
||||
permBase)
|
||||
}
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound("ParseCompareInfo", nil)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
// user should have permission to read headrepo's codes
|
||||
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
if !permHead.CanRead(models.UnitTypeCode) {
|
||||
if log.IsTrace() {
|
||||
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
|
||||
ctx.User,
|
||||
headRepo,
|
||||
permHead)
|
||||
if !isSameRepo {
|
||||
// user should have permission to read headrepo's codes
|
||||
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserRepoPermission", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
if !permHead.CanRead(models.UnitTypeCode) {
|
||||
if log.IsTrace() {
|
||||
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
|
||||
ctx.User,
|
||||
headRepo,
|
||||
permHead)
|
||||
}
|
||||
ctx.NotFound("ParseCompareInfo", nil)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound("ParseCompareInfo", nil)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
// Check if head branch is valid.
|
||||
headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch)
|
||||
headIsCommit := headGitRepo.IsCommitExist(headBranch)
|
||||
headIsBranch := headGitRepo.IsBranchExist(headBranch)
|
||||
headIsTag := headGitRepo.IsTagExist(headBranch)
|
||||
if !headIsCommit && !headIsBranch && !headIsTag {
|
||||
// Check if headBranch is short sha commit hash
|
||||
if headCommit, _ := ctx.Repo.GitRepo.GetCommit(headBranch); headCommit != nil {
|
||||
if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil {
|
||||
headBranch = headCommit.ID.String()
|
||||
ctx.Data["HeadBranch"] = headBranch
|
||||
headIsCommit = true
|
||||
} else {
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound("IsRefExist", nil)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
@@ -224,14 +222,12 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *
|
||||
baseRepo,
|
||||
permBase)
|
||||
}
|
||||
headGitRepo.Close()
|
||||
ctx.NotFound("ParseCompareInfo", nil)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranch, headBranch)
|
||||
if err != nil {
|
||||
headGitRepo.Close()
|
||||
ctx.ServerError("GetCompareInfo", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
@@ -346,6 +342,30 @@ func PrepareCompareDiff(
|
||||
return false
|
||||
}
|
||||
|
||||
// parseBaseRepoInfo parse base repository if current repo is forked.
|
||||
// The "base" here means the repository where current repo forks from,
|
||||
// not the repository fetch from current URL.
|
||||
func parseBaseRepoInfo(ctx *context.Context, repo *models.Repository) error {
|
||||
if !repo.IsFork {
|
||||
return nil
|
||||
}
|
||||
if err := repo.GetBaseRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := repo.BaseRepo.GetOwnerName(); err != nil {
|
||||
return err
|
||||
}
|
||||
baseGitRepo, err := git.OpenRepository(models.RepoPath(repo.BaseRepo.OwnerName, repo.BaseRepo.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Data["BaseRepoBranches"], err = baseGitRepo.GetBranches()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompareDiff show different from one commit to another commit
|
||||
func CompareDiff(ctx *context.Context) {
|
||||
headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx)
|
||||
@@ -353,6 +373,11 @@ func CompareDiff(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
defer headGitRepo.Close()
|
||||
var err error
|
||||
if err = parseBaseRepoInfo(ctx, headRepo); err != nil {
|
||||
ctx.ServerError("parseBaseRepoInfo", err)
|
||||
return
|
||||
}
|
||||
|
||||
nothingToCompare := PrepareCompareDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch)
|
||||
if ctx.Written() {
|
||||
@@ -382,7 +407,7 @@ func CompareDiff(ctx *context.Context) {
|
||||
|
||||
if !nothingToCompare {
|
||||
// Setup information for new form.
|
||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
|
||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository, true)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -391,6 +416,11 @@ func CompareDiff(ctx *context.Context) {
|
||||
beforeCommitID := ctx.Data["BeforeCommitID"].(string)
|
||||
afterCommitID := ctx.Data["AfterCommitID"].(string)
|
||||
|
||||
if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil {
|
||||
ctx.ServerError("GetAssignees", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID)
|
||||
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
@@ -33,7 +34,12 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error {
|
||||
name = strings.Replace(name, ",", " ", -1)
|
||||
|
||||
if base.IsTextFile(buf) || ctx.QueryBool("render") {
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
cs, err := charset.DetectEncoding(buf)
|
||||
if err != nil {
|
||||
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err)
|
||||
cs = "utf-8"
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs))
|
||||
} else if base.IsImageFile(buf) || base.IsPDFFile(buf) {
|
||||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name))
|
||||
} else {
|
||||
|
||||
+22
-11
@@ -67,7 +67,7 @@ func MustAllowUserComment(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
|
||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
|
||||
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
|
||||
ctx.Redirect(issue.HTMLURL())
|
||||
return
|
||||
@@ -261,7 +261,8 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
|
||||
}
|
||||
|
||||
ctx.Data["IssueStats"] = issueStats
|
||||
ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
|
||||
ctx.Data["SelLabelIDs"] = labelIDs
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
ctx.Data["ViewType"] = viewType
|
||||
ctx.Data["SortType"] = sortType
|
||||
ctx.Data["MilestoneID"] = milestoneID
|
||||
@@ -345,8 +346,8 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos
|
||||
}
|
||||
|
||||
// RetrieveRepoMetas find all the meta information of a repository
|
||||
func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label {
|
||||
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull bool) []*models.Label {
|
||||
if !ctx.Repo.CanWriteIssuesOrPulls(isPull) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -370,7 +371,7 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
|
||||
ctx.Data["Branches"] = brs
|
||||
|
||||
// Contains true if the user can create issue dependencies
|
||||
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
|
||||
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, isPull)
|
||||
|
||||
return labels
|
||||
}
|
||||
@@ -440,7 +441,7 @@ func NewIssue(ctx *context.Context) {
|
||||
setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
|
||||
renderAttachmentSettings(ctx)
|
||||
|
||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
|
||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -455,7 +456,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b
|
||||
err error
|
||||
)
|
||||
|
||||
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
|
||||
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull)
|
||||
if ctx.Written() {
|
||||
return nil, nil, 0
|
||||
}
|
||||
@@ -775,8 +776,16 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if issue.IsPull && !ctx.Repo.CanRead(models.UnitTypeIssues) {
|
||||
ctx.Data["IssueType"] = "pulls"
|
||||
} else if !issue.IsPull && !ctx.Repo.CanRead(models.UnitTypePullRequests) {
|
||||
ctx.Data["IssueType"] = "issues"
|
||||
} else {
|
||||
ctx.Data["IssueType"] = "all"
|
||||
}
|
||||
|
||||
// Check if the user can use the dependencies
|
||||
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
|
||||
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull)
|
||||
|
||||
// Render comments and and fetch participants.
|
||||
participants[0] = issue.Poster
|
||||
@@ -930,7 +939,10 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.Data["IsBlockedByApprovals"] = pull.ProtectedBranch.RequiredApprovals > 0 && cnt < pull.ProtectedBranch.RequiredApprovals
|
||||
ctx.Data["GrantedApprovals"] = cnt
|
||||
}
|
||||
ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
|
||||
ctx.Data["IsPullBranchDeletable"] = canDelete &&
|
||||
pull.HeadRepo != nil &&
|
||||
git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) &&
|
||||
(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
|
||||
|
||||
ctx.Data["PullReviewersWithType"], err = models.GetReviewersByPullID(issue.ID)
|
||||
if err != nil {
|
||||
@@ -959,7 +971,6 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
|
||||
ctx.Data["IsIssueWriter"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin)
|
||||
ctx.Data["IsRepoIssuesWriter"] = ctx.IsSigned && (ctx.Repo.CanWrite(models.UnitTypeIssues) || ctx.User.IsAdmin)
|
||||
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
|
||||
ctx.HTML(200, tplIssueView)
|
||||
}
|
||||
@@ -1204,7 +1215,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
|
||||
ctx.Error(403)
|
||||
}
|
||||
|
||||
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
|
||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
|
||||
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
|
||||
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther)
|
||||
return
|
||||
|
||||
@@ -14,14 +14,6 @@ import (
|
||||
|
||||
// AddDependency adds new dependencies
|
||||
func AddDependency(ctx *context.Context) {
|
||||
// Check if the Repo is allowed to have dependencies
|
||||
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
|
||||
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
|
||||
return
|
||||
}
|
||||
|
||||
depID := ctx.QueryInt64("newDependency")
|
||||
|
||||
issueIndex := ctx.ParamsInt64("index")
|
||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
|
||||
if err != nil {
|
||||
@@ -29,6 +21,14 @@ func AddDependency(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the Repo is allowed to have dependencies
|
||||
if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) {
|
||||
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
|
||||
return
|
||||
}
|
||||
|
||||
depID := ctx.QueryInt64("newDependency")
|
||||
|
||||
// Redirect
|
||||
defer ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
|
||||
|
||||
@@ -68,14 +68,6 @@ func AddDependency(ctx *context.Context) {
|
||||
|
||||
// RemoveDependency removes the dependency
|
||||
func RemoveDependency(ctx *context.Context) {
|
||||
// Check if the Repo is allowed to have dependencies
|
||||
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
|
||||
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
|
||||
return
|
||||
}
|
||||
|
||||
depID := ctx.QueryInt64("removeDependencyID")
|
||||
|
||||
issueIndex := ctx.ParamsInt64("index")
|
||||
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
|
||||
if err != nil {
|
||||
@@ -83,8 +75,13 @@ func RemoveDependency(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect
|
||||
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
|
||||
// Check if the Repo is allowed to have dependencies
|
||||
if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) {
|
||||
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
|
||||
return
|
||||
}
|
||||
|
||||
depID := ctx.QueryInt64("removeDependencyID")
|
||||
|
||||
// Dependency Type
|
||||
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
|
||||
@@ -116,4 +113,7 @@ func RemoveDependency(ctx *context.Context) {
|
||||
ctx.ServerError("RemoveIssueDependency", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect
|
||||
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ import (
|
||||
|
||||
// SetEditorconfigIfExists set editor config as render variable
|
||||
func SetEditorconfigIfExists(ctx *context.Context) {
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Data["Editorconfig"] = nil
|
||||
return
|
||||
}
|
||||
|
||||
ec, err := ctx.Repo.GetEditorconfig()
|
||||
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
|
||||
@@ -192,7 +192,7 @@ func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
|
||||
m.Name = form.Title
|
||||
m.Content = form.Content
|
||||
m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
if err = models.UpdateMilestone(m); err != nil {
|
||||
if err = models.UpdateMilestone(m, m.IsClosed); err != nil {
|
||||
ctx.ServerError("UpdateMilestone", err)
|
||||
return
|
||||
}
|
||||
|
||||
+70
-35
@@ -21,6 +21,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
"code.gitea.io/gitea/modules/repofiles"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
@@ -314,25 +315,37 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
|
||||
repo := ctx.Repo.Repository
|
||||
pull := issue.PullRequest
|
||||
|
||||
var err error
|
||||
if err = pull.GetHeadRepo(); err != nil {
|
||||
if err := pull.GetHeadRepo(); err != nil {
|
||||
ctx.ServerError("GetHeadRepo", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pull.GetBaseRepo(); err != nil {
|
||||
ctx.ServerError("GetBaseRepo", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
setMergeTarget(ctx, pull)
|
||||
|
||||
if err = pull.LoadProtectedBranch(); err != nil {
|
||||
if err := pull.LoadProtectedBranch(); err != nil {
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
}
|
||||
ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
|
||||
|
||||
var headGitRepo *git.Repository
|
||||
baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath())
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return nil
|
||||
}
|
||||
defer baseGitRepo.Close()
|
||||
var headBranchExist bool
|
||||
var headBranchSha string
|
||||
// HeadRepo may be missing
|
||||
if pull.HeadRepo != nil {
|
||||
headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath())
|
||||
var err error
|
||||
|
||||
headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath())
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return nil
|
||||
@@ -342,46 +355,53 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
|
||||
headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
|
||||
|
||||
if headBranchExist {
|
||||
sha, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
|
||||
headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommitID", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
}
|
||||
if len(commitStatuses) > 0 {
|
||||
ctx.Data["LatestCommitStatuses"] = commitStatuses
|
||||
ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
|
||||
}
|
||||
|
||||
if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
|
||||
ctx.Data["is_context_required"] = func(context string) bool {
|
||||
for _, c := range pull.ProtectedBranch.StatusCheckContexts {
|
||||
if c == context {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pull.HeadRepo == nil || !headBranchExist {
|
||||
ctx.Data["IsPullRequestBroken"] = true
|
||||
ctx.Data["HeadTarget"] = "deleted"
|
||||
ctx.Data["NumCommits"] = 0
|
||||
ctx.Data["NumFiles"] = 0
|
||||
sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
|
||||
if err != nil {
|
||||
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name),
|
||||
pull.BaseBranch, pull.HeadBranch)
|
||||
commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLatestCommitStatus", err)
|
||||
return nil
|
||||
}
|
||||
if len(commitStatuses) > 0 {
|
||||
ctx.Data["LatestCommitStatuses"] = commitStatuses
|
||||
ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
|
||||
}
|
||||
|
||||
if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
|
||||
ctx.Data["is_context_required"] = func(context string) bool {
|
||||
for _, c := range pull.ProtectedBranch.StatusCheckContexts {
|
||||
if c == context {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
|
||||
}
|
||||
|
||||
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
|
||||
ctx.Data["HeadBranchCommitID"] = headBranchSha
|
||||
ctx.Data["PullHeadCommitID"] = sha
|
||||
|
||||
if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha {
|
||||
ctx.Data["IsPullRequestBroken"] = true
|
||||
ctx.Data["HeadTarget"] = "deleted"
|
||||
}
|
||||
|
||||
compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
|
||||
pull.BaseBranch, pull.GetGitRefName())
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
|
||||
ctx.Data["IsPullRequestBroken"] = true
|
||||
@@ -928,6 +948,21 @@ func CleanUpPullRequest(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := repofiles.PushUpdate(
|
||||
pr.HeadRepo,
|
||||
pr.HeadBranch,
|
||||
models.PushUpdateOptions{
|
||||
RefFullName: git.BranchPrefix + pr.HeadBranch,
|
||||
OldCommitID: branchCommitID,
|
||||
NewCommitID: git.EmptySHA,
|
||||
PusherID: ctx.User.ID,
|
||||
PusherName: ctx.User.Name,
|
||||
RepoUserName: pr.HeadRepo.Owner.Name,
|
||||
RepoName: pr.HeadRepo.Name,
|
||||
}); err != nil {
|
||||
log.Error("Update: %v", err)
|
||||
}
|
||||
|
||||
if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil {
|
||||
// Do not fail here as branch has already been deleted
|
||||
log.Error("DeleteBranch: %v", err)
|
||||
|
||||
@@ -117,9 +117,7 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
|
||||
|
||||
// can not approve/reject your own PR
|
||||
case models.ReviewTypeApprove, models.ReviewTypeReject:
|
||||
|
||||
if issue.Poster.ID == ctx.User.ID {
|
||||
|
||||
if issue.IsPoster(ctx.User.ID) {
|
||||
var translated string
|
||||
|
||||
if reviewType == models.ReviewTypeApprove {
|
||||
|
||||
@@ -291,6 +291,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
|
||||
}
|
||||
|
||||
var opts = migrations.MigrateOptions{
|
||||
OriginalURL: form.CloneAddr,
|
||||
CloneAddr: remoteAddr,
|
||||
RepoName: form.RepoName,
|
||||
Description: form.Description,
|
||||
|
||||
@@ -600,7 +600,7 @@ func AddTeamPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team")))
|
||||
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||
if len(name) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
}
|
||||
|
||||
+10
-18
@@ -65,27 +65,20 @@ type PageMeta struct {
|
||||
|
||||
// findEntryForFile finds the tree entry for a target filepath.
|
||||
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
|
||||
entries, err := commit.ListEntries()
|
||||
if err != nil {
|
||||
entry, err := commit.GetTreeEntryByPath(target)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
// The longest name should be checked first
|
||||
for _, entry := range entries {
|
||||
if entry.IsRegular() && entry.Name() == target {
|
||||
return entry, nil
|
||||
}
|
||||
if entry != nil {
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// Then the unescaped, shortest alternative
|
||||
var unescapedTarget string
|
||||
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsRegular() && entry.Name() == unescapedTarget {
|
||||
return entry, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
return commit.GetTreeEntryByPath(unescapedTarget)
|
||||
}
|
||||
|
||||
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
|
||||
@@ -122,10 +115,9 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
|
||||
// wikiContentsByName returns the contents of a wiki page, along with a boolean
|
||||
// indicating whether the page exists. Writes to ctx if an error occurs.
|
||||
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
|
||||
var entry *git.TreeEntry
|
||||
var err error
|
||||
pageFilename := models.WikiNameToFilename(wikiName)
|
||||
if entry, err = findEntryForFile(commit, pageFilename); err != nil {
|
||||
entry, err := findEntryForFile(commit, pageFilename)
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("findEntryForFile", err)
|
||||
return nil, nil, "", false
|
||||
} else if entry == nil {
|
||||
@@ -517,7 +509,7 @@ func WikiRaw(ctx *context.Context) {
|
||||
if commit != nil {
|
||||
// Try to find a file with that name
|
||||
entry, err = findEntryForFile(commit, providedPath)
|
||||
if err != nil {
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("findFile", err)
|
||||
return
|
||||
}
|
||||
@@ -530,7 +522,7 @@ func WikiRaw(ctx *context.Context) {
|
||||
|
||||
wikiPath := models.WikiNameToFilename(providedPath)
|
||||
entry, err = findEntryForFile(commit, wikiPath)
|
||||
if err != nil {
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("findFile", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ func TestDeleteWikiPagePost(t *testing.T) {
|
||||
func TestWikiRaw(t *testing.T) {
|
||||
for filepath, filetype := range map[string]string{
|
||||
"jpeg.jpg": "image/jpeg",
|
||||
"images/jpeg.jpg": "image/jpeg",
|
||||
"Page With Spaced Name": "text/plain; charset=utf-8",
|
||||
"Page-With-Spaced-Name": "text/plain; charset=utf-8",
|
||||
"Page With Spaced Name.md": "text/plain; charset=utf-8",
|
||||
|
||||
@@ -457,7 +457,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
|
||||
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
|
||||
m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
|
||||
m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
|
||||
m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
|
||||
})
|
||||
|
||||
m.Group("/auths", func() {
|
||||
@@ -532,18 +532,12 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases)
|
||||
reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki)
|
||||
reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues)
|
||||
reqRepoIssueWriter := context.RequireRepoWriter(models.UnitTypeIssues)
|
||||
reqRepoPullsWriter := context.RequireRepoWriter(models.UnitTypePullRequests)
|
||||
reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests)
|
||||
reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests)
|
||||
reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests)
|
||||
|
||||
reqRepoIssueWriter := func(ctx *context.Context) {
|
||||
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
|
||||
ctx.Error(403)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ***** START: Organization *****
|
||||
m.Group("/org", func() {
|
||||
m.Group("", func() {
|
||||
@@ -590,6 +584,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
|
||||
m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
|
||||
m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
|
||||
m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
|
||||
m.Get("/:id", repo.WebHooksEdit)
|
||||
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
|
||||
@@ -597,6 +592,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
|
||||
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
|
||||
m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
|
||||
m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
|
||||
})
|
||||
|
||||
m.Route("/delete", "GET,POST", org.SettingsDelete)
|
||||
|
||||
+76
-8
@@ -786,6 +786,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
|
||||
u, err := models.UserSignIn(signInForm.UserName, signInForm.Password)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
ctx.Data["user_exists"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplLinkAccount, &signInForm)
|
||||
} else {
|
||||
ctx.ServerError("UserLinkAccount", err)
|
||||
@@ -1281,7 +1282,7 @@ func ForgotPasswdPost(ctx *context.Context) {
|
||||
ctx.HTML(200, tplForgotPassword)
|
||||
}
|
||||
|
||||
func commonResetPassword(ctx *context.Context) *models.User {
|
||||
func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) {
|
||||
code := ctx.Query("code")
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
|
||||
@@ -1293,14 +1294,25 @@ func commonResetPassword(ctx *context.Context) *models.User {
|
||||
|
||||
if len(code) == 0 {
|
||||
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Fail early, don't frustrate the user
|
||||
u := models.VerifyUserActiveCode(code)
|
||||
if u == nil {
|
||||
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
twofa, err := models.GetTwoFactorByUID(u.ID)
|
||||
if err != nil {
|
||||
if !models.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
ctx.Data["has_two_factor"] = true
|
||||
ctx.Data["scratch_code"] = ctx.QueryBool("scratch_code")
|
||||
}
|
||||
|
||||
// Show the user that they are affecting the account that they intended to
|
||||
@@ -1308,10 +1320,10 @@ func commonResetPassword(ctx *context.Context) *models.User {
|
||||
|
||||
if nil != ctx.User && u.ID != ctx.User.ID {
|
||||
ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email))
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return u
|
||||
return u, twofa
|
||||
}
|
||||
|
||||
// ResetPasswd render the account recovery page
|
||||
@@ -1319,13 +1331,19 @@ func ResetPasswd(ctx *context.Context) {
|
||||
ctx.Data["IsResetForm"] = true
|
||||
|
||||
commonResetPassword(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(200, tplResetPassword)
|
||||
}
|
||||
|
||||
// ResetPasswdPost response from account recovery request
|
||||
func ResetPasswdPost(ctx *context.Context) {
|
||||
u := commonResetPassword(ctx)
|
||||
u, twofa := commonResetPassword(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if u == nil {
|
||||
// Flash error has been set
|
||||
@@ -1347,6 +1365,39 @@ func ResetPasswdPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle two-factor
|
||||
regenerateScratchToken := false
|
||||
if twofa != nil {
|
||||
if ctx.QueryBool("scratch_code") {
|
||||
if !twofa.VerifyScratchToken(ctx.Query("token")) {
|
||||
ctx.Data["IsResetForm"] = true
|
||||
ctx.Data["Err_Token"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil)
|
||||
return
|
||||
}
|
||||
regenerateScratchToken = true
|
||||
} else {
|
||||
passcode := ctx.Query("passcode")
|
||||
ok, err := twofa.ValidateTOTP(passcode)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error())
|
||||
return
|
||||
}
|
||||
if !ok || twofa.LastUsedPasscode == passcode {
|
||||
ctx.Data["IsResetForm"] = true
|
||||
ctx.Data["Err_Passcode"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
|
||||
return
|
||||
}
|
||||
|
||||
twofa.LastUsedPasscode = passcode
|
||||
if err = models.UpdateTwoFactor(twofa); err != nil {
|
||||
ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if u.Rands, err = models.GetUserSalt(); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
@@ -1356,7 +1407,6 @@ func ResetPasswdPost(ctx *context.Context) {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
u.HashPassword(passwd)
|
||||
u.MustChangePassword = false
|
||||
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
|
||||
@@ -1365,9 +1415,27 @@ func ResetPasswdPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
log.Trace("User password reset: %s", u.Name)
|
||||
|
||||
ctx.Data["IsResetFailed"] = true
|
||||
remember := len(ctx.Query("remember")) != 0
|
||||
|
||||
if regenerateScratchToken {
|
||||
// Invalidate the scratch token.
|
||||
_, err = twofa.GenerateScratchToken()
|
||||
if err != nil {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
return
|
||||
}
|
||||
if err = models.UpdateTwoFactor(twofa); err != nil {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
return
|
||||
}
|
||||
|
||||
handleSignInFull(ctx, u, remember, false)
|
||||
ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
return
|
||||
}
|
||||
|
||||
handleSignInFull(ctx, u, remember, true)
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -6,6 +6,7 @@ package user
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
@@ -23,14 +24,19 @@ func Avatar(ctx *context.Context) {
|
||||
|
||||
log.Debug("Asked avatar for user %v and size %v", userName, size)
|
||||
|
||||
user, err := models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
ctx.ServerError("Requested avatar for invalid user", err)
|
||||
} else {
|
||||
ctx.ServerError("Retrieving user by name", err)
|
||||
var user *models.User
|
||||
if strings.ToLower(userName) != "ghost" {
|
||||
user, err = models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
ctx.ServerError("Requested avatar for invalid user", err)
|
||||
} else {
|
||||
ctx.ServerError("Retrieving user by name", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
} else {
|
||||
user = models.NewGhostUser()
|
||||
}
|
||||
|
||||
ctx.Redirect(user.RealSizedAvatarLink(size))
|
||||
|
||||
@@ -74,7 +74,9 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) {
|
||||
if act.ActUser != nil {
|
||||
userCache[act.ActUserID] = act.ActUser
|
||||
}
|
||||
}
|
||||
|
||||
for _, act := range actions {
|
||||
repoOwner, ok := userCache[act.Repo.OwnerID]
|
||||
if !ok {
|
||||
repoOwner, err = models.GetUserByID(act.Repo.OwnerID)
|
||||
|
||||
@@ -546,6 +546,15 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||
|
||||
// Get new file.
|
||||
if strings.HasPrefix(line, cmdDiffHead) {
|
||||
if len(diff.Files) >= maxFiles {
|
||||
diff.IsIncomplete = true
|
||||
_, err := io.Copy(ioutil.Discard, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Copy: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
var middle int
|
||||
|
||||
// Note: In case file name is surrounded by double quotes (it happens only in git-shell).
|
||||
@@ -590,14 +599,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
|
||||
IsRenamed: a != b,
|
||||
}
|
||||
diff.Files = append(diff.Files, curFile)
|
||||
if len(diff.Files) >= maxFiles {
|
||||
diff.IsIncomplete = true
|
||||
_, err := io.Copy(ioutil.Discard, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Copy: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
curFileLinesCount = 0
|
||||
curFileLFSPrefix = false
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
package gitdiff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
@@ -195,3 +197,15 @@ func TestDiffLine_GetCommentSide(t *testing.T) {
|
||||
assert.Equal(t, "previous", (&DiffLine{Comments: []*models.Comment{{Line: -3}}}).GetCommentSide())
|
||||
assert.Equal(t, "proposed", (&DiffLine{Comments: []*models.Comment{{Line: 3}}}).GetCommentSide())
|
||||
}
|
||||
|
||||
func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
|
||||
git.Debug = true
|
||||
for _, behavior := range []string{"-w", "--ignore-space-at-eol", "-b", ""} {
|
||||
diffs, err := GetDiffRangeWithWhitespaceBehavior("./testdata/academic-module", "559c156f8e0178b71cb44355428f24001b08fc68", "bd7063cc7c04689c4d082183d32a604ed27a24f9",
|
||||
setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles, behavior)
|
||||
assert.NoError(t, err, fmt.Sprintf("Error when diff with %s", behavior))
|
||||
for _, f := range diffs.Files {
|
||||
assert.True(t, len(f.Sections) > 0, fmt.Sprintf("%s should have sections", f.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
@@ -0,0 +1,10 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[branch "master"]
|
||||
remote = origin
|
||||
merge = refs/heads/master
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||
:
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
|
||||
# An example hook script to integrate Watchman
|
||||
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||
# new and modified files.
|
||||
#
|
||||
# The hook is passed a version (currently 1) and a time in nanoseconds
|
||||
# formatted as a string and outputs to stdout all files that have been
|
||||
# modified since the given time. Paths must be relative to the root of
|
||||
# the working tree and separated by a single NUL.
|
||||
#
|
||||
# To enable this hook, rename this file to "query-watchman" and set
|
||||
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||
#
|
||||
my ($version, $time) = @ARGV;
|
||||
|
||||
# Check the hook interface version
|
||||
|
||||
if ($version == 1) {
|
||||
# convert nanoseconds to seconds
|
||||
$time = int $time / 1000000000;
|
||||
} else {
|
||||
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||
"Falling back to scanning...\n";
|
||||
}
|
||||
|
||||
my $git_work_tree;
|
||||
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||
$git_work_tree = Win32::GetCwd();
|
||||
$git_work_tree =~ tr/\\/\//;
|
||||
} else {
|
||||
require Cwd;
|
||||
$git_work_tree = Cwd::cwd();
|
||||
}
|
||||
|
||||
my $retry = 1;
|
||||
|
||||
launch_watchman();
|
||||
|
||||
sub launch_watchman {
|
||||
|
||||
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||
or die "open2() failed: $!\n" .
|
||||
"Falling back to scanning...\n";
|
||||
|
||||
# In the query expression below we're asking for names of files that
|
||||
# changed since $time but were not transient (ie created after
|
||||
# $time but no longer exist).
|
||||
#
|
||||
# To accomplish this, we're using the "since" generator to use the
|
||||
# recency index to select candidate nodes and "fields" to limit the
|
||||
# output to file names only. Then we're using the "expression" term to
|
||||
# further constrain the results.
|
||||
#
|
||||
# The category of transient files that we want to ignore will have a
|
||||
# creation clock (cclock) newer than $time_t value and will also not
|
||||
# currently exist.
|
||||
|
||||
my $query = <<" END";
|
||||
["query", "$git_work_tree", {
|
||||
"since": $time,
|
||||
"fields": ["name"],
|
||||
"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
|
||||
}]
|
||||
END
|
||||
|
||||
print CHLD_IN $query;
|
||||
close CHLD_IN;
|
||||
my $response = do {local $/; <CHLD_OUT>};
|
||||
|
||||
die "Watchman: command returned no output.\n" .
|
||||
"Falling back to scanning...\n" if $response eq "";
|
||||
die "Watchman: command returned invalid output: $response\n" .
|
||||
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||
|
||||
my $json_pkg;
|
||||
eval {
|
||||
require JSON::XS;
|
||||
$json_pkg = "JSON::XS";
|
||||
1;
|
||||
} or do {
|
||||
require JSON::PP;
|
||||
$json_pkg = "JSON::PP";
|
||||
};
|
||||
|
||||
my $o = $json_pkg->new->utf8->decode($response);
|
||||
|
||||
if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||
print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
|
||||
$retry--;
|
||||
qx/watchman watch "$git_work_tree"/;
|
||||
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||
"Falling back to scanning...\n" if $? != 0;
|
||||
|
||||
# Watchman will always return all files on the first query so
|
||||
# return the fast "everything is dirty" flag to git and do the
|
||||
# Watchman query just to get it over with now so we won't pay
|
||||
# the cost in git to look up each individual file.
|
||||
print "/\0";
|
||||
eval { launch_watchman() };
|
||||
exit 0;
|
||||
}
|
||||
|
||||
die "Watchman: $o->{error}.\n" .
|
||||
"Falling back to scanning...\n" if $o->{error};
|
||||
|
||||
binmode STDOUT, ":utf8";
|
||||
local $, = "\0";
|
||||
print @{$o->{files}};
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user