mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-03 08:02:36 +09:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75496b9ff5 | ||
|
|
8dad47a94a | ||
|
|
8e792986bb | ||
|
|
da80e90ac8 | ||
|
|
74dc22358b | ||
|
|
7d3e174906 | ||
|
|
8456700411 | ||
|
|
8a6acbbc12 | ||
|
|
98b3d8d5e1 | ||
|
|
e663f7459a | ||
|
|
7e85cba3e5 | ||
|
|
26628aa1d1 | ||
|
|
d9d2e8f1e8 | ||
|
|
4558eeb21a | ||
|
|
be25afc6de | ||
|
|
90bf1e7961 | ||
|
|
77ce08976d | ||
|
|
8f389c5dfa | ||
|
|
edef62e69e | ||
|
|
cdff144f76 | ||
|
|
ad6084a222 | ||
|
|
d3200db041 | ||
|
|
f305cffcaf | ||
|
|
c0320065b6 | ||
|
|
a1b74c5509 | ||
|
|
101fb0d7e2 | ||
|
|
82637c240a | ||
|
|
d0174d45ed | ||
|
|
da7a525c5c | ||
|
|
014313134f | ||
|
|
7dddf2186b | ||
|
|
446c06b817 | ||
|
|
9569607abb | ||
|
|
8ff4f82e05 | ||
|
|
2595c70868 | ||
|
|
00dc35e2de | ||
|
|
841efac895 | ||
|
|
dd827d6f2f | ||
|
|
4d2a6c40f8 | ||
|
|
fb274ec54b | ||
|
|
0c3f95034a | ||
|
|
4583caa077 | ||
|
|
cf20ebc8ba | ||
|
|
5ee09d3c81 | ||
|
|
e846b712fc | ||
|
|
49d113945f | ||
|
|
096aa18249 | ||
|
|
bf853db450 | ||
|
|
fb656b5124 | ||
|
|
4be59eb5d9 | ||
|
|
450b32c1a1 | ||
|
|
06673cbccb | ||
|
|
2fd708a397 | ||
|
|
7a0a133d7c | ||
|
|
17022f8b62 | ||
|
|
5568dd6475 | ||
|
|
58c105d4bf |
68
CHANGELOG.md
68
CHANGELOG.md
@@ -4,6 +4,74 @@ This changelog goes through all the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
|
||||
## [1.13.4](https://github.com/go-gitea/gitea/releases/tag/v1.13.4) - 2021-03-07
|
||||
|
||||
* SECURITY
|
||||
* Fix issue popups (#14898) (#14899)
|
||||
* BUGFIXES
|
||||
* Fix race in LFS ContentStore.Put(...) (#14895) (#14913)
|
||||
* Fix a couple of issues with a feeds (#14897) (#14903)
|
||||
* When transfering repository and database transaction failed, rollback the renames (#14864) (#14902)
|
||||
* Fix race in local storage (#14888) (#14901)
|
||||
* Fix 500 on pull view page if user is not loged in (#14885) (#14886)
|
||||
* DOCS
|
||||
* Fix how lfs data path is set (#14855) (#14884)
|
||||
|
||||
## [1.13.3](https://github.com/go-gitea/gitea/releases/tag/v1.13.3) - 2021-03-04
|
||||
|
||||
* BREAKING
|
||||
* Turn default hash password algorithm back to pbkdf2 from argon2 until we find a better one (#14673) (#14675)
|
||||
* BUGFIXES
|
||||
* Fix paging of file commit logs (#14831) (#14879)
|
||||
* Print useful error if SQLite is used in settings but not supported (#14476) (#14874)
|
||||
* Fix display since time round (#14226) (#14873)
|
||||
* When Deleting Repository only explicitly close PRs whose base is not this repository (#14823) (#14842)
|
||||
* Set HCaptchaSiteKey on Link Account pages (#14834) (#14839)
|
||||
* Fix a couple of CommentAsPatch issues. (#14804) (#14820)
|
||||
* Disable broken OAuth2 providers at startup (#14802) (#14811)
|
||||
* Repo Transfer permission checks (#14792) (#14794)
|
||||
* Fix double alert in oauth2 application edit view (#14764) (#14768)
|
||||
* Fix broken spans in diffs (#14678) (#14683)
|
||||
* Prevent race in PersistableChannelUniqueQueue.Has (#14651) (#14676)
|
||||
* HasPreviousCommit causes recursive load of commits unnecessarily (#14598) (#14649)
|
||||
* Do not assume all 40 char strings are SHA1s (#14624) (#14648)
|
||||
* Allow org labels to be set with issue templates (#14593) (#14647)
|
||||
* Accept multiple SSH keys in single LDAP SSHPublicKey attribute (#13989) (#14607)
|
||||
* Fix bug about ListOptions and stars/watchers pagnation (#14556) (#14573)
|
||||
* Fix GPG key deletion during account deletion (#14561) (#14569)
|
||||
|
||||
## [1.13.2](https://github.com/go-gitea/gitea/releases/tag/v1.13.2) - 2021-01-31
|
||||
|
||||
* SECURITY
|
||||
* Prevent panic on fuzzer provided string (#14405) (#14409)
|
||||
* Add secure/httpOnly attributes to the lang cookie (#14279) (#14280)
|
||||
* API
|
||||
* If release publisher is deleted use ghost user (#14375)
|
||||
* BUGFIXES
|
||||
* Internal ssh server respect Ciphers, MACs and KeyExchanges settings (#14523) (#14530)
|
||||
* Set the name Mapper in migrations (#14526) (#14529)
|
||||
* Fix wiki preview (#14515)
|
||||
* Update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 (#14497)
|
||||
* ChangeUserName: rename user files back on DB issue (#14447)
|
||||
* Fix lfs preview bug (#14428) (#14433)
|
||||
* Ensure timeout error is shown on u2f timeout (#14417) (#14431)
|
||||
* Fix Deadlock & Delete affected reactions on comment deletion (#14392) (#14425)
|
||||
* Use path not filepath in routers/editor (#14390) (#14396)
|
||||
* Check if label template exist first (#14384) (#14389)
|
||||
* Fix migration v141 (#14387) (#14388)
|
||||
* Use Request.URL.RequestURI() for fcgi (#14347)
|
||||
* Use ServerError provided by Context (#14333) (#14345)
|
||||
* Fix edit-label form init (#14337)
|
||||
* Fix mailIssueCommentBatch for pull request (#14252) (#14296)
|
||||
* Render links for commit hashes followed by comma (#14224) (#14227)
|
||||
* Send notifications for mentions in pulls, issues, (code-)comments (#14218) (#14221)
|
||||
* Fix avatar bugs (#14217) (#14220)
|
||||
* Ensure that schema search path is set with every connection on postgres (#14131) (#14216)
|
||||
* Fix dashboard issues labels filter bug (#14210) (#14214)
|
||||
* When visit /favicon.ico but the static file is not exist return 404 but not continue to handle the route (#14211) (#14213)
|
||||
* Fix branch selector on new issue page (#14194) (#14207)
|
||||
* Check for notExist on profile repository page (#14197) (#14203)
|
||||
|
||||
## [1.13.1](https://github.com/go-gitea/gitea/releases/tag/v1.13.1) - 2020-12-29
|
||||
|
||||
* SECURITY
|
||||
|
||||
2
Makefile
2
Makefile
@@ -585,7 +585,7 @@ release-darwin: | $(DIST_DIRS)
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
GO111MODULE=off $(GO) get -u src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" GO111MODULE=off xgo -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/amd64' -out gitea-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
@@ -548,7 +548,7 @@ ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true
|
||||
;Classes include "lower,upper,digit,spec"
|
||||
PASSWORD_COMPLEXITY = off
|
||||
; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt"
|
||||
PASSWORD_HASH_ALGO = argon2
|
||||
PASSWORD_HASH_ALGO = pbkdf2
|
||||
; Set false to allow JavaScript to read CSRF cookie
|
||||
CSRF_COOKIE_HTTP_ONLY = true
|
||||
; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed
|
||||
@@ -850,7 +850,7 @@ MACARON = file
|
||||
ROUTER_LOG_LEVEL = Info
|
||||
ROUTER = console
|
||||
ENABLE_ACCESS_LOG = false
|
||||
ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"
|
||||
ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"
|
||||
ACCESS = file
|
||||
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace"
|
||||
LEVEL = Info
|
||||
|
||||
@@ -276,7 +276,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||
- `LANDING_PAGE`: **home**: Landing page for unauthenticated users \[home, explore, organizations, login\].
|
||||
|
||||
- `LFS_START_SERVER`: **false**: Enables git-lfs support.
|
||||
- `LFS_CONTENT_PATH`: **%(APP_DATA_PATH)/lfs**: Default LFS content path. (if it is on local storage.)
|
||||
- `LFS_CONTENT_PATH`: **%(APP_DATA_PATH)/lfs**: DEPRECATED: Default LFS content path. (if it is on local storage.)
|
||||
- `LFS_JWT_SECRET`: **\<empty\>**: LFS authentication secret, change this a unique string.
|
||||
- `LFS_HTTP_AUTH_EXPIRY`: **20m**: LFS authentication validity period in time.Duration, pushes taking longer than this may fail.
|
||||
- `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit).
|
||||
@@ -402,7 +402,7 @@ relation to port exhaustion.
|
||||
- `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server.
|
||||
- `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary.
|
||||
- `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`)
|
||||
- `PASSWORD_HASH_ALGO`: **argon2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\].
|
||||
- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\], argon2 will spend more memory than others.
|
||||
- `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie.
|
||||
- `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users.
|
||||
- `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off):
|
||||
@@ -828,7 +828,7 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
|
||||
|
||||
- `STORAGE_TYPE`: **local**: Storage type for lfs, `local` for local disk or `minio` for s3 compatible object storage service or other name defined with `[storage.xxx]`
|
||||
- `SERVE_DIRECT`: **false**: Allows the storage driver to redirect to authenticated URLs to serve files directly. Currently, only Minio/S3 is supported via signed URLs, local does nothing.
|
||||
- `CONTENT_PATH`: **./data/lfs**: Where to store LFS files, only available when `STORAGE_TYPE` is `local`.
|
||||
- `PATH`: **./data/lfs**: Where to store LFS files, only available when `STORAGE_TYPE` is `local`. If not set it fall back to deprecated LFS_CONTENT_PATH value in [server] section.
|
||||
- `MINIO_ENDPOINT`: **localhost:9000**: Minio endpoint to connect only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID to connect only available when `STORAGE_TYPE` is `minio`
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey to connect only available when `STORAGE_TYPE is` `minio`
|
||||
|
||||
@@ -73,6 +73,7 @@ menu:
|
||||
|
||||
- `LFS_START_SERVER`: 是否启用 git-lfs 支持. 可以为 `true` 或 `false`, 默认是 `false`。
|
||||
- `LFS_JWT_SECRET`: LFS 认证密钥,改成自己的。
|
||||
- `LFS_CONTENT_PATH`: **已废弃**, 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。
|
||||
|
||||
## Database (`database`)
|
||||
|
||||
@@ -323,7 +324,7 @@ LFS 的存储配置。 如果 `STORAGE_TYPE` 为空,则此配置将从 `[stora
|
||||
|
||||
- `STORAGE_TYPE`: **local**: LFS 的存储类型,`local` 将存储到磁盘,`minio` 将存储到 s3 兼容的对象服务。
|
||||
- `SERVE_DIRECT`: **false**: 允许直接重定向到存储系统。当前,仅 Minio/S3 是支持的。
|
||||
- `CONTENT_PATH`: 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。
|
||||
- `PATH`: 存放 lfs 命令上传的文件的地方,默认是 `data/lfs`。
|
||||
- `MINIO_ENDPOINT`: **localhost:9000**: Minio 地址,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。
|
||||
- `MINIO_ACCESS_KEY_ID`: Minio accessKeyID,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。
|
||||
- `MINIO_SECRET_ACCESS_KEY`: Minio secretAccessKey,仅当 `LFS_STORAGE_TYPE` 为 `minio` 时有效。
|
||||
|
||||
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
code.gitea.io/gitea-vet v0.2.1
|
||||
code.gitea.io/sdk/gitea v0.13.1
|
||||
code.gitea.io/sdk/gitea v0.13.2
|
||||
gitea.com/lunny/levelqueue v0.3.0
|
||||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
|
||||
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76
|
||||
|
||||
4
go.sum
4
go.sum
@@ -15,8 +15,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||
code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk=
|
||||
code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
|
||||
code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA=
|
||||
code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I=
|
||||
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
|
||||
|
||||
@@ -445,11 +445,12 @@ func TestAPIRepoTransfer(t *testing.T) {
|
||||
expectedStatus int
|
||||
}{
|
||||
{ctxUserID: 1, newOwner: "user2", teams: nil, expectedStatus: http.StatusAccepted},
|
||||
{ctxUserID: 2, newOwner: "user1", teams: nil, expectedStatus: http.StatusAccepted},
|
||||
{ctxUserID: 2, newOwner: "user1", teams: nil, expectedStatus: http.StatusForbidden},
|
||||
{ctxUserID: 2, newOwner: "user6", teams: nil, expectedStatus: http.StatusForbidden},
|
||||
{ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity},
|
||||
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
|
||||
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
|
||||
{ctxUserID: 2, newOwner: "user2", teams: nil, expectedStatus: http.StatusAccepted},
|
||||
}
|
||||
|
||||
defer prepareTestEnv(t)()
|
||||
|
||||
@@ -237,6 +237,6 @@ func TestLDAPUserSSHKeySync(t *testing.T) {
|
||||
syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text())
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, u.SSHKeys, syncedKeys)
|
||||
assert.ElementsMatch(t, u.SSHKeys, syncedKeys, "Unequal number of keys synchronized for user: %s", u.UserName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path
|
||||
if err := bucket.Delete(path); err != nil {
|
||||
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
|
||||
log.Warn(title+" [%s]: %v", path, err)
|
||||
if err = createNotice(x, NoticeRepository, desc); err != nil {
|
||||
if err = createNotice(e, NoticeRepository, desc); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestGetCommitStatuses(t *testing.T) {
|
||||
|
||||
sha1 := "1234123412341234123412341234123412341234"
|
||||
|
||||
statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{})
|
||||
statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{ListOptions: ListOptions{Page: 1, PageSize: 50}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(maxResults), 5)
|
||||
assert.Len(t, statuses, 5)
|
||||
|
||||
@@ -65,7 +65,11 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) {
|
||||
|
||||
// ListGPGKeys returns a list of public keys belongs to given user.
|
||||
func ListGPGKeys(uid int64, listOptions ListOptions) ([]*GPGKey, error) {
|
||||
sess := x.Where("owner_id=? AND primary_key_id=''", uid)
|
||||
return listGPGKeys(x, uid, listOptions)
|
||||
}
|
||||
|
||||
func listGPGKeys(e Engine, uid int64, listOptions ListOptions) ([]*GPGKey, error) {
|
||||
sess := e.Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid)
|
||||
if listOptions.Page != 0 {
|
||||
sess = listOptions.setSessionPagination(sess)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -1491,6 +1492,7 @@ type UserIssueStatsOptions struct {
|
||||
IsPull bool
|
||||
IsClosed bool
|
||||
IssueIDs []int64
|
||||
LabelIDs []int64
|
||||
}
|
||||
|
||||
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
|
||||
@@ -1507,29 +1509,38 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
cond = cond.And(builder.In("issue.id", opts.IssueIDs))
|
||||
}
|
||||
|
||||
sess := func(cond builder.Cond) *xorm.Session {
|
||||
s := x.Where(cond)
|
||||
if len(opts.LabelIDs) > 0 {
|
||||
s.Join("INNER", "issue_label", "issue_label.issue_id = issue.id").
|
||||
In("issue_label.label_id", opts.LabelIDs)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
switch opts.FilterMode {
|
||||
case FilterModeAll:
|
||||
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
|
||||
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
|
||||
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
|
||||
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
|
||||
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case FilterModeAssign:
|
||||
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
|
||||
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
|
||||
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||
And("issue_assignees.assignee_id = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
|
||||
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
|
||||
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||
And("issue_assignees.assignee_id = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
@@ -1537,27 +1548,27 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
return nil, err
|
||||
}
|
||||
case FilterModeCreate:
|
||||
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
|
||||
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
|
||||
And("issue.poster_id = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
|
||||
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
|
||||
And("issue.poster_id = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case FilterModeMention:
|
||||
stats.OpenCount, err = x.Where(cond).And("issue.is_closed = ?", false).
|
||||
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false).
|
||||
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
|
||||
And("issue_user.uid = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.ClosedCount, err = x.Where(cond).And("issue.is_closed = ?", true).
|
||||
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true).
|
||||
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
|
||||
And("issue_user.uid = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
@@ -1567,7 +1578,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
}
|
||||
|
||||
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
|
||||
stats.AssignCount, err = x.Where(cond).
|
||||
stats.AssignCount, err = sess(cond).
|
||||
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||
And("issue_assignees.assignee_id = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
@@ -1575,14 +1586,14 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats.CreateCount, err = x.Where(cond).
|
||||
stats.CreateCount, err = sess(cond).
|
||||
And("poster_id = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats.MentionCount, err = x.Where(cond).
|
||||
stats.MentionCount, err = sess(cond).
|
||||
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
|
||||
And("issue_user.uid = ?", opts.UserID).
|
||||
Count(new(Issue))
|
||||
@@ -1590,7 +1601,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats.YourRepositoriesCount, err = x.Where(cond).
|
||||
stats.YourRepositoriesCount, err = sess(cond).
|
||||
And(builder.In("issue.repo_id", opts.UserRepoIDs)).
|
||||
Count(new(Issue))
|
||||
if err != nil {
|
||||
@@ -1829,6 +1840,19 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
|
||||
func (issue *Issue) FindAndUpdateIssueMentions(ctx DBContext, doer *User, content string) (mentions []*User, err error) {
|
||||
rawMentions := references.FindAllMentionsMarkdown(content)
|
||||
mentions, err = issue.ResolveMentionsByVisibility(ctx, doer, rawMentions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
|
||||
}
|
||||
if err = UpdateIssueMentions(ctx, issue.ID, mentions); err != nil {
|
||||
return nil, fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResolveMentionsByVisibility returns the users mentioned in an issue, removing those that
|
||||
// don't have access to reading it. Teams are expanded into their users, but organizations are ignored.
|
||||
func (issue *Issue) ResolveMentionsByVisibility(ctx DBContext, doer *User, mentions []string) (users []*User, err error) {
|
||||
|
||||
@@ -82,7 +82,7 @@ func isUserAssignedToIssue(e Engine, issue *Issue, user *User) (isAssigned bool,
|
||||
}
|
||||
|
||||
// ClearAssigneeByUserID deletes all assignments of an user
|
||||
func clearAssigneeByUserID(sess *xorm.Session, userID int64) (err error) {
|
||||
func clearAssigneeByUserID(sess Engine, userID int64) (err error) {
|
||||
_, err = sess.Delete(&IssueAssignees{AssigneeID: userID})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1077,6 +1077,10 @@ func DeleteComment(comment *Comment, doer *User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := deleteReaction(sess, &ReactionOptions{Comment: comment}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ type Label struct {
|
||||
func GetLabelTemplateFile(name string) ([][3]string, error) {
|
||||
data, err := GetRepoInitFile("label", name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetRepoInitFile: %v", err)
|
||||
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %v", err)}
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
@@ -62,7 +62,7 @@ func GetLabelTemplateFile(name string) ([][3]string, error) {
|
||||
|
||||
fields := strings.SplitN(parts[0], " ", 2)
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("line is malformed: %s", line)
|
||||
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
|
||||
}
|
||||
|
||||
color := strings.Trim(fields[0], " ")
|
||||
@@ -70,7 +70,7 @@ func GetLabelTemplateFile(name string) ([][3]string, error) {
|
||||
color = "#" + color
|
||||
}
|
||||
if !LabelColorPattern.MatchString(color) {
|
||||
return nil, fmt.Errorf("bad HTML color code in line: %s", line)
|
||||
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("bad HTML color code in line: %s", line)}
|
||||
}
|
||||
|
||||
var description string
|
||||
@@ -167,7 +167,7 @@ func (label *Label) ForegroundColor() template.CSS {
|
||||
func loadLabels(labelTemplate string) ([]string, error) {
|
||||
list, err := GetLabelTemplateFile(labelTemplate)
|
||||
if err != nil {
|
||||
return nil, ErrIssueLabelTemplateLoad{labelTemplate, err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels := make([]string, len(list))
|
||||
@@ -186,7 +186,7 @@ func LoadLabelsFormatted(labelTemplate string) (string, error) {
|
||||
func initializeLabels(e Engine, id int64, labelTemplate string, isOrg bool) error {
|
||||
list, err := GetLabelTemplateFile(labelTemplate)
|
||||
if err != nil {
|
||||
return ErrIssueLabelTemplateLoad{labelTemplate, err}
|
||||
return err
|
||||
}
|
||||
|
||||
labels := make([]*Label, len(list))
|
||||
|
||||
@@ -178,11 +178,15 @@ func CreateCommentReaction(doer *User, issue *Issue, comment *Comment, content s
|
||||
})
|
||||
}
|
||||
|
||||
func deleteReaction(e *xorm.Session, opts *ReactionOptions) error {
|
||||
func deleteReaction(e Engine, opts *ReactionOptions) error {
|
||||
reaction := &Reaction{
|
||||
Type: opts.Type,
|
||||
UserID: opts.Doer.ID,
|
||||
IssueID: opts.Issue.ID,
|
||||
Type: opts.Type,
|
||||
}
|
||||
if opts.Doer != nil {
|
||||
reaction.UserID = opts.Doer.ID
|
||||
}
|
||||
if opts.Issue != nil {
|
||||
reaction.IssueID = opts.Issue.ID
|
||||
}
|
||||
if opts.Comment != nil {
|
||||
reaction.CommentID = opts.Comment.ID
|
||||
|
||||
@@ -16,13 +16,13 @@ type ListOptions struct {
|
||||
Page int // start from 1
|
||||
}
|
||||
|
||||
func (opts ListOptions) getPaginatedSession() *xorm.Session {
|
||||
func (opts *ListOptions) getPaginatedSession() *xorm.Session {
|
||||
opts.setDefaultValues()
|
||||
|
||||
return x.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
|
||||
func (opts ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session {
|
||||
func (opts *ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session {
|
||||
opts.setDefaultValues()
|
||||
|
||||
if opts.PageSize <= 0 {
|
||||
@@ -31,21 +31,21 @@ func (opts ListOptions) setSessionPagination(sess *xorm.Session) *xorm.Session {
|
||||
return sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
|
||||
func (opts ListOptions) setEnginePagination(e Engine) Engine {
|
||||
func (opts *ListOptions) setEnginePagination(e Engine) Engine {
|
||||
opts.setDefaultValues()
|
||||
|
||||
return e.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
|
||||
// GetStartEnd returns the start and end of the ListOptions
|
||||
func (opts ListOptions) GetStartEnd() (start, end int) {
|
||||
func (opts *ListOptions) GetStartEnd() (start, end int) {
|
||||
opts.setDefaultValues()
|
||||
start = (opts.Page - 1) * opts.PageSize
|
||||
end = start + opts.Page
|
||||
return
|
||||
}
|
||||
|
||||
func (opts ListOptions) setDefaultValues() {
|
||||
func (opts *ListOptions) setDefaultValues() {
|
||||
if opts.PageSize <= 0 {
|
||||
opts.PageSize = setting.API.DefaultPagingNum
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
const minDBVersion = 70 // Gitea 1.5.3
|
||||
@@ -296,6 +297,8 @@ func EnsureUpToDate(x *xorm.Engine) error {
|
||||
|
||||
// Migrate database to current version
|
||||
func Migrate(x *xorm.Engine) error {
|
||||
// Set a new clean the default mapper to GonicMapper as that is the default for Gitea.
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync(new(Version)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
@@ -334,6 +337,8 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
|
||||
// Migrate
|
||||
for i, m := range migrations[v-minDBVersion:] {
|
||||
log.Info("Migration[%d]: %s", v+int64(i), m.Description())
|
||||
// Reset the mapper between each migration - migrations are not supposed to depend on each other
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err = m.Migrate(x); err != nil {
|
||||
return fmt.Errorf("do migrate: %v", err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func addKeepActivityPrivateUserColumn(x *xorm.Engine) error {
|
||||
type User struct {
|
||||
KeepActivityPrivate bool
|
||||
KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
if err := x.Sync2(new(User)); err != nil {
|
||||
|
||||
@@ -15,12 +15,14 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
// Needed for the MySQL driver
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
// Needed for the MySQL driver
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
// Needed for the Postgresql driver
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
@@ -145,7 +147,16 @@ func getEngine() (*xorm.Engine, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
engine, err := xorm.NewEngine(setting.Database.Type, connStr)
|
||||
var engine *xorm.Engine
|
||||
|
||||
if setting.Database.UsePostgreSQL && len(setting.Database.Schema) > 0 {
|
||||
// OK whilst we sort out our schema issues - create a schema aware postgres
|
||||
registerPostgresSchemaDriver()
|
||||
engine, err = xorm.NewEngine("postgresschema", connStr)
|
||||
} else {
|
||||
engine, err = xorm.NewEngine(setting.Database.Type, connStr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,16 +166,6 @@ func getEngine() (*xorm.Engine, error) {
|
||||
engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"})
|
||||
}
|
||||
engine.SetSchema(setting.Database.Schema)
|
||||
if setting.Database.UsePostgreSQL && len(setting.Database.Schema) > 0 {
|
||||
// Add the schema to the search path
|
||||
if _, err := engine.Exec(`SELECT set_config(
|
||||
'search_path',
|
||||
? || ',' || current_setting('search_path'),
|
||||
false)`,
|
||||
setting.Database.Schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
@@ -313,6 +314,13 @@ func DumpDatabase(filePath string, dbType string) error {
|
||||
tbs = append(tbs, t)
|
||||
}
|
||||
|
||||
// temporary fix for v1.13.x (https://github.com/go-gitea/gitea/issues/14069)
|
||||
if _, err := x.Where(builder.IsNull{"keep_activity_private"}).
|
||||
Cols("keep_activity_private").
|
||||
Update(User{KeepActivityPrivate: false}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"code.gitea.io/gitea/modules/auth/oauth2"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// OAuth2Provider describes the display values of a single OAuth2 provider
|
||||
@@ -135,7 +136,12 @@ func initOAuth2LoginSources() error {
|
||||
oAuth2Config := source.OAuth2()
|
||||
err := oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Critical("Unable to register source: %s due to Error: %v. This source will be disabled.", source.Name, err)
|
||||
source.IsActived = false
|
||||
if err = UpdateSource(source); err != nil {
|
||||
log.Critical("Unable to update source %s to disable it. Error: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -54,7 +54,11 @@ func (r *Release) loadAttributes(e Engine) error {
|
||||
if r.Publisher == nil {
|
||||
r.Publisher, err = getUserByID(e, r.PublisherID)
|
||||
if err != nil {
|
||||
return err
|
||||
if IsErrUserNotExist(err) {
|
||||
r.Publisher = NewGhostUser()
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return getReleaseAttachments(e, r)
|
||||
|
||||
@@ -1290,11 +1290,44 @@ func IncrementRepoForkNum(ctx DBContext, repoID int64) error {
|
||||
}
|
||||
|
||||
// TransferOwnership transfers all corresponding setting from old user to new one.
|
||||
func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error {
|
||||
func TransferOwnership(doer *User, newOwnerName string, repo *Repository) (err error) {
|
||||
repoRenamed := false
|
||||
wikiRenamed := false
|
||||
oldOwnerName := doer.Name
|
||||
|
||||
defer func() {
|
||||
if !repoRenamed && !wikiRenamed {
|
||||
return
|
||||
}
|
||||
|
||||
recoverErr := recover()
|
||||
if err == nil && recoverErr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if repoRenamed {
|
||||
if err := os.Rename(RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name)); err != nil {
|
||||
log.Critical("Unable to move repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, RepoPath(newOwnerName, repo.Name), RepoPath(oldOwnerName, repo.Name), err)
|
||||
}
|
||||
}
|
||||
|
||||
if wikiRenamed {
|
||||
if err := os.Rename(WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name)); err != nil {
|
||||
log.Critical("Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v", oldOwnerName, repo.Name, WikiPath(newOwnerName, repo.Name), WikiPath(oldOwnerName, repo.Name), err)
|
||||
}
|
||||
}
|
||||
|
||||
if recoverErr != nil {
|
||||
log.Error("Panic within TransferOwnership: %v\n%s", recoverErr, log.Stack(2))
|
||||
panic(recoverErr)
|
||||
}
|
||||
}()
|
||||
|
||||
newOwner, err := GetUserByName(newOwnerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get new owner '%s': %v", newOwnerName, err)
|
||||
}
|
||||
newOwnerName = newOwner.Name // ensure capitalisation matches
|
||||
|
||||
// Check if new owner has repository with same name.
|
||||
has, err := IsRepositoryExist(newOwner, repo.Name)
|
||||
@@ -1311,6 +1344,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||
}
|
||||
|
||||
oldOwner := repo.Owner
|
||||
oldOwnerName = oldOwner.Name
|
||||
|
||||
// Note: we have to set value here to make sure recalculate accesses is based on
|
||||
// new owner.
|
||||
@@ -1370,9 +1404,9 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||
}
|
||||
|
||||
// Update repository count.
|
||||
if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil {
|
||||
if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil {
|
||||
return fmt.Errorf("increase new owner repository count: %v", err)
|
||||
} else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", oldOwner.ID); err != nil {
|
||||
} else if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", oldOwner.ID); err != nil {
|
||||
return fmt.Errorf("decrease old owner repository count: %v", err)
|
||||
}
|
||||
|
||||
@@ -1382,7 +1416,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||
|
||||
// Remove watch for organization.
|
||||
if oldOwner.IsOrganization() {
|
||||
if err = watchRepo(sess, oldOwner.ID, repo.ID, false); err != nil {
|
||||
if err := watchRepo(sess, oldOwner.ID, repo.ID, false); err != nil {
|
||||
return fmt.Errorf("watchRepo [false]: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1394,16 +1428,18 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||
return fmt.Errorf("Failed to create dir %s: %v", dir, err)
|
||||
}
|
||||
|
||||
if err = os.Rename(RepoPath(oldOwner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
|
||||
if err := os.Rename(RepoPath(oldOwner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
|
||||
return fmt.Errorf("rename repository directory: %v", err)
|
||||
}
|
||||
repoRenamed = true
|
||||
|
||||
// Rename remote wiki repository to new path and delete local copy.
|
||||
wikiPath := WikiPath(oldOwner.Name, repo.Name)
|
||||
if com.IsExist(wikiPath) {
|
||||
if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil {
|
||||
if err := os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil {
|
||||
return fmt.Errorf("rename repository wiki: %v", err)
|
||||
}
|
||||
wikiRenamed = true
|
||||
}
|
||||
|
||||
// If there was previously a redirect at this location, remove it.
|
||||
@@ -1600,26 +1636,27 @@ func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes [
|
||||
}
|
||||
|
||||
// DeleteRepository deletes a repository for a user or organization.
|
||||
// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock)
|
||||
func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In case is a organization.
|
||||
org, err := GetUserByID(uid)
|
||||
org, err := getUserByID(sess, uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if org.IsOrganization() {
|
||||
if err = org.GetTeams(&SearchTeamOptions{}); err != nil {
|
||||
if err = org.getTeams(sess); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo := &Repository{ID: repoID, OwnerID: uid}
|
||||
has, err := sess.Get(repo)
|
||||
repo := &Repository{OwnerID: uid}
|
||||
has, err := sess.ID(repoID).Get(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
@@ -1768,14 +1805,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
sess.Close()
|
||||
if len(deployKeys) > 0 {
|
||||
// We need to rewrite the public keys because the commit failed
|
||||
if err2 := RewriteAllPublicKeys(); err2 != nil {
|
||||
return fmt.Errorf("Commit: %v SSH Keys: %v", err, err2)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
sess.Close()
|
||||
|
||||
75
models/sql_postgres_with_schema.go
Normal file
75
models/sql_postgres_with_schema.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"xorm.io/xorm/dialects"
|
||||
)
|
||||
|
||||
var registerOnce sync.Once
|
||||
|
||||
func registerPostgresSchemaDriver() {
|
||||
registerOnce.Do(func() {
|
||||
sql.Register("postgresschema", &postgresSchemaDriver{})
|
||||
dialects.RegisterDriver("postgresschema", dialects.QueryDriver("postgres"))
|
||||
})
|
||||
}
|
||||
|
||||
type postgresSchemaDriver struct {
|
||||
pq.Driver
|
||||
}
|
||||
|
||||
// Open opens a new connection to the database. name is a connection string.
|
||||
// This function opens the postgres connection in the default manner but immediately
|
||||
// runs set_config to set the search_path appropriately
|
||||
func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
|
||||
conn, err := d.Driver.Open(name)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema)
|
||||
|
||||
// golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
|
||||
// and in any case pq does not implement it
|
||||
if execer, ok := conn.(driver.Execer); ok { //nolint
|
||||
_, err := execer.Exec(`SELECT set_config(
|
||||
'search_path',
|
||||
$1 || ',' || current_setting('search_path'),
|
||||
false)`, []driver.Value{schemaValue}) //nolint
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
stmt, err := conn.Prepare(`SELECT set_config(
|
||||
'search_path',
|
||||
$1 || ',' || current_setting('search_path'),
|
||||
false)`)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
// driver.String.ConvertValue will never return err for string
|
||||
|
||||
// golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
|
||||
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// UserType defines the user type
|
||||
@@ -923,6 +922,7 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress {
|
||||
|
||||
// ChangeUserName changes all corresponding setting from old user name to new one.
|
||||
func ChangeUserName(u *User, newUserName string) (err error) {
|
||||
oldUserName := u.Name
|
||||
if err = IsUsableUsername(newUserName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -940,16 +940,24 @@ func ChangeUserName(u *User, newUserName string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, u.Name); err != nil {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
|
||||
return fmt.Errorf("Change repo owner name: %v", err)
|
||||
}
|
||||
|
||||
// Do not fail if directory does not exist
|
||||
if err = os.Rename(UserPath(u.Name), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
|
||||
if err = os.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("Rename user directory: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
if err = sess.Commit(); err != nil {
|
||||
if err2 := os.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
|
||||
log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
|
||||
return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDupEmail checks whether there are the same email with the user
|
||||
@@ -1020,8 +1028,7 @@ func deleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: need some kind of mechanism to record failure. HINT: system notice
|
||||
func deleteUser(e *xorm.Session, u *User) error {
|
||||
func deleteUser(e Engine, u *User) error {
|
||||
// Note: A user owns any repository or belongs to any organization
|
||||
// cannot perform delete operation.
|
||||
|
||||
@@ -1115,6 +1122,16 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||
// ***** END: PublicKey *****
|
||||
|
||||
// ***** START: GPGPublicKey *****
|
||||
keys, err := listGPGKeys(e, u.ID, ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ListGPGKeys: %v", err)
|
||||
}
|
||||
// Delete GPGKeyImport(s).
|
||||
for _, key := range keys {
|
||||
if _, err = e.Delete(&GPGKeyImport{KeyID: key.KeyID}); err != nil {
|
||||
return fmt.Errorf("deleteGPGKeyImports: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err = e.Delete(&GPGKey{OwnerID: u.ID}); err != nil {
|
||||
return fmt.Errorf("deleteGPGKeys: %v", err)
|
||||
}
|
||||
@@ -1135,18 +1152,21 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: system notice
|
||||
// Note: There are something just cannot be roll back,
|
||||
// so just keep error logs of those operations.
|
||||
path := UserPath(u.Name)
|
||||
if err := util.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
|
||||
if err = util.RemoveAll(path); err != nil {
|
||||
err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
|
||||
_ = createNotice(e, NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(u.Avatar) > 0 {
|
||||
avatarPath := u.CustomAvatarRelativePath()
|
||||
if err := storage.Avatars.Delete(avatarPath); err != nil {
|
||||
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
|
||||
if err = storage.Avatars.Delete(avatarPath); err != nil {
|
||||
err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
|
||||
_ = createNotice(e, NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1602,20 +1622,34 @@ func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
|
||||
func addLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool {
|
||||
var sshKeysNeedUpdate bool
|
||||
for _, sshKey := range sshPublicKeys {
|
||||
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKey))
|
||||
if err == nil {
|
||||
sshKeyName := fmt.Sprintf("%s-%s", s.Name, sshKey[0:40])
|
||||
if _, err := AddPublicKey(usr.ID, sshKeyName, sshKey, s.ID); err != nil {
|
||||
var err error
|
||||
found := false
|
||||
keys := []byte(sshKey)
|
||||
loop:
|
||||
for len(keys) > 0 && err == nil {
|
||||
var out ssh.PublicKey
|
||||
// We ignore options as they are not relevant to Gitea
|
||||
out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
found = true
|
||||
marshalled := string(ssh.MarshalAuthorizedKey(out))
|
||||
marshalled = marshalled[:len(marshalled)-1]
|
||||
sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
|
||||
|
||||
if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
|
||||
if IsErrKeyAlreadyExist(err) {
|
||||
log.Trace("addLdapSSHPublicKeys[%s]: LDAP Public SSH Key %s already exists for user", s.Name, usr.Name)
|
||||
log.Trace("addLdapSSHPublicKeys[%s]: LDAP Public SSH Key %s already exists for user", sshKeyName, usr.Name)
|
||||
} else {
|
||||
log.Error("addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err)
|
||||
log.Error("addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
|
||||
}
|
||||
} else {
|
||||
log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", s.Name, usr.Name)
|
||||
log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", sshKeyName, usr.Name)
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if !found && err != nil {
|
||||
log.Warn("addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,9 @@ func (u *User) generateRandomAvatar(e Engine) error {
|
||||
return fmt.Errorf("RandomImage: %v", err)
|
||||
}
|
||||
|
||||
if u.Avatar == "" {
|
||||
u.Avatar = base.HashEmail(u.AvatarEmail)
|
||||
}
|
||||
u.Avatar = base.HashEmail(seed)
|
||||
|
||||
// Don't share the images so that we can delete them easily
|
||||
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
if err := png.Encode(w, img); err != nil {
|
||||
log.Error("Encode: %v", err)
|
||||
@@ -133,7 +132,7 @@ func (u *User) UploadAvatar(data []byte) error {
|
||||
// Otherwise, if any of the users delete his avatar
|
||||
// Other users will lose their avatars too.
|
||||
u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data)))))
|
||||
if err = updateUser(sess, u); err != nil {
|
||||
if err = updateUserCols(sess, u, "use_custom_avatar", "avatar"); err != nil {
|
||||
return fmt.Errorf("updateUser: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -421,3 +421,71 @@ func TestGetMaileableUsersByIDs(t *testing.T) {
|
||||
assert.Equal(t, results[1].ID, 4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddLdapSSHPublicKeys(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
s := &LoginSource{ID: 1}
|
||||
|
||||
testCases := []struct {
|
||||
keyString string
|
||||
number int
|
||||
keyContents []string
|
||||
}{
|
||||
{
|
||||
keyString: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
|
||||
number: 1,
|
||||
keyContents: []string{
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
|
||||
},
|
||||
},
|
||||
{
|
||||
keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment
|
||||
ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`,
|
||||
number: 2,
|
||||
keyContents: []string{
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
|
||||
"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=",
|
||||
},
|
||||
},
|
||||
{
|
||||
keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment
|
||||
# comment asmdna,ndp
|
||||
ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`,
|
||||
number: 2,
|
||||
keyContents: []string{
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
|
||||
"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=",
|
||||
},
|
||||
},
|
||||
{
|
||||
keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment
|
||||
382488320jasdj1lasmva/vasodifipi4193-fksma.cm
|
||||
ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`,
|
||||
number: 2,
|
||||
keyContents: []string{
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=",
|
||||
"ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, kase := range testCases {
|
||||
s.ID = int64(i) + 20
|
||||
addLdapSSHPublicKeys(user, s, []string{kase.keyString})
|
||||
keys, err := ListPublicLdapSSHKeys(user.ID, s.ID)
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, kase.number, len(keys))
|
||||
|
||||
for _, key := range keys {
|
||||
assert.Contains(t, kase.keyContents, key.Content)
|
||||
}
|
||||
for _, key := range keys {
|
||||
DeletePublicKey(user, key.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
_ "image/png" // for processing png images
|
||||
"io"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -309,23 +311,33 @@ func (c *Commit) CommitsBefore() (*list.List, error) {
|
||||
|
||||
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
|
||||
func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
|
||||
for i := 0; i < c.ParentCount(); i++ {
|
||||
commit, err := c.Parent(i)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if commit.ID == commitHash {
|
||||
return true, nil
|
||||
}
|
||||
commitInParentCommit, err := commit.HasPreviousCommit(commitHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if commitInParentCommit {
|
||||
return true, nil
|
||||
}
|
||||
this := c.ID.String()
|
||||
that := commitHash.String()
|
||||
|
||||
if this == that {
|
||||
return false, nil
|
||||
}
|
||||
return false, nil
|
||||
|
||||
if err := CheckGitVersionConstraint(">= 1.8.0"); err == nil {
|
||||
_, err := NewCommand("merge-base", "--is-ancestor", that, this).RunInDir(c.repo.Path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
result, err := NewCommand("rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunInDir(c.repo.Path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(strings.TrimSpace(result)) > 0, nil
|
||||
}
|
||||
|
||||
// CommitsBeforeLimit returns num commits before current revision
|
||||
|
||||
@@ -125,30 +125,39 @@ var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+
|
||||
|
||||
const cmdDiffHead = "diff --git "
|
||||
|
||||
func isHeader(lof string) bool {
|
||||
return strings.HasPrefix(lof, cmdDiffHead) || strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")
|
||||
func isHeader(lof string, inHunk bool) bool {
|
||||
return strings.HasPrefix(lof, cmdDiffHead) || (!inHunk && (strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")))
|
||||
}
|
||||
|
||||
// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
|
||||
// it also recalculates hunks and adds the appropriate headers to the new diff.
|
||||
// Warning: Only one-file diffs are allowed.
|
||||
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) string {
|
||||
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) (string, error) {
|
||||
if line == 0 || numbersOfLine == 0 {
|
||||
// no line or num of lines => no diff
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(originalDiff)
|
||||
hunk := make([]string, 0)
|
||||
|
||||
// begin is the start of the hunk containing searched line
|
||||
// end is the end of the hunk ...
|
||||
// currentLine is the line number on the side of the searched line (differentiated by old)
|
||||
// otherLine is the line number on the opposite side of the searched line (differentiated by old)
|
||||
var begin, end, currentLine, otherLine int64
|
||||
var headerLines int
|
||||
|
||||
inHunk := false
|
||||
|
||||
for scanner.Scan() {
|
||||
lof := scanner.Text()
|
||||
// Add header to enable parsing
|
||||
if isHeader(lof) {
|
||||
|
||||
if isHeader(lof, inHunk) {
|
||||
if strings.HasPrefix(lof, cmdDiffHead) {
|
||||
inHunk = false
|
||||
}
|
||||
hunk = append(hunk, lof)
|
||||
headerLines++
|
||||
}
|
||||
@@ -157,6 +166,7 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
||||
}
|
||||
// Detect "hunk" with contains commented lof
|
||||
if strings.HasPrefix(lof, "@@") {
|
||||
inHunk = true
|
||||
// Already got our hunk. End of hunk detected!
|
||||
if len(hunk) > headerLines {
|
||||
break
|
||||
@@ -213,15 +223,19 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
||||
}
|
||||
}
|
||||
}
|
||||
err := scanner.Err()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// No hunk found
|
||||
if currentLine == 0 {
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
// headerLines + hunkLine (1) = totalNonCodeLines
|
||||
if len(hunk)-headerLines-1 <= numbersOfLine {
|
||||
// No need to cut the hunk => return existing hunk
|
||||
return strings.Join(hunk, "\n")
|
||||
return strings.Join(hunk, "\n"), nil
|
||||
}
|
||||
var oldBegin, oldNumOfLines, newBegin, newNumOfLines int64
|
||||
if old {
|
||||
@@ -256,5 +270,5 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
||||
// construct the new hunk header
|
||||
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
|
||||
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
|
||||
return strings.Join(newHunk, "\n")
|
||||
return strings.Join(newHunk, "\n"), nil
|
||||
}
|
||||
|
||||
@@ -23,8 +23,28 @@ const exampleDiff = `diff --git a/README.md b/README.md
|
||||
+ cut off
|
||||
+ cut off`
|
||||
|
||||
const breakingDiff = `diff --git a/aaa.sql b/aaa.sql
|
||||
index d8e4c92..19dc8ad 100644
|
||||
--- a/aaa.sql
|
||||
+++ b/aaa.sql
|
||||
@@ -1,9 +1,10 @@
|
||||
--some comment
|
||||
--- some comment 5
|
||||
+--some coment 2
|
||||
+-- some comment 3
|
||||
create or replace procedure test(p1 varchar2)
|
||||
is
|
||||
begin
|
||||
---new comment
|
||||
dbms_output.put_line(p1);
|
||||
+--some other comment
|
||||
end;
|
||||
/
|
||||
`
|
||||
|
||||
func TestCutDiffAroundLine(t *testing.T) {
|
||||
result := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
|
||||
result, err := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
|
||||
assert.NoError(t, err)
|
||||
resultByLine := strings.Split(result, "\n")
|
||||
assert.Len(t, resultByLine, 7)
|
||||
// Check if headers got transferred
|
||||
@@ -37,18 +57,50 @@ func TestCutDiffAroundLine(t *testing.T) {
|
||||
assert.Equal(t, "+ Build Status", resultByLine[4])
|
||||
|
||||
// Must be same result as before since old line 3 == new line 5
|
||||
newResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
|
||||
newResult, err := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, result, newResult, "Must be same result as before since old line 3 == new line 5")
|
||||
|
||||
newResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
|
||||
newResult, err = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, exampleDiff, newResult)
|
||||
|
||||
emptyResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
|
||||
emptyResult, err := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, emptyResult)
|
||||
|
||||
// Line is out of scope
|
||||
emptyResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
|
||||
emptyResult, err = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, emptyResult)
|
||||
|
||||
// Handle minus diffs properly
|
||||
minusDiff, err := CutDiffAroundLine(strings.NewReader(breakingDiff), 2, false, 4)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := `diff --git a/aaa.sql b/aaa.sql
|
||||
--- a/aaa.sql
|
||||
+++ b/aaa.sql
|
||||
@@ -1,9 +1,10 @@
|
||||
--some comment
|
||||
--- some comment 5
|
||||
+--some coment 2`
|
||||
assert.Equal(t, expected, minusDiff)
|
||||
|
||||
// Handle minus diffs properly
|
||||
minusDiff, err = CutDiffAroundLine(strings.NewReader(breakingDiff), 3, false, 4)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected = `diff --git a/aaa.sql b/aaa.sql
|
||||
--- a/aaa.sql
|
||||
+++ b/aaa.sql
|
||||
@@ -1,9 +1,10 @@
|
||||
--some comment
|
||||
--- some comment 5
|
||||
+--some coment 2
|
||||
+-- some comment 3`
|
||||
|
||||
assert.Equal(t, expected, minusDiff)
|
||||
}
|
||||
|
||||
func BenchmarkCutDiffAroundLine(b *testing.B) {
|
||||
@@ -69,7 +121,7 @@ func ExampleCutDiffAroundLine() {
|
||||
Docker Pulls
|
||||
+ cut off
|
||||
+ cut off`
|
||||
result := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
|
||||
result, _ := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
|
||||
println(result)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -129,19 +131,23 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
|
||||
|
||||
// ConvertToSHA1 returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
|
||||
if len(commitID) != 40 {
|
||||
var err error
|
||||
actualCommitID, err := NewCommand("rev-parse", "--verify", commitID).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unknown revision or path") ||
|
||||
strings.Contains(err.Error(), "fatal: Needed a single revision") {
|
||||
return SHA1{}, ErrNotExist{commitID, ""}
|
||||
}
|
||||
return SHA1{}, err
|
||||
if len(commitID) == 40 {
|
||||
sha1, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return sha1, nil
|
||||
}
|
||||
commitID = actualCommitID
|
||||
}
|
||||
return NewIDFromString(commitID)
|
||||
|
||||
actualCommitID, err := NewCommand("rev-parse", "--verify", commitID).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unknown revision or path") ||
|
||||
strings.Contains(err.Error(), "fatal: Needed a single revision") {
|
||||
return SHA1{}, ErrNotExist{commitID, ""}
|
||||
}
|
||||
return SHA1{}, err
|
||||
}
|
||||
|
||||
return NewIDFromString(actualCommitID)
|
||||
}
|
||||
|
||||
// GetCommit returns commit object of by ID string.
|
||||
@@ -323,8 +329,41 @@ func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
|
||||
|
||||
// CommitsByFileAndRange return the commits according revison file and the page
|
||||
func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) {
|
||||
stdout, err := NewCommand("log", revision, "--follow", "--skip="+strconv.Itoa((page-1)*50),
|
||||
"--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path)
|
||||
skip := (page - 1) * CommitsRangeSize
|
||||
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
defer func() {
|
||||
_ = stdoutReader.Close()
|
||||
_ = stdoutWriter.Close()
|
||||
}()
|
||||
go func() {
|
||||
stderr := strings.Builder{}
|
||||
err := NewCommand("log", revision, "--follow",
|
||||
"--max-count="+strconv.Itoa(CommitsRangeSize*page),
|
||||
prettyLogFormat, "--", file).
|
||||
RunInDirPipeline(repo.Path, stdoutWriter, &stderr)
|
||||
if err != nil {
|
||||
if stderr.Len() > 0 {
|
||||
err = fmt.Errorf("%v - %s", err, stderr.String())
|
||||
}
|
||||
_ = stdoutWriter.CloseWithError(err)
|
||||
} else {
|
||||
_ = stdoutWriter.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if skip > 0 {
|
||||
_, err := io.CopyN(ioutil.Discard, stdoutReader, int64(skip*41))
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return list.New(), nil
|
||||
}
|
||||
_ = stdoutReader.CloseWithError(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := ioutil.ReadAll(stdoutReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
@@ -66,15 +67,20 @@ func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadC
|
||||
|
||||
// Put takes a Meta object and an io.Reader and writes the content to the store.
|
||||
func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
|
||||
hash := sha256.New()
|
||||
rd := io.TeeReader(r, hash)
|
||||
p := meta.RelativePath()
|
||||
written, err := s.Save(p, rd)
|
||||
|
||||
// Wrap the provided reader with an inline hashing and size checker
|
||||
wrappedRd := newHashingReader(meta.Size, meta.Oid, r)
|
||||
|
||||
// now pass the wrapped reader to Save - if there is a size mismatch or hash mismatch then
|
||||
// the errors returned by the newHashingReader should percolate up to here
|
||||
written, err := s.Save(p, wrappedRd)
|
||||
if err != nil {
|
||||
log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, p, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// This shouldn't happen but it is sensible to test
|
||||
if written != meta.Size {
|
||||
if err := s.Delete(p); err != nil {
|
||||
log.Error("Cleaning the LFS OID[%s] failed: %v", meta.Oid, err)
|
||||
@@ -82,14 +88,6 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
|
||||
return errSizeMismatch
|
||||
}
|
||||
|
||||
shaStr := hex.EncodeToString(hash.Sum(nil))
|
||||
if shaStr != meta.Oid {
|
||||
if err := s.Delete(p); err != nil {
|
||||
log.Error("Cleaning the LFS OID[%s] failed: %v", meta.Oid, err)
|
||||
}
|
||||
return errHashMismatch
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,3 +116,45 @@ func (s *ContentStore) Verify(meta *models.LFSMetaObject) (bool, error) {
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type hashingReader struct {
|
||||
internal io.Reader
|
||||
currentSize int64
|
||||
expectedSize int64
|
||||
hash hash.Hash
|
||||
expectedHash string
|
||||
}
|
||||
|
||||
func (r *hashingReader) Read(b []byte) (int, error) {
|
||||
n, err := r.internal.Read(b)
|
||||
|
||||
if n > 0 {
|
||||
r.currentSize += int64(n)
|
||||
wn, werr := r.hash.Write(b[:n])
|
||||
if wn != n || werr != nil {
|
||||
return n, werr
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && err == io.EOF {
|
||||
if r.currentSize != r.expectedSize {
|
||||
return n, errSizeMismatch
|
||||
}
|
||||
|
||||
shaStr := hex.EncodeToString(r.hash.Sum(nil))
|
||||
if shaStr != r.expectedHash {
|
||||
return n, errHashMismatch
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func newHashingReader(expectedSize int64, expectedHash string, reader io.Reader) *hashingReader {
|
||||
return &hashingReader{
|
||||
internal: reader,
|
||||
expectedSize: expectedSize,
|
||||
expectedHash: expectedHash,
|
||||
hash: sha256.New(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ var (
|
||||
// sha1CurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
|
||||
// Although SHA1 hashes are 40 chars long, the regex matches the hash from 7 to 40 chars in length
|
||||
// so that abbreviated hash links can be used as well. This matches git and github useability.
|
||||
sha1CurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,40})(?:\s|$|\)|\]|\.(\s|$))`)
|
||||
sha1CurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,40})(?:\s|$|\)|\]|[.,](\s|$))`)
|
||||
|
||||
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
|
||||
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
||||
@@ -298,9 +298,6 @@ func RenderEmoji(
|
||||
return ctx.postProcess(rawHTML)
|
||||
}
|
||||
|
||||
var byteBodyTag = []byte("<body>")
|
||||
var byteBodyTagClosing = []byte("</body>")
|
||||
|
||||
func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
|
||||
if ctx.procs == nil {
|
||||
ctx.procs = defaultProcessors
|
||||
@@ -308,9 +305,9 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
|
||||
|
||||
// give a generous extra 50 bytes
|
||||
res := make([]byte, 0, len(rawHTML)+50)
|
||||
res = append(res, byteBodyTag...)
|
||||
res = append(res, "<html><body>"...)
|
||||
res = append(res, rawHTML...)
|
||||
res = append(res, byteBodyTagClosing...)
|
||||
res = append(res, "</body></html>"...)
|
||||
|
||||
// parse the HTML
|
||||
nodes, err := html.ParseFragment(bytes.NewReader(res), nil)
|
||||
@@ -322,6 +319,31 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
|
||||
ctx.visitNode(node, true)
|
||||
}
|
||||
|
||||
newNodes := make([]*html.Node, 0, len(nodes))
|
||||
|
||||
for _, node := range nodes {
|
||||
if node.Data == "html" {
|
||||
node = node.FirstChild
|
||||
for node != nil && node.Data != "body" {
|
||||
node = node.NextSibling
|
||||
}
|
||||
}
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
if node.Data == "body" {
|
||||
child := node.FirstChild
|
||||
for child != nil {
|
||||
newNodes = append(newNodes, child)
|
||||
child = child.NextSibling
|
||||
}
|
||||
} else {
|
||||
newNodes = append(newNodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
nodes = newNodes
|
||||
|
||||
// Create buffer in which the data will be placed again. We know that the
|
||||
// length will be at least that of res; to spare a few alloc+copy, we
|
||||
// reuse res, resetting its length to 0.
|
||||
@@ -334,12 +356,8 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// remove initial parts - because Render creates a whole HTML page.
|
||||
res = buf.Bytes()
|
||||
res = res[bytes.Index(res, byteBodyTag)+len(byteBodyTag) : bytes.LastIndex(res, byteBodyTagClosing)]
|
||||
|
||||
// Everything done successfully, return parsed data.
|
||||
return res, nil
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (ctx *postProcessCtx) visitNode(node *html.Node, visitText bool) {
|
||||
|
||||
@@ -46,6 +46,12 @@ func TestRender_Commits(t *testing.T) {
|
||||
test("/home/gitea/"+sha, "<p>/home/gitea/"+sha+"</p>")
|
||||
test("deadbeef", `<p>deadbeef</p>`)
|
||||
test("d27ace93", `<p>d27ace93</p>`)
|
||||
test(sha[:14]+".x", `<p>`+sha[:14]+`.x</p>`)
|
||||
|
||||
expected14 := `<a href="` + commit[:len(commit)-(40-14)] + `" rel="nofollow"><code>` + sha[:10] + `</code></a>`
|
||||
test(sha[:14]+".", `<p>`+expected14+`.</p>`)
|
||||
test(sha[:14]+",", `<p>`+expected14+`,</p>`)
|
||||
test("["+sha[:14]+"]", `<p>[`+expected14+`]</p>`)
|
||||
}
|
||||
|
||||
func TestRender_CrossReferences(t *testing.T) {
|
||||
@@ -377,3 +383,28 @@ func TestRender_ShortLinks(t *testing.T) {
|
||||
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
|
||||
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
|
||||
}
|
||||
|
||||
func Test_ParseClusterFuzz(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
var localMetas = map[string]string{
|
||||
"user": "go-gitea",
|
||||
"repo": "gitea",
|
||||
}
|
||||
|
||||
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
||||
|
||||
val, err := PostProcess([]byte(data), "https://example.com", localMetas, false)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, string(val), "<html")
|
||||
|
||||
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
|
||||
|
||||
val, err = PostProcess([]byte(data), "https://example.com", localMetas, false)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotContains(t, string(val), "<html")
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -811,13 +810,20 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
|
||||
}
|
||||
|
||||
var patch string
|
||||
patchBuf := new(bytes.Buffer)
|
||||
if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, patchBuf); err != nil {
|
||||
// We should ignore the error since the commit maybe removed when force push to the pull request
|
||||
log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
|
||||
} else {
|
||||
patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
|
||||
}
|
||||
reader, writer := io.Pipe()
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
_ = writer.Close()
|
||||
}()
|
||||
go func() {
|
||||
if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, writer); err != nil {
|
||||
// We should ignore the error since the commit maybe removed when force push to the pull request
|
||||
log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
|
||||
}
|
||||
_ = writer.Close()
|
||||
}()
|
||||
|
||||
patch, _ = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
|
||||
|
||||
var c = models.Comment{
|
||||
Type: models.CommentTypeCode,
|
||||
|
||||
@@ -29,7 +29,7 @@ func NewNotifier() base.Notifier {
|
||||
return &actionNotifier{}
|
||||
}
|
||||
|
||||
func (a *actionNotifier) NotifyNewIssue(issue *models.Issue) {
|
||||
func (a *actionNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
if err := issue.LoadPoster(); err != nil {
|
||||
log.Error("issue.LoadPoster: %v", err)
|
||||
return
|
||||
@@ -88,7 +88,7 @@ func (a *actionNotifier) NotifyIssueChangeStatus(doer *models.User, issue *model
|
||||
|
||||
// NotifyCreateIssueComment notifies comment on an issue to notifiers
|
||||
func (a *actionNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
act := &models.Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUser: doer,
|
||||
@@ -120,7 +120,7 @@ func (a *actionNotifier) NotifyCreateIssueComment(doer *models.User, repo *model
|
||||
}
|
||||
}
|
||||
|
||||
func (a *actionNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
|
||||
func (a *actionNotifier) NotifyNewPullRequest(pull *models.PullRequest, mentions []*models.User) {
|
||||
if err := pull.LoadIssue(); err != nil {
|
||||
log.Error("pull.LoadIssue: %v", err)
|
||||
return
|
||||
@@ -203,7 +203,7 @@ func (a *actionNotifier) NotifyForkRepository(doer *models.User, oldRepo, repo *
|
||||
}
|
||||
}
|
||||
|
||||
func (a *actionNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
|
||||
func (a *actionNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User) {
|
||||
if err := review.LoadReviewer(); err != nil {
|
||||
log.Error("LoadReviewer '%d/%d': %v", review.ID, review.ReviewerID, err)
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ type Notifier interface {
|
||||
NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string)
|
||||
NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string)
|
||||
|
||||
NotifyNewIssue(*models.Issue)
|
||||
NotifyNewIssue(issue *models.Issue, mentions []*models.User)
|
||||
NotifyIssueChangeStatus(*models.User, *models.Issue, *models.Comment, bool)
|
||||
NotifyIssueChangeMilestone(doer *models.User, issue *models.Issue, oldMilestoneID int64)
|
||||
NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment)
|
||||
@@ -32,15 +32,16 @@ type Notifier interface {
|
||||
NotifyIssueChangeLabels(doer *models.User, issue *models.Issue,
|
||||
addedLabels []*models.Label, removedLabels []*models.Label)
|
||||
|
||||
NotifyNewPullRequest(*models.PullRequest)
|
||||
NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User)
|
||||
NotifyMergePullRequest(*models.PullRequest, *models.User)
|
||||
NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest)
|
||||
NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment)
|
||||
NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User)
|
||||
NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User)
|
||||
NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string)
|
||||
NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment)
|
||||
|
||||
NotifyCreateIssueComment(*models.User, *models.Repository,
|
||||
*models.Issue, *models.Comment)
|
||||
NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User)
|
||||
NotifyUpdateComment(*models.User, *models.Comment, string)
|
||||
NotifyDeleteComment(*models.User, *models.Comment)
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ func (*NullNotifier) Run() {
|
||||
|
||||
// NotifyCreateIssueComment places a place holder function
|
||||
func (*NullNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
}
|
||||
|
||||
// NotifyNewIssue places a place holder function
|
||||
func (*NullNotifier) NotifyNewIssue(issue *models.Issue) {
|
||||
func (*NullNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
}
|
||||
|
||||
// NotifyIssueChangeStatus places a place holder function
|
||||
@@ -35,11 +35,15 @@ func (*NullNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Is
|
||||
}
|
||||
|
||||
// NotifyNewPullRequest places a place holder function
|
||||
func (*NullNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
|
||||
func (*NullNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
|
||||
}
|
||||
|
||||
// NotifyPullRequestReview places a place holder function
|
||||
func (*NullNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment) {
|
||||
func (*NullNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment, mentions []*models.User) {
|
||||
}
|
||||
|
||||
// NotifyPullRequestCodeComment places a place holder function
|
||||
func (*NullNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
|
||||
}
|
||||
|
||||
// NotifyMergePullRequest places a place holder function
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewNotifier() base.Notifier {
|
||||
}
|
||||
|
||||
func (r *indexerNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
if comment.Type == models.CommentTypeComment {
|
||||
if issue.Comments == nil {
|
||||
if err := issue.LoadDiscussComments(); err != nil {
|
||||
@@ -45,11 +45,11 @@ func (r *indexerNotifier) NotifyCreateIssueComment(doer *models.User, repo *mode
|
||||
}
|
||||
}
|
||||
|
||||
func (r *indexerNotifier) NotifyNewIssue(issue *models.Issue) {
|
||||
func (r *indexerNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
issue_indexer.UpdateIssueIndexer(issue)
|
||||
}
|
||||
|
||||
func (r *indexerNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
|
||||
func (r *indexerNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
|
||||
issue_indexer.UpdateIssueIndexer(pr.Issue)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func NewNotifier() base.Notifier {
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
var act models.ActionType
|
||||
if comment.Type == models.CommentTypeClose {
|
||||
act = models.ActionCloseIssue
|
||||
@@ -41,13 +41,13 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.
|
||||
act = 0
|
||||
}
|
||||
|
||||
if err := mailer.MailParticipantsComment(comment, act, issue); err != nil {
|
||||
if err := mailer.MailParticipantsComment(comment, act, issue, mentions); err != nil {
|
||||
log.Error("MailParticipantsComment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) {
|
||||
if err := mailer.MailParticipants(issue, issue.Poster, models.ActionCreateIssue); err != nil {
|
||||
func (m *mailNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
if err := mailer.MailParticipants(issue, issue.Poster, models.ActionCreateIssue, mentions); err != nil {
|
||||
log.Error("MailParticipants: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -69,18 +69,18 @@ func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.
|
||||
}
|
||||
}
|
||||
|
||||
if err := mailer.MailParticipants(issue, doer, actionType); err != nil {
|
||||
if err := mailer.MailParticipants(issue, doer, actionType, nil); err != nil {
|
||||
log.Error("MailParticipants: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
|
||||
if err := mailer.MailParticipants(pr.Issue, pr.Issue.Poster, models.ActionCreatePullRequest); err != nil {
|
||||
func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
|
||||
if err := mailer.MailParticipants(pr.Issue, pr.Issue.Poster, models.ActionCreatePullRequest, mentions); err != nil {
|
||||
log.Error("MailParticipants: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment) {
|
||||
func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, comment *models.Comment, mentions []*models.User) {
|
||||
var act models.ActionType
|
||||
if comment.Type == models.CommentTypeClose {
|
||||
act = models.ActionCloseIssue
|
||||
@@ -89,11 +89,17 @@ func (m *mailNotifier) NotifyPullRequestReview(pr *models.PullRequest, r *models
|
||||
} else if comment.Type == models.CommentTypeComment {
|
||||
act = models.ActionCommentPull
|
||||
}
|
||||
if err := mailer.MailParticipantsComment(comment, act, pr.Issue); err != nil {
|
||||
if err := mailer.MailParticipantsComment(comment, act, pr.Issue, mentions); err != nil {
|
||||
log.Error("MailParticipantsComment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
|
||||
if err := mailer.MailMentionsComment(pr, comment, mentions); err != nil {
|
||||
log.Error("MailMentionsComment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) {
|
||||
// mail only sent to added assignees and not self-assignee
|
||||
if !removed && doer.ID != assignee.ID && assignee.EmailNotifications() == models.EmailNotificationsEnabled {
|
||||
@@ -115,7 +121,7 @@ func (m *mailNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mode
|
||||
return
|
||||
}
|
||||
pr.Issue.Content = ""
|
||||
if err := mailer.MailParticipants(pr.Issue, doer, models.ActionMergePullRequest); err != nil {
|
||||
if err := mailer.MailParticipants(pr.Issue, doer, models.ActionMergePullRequest, nil); err != nil {
|
||||
log.Error("MailParticipants: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +149,7 @@ func (m *mailNotifier) NotifyPullRequestPushCommits(doer *models.User, pr *model
|
||||
}
|
||||
comment.Content = ""
|
||||
|
||||
m.NotifyCreateIssueComment(doer, comment.Issue.Repo, comment.Issue, comment)
|
||||
m.NotifyCreateIssueComment(doer, comment.Issue.Repo, comment.Issue, comment, nil)
|
||||
}
|
||||
|
||||
func (m *mailNotifier) NotifyNewRelease(rel *models.Release) {
|
||||
|
||||
@@ -39,16 +39,16 @@ func NewContext() {
|
||||
|
||||
// NotifyCreateIssueComment notifies issue comment related message to notifiers
|
||||
func NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyCreateIssueComment(doer, repo, issue, comment)
|
||||
notifier.NotifyCreateIssueComment(doer, repo, issue, comment, mentions)
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyNewIssue notifies new issue to notifiers
|
||||
func NotifyNewIssue(issue *models.Issue) {
|
||||
func NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyNewIssue(issue)
|
||||
notifier.NotifyNewIssue(issue, mentions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ func NotifyMergePullRequest(pr *models.PullRequest, doer *models.User) {
|
||||
}
|
||||
|
||||
// NotifyNewPullRequest notifies new pull request to notifiers
|
||||
func NotifyNewPullRequest(pr *models.PullRequest) {
|
||||
func NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyNewPullRequest(pr)
|
||||
notifier.NotifyNewPullRequest(pr, mentions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,16 @@ func NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) {
|
||||
}
|
||||
|
||||
// NotifyPullRequestReview notifies new pull request review
|
||||
func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
|
||||
func NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyPullRequestReview(pr, review, comment)
|
||||
notifier.NotifyPullRequestReview(pr, review, comment, mentions)
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyPullRequestCodeComment notifies new pull request code comment
|
||||
func NotifyPullRequestCodeComment(pr *models.PullRequest, comment *models.Comment, mentions []*models.User) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.NotifyPullRequestCodeComment(pr, comment, mentions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func (ns *notificationService) Run() {
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
var opts = issueNotificationOpts{
|
||||
IssueID: issue.ID,
|
||||
NotificationAuthorID: doer.ID,
|
||||
@@ -60,13 +60,31 @@ func (ns *notificationService) NotifyCreateIssueComment(doer *models.User, repo
|
||||
opts.CommentID = comment.ID
|
||||
}
|
||||
_ = ns.issueQueue.Push(opts)
|
||||
for _, mention := range mentions {
|
||||
var opts = issueNotificationOpts{
|
||||
IssueID: issue.ID,
|
||||
NotificationAuthorID: doer.ID,
|
||||
ReceiverID: mention.ID,
|
||||
}
|
||||
if comment != nil {
|
||||
opts.CommentID = comment.ID
|
||||
}
|
||||
_ = ns.issueQueue.Push(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyNewIssue(issue *models.Issue) {
|
||||
func (ns *notificationService) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||
IssueID: issue.ID,
|
||||
NotificationAuthorID: issue.Poster.ID,
|
||||
})
|
||||
for _, mention := range mentions {
|
||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||
IssueID: issue.ID,
|
||||
NotificationAuthorID: issue.Poster.ID,
|
||||
ReceiverID: mention.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, actionComment *models.Comment, isClosed bool) {
|
||||
@@ -83,7 +101,7 @@ func (ns *notificationService) NotifyMergePullRequest(pr *models.PullRequest, do
|
||||
})
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest) {
|
||||
func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest, mentions []*models.User) {
|
||||
if err := pr.LoadIssue(); err != nil {
|
||||
log.Error("Unable to load issue: %d for pr: %d: Error: %v", pr.IssueID, pr.ID, err)
|
||||
return
|
||||
@@ -92,9 +110,16 @@ func (ns *notificationService) NotifyNewPullRequest(pr *models.PullRequest) {
|
||||
IssueID: pr.Issue.ID,
|
||||
NotificationAuthorID: pr.Issue.PosterID,
|
||||
})
|
||||
for _, mention := range mentions {
|
||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||
IssueID: pr.Issue.ID,
|
||||
NotificationAuthorID: pr.Issue.PosterID,
|
||||
ReceiverID: mention.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, c *models.Comment) {
|
||||
func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r *models.Review, c *models.Comment, mentions []*models.User) {
|
||||
var opts = issueNotificationOpts{
|
||||
IssueID: pr.Issue.ID,
|
||||
NotificationAuthorID: r.Reviewer.ID,
|
||||
@@ -103,6 +128,28 @@ func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r
|
||||
opts.CommentID = c.ID
|
||||
}
|
||||
_ = ns.issueQueue.Push(opts)
|
||||
for _, mention := range mentions {
|
||||
var opts = issueNotificationOpts{
|
||||
IssueID: pr.Issue.ID,
|
||||
NotificationAuthorID: r.Reviewer.ID,
|
||||
ReceiverID: mention.ID,
|
||||
}
|
||||
if c != nil {
|
||||
opts.CommentID = c.ID
|
||||
}
|
||||
_ = ns.issueQueue.Push(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyPullRequestCodeComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) {
|
||||
for _, mention := range mentions {
|
||||
_ = ns.issueQueue.Push(issueNotificationOpts{
|
||||
IssueID: pr.Issue.ID,
|
||||
NotificationAuthorID: c.Poster.ID,
|
||||
CommentID: c.ID,
|
||||
ReceiverID: mention.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *notificationService) NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) {
|
||||
|
||||
@@ -249,7 +249,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *models.User, issue *mode
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
|
||||
func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue, mentions []*models.User) {
|
||||
if err := issue.LoadRepo(); err != nil {
|
||||
log.Error("issue.LoadRepo: %v", err)
|
||||
return
|
||||
@@ -271,7 +271,7 @@ func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest) {
|
||||
func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest, mentions []*models.User) {
|
||||
if err := pull.LoadIssue(); err != nil {
|
||||
log.Error("pull.LoadIssue: %v", err)
|
||||
return
|
||||
@@ -387,7 +387,7 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *models.User, c *models.Comme
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.Repository,
|
||||
issue *models.Issue, comment *models.Comment) {
|
||||
issue *models.Issue, comment *models.Comment, mentions []*models.User) {
|
||||
mode, _ := models.AccessLevel(doer, repo)
|
||||
|
||||
var err error
|
||||
@@ -639,7 +639,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment) {
|
||||
func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review *models.Review, comment *models.Comment, mentions []*models.User) {
|
||||
var reviewHookType models.HookEventType
|
||||
|
||||
switch review.Type {
|
||||
|
||||
@@ -38,6 +38,7 @@ var KnownPublicEntries = []string{
|
||||
"js",
|
||||
"serviceworker.js",
|
||||
"vendor",
|
||||
"favicon.ico",
|
||||
}
|
||||
|
||||
// Custom implements the macaron static handler for serving custom assets.
|
||||
|
||||
@@ -149,6 +149,11 @@ func (q *PersistableChannelUniqueQueue) Has(data Data) (bool, error) {
|
||||
if err != nil || has {
|
||||
return has, err
|
||||
}
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
if q.internal == nil {
|
||||
return false, nil
|
||||
}
|
||||
return q.internal.(UniqueQueue).Has(data)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
|
||||
opts.DefaultBranch = setting.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
// Check if label template exist
|
||||
if len(opts.IssueLabels) > 0 {
|
||||
if _, err := models.GetLabelTemplateFile(opts.IssueLabels); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
repo := &models.Repository{
|
||||
OwnerID: u.ID,
|
||||
Owner: u,
|
||||
@@ -47,6 +54,8 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
|
||||
TrustModel: opts.TrustModel,
|
||||
}
|
||||
|
||||
var rollbackRepo *models.Repository
|
||||
|
||||
if err := models.WithTx(func(ctx models.DBContext) error {
|
||||
if err := models.CreateRepository(ctx, doer, u, repo, false); err != nil {
|
||||
return err
|
||||
@@ -85,9 +94,8 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
|
||||
// Initialize Issue Labels if selected
|
||||
if len(opts.IssueLabels) > 0 {
|
||||
if err := models.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil {
|
||||
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
|
||||
log.Error("Rollback deleteRepository: %v", errDelete)
|
||||
}
|
||||
rollbackRepo = repo
|
||||
rollbackRepo.OwnerID = u.ID
|
||||
return fmt.Errorf("InitializeLabels: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -96,13 +104,18 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
|
||||
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
|
||||
RunInDir(repoPath); err != nil {
|
||||
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
|
||||
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
|
||||
log.Error("Rollback deleteRepository: %v", errDelete)
|
||||
}
|
||||
rollbackRepo = repo
|
||||
rollbackRepo.OwnerID = u.ID
|
||||
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
if rollbackRepo != nil {
|
||||
if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil {
|
||||
log.Error("Rollback deleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -771,7 +771,7 @@ func NewContext() {
|
||||
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
|
||||
DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true)
|
||||
OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true)
|
||||
PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("argon2")
|
||||
PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2")
|
||||
CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
|
||||
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
|
||||
|
||||
|
||||
@@ -196,13 +196,17 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||
|
||||
// Listen starts a SSH server listens on given port.
|
||||
func Listen(host string, port int, ciphers []string, keyExchanges []string, macs []string) {
|
||||
// TODO: Handle ciphers, keyExchanges, and macs
|
||||
|
||||
srv := ssh.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||
PublicKeyHandler: publicKeyHandler,
|
||||
Handler: sessionHandler,
|
||||
|
||||
ServerConfigCallback: func(ctx ssh.Context) *gossh.ServerConfig {
|
||||
config := &gossh.ServerConfig{}
|
||||
config.KeyExchanges = keyExchanges
|
||||
config.MACs = macs
|
||||
config.Ciphers = ciphers
|
||||
return config
|
||||
},
|
||||
// We need to explicitly disable the PtyCallback so text displays
|
||||
// properly.
|
||||
PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool {
|
||||
|
||||
@@ -7,6 +7,7 @@ package storage
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -24,13 +25,15 @@ const LocalStorageType Type = "local"
|
||||
|
||||
// LocalStorageConfig represents the configuration for a local storage
|
||||
type LocalStorageConfig struct {
|
||||
Path string `ini:"PATH"`
|
||||
Path string `ini:"PATH"`
|
||||
TemporaryPath string `ini:"TEMPORARY_PATH"`
|
||||
}
|
||||
|
||||
// LocalStorage represents a local files storage
|
||||
type LocalStorage struct {
|
||||
ctx context.Context
|
||||
dir string
|
||||
ctx context.Context
|
||||
dir string
|
||||
tmpdir string
|
||||
}
|
||||
|
||||
// NewLocalStorage returns a local files
|
||||
@@ -45,9 +48,14 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.TemporaryPath == "" {
|
||||
config.TemporaryPath = config.Path + "/tmp"
|
||||
}
|
||||
|
||||
return &LocalStorage{
|
||||
ctx: ctx,
|
||||
dir: config.Path,
|
||||
ctx: ctx,
|
||||
dir: config.Path,
|
||||
tmpdir: config.TemporaryPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -63,17 +71,37 @@ func (l *LocalStorage) Save(path string, r io.Reader) (int64, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// always override
|
||||
if err := util.Remove(p); err != nil {
|
||||
// Create a temporary file to save to
|
||||
if err := os.MkdirAll(l.tmpdir, os.ModePerm); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
f, err := os.Create(p)
|
||||
tmp, err := ioutil.TempFile(l.tmpdir, "upload-*")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
return io.Copy(f, r)
|
||||
tmpRemoved := false
|
||||
defer func() {
|
||||
if !tmpRemoved {
|
||||
_ = util.Remove(tmp.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
n, err := io.Copy(tmp, r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := tmp.Close(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := os.Rename(tmp.Name(), p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tmpRemoved = true
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Stat returns the info of the file
|
||||
|
||||
@@ -105,7 +105,7 @@ type CreateRepoOption struct {
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
// Whether the repository is private
|
||||
Private bool `json:"private"`
|
||||
// Issue Label set to use
|
||||
// Label-Set to use
|
||||
IssueLabels string `json:"issue_labels"`
|
||||
// Whether the repository should be auto-intialized?
|
||||
AutoInit bool `json:"auto_init"`
|
||||
|
||||
@@ -689,6 +689,11 @@ func ActionIcon(opType models.ActionType) string {
|
||||
// ActionContent2Commits converts action content to push commits
|
||||
func ActionContent2Commits(act Actioner) *repository.PushCommits {
|
||||
push := repository.NewPushCommits()
|
||||
|
||||
if act == nil || act.GetContent() == "" {
|
||||
return push
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
|
||||
log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package timeutil
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,7 +26,11 @@ const (
|
||||
Year = 12 * Month
|
||||
)
|
||||
|
||||
func computeTimeDiff(diff int64, lang string) (int64, string) {
|
||||
func round(s float64) int64 {
|
||||
return int64(math.Round(s))
|
||||
}
|
||||
|
||||
func computeTimeDiffFloor(diff int64, lang string) (int64, string) {
|
||||
diffStr := ""
|
||||
switch {
|
||||
case diff <= 0:
|
||||
@@ -83,6 +88,94 @@ func computeTimeDiff(diff int64, lang string) (int64, string) {
|
||||
return diff, diffStr
|
||||
}
|
||||
|
||||
func computeTimeDiff(diff int64, lang string) (int64, string) {
|
||||
diffStr := ""
|
||||
switch {
|
||||
case diff <= 0:
|
||||
diff = 0
|
||||
diffStr = i18n.Tr(lang, "tool.now")
|
||||
case diff < 2:
|
||||
diff = 0
|
||||
diffStr = i18n.Tr(lang, "tool.1s")
|
||||
case diff < 1*Minute:
|
||||
diffStr = i18n.Tr(lang, "tool.seconds", diff)
|
||||
diff = 0
|
||||
|
||||
case diff < Minute+Minute/2:
|
||||
diff -= 1 * Minute
|
||||
diffStr = i18n.Tr(lang, "tool.1m")
|
||||
case diff < 1*Hour:
|
||||
minutes := round(float64(diff) / Minute)
|
||||
if minutes > 1 {
|
||||
diffStr = i18n.Tr(lang, "tool.minutes", minutes)
|
||||
} else {
|
||||
diffStr = i18n.Tr(lang, "tool.1m")
|
||||
}
|
||||
diff -= diff / Minute * Minute
|
||||
|
||||
case diff < Hour+Hour/2:
|
||||
diff -= 1 * Hour
|
||||
diffStr = i18n.Tr(lang, "tool.1h")
|
||||
case diff < 1*Day:
|
||||
hours := round(float64(diff) / Hour)
|
||||
if hours > 1 {
|
||||
diffStr = i18n.Tr(lang, "tool.hours", hours)
|
||||
} else {
|
||||
diffStr = i18n.Tr(lang, "tool.1h")
|
||||
}
|
||||
diff -= diff / Hour * Hour
|
||||
|
||||
case diff < Day+Day/2:
|
||||
diff -= 1 * Day
|
||||
diffStr = i18n.Tr(lang, "tool.1d")
|
||||
case diff < 1*Week:
|
||||
days := round(float64(diff) / Day)
|
||||
if days > 1 {
|
||||
diffStr = i18n.Tr(lang, "tool.days", days)
|
||||
} else {
|
||||
diffStr = i18n.Tr(lang, "tool.1d")
|
||||
}
|
||||
diff -= diff / Day * Day
|
||||
|
||||
case diff < Week+Week/2:
|
||||
diff -= 1 * Week
|
||||
diffStr = i18n.Tr(lang, "tool.1w")
|
||||
case diff < 1*Month:
|
||||
weeks := round(float64(diff) / Week)
|
||||
if weeks > 1 {
|
||||
diffStr = i18n.Tr(lang, "tool.weeks", weeks)
|
||||
} else {
|
||||
diffStr = i18n.Tr(lang, "tool.1w")
|
||||
}
|
||||
diff -= diff / Week * Week
|
||||
|
||||
case diff < 1*Month+Month/2:
|
||||
diff -= 1 * Month
|
||||
diffStr = i18n.Tr(lang, "tool.1mon")
|
||||
case diff < 1*Year:
|
||||
months := round(float64(diff) / Month)
|
||||
if months > 1 {
|
||||
diffStr = i18n.Tr(lang, "tool.months", months)
|
||||
} else {
|
||||
diffStr = i18n.Tr(lang, "tool.1mon")
|
||||
}
|
||||
diff -= diff / Month * Month
|
||||
|
||||
case diff < Year+Year/2:
|
||||
diff -= 1 * Year
|
||||
diffStr = i18n.Tr(lang, "tool.1y")
|
||||
default:
|
||||
years := round(float64(diff) / Year)
|
||||
if years > 1 {
|
||||
diffStr = i18n.Tr(lang, "tool.years", years)
|
||||
} else {
|
||||
diffStr = i18n.Tr(lang, "tool.1y")
|
||||
}
|
||||
diff -= (diff / Year) * Year
|
||||
}
|
||||
return diff, diffStr
|
||||
}
|
||||
|
||||
// MinutesToFriendly returns a user friendly string with number of minutes
|
||||
// converted to hours and minutes.
|
||||
func MinutesToFriendly(minutes int, lang string) string {
|
||||
@@ -111,7 +204,7 @@ func timeSincePro(then, now time.Time, lang string) string {
|
||||
break
|
||||
}
|
||||
|
||||
diff, diffStr = computeTimeDiff(diff, lang)
|
||||
diff, diffStr = computeTimeDiffFloor(diff, lang)
|
||||
timeStr += ", " + diffStr
|
||||
}
|
||||
return strings.TrimPrefix(timeStr, ", ")
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -47,27 +48,39 @@ func TestTimeSince(t *testing.T) {
|
||||
|
||||
// test that each diff in `diffs` yields the expected string
|
||||
test := func(expected string, diffs ...time.Duration) {
|
||||
for _, diff := range diffs {
|
||||
actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
|
||||
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
|
||||
actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
|
||||
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
|
||||
}
|
||||
t.Run(expected, func(t *testing.T) {
|
||||
for _, diff := range diffs {
|
||||
actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
|
||||
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
|
||||
actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
|
||||
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
test("1 second", time.Second, time.Second+50*time.Millisecond)
|
||||
test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond)
|
||||
test("1 minute", time.Minute, time.Minute+30*time.Second)
|
||||
test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second)
|
||||
test("1 hour", time.Hour, time.Hour+30*time.Minute)
|
||||
test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute)
|
||||
test("1 day", DayDur, DayDur+12*time.Hour)
|
||||
test("2 days", 2*DayDur, 2*DayDur+12*time.Hour)
|
||||
test("1 minute", time.Minute, time.Minute+29*time.Second)
|
||||
test("2 minutes", 2*time.Minute, time.Minute+30*time.Second)
|
||||
test("2 minutes", 2*time.Minute, 2*time.Minute+29*time.Second)
|
||||
test("1 hour", time.Hour, time.Hour+29*time.Minute)
|
||||
test("2 hours", 2*time.Hour, time.Hour+30*time.Minute)
|
||||
test("2 hours", 2*time.Hour, 2*time.Hour+29*time.Minute)
|
||||
test("3 hours", 3*time.Hour, 2*time.Hour+30*time.Minute)
|
||||
test("1 day", DayDur, DayDur+11*time.Hour)
|
||||
test("2 days", 2*DayDur, DayDur+12*time.Hour)
|
||||
test("2 days", 2*DayDur, 2*DayDur+11*time.Hour)
|
||||
test("3 days", 3*DayDur, 2*DayDur+12*time.Hour)
|
||||
test("1 week", WeekDur, WeekDur+3*DayDur)
|
||||
test("2 weeks", 2*WeekDur, WeekDur+4*DayDur)
|
||||
test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur)
|
||||
test("1 month", MonthDur, MonthDur+15*DayDur)
|
||||
test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur)
|
||||
test("1 year", YearDur, YearDur+6*MonthDur)
|
||||
test("2 years", 2*YearDur, 2*YearDur+6*MonthDur)
|
||||
test("3 weeks", 3*WeekDur, 2*WeekDur+4*DayDur)
|
||||
test("1 month", MonthDur, MonthDur+14*DayDur)
|
||||
test("2 months", 2*MonthDur, MonthDur+15*DayDur)
|
||||
test("2 months", 2*MonthDur, 2*MonthDur+14*DayDur)
|
||||
test("1 year", YearDur, YearDur+5*MonthDur)
|
||||
test("2 years", 2*YearDur, YearDur+6*MonthDur)
|
||||
test("2 years", 2*YearDur, 2*YearDur+5*MonthDur)
|
||||
test("3 years", 3*YearDur, 2*YearDur+6*MonthDur)
|
||||
}
|
||||
|
||||
func TestTimeSincePro(t *testing.T) {
|
||||
@@ -114,11 +127,11 @@ func TestHtmlTimeSince(t *testing.T) {
|
||||
}
|
||||
test("1 second", time.Second)
|
||||
test("3 minutes", 3*time.Minute+5*time.Second)
|
||||
test("1 day", DayDur+18*time.Hour)
|
||||
test("1 week", WeekDur+6*DayDur)
|
||||
test("3 months", 3*MonthDur+3*WeekDur)
|
||||
test("1 day", DayDur+11*time.Hour)
|
||||
test("1 week", WeekDur+3*DayDur)
|
||||
test("3 months", 3*MonthDur+2*WeekDur)
|
||||
test("2 years", 2*YearDur)
|
||||
test("3 years", 3*YearDur+11*MonthDur+4*WeekDur)
|
||||
test("3 years", 2*YearDur+11*MonthDur+4*WeekDur)
|
||||
}
|
||||
|
||||
func TestComputeTimeDiff(t *testing.T) {
|
||||
@@ -126,26 +139,35 @@ func TestComputeTimeDiff(t *testing.T) {
|
||||
// computeTimeDiff(base + offset) == (offset, str)
|
||||
test := func(base int64, str string, offsets ...int64) {
|
||||
for _, offset := range offsets {
|
||||
diff, diffStr := computeTimeDiff(base+offset, "en")
|
||||
assert.Equal(t, offset, diff)
|
||||
assert.Equal(t, str, diffStr)
|
||||
t.Run(fmt.Sprintf("%s:%d", str, offset), func(t *testing.T) {
|
||||
diff, diffStr := computeTimeDiff(base+offset, "en")
|
||||
assert.Equal(t, offset, diff)
|
||||
assert.Equal(t, str, diffStr)
|
||||
})
|
||||
}
|
||||
}
|
||||
test(0, "now", 0)
|
||||
test(1, "1 second", 0)
|
||||
test(2, "2 seconds", 0)
|
||||
test(Minute, "1 minute", 0, 1, 30, Minute-1)
|
||||
test(2*Minute, "2 minutes", 0, Minute-1)
|
||||
test(Hour, "1 hour", 0, 1, Hour-1)
|
||||
test(5*Hour, "5 hours", 0, Hour-1)
|
||||
test(Day, "1 day", 0, 1, Day-1)
|
||||
test(5*Day, "5 days", 0, Day-1)
|
||||
test(Week, "1 week", 0, 1, Week-1)
|
||||
test(3*Week, "3 weeks", 0, 4*Day+25000)
|
||||
test(Month, "1 month", 0, 1, Month-1)
|
||||
test(10*Month, "10 months", 0, Month-1)
|
||||
test(Year, "1 year", 0, Year-1)
|
||||
test(3*Year, "3 years", 0, Year-1)
|
||||
test(Minute, "1 minute", 0, 1, 29)
|
||||
test(Minute, "2 minutes", 30, Minute-1)
|
||||
test(2*Minute, "2 minutes", 0, 29)
|
||||
test(2*Minute, "3 minutes", 30, Minute-1)
|
||||
test(Hour, "1 hour", 0, 1, 29*Minute)
|
||||
test(Hour, "2 hours", 30*Minute, Hour-1)
|
||||
test(5*Hour, "5 hours", 0, 29*Minute)
|
||||
test(Day, "1 day", 0, 1, 11*Hour)
|
||||
test(Day, "2 days", 12*Hour, Day-1)
|
||||
test(5*Day, "5 days", 0, 11*Hour)
|
||||
test(Week, "1 week", 0, 1, 3*Day)
|
||||
test(Week, "2 weeks", 4*Day, Week-1)
|
||||
test(3*Week, "3 weeks", 0, 3*Day)
|
||||
test(Month, "1 month", 0, 1)
|
||||
test(Month, "2 months", 16*Day, Month-1)
|
||||
test(10*Month, "10 months", 0, 13*Day)
|
||||
test(Year, "1 year", 0, 179*Day)
|
||||
test(Year, "2 years", 180*Day, Year-1)
|
||||
test(3*Year, "3 years", 0, 179*Day)
|
||||
}
|
||||
|
||||
func TestMinutesToFriendly(t *testing.T) {
|
||||
|
||||
@@ -93,7 +93,12 @@ func Transfer(ctx *context.APIContext, opts api.TransferRepoOption) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo_service.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository, teams); err != nil {
|
||||
if err = repo_service.StartRepositoryTransfer(ctx.User, newOwner, ctx.Repo.Repository, teams); err != nil {
|
||||
if models.IsErrCancelled(err) {
|
||||
ctx.Error(http.StatusForbidden, "transfer", "user has no right to create repo for new owner")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -110,13 +110,15 @@ func InitLocales() {
|
||||
}
|
||||
}
|
||||
i18n.I18n(i18n.Options{
|
||||
SubURL: setting.AppSubURL,
|
||||
Files: localFiles,
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: false,
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
SubURL: setting.AppSubURL,
|
||||
Files: localFiles,
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: false,
|
||||
CookieHttpOnly: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,12 +133,19 @@ func GlobalInit(ctx context.Context) {
|
||||
log.Trace("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Trace("Custom path: %s", setting.CustomPath)
|
||||
log.Trace("Log path: %s", setting.LogRootPath)
|
||||
checkRunMode()
|
||||
|
||||
// Setup i18n
|
||||
InitLocales()
|
||||
|
||||
NewServices()
|
||||
|
||||
if setting.EnableSQLite3 {
|
||||
log.Info("SQLite3 Supported")
|
||||
} else if setting.Database.UseSQLite3 {
|
||||
log.Fatal("SQLite3 is set in settings but NOT Supported")
|
||||
}
|
||||
|
||||
if setting.InstallLock {
|
||||
highlight.NewContext()
|
||||
external.RegisterParsers()
|
||||
@@ -170,10 +179,6 @@ func GlobalInit(ctx context.Context) {
|
||||
}
|
||||
eventsource.GetManager().Init()
|
||||
}
|
||||
if setting.EnableSQLite3 {
|
||||
log.Info("SQLite3 Supported")
|
||||
}
|
||||
checkRunMode()
|
||||
|
||||
if err := repo_migrations.Init(); err != nil {
|
||||
log.Fatal("Failed to initialize repository migrations: %v", err)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
@@ -502,7 +501,7 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
|
||||
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + ctx.Repo.BranchName + "..." + form.NewBranchName)
|
||||
} else {
|
||||
treePath := filepath.Dir(ctx.Repo.TreePath)
|
||||
treePath := path.Dir(ctx.Repo.TreePath)
|
||||
if treePath == "." {
|
||||
treePath = "" // the file deleted was in the root, so we return the user to the root directory
|
||||
}
|
||||
@@ -805,10 +804,10 @@ func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
|
||||
// see if the tree has entries
|
||||
if tree, err := commit.SubTree(treePath); err != nil {
|
||||
// failed to get tree, going up a dir
|
||||
return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
|
||||
return GetClosestParentWithFiles(path.Dir(treePath), commit)
|
||||
} else if entries, err := tree.ListEntries(); err != nil || len(entries) == 0 {
|
||||
// no files in this dir, going up a dir
|
||||
return GetClosestParentWithFiles(filepath.Dir(treePath), commit)
|
||||
return GetClosestParentWithFiles(path.Dir(treePath), commit)
|
||||
}
|
||||
return treePath
|
||||
}
|
||||
|
||||
@@ -725,6 +725,14 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs [
|
||||
ctx.Data[ctxDataKey] = templateBody
|
||||
labelIDs := make([]string, 0, len(meta.Labels))
|
||||
if repoLabels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, "", models.ListOptions{}); err == nil {
|
||||
ctx.Data["Labels"] = repoLabels
|
||||
if ctx.Repo.Owner.IsOrganization() {
|
||||
if orgLabels, err := models.GetLabelsByOrgID(ctx.Repo.Owner.ID, ctx.Query("sort"), models.ListOptions{}); err == nil {
|
||||
ctx.Data["OrgLabels"] = orgLabels
|
||||
repoLabels = append(repoLabels, orgLabels...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, metaLabel := range meta.Labels {
|
||||
for _, repoLabel := range repoLabels {
|
||||
if strings.EqualFold(repoLabel.Name, metaLabel) {
|
||||
@@ -734,7 +742,6 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs [
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Data["Labels"] = repoLabels
|
||||
}
|
||||
ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0
|
||||
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
|
||||
@@ -1105,7 +1112,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
iw.IssueID = issue.ID
|
||||
iw.IsWatching, err = models.CheckIssueWatch(ctx.User, issue)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.ServerError("CheckIssueWatch", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@ func DeleteProjectBoard(ctx *context.Context) {
|
||||
|
||||
pb, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.ServerError("GetProjectBoard", err)
|
||||
return
|
||||
}
|
||||
if pb.ProjectID != ctx.ParamsInt64(":id") {
|
||||
@@ -443,7 +443,7 @@ func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitle
|
||||
|
||||
board, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.ServerError("GetProjectBoard", err)
|
||||
return
|
||||
}
|
||||
if board.ProjectID != ctx.ParamsInt64(":id") {
|
||||
|
||||
@@ -696,11 +696,11 @@ func UpdatePullRequest(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if err := issue.PullRequest.LoadBaseRepo(); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.ServerError("LoadBaseRepo", err)
|
||||
return
|
||||
}
|
||||
if err := issue.PullRequest.LoadHeadRepo(); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
ctx.ServerError("LoadHeadRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -475,9 +475,12 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
ctx.Repo.GitRepo = nil
|
||||
}
|
||||
if err = repo_service.TransferOwnership(ctx.User, newOwner, repo, nil); err != nil {
|
||||
if err = repo_service.StartRepositoryTransfer(ctx.User, newOwner, repo, nil); err != nil {
|
||||
if models.IsErrRepoAlreadyExist(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
|
||||
} else if models.IsErrCancelled(err) {
|
||||
// this err msg is not translated, since it was introduced in a backport
|
||||
ctx.RenderWithErr("user has no right to create repo for new owner", tplSettingsOptions, nil)
|
||||
} else {
|
||||
ctx.ServerError("TransferOwnership", err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
gotemplate "html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path"
|
||||
@@ -420,7 +421,9 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
||||
|
||||
buf = make([]byte, 1024)
|
||||
n, err = dataRc.Read(buf)
|
||||
if err != nil {
|
||||
// Error EOF don't mean there is an error, it just means we read to
|
||||
// the end
|
||||
if err != nil && err != io.EOF {
|
||||
ctx.ServerError("Data", err)
|
||||
return
|
||||
}
|
||||
@@ -689,7 +692,10 @@ func RenderUserCards(ctx *context.Context, total int, getter func(opts models.Li
|
||||
pager := context.NewPagination(total, models.ItemsPerPage, page, 5)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
items, err := getter(models.ListOptions{Page: pager.Paginater.Current()})
|
||||
items, err := getter(models.ListOptions{
|
||||
Page: pager.Paginater.Current(),
|
||||
PageSize: models.ItemsPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("getter", err)
|
||||
return
|
||||
@@ -720,6 +726,7 @@ func Stars(ctx *context.Context) {
|
||||
func Forks(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repos.forks")
|
||||
|
||||
// TODO: need pagination
|
||||
forks, err := ctx.Repo.Repository.GetForks(models.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetForks", err)
|
||||
|
||||
@@ -121,11 +121,11 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.RequestURI, "/"+prefix) {
|
||||
if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
|
||||
return
|
||||
}
|
||||
|
||||
rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix)
|
||||
rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
|
||||
u, err := objStore.URL(rPath, path.Base(rPath))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
|
||||
@@ -152,11 +152,11 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.RequestURI, "/"+prefix) {
|
||||
if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
|
||||
return
|
||||
}
|
||||
|
||||
rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix)
|
||||
rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
|
||||
rPath = strings.TrimPrefix(rPath, "/")
|
||||
//If we have matched and access to release or issue
|
||||
fr, err := objStore.Open(rPath)
|
||||
@@ -247,13 +247,15 @@ func NewMacaron() *macaron.Macaron {
|
||||
}
|
||||
|
||||
m.Use(i18n.I18n(i18n.Options{
|
||||
SubURL: setting.AppSubURL,
|
||||
Files: localFiles,
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: false,
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
SubURL: setting.AppSubURL,
|
||||
Files: localFiles,
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: false,
|
||||
CookieHttpOnly: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
}))
|
||||
m.Use(cache.Cacher(cache.Options{
|
||||
Adapter: setting.CacheService.Adapter,
|
||||
|
||||
@@ -746,6 +746,7 @@ func LinkAccount(ctx *context.Context) {
|
||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
@@ -797,6 +798,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
@@ -881,6 +883,7 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
|
||||
ctx.Data["ShowRegistrationButton"] = false
|
||||
|
||||
|
||||
@@ -567,6 +567,7 @@ func Issues(ctx *context.Context) {
|
||||
FilterMode: filterMode,
|
||||
IsPull: isPullList,
|
||||
IsClosed: isShowClosed,
|
||||
LabelIDs: opts.LabelIDs,
|
||||
}
|
||||
if len(repoIDs) > 0 {
|
||||
userIssueStatsOpts.UserRepoIDs = repoIDs
|
||||
@@ -586,6 +587,7 @@ func Issues(ctx *context.Context) {
|
||||
IsPull: isPullList,
|
||||
IsClosed: isShowClosed,
|
||||
IssueIDs: issueIDsFromSearch,
|
||||
LabelIDs: opts.LabelIDs,
|
||||
}
|
||||
if len(repoIDs) > 0 {
|
||||
statsOpts.RepoIDs = repoIDs
|
||||
@@ -608,6 +610,7 @@ func Issues(ctx *context.Context) {
|
||||
IsPull: isPullList,
|
||||
IsClosed: isShowClosed,
|
||||
IssueIDs: issueIDsFromSearch,
|
||||
LabelIDs: opts.LabelIDs,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserIssueStats All", err)
|
||||
@@ -659,6 +662,7 @@ func Issues(ctx *context.Context) {
|
||||
ctx.Data["RepoIDs"] = repoIDs
|
||||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
ctx.Data["TotalIssueCount"] = totalIssues
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
|
||||
if isShowClosed {
|
||||
ctx.Data["State"] = "closed"
|
||||
|
||||
@@ -227,6 +227,9 @@ func Repos(ctx *context.Context) {
|
||||
root := filepath.Join(models.UserPath(ctxUser.Name))
|
||||
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() || path == root {
|
||||
|
||||
@@ -22,8 +22,11 @@ func CreateIssueComment(doer *models.User, repo *models.Repository, issue *model
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notification.NotifyCreateIssueComment(doer, repo, issue, comment)
|
||||
mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), doer, comment.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notification.NotifyCreateIssueComment(doer, repo, issue, comment, mentions)
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
@@ -182,6 +182,8 @@ var (
|
||||
removedCodePrefix = []byte(`<span class="removed-code">`)
|
||||
codeTagSuffix = []byte(`</span>`)
|
||||
)
|
||||
|
||||
var unfinishedtagRegex = regexp.MustCompile(`<[^>]*$`)
|
||||
var trailingSpanRegex = regexp.MustCompile(`<span\s*[[:alpha:]="]*?[>]?$`)
|
||||
var entityRegex = regexp.MustCompile(`&[#]*?[0-9[:alpha:]]*$`)
|
||||
|
||||
@@ -196,10 +198,218 @@ func shouldWriteInline(diff diffmatchpatch.Diff, lineType DiffLineType) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func fixupBrokenSpans(diffs []diffmatchpatch.Diff) []diffmatchpatch.Diff {
|
||||
|
||||
// Create a new array to store our fixed up blocks
|
||||
fixedup := make([]diffmatchpatch.Diff, 0, len(diffs))
|
||||
|
||||
// semantically label some numbers
|
||||
const insert, delete, equal = 0, 1, 2
|
||||
|
||||
// record the positions of the last type of each block in the fixedup blocks
|
||||
last := []int{-1, -1, -1}
|
||||
operation := []diffmatchpatch.Operation{diffmatchpatch.DiffInsert, diffmatchpatch.DiffDelete, diffmatchpatch.DiffEqual}
|
||||
|
||||
// create a writer for insert and deletes
|
||||
toWrite := []strings.Builder{
|
||||
{},
|
||||
{},
|
||||
}
|
||||
|
||||
// make some flags for insert and delete
|
||||
unfinishedTag := []bool{false, false}
|
||||
unfinishedEnt := []bool{false, false}
|
||||
|
||||
// store stores the provided text in the writer for the typ
|
||||
store := func(text string, typ int) {
|
||||
(&(toWrite[typ])).WriteString(text)
|
||||
}
|
||||
|
||||
// hasStored returns true if there is stored content
|
||||
hasStored := func(typ int) bool {
|
||||
return (&toWrite[typ]).Len() > 0
|
||||
}
|
||||
|
||||
// stored will return that content
|
||||
stored := func(typ int) string {
|
||||
return (&toWrite[typ]).String()
|
||||
}
|
||||
|
||||
// empty will empty the stored content
|
||||
empty := func(typ int) {
|
||||
(&toWrite[typ]).Reset()
|
||||
}
|
||||
|
||||
// pop will remove the stored content appending to a diff block for that typ
|
||||
pop := func(typ int, fixedup []diffmatchpatch.Diff) []diffmatchpatch.Diff {
|
||||
if hasStored(typ) {
|
||||
if last[typ] > last[equal] {
|
||||
fixedup[last[typ]].Text += stored(typ)
|
||||
} else {
|
||||
fixedup = append(fixedup, diffmatchpatch.Diff{
|
||||
Type: operation[typ],
|
||||
Text: stored(typ),
|
||||
})
|
||||
}
|
||||
empty(typ)
|
||||
}
|
||||
return fixedup
|
||||
}
|
||||
|
||||
// Now we walk the provided diffs and check the type of each block in turn
|
||||
for _, diff := range diffs {
|
||||
|
||||
typ := delete // flag for handling insert or delete typs
|
||||
switch diff.Type {
|
||||
case diffmatchpatch.DiffEqual:
|
||||
// First check if there is anything stored
|
||||
if hasStored(insert) || hasStored(delete) {
|
||||
// There are two reasons for storing content:
|
||||
// 1. Unfinished Entity <- Could be more efficient here by not doing this if we're looking for a tag
|
||||
if unfinishedEnt[insert] || unfinishedEnt[delete] {
|
||||
// we look for a ';' to finish an entity
|
||||
idx := strings.IndexRune(diff.Text, ';')
|
||||
if idx >= 0 {
|
||||
// if we find a ';' store the preceding content to both insert and delete
|
||||
store(diff.Text[:idx+1], insert)
|
||||
store(diff.Text[:idx+1], delete)
|
||||
|
||||
// and remove it from this block
|
||||
diff.Text = diff.Text[idx+1:]
|
||||
|
||||
// reset the ent flags
|
||||
unfinishedEnt[insert] = false
|
||||
unfinishedEnt[delete] = false
|
||||
} else {
|
||||
// otherwise store it all on insert and delete
|
||||
store(diff.Text, insert)
|
||||
store(diff.Text, delete)
|
||||
// and empty this block
|
||||
diff.Text = ""
|
||||
}
|
||||
}
|
||||
// 2. Unfinished Tag
|
||||
if unfinishedTag[insert] || unfinishedTag[delete] {
|
||||
// we look for a '>' to finish a tag
|
||||
idx := strings.IndexRune(diff.Text, '>')
|
||||
if idx >= 0 {
|
||||
store(diff.Text[:idx+1], insert)
|
||||
store(diff.Text[:idx+1], delete)
|
||||
diff.Text = diff.Text[idx+1:]
|
||||
unfinishedTag[insert] = false
|
||||
unfinishedTag[delete] = false
|
||||
} else {
|
||||
store(diff.Text, insert)
|
||||
store(diff.Text, delete)
|
||||
diff.Text = ""
|
||||
}
|
||||
}
|
||||
|
||||
// If we've completed the required tag/entities
|
||||
if !(unfinishedTag[insert] || unfinishedTag[delete] || unfinishedEnt[insert] || unfinishedEnt[delete]) {
|
||||
// pop off the stack
|
||||
fixedup = pop(insert, fixedup)
|
||||
fixedup = pop(delete, fixedup)
|
||||
}
|
||||
|
||||
// If that has left this diff block empty then shortcut
|
||||
if len(diff.Text) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// check if this block ends in an unfinished tag?
|
||||
idx := unfinishedtagRegex.FindStringIndex(diff.Text)
|
||||
if idx != nil {
|
||||
unfinishedTag[insert] = true
|
||||
unfinishedTag[delete] = true
|
||||
} else {
|
||||
// otherwise does it end in an unfinished entity?
|
||||
idx = entityRegex.FindStringIndex(diff.Text)
|
||||
if idx != nil {
|
||||
unfinishedEnt[insert] = true
|
||||
unfinishedEnt[delete] = true
|
||||
}
|
||||
}
|
||||
|
||||
// If there is an unfinished component
|
||||
if idx != nil {
|
||||
// Store the fragment
|
||||
store(diff.Text[idx[0]:], insert)
|
||||
store(diff.Text[idx[0]:], delete)
|
||||
// and remove it from this block
|
||||
diff.Text = diff.Text[:idx[0]]
|
||||
}
|
||||
|
||||
// If that hasn't left the block empty
|
||||
if len(diff.Text) > 0 {
|
||||
// store the position of the last equal block and store it in our diffs
|
||||
last[equal] = len(fixedup)
|
||||
fixedup = append(fixedup, diff)
|
||||
}
|
||||
continue
|
||||
case diffmatchpatch.DiffInsert:
|
||||
typ = insert
|
||||
fallthrough
|
||||
case diffmatchpatch.DiffDelete:
|
||||
// First check if there is anything stored for this type
|
||||
if hasStored(typ) {
|
||||
// if there is prepend it to this block, empty the storage and reset our flags
|
||||
diff.Text = stored(typ) + diff.Text
|
||||
empty(typ)
|
||||
unfinishedEnt[typ] = false
|
||||
unfinishedTag[typ] = false
|
||||
}
|
||||
|
||||
// check if this block ends in an unfinished tag
|
||||
idx := unfinishedtagRegex.FindStringIndex(diff.Text)
|
||||
if idx != nil {
|
||||
unfinishedTag[typ] = true
|
||||
} else {
|
||||
// otherwise does it end in an unfinished entity
|
||||
idx = entityRegex.FindStringIndex(diff.Text)
|
||||
if idx != nil {
|
||||
unfinishedEnt[typ] = true
|
||||
}
|
||||
}
|
||||
|
||||
// If there is an unfinished component
|
||||
if idx != nil {
|
||||
// Store the fragment
|
||||
store(diff.Text[idx[0]:], typ)
|
||||
// and remove it from this block
|
||||
diff.Text = diff.Text[:idx[0]]
|
||||
}
|
||||
|
||||
// If that hasn't left the block empty
|
||||
if len(diff.Text) > 0 {
|
||||
// if the last block of this type was after the last equal block
|
||||
if last[typ] > last[equal] {
|
||||
// store this blocks content on that block
|
||||
fixedup[last[typ]].Text += diff.Text
|
||||
} else {
|
||||
// otherwise store the position of the last block of this type and store the block
|
||||
last[typ] = len(fixedup)
|
||||
fixedup = append(fixedup, diff)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// pop off any remaining stored content
|
||||
fixedup = pop(insert, fixedup)
|
||||
fixedup = pop(delete, fixedup)
|
||||
|
||||
return fixedup
|
||||
}
|
||||
|
||||
func diffToHTML(fileName string, diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
match := ""
|
||||
|
||||
diffs = fixupBrokenSpans(diffs)
|
||||
|
||||
for _, diff := range diffs {
|
||||
if shouldWriteInline(diff, lineType) {
|
||||
if len(match) > 0 {
|
||||
@@ -373,6 +583,7 @@ type DiffFile struct {
|
||||
IsBin bool
|
||||
IsLFSFile bool
|
||||
IsRenamed bool
|
||||
IsAmbiguous bool
|
||||
IsSubmodule bool
|
||||
Sections []*DiffSection
|
||||
IsIncomplete bool
|
||||
@@ -566,12 +777,32 @@ parsingLoop:
|
||||
if strings.HasSuffix(line, " 160000\n") {
|
||||
curFile.IsSubmodule = true
|
||||
}
|
||||
case strings.HasPrefix(line, "rename from "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileRename
|
||||
if curFile.IsAmbiguous {
|
||||
curFile.OldName = line[len("rename from ") : len(line)-1]
|
||||
}
|
||||
case strings.HasPrefix(line, "rename to "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileRename
|
||||
if curFile.IsAmbiguous {
|
||||
curFile.Name = line[len("rename to ") : len(line)-1]
|
||||
curFile.IsAmbiguous = false
|
||||
}
|
||||
case strings.HasPrefix(line, "copy from "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileCopy
|
||||
if curFile.IsAmbiguous {
|
||||
curFile.OldName = line[len("copy from ") : len(line)-1]
|
||||
}
|
||||
case strings.HasPrefix(line, "copy to "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileCopy
|
||||
if curFile.IsAmbiguous {
|
||||
curFile.Name = line[len("copy to ") : len(line)-1]
|
||||
curFile.IsAmbiguous = false
|
||||
}
|
||||
case strings.HasPrefix(line, "new file"):
|
||||
curFile.Type = DiffFileAdd
|
||||
curFile.IsCreated = true
|
||||
@@ -593,9 +824,35 @@ parsingLoop:
|
||||
case strings.HasPrefix(line, "Binary"):
|
||||
curFile.IsBin = true
|
||||
case strings.HasPrefix(line, "--- "):
|
||||
// Do nothing with this line
|
||||
// Handle ambiguous filenames
|
||||
if curFile.IsAmbiguous {
|
||||
if len(line) > 6 && line[4] == 'a' {
|
||||
curFile.OldName = line[6 : len(line)-1]
|
||||
if line[len(line)-2] == '\t' {
|
||||
curFile.OldName = curFile.OldName[:len(curFile.OldName)-1]
|
||||
}
|
||||
} else {
|
||||
curFile.OldName = ""
|
||||
}
|
||||
}
|
||||
// Otherwise do nothing with this line
|
||||
case strings.HasPrefix(line, "+++ "):
|
||||
// Do nothing with this line
|
||||
// Handle ambiguous filenames
|
||||
if curFile.IsAmbiguous {
|
||||
if len(line) > 6 && line[4] == 'b' {
|
||||
curFile.Name = line[6 : len(line)-1]
|
||||
if line[len(line)-2] == '\t' {
|
||||
curFile.Name = curFile.Name[:len(curFile.Name)-1]
|
||||
}
|
||||
if curFile.OldName == "" {
|
||||
curFile.OldName = curFile.Name
|
||||
}
|
||||
} else {
|
||||
curFile.Name = curFile.OldName
|
||||
}
|
||||
curFile.IsAmbiguous = false
|
||||
}
|
||||
// Otherwise do nothing with this line, but now switch to parsing hunks
|
||||
lineBytes, isFragment, err := parseHunks(curFile, maxLines, maxLineCharacters, input)
|
||||
diff.TotalAddition += curFile.Addition
|
||||
diff.TotalDeletion += curFile.Deletion
|
||||
@@ -846,13 +1103,33 @@ func createDiffFile(diff *Diff, line string) *DiffFile {
|
||||
|
||||
rd := strings.NewReader(line[len(cmdDiffHead):] + " ")
|
||||
curFile.Type = DiffFileChange
|
||||
curFile.OldName = readFileName(rd)
|
||||
curFile.Name = readFileName(rd)
|
||||
oldNameAmbiguity := false
|
||||
newNameAmbiguity := false
|
||||
|
||||
curFile.OldName, oldNameAmbiguity = readFileName(rd)
|
||||
curFile.Name, newNameAmbiguity = readFileName(rd)
|
||||
if oldNameAmbiguity && newNameAmbiguity {
|
||||
curFile.IsAmbiguous = true
|
||||
// OK we should bet that the oldName and the newName are the same if they can be made to be same
|
||||
// So we need to start again ...
|
||||
if (len(line)-len(cmdDiffHead)-1)%2 == 0 {
|
||||
// diff --git a/b b/b b/b b/b b/b b/b
|
||||
//
|
||||
midpoint := (len(line) + len(cmdDiffHead) - 1) / 2
|
||||
new, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:]
|
||||
if len(new) > 2 && len(old) > 2 && new[2:] == old[2:] {
|
||||
curFile.OldName = old[2:]
|
||||
curFile.Name = old[2:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curFile.IsRenamed = curFile.Name != curFile.OldName
|
||||
return curFile
|
||||
}
|
||||
|
||||
func readFileName(rd *strings.Reader) string {
|
||||
func readFileName(rd *strings.Reader) (string, bool) {
|
||||
ambiguity := false
|
||||
var name string
|
||||
char, _ := rd.ReadByte()
|
||||
_ = rd.UnreadByte()
|
||||
@@ -862,9 +1139,24 @@ func readFileName(rd *strings.Reader) string {
|
||||
name = name[1:]
|
||||
}
|
||||
} else {
|
||||
// This technique is potentially ambiguous it may not be possible to uniquely identify the filenames from the diff line alone
|
||||
ambiguity = true
|
||||
fmt.Fscanf(rd, "%s ", &name)
|
||||
char, _ := rd.ReadByte()
|
||||
_ = rd.UnreadByte()
|
||||
for !(char == 0 || char == '"' || char == 'b') {
|
||||
var suffix string
|
||||
fmt.Fscanf(rd, "%s ", &suffix)
|
||||
name += " " + suffix
|
||||
char, _ = rd.ReadByte()
|
||||
_ = rd.UnreadByte()
|
||||
}
|
||||
}
|
||||
return name[2:]
|
||||
if len(name) < 2 {
|
||||
log.Error("Unable to determine name from reader: %v", rd)
|
||||
return "", true
|
||||
}
|
||||
return name[2:], ambiguity
|
||||
}
|
||||
|
||||
// GetDiffRange builds a Diff between two commits of a repository.
|
||||
@@ -975,6 +1267,7 @@ func CommentAsDiff(c *models.Comment) (*Diff, error) {
|
||||
diff, err := ParsePatch(setting.Git.MaxGitDiffLines,
|
||||
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch))
|
||||
if err != nil {
|
||||
log.Error("Unable to parse patch: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(diff.Files) == 0 {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/highlight"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -23,7 +24,7 @@ import (
|
||||
|
||||
func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
|
||||
if s1 != string(s2) {
|
||||
t.Errorf("%s should be equal %s", s2, s1)
|
||||
t.Errorf("Did not receive expected results:\nExpected: %s\nActual: %s", s1, s2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ func TestDiffToHTML(t *testing.T) {
|
||||
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">)</span>"},
|
||||
}, DiffLineDel))
|
||||
|
||||
assertEqual(t, "<span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nf\">WrapperRenderer</span><span class=\"p\">(</span><span class=\"nx\">w</span><span class=\"p\">,</span> <span class=\"removed-code\"><span class=\"nx\">language</span></span><span class=\"removed-code\"><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
|
||||
assertEqual(t, "<span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nf\">WrapperRenderer</span><span class=\"p\">(</span><span class=\"nx\">w</span><span class=\"p\">,</span> <span class=\"removed-code\"><span class=\"nx\">language</span><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
|
||||
{Type: dmp.DiffEqual, Text: "<span class=\"nx\">r</span><span class=\"p\">.</span><span class=\"nf\">WrapperRenderer</span><span class=\"p\">(</span><span class=\"nx\">w</span><span class=\"p\">,</span> <span class=\"nx\">"},
|
||||
{Type: dmp.DiffDelete, Text: "language</span><span "},
|
||||
{Type: dmp.DiffEqual, Text: "c"},
|
||||
@@ -69,14 +70,14 @@ func TestDiffToHTML(t *testing.T) {
|
||||
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>"},
|
||||
}, DiffLineDel))
|
||||
|
||||
assertEqual(t, "<span class=\"added-code\">language</span></span><span class=\"added-code\"><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
|
||||
assertEqual(t, "<span class=\"added-code\">language</span><span class=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs</span></span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>", diffToHTML("", []dmp.Diff{
|
||||
{Type: dmp.DiffInsert, Text: "language</span><span "},
|
||||
{Type: dmp.DiffEqual, Text: "c"},
|
||||
{Type: dmp.DiffInsert, Text: "lass=\"p\">,</span> <span class=\"kc\">true</span><span class=\"p\">,</span> <span class=\"nx\">attrs"},
|
||||
{Type: dmp.DiffEqual, Text: "</span><span class=\"p\">,</span> <span class=\"kc\">false</span><span class=\"p\">)</span>"},
|
||||
}, DiffLineAdd))
|
||||
|
||||
assertEqual(t, "<span class=\"k\">print</span><span class=\"added-code\"></span><span class=\"added-code\"><span class=\"p\">(</span></span><span class=\"sa\"></span><span class=\"s2\">"</span><span class=\"s2\">// </span><span class=\"s2\">"</span><span class=\"p\">,</span> <span class=\"n\">sys</span><span class=\"o\">.</span><span class=\"n\">argv</span><span class=\"added-code\"><span class=\"p\">)</span></span>", diffToHTML("", []dmp.Diff{
|
||||
assertEqual(t, "<span class=\"k\">print</span><span class=\"added-code\"><span class=\"p\">(</span></span><span class=\"sa\"></span><span class=\"s2\">"</span><span class=\"s2\">// </span><span class=\"s2\">"</span><span class=\"p\">,</span> <span class=\"n\">sys</span><span class=\"o\">.</span><span class=\"n\">argv</span><span class=\"added-code\"><span class=\"p\">)</span></span>", diffToHTML("", []dmp.Diff{
|
||||
{Type: dmp.DiffEqual, Text: "<span class=\"k\">print</span>"},
|
||||
{Type: dmp.DiffInsert, Text: "<span"},
|
||||
{Type: dmp.DiffEqual, Text: " "},
|
||||
@@ -85,14 +86,14 @@ func TestDiffToHTML(t *testing.T) {
|
||||
{Type: dmp.DiffInsert, Text: "<span class=\"p\">)</span>"},
|
||||
}, DiffLineAdd))
|
||||
|
||||
assertEqual(t, "sh <span class=\"added-code\">'useradd -u $(stat -c "%u" .gitignore) jenkins</span>'", diffToHTML("", []dmp.Diff{
|
||||
assertEqual(t, "sh <span class=\"added-code\">'useradd -u $(stat -c "%u" .gitignore) jenkins'</span>", diffToHTML("", []dmp.Diff{
|
||||
{Type: dmp.DiffEqual, Text: "sh "},
|
||||
{Type: dmp.DiffDelete, Text: "4;useradd -u 111 jenkins""},
|
||||
{Type: dmp.DiffInsert, Text: "9;useradd -u $(stat -c "%u" .gitignore) jenkins'"},
|
||||
{Type: dmp.DiffEqual, Text: ";"},
|
||||
}, DiffLineAdd))
|
||||
|
||||
assertEqual(t, "<span class=\"x\"> <h<span class=\"added-code\">4 class=</span><span class=\"added-code\">"release-list-title df ac"</span>></span>", diffToHTML("", []dmp.Diff{
|
||||
assertEqual(t, "<span class=\"x\"> <h<span class=\"added-code\">4 class="release-list-title df ac"</span>></span>", diffToHTML("", []dmp.Diff{
|
||||
{Type: dmp.DiffEqual, Text: "<span class=\"x\"> <h"},
|
||||
{Type: dmp.DiffInsert, Text: "4 class=&#"},
|
||||
{Type: dmp.DiffEqual, Text: "3"},
|
||||
@@ -207,6 +208,66 @@ rename to a b/a a/file b/b file
|
||||
oldFilename: "a b/file b/a a/file",
|
||||
filename: "a b/a a/file b/b file",
|
||||
},
|
||||
{
|
||||
name: "ambiguous deleted",
|
||||
gitdiff: `diff --git a/b b/b b/b b/b
|
||||
deleted file mode 100644
|
||||
index 92e798b..0000000
|
||||
--- a/b b/b` + "\t" + `
|
||||
+++ /dev/null
|
||||
@@ -1 +0,0 @@
|
||||
-b b/b
|
||||
`,
|
||||
oldFilename: "b b/b",
|
||||
filename: "b b/b",
|
||||
addition: 0,
|
||||
deletion: 1,
|
||||
},
|
||||
{
|
||||
name: "ambiguous addition",
|
||||
gitdiff: `diff --git a/b b/b b/b b/b
|
||||
new file mode 100644
|
||||
index 0000000..92e798b
|
||||
--- /dev/null
|
||||
+++ b/b b/b` + "\t" + `
|
||||
@@ -0,0 +1 @@
|
||||
+b b/b
|
||||
`,
|
||||
oldFilename: "b b/b",
|
||||
filename: "b b/b",
|
||||
addition: 1,
|
||||
deletion: 0,
|
||||
},
|
||||
{
|
||||
name: "rename",
|
||||
gitdiff: `diff --git a/b b/b b/b b/b b/b b/b
|
||||
similarity index 100%
|
||||
rename from b b/b b/b b/b b/b
|
||||
rename to b
|
||||
`,
|
||||
oldFilename: "b b/b b/b b/b b/b",
|
||||
filename: "b",
|
||||
},
|
||||
{
|
||||
name: "ambiguous 1",
|
||||
gitdiff: `diff --git a/b b/b b/b b/b b/b b/b
|
||||
similarity index 100%
|
||||
rename from b b/b b/b b/b b/b
|
||||
rename to b
|
||||
`,
|
||||
oldFilename: "b b/b b/b b/b b/b",
|
||||
filename: "b",
|
||||
},
|
||||
{
|
||||
name: "ambiguous 2",
|
||||
gitdiff: `diff --git a/b b/b b/b b/b b/b b/b
|
||||
similarity index 100%
|
||||
rename from b b/b b/b b/b
|
||||
rename to b b/b
|
||||
`,
|
||||
oldFilename: "b b/b b/b b/b",
|
||||
filename: "b b/b",
|
||||
},
|
||||
{
|
||||
name: "minuses-and-pluses",
|
||||
gitdiff: `diff --git a/minuses-and-pluses b/minuses-and-pluses
|
||||
@@ -234,32 +295,32 @@ index 6961180..9ba1a00 100644
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
got, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff))
|
||||
if (err != nil) != testcase.wantErr {
|
||||
t.Errorf("ParsePatch() error = %v, wantErr %v", err, testcase.wantErr)
|
||||
t.Errorf("ParsePatch(%q) error = %v, wantErr %v", testcase.name, err, testcase.wantErr)
|
||||
return
|
||||
}
|
||||
gotMarshaled, _ := json.MarshalIndent(got, " ", " ")
|
||||
if got.NumFiles != 1 {
|
||||
t.Errorf("ParsePath() did not receive 1 file:\n%s", string(gotMarshaled))
|
||||
t.Errorf("ParsePath(%q) did not receive 1 file:\n%s", testcase.name, string(gotMarshaled))
|
||||
return
|
||||
}
|
||||
if got.TotalAddition != testcase.addition {
|
||||
t.Errorf("ParsePath() does not have correct totalAddition %d, wanted %d", got.TotalAddition, testcase.addition)
|
||||
t.Errorf("ParsePath(%q) does not have correct totalAddition %d, wanted %d", testcase.name, got.TotalAddition, testcase.addition)
|
||||
}
|
||||
if got.TotalDeletion != testcase.deletion {
|
||||
t.Errorf("ParsePath() did not have correct totalDeletion %d, wanted %d", got.TotalDeletion, testcase.deletion)
|
||||
t.Errorf("ParsePath(%q) did not have correct totalDeletion %d, wanted %d", testcase.name, got.TotalDeletion, testcase.deletion)
|
||||
}
|
||||
file := got.Files[0]
|
||||
if file.Addition != testcase.addition {
|
||||
t.Errorf("ParsePath() does not have correct file addition %d, wanted %d", file.Addition, testcase.addition)
|
||||
t.Errorf("ParsePath(%q) does not have correct file addition %d, wanted %d", testcase.name, file.Addition, testcase.addition)
|
||||
}
|
||||
if file.Deletion != testcase.deletion {
|
||||
t.Errorf("ParsePath() did not have correct file deletion %d, wanted %d", file.Deletion, testcase.deletion)
|
||||
t.Errorf("ParsePath(%q) did not have correct file deletion %d, wanted %d", testcase.name, file.Deletion, testcase.deletion)
|
||||
}
|
||||
if file.OldName != testcase.oldFilename {
|
||||
t.Errorf("ParsePath() did not have correct OldName %s, wanted %s", file.OldName, testcase.oldFilename)
|
||||
t.Errorf("ParsePath(%q) did not have correct OldName %q, wanted %q", testcase.name, file.OldName, testcase.oldFilename)
|
||||
}
|
||||
if file.Name != testcase.filename {
|
||||
t.Errorf("ParsePath() did not have correct Name %s, wanted %s", file.Name, testcase.filename)
|
||||
t.Errorf("ParsePath(%q) did not have correct Name %q, wanted %q", testcase.name, file.Name, testcase.filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -462,3 +523,14 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffToHTML_14231(t *testing.T) {
|
||||
setting.Cfg = ini.Empty()
|
||||
diffRecord := diffMatchPatch.DiffMain(highlight.Code("main.v", " run()\n"), highlight.Code("main.v", " run(db)\n"), true)
|
||||
diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
|
||||
|
||||
expected := ` <span class="n">run</span><span class="added-code"><span class="o">(</span><span class="n">db</span></span><span class="o">)</span>`
|
||||
output := diffToHTML("main.v", diffRecord, DiffLineAdd)
|
||||
|
||||
assertEqual(t, expected, output)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,12 @@ func NewIssue(repo *models.Repository, issue *models.Issue, labelIDs []int64, uu
|
||||
}
|
||||
}
|
||||
|
||||
notification.NotifyNewIssue(issue)
|
||||
mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), issue.Poster, issue.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notification.NotifyNewIssue(issue, mentions)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,31 +5,20 @@
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
)
|
||||
|
||||
// MailParticipantsComment sends new comment emails to repository watchers
|
||||
// and mentioned people.
|
||||
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue) error {
|
||||
return mailParticipantsComment(models.DefaultDBContext(), c, opType, issue)
|
||||
func MailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) error {
|
||||
return mailParticipantsComment(c, opType, issue, mentions)
|
||||
}
|
||||
|
||||
func mailParticipantsComment(ctx models.DBContext, c *models.Comment, opType models.ActionType, issue *models.Issue) (err error) {
|
||||
rawMentions := references.FindAllMentionsMarkdown(c.Content)
|
||||
userMentions, err := issue.ResolveMentionsByVisibility(ctx, c.Poster, rawMentions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", c.IssueID, err)
|
||||
}
|
||||
if err = models.UpdateIssueMentions(ctx, c.IssueID, userMentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
|
||||
}
|
||||
mentions := make([]int64, len(userMentions))
|
||||
for i, u := range userMentions {
|
||||
mentions[i] = u.ID
|
||||
func mailParticipantsComment(c *models.Comment, opType models.ActionType, issue *models.Issue, mentions []*models.User) (err error) {
|
||||
mentionedIDs := make([]int64, len(mentions))
|
||||
for i, u := range mentions {
|
||||
mentionedIDs[i] = u.ID
|
||||
}
|
||||
if err = mailIssueCommentToParticipants(
|
||||
&mailCommentContext{
|
||||
@@ -38,8 +27,29 @@ func mailParticipantsComment(ctx models.DBContext, c *models.Comment, opType mod
|
||||
ActionType: opType,
|
||||
Content: c.Content,
|
||||
Comment: c,
|
||||
}, mentions); err != nil {
|
||||
}, mentionedIDs); err != nil {
|
||||
log.Error("mailIssueCommentToParticipants: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MailMentionsComment sends email to users mentioned in a code comment
|
||||
func MailMentionsComment(pr *models.PullRequest, c *models.Comment, mentions []*models.User) (err error) {
|
||||
mentionedIDs := make([]int64, len(mentions))
|
||||
for i, u := range mentions {
|
||||
mentionedIDs[i] = u.ID
|
||||
}
|
||||
visited := make(map[int64]bool, len(mentions)+1)
|
||||
visited[c.Poster.ID] = true
|
||||
if err = mailIssueCommentBatch(
|
||||
&mailCommentContext{
|
||||
Issue: pr.Issue,
|
||||
Doer: c.Poster,
|
||||
ActionType: models.ActionCommentPull,
|
||||
Content: c.Content,
|
||||
Comment: c,
|
||||
}, mentionedIDs, visited, true); err != nil {
|
||||
log.Error("mailIssueCommentBatch: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
)
|
||||
|
||||
func fallbackMailSubject(issue *models.Issue) string {
|
||||
@@ -80,6 +79,12 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) e
|
||||
|
||||
// Avoid mailing the doer
|
||||
visited[ctx.Doer.ID] = true
|
||||
|
||||
// =========== Mentions ===========
|
||||
if err = mailIssueCommentBatch(ctx, mentions, visited, true); err != nil {
|
||||
return fmt.Errorf("mailIssueCommentBatch() mentions: %v", err)
|
||||
}
|
||||
|
||||
// Avoid mailing explicit unwatched
|
||||
ids, err = models.GetIssueWatchersIDs(ctx.Issue.ID, false)
|
||||
if err != nil {
|
||||
@@ -93,11 +98,6 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []int64) e
|
||||
return fmt.Errorf("mailIssueCommentBatch(): %v", err)
|
||||
}
|
||||
|
||||
// =========== Mentions ===========
|
||||
if err = mailIssueCommentBatch(ctx, mentions, visited, true); err != nil {
|
||||
return fmt.Errorf("mailIssueCommentBatch() mentions: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -123,10 +123,14 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int
|
||||
return err
|
||||
}
|
||||
|
||||
checkUnit := models.UnitTypeIssues
|
||||
if ctx.Issue.IsPull {
|
||||
checkUnit = models.UnitTypePullRequests
|
||||
}
|
||||
// Make sure all recipients can still see the issue
|
||||
idx := 0
|
||||
for _, r := range recipients {
|
||||
if ctx.Issue.Repo.CheckUnitUser(r, models.UnitTypeIssues) {
|
||||
if ctx.Issue.Repo.CheckUnitUser(r, checkUnit) {
|
||||
recipients[idx] = r
|
||||
idx++
|
||||
}
|
||||
@@ -145,22 +149,14 @@ func mailIssueCommentBatch(ctx *mailCommentContext, ids []int64, visited map[int
|
||||
|
||||
// MailParticipants sends new issue thread created emails to repository watchers
|
||||
// and mentioned people.
|
||||
func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType) error {
|
||||
return mailParticipants(models.DefaultDBContext(), issue, doer, opType)
|
||||
func MailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) error {
|
||||
return mailParticipants(issue, doer, opType, mentions)
|
||||
}
|
||||
|
||||
func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.User, opType models.ActionType) (err error) {
|
||||
rawMentions := references.FindAllMentionsMarkdown(issue.Content)
|
||||
userMentions, err := issue.ResolveMentionsByVisibility(ctx, doer, rawMentions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", issue.ID, err)
|
||||
}
|
||||
if err = models.UpdateIssueMentions(ctx, issue.ID, userMentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
|
||||
}
|
||||
mentions := make([]int64, len(userMentions))
|
||||
for i, u := range userMentions {
|
||||
mentions[i] = u.ID
|
||||
func mailParticipants(issue *models.Issue, doer *models.User, opType models.ActionType, mentions []*models.User) (err error) {
|
||||
mentionedIDs := make([]int64, len(mentions))
|
||||
for i, u := range mentions {
|
||||
mentionedIDs[i] = u.ID
|
||||
}
|
||||
if err = mailIssueCommentToParticipants(
|
||||
&mailCommentContext{
|
||||
@@ -169,7 +165,7 @@ func mailParticipants(ctx models.DBContext, issue *models.Issue, doer *models.Us
|
||||
ActionType: opType,
|
||||
Content: issue.Content,
|
||||
Comment: nil,
|
||||
}, mentions); err != nil {
|
||||
}, mentionedIDs); err != nil {
|
||||
log.Error("mailIssueCommentToParticipants: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -54,7 +54,12 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6
|
||||
return err
|
||||
}
|
||||
|
||||
notification.NotifyNewPullRequest(pr)
|
||||
mentions, err := pull.FindAndUpdateIssueMentions(models.DefaultDBContext(), pull.Poster, pull.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notification.NotifyNewPullRequest(pr, mentions)
|
||||
|
||||
// add first push codes comment
|
||||
baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath())
|
||||
@@ -470,7 +475,7 @@ func CloseBranchPulls(doer *models.User, repoID int64, branch string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository
|
||||
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
|
||||
func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
|
||||
branches, err := git.GetBranchesByPath(repo.RepoPath())
|
||||
if err != nil {
|
||||
@@ -489,6 +494,11 @@ func CloseRepoBranchesPulls(doer *models.User, repo *models.Repository) error {
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
// If the base repository for this pr is this repository there is no need to close it
|
||||
// as it is going to be deleted anyway
|
||||
if pr.BaseRepoID == repo.ID {
|
||||
continue
|
||||
}
|
||||
if err = issue_service.ChangeStatus(pr.Issue, doer, true); err != nil && !models.IsErrPullWasClosed(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
package pull
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@@ -57,7 +58,12 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notification.NotifyCreateIssueComment(doer, issue.Repo, issue, comment)
|
||||
mentions, err := issue.FindAndUpdateIssueMentions(models.DefaultDBContext(), doer, comment.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notification.NotifyCreateIssueComment(doer, issue.Repo, issue, comment, mentions)
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
@@ -174,11 +180,24 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models
|
||||
if len(commitID) == 0 {
|
||||
commitID = headCommitID
|
||||
}
|
||||
patchBuf := new(bytes.Buffer)
|
||||
if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, treePath, patchBuf); err != nil {
|
||||
return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", gitRepo.Path, pr.MergeBase, headCommitID, treePath, err)
|
||||
reader, writer := io.Pipe()
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
_ = writer.Close()
|
||||
}()
|
||||
go func() {
|
||||
if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, treePath, writer); err != nil {
|
||||
_ = writer.CloseWithError(fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", gitRepo.Path, pr.MergeBase, headCommitID, treePath, err))
|
||||
return
|
||||
}
|
||||
_ = writer.Close()
|
||||
}()
|
||||
|
||||
patch, err = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
|
||||
if err != nil {
|
||||
log.Error("Error whilst generating patch: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
|
||||
}
|
||||
return models.CreateComment(&models.CreateCommentOptions{
|
||||
Type: models.CommentTypeCode,
|
||||
@@ -226,7 +245,25 @@ func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issu
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
notification.NotifyPullRequestReview(pr, review, comm)
|
||||
ctx := models.DefaultDBContext()
|
||||
mentions, err := issue.FindAndUpdateIssueMentions(ctx, doer, comm.Content)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
notification.NotifyPullRequestReview(pr, review, comm, mentions)
|
||||
|
||||
for _, lines := range review.CodeComments {
|
||||
for _, comments := range lines {
|
||||
for _, codeComment := range comments {
|
||||
mentions, err := issue.FindAndUpdateIssueMentions(ctx, doer, codeComment.Content)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
notification.NotifyPullRequestCodeComment(pr, codeComment, mentions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return review, comm, nil
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
|
||||
|
||||
// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
|
||||
func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, error) {
|
||||
if user == nil {
|
||||
return false, nil
|
||||
}
|
||||
headRepoPerm, err := models.GetUserRepoPermission(pull.HeadRepo, user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -72,3 +72,31 @@ func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoNam
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartRepositoryTransfer transfer a repo from one owner to a new one.
|
||||
// it make repository into pending transfer state, if doer can not create repo for new owner.
|
||||
func StartRepositoryTransfer(doer, newOwner *models.User, repo *models.Repository, teams []*models.Team) error {
|
||||
if repo.Status != models.RepositoryReady {
|
||||
return fmt.Errorf("repository is not ready for transfer")
|
||||
}
|
||||
|
||||
// Admin is always allowed to transfer || user transfer repo back to his account
|
||||
if doer.IsAdmin || doer.ID == newOwner.ID {
|
||||
return TransferOwnership(doer, newOwner, repo, teams)
|
||||
}
|
||||
|
||||
// If new owner is an org and user can create repos he can transfer directly too
|
||||
if newOwner.IsOrganization() {
|
||||
allowed, err := models.CanCreateOrgRepo(newOwner.ID, doer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if allowed {
|
||||
return TransferOwnership(doer, newOwner, repo, teams)
|
||||
}
|
||||
}
|
||||
|
||||
// Block Transfer, new feature will come in v1.14.0
|
||||
// https://github.com/go-gitea/gitea/pull/14792
|
||||
return models.ErrCancelled{}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="branch-list" class="scrolling menu reference-list-menu">
|
||||
<div id="branch-list" class="scrolling menu reference-list-menu {{if not .Issue}}new-issue{{end}}">
|
||||
{{if .Issue.Ref}}
|
||||
<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{$.i18n.Tr "repo.clear_ref"}}</a></strong></div>
|
||||
{{end}}
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="tag-list" class="scrolling menu reference-list-menu" style="display: none">
|
||||
<div id="tag-list" class="scrolling menu reference-list-menu {{if not .Issue}}new-issue{{end}}" style="display: none">
|
||||
{{if .Issue.Ref}}
|
||||
<div class="item text small" data-id="" data-id-selector="#ref_selector"><strong><a href="#">{{.i18n.Tr "repo.clear_ref"}}</a></strong></div>
|
||||
{{end}}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{{.i18n.Tr "repo.issues.label_modify"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<form class="ui edit-label form" action="{{$.Link}}/edit" method="post">
|
||||
<form class="ui edit-label form ignore-dirty" action="{{$.Link}}/edit" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input id="label-modal-id" name="id" type="hidden">
|
||||
<div class="ui grid">
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
{{if .IsProjectsEnabled}}
|
||||
<div class="ui divider"></div>
|
||||
|
||||
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown">
|
||||
<span class="text">
|
||||
<strong>{{.i18n.Tr "repo.issues.new.projects"}}</strong>
|
||||
@@ -518,18 +518,15 @@
|
||||
{{ if and .IsRepoAdmin (not .Repository.IsArchived) }}
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui watching">
|
||||
<div>
|
||||
<button class="fluid ui show-modal button {{if .Issue.IsLocked }} negative {{ end }}" data-modal="#lock">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{svg "octicon-key"}}
|
||||
{{.i18n.Tr "repo.issues.unlock"}}
|
||||
{{else}}
|
||||
{{svg "octicon-lock"}}
|
||||
{{.i18n.Tr "repo.issues.lock"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<button class="fluid ui show-modal button {{if .Issue.IsLocked }} negative {{ end }}" data-modal="#lock">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{svg "octicon-key"}}
|
||||
{{.i18n.Tr "repo.issues.unlock"}}
|
||||
{{else}}
|
||||
{{svg "octicon-lock"}}
|
||||
{{.i18n.Tr "repo.issues.lock"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -12215,7 +12215,7 @@
|
||||
"x-go-name": "Gitignores"
|
||||
},
|
||||
"issue_labels": {
|
||||
"description": "Issue Label set to use",
|
||||
"description": "Label-Set to use",
|
||||
"type": "string",
|
||||
"x-go-name": "IssueLabels"
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="hide" id="u2f-error-4">
|
||||
{{.i18n.Tr "u2f_error_4"}}
|
||||
</div>
|
||||
<div class="hide u2f-error-5">
|
||||
<div class="hide u2f_error_5">
|
||||
{{.i18n.Tr "u2f_error_5"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -96,7 +96,8 @@
|
||||
<span class="text truncate issue title">{{index .GetIssueInfos 1 | RenderEmoji}}</span>
|
||||
{{else if or (eq .GetOpType 10) (eq .GetOpType 21) (eq .GetOpType 22) (eq .GetOpType 23)}}
|
||||
<a href="{{.GetCommentLink}}" class="text truncate issue title">{{.GetIssueTitle | RenderEmoji}}</a>
|
||||
<p class="text light grey">{{index .GetIssueInfos 1 | RenderEmoji}}</p>
|
||||
{{$comment := index .GetIssueInfos 1}}
|
||||
{{if gt (len $comment) 0}}<p class="text light grey">{{$comment | RenderEmoji}}</p>{{end}}
|
||||
{{else if eq .GetOpType 11}}
|
||||
<p class="text light grey">{{index .GetIssueInfos 1}}</p>
|
||||
{{else if or (eq .GetOpType 12) (eq .GetOpType 13) (eq .GetOpType 14) (eq .GetOpType 15)}}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
{{.i18n.Tr "settings.edit_oauth2_application"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
<p>{{.i18n.Tr "settings.oauth2_application_create_description"}}</p>
|
||||
</div>
|
||||
<div class="ui attached segment form ignore-dirty">
|
||||
|
||||
4
vendor/code.gitea.io/sdk/gitea/admin_cron.go
generated
vendored
4
vendor/code.gitea.io/sdk/gitea/admin_cron.go
generated
vendored
@@ -25,7 +25,7 @@ type ListCronTaskOptions struct {
|
||||
|
||||
// ListCronTasks list available cron tasks
|
||||
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
@@ -36,7 +36,7 @@ func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response,
|
||||
|
||||
// RunCronTasks run a cron task
|
||||
func (c *Client) RunCronTasks(task string) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil)
|
||||
|
||||
2
vendor/code.gitea.io/sdk/gitea/client.go
generated
vendored
2
vendor/code.gitea.io/sdk/gitea/client.go
generated
vendored
@@ -56,7 +56,7 @@ func NewClient(url string, options ...func(*Client)) (*Client, error) {
|
||||
for _, opt := range options {
|
||||
opt(client)
|
||||
}
|
||||
if err := client.CheckServerVersionConstraint(">=1.10"); err != nil {
|
||||
if err := client.checkServerVersionGreaterThanOrEqual(version1_10_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
|
||||
2
vendor/code.gitea.io/sdk/gitea/go.mod
generated
vendored
2
vendor/code.gitea.io/sdk/gitea/go.mod
generated
vendored
@@ -3,6 +3,6 @@ module code.gitea.io/sdk/gitea
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
)
|
||||
|
||||
6
vendor/code.gitea.io/sdk/gitea/issue.go
generated
vendored
6
vendor/code.gitea.io/sdk/gitea/issue.go
generated
vendored
@@ -121,7 +121,7 @@ func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) {
|
||||
link, _ := url.Parse("/repos/issues/search")
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil {
|
||||
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
|
||||
for i := 0; i < len(issues); i++ {
|
||||
if issues[i].Repository != nil {
|
||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
||||
@@ -139,7 +139,7 @@ func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Iss
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo))
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil {
|
||||
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
|
||||
for i := 0; i < len(issues); i++ {
|
||||
if issues[i].Repository != nil {
|
||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
||||
@@ -153,7 +153,7 @@ func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Iss
|
||||
func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) {
|
||||
issue := new(Issue)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue)
|
||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil && issue.Repository != nil {
|
||||
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil {
|
||||
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0]
|
||||
}
|
||||
return issue, resp, err
|
||||
|
||||
2
vendor/code.gitea.io/sdk/gitea/issue_comment.go
generated
vendored
2
vendor/code.gitea.io/sdk/gitea/issue_comment.go
generated
vendored
@@ -68,7 +68,7 @@ func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentO
|
||||
// GetIssueComment get a comment for a given repo by id.
|
||||
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) {
|
||||
comment := new(Comment)
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return comment, nil, err
|
||||
}
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment)
|
||||
|
||||
2
vendor/code.gitea.io/sdk/gitea/issue_label.go
generated
vendored
2
vendor/code.gitea.io/sdk/gitea/issue_label.go
generated
vendored
@@ -71,7 +71,7 @@ func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label,
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(opt.Color) == 6 {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
opt.Color = "#" + opt.Color
|
||||
}
|
||||
}
|
||||
|
||||
12
vendor/code.gitea.io/sdk/gitea/issue_reaction.go
generated
vendored
12
vendor/code.gitea.io/sdk/gitea/issue_reaction.go
generated
vendored
@@ -20,7 +20,7 @@ type Reaction struct {
|
||||
|
||||
// GetIssueReactions get a list reactions of an issue
|
||||
func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reactions := make([]*Reaction, 0, 10)
|
||||
@@ -30,7 +30,7 @@ func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction
|
||||
|
||||
// GetIssueCommentReactions get a list of reactions from a comment of an issue
|
||||
func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reactions := make([]*Reaction, 0, 10)
|
||||
@@ -45,7 +45,7 @@ type editReactionOption struct {
|
||||
|
||||
// PostIssueReaction add a reaction to an issue
|
||||
func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reactionResponse := new(Reaction)
|
||||
@@ -61,7 +61,7 @@ func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction str
|
||||
|
||||
// DeleteIssueReaction remove a reaction from an issue
|
||||
func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
||||
@@ -74,7 +74,7 @@ func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction s
|
||||
|
||||
// PostIssueCommentReaction add a reaction to a comment of an issue
|
||||
func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reactionResponse := new(Reaction)
|
||||
@@ -90,7 +90,7 @@ func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, r
|
||||
|
||||
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
|
||||
func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
||||
|
||||
8
vendor/code.gitea.io/sdk/gitea/issue_subscription.go
generated
vendored
8
vendor/code.gitea.io/sdk/gitea/issue_subscription.go
generated
vendored
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// GetIssueSubscribers get list of users who subscribed on an issue
|
||||
func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
subscribers := make([]*User, 0, 10)
|
||||
@@ -21,7 +21,7 @@ func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User,
|
||||
|
||||
// AddIssueSubscription Subscribe user to issue
|
||||
func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
||||
@@ -39,7 +39,7 @@ func (c *Client) AddIssueSubscription(owner, repo string, index int64, user stri
|
||||
|
||||
// DeleteIssueSubscription unsubscribe user from issue
|
||||
func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
||||
@@ -57,7 +57,7 @@ func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user s
|
||||
|
||||
// CheckIssueSubscription check if current user is subscribed to an issue
|
||||
func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
wi := new(WatchInfo)
|
||||
|
||||
14
vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go
generated
vendored
14
vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go
generated
vendored
@@ -27,7 +27,7 @@ type TrackedTime struct {
|
||||
|
||||
// GetUserTrackedTimes list tracked times of a user
|
||||
func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
times := make([]*TrackedTime, 0, 10)
|
||||
@@ -37,7 +37,7 @@ func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime,
|
||||
|
||||
// GetRepoTrackedTimes list tracked times of a repository
|
||||
func (c *Client) GetRepoTrackedTimes(owner, repo string) ([]*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
times := make([]*TrackedTime, 0, 10)
|
||||
@@ -47,7 +47,7 @@ func (c *Client) GetRepoTrackedTimes(owner, repo string) ([]*TrackedTime, *Respo
|
||||
|
||||
// GetMyTrackedTimes list tracked times of the current user
|
||||
func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
times := make([]*TrackedTime, 0, 10)
|
||||
@@ -75,7 +75,7 @@ func (opt AddTimeOption) Validate() error {
|
||||
|
||||
// AddTime adds time to issue with the given index
|
||||
func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
@@ -99,7 +99,7 @@ type ListTrackedTimesOptions struct {
|
||||
|
||||
// ListTrackedTimes list tracked times of a single issue for a given repository
|
||||
func (c *Client) ListTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
@@ -110,7 +110,7 @@ func (c *Client) ListTrackedTimes(owner, repo string, index int64, opt ListTrack
|
||||
|
||||
// ResetIssueTime reset tracked time of a single issue for a given repository
|
||||
func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, nil)
|
||||
@@ -119,7 +119,7 @@ func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, err
|
||||
|
||||
// DeleteTime delete a specific tracked time by id of a single issue for a given repository
|
||||
func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), nil, nil)
|
||||
|
||||
24
vendor/code.gitea.io/sdk/gitea/notifications.go
generated
vendored
24
vendor/code.gitea.io/sdk/gitea/notifications.go
generated
vendored
@@ -8,6 +8,12 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
var (
|
||||
version1_12_3, _ = version.NewVersion("1.12.3")
|
||||
)
|
||||
|
||||
// NotificationThread expose Notification on API
|
||||
@@ -75,7 +81,7 @@ func (opt *ListNotificationOptions) QueryEncode() string {
|
||||
// Validate the CreateUserOption struct
|
||||
func (opt ListNotificationOptions) Validate(c *Client) error {
|
||||
if len(opt.Status) != 0 {
|
||||
return c.CheckServerVersionConstraint(">=1.12.3")
|
||||
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -98,14 +104,14 @@ func (opt *MarkNotificationOptions) QueryEncode() string {
|
||||
// Validate the CreateUserOption struct
|
||||
func (opt MarkNotificationOptions) Validate(c *Client) error {
|
||||
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 {
|
||||
return c.CheckServerVersionConstraint(">=1.12.3")
|
||||
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckNotifications list users's notification threads
|
||||
func (c *Client) CheckNotifications() (int64, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
new := struct {
|
||||
@@ -118,7 +124,7 @@ func (c *Client) CheckNotifications() (int64, *Response, error) {
|
||||
|
||||
// GetNotification get notification thread by ID
|
||||
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
thread := new(NotificationThread)
|
||||
@@ -129,7 +135,7 @@ func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, erro
|
||||
// ReadNotification mark notification thread as read by ID
|
||||
// It optionally takes a second argument if status has to be set other than 'read'
|
||||
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := fmt.Sprintf("/notifications/threads/%d", id)
|
||||
@@ -142,7 +148,7 @@ func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response,
|
||||
|
||||
// ListNotifications list users's notification threads
|
||||
func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
@@ -157,7 +163,7 @@ func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*Notification
|
||||
|
||||
// ReadNotifications mark notification threads as read
|
||||
func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
@@ -171,7 +177,7 @@ func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, erro
|
||||
|
||||
// ListRepoNotifications list users's notification threads on a specific repo
|
||||
func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
@@ -186,7 +192,7 @@ func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificat
|
||||
|
||||
// ReadRepoNotifications mark notification threads as read on a specific repo
|
||||
func (c *Client) ReadRepoNotifications(owner, reponame string, opt MarkNotificationOptions) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
|
||||
10
vendor/code.gitea.io/sdk/gitea/oauth2.go
generated
vendored
10
vendor/code.gitea.io/sdk/gitea/oauth2.go
generated
vendored
@@ -34,7 +34,7 @@ type CreateOauth2Option struct {
|
||||
|
||||
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
|
||||
func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
@@ -48,7 +48,7 @@ func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error
|
||||
|
||||
// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object.
|
||||
func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
@@ -62,7 +62,7 @@ func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2,
|
||||
|
||||
// GetOauth2 a specific Oauth2 Application by ID.
|
||||
func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
oauth2s := &Oauth2{}
|
||||
@@ -72,7 +72,7 @@ func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
||||
|
||||
// ListOauth2 all of your Oauth2 Applications.
|
||||
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
@@ -83,7 +83,7 @@ func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error)
|
||||
|
||||
// DeleteOauth2 delete an Oauth2 application by ID
|
||||
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil)
|
||||
|
||||
4
vendor/code.gitea.io/sdk/gitea/pull.go
generated
vendored
4
vendor/code.gitea.io/sdk/gitea/pull.go
generated
vendored
@@ -160,7 +160,7 @@ func (opt EditPullRequestOption) Validate(c *Client) error {
|
||||
return fmt.Errorf("title is empty")
|
||||
}
|
||||
if len(opt.Base) != 0 {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return fmt.Errorf("can not change base gitea to old")
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Re
|
||||
|
||||
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
|
||||
func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||
r, _, err2 := c.GetRepo(owner, repo)
|
||||
if err2 != nil {
|
||||
return nil, nil, err
|
||||
|
||||
12
vendor/code.gitea.io/sdk/gitea/pull_review.go
generated
vendored
12
vendor/code.gitea.io/sdk/gitea/pull_review.go
generated
vendored
@@ -132,7 +132,7 @@ func (opt CreatePullReviewComment) Validate() error {
|
||||
|
||||
// ListPullReviews lists all reviews of a pull request
|
||||
func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
@@ -147,7 +147,7 @@ func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullRe
|
||||
|
||||
// GetPullReview gets a specific review of a pull request
|
||||
func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview
|
||||
|
||||
// ListPullReviewComments lists all comments of a pull request review
|
||||
func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rcl := make([]*PullReviewComment, 0, 4)
|
||||
@@ -170,7 +170,7 @@ func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]
|
||||
|
||||
// DeletePullReview delete a specific review from a pull request
|
||||
func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Respons
|
||||
|
||||
// CreatePullReview create a review to an pull request
|
||||
func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
@@ -200,7 +200,7 @@ func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePul
|
||||
|
||||
// SubmitPullReview submit a pending review to an pull request
|
||||
func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
|
||||
2
vendor/code.gitea.io/sdk/gitea/release.go
generated
vendored
2
vendor/code.gitea.io/sdk/gitea/release.go
generated
vendored
@@ -57,7 +57,7 @@ func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, e
|
||||
|
||||
// GetReleaseByTag get a release of a repository by tag
|
||||
func (c *Client) GetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) {
|
||||
if c.CheckServerVersionConstraint(">=1.13.0") != nil {
|
||||
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||
return c.fallbackGetReleaseByTag(user, repo, tag)
|
||||
}
|
||||
r := new(Release)
|
||||
|
||||
40
vendor/code.gitea.io/sdk/gitea/repo.go
generated
vendored
40
vendor/code.gitea.io/sdk/gitea/repo.go
generated
vendored
@@ -73,6 +73,20 @@ const (
|
||||
RepoTypeMirror RepoType = "mirror"
|
||||
)
|
||||
|
||||
// TrustModel represent how git signatures are handled in a repository
|
||||
type TrustModel string
|
||||
|
||||
const (
|
||||
// TrustModelDefault use TM set by global config
|
||||
TrustModelDefault TrustModel = "default"
|
||||
// TrustModelCollaborator gpg signature has to be owned by a repo collaborator
|
||||
TrustModelCollaborator TrustModel = "collaborator"
|
||||
// TrustModelCommitter gpg signature has to match committer
|
||||
TrustModelCommitter TrustModel = "committer"
|
||||
// TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator
|
||||
TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter"
|
||||
)
|
||||
|
||||
// ListReposOptions options for listing repositories
|
||||
type ListReposOptions struct {
|
||||
ListOptions
|
||||
@@ -224,7 +238,7 @@ func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, e
|
||||
} else {
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
// IsPrivate only works on gitea >= 1.12.0
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil && opt.IsPrivate != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil {
|
||||
if *opt.IsPrivate {
|
||||
// private repos only not supported on gitea <= 1.11.x
|
||||
return nil, nil, err
|
||||
@@ -249,6 +263,8 @@ type CreateRepoOption struct {
|
||||
IssueLabels string `json:"issue_labels"`
|
||||
// Whether the repository should be auto-intialized?
|
||||
AutoInit bool `json:"auto_init"`
|
||||
// Whether the repository is template
|
||||
Template bool `json:"template"`
|
||||
// Gitignores to use
|
||||
Gitignores string `json:"gitignores"`
|
||||
// License to use
|
||||
@@ -257,19 +273,35 @@ type CreateRepoOption struct {
|
||||
Readme string `json:"readme"`
|
||||
// DefaultBranch of the repository (used when initializes and in template)
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
// TrustModel of the repository
|
||||
TrustModel TrustModel `json:"trust_model"`
|
||||
}
|
||||
|
||||
// Validate the CreateRepoOption struct
|
||||
func (opt CreateRepoOption) Validate() error {
|
||||
func (opt CreateRepoOption) Validate(c *Client) error {
|
||||
if len(strings.TrimSpace(opt.Name)) == 0 {
|
||||
return fmt.Errorf("name is empty")
|
||||
}
|
||||
if len(opt.Name) > 100 {
|
||||
return fmt.Errorf("name has more than 100 chars")
|
||||
}
|
||||
if len(opt.Description) > 255 {
|
||||
return fmt.Errorf("name has more than 255 chars")
|
||||
}
|
||||
if len(opt.DefaultBranch) > 100 {
|
||||
return fmt.Errorf("name has more than 100 chars")
|
||||
}
|
||||
if len(opt.TrustModel) != 0 {
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRepo creates a repository for authenticated user.
|
||||
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) {
|
||||
if err := opt.Validate(); err != nil {
|
||||
if err := opt.Validate(c); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
@@ -283,7 +315,7 @@ func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error
|
||||
|
||||
// CreateOrgRepo creates an organization repository for authenticated user.
|
||||
func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
|
||||
if err := opt.Validate(); err != nil {
|
||||
if err := opt.Validate(c); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
|
||||
4
vendor/code.gitea.io/sdk/gitea/repo_branch.go
generated
vendored
4
vendor/code.gitea.io/sdk/gitea/repo_branch.go
generated
vendored
@@ -84,7 +84,7 @@ func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, e
|
||||
|
||||
// DeleteRepoBranch delete a branch in a repository
|
||||
func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil)
|
||||
@@ -118,7 +118,7 @@ func (opt CreateBranchOption) Validate() error {
|
||||
|
||||
// CreateBranch creates a branch for a user's repository
|
||||
func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
|
||||
10
vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go
generated
vendored
10
vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go
generated
vendored
@@ -92,7 +92,7 @@ type ListBranchProtectionsOptions struct {
|
||||
|
||||
// ListBranchProtections list branch protections for a repo
|
||||
func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bps := make([]*BranchProtection, 0, opt.PageSize)
|
||||
@@ -104,7 +104,7 @@ func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtect
|
||||
|
||||
// GetBranchProtection gets a branch protection
|
||||
func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bp := new(BranchProtection)
|
||||
@@ -114,7 +114,7 @@ func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtectio
|
||||
|
||||
// CreateBranchProtection creates a branch protection for a repo
|
||||
func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bp := new(BranchProtection)
|
||||
@@ -128,7 +128,7 @@ func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProt
|
||||
|
||||
// EditBranchProtection edits a branch protection for a repo
|
||||
func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
bp := new(BranchProtection)
|
||||
@@ -142,7 +142,7 @@ func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchPr
|
||||
|
||||
// DeleteBranchProtection deletes a branch protection for a repo
|
||||
func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user