Compare commits

..

42 Commits

Author SHA1 Message Date
zeripath
3c8842d58c v1.10.4 Changelog (#10294)
* v1.10.4 Changelog

* Add backport identifier for #10261

* Update CHANGELOG.md entry for #9884

* Workaround docker-library/postgres#658

* Update .drone.yml
2020-02-16 22:45:38 +02:00
Lunny Xiao
e786f098d5 Fix reply on code review (#10261)
* Fix branch page pull request title and link error (#10092)

* Fix branch page pull request title and link error

* Fix ui

* Fix reply on code review (#10227)

Co-authored-by: zeripath <art27@cantab.net>

* Revert unrelated change

* Fix lint

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
2020-02-14 13:53:36 +02:00
Lunny Xiao
22dec1cea6 Fix branch page pull request title and link error (#10092) (#10098)
* Fix branch page pull request title and link error (#10092)
* Fix tmpl
2020-02-01 17:37:52 +00:00
Lunny Xiao
0c5e2e2e4c Fix milestone API state parameter unhandled (#10049) (#10053)
* Fix milestone API state parameter unhandled (#10049)

* Fix milestone API state parameter unhandled

* Fix test

* Fix test
2020-01-30 11:49:02 +08:00
Lunny Xiao
158b716387 Fix wiki raw view on sub path (#10002) (#10041)
* Fix wiki raw view on sub path

* Add test for subpath wiki raw file

* Fix bug

Co-authored-by: zeripath <art27@cantab.net>
2020-01-28 15:10:40 +00:00
John Olheiser
e611dbbe86 Fix RocketChat (#9925)
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2020-01-22 10:07:11 +02:00
dioss-Machiel
68bca621cd Prevent empty LDAP search from deactivating all users (#9879) (#9890)
* Backport of #9879 (Add option to prevent LDAP from deactivating everything on empty search)

* go fmtted

Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com>
Co-authored-by: zeripath <art27@cantab.net>
2020-01-20 15:02:35 -05:00
Lunny Xiao
c4e0f717e7 fix bug about wrong dependencies permissions check and other wr… (#9884)
* fix bug about wrong dependencies permissions check and other wrong permissions check

* improve code
2020-01-20 16:45:42 +01:00
zeripath
e3e024876e Ensure that 2fa is checked on reset-password (#9857) (#9877)
* Ensure that 2fa is checked on reset-password

* Apply suggestions from code review

Co-Authored-By: Lauris BH <lauris@nix.lv>

* Properly manage scratch_code regeneration

Co-authored-by: Lauris BH <lauris@nix.lv>

Co-authored-by: Lauris BH <lauris@nix.lv>
2020-01-19 18:37:28 -05:00
6543
cebc125f7f Changelog 1.10.3 (#9832) 2020-01-17 16:18:34 -05:00
6543
c975287149 fix (#9838) 2020-01-17 23:25:46 +08:00
Lunny Xiao
b31068652a Fix download file wrong content-type (#9825) (#9835)
* Fix download file wrong content-type

* change the error text to be more precise

* fix test

Co-authored-by: Lauris BH <lauris@nix.lv>
2020-01-17 14:56:25 +01:00
Lunny Xiao
918e640590 Fix wrong identify poster on a migrated pull request when submi… (#9827) (#9831) 2020-01-17 13:38:53 +01:00
Lunny Xiao
9809fe27c4 fix dump non-exist log directory (#9818) (#9820)
Co-authored-by: Lauris BH <lauris@nix.lv>
2020-01-17 08:51:21 +01:00
Lunny Xiao
ed6a2f2e2e Fix compare (#9808) (#9815)
Co-authored-by: techknowlogick <matti@mdranta.net>

Co-authored-by: techknowlogick <matti@mdranta.net>
2020-01-17 10:08:07 +08:00
Lunny Xiao
a0435fcd63 Fix missing msteam webhook on organization (#9781) (#9795) 2020-01-16 07:36:05 +02:00
David Svantesson
a2e2045a96 Fix #9662 (#9783) 2020-01-16 01:26:55 +01:00
Lauris BH
b67a023bec Send tag create and push webhook when release created on UI (#8671) (#9702)
* 'update'

* Send push tag event when release created

* send tag create event while release created in UI

* update to go v1.13

* fix gofmt error

* update #8671 move release tag created hook to modules/notification/webhook due to #8802 refactoring

* use NotifyCreateRef and NotifyPushCommits instead of NotifyNewReleaseTag

* move tag notification to correct place

Co-authored-by: Benno <blueworrybear@gmail.com>
2020-01-13 07:09:13 +00:00
Lauris BH
b1a90f7286 Hide credentials when submitting migration (#9102) (#9704)
through API.
Same fix, using form.CloneAddr instead of opts.CloneAddr.

Co-authored-by: Jordan <eatsleepgame@gmail.com>
2020-01-11 17:25:16 +02:00
Lauris BH
45c8a3aeeb Fix cache problem on dashboard (#9358) (#9703)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-01-11 16:23:40 +02:00
zeripath
0e5126da22 Never allow an empty password to validate (#9682) (#9684)
* Restore IsPasswordSet previous value

* Update models/user.go
2020-01-11 13:14:03 +02:00
zeripath
cd3e52d91b Prevent redirect to Host (#9678) (#9680) 2020-01-09 16:38:12 -05:00
zeripath
319c92163f Branches not at ref commit ID should not be listed as Merged (#9614) (#9639)
Once a branch has been merged if the commit ID no longer equals that of
the pulls ref commit id don't offer to delete the branch on the pull screen
and don't list it as merged on branches.

Fix #9201

When looking at the pull page we should also get the commits from the refs/pulls/x/head

Fix #9158
2020-01-08 03:06:53 +02:00
6543
a15a644c05 [BugFix] Hide public repos owned by private orgs (#9616) 2020-01-06 10:12:55 +08:00
6543
2ccd92407a Changelog for 1.10.2 (#9585)
* Changelog for 1.10.2

* imprufe

* fix misspell

* Update CHANGELOG.md
2020-01-02 19:04:19 +08:00
6543
17c691f8aa [Backport] [API] Allow only specific Colums to be updated on Issue (#9539) (#9580)
* Fix #9189 - API Allow only specific Colums to be updated on Issue (#9539)

* dont insert "-1" in any case to issue.poster_id

* Make sure API cant override importand fields

* code format

* add Test for IssueEdit

* load milestone and return it on IssueEdit via API

* extend Test for TestAPIEditIssue

* fix TEST

* make sure Poster is loaded

* keep code format on backport as it is
2020-01-02 02:38:42 -05:00
6543
cb3fe4cbf1 [Backport] Fix bug when migrate from API (#8631) (#9563)
* Fix bug when migrate from API (#8631)

* fix bug when migrate from API

* fix test

* fix test

* improve

* fix error message

* Update routers/api/v1/repo/repo.go

* remove unrelated

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-01-02 01:36:00 +02:00
6543
c63a80138a backport part of #9550 (#9564)
* add ErrReactionAlreadyExist
 * extend FindReactionsOptions
 * createReaction check if exit before create
2019-12-31 19:00:17 +02:00
6543
b4b8c9679f fix 500 error for ghost avatar (#9537) 2019-12-30 18:55:51 +08:00
Lunny Xiao
c0bb5ebc15 Fix repository issues pagination bug when there are more than one label filter (#9512) (#9528)
* Fix repository issues pagination bug when there are more than one label filter (#9512)

* fix merge
2019-12-28 23:54:24 +00:00
Lunny Xiao
9409ac9030 Fix deleted branch isn't removed when push the branch again (#9516) (#9524) 2019-12-28 12:41:41 +08:00
Lunny Xiao
a3928fd820 Fix missing repository status when migrating repository via API (#9511)
* Fix API migration wrong repository status

* Force push for ci
2019-12-27 14:34:28 +02:00
Brad Albright
f0bda12c49 when branch is deleted after a pull request is merged, trigger webhook (#9510) 2019-12-27 14:36:52 +08:00
Brad Albright
58c38ab4b6 backport fix: fixed bug in GitTreeBySHA where pulling items from a page other than page 1 would fail because the wrong var was used to set the entries to return (#9482) 2019-12-24 07:01:02 +00:00
John Olheiser
a276aaf61e Fix NewCommitStatus (#9434) (#9435)
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2019-12-19 23:49:47 +01:00
mrsdizzie
e03934f035 Use OriginalURL insead of CloneAddr in migration logging (#9418) (#9420)
CloneAddr will contain username and password credentials and they will
get stored in system notices about failed migrations (and logs if trace
is set). Replace with OriginalURL that doesn't have those.
2019-12-18 21:02:47 -05:00
Cornel
3a14a69e8a Fix Slack webhook payload title generation to work with Mattermost (#9378) (#9404) 2019-12-18 20:56:35 +08:00
zeripath
f0f48e0fff DefaultBranch needs to be prefixed by BranchPrefix (#9356) (#9359) 2019-12-15 11:08:19 +00:00
Lunny Xiao
1aeeaa8e89 fix issue indexer not triggered when migrating a repository (#9333) 2019-12-13 13:07:11 +08:00
Lunny Xiao
eb8d5f6aff Fix bug that release attachment files not deleted when deleting repository (#9322) (#9329)
* Fix bug that release attachment files not deleted when deleting repository

* improve code

* add quote

* improve code
2019-12-12 15:05:44 +08:00
John Olheiser
9ef148abeb Fix migration releases (#9319) (#9326) (#9328)
* Only sync tags after all batches (#9319)

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add SyncTags to uploader interface (#9326)

* Add sync tags to interface

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix revive

Signed-off-by: jolheiser <john.olheiser@gmail.com>
2019-12-11 23:38:58 -05:00
6543
f11df80058 File Edit: Author/Committer interchanged [BugFix] (#9300) 2019-12-09 21:12:36 +08:00
86 changed files with 1057 additions and 375 deletions

View File

@@ -1,44 +1,44 @@
repo: go-gitea/gitea
groups:
-
-
name: BREAKING
labels:
- kind/breaking
-
-
name: FEATURE
labels:
- kind/feature
-
name: SECURITY
labels:
- kind/security
-
name: BUGFIXES
labels:
- kind/bug
-
-
name: ENHANCEMENT
labels:
- kind/enhancement
- kind/refactor
- kind/ui
-
name: SECURITY
labels:
- kind/security
-
name: TESTING
labels:
- kind/testing
-
-
name: TRANSLATION
labels:
- kind/translation
-
-
name: BUILD
labels:
- kind/build
- kind/lint
-
-
name: DOCS
labels:
- kind/docs
-
-
name: MISC
default: true
default: true

View File

@@ -30,6 +30,7 @@ services:
image: postgres:9.5
environment:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
- name: mssql
pull: default

View File

@@ -4,6 +4,57 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.10.4](https://github.com/go-gitea/gitea/releases/tag/v1.10.4) - 2020-02-16
* FEATURE
* Prevent empty LDAP search from deactivating all users (#9879) (#9890)
* BUGFIXES
* Fix reply on code review (#10261) (#10227)
* Fix branch page pull request title and link error (#10092) (#10098)
* Fix milestone API state parameter unhandled (#10049) (#10053)
* Fix wiki raw view on sub path (#10002) (#10041)
* Fix RocketChat Webhook (#9908) (#9921) (#9925)
* Fix bug about wrong dependencies permissions check and other wrong permissions check (#9884) (Partial backport #9842)
* Ensure that 2fa is checked on reset-password (#9857) (#9877)
## [1.10.3](https://github.com/go-gitea/gitea/releases/tag/v1.10.3) - 2020-01-17
* SECURITY
* Hide credentials when submitting migration (#9102) (#9704)
* Never allow an empty password to validate (#9682) (#9684)
* Prevent redirect to Host (#9678) (#9680)
* Hide public repos owned by private orgs (#9609) (#9616)
* BUGFIXES
* Allow assignee on Pull Creation when Issue Unit is deactivated (#9836) (#9838)
* Fix download file wrong content-type (#9825) (#9835)
* Fix wrong identify poster on a migrated pull request when submit review (#9827) (#9831)
* Fix dump non-exist log directory (#9818) (#9820)
* Fix compare (#9808) (#9815)
* Fix missing msteam webhook on organization (#9781) (#9795)
* Fix add team on collaborator page when same name as organization (#9783)
* Fix cache problem on dashboard (#9358) (#9703)
* Send tag create and push webhook when release created on UI (#8671) (#9702)
* Branches not at ref commit ID should not be listed as Merged (#9614) (#9639)
## [1.10.2](https://github.com/go-gitea/gitea/releases/tag/v1.10.2) - 2020-01-02
* BUGFIXES
* Allow only specific Columns to be updated on Issue via API (#9539) (#9580)
* Add ErrReactionAlreadyExist error (#9550) (#9564)
* Fix bug when migrate from API (#8631) (#9563)
* Use default avatar for ghost user (#9536) (#9537)
* Fix repository issues pagination bug when there are more than one label filter (#9512) (#9528)
* Fix deleted branch not removed when push the branch again (#9516) (#9524)
* Fix missing repository status when migrating repository via API (#9511)
* Trigger webhook when deleting a branch after merging a PR (#9510)
* Fix paging on /repos/{owner}/{repo}/git/trees/{sha} API endpoint (#9482)
* Fix NewCommitStatus (#9434) (#9435)
* Use OriginalURL instead of CloneAddr in migration logging (#9418) (#9420)
* Fix Slack webhook payload title generation to work with Mattermost (#9404)
* DefaultBranch needs to be prefixed by BranchPrefix (#9356) (#9359)
* Fix issue indexer not triggered when migrating a repository (#9333)
* Fix bug that release attachment files not deleted when deleting repository (#9322) (#9329)
* Fix migration releases (#9319) (#9326) (#9328)
* Fix File Edit: Author/Committer interchanged (#9297) (#9300)
## [1.10.1](https://github.com/go-gitea/gitea/releases/tag/v1.10.1) - 2019-12-05
* BUGFIXES
* Fix max length check and limit in multiple repo forms (#9148) (#9204)
@@ -48,7 +99,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Add option to initialize repository with labels (#6061)
* Add additional password hash algorithms (#6023)
* BUGFIXES
* Allow to merge if file path contains " or \ (#8629) (#8771)
* Allow to merge if file path contains " or \ (#8629) (#8771)
* On windows set core.longpaths true (#8776) (#8786)
* Fix 500 when edit hook (#8782) (#8789)
* Fix Checkbox at RepoSettings Protected Branch (#8799) (#8801)
@@ -60,7 +111,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Fix require external registration password (#8885) (#8890)
* Fix password complexity check on registration (#8887) (#8888)
* Update Github Migration Tests (#8896) (#8938) (#8945)
* Enable punctuations ending mentions (#8889) (#8894)
* Enable punctuations ending mentions (#8889) (#8894)
* Add Close() method to gogitRepository (#8901) (#8956)
* Hotfix for review actions and notifications (#8965)
* Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) (#8618)

View File

@@ -61,6 +61,10 @@ var (
Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
},
cli.BoolFlag{
Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.",
},
cli.StringFlag{
Name: "username-attribute",
Usage: "The attribute of the users LDAP record containing the user name.",
@@ -231,6 +235,9 @@ func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
if c.IsSet("admin-filter") {
config.Source.AdminFilter = c.String("admin-filter")
}
if c.IsSet("allow-deactivate-all") {
config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
}
return nil
}

View File

@@ -145,8 +145,10 @@ func runDump(ctx *cli.Context) error {
}
}
if err := z.AddDir("log", setting.LogRootPath); err != nil {
log.Fatalf("Failed to include log: %v", err)
if com.IsExist(setting.LogRootPath) {
if err := z.AddDir("log", setting.LogRootPath); err != nil {
log.Fatalf("Failed to include log: %v", err)
}
}
if err = z.Close(); err != nil {

View File

@@ -0,0 +1,47 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"fmt"
"net/http"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestAPIIssuesMilestone(t *testing.T) {
prepareTestEnv(t)
milestone := models.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: milestone.RepoID}).(*models.Repository)
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
assert.Equal(t, int64(1), int64(milestone.NumIssues))
assert.Equal(t, structs.StateOpen, milestone.State())
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session)
// update values of issue
milestoneState := "closed"
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, milestone.ID, token)
req := NewRequestWithJSON(t, "PATCH", urlStr, structs.EditMilestoneOption{
State: &milestoneState,
})
resp := session.MakeRequest(t, req, http.StatusOK)
var apiMilestone structs.Milestone
DecodeJSON(t, resp, &apiMilestone)
assert.EqualValues(t, "closed", apiMilestone.State)
req = NewRequest(t, "GET", urlStr)
resp = session.MakeRequest(t, req, http.StatusOK)
var apiMilestone2 structs.Milestone
DecodeJSON(t, resp, &apiMilestone2)
assert.EqualValues(t, "closed", apiMilestone2.State)
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"net/http"
"testing"
"time"
"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
@@ -62,3 +63,61 @@ func TestAPICreateIssue(t *testing.T) {
Title: title,
})
}
func TestAPIEditIssue(t *testing.T) {
prepareTestEnv(t)
issueBefore := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 9}).(*models.Issue)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: issueBefore.RepoID}).(*models.Repository)
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
assert.NoError(t, issueBefore.LoadAttributes())
assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
assert.Equal(t, api.StateOpen, issueBefore.State())
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session)
// update values of issue
issueState := "closed"
removeDeadline := time.Unix(0, 0)
milestone := int64(4)
body := "new content!"
title := "new title from api set"
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repo.Name, issueBefore.Index, token)
req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
State: &issueState,
Deadline: &removeDeadline,
Milestone: &milestone,
Body: &body,
Title: title,
// ToDo change more
})
resp := session.MakeRequest(t, req, http.StatusCreated)
var apiIssue api.Issue
DecodeJSON(t, resp, &apiIssue)
issueAfter := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 9}).(*models.Issue)
// check deleted user
assert.Equal(t, int64(500), issueAfter.PosterID)
assert.NoError(t, issueAfter.LoadAttributes())
assert.Equal(t, int64(-1), issueAfter.PosterID)
assert.Equal(t, int64(-1), issueBefore.PosterID)
assert.Equal(t, int64(-1), apiIssue.Poster.ID)
// API response
assert.Equal(t, api.StateClosed, apiIssue.State)
assert.Equal(t, milestone, apiIssue.Milestone.ID)
assert.Equal(t, body, apiIssue.Body)
assert.True(t, apiIssue.Deadline == nil)
assert.Equal(t, title, apiIssue.Title)
// in database
assert.Equal(t, api.StateClosed, issueAfter.State())
assert.Equal(t, milestone, issueAfter.MilestoneID)
assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix))
assert.Equal(t, body, issueAfter.Content)
assert.Equal(t, title, issueAfter.Title)
}

View File

@@ -30,12 +30,12 @@ func getCreateFileOptions() api.CreateFileOptions {
NewBranchName: "master",
Message: "Making this new file new/file.txt",
Author: api.Identity{
Name: "John Doe",
Email: "johndoe@example.com",
Name: "Anne Doe",
Email: "annedoe@example.com",
},
Committer: api.Identity{
Name: "Jane Doe",
Email: "janedoe@example.com",
Name: "John Doe",
Email: "johndoe@example.com",
},
},
Content: contentEncoded,
@@ -77,8 +77,8 @@ func getExpectedFileResponseForCreate(commitID, treePath string) *api.FileRespon
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Jane Doe",
Email: "janedoe@example.com",
Name: "Anne Doe",
Email: "annedoe@example.com",
},
},
Committer: &api.CommitUser{

View File

@@ -35,8 +35,8 @@ func getUpdateFileOptions() *api.UpdateFileOptions {
Email: "johndoe@example.com",
},
Committer: api.Identity{
Name: "Jane Doe",
Email: "janedoe@example.com",
Name: "Anne Doe",
Email: "annedoe@example.com",
},
},
SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
@@ -80,14 +80,14 @@ func getExpectedFileResponseForUpdate(commitID, treePath string) *api.FileRespon
HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Jane Doe",
Email: "janedoe@example.com",
Name: "John Doe",
Email: "johndoe@example.com",
},
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "John Doe",
Email: "johndoe@example.com",
Name: "Anne Doe",
Email: "annedoe@example.com",
},
},
Message: "My update of README.md\n",

View File

@@ -334,7 +334,7 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) {
resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict)
respJSON := map[string]string{}
DecodeJSON(t, resp, &respJSON)
assert.Equal(t, respJSON["message"], "The repository with the same name already exists.")
assert.Equal(t, "The repository with the same name already exists.", respJSON["message"])
})
}

View File

@@ -0,0 +1 @@
4a357436d925b5c974181ff12a994538ddc5a269

View File

@@ -1 +1 @@
0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3
423313fbd38093bb10d0c8387db9105409c6f196

View File

@@ -480,6 +480,12 @@ func (deletedBranch *DeletedBranch) LoadUser() {
deletedBranch.DeletedBy = user
}
// RemoveDeletedBranch removes all deleted branches
func RemoveDeletedBranch(repoID int64, branch string) error {
_, err := x.Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch))
return err
}
// RemoveOldDeletedBranches removes old deleted branches
func RemoveOldDeletedBranches() {
log.Trace("Doing: DeletedBranchesCleanup")

View File

@@ -1104,6 +1104,21 @@ func (err ErrNewIssueInsert) Error() string {
return err.OriginalError.Error()
}
// ErrReactionAlreadyExist is used when a existing reaction was try to created
type ErrReactionAlreadyExist struct {
Reaction string
}
// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist.
func IsErrReactionAlreadyExist(err error) bool {
_, ok := err.(ErrReactionAlreadyExist)
return ok
}
func (err ErrReactionAlreadyExist) Error() string {
return fmt.Sprintf("reaction '%s' already exists", err.Reaction)
}
// __________ .__ .__ __________ __
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\

View File

@@ -96,4 +96,17 @@
is_closed: false
is_pull: true
created_unix: 946684820
updated_unix: 978307180
updated_unix: 978307180
-
id: 9
repo_id: 42
index: 1
poster_id: 500
name: issue from deleted account
content: content from deleted account
is_closed: false
is_pull: false
created_unix: 946684830
updated_unix: 999307200
deadline_unix: 1019307200

View File

@@ -21,3 +21,11 @@
content: content3
is_closed: true
num_issues: 0
-
id: 4
repo_id: 42
name: milestone of repo42
content: content random
is_closed: false
num_issues: 0

View File

@@ -547,7 +547,8 @@
is_private: false
num_stars: 0
num_forks: 0
num_issues: 0
num_issues: 1
num_milestones: 1
is_mirror: false
-

View File

@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@@ -238,6 +239,16 @@ func (issue *Issue) loadReactions(e Engine) (err error) {
return nil
}
func (issue *Issue) loadMilestone(e Engine) (err error) {
if issue.Milestone == nil && issue.MilestoneID > 0 {
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
if err != nil && !IsErrMilestoneNotExist(err) {
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
}
}
return nil
}
func (issue *Issue) loadAttributes(e Engine) (err error) {
if err = issue.loadRepo(e); err != nil {
return
@@ -251,11 +262,8 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
return
}
if issue.Milestone == nil && issue.MilestoneID > 0 {
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
if err != nil && !IsErrMilestoneNotExist(err) {
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
}
if err = issue.loadMilestone(e); err != nil {
return
}
if err = issue.loadAssignees(e); err != nil {
@@ -295,6 +303,11 @@ func (issue *Issue) LoadAttributes() error {
return issue.loadAttributes(x)
}
// LoadMilestone load milestone of this issue.
func (issue *Issue) LoadMilestone() error {
return issue.loadMilestone(x)
}
// GetIsRead load the `IsRead` field of the issue
func (issue *Issue) GetIsRead(userID int64) error {
issueUser := &IssueUser{IssueID: issue.ID, UID: userID}
@@ -416,7 +429,7 @@ func (issue *Issue) HashTag() string {
// IsPoster returns true if given user by ID is the poster.
func (issue *Issue) IsPoster(uid int64) bool {
return issue.PosterID == uid
return issue.OriginalAuthorID == 0 && issue.PosterID == uid
}
func (issue *Issue) hasLabel(e Engine, labelID int64) bool {
@@ -1730,22 +1743,17 @@ func SearchIssueIDsByKeyword(kw string, repoID int64, limit, start int) (int64,
return total, ids, nil
}
func updateIssue(e Engine, issue *Issue) error {
_, err := e.ID(issue.ID).AllCols().Update(issue)
if err != nil {
return err
}
return nil
}
// UpdateIssue updates all fields of given issue.
func UpdateIssue(issue *Issue) error {
// UpdateIssueByAPI updates all allowed fields of given issue.
func UpdateIssueByAPI(issue *Issue) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := updateIssue(sess, issue); err != nil {
if _, err := sess.ID(issue.ID).Cols(
"name", "is_closed", "content", "milestone_id", "priority",
"deadline_unix", "updated_unix", "closed_unix", "is_locked").
Update(issue); err != nil {
return err
}
if err := issue.neuterCrossReferences(sess); err != nil {

View File

@@ -253,12 +253,33 @@ func updateMilestone(e Engine, m *Milestone) error {
}
// UpdateMilestone updates information of given milestone.
func UpdateMilestone(m *Milestone) error {
if err := updateMilestone(x, m); err != nil {
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
return updateMilestoneCompleteness(x, m.ID)
if m.IsClosed && !oldIsClosed {
m.ClosedDateUnix = timeutil.TimeStampNow()
}
if err := updateMilestone(sess, m); err != nil {
return err
}
if err := updateMilestoneCompleteness(sess, m.ID); err != nil {
return err
}
// if IsClosed changed, update milestone numbers of repository
if oldIsClosed != m.IsClosed {
if err := updateRepoMilestoneNum(sess, m.RepoID); err != nil {
return err
}
}
return sess.Commit()
}
func updateMilestoneCompleteness(e Engine, milestoneID int64) error {

View File

@@ -160,8 +160,9 @@ func TestUpdateMilestone(t *testing.T) {
milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
milestone.Name = "newMilestoneName"
milestone.Content = "newMilestoneContent"
assert.NoError(t, UpdateMilestone(milestone))
AssertExistsAndLoadBean(t, milestone)
assert.NoError(t, UpdateMilestone(milestone, milestone.IsClosed))
milestone = AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.EqualValues(t, "newMilestoneName", milestone.Name)
CheckConsistencyFor(t, &Milestone{})
}

View File

@@ -30,6 +30,8 @@ type Reaction struct {
type FindReactionsOptions struct {
IssueID int64
CommentID int64
UserID int64
Reaction string
}
func (opts *FindReactionsOptions) toConds() builder.Cond {
@@ -40,6 +42,13 @@ func (opts *FindReactionsOptions) toConds() builder.Cond {
if opts.CommentID > 0 {
cond = cond.And(builder.Eq{"reaction.comment_id": opts.CommentID})
}
if opts.UserID > 0 {
cond = cond.And(builder.Eq{"reaction.user_id": opts.UserID})
}
if opts.Reaction != "" {
cond = cond.And(builder.Eq{"reaction.type": opts.Reaction})
}
return cond
}
@@ -57,9 +66,25 @@ func createReaction(e *xorm.Session, opts *ReactionOptions) (*Reaction, error) {
UserID: opts.Doer.ID,
IssueID: opts.Issue.ID,
}
findOpts := FindReactionsOptions{
IssueID: opts.Issue.ID,
CommentID: -1, // reaction to issue only
Reaction: opts.Type,
UserID: opts.Doer.ID,
}
if opts.Comment != nil {
reaction.CommentID = opts.Comment.ID
findOpts.CommentID = opts.Comment.ID
}
existingR, err := findReactions(e, findOpts)
if err != nil {
return nil, err
}
if len(existingR) > 0 {
return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type}
}
if _, err := e.Insert(reaction); err != nil {
return nil, err
}
@@ -76,19 +101,19 @@ type ReactionOptions struct {
}
// CreateReaction creates reaction for issue or comment.
func CreateReaction(opts *ReactionOptions) (reaction *Reaction, err error) {
func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
if err := sess.Begin(); err != nil {
return nil, err
}
reaction, err = createReaction(sess, opts)
reaction, err := createReaction(sess, opts)
if err != nil {
return nil, err
return reaction, err
}
if err = sess.Commit(); err != nil {
if err := sess.Commit(); err != nil {
return nil, err
}
return reaction, nil

View File

@@ -50,9 +50,10 @@ func TestIssueAddDuplicateReaction(t *testing.T) {
Type: "heart",
})
assert.Error(t, err)
assert.Nil(t, reaction)
assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err)
AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID})
existingR := AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction)
assert.Equal(t, existingR.ID, reaction.ID)
}
func TestIssueDeleteReaction(t *testing.T) {
@@ -129,7 +130,6 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
user4 := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
ghost := NewGhostUser()
issue1 := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue)
@@ -139,14 +139,13 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
addReaction(t, user2, issue1, comment1, "heart")
addReaction(t, user3, issue1, comment1, "heart")
addReaction(t, user4, issue1, comment1, "+1")
addReaction(t, ghost, issue1, comment1, "heart")
err := comment1.LoadReactions()
assert.NoError(t, err)
assert.Len(t, comment1.Reactions, 5)
assert.Len(t, comment1.Reactions, 4)
reactions := comment1.Reactions.GroupByType()
assert.Len(t, reactions["heart"], 4)
assert.Len(t, reactions["heart"], 3)
assert.Len(t, reactions["+1"], 1)
}
@@ -160,7 +159,7 @@ func TestIssueCommentReactionCount(t *testing.T) {
comment1 := AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment)
addReaction(t, user1, issue1, comment1, "heart")
DeleteCommentReaction(user1, issue1, comment1, "heart")
assert.NoError(t, DeleteCommentReaction(user1, issue1, comment1, "heart"))
AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID})
}

View File

@@ -136,7 +136,7 @@ func (pr *PullRequest) LoadHeadRepo() error {
if has, err := x.ID(pr.HeadRepoID).Get(&repo); err != nil {
return err
} else if !has {
return ErrRepoNotExist{ID: pr.BaseRepoID}
return ErrRepoNotExist{ID: pr.HeadRepoID}
}
pr.HeadRepo = &repo
}

View File

@@ -1781,6 +1781,12 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) {
return sess.Commit()
}
// UpdateRepositoryStatus updates a repository's status
func UpdateRepositoryStatus(repoID int64, status RepositoryStatus) error {
_, err := x.Exec("UPDATE repository SET status = ? WHERE id = ?", status, repoID)
return err
}
// UpdateRepositoryUpdatedTime updates a repository's updated time
func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error {
_, err := x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", updateTime.Unix(), repoID)
@@ -1860,6 +1866,17 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
}
}
attachments := make([]*Attachment, 0, 20)
if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
Where("`release`.repo_id = ?", repoID).
Find(&attachments); err != nil {
return err
}
releaseAttachments := make([]string, 0, len(attachments))
for i := 0; i < len(attachments); i++ {
releaseAttachments = append(releaseAttachments, attachments[i].LocalPath())
}
if err = deleteBeans(sess,
&Access{RepoID: repo.ID},
&Action{RepoID: repo.ID},
@@ -1910,13 +1927,13 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
return err
}
attachmentPaths := make([]string, 0, 20)
attachments := make([]*Attachment, 0, len(attachmentPaths))
attachments = attachments[:0]
if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id").
Where("issue.repo_id = ?", repoID).
Find(&attachments); err != nil {
return err
}
attachmentPaths := make([]string, 0, len(attachments))
for j := range attachments {
attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
}
@@ -1953,11 +1970,6 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
return err
}
// Remove attachment files.
for i := range attachmentPaths {
removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i])
}
// Remove LFS objects
var lfsObjects []*LFSMetaObject
if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
@@ -1997,6 +2009,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
return fmt.Errorf("Commit: %v", err)
}
sess.Close()
if org.IsOrganization() {
if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{
Action: api.HookRepoDeleted,
@@ -2009,6 +2023,19 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
go HookQueue.Add(repo.ID)
}
// We should always delete the files after the database transaction succeed. If
// we delete the file but the database rollback, the repository will be borken.
// Remove issue attachment files.
for i := range attachmentPaths {
removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i])
}
// Remove release attachment files.
for i := range releaseAttachments {
removeAllWithNotice(x, "Delete release attachment", releaseAttachments[i])
}
if len(repo.Avatar) > 0 {
avatarPath := repo.CustomAvatarPath()
if com.IsExist(avatarPath) {
@@ -2784,3 +2811,9 @@ func (repo *Repository) GetOriginalURLHostname() string {
return u.Host
}
// UpdateRepositoryCols updates repository's columns
func UpdateRepositoryCols(repo *Repository, cols ...string) error {
_, err := x.ID(repo.ID).Cols(cols...).Update(repo)
return err
}

View File

@@ -177,7 +177,7 @@ type fileUpdate struct {
}
func getDefaultBranchSha(repo *Repository) (string, error) {
stdout, err := git.NewCommand("show-ref", "-s", repo.DefaultBranch).RunInDir(repo.RepoPath())
stdout, err := git.NewCommand("show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunInDir(repo.RepoPath())
if err != nil {
return "", err
}

View File

@@ -120,7 +120,8 @@ type SearchRepoOptions struct {
StarredByID int64
Page int
IsProfile bool
AllPublic bool // Include also all public repositories
AllPublic bool // Include also all public repositories of users and public organisations
AllLimited bool // Include also all public repositories of limited organisations
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
// None -> include collaborative AND non-collaborative
// True -> include just collaborative
@@ -240,7 +241,11 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
}
if opts.AllPublic {
accessCond = accessCond.Or(builder.Eq{"is_private": false})
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}))))
}
if opts.AllLimited {
accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited}))))
}
cond = cond.And(accessCond)

View File

@@ -177,8 +177,8 @@ func TestSearchRepository(t *testing.T) {
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
count: 22},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 28},
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true},
count: 27},
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
count: 15},

View File

@@ -502,7 +502,7 @@ func (u *User) ValidatePassword(passwd string) bool {
// IsPasswordSet checks if the password is set or left empty
func (u *User) IsPasswordSet() bool {
return len(u.Passwd) > 0
return !u.ValidatePassword("")
}
// UploadAvatar saves custom avatar for user.
@@ -1715,6 +1715,15 @@ func SyncExternalUsers() {
continue
}
if len(sr) == 0 {
if !s.LDAP().AllowDeactivateAll {
log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
continue
} else {
log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
}
}
for _, su := range sr {
if len(su.Username) == 0 {
continue

View File

@@ -36,10 +36,11 @@ type SlackPayload struct {
// SlackAttachment contains the slack message
type SlackAttachment struct {
Fallback string `json:"fallback"`
Color string `json:"color"`
Title string `json:"title"`
Text string `json:"text"`
Fallback string `json:"fallback"`
Color string `json:"color"`
Title string `json:"title"`
TitleLink string `json:"title_link"`
Text string `json:"text"`
}
// SetSecret sets the slack secret
@@ -133,70 +134,78 @@ func getSlackForkPayload(p *api.ForkPayload, slack *SlackMeta) (*SlackPayload, e
func getSlackIssuesPayload(p *api.IssuePayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
var text, title, attachmentText string
title := SlackTextFormatter(fmt.Sprintf("#%d %s", p.Index, p.Issue.Title))
titleLink := fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Index)
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text, attachmentText string
switch p.Action {
case api.HookIssueOpened:
text = fmt.Sprintf("[%s] Issue submitted by %s", p.Repository.FullName, senderLink)
title = titleLink
text = fmt.Sprintf("[%s] Issue opened by %s", repoLink, senderLink)
attachmentText = SlackTextFormatter(p.Issue.Body)
case api.HookIssueClosed:
text = fmt.Sprintf("[%s] Issue closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue closed: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueReOpened:
text = fmt.Sprintf("[%s] Issue re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue re-opened: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueEdited:
text = fmt.Sprintf("[%s] Issue edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue edited: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
attachmentText = SlackTextFormatter(p.Issue.Body)
case api.HookIssueAssigned:
text = fmt.Sprintf("[%s] Issue assigned to %s: %s by %s", p.Repository.FullName,
text = fmt.Sprintf("[%s] Issue assigned to %s: [%s](%s) by %s", repoLink,
SlackLinkFormatter(setting.AppURL+p.Issue.Assignee.UserName, p.Issue.Assignee.UserName),
titleLink, senderLink)
title, titleLink, senderLink)
case api.HookIssueUnassigned:
text = fmt.Sprintf("[%s] Issue unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue unassigned: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueLabelUpdated:
text = fmt.Sprintf("[%s] Issue labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue labels updated: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueLabelCleared:
text = fmt.Sprintf("[%s] Issue labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue labels cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueSynchronized:
text = fmt.Sprintf("[%s] Issue synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue synchronized: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueMilestoned:
text = fmt.Sprintf("[%s] Issue milestoned: #%s %s", p.Repository.FullName, titleLink, senderLink)
mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID)
text = fmt.Sprintf("[%s] Issue milestoned to [%s](%s): [%s](%s) by %s", repoLink,
p.Issue.Milestone.Title, mileStoneLink, title, titleLink, senderLink)
case api.HookIssueDemilestoned:
text = fmt.Sprintf("[%s] Issue milestone cleared: #%s %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Issue milestone cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
}
return &SlackPayload{
pl := &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, nil
}
if attachmentText != "" {
pl.Attachments = []SlackAttachment{{
Color: slack.Color,
Title: title,
TitleLink: titleLink,
Text: attachmentText,
}}
}
return pl, nil
}
func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID)),
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
var text, title, attachmentText string
title := SlackTextFormatter(fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text, titleLink, attachmentText string
switch p.Action {
case api.HookIssueCommentCreated:
text = fmt.Sprintf("[%s] New comment created by %s", p.Repository.FullName, senderLink)
title = titleLink
text = fmt.Sprintf("[%s] New comment created by %s", repoLink, senderLink)
titleLink = fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
attachmentText = SlackTextFormatter(p.Comment.Body)
case api.HookIssueCommentEdited:
text = fmt.Sprintf("[%s] Comment edited by %s", p.Repository.FullName, senderLink)
title = titleLink
text = fmt.Sprintf("[%s] Comment edited by %s", repoLink, senderLink)
titleLink = fmt.Sprintf("%s/issues/%d#%s", p.Repository.HTMLURL, p.Issue.Index, CommentHashTag(p.Comment.ID))
attachmentText = SlackTextFormatter(p.Comment.Body)
case api.HookIssueCommentDeleted:
text = fmt.Sprintf("[%s] Comment deleted by %s", p.Repository.FullName, senderLink)
title = SlackLinkFormatter(fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index),
fmt.Sprintf("#%d %s", p.Issue.Index, p.Issue.Title))
text = fmt.Sprintf("[%s] Comment deleted by %s", repoLink, senderLink)
titleLink = fmt.Sprintf("%s/issues/%d", p.Repository.HTMLURL, p.Issue.Index)
attachmentText = SlackTextFormatter(p.Comment.Body)
}
@@ -206,9 +215,10 @@ func getSlackIssueCommentPayload(p *api.IssueCommentPayload, slack *SlackMeta) (
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
Color: slack.Color,
Title: title,
TitleLink: titleLink,
Text: attachmentText,
}},
}, nil
}
@@ -273,73 +283,85 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Text: attachmentText,
Color: slack.Color,
Title: p.Repo.HTMLURL,
TitleLink: p.Repo.HTMLURL,
Text: attachmentText,
}},
}, nil
}
func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
var text, title, attachmentText string
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text, attachmentText string
switch p.Action {
case api.HookIssueOpened:
text = fmt.Sprintf("[%s] Pull request submitted by %s", p.Repository.FullName, senderLink)
title = titleLink
text = fmt.Sprintf("[%s] Pull request opened by %s", repoLink, senderLink)
attachmentText = SlackTextFormatter(p.PullRequest.Body)
case api.HookIssueClosed:
if p.PullRequest.HasMerged {
text = fmt.Sprintf("[%s] Pull request merged: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request merged: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
} else {
text = fmt.Sprintf("[%s] Pull request closed: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request closed: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
}
case api.HookIssueReOpened:
text = fmt.Sprintf("[%s] Pull request re-opened: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request re-opened: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueEdited:
text = fmt.Sprintf("[%s] Pull request edited: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request edited: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
attachmentText = SlackTextFormatter(p.PullRequest.Body)
case api.HookIssueAssigned:
list := make([]string, len(p.PullRequest.Assignees))
for i, user := range p.PullRequest.Assignees {
list[i] = SlackLinkFormatter(setting.AppURL+user.UserName, user.UserName)
}
text = fmt.Sprintf("[%s] Pull request assigned to %s: %s by %s", p.Repository.FullName,
text = fmt.Sprintf("[%s] Pull request assigned to %s: [%s](%s) by %s", repoLink,
strings.Join(list, ", "),
titleLink, senderLink)
title, titleLink, senderLink)
case api.HookIssueUnassigned:
text = fmt.Sprintf("[%s] Pull request unassigned: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request unassigned: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueLabelUpdated:
text = fmt.Sprintf("[%s] Pull request labels updated: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request labels updated: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueLabelCleared:
text = fmt.Sprintf("[%s] Pull request labels cleared: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request labels cleared: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueSynchronized:
text = fmt.Sprintf("[%s] Pull request synchronized: %s by %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request synchronized: [%s](%s) by %s", repoLink, title, titleLink, senderLink)
case api.HookIssueMilestoned:
text = fmt.Sprintf("[%s] Pull request milestoned: #%s %s", p.Repository.FullName, titleLink, senderLink)
mileStoneLink := fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID)
text = fmt.Sprintf("[%s] Pull request milestoned to [%s](%s): [%s](%s) %s", repoLink,
p.PullRequest.Milestone.Title, mileStoneLink, title, titleLink, senderLink)
case api.HookIssueDemilestoned:
text = fmt.Sprintf("[%s] Pull request milestone cleared: #%s %s", p.Repository.FullName, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request milestone cleared: [%s](%s) %s", repoLink, title, titleLink, senderLink)
}
return &SlackPayload{
pl := &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, nil
}
if attachmentText != "" {
pl.Attachments = []SlackAttachment{{
Color: slack.Color,
Title: title,
TitleLink: titleLink,
Text: attachmentText,
}}
}
return pl, nil
}
func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackMeta, event HookEventType) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
titleLink := SlackLinkFormatter(fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index),
fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title))
var text, title, attachmentText string
title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
repoLink := SlackLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
var text string
switch p.Action {
case api.HookIssueSynchronized:
action, err := parseHookPullRequestEventType(event)
@@ -347,7 +369,7 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM
return nil, err
}
text = fmt.Sprintf("[%s] Pull request review %s : %s by %s", p.Repository.FullName, action, titleLink, senderLink)
text = fmt.Sprintf("[%s] Pull request review %s: [%s](%s) by %s", repoLink, action, title, titleLink, senderLink)
}
return &SlackPayload{
@@ -355,21 +377,16 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, nil
}
func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
var text, title, attachmentText string
var text string
switch p.Action {
case api.HookRepoCreated:
text = fmt.Sprintf("[%s] Repository created by %s", p.Repository.FullName, senderLink)
title = p.Repository.HTMLURL
case api.HookRepoDeleted:
text = fmt.Sprintf("[%s] Repository deleted by %s", p.Repository.FullName, senderLink)
}
@@ -379,11 +396,6 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, nil
}

View File

@@ -30,6 +30,7 @@ type AuthenticationForm struct {
SearchPageSize int
Filter string
AdminFilter string
AllowDeactivateAll bool
IsActive bool
IsSyncEnabled bool
SMTPAuth string

View File

@@ -47,6 +47,7 @@ type Source struct {
Filter string // Query filter to validate entry
AdminFilter string // Query filter to check if user is admin
Enabled bool // if this source is disabled
AllowDeactivateAll bool // Allow an empty search response to deactivate all users from this source
}
// SearchResult : user data

View File

@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
@@ -122,7 +123,7 @@ func (ctx *Context) RedirectToFirst(location ...string) {
}
u, err := url.Parse(loc)
if err != nil || (u.Scheme != "" && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
continue
}

View File

@@ -91,12 +91,12 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
// 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
isAssigned, _ := models.IsUserAssignedToIssue(issue, user)
return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() ||
r.Permission.CanWrite(models.UnitTypeIssues) || issue.IsPoster(user.ID) || isAssigned)
r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
}
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
return r.Permission.CanWrite(models.UnitTypeIssues) && r.Repository.IsDependenciesEnabled()
func (r *Repository) CanCreateIssueDependencies(user *models.User, isPull bool) bool {
return r.Repository.IsDependenciesEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull)
}
// GetCommitsCount returns cached commit count for current view

View File

@@ -138,26 +138,31 @@ func populateIssueIndexer() {
}
for _, repo := range repos {
is, err := models.Issues(&models.IssuesOptions{
RepoIDs: []int64{repo.ID},
IsClosed: util.OptionalBoolNone,
IsPull: util.OptionalBoolNone,
})
if err != nil {
log.Error("Issues: %v", err)
continue
}
if err = models.IssueList(is).LoadDiscussComments(); err != nil {
log.Error("LoadComments: %v", err)
continue
}
for _, issue := range is {
UpdateIssueIndexer(issue)
}
UpdateRepoIndexer(repo)
}
}
}
// UpdateRepoIndexer add/update all issues of the repositories
func UpdateRepoIndexer(repo *models.Repository) {
is, err := models.Issues(&models.IssuesOptions{
RepoIDs: []int64{repo.ID},
IsClosed: util.OptionalBoolNone,
IsPull: util.OptionalBoolNone,
})
if err != nil {
log.Error("Issues: %v", err)
return
}
if err = models.IssueList(is).LoadDiscussComments(); err != nil {
log.Error("LoadComments: %v", err)
return
}
for _, issue := range is {
UpdateIssueIndexer(issue)
}
}
// UpdateIssueIndexer add/update an issue to the issue indexer
func UpdateIssueIndexer(issue *models.Issue) {
var comments []string

View File

@@ -12,6 +12,7 @@ type Uploader interface {
CreateTopics(topic ...string) error
CreateMilestones(milestones ...*Milestone) error
CreateReleases(releases ...*Release) error
SyncTags() error
CreateLabels(labels ...*Label) error
CreateIssues(issues ...*Issue) error
CreateComments(comments ...*Comment) error

View File

@@ -288,11 +288,12 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
rels = append(rels, &rel)
}
if err := models.InsertReleases(rels...); err != nil {
return err
}
// sync tags to releases in database
return models.InsertReleases(rels...)
}
// SyncTags syncs releases with tags in the database
func (g *GiteaLocalUploader) SyncTags() error {
return models.SyncReleasesWithTags(g.repo, g.gitRepo)
}

View File

@@ -57,7 +57,7 @@ func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOpt
opts.PullRequests = false
opts.GitServiceType = structs.PlainGitService
downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
log.Trace("Will migrate from git: %s", opts.CloneAddr)
log.Trace("Will migrate from git: %s", opts.OriginalURL)
} else if opts.GitServiceType == structs.NotMigrated {
opts.GitServiceType = theFactory.GitServiceType()
}
@@ -68,7 +68,7 @@ func MigrateRepository(doer *models.User, ownerName string, opts base.MigrateOpt
log.Error("rollback failed: %v", err1)
}
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.CloneAddr, err)); err2 != nil {
if err2 := models.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
log.Error("create respotiry notice failed: ", err2)
}
return nil, err
@@ -165,6 +165,11 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
}
releases = releases[relBatchSize:]
}
// Once all releases (if any) are inserted, sync any remaining non-release tags
if err := uploader.SyncTags(); err != nil {
return err
}
}
var commentBatchSize = uploader.MaxBatchInsertSize("comment")

View File

@@ -107,3 +107,8 @@ func (r *indexerNotifier) NotifyIssueChangeContent(doer *models.User, issue *mod
func (r *indexerNotifier) NotifyIssueChangeTitle(doer *models.User, issue *models.Issue, oldTitle string) {
issue_indexer.UpdateIssueIndexer(issue)
}
func (r *indexerNotifier) NotifyMigrateRepository(doer *models.User, u *models.User, repo *models.Repository) {
issue_indexer.UpdateRepoIndexer(repo)
models.UpdateRepoIndexer(repo)
}

View File

@@ -69,7 +69,7 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo
message := strings.TrimSpace(opts.Message)
author, committer := GetAuthorAndCommitterUsers(opts.Committer, opts.Author, doer)
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
t, err := NewTemporaryUploadRepository(repo)
if err != nil {

View File

@@ -80,7 +80,7 @@ func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.Fi
}
// GetAuthorAndCommitterUsers Gets the author and committer user objects from the IdentityOptions
func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (committerUser, authorUser *models.User) {
func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *models.User) (authorUser, committerUser *models.User) {
// Committer and author are optional. If they are not the doer (not same email address)
// then we use bogus User objects for them to store their FullName and Email.
// If only one of the two are provided, we set both of them to it.

View File

@@ -79,11 +79,11 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs
for e := rangeStart; e < rangeEnd; e++ {
i := e - rangeStart
tree.Entries[e].Path = entries[e].Name()
tree.Entries[e].Mode = fmt.Sprintf("%06o", entries[e].Mode())
tree.Entries[e].Type = entries[e].Type()
tree.Entries[e].Size = entries[e].Size()
tree.Entries[e].SHA = entries[e].ID.String()
tree.Entries[i].Path = entries[e].Name()
tree.Entries[i].Mode = fmt.Sprintf("%06o", entries[e].Mode())
tree.Entries[i].Type = entries[e].Type()
tree.Entries[i].Size = entries[e].Size()
tree.Entries[i].SHA = entries[e].ID.String()
if entries[e].IsDir() {
copy(treeURL[copyPos:], entries[e].ID.String())

View File

@@ -167,7 +167,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
message := strings.TrimSpace(opts.Message)
author, committer := GetAuthorAndCommitterUsers(opts.Committer, opts.Author, doer)
author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)
t, err := NewTemporaryUploadRepository(repo)
if err != nil {
@@ -453,10 +453,15 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp
}
}
} else if !isDelRef {
branchName := opts.RefFullName[len(git.BranchPrefix):]
if err = models.RemoveDeletedBranch(repo.ID, branchName); err != nil {
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branchName, err)
}
// If is branch reference
// Clear cache for branch commit count
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefFullName[len(git.BranchPrefix):], true))
cache.Remove(repo.GetCommitsCountCacheKey(branchName, true))
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
if err != nil {

View File

@@ -97,8 +97,6 @@ func runMigrateTask(t *models.Task) (err error) {
opts.MigrateToRepoID = t.RepoID
repo, err := migrations.MigrateRepository(t.Doer, t.Owner.Name, *opts)
if err == nil {
notification.NotifyMigrateRepository(t.Doer, t.Owner, repo)
log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name)
return nil
}

View File

@@ -239,6 +239,14 @@ func NewFuncMap() []template.FuncMap {
"MirrorFullAddress": mirror_service.AddressNoCredentials,
"MirrorUserName": mirror_service.Username,
"MirrorPassword": mirror_service.Password,
"contain": func(s []int64, id int64) bool {
for i := 0; i < len(s); i++ {
if s[i] == id {
return true
}
}
return false
},
}}
}

View File

@@ -1700,6 +1700,7 @@ auths.attribute_surname = Surname Attribute
auths.attribute_mail = Email Attribute
auths.attribute_ssh_public_key = Public SSH Key Attribute
auths.attributes_in_bind = Fetch Attributes in Bind DN Context
auths.allow_deactivate_all = Allow an empty search result to deactivate all users
auths.use_paged_search = Use Paged Search
auths.search_page_size = Page Size
auths.filter = User Filter

View File

@@ -1,6 +1,6 @@
/* globals wipPrefixes, issuesTribute, emojiTribute */
/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */
/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */
/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, submitReply, cancelCodeComment, onOAuthLoginClick */
'use strict';
function htmlEncode(text) {
@@ -3134,10 +3134,11 @@ function deleteDependencyModal(id, type) {
function initIssueList() {
const repolink = $('#repolink').val();
const tp = $('#type').val();
$('#new-dependency-drop-list')
.dropdown({
apiSettings: {
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}',
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}&type='+tp,
onResponse: function(response) {
const filteredResponse = {'success': true, 'results': []};
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
@@ -3170,6 +3171,14 @@ function cancelCodeComment(btn) {
form.closest('.comment-code-cloud').remove()
}
}
function submitReply(btn) {
const form = $(btn).closest('form');
if (form.length > 0 && form.hasClass('comment-form')) {
form.submit();
}
}
function onOAuthLoginClick() {
const oauthLoader = $('#oauth2-login-loader');
const oauthNav = $('#oauth2-login-navigator');

View File

@@ -115,6 +115,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig {
SearchPageSize: pageSize,
Filter: form.Filter,
AdminFilter: form.AdminFilter,
AllowDeactivateAll: form.AllowDeactivateAll,
Enabled: true,
},
}

View File

@@ -57,6 +57,10 @@ func ListIssues(ctx *context.APIContext) {
// in: query
// description: search string
// type: string
// - name: type
// in: query
// description: filter by type (issues / pulls) if set
// type: string
// responses:
// "200":
// "$ref": "#/responses/IssueList"
@@ -91,6 +95,16 @@ func ListIssues(ctx *context.APIContext) {
}
}
var isPull util.OptionalBool
switch ctx.Query("type") {
case "pulls":
isPull = util.OptionalBoolTrue
case "issues":
isPull = util.OptionalBoolFalse
default:
isPull = util.OptionalBoolNone
}
// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
@@ -101,6 +115,7 @@ func ListIssues(ctx *context.APIContext) {
IsClosed: isClosed,
IssueIDs: issueIDs,
LabelIDs: labelIDs,
IsPull: isPull,
})
}
@@ -292,6 +307,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
return
}
issue.Repo = ctx.Repo.Repository
canWrite := ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
err = issue.LoadAttributes()
if err != nil {
@@ -299,7 +315,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
return
}
if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !issue.IsPoster(ctx.User.ID) && !canWrite {
ctx.Status(403)
return
}
@@ -312,7 +328,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
// Update the deadline
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
if form.Deadline != nil && canWrite {
deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix())
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err)
@@ -329,7 +345,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
if ctx.Repo.CanWrite(models.UnitTypeIssues) && (form.Assignees != nil || form.Assignee != nil) {
if canWrite && (form.Assignees != nil || form.Assignee != nil) {
oneAssignee := ""
if form.Assignee != nil {
oneAssignee = *form.Assignee
@@ -342,7 +358,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
}
if ctx.Repo.CanWrite(models.UnitTypeIssues) && form.Milestone != nil &&
if canWrite && form.Milestone != nil &&
issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone
@@ -352,8 +368,8 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
}
if err = models.UpdateIssue(issue); err != nil {
ctx.Error(500, "UpdateIssue", err)
if err = models.UpdateIssueByAPI(issue); err != nil {
ctx.InternalServerError(err)
return
}
if form.State != nil {
@@ -372,7 +388,11 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
// Refetch from database to assign some automatic values
issue, err = models.GetIssueByID(issue.ID)
if err != nil {
ctx.Error(500, "GetIssueByID", err)
ctx.InternalServerError(err)
return
}
if err = issue.LoadMilestone(); err != nil {
ctx.InternalServerError(err)
return
}
ctx.JSON(201, issue.APIFormat())
@@ -426,7 +446,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
return
}
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
return
}
@@ -493,7 +513,7 @@ func StartIssueStopwatch(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
return
}
@@ -562,7 +582,7 @@ func StopIssueStopwatch(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
return
}

View File

@@ -185,7 +185,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti
return
}
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
ctx.Error(403, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
return
}

View File

@@ -189,7 +189,12 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) {
milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
}
if err := models.UpdateMilestone(milestone); err != nil {
var oldIsClosed = milestone.IsClosed
if form.State != nil {
milestone.IsClosed = *form.State == string(api.StateClosed)
}
if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil {
ctx.ServerError("UpdateMilestone", err)
return
}

View File

@@ -420,8 +420,8 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
}
}
if err = models.UpdateIssue(issue); err != nil {
ctx.Error(500, "UpdateIssue", err)
if err = models.UpdateIssueByAPI(issue); err != nil {
ctx.InternalServerError(err)
return
}
if form.State != nil {

View File

@@ -6,6 +6,8 @@
package repo
import (
"bytes"
"errors"
"fmt"
"net/http"
"net/url"
@@ -431,15 +433,53 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
opts.Releases = false
}
repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts)
if err == nil {
notification.NotifyCreateRepository(ctx.User, ctxUser, repo)
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: form.CloneAddr,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
})
if err != nil {
handleMigrateError(ctx, ctxUser, remoteAddr, err)
return
}
opts.MigrateToRepoID = repo.ID
defer func() {
if e := recover(); e != nil {
var buf bytes.Buffer
fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
err = errors.New(buf.String())
}
if err == nil {
repo.Status = models.RepositoryReady
if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
return
}
}
if repo != nil {
if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
log.Error("DeleteRepository: %v", errDelete)
}
}
}()
if _, err = migrations.MigrateRepository(ctx.User, ctxUser.Name, opts); err != nil {
handleMigrateError(ctx, ctxUser, remoteAddr, err)
return
}
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
}
func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
switch {
case models.IsErrRepoAlreadyExist(err):
ctx.Error(409, "", "The repository with the same name already exists.")
@@ -448,7 +488,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
case migrations.IsTwoFactorAuthError(err):
ctx.Error(422, "", "Remote visit required two factors authentication.")
case models.IsErrReachLimitOfRepo(err):
ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", ctxUser.MaxCreationLimit()))
ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
case models.IsErrNameReserved(err):
ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
case models.IsErrNamePatternNotAllowed(err):

View File

@@ -41,8 +41,8 @@ func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) {
// schema:
// "$ref": "#/definitions/CreateStatusOption"
// responses:
// "200":
// "$ref": "#/responses/StatusList"
// "201":
// "$ref": "#/responses/Status"
sha := ctx.Params("sha")
if len(sha) == 0 {
ctx.Error(400, "sha not given", nil)

View File

@@ -141,6 +141,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
Keyword: keyword,
OwnerID: opts.OwnerID,
AllPublic: true,
AllLimited: true,
TopicOnly: topicOnly,
IncludeDescription: setting.UI.SearchRepoDescription,
})

View File

@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/util"
"gopkg.in/src-d/go-git.v4/plumbing"
)
const (
@@ -32,6 +33,7 @@ type Branch struct {
CommitsAhead int
CommitsBehind int
LatestPullRequest *models.PullRequest
MergeMovedOn bool
}
// Branches render repository branch page
@@ -168,6 +170,12 @@ func loadBranches(ctx *context.Context) []*Branch {
return nil
}
repoIDToRepo := map[int64]*models.Repository{}
repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository
repoIDToGitRepo := map[int64]*git.Repository{}
repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo
branches := make([]*Branch, len(rawBranches))
for i := range rawBranches {
commit, err := rawBranches[i].GetCommit()
@@ -196,11 +204,46 @@ func loadBranches(ctx *context.Context) []*Branch {
ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
return nil
}
headCommit := commit.ID.String()
mergeMovedOn := false
if pr != nil {
pr.HeadRepo = ctx.Repo.Repository
if err := pr.LoadIssue(); err != nil {
ctx.ServerError("pr.LoadIssue", err)
return nil
}
if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
pr.BaseRepo = repo
} else if err := pr.LoadBaseRepo(); err != nil {
ctx.ServerError("pr.LoadBaseRepo", err)
return nil
} else {
repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
}
pr.Issue.Repo = pr.BaseRepo
if pr.HasMerged {
baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
if !ok {
baseGitRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
}
defer baseGitRepo.Close()
repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
}
pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil && err != plumbing.ErrReferenceNotFound {
ctx.ServerError("GetBranchCommitID", err)
return nil
}
if err == nil && headCommit != pullCommit {
// the head has moved on from the merge - we shouldn't delete
mergeMovedOn = true
}
}
}
branches[i] = &Branch{
@@ -210,6 +253,7 @@ func loadBranches(ctx *context.Context) []*Branch {
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
LatestPullRequest: pr,
MergeMovedOn: mergeMovedOn,
}
}

View File

@@ -152,12 +152,12 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *
ctx.ServerError("OpenRepository", err)
return nil, nil, nil, nil, "", ""
}
defer headGitRepo.Close()
}
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
@@ -168,42 +168,40 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *
baseRepo,
permBase)
}
headGitRepo.Close()
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
if !isSameRepo {
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
}
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
headGitRepo.Close()
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
// Check if head branch is valid.
headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch)
headIsCommit := headGitRepo.IsCommitExist(headBranch)
headIsBranch := headGitRepo.IsBranchExist(headBranch)
headIsTag := headGitRepo.IsTagExist(headBranch)
if !headIsCommit && !headIsBranch && !headIsTag {
// Check if headBranch is short sha commit hash
if headCommit, _ := ctx.Repo.GitRepo.GetCommit(headBranch); headCommit != nil {
if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil {
headBranch = headCommit.ID.String()
ctx.Data["HeadBranch"] = headBranch
headIsCommit = true
} else {
headGitRepo.Close()
ctx.NotFound("IsRefExist", nil)
return nil, nil, nil, nil, "", ""
}
@@ -224,14 +222,12 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, *
baseRepo,
permBase)
}
headGitRepo.Close()
ctx.NotFound("ParseCompareInfo", nil)
return nil, nil, nil, nil, "", ""
}
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
compareInfo, err := headGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranch, headBranch)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetCompareInfo", err)
return nil, nil, nil, nil, "", ""
}
@@ -377,7 +373,8 @@ func CompareDiff(ctx *context.Context) {
return
}
defer headGitRepo.Close()
if err := parseBaseRepoInfo(ctx, headRepo); err != nil {
var err error
if err = parseBaseRepoInfo(ctx, headRepo); err != nil {
ctx.ServerError("parseBaseRepoInfo", err)
return
}
@@ -410,7 +407,7 @@ func CompareDiff(ctx *context.Context) {
if !nothingToCompare {
// Setup information for new form.
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
RetrieveRepoMetas(ctx, ctx.Repo.Repository, true)
if ctx.Written() {
return
}
@@ -419,6 +416,11 @@ func CompareDiff(ctx *context.Context) {
beforeCommitID := ctx.Data["BeforeCommitID"].(string)
afterCommitID := ctx.Data["AfterCommitID"].(string)
if ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees(); err != nil {
ctx.ServerError("GetAssignees", err)
return
}
ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + "..." + base.ShortSha(afterCommitID)
ctx.Data["IsRepoToolbarCommits"] = true

View File

@@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs"
@@ -33,7 +34,12 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error {
name = strings.Replace(name, ",", " ", -1)
if base.IsTextFile(buf) || ctx.QueryBool("render") {
ctx.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
cs, err := charset.DetectEncoding(buf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", name, err)
cs = "utf-8"
}
ctx.Resp.Header().Set("Content-Type", "text/plain; charset="+strings.ToLower(cs))
} else if base.IsImageFile(buf) || base.IsPDFFile(buf) {
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, name))
} else {

View File

@@ -67,7 +67,7 @@ func MustAllowUserComment(ctx *context.Context) {
return
}
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.HTMLURL())
return
@@ -261,7 +261,8 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB
}
ctx.Data["IssueStats"] = issueStats
ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
ctx.Data["SelLabelIDs"] = labelIDs
ctx.Data["SelectLabels"] = selectLabels
ctx.Data["ViewType"] = viewType
ctx.Data["SortType"] = sortType
ctx.Data["MilestoneID"] = milestoneID
@@ -345,8 +346,8 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos
}
// RetrieveRepoMetas find all the meta information of a repository
func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label {
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull bool) []*models.Label {
if !ctx.Repo.CanWriteIssuesOrPulls(isPull) {
return nil
}
@@ -370,7 +371,7 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
ctx.Data["Branches"] = brs
// Contains true if the user can create issue dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, isPull)
return labels
}
@@ -440,7 +441,7 @@ func NewIssue(ctx *context.Context) {
setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
renderAttachmentSettings(ctx)
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
if ctx.Written() {
return
}
@@ -455,7 +456,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b
err error
)
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull)
if ctx.Written() {
return nil, nil, 0
}
@@ -775,8 +776,16 @@ func ViewIssue(ctx *context.Context) {
}
}
if issue.IsPull && !ctx.Repo.CanRead(models.UnitTypeIssues) {
ctx.Data["IssueType"] = "pulls"
} else if !issue.IsPull && !ctx.Repo.CanRead(models.UnitTypePullRequests) {
ctx.Data["IssueType"] = "issues"
} else {
ctx.Data["IssueType"] = "all"
}
// Check if the user can use the dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull)
// Render comments and and fetch participants.
participants[0] = issue.Poster
@@ -930,7 +939,10 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["IsBlockedByApprovals"] = pull.ProtectedBranch.RequiredApprovals > 0 && cnt < pull.ProtectedBranch.RequiredApprovals
ctx.Data["GrantedApprovals"] = cnt
}
ctx.Data["IsPullBranchDeletable"] = canDelete && pull.HeadRepo != nil && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
ctx.Data["IsPullBranchDeletable"] = canDelete &&
pull.HeadRepo != nil &&
git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) &&
(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
ctx.Data["PullReviewersWithType"], err = models.GetReviewersByPullID(issue.ID)
if err != nil {
@@ -959,7 +971,6 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
ctx.Data["IsIssueWriter"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin)
ctx.Data["IsRepoIssuesWriter"] = ctx.IsSigned && (ctx.Repo.CanWrite(models.UnitTypeIssues) || ctx.User.IsAdmin)
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
ctx.HTML(200, tplIssueView)
}
@@ -1204,7 +1215,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
ctx.Error(403)
}
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther)
return

View File

@@ -14,14 +14,6 @@ import (
// AddDependency adds new dependencies
func AddDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("newDependency")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
@@ -29,6 +21,14 @@ func AddDependency(ctx *context.Context) {
return
}
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("newDependency")
// Redirect
defer ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
@@ -68,14 +68,6 @@ func AddDependency(ctx *context.Context) {
// RemoveDependency removes the dependency
func RemoveDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("removeDependencyID")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
@@ -83,8 +75,13 @@ func RemoveDependency(ctx *context.Context) {
return
}
// Redirect
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("removeDependencyID")
// Dependency Type
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
@@ -116,4 +113,7 @@ func RemoveDependency(ctx *context.Context) {
ctx.ServerError("RemoveIssueDependency", err)
return
}
// Redirect
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
}

View File

@@ -192,7 +192,7 @@ func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
m.Name = form.Title
m.Content = form.Content
m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
if err = models.UpdateMilestone(m); err != nil {
if err = models.UpdateMilestone(m, m.IsClosed); err != nil {
ctx.ServerError("UpdateMilestone", err)
return
}

View File

@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
@@ -314,25 +315,37 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
repo := ctx.Repo.Repository
pull := issue.PullRequest
var err error
if err = pull.GetHeadRepo(); err != nil {
if err := pull.GetHeadRepo(); err != nil {
ctx.ServerError("GetHeadRepo", err)
return nil
}
if err := pull.GetBaseRepo(); err != nil {
ctx.ServerError("GetBaseRepo", err)
return nil
}
setMergeTarget(ctx, pull)
if err = pull.LoadProtectedBranch(); err != nil {
if err := pull.LoadProtectedBranch(); err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
}
ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
var headGitRepo *git.Repository
baseGitRepo, err := git.OpenRepository(pull.BaseRepo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
}
defer baseGitRepo.Close()
var headBranchExist bool
var headBranchSha string
// HeadRepo may be missing
if pull.HeadRepo != nil {
headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath())
var err error
headGitRepo, err := git.OpenRepository(pull.HeadRepo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
@@ -342,46 +355,53 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
headBranchExist = headGitRepo.IsBranchExist(pull.HeadBranch)
if headBranchExist {
sha, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
headBranchSha, err = headGitRepo.GetBranchCommitID(pull.HeadBranch)
if err != nil {
ctx.ServerError("GetBranchCommitID", err)
return nil
}
commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
}
if len(commitStatuses) > 0 {
ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
}
if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
ctx.Data["is_context_required"] = func(context string) bool {
for _, c := range pull.ProtectedBranch.StatusCheckContexts {
if c == context {
return true
}
}
return false
}
ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
}
}
}
if pull.HeadRepo == nil || !headBranchExist {
ctx.Data["IsPullRequestBroken"] = true
ctx.Data["HeadTarget"] = "deleted"
ctx.Data["NumCommits"] = 0
ctx.Data["NumFiles"] = 0
sha, err := baseGitRepo.GetRefCommitID(pull.GetGitRefName())
if err != nil {
ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
return nil
}
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(repo.Owner.Name, repo.Name),
pull.BaseBranch, pull.HeadBranch)
commitStatuses, err := models.GetLatestCommitStatus(repo, sha, 0)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return nil
}
if len(commitStatuses) > 0 {
ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
}
if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
ctx.Data["is_context_required"] = func(context string) bool {
for _, c := range pull.ProtectedBranch.StatusCheckContexts {
if c == context {
return true
}
}
return false
}
ctx.Data["IsRequiredStatusCheckSuccess"] = pull_service.IsCommitStatusContextSuccess(commitStatuses, pull.ProtectedBranch.StatusCheckContexts)
}
ctx.Data["HeadBranchMovedOn"] = headBranchSha != sha
ctx.Data["HeadBranchCommitID"] = headBranchSha
ctx.Data["PullHeadCommitID"] = sha
if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha {
ctx.Data["IsPullRequestBroken"] = true
ctx.Data["HeadTarget"] = "deleted"
}
compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
pull.BaseBranch, pull.GetGitRefName())
if err != nil {
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
ctx.Data["IsPullRequestBroken"] = true
@@ -928,6 +948,21 @@ func CleanUpPullRequest(ctx *context.Context) {
return
}
if err := repofiles.PushUpdate(
pr.HeadRepo,
pr.HeadBranch,
models.PushUpdateOptions{
RefFullName: git.BranchPrefix + pr.HeadBranch,
OldCommitID: branchCommitID,
NewCommitID: git.EmptySHA,
PusherID: ctx.User.ID,
PusherName: ctx.User.Name,
RepoUserName: pr.HeadRepo.Owner.Name,
RepoName: pr.HeadRepo.Name,
}); err != nil {
log.Error("Update: %v", err)
}
if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil {
// Do not fail here as branch has already been deleted
log.Error("DeleteBranch: %v", err)

View File

@@ -117,9 +117,7 @@ func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
// can not approve/reject your own PR
case models.ReviewTypeApprove, models.ReviewTypeReject:
if issue.Poster.ID == ctx.User.ID {
if issue.IsPoster(ctx.User.ID) {
var translated string
if reviewType == models.ReviewTypeApprove {

View File

@@ -600,7 +600,7 @@ func AddTeamPost(ctx *context.Context) {
}
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team")))
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
if len(name) == 0 {
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
return
}

View File

@@ -65,27 +65,20 @@ type PageMeta struct {
// findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
entries, err := commit.ListEntries()
if err != nil {
entry, err := commit.GetTreeEntryByPath(target)
if err != nil && !git.IsErrNotExist(err) {
return nil, err
}
// The longest name should be checked first
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == target {
return entry, nil
}
if entry != nil {
return entry, nil
}
// Then the unescaped, shortest alternative
var unescapedTarget string
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == unescapedTarget {
return entry, nil
}
}
return nil, nil
return commit.GetTreeEntryByPath(unescapedTarget)
}
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
@@ -122,10 +115,9 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
var entry *git.TreeEntry
var err error
pageFilename := models.WikiNameToFilename(wikiName)
if entry, err = findEntryForFile(commit, pageFilename); err != nil {
entry, err := findEntryForFile(commit, pageFilename)
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findEntryForFile", err)
return nil, nil, "", false
} else if entry == nil {
@@ -517,7 +509,7 @@ func WikiRaw(ctx *context.Context) {
if commit != nil {
// Try to find a file with that name
entry, err = findEntryForFile(commit, providedPath)
if err != nil {
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findFile", err)
return
}
@@ -530,7 +522,7 @@ func WikiRaw(ctx *context.Context) {
wikiPath := models.WikiNameToFilename(providedPath)
entry, err = findEntryForFile(commit, wikiPath)
if err != nil {
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findFile", err)
return
}

View File

@@ -191,6 +191,7 @@ func TestDeleteWikiPagePost(t *testing.T) {
func TestWikiRaw(t *testing.T) {
for filepath, filetype := range map[string]string{
"jpeg.jpg": "image/jpeg",
"images/jpeg.jpg": "image/jpeg",
"Page With Spaced Name": "text/plain; charset=utf-8",
"Page-With-Spaced-Name": "text/plain; charset=utf-8",
"Page With Spaced Name.md": "text/plain; charset=utf-8",

View File

@@ -457,7 +457,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
})
m.Group("/auths", func() {
@@ -532,18 +532,12 @@ func RegisterRoutes(m *macaron.Macaron) {
reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases)
reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki)
reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues)
reqRepoIssueWriter := context.RequireRepoWriter(models.UnitTypeIssues)
reqRepoPullsWriter := context.RequireRepoWriter(models.UnitTypePullRequests)
reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests)
reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests)
reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests)
reqRepoIssueWriter := func(ctx *context.Context) {
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Error(403)
return
}
}
// ***** START: Organization *****
m.Group("/org", func() {
m.Group("", func() {
@@ -590,6 +584,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Get("/:id", repo.WebHooksEdit)
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
@@ -597,6 +592,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
})
m.Route("/delete", "GET,POST", org.SettingsDelete)

View File

@@ -1282,7 +1282,7 @@ func ForgotPasswdPost(ctx *context.Context) {
ctx.HTML(200, tplForgotPassword)
}
func commonResetPassword(ctx *context.Context) *models.User {
func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) {
code := ctx.Query("code")
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
@@ -1294,14 +1294,25 @@ func commonResetPassword(ctx *context.Context) *models.User {
if len(code) == 0 {
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
return nil
return nil, nil
}
// Fail early, don't frustrate the user
u := models.VerifyUserActiveCode(code)
if u == nil {
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
return nil
return nil, nil
}
twofa, err := models.GetTwoFactorByUID(u.ID)
if err != nil {
if !models.IsErrTwoFactorNotEnrolled(err) {
ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
return nil, nil
}
} else {
ctx.Data["has_two_factor"] = true
ctx.Data["scratch_code"] = ctx.QueryBool("scratch_code")
}
// Show the user that they are affecting the account that they intended to
@@ -1309,10 +1320,10 @@ func commonResetPassword(ctx *context.Context) *models.User {
if nil != ctx.User && u.ID != ctx.User.ID {
ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email))
return nil
return nil, nil
}
return u
return u, twofa
}
// ResetPasswd render the account recovery page
@@ -1320,13 +1331,19 @@ func ResetPasswd(ctx *context.Context) {
ctx.Data["IsResetForm"] = true
commonResetPassword(ctx)
if ctx.Written() {
return
}
ctx.HTML(200, tplResetPassword)
}
// ResetPasswdPost response from account recovery request
func ResetPasswdPost(ctx *context.Context) {
u := commonResetPassword(ctx)
u, twofa := commonResetPassword(ctx)
if ctx.Written() {
return
}
if u == nil {
// Flash error has been set
@@ -1348,6 +1365,39 @@ func ResetPasswdPost(ctx *context.Context) {
return
}
// Handle two-factor
regenerateScratchToken := false
if twofa != nil {
if ctx.QueryBool("scratch_code") {
if !twofa.VerifyScratchToken(ctx.Query("token")) {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Token"] = true
ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil)
return
}
regenerateScratchToken = true
} else {
passcode := ctx.Query("passcode")
ok, err := twofa.ValidateTOTP(passcode)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error())
return
}
if !ok || twofa.LastUsedPasscode == passcode {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Passcode"] = true
ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
return
}
twofa.LastUsedPasscode = passcode
if err = models.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
return
}
}
}
var err error
if u.Rands, err = models.GetUserSalt(); err != nil {
ctx.ServerError("UpdateUser", err)
@@ -1357,7 +1407,6 @@ func ResetPasswdPost(ctx *context.Context) {
ctx.ServerError("UpdateUser", err)
return
}
u.HashPassword(passwd)
u.MustChangePassword = false
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
@@ -1366,9 +1415,27 @@ func ResetPasswdPost(ctx *context.Context) {
}
log.Trace("User password reset: %s", u.Name)
ctx.Data["IsResetFailed"] = true
remember := len(ctx.Query("remember")) != 0
if regenerateScratchToken {
// Invalidate the scratch token.
_, err = twofa.GenerateScratchToken()
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
if err = models.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
handleSignInFull(ctx, u, remember, false)
ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
handleSignInFull(ctx, u, remember, true)
}

View File

@@ -6,6 +6,7 @@ package user
import (
"strconv"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
@@ -23,14 +24,19 @@ func Avatar(ctx *context.Context) {
log.Debug("Asked avatar for user %v and size %v", userName, size)
user, err := models.GetUserByName(userName)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.ServerError("Requested avatar for invalid user", err)
} else {
ctx.ServerError("Retrieving user by name", err)
var user *models.User
if strings.ToLower(userName) != "ghost" {
user, err = models.GetUserByName(userName)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.ServerError("Requested avatar for invalid user", err)
} else {
ctx.ServerError("Retrieving user by name", err)
}
return
}
return
} else {
user = models.NewGhostUser()
}
ctx.Redirect(user.RealSizedAvatarLink(size))

View File

@@ -74,7 +74,9 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) {
if act.ActUser != nil {
userCache[act.ActUserID] = act.ActUser
}
}
for _, act := range actions {
repoOwner, ok := userCache[act.Repo.OwnerID]
if !ok {
repoOwner, err = models.GetUserByID(act.Repo.OwnerID)

View File

@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
)
@@ -37,6 +38,54 @@ func createTag(gitRepo *git.Repository, rel *models.Release) error {
return err
}
rel.LowerTagName = strings.ToLower(rel.TagName)
// Prepare Notify
if err := rel.LoadAttributes(); err != nil {
log.Error("LoadAttributes: %v", err)
return err
}
refName := git.TagPrefix + rel.TagName
apiPusher := rel.Publisher.APIFormat()
apiRepo := rel.Repo.APIFormat(models.AccessModeNone)
apiCommits, err := models.NewPushCommits().ToAPIPayloadCommits(rel.Repo.RepoPath(), rel.Repo.HTMLURL())
if err != nil {
log.Error("commits.ToAPIPayloadCommits failed: %v", err)
return err
}
if err := models.PrepareWebhooks(rel.Repo, models.HookEventPush, &api.PushPayload{
Ref: refName,
Before: git.EmptySHA,
After: commit.ID.String(),
CompareURL: setting.AppURL + models.NewPushCommits().CompareURL,
Commits: apiCommits,
Repo: apiRepo,
Pusher: apiPusher,
Sender: apiPusher,
}); err != nil {
log.Error("PrepareWebhooks: %v", err)
}
gitRepo, err := git.OpenRepository(rel.Repo.RepoPath())
if err != nil {
log.Error("OpenRepository[%s]: %v", rel.Repo.RepoPath(), err)
}
shaSum, err := gitRepo.GetTagCommitID(refName)
if err != nil {
gitRepo.Close()
log.Error("GetTagCommitID[%s]: %v", refName, err)
}
gitRepo.Close()
if err = models.PrepareWebhooks(rel.Repo, models.HookEventCreate, &api.CreatePayload{
Ref: git.RefEndName(refName),
Sha: shaSum,
RefType: "tag",
Repo: apiRepo,
Sender: apiPusher,
}); err != nil {
return fmt.Errorf("PrepareWebhooks: %v", err)
}
}
commit, err := gitRepo.GetTagCommit(rel.TagName)
if err != nil {

View File

@@ -112,6 +112,12 @@
</div>
</div>
{{end}}
<div class="inline field">
<div class="ui checkbox">
<label for="allow_deactivate_all"><strong>{{.i18n.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
<input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if $cfg.AllowDeactivateAll}}checked{{end}}>
</div>
</div>
{{end}}
<!-- SMTP -->

View File

@@ -73,21 +73,27 @@
</div>
{{end}}
</td>
<td class="two wide right aligned">
<td class="three wide right aligned">
{{if not .LatestPullRequest}}
{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{$.i18n.Tr "repo.pulls.compare_changes"}}</button>
</a>
{{end}}
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{$.i18n.Tr "repo.pulls.compare_changes"}}</button>
</a>
{{end}}
{{else}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}">#{{.LatestPullRequest.Issue.Index}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}">{{if ne .LatestPullRequest.BaseRepoID .LatestPullRequest.HeadRepoID}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
{{if .LatestPullRequest.HasMerged}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}" class="ui purple small label"><i class="octicon octicon-git-pull-request"></i> {{$.i18n.Tr "repo.pulls.merged"}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui purple mini label"><i class="octicon octicon-git-pull-request"></i> {{$.i18n.Tr "repo.pulls.merged"}}</a>
{{else if .LatestPullRequest.Issue.IsClosed}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}" class="ui red small label"><i class="octicon octicon-issue-closed"></i> {{$.i18n.Tr "repo.issues.closed_title"}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui red mini label"><i class="octicon octicon-issue-closed"></i> {{$.i18n.Tr "repo.issues.closed_title"}}</a>
{{else}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}" class="ui green small label"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.open_title"}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui green mini label"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.open_title"}}</a>
{{end}}
{{end}}
</td>

View File

@@ -26,7 +26,8 @@
<span class="markdown-info"><i class="octicon octicon-markdown"></i> {{$.root.i18n.Tr "repo.diff.comment.markdown_info"}}</span>
<div class="ui right floated">
{{if $.reply}}
<button name="reply" value="{{$.reply}}" class="ui submit green tiny button btn-reply">{{$.root.i18n.Tr "repo.diff.comment.reply"}}</button>
<input type="hidden" name="reply" value="{{$.reply}}">
<button class="ui submit green tiny button btn-reply" onclick="submitReply(this);">{{$.root.i18n.Tr "repo.diff.comment.reply"}}</button>
{{else}}
{{if $.root.CurrentReview}}
<button name="is_review" value="true" type="submit"

View File

@@ -154,7 +154,7 @@
<div class="menu">
{{range .Labels}}
<div class="item issue-action has-emoji" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
<span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}
<span class="octicon {{if contain $.SelLabelIDs .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}
</div>
{{end}}
</div>

View File

@@ -60,7 +60,7 @@
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.ID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
<a class="item has-emoji" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.ID}}&assignee={{$.AssigneeID}}"><span class="octicon {{if contain $.SelLabelIDs .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}</a>
{{end}}
</div>
</div>
@@ -148,7 +148,7 @@
<div class="menu">
{{range .Labels}}
<div class="item issue-action has-emoji" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
<span class="octicon {{if eq $.SelectLabels .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}
<span class="octicon {{if contain $.SelLabelIDs .ID}}octicon-check{{end}}"></span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name}}
</div>
{{end}}
</div>

View File

@@ -78,7 +78,7 @@
{{ template "repo/issue/view_content/pull". }}
{{end}}
{{if .IsSigned}}
{{ if and (or .IsRepoAdmin .IsRepoIssuesWriter (or (not .Issue.IsLocked))) (not .Repository.IsArchived) }}
{{ if and (or .IsRepoAdmin .IsIssuesWriter (or (not .Issue.IsLocked))) (not .Repository.IsArchived) }}
<div class="comment form">
<a class="avatar" href="{{.SignedUser.HomeLink}}">
<img src="{{.SignedUser.RelAvatarLink}}">

View File

@@ -71,7 +71,7 @@
{{$.i18n.Tr "repo.pulls.reopen_to_merge"}}
{{end}}
</div>
{{if .IsPullBranchDeletable}}
{{if and .IsPullBranchDeletable ( not .IsPullRequestBroken )}}
<div class="ui divider"></div>
<div>
<a class="delete-button ui red button" href="" data-url="{{.DeleteBranchLink}}">{{$.i18n.Tr "repo.branch.delete" .HeadTarget}}</a>

View File

@@ -426,6 +426,7 @@
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
<!-- I know, there is probably a better way to do this -->
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
<input type="hidden" id="type" value="{{.IssueType}}">
<div class="ui basic modal remove-dependency">
<div class="ui icon header">

View File

@@ -2777,6 +2777,12 @@
"description": "search string",
"name": "q",
"in": "query"
},
{
"type": "string",
"description": "filter by type (issues / pulls) if set",
"name": "type",
"in": "query"
}
],
"responses": {
@@ -5284,8 +5290,8 @@
}
],
"responses": {
"200": {
"$ref": "#/responses/StatusList"
"201": {
"$ref": "#/responses/Status"
}
}
}

View File

@@ -18,7 +18,7 @@
{{end}}
{{if .IsResetForm}}
<div class="required inline field {{if .Err_Password}}error{{end}}">
<label for="password">{{.i18n.Tr "password"}}</label>
<label for="password">{{.i18n.Tr "settings.new_password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" autofocus required>
</div>
{{if not .user_signed_in}}
@@ -30,10 +30,31 @@
</div>
</div>
{{end}}
{{if .has_two_factor}}
<h4 class="ui dividing header">
{{.i18n.Tr "twofa"}}
</h4>
<div class="ui warning visible message">{{.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}</div>
{{if .scratch_code}}
<div class="required inline field {{if .Err_Token}}error{{end}}">
<label for="token">{{.i18n.Tr "auth.scratch_code"}}</label>
<input id="token" name="token" type="text" autocomplete="off" autofocus required>
</div>
<input type="hidden" name="scratch_code" value="true">
{{else}}
<div class="required inline field {{if .Err_Passcode}}error{{end}}">
<label for="passcode">{{.i18n.Tr "passcode"}}</label>
<input id="passcode" name="passcode" type="number" autocomplete="off" autofocus required>
</div>
{{end}}
{{end}}
<div class="ui divider"></div>
<div class="inline field">
<label></label>
<button class="ui blue button">{{.i18n.Tr "auth.reset_password_helper"}}</button>
{{if and .has_two_factor (not .scratch_code)}}
<a href="{{.Link}}?code={{.Code}}&amp;scratch_code=true">{{.i18n.Tr "auth.use_scratch_code" | Str2html}}</a>
{{end}}
</div>
{{else}}
<p class="center">{{.i18n.Tr "auth.invalid_code"}}</p>