From 456648adac968df649fe71a44dc81177b9ea16c2 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 20 Dec 2017 11:41:29 +0800 Subject: [PATCH 01/12] chore: upgrade gitea/git version (#3240) ref: https://github.com/go-gitea/gitea/pull/3190 --- vendor/code.gitea.io/git/commit_info.go | 4 ++-- vendor/vendor.json | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vendor/code.gitea.io/git/commit_info.go b/vendor/code.gitea.io/git/commit_info.go index 77fe53bdd..106ebc618 100644 --- a/vendor/code.gitea.io/git/commit_info.go +++ b/vendor/code.gitea.io/git/commit_info.go @@ -79,7 +79,7 @@ func targetedSearch(state *getCommitsInfoState, done chan error) { done <- nil return } - command := NewCommand("rev-list", "-1", "HEAD", "--", entryPath) + command := NewCommand("rev-list", "-1", state.headCommit.ID.String(), "--", entryPath) output, err := command.RunInDir(state.headCommit.repo.Path) if err != nil { done <- err @@ -192,7 +192,7 @@ func getCommitsInfo(state *getCommitsInfoState) error { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) defer cancel() - args := []string{"log", getCommitsInfoPretty, "--name-status", "-c"} + args := []string{"log", state.headCommit.ID.String(), getCommitsInfoPretty, "--name-status", "-c"} if len(state.treePath) > 0 { args = append(args, "--", state.treePath) } diff --git a/vendor/vendor.json b/vendor/vendor.json index f2d4a9aab..e65aa3969 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,10 +3,10 @@ "ignore": "test appengine", "package": [ { - "checksumSHA1": "UnJFMWkh0ulYlOV0etJYgt5SzJY=", + "checksumSHA1": "4OG03XZWTU5eUkWPTOwIB6H4BF0=", "path": "code.gitea.io/git", - "revision": "4768133d10fa395278f545f3bf3ce44552b30ad6", - "revisionTime": "2017-12-10T10:06:09Z" + "revision": "4573c63ec9c8257caf11e361c238b6ceb53781d7", + "revisionTime": "2017-12-20T02:56:39Z" }, { "checksumSHA1": "QQ7g7B9+EIzGjO14KCGEs9TNEzM=", From 7cf17e376ba06e246fb3143e94f3f4b8af259ae4 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 20 Dec 2017 03:42:21 +0000 Subject: [PATCH 02/12] [skip ci] Updated translations via Crowdin --- options/locale/locale_de-DE.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index dbffa85ea..8d6aecfd4 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -489,6 +489,8 @@ mirror_last_synced=Zuletzt synchronisiert watchers=Beobachter stargazers=In Favoriten von forks=Forks +pick_reaction=Wähle eine Reaktion +reactions_more=und %d weitere form.reach_limit_of_creation=Du hast bereits dein Limit von %d Repositories erreicht. form.name_reserved=Der Repository-Name '%s' ist reserviert. @@ -539,6 +541,7 @@ pulls=Pull-Requests labels=Label milestones=Meilensteine commits=Commits +commit=Committen releases=Releases file_raw=Originalformat file_history=Verlauf @@ -1544,6 +1547,7 @@ no_read=Du hast momentan keine gelesenen Benachrichtigungen. pin=Benachrichtigung pinnen mark_as_read=Als gelesen markieren mark_as_unread=Als ungelesen markieren +mark_all_as_read=Alle als gelesen markieren [gpg] error.extract_sign=Die Signatur konnte nicht extrahiert werden From e67b4055f9087cc381df437502f3cd912abb53ed Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Tue, 19 Dec 2017 21:37:56 -0800 Subject: [PATCH 03/12] Fix repo-transfer-and-team-repo-count bug (#3241) --- models/repo.go | 14 -------------- models/repo_test.go | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/models/repo.go b/models/repo.go index ff0d00308..40495e439 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1503,20 +1503,6 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error // Remove old team-repository relations. if owner.IsOrganization() { - if err = owner.getTeams(sess); err != nil { - return fmt.Errorf("getTeams: %v", err) - } - for _, t := range owner.Teams { - if !t.hasRepository(sess, repo.ID) { - continue - } - - t.NumRepos-- - if _, err := sess.ID(t.ID).Cols("num_repos").Update(t); err != nil { - return fmt.Errorf("decrease team repository count '%d': %v", t.ID, err) - } - } - if err = owner.removeOrgRepo(sess, repo.ID); err != nil { return fmt.Errorf("removeOrgRepo: %v", err) } diff --git a/models/repo_test.go b/models/repo_test.go index 34eaa16c0..752ffc2dd 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -153,3 +153,26 @@ func TestRepoLocalCopyPath(t *testing.T) { setting.Repository.Local.LocalCopyPath = tempPath assert.Equal(t, expected, repo.LocalCopyPath()) } + +func TestTransferOwnership(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + repo.Owner = AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User) + assert.NoError(t, TransferOwnership(doer, "user2", repo)) + + transferredRepo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) + assert.EqualValues(t, 2, transferredRepo.OwnerID) + + assert.False(t, com.IsExist(RepoPath("user3", "repo3"))) + assert.True(t, com.IsExist(RepoPath("user2", "repo3"))) + AssertExistsAndLoadBean(t, &Action{ + OpType: ActionTransferRepo, + ActUserID: 2, + RepoID: 3, + Content: "user3/repo3", + }) + + CheckConsistencyFor(t, &Repository{}, &User{}, &Team{}) +} From 529482135c8e9304dd7cdf08772eaba61d903894 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 20 Dec 2017 06:59:56 -0600 Subject: [PATCH 04/12] Support default private when creating or migrating repository (#3239) * support default private when creating or migrating repository * fix fmt * use string constants on repository default private in app.ini * fix fmt --- custom/conf/app.ini.sample | 2 ++ .../doc/advanced/config-cheat-sheet.en-us.md | 1 + .../doc/advanced/config-cheat-sheet.zh-cn.md | 1 + modules/setting/setting.go | 9 +++++++++ routers/repo/repo.go | 17 +++++++++++++++-- 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 6a8ae48a9..abe004217 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -16,6 +16,8 @@ SCRIPT_TYPE = bash ANSI_CHARSET = ; Force every new repository to be private FORCE_PRIVATE = false +; Default private when create a new repository, could be: last, private, public. Default is last which means last user repo visiblity. +DEFAULT_PRIVATE = last ; Global maximum creation limit of repository per user, -1 means no limit MAX_CREATION_LIMIT = -1 ; Mirror sync queue length, increase if mirror syncing starts hanging diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index ea01b07d1..b5517080d 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -39,6 +39,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `SCRIPT_TYPE`: The script type your server supports, usually this is `bash`, but some customers report that they only have `sh`. - `ANSI_CHARSET`: The default charset for an unrecognized charset. - `FORCE_PRIVATE`: Force every new repository to be private. +- `DEFAULT_PRIVATE`: Default private when create a new repository, could be: `last`, `private` and `public`. Default is last which means last user repo visiblity. - `MAX_CREATION_LIMIT`: Global maximum creation limit of repositories per user, `-1` means no limit. - `PULL_REQUEST_QUEUE_LENGTH`:exclamation:: Length of pull request patch test queue, make it as large as possible. - `MIRROR_QUEUE_LENGTH`: Patch test queue length, increase if pull request patch testing starts hanging. Defaults to 1000. diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index 7ec650ddb..9a11d960c 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -29,6 +29,7 @@ menu: - `SCRIPT_TYPE`: 服务器支持的Shell类型,通常是 `bash`,但有些服务器也有可能是 `sh`。 - `ANSI_CHARSET`: 默认字符编码。 - `FORCE_PRIVATE`: 强制所有git工程必须私有。 +- `DEFAULT_PRIVATE`: 默认创建的git工程为私有。 可以是`last`, `private` 或 `public`。默认值是 `last`表示用户最后创建的Repo的选择。 - `MAX_CREATION_LIMIT`: 全局最大每个用户创建的git工程数目, `-1` 表示没限制。 - `PULL_REQUEST_QUEUE_LENGTH`: 小心:合并请求测试队列的长度,尽量放大。 diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 7d86ef305..587666227 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -70,6 +70,13 @@ type MarkupParser struct { IsInputFile bool } +// enumerates all the policy repository creating +const ( + RepoCreatingLastUserVisibility = "last" + RepoCreatingPrivate = "private" + RepoCreatingPublic = "public" +) + // settings var ( // AppVer settings @@ -180,6 +187,7 @@ var ( Repository = struct { AnsiCharset string ForcePrivate bool + DefaultPrivate string MaxCreationLimit int MirrorQueueLength int PullRequestQueueLength int @@ -209,6 +217,7 @@ var ( }{ AnsiCharset: "", ForcePrivate: false, + DefaultPrivate: RepoCreatingLastUserVisibility, MaxCreationLimit: -1, MirrorQueueLength: 1000, PullRequestQueueLength: 1000, diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 36105bfe1..aedc4e547 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -81,6 +81,19 @@ func checkContextUser(ctx *context.Context, uid int64) *models.User { return org } +func getRepoPrivate(ctx *context.Context) bool { + switch strings.ToLower(setting.Repository.DefaultPrivate) { + case setting.RepoCreatingLastUserVisibility: + return ctx.User.LastRepoVisibility + case setting.RepoCreatingPrivate: + return true + case setting.RepoCreatingPublic: + return false + default: + return ctx.User.LastRepoVisibility + } +} + // Create render creating repository page func Create(ctx *context.Context) { if !ctx.User.CanCreateRepo() { @@ -94,7 +107,7 @@ func Create(ctx *context.Context) { ctx.Data["Licenses"] = models.Licenses ctx.Data["Readmes"] = models.Readmes ctx.Data["readme"] = "Default" - ctx.Data["private"] = ctx.User.LastRepoVisibility + ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) @@ -170,7 +183,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { // Migrate render migration of repository page func Migrate(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_migrate") - ctx.Data["private"] = ctx.User.LastRepoVisibility + ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate ctx.Data["mirror"] = ctx.Query("mirror") == "1" ctx.Data["LFSActive"] = setting.LFS.StartServer From 515cdaa85d6087d91a61ebe74fae39e0c4bdf1c4 Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Wed, 20 Dec 2017 23:43:26 -0800 Subject: [PATCH 05/12] Fix ignored errors when checking if organization, team member (#3177) --- models/org.go | 39 +++++++------- models/org_team.go | 31 +++++++---- models/org_team_test.go | 19 ++++--- models/org_test.go | 101 +++++++++++++++++++++++------------ models/repo.go | 14 +++-- models/user.go | 14 ++++- modules/context/org.go | 13 +++-- routers/api/v1/api.go | 10 +++- routers/api/v1/org/member.go | 41 ++++++++++---- routers/api/v1/org/team.go | 6 ++- routers/api/v1/repo/fork.go | 6 ++- routers/api/v1/repo/repo.go | 39 +++++++++++--- routers/repo/issue.go | 32 ++++++++--- routers/repo/pull.go | 6 ++- routers/repo/repo.go | 12 ++++- routers/repo/setting.go | 42 ++++----------- 16 files changed, 281 insertions(+), 144 deletions(-) diff --git a/models/org.go b/models/org.go index b349e4c17..a28a8e28e 100644 --- a/models/org.go +++ b/models/org.go @@ -21,13 +21,13 @@ var ( ) // IsOwnedBy returns true if given user is in the owner team. -func (org *User) IsOwnedBy(uid int64) bool { +func (org *User) IsOwnedBy(uid int64) (bool, error) { return IsOrganizationOwner(org.ID, uid) } // IsOrgMember returns true if given user is member of organization. -func (org *User) IsOrgMember(uid int64) bool { - return org.IsOrganization() && IsOrganizationMember(org.ID, uid) +func (org *User) IsOrgMember(uid int64) (bool, error) { + return IsOrganizationMember(org.ID, uid) } func (org *User) getTeam(e Engine, name string) (*Team, error) { @@ -285,32 +285,32 @@ type OrgUser struct { } // IsOrganizationOwner returns true if given user is in the owner team. -func IsOrganizationOwner(orgID, uid int64) bool { - has, _ := x. +func IsOrganizationOwner(orgID, uid int64) (bool, error) { + return x. Where("is_owner=?", true). And("uid=?", uid). And("org_id=?", orgID). - Get(new(OrgUser)) - return has + Table("org_user"). + Exist() } // IsOrganizationMember returns true if given user is member of organization. -func IsOrganizationMember(orgID, uid int64) bool { - has, _ := x. +func IsOrganizationMember(orgID, uid int64) (bool, error) { + return x. Where("uid=?", uid). And("org_id=?", orgID). - Get(new(OrgUser)) - return has + Table("org_user"). + Exist() } // IsPublicMembership returns true if given user public his/her membership. -func IsPublicMembership(orgID, uid int64) bool { - has, _ := x. +func IsPublicMembership(orgID, uid int64) (bool, error) { + return x. Where("uid=?", uid). And("org_id=?", orgID). And("is_public=?", true). - Get(new(OrgUser)) - return has + Table("org_user"). + Exist() } func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) { @@ -401,8 +401,9 @@ func ChangeOrgUserStatus(orgID, uid int64, public bool) error { // AddOrgUser adds new user to given organization. func AddOrgUser(orgID, uid int64) error { - if IsOrganizationMember(orgID, uid) { - return nil + isAlreadyMember, err := IsOrganizationMember(orgID, uid) + if err != nil || isAlreadyMember { + return err } sess := x.NewSession() @@ -447,7 +448,9 @@ func RemoveOrgUser(orgID, userID int64) error { } // Check if the user to delete is the last member in owner team. - if IsOrganizationOwner(orgID, userID) { + if isOwner, err := IsOrganizationOwner(orgID, userID); err != nil { + return err + } else if isOwner { t, err := org.GetOwnerTeam() if err != nil { return err diff --git a/models/org_team.go b/models/org_team.go index dcbf07383..1e3bc2707 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -8,6 +8,8 @@ import ( "errors" "fmt" "strings" + + "code.gitea.io/gitea/modules/log" ) const ownerTeamName = "Owners" @@ -47,7 +49,12 @@ func (t *Team) IsOwnerTeam() bool { // IsMember returns true if given user is a member of team. func (t *Team) IsMember(userID int64) bool { - return IsTeamMember(t.OrgID, t.ID, userID) + isMember, err := IsTeamMember(t.OrgID, t.ID, userID) + if err != nil { + log.Error(4, "IsMember: %v", err) + return false + } + return isMember } func (t *Team) getRepositories(e Engine) error { @@ -413,17 +420,17 @@ type TeamUser struct { UID int64 `xorm:"UNIQUE(s)"` } -func isTeamMember(e Engine, orgID, teamID, userID int64) bool { - has, _ := e. +func isTeamMember(e Engine, orgID, teamID, userID int64) (bool, error) { + return e. Where("org_id=?", orgID). And("team_id=?", teamID). And("uid=?", userID). - Get(new(TeamUser)) - return has + Table("team_user"). + Exist() } // IsTeamMember returns true if given user is a member of team. -func IsTeamMember(orgID, teamID, userID int64) bool { +func IsTeamMember(orgID, teamID, userID int64) (bool, error) { return isTeamMember(x, orgID, teamID, userID) } @@ -471,8 +478,9 @@ func GetUserTeams(orgID, userID int64) ([]*Team, error) { // AddTeamMember adds new membership of given team to given organization, // the user will have membership to given organization automatically when needed. func AddTeamMember(team *Team, userID int64) error { - if IsTeamMember(team.OrgID, team.ID, userID) { - return nil + isAlreadyMember, err := IsTeamMember(team.OrgID, team.ID, userID) + if err != nil || isAlreadyMember { + return err } if err := AddOrgUser(team.OrgID, userID); err != nil { @@ -529,8 +537,9 @@ func AddTeamMember(team *Team, userID int64) error { } func removeTeamMember(e Engine, team *Team, userID int64) error { - if !isTeamMember(e, team.OrgID, team.ID, userID) { - return nil + isMember, err := isTeamMember(e, team.OrgID, team.ID, userID) + if err != nil || !isMember { + return err } // Check if the user to delete is the last member in owner team. @@ -566,7 +575,7 @@ func removeTeamMember(e Engine, team *Team, userID int64) error { // This must exist. ou := new(OrgUser) - _, err := e. + _, err = e. Where("uid = ?", userID). And("org_id = ?", team.OrgID). Get(ou) diff --git a/models/org_team_test.go b/models/org_team_test.go index 9afd733d8..05429c7cc 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -250,16 +250,21 @@ func TestDeleteTeam(t *testing.T) { func TestIsTeamMember(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) + test := func(orgID, teamID, userID int64, expected bool) { + isMember, err := IsTeamMember(orgID, teamID, userID) + assert.NoError(t, err) + assert.Equal(t, expected, isMember) + } - assert.True(t, IsTeamMember(3, 1, 2)) - assert.False(t, IsTeamMember(3, 1, 4)) - assert.False(t, IsTeamMember(3, 1, NonexistentID)) + test(3, 1, 2, true) + test(3, 1, 4, false) + test(3, 1, NonexistentID, false) - assert.True(t, IsTeamMember(3, 2, 2)) - assert.True(t, IsTeamMember(3, 2, 4)) + test(3, 2, 2, true) + test(3, 2, 4, true) - assert.False(t, IsTeamMember(3, NonexistentID, NonexistentID)) - assert.False(t, IsTeamMember(NonexistentID, NonexistentID, NonexistentID)) + test(3, NonexistentID, NonexistentID, false) + test(NonexistentID, NonexistentID, NonexistentID, false) } func TestGetTeamMembers(t *testing.T) { diff --git a/models/org_test.go b/models/org_test.go index 8f59af074..aef313d05 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -12,28 +12,44 @@ import ( func TestUser_IsOwnedBy(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - assert.True(t, org.IsOwnedBy(2)) - assert.False(t, org.IsOwnedBy(1)) - assert.False(t, org.IsOwnedBy(3)) - assert.False(t, org.IsOwnedBy(4)) - - nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - assert.False(t, nonOrg.IsOwnedBy(2)) - assert.False(t, nonOrg.IsOwnedBy(3)) + for _, testCase := range []struct { + OrgID int64 + UserID int64 + ExpectedOwner bool + }{ + {3, 2, true}, + {3, 1, false}, + {3, 3, false}, + {3, 4, false}, + {2, 2, false}, // user2 is not an organization + {2, 3, false}, + } { + org := AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User) + isOwner, err := org.IsOwnedBy(testCase.UserID) + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedOwner, isOwner) + } } func TestUser_IsOrgMember(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) - assert.True(t, org.IsOrgMember(2)) - assert.True(t, org.IsOrgMember(4)) - assert.False(t, org.IsOrgMember(1)) - assert.False(t, org.IsOrgMember(3)) - - nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) - assert.False(t, nonOrg.IsOrgMember(2)) - assert.False(t, nonOrg.IsOrgMember(3)) + for _, testCase := range []struct { + OrgID int64 + UserID int64 + ExpectedMember bool + }{ + {3, 2, true}, + {3, 4, true}, + {3, 1, false}, + {3, 3, false}, + {2, 2, false}, // user2 is not an organization + {2, 3, false}, + } { + org := AssertExistsAndLoadBean(t, &User{ID: testCase.OrgID}).(*User) + isMember, err := org.IsOrgMember(testCase.UserID) + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedMember, isMember) + } } func TestUser_GetTeam(t *testing.T) { @@ -257,31 +273,46 @@ func TestDeleteOrganization(t *testing.T) { func TestIsOrganizationOwner(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - assert.True(t, IsOrganizationOwner(3, 2)) - assert.False(t, IsOrganizationOwner(3, 3)) - assert.True(t, IsOrganizationOwner(6, 5)) - assert.False(t, IsOrganizationOwner(6, 4)) - assert.False(t, IsOrganizationOwner(NonexistentID, NonexistentID)) + test := func(orgID, userID int64, expected bool) { + isOwner, err := IsOrganizationOwner(orgID, userID) + assert.NoError(t, err) + assert.EqualValues(t, expected, isOwner) + } + test(3, 2, true) + test(3, 3, false) + test(6, 5, true) + test(6, 4, false) + test(NonexistentID, NonexistentID, false) } func TestIsOrganizationMember(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - assert.True(t, IsOrganizationMember(3, 2)) - assert.False(t, IsOrganizationMember(3, 3)) - assert.True(t, IsOrganizationMember(3, 4)) - assert.True(t, IsOrganizationMember(6, 5)) - assert.False(t, IsOrganizationMember(6, 4)) - assert.False(t, IsOrganizationMember(NonexistentID, NonexistentID)) + test := func(orgID, userID int64, expected bool) { + isMember, err := IsOrganizationMember(orgID, userID) + assert.NoError(t, err) + assert.EqualValues(t, expected, isMember) + } + test(3, 2, true) + test(3, 3, false) + test(3, 4, true) + test(6, 5, true) + test(6, 4, false) + test(NonexistentID, NonexistentID, false) } func TestIsPublicMembership(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - assert.True(t, IsPublicMembership(3, 2)) - assert.False(t, IsPublicMembership(3, 3)) - assert.False(t, IsPublicMembership(3, 4)) - assert.True(t, IsPublicMembership(6, 5)) - assert.False(t, IsPublicMembership(6, 4)) - assert.False(t, IsPublicMembership(NonexistentID, NonexistentID)) + test := func(orgID, userID int64, expected bool) { + isMember, err := IsPublicMembership(orgID, userID) + assert.NoError(t, err) + assert.EqualValues(t, expected, isMember) + } + test(3, 2, true) + test(3, 3, false) + test(3, 4, false) + test(6, 5, true) + test(6, 4, false) + test(NonexistentID, NonexistentID, false) } func TestGetOrgsByUserID(t *testing.T) { diff --git a/models/repo.go b/models/repo.go index 40495e439..7c538525f 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1493,12 +1493,18 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error // Dummy object. collaboration := &Collaboration{RepoID: repo.ID} for _, c := range collaborators { - collaboration.UserID = c.ID - if c.ID == newOwner.ID || newOwner.IsOrgMember(c.ID) { - if _, err = sess.Delete(collaboration); err != nil { - return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) + if c.ID != newOwner.ID { + isMember, err := newOwner.IsOrgMember(c.ID) + if err != nil { + return fmt.Errorf("IsOrgMember: %v", err) + } else if !isMember { + continue } } + collaboration.UserID = c.ID + if _, err = sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) + } } // Remove old team-repository relations. diff --git a/models/user.go b/models/user.go index fa5dc73de..3839e1459 100644 --- a/models/user.go +++ b/models/user.go @@ -487,12 +487,22 @@ func (u *User) IsOrganization() bool { // IsUserOrgOwner returns true if user is in the owner team of given organization. func (u *User) IsUserOrgOwner(orgID int64) bool { - return IsOrganizationOwner(orgID, u.ID) + isOwner, err := IsOrganizationOwner(orgID, u.ID) + if err != nil { + log.Error(4, "IsOrganizationOwner: %v", err) + return false + } + return isOwner } // IsPublicMember returns true if user public his/her membership in given organization. func (u *User) IsPublicMember(orgID int64) bool { - return IsPublicMembership(orgID, u.ID) + isMember, err := IsPublicMembership(orgID, u.ID) + if err != nil { + log.Error(4, "IsPublicMembership: %v", err) + return false + } + return isMember } func (u *User) getOrganizationCount(e Engine) (int64, error) { diff --git a/modules/context/org.go b/modules/context/org.go index cfe9a2622..29cc67dcc 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -73,14 +73,21 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { ctx.Org.IsTeamMember = true ctx.Org.IsTeamAdmin = true } else if ctx.IsSigned { - ctx.Org.IsOwner = org.IsOwnedBy(ctx.User.ID) + ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.Handle(500, "IsOwnedBy", err) + return + } + if ctx.Org.IsOwner { ctx.Org.IsMember = true ctx.Org.IsTeamMember = true ctx.Org.IsTeamAdmin = true } else { - if org.IsOrgMember(ctx.User.ID) { - ctx.Org.IsMember = true + ctx.Org.IsMember, err = org.IsOrgMember(ctx.User.ID) + if err != nil { + ctx.Handle(500, "IsOrgMember", err) + return } } } else { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f6ed844d4..588a76361 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -177,7 +177,10 @@ func reqOrgMembership() macaron.Handler { return } - if !models.IsOrganizationMember(orgID, ctx.User.ID) { + if isMember, err := models.IsOrganizationMember(orgID, ctx.User.ID); err != nil { + ctx.Error(500, "IsOrganizationMember", err) + return + } else if !isMember { if ctx.Org.Organization != nil { ctx.Error(403, "", "Must be an organization member") } else { @@ -200,7 +203,10 @@ func reqOrgOwnership() macaron.Handler { return } - if !models.IsOrganizationOwner(orgID, ctx.User.ID) { + isOwner, err := models.IsOrganizationOwner(orgID, ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOrganizationOwner", err) + } else if !isOwner { if ctx.Org.Organization != nil { ctx.Error(403, "", "Must be an organization owner") } else { diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 7cae7c19f..0cc531780 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -67,7 +67,15 @@ func ListMembers(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/UserList" - publicOnly := ctx.User == nil || !ctx.Org.Organization.IsOrgMember(ctx.User.ID) + publicOnly := true + if ctx.User != nil { + isMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOrgMember", err) + return + } + publicOnly = !isMember + } listMembers(ctx, publicOnly) } @@ -119,19 +127,30 @@ func IsMember(ctx *context.APIContext) { if ctx.Written() { return } - if ctx.User != nil && ctx.Org.Organization.IsOrgMember(ctx.User.ID) { - if ctx.Org.Organization.IsOrgMember(userToCheck.ID) { - ctx.Status(204) - } else { + if ctx.User != nil { + userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOrgMember", err) + return + } else if userIsMember { + userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOrgMember", err) + } else if userToCheckIsMember { + ctx.Status(204) + } else { + ctx.Status(404) + } + return + } else if ctx.User.ID == userToCheck.ID { ctx.Status(404) + return } - } else if ctx.User != nil && ctx.User.ID == userToCheck.ID { - ctx.Status(404) - } else { - redirectURL := fmt.Sprintf("%sapi/v1/orgs/%s/public_members/%s", - setting.AppURL, ctx.Org.Organization.Name, userToCheck.Name) - ctx.Redirect(redirectURL, 302) } + + redirectURL := fmt.Sprintf("%sapi/v1/orgs/%s/public_members/%s", + setting.AppURL, ctx.Org.Organization.Name, userToCheck.Name) + ctx.Redirect(redirectURL, 302) } // IsPublicMember check if a user is a public member of an organization diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index eead7dd8f..b999d62aa 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -176,7 +176,11 @@ func GetTeamMembers(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/UserList" - if !models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID) { + isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOrganizationMember", err) + return + } else if !isMember { ctx.Status(404) return } diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 90301cc35..ec1b37b91 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -89,7 +89,11 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) { } return } - if !org.IsOrgMember(ctx.User.ID) { + isMember, err := org.IsOrgMember(ctx.User.ID) + if err != nil { + ctx.Handle(500, "IsOrgMember", err) + return + } else if !isMember { ctx.Status(403) return } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index b154d50a0..c9c7aa805 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -108,8 +108,19 @@ func Search(ctx *context.APIContext) { } // Check visibility. - if ctx.IsSigned && (ctx.User.ID == repoOwner.ID || (repoOwner.IsOrganization() && repoOwner.IsOwnedBy(ctx.User.ID))) { - opts.Private = true + if ctx.IsSigned { + if ctx.User.ID == repoOwner.ID { + opts.Private = true + } else if repoOwner.IsOrganization() { + opts.Private, err = repoOwner.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.JSON(500, api.SearchError{ + OK: false, + Error: err.Error(), + }) + return + } + } } } @@ -245,7 +256,11 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) { return } - if !org.IsOwnedBy(ctx.User.ID) { + isOwner, err := org.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.Handle(500, "IsOwnedBy", err) + return + } else if !isOwner { ctx.Error(403, "", "Given user is not owner of organization.") return } @@ -292,7 +307,11 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) { if ctxUser.IsOrganization() && !ctx.User.IsAdmin { // Check ownership of organization. - if !ctxUser.IsOwnedBy(ctx.User.ID) { + isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOwnedBy", err) + return + } else if !isOwner { ctx.Error(403, "", "Given user is not owner of organization.") return } @@ -431,9 +450,15 @@ func Delete(ctx *context.APIContext) { owner := ctx.Repo.Owner repo := ctx.Repo.Repository - if owner.IsOrganization() && !owner.IsOwnedBy(ctx.User.ID) { - ctx.Error(403, "", "Given user is not owner of organization.") - return + if owner.IsOrganization() { + isOwner, err := owner.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.Error(500, "IsOwnedBy", err) + return + } else if !isOwner { + ctx.Error(403, "", "Given user is not owner of organization.") + return + } } if err := models.DeleteRepository(ctx.User, owner.ID, repo.ID); err != nil { diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 578ead134..4e12d62f3 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -475,6 +475,26 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) } +// commentTag returns the CommentTag for a comment in/with the given repo, poster and issue +func commentTag(repo *models.Repository, poster *models.User, issue *models.Issue) (models.CommentTag, error) { + if repo.IsOwnedBy(poster.ID) { + return models.CommentTagOwner, nil + } else if repo.Owner.IsOrganization() { + isOwner, err := repo.Owner.IsOwnedBy(poster.ID) + if err != nil { + return models.CommentTagNone, err + } else if isOwner { + return models.CommentTagOwner, nil + } + } + if poster.IsWriterOfRepo(repo) { + return models.CommentTagWriter, nil + } else if poster.ID == issue.PosterID { + return models.CommentTagPoster, nil + } + return models.CommentTagNone, nil +} + // ViewIssue render issue view page func ViewIssue(ctx *context.Context) { ctx.Data["RequireHighlightJS"] = true @@ -644,15 +664,11 @@ func ViewIssue(ctx *context.Context) { continue } - if repo.IsOwnedBy(comment.PosterID) || - (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) { - comment.ShowTag = models.CommentTagOwner - } else if comment.Poster.IsWriterOfRepo(repo) { - comment.ShowTag = models.CommentTagWriter - } else if comment.PosterID == issue.PosterID { - comment.ShowTag = models.CommentTagPoster + comment.ShowTag, err = commentTag(repo, comment.Poster, issue) + if err != nil { + ctx.Handle(500, "commentTag", err) + return } - marked[comment.PosterID] = comment.ShowTag isAdded := false diff --git a/routers/repo/pull.go b/routers/repo/pull.go index c2f0a07fe..5575009af 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -173,7 +173,11 @@ func ForkPost(ctx *context.Context, form auth.CreateRepoForm) { // Check ownership of organization. if ctxUser.IsOrganization() { - if !ctxUser.IsOwnedBy(ctx.User.ID) { + isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.Handle(500, "IsOwnedBy", err) + return + } else if !isOwner { ctx.Error(403) return } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index aedc4e547..4cd7c8062 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -74,10 +74,20 @@ func checkContextUser(ctx *context.Context, uid int64) *models.User { } // Check ownership of organization. - if !org.IsOrganization() || !(ctx.User.IsAdmin || org.IsOwnedBy(ctx.User.ID)) { + if !org.IsOrganization() { ctx.Error(403) return nil } + if !ctx.User.IsAdmin { + isOwner, err := org.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.Handle(500, "IsOwnedBy", err) + return nil + } else if !isOwner { + ctx.Error(403) + return nil + } + } return org } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 329802673..8cb551707 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -234,13 +234,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { return } - if ctx.Repo.Owner.IsOrganization() { - if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) { - ctx.Error(404) - return - } - } - if !repo.IsMirror { ctx.Error(404) return @@ -268,13 +261,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { return } - if ctx.Repo.Owner.IsOrganization() { - if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) { - ctx.Error(404) - return - } - } - newOwner := ctx.Query("new_owner_name") isExist, err := models.IsUserExist(0, newOwner) if err != nil { @@ -307,13 +293,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { return } - if ctx.Repo.Owner.IsOrganization() { - if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) { - ctx.Error(404) - return - } - } - if err := models.DeleteRepository(ctx.User, ctx.Repo.Owner.ID, repo.ID); err != nil { ctx.Handle(500, "DeleteRepository", err) return @@ -333,13 +312,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { return } - if ctx.Repo.Owner.IsOrganization() { - if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) { - ctx.Error(404) - return - } - } - repo.DeleteWiki() log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -393,10 +365,16 @@ func CollaborationPost(ctx *context.Context) { } // Check if user is organization member. - if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgMember(u.ID) { - ctx.Flash.Info(ctx.Tr("repo.settings.user_is_org_member")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") - return + if ctx.Repo.Owner.IsOrganization() { + isMember, err := ctx.Repo.Owner.IsOrgMember(u.ID) + if err != nil { + ctx.Handle(500, "IsOrgMember", err) + return + } else if isMember { + ctx.Flash.Info(ctx.Tr("repo.settings.user_is_org_member")) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") + return + } } if err = ctx.Repo.Repository.AddCollaborator(u); err != nil { From 156aa42ba00c52e56be5ceca1615a574fa1fd052 Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Thu, 21 Dec 2017 23:00:30 -0800 Subject: [PATCH 06/12] Update code.gitea.io/git (#3251) --- vendor/code.gitea.io/git/commit_info.go | 12 ++++++++++++ vendor/vendor.json | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/vendor/code.gitea.io/git/commit_info.go b/vendor/code.gitea.io/git/commit_info.go index 106ebc618..6b42b57c9 100644 --- a/vendor/code.gitea.io/git/commit_info.go +++ b/vendor/code.gitea.io/git/commit_info.go @@ -207,6 +207,10 @@ func getCommitsInfo(state *getCommitsInfoState) error { if err := cmd.Start(); err != nil { return err } + // it's okay to ignore the error returned by cmd.Wait(); we expect the + // subprocess to sometimes have a non-zero exit status, since we may + // prematurely close stdout, resulting in a broken pipe. + defer cmd.Wait() numThreads := runtime.NumCPU() done := make(chan error, numThreads) @@ -216,6 +220,14 @@ func getCommitsInfo(state *getCommitsInfoState) error { scanner := bufio.NewScanner(readCloser) err = state.processGitLogOutput(scanner) + + // it is important that we close stdout here; if we do not close + // stdout, the subprocess will keep running, and the deffered call + // cmd.Wait() may block for a long time. + if closeErr := readCloser.Close(); closeErr != nil && err == nil { + err = closeErr + } + for i := 0; i < numThreads; i++ { doneErr := <-done if doneErr != nil && err == nil { diff --git a/vendor/vendor.json b/vendor/vendor.json index e65aa3969..3689f57b6 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,10 +3,10 @@ "ignore": "test appengine", "package": [ { - "checksumSHA1": "4OG03XZWTU5eUkWPTOwIB6H4BF0=", + "checksumSHA1": "Em29XiKkOh5rFFXdkCjqqsQ7fe4=", "path": "code.gitea.io/git", - "revision": "4573c63ec9c8257caf11e361c238b6ceb53781d7", - "revisionTime": "2017-12-20T02:56:39Z" + "revision": "4ec3654064ef7eef4f05f891073a38039ad8d0f7", + "revisionTime": "2017-12-22T02:43:26Z" }, { "checksumSHA1": "QQ7g7B9+EIzGjO14KCGEs9TNEzM=", From a995ad90e1055ad001a025a6d94fcc3d8ea87004 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Fri, 22 Dec 2017 07:01:25 +0000 Subject: [PATCH 07/12] [skip ci] Updated translations via Crowdin --- options/locale/locale_hu-HU.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 23a62c54e..a8b60356a 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -8,7 +8,7 @@ sign_in=Bejelentkezés sign_in_with=Bejelentkezés a következővel sign_out=Kijelentkezés sign_up=Regisztráció -link_account=Fiók Összekötés +link_account=Fiók kapcsolása link_account_signin_or_signup=Bejelentkezés meglévő felhasználóval, hogy a meglévő fiókodhoz kösd, vagy új fiók létrehozása register=Regisztráció website=Webhely @@ -49,22 +49,22 @@ your_settings=Beállításaid all=Összes sources=Saját -mirrors=Tükör +mirrors=Tükrök collaborative=Közreműködő -forks=Másolat +forks=Másolatok activities=Tevékenységek pull_requests=Egyesítési Kérések issues=Hibajegyek -cancel=Mégsem +cancel=Mégse [install] install=Telepítés title=Kezdeti konfiguráció -docker_helper=Hogyha a Gitea-t Docker-en belül futtatod, akkor kérlek olvasd el az irányelveket mielőtt bármit megváltoztatnál ezen az oldalon. -requite_db_desc=Giteához szükséges MySQL, PostgreSQL, SQLite3 vagy TiDB. -db_title=Adatbázis beállításai +docker_helper=Ha Docker-en belül futtatja a Gitea-t, kérjük olvassa el az irányelveket mielőtt bármit megváltoztatna ezen az oldalon. +requite_db_desc=A Giteához MySQL, PostgreSQL, SQLite3 vagy TiDB adatbázis szükséges. +db_title=Adatbázis beállítások db_type=Adatbázis típusa host=Kiszolgáló user=Felhasználó @@ -103,7 +103,7 @@ optional_title=További beállítások email_title=E-mail szolgáltatás beállításai smtp_host=SMTP kiszolgáló smtp_from=Feladó -smtp_from_helper=E-Mail feladójának a címe az RFC 5322 formátumban. Lehet megadni csak egy e-mail címet, vagy "Név" formátumban. +smtp_from_helper=Az e-mail feladójának a címe, RFC 5322 formátumban, amely lehet csak az e-mail cím, vagy a "Név" formátum is. mailer_user=Feladó Felhasználója mailer_password=Feladó Jelszava register_confirm=Regisztráció megerősítésének engedélyezése @@ -140,7 +140,7 @@ run_user_not_match=A futtató felhasználó nem az aktuális felhasználó: %s - save_config_failed=Hiba történt a konfiguráció mentése közben: %v invalid_admin_setting=Rendszergazda fiók beállítása helytelen: %v install_success=Üdvözlünk! Köszönjük, hogy a Gitea-t választotta és jó szórakozást kívánunk a használatához! -invalid_log_root_path=Napó gyökérkönyvtára helytelen: %v +invalid_log_root_path=Napló gyökérkönyvtára helytelen: %v default_keep_email_private=Email cím ne látszódjon" alapértelmezett értéke default_keep_email_private_popup=Ez az alapértelmezett értéke a felhasználó e-mail cím láthatóságának. Ha értéke igaz, akkor minden új felhasználó e-mail címe rejtve marad, amíg a felhasználó nem módosítja a beállítását. default_allow_create_organization=Felhasználó létrehozhat Szervezeteket" jogosultság alapértelmezett értéke From cc7b8e3379f46469d3ec72b044fb0f993fec4d1b Mon Sep 17 00:00:00 2001 From: Antoine GIRARD Date: Sun, 24 Dec 2017 01:33:34 +0100 Subject: [PATCH 08/12] Add more bench (#3161) * Improve makefile + Add benchs * Apply recommendations of @ethantkoenig --- Makefile | 6 +- integrations/benchmarks_test.go | 113 ++++++++++++++++++++++++++++++ integrations/repo_branch_test.go | 2 +- integrations/repo_migrate_test.go | 26 ------- models/unit_tests.go | 14 ++-- 5 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 integrations/benchmarks_test.go diff --git a/Makefile b/Makefile index d7ddd197b..4f4802a02 100644 --- a/Makefile +++ b/Makefile @@ -183,15 +183,15 @@ test-pgsql: integrations.test generate-ini .PHONY: bench-sqlite bench-sqlite: integrations.sqlite.test - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.bench . + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: bench-mysql bench-mysql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test -test.bench . + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: bench-pgsql bench-pgsql: integrations.test generate-ini - GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test -test.bench . + GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . .PHONY: integration-test-coverage diff --git a/integrations/benchmarks_test.go b/integrations/benchmarks_test.go new file mode 100644 index 000000000..7c4196f2b --- /dev/null +++ b/integrations/benchmarks_test.go @@ -0,0 +1,113 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "math/rand" + "net/http" + "testing" + + "code.gitea.io/gitea/models" + api "code.gitea.io/sdk/gitea" +) + +func BenchmarkRepo(b *testing.B) { + samples := []struct { + url string + name string + skipShort bool + }{ + {url: "https://github.com/go-gitea/gitea.git", name: "gitea"}, + {url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"}, + {url: "https://github.com/moby/moby.git", name: "moby", skipShort: true}, + {url: "https://github.com/golang/go.git", name: "go", skipShort: true}, + {url: "https://github.com/torvalds/linux.git", name: "linux", skipShort: true}, + } + prepareTestEnv(b) + session := loginUser(b, "user2") + b.ResetTimer() + + for _, s := range samples { + b.Run(s.name, func(b *testing.B) { + if testing.Short() && s.skipShort { + b.Skip("skipping test in short mode.") + } + b.Run("Migrate", func(b *testing.B) { + for i := 0; i < b.N; i++ { + testRepoMigrate(b, session, s.url, s.name) + } + }) + b.Run("Access", func(b *testing.B) { + var branches []*api.Branch + b.Run("APIBranchList", func(b *testing.B) { + for i := 0; i < b.N; i++ { + req := NewRequestf(b, "GET", "/api/v1/repos/%s/%s/branches", "user2", s.name) + resp := session.MakeRequest(b, req, http.StatusOK) + b.StopTimer() + if len(branches) == 0 { + DecodeJSON(b, resp, &branches) //Store for next phase + } + b.StartTimer() + } + }) + branchCount := len(branches) + b.Run("WebViewCommit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + req := NewRequestf(b, "GET", "/%s/%s/commit/%s", "user2", s.name, branches[i%branchCount].Commit.ID) + session.MakeRequest(b, req, http.StatusOK) + } + }) + }) + }) + } +} + +//StringWithCharset random string (from https://www.calhoun.io/creating-random-strings-in-go/) +func StringWithCharset(length int, charset string) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} + +func BenchmarkRepoBranchCommit(b *testing.B) { + samples := []int64{1, 3, 15, 16} + prepareTestEnv(b) + b.ResetTimer() + + for _, repoID := range samples { + b.StopTimer() + repo := models.AssertExistsAndLoadBean(b, &models.Repository{ID: repoID}).(*models.Repository) + b.StartTimer() + b.Run(repo.Name, func(b *testing.B) { + owner := models.AssertExistsAndLoadBean(b, &models.User{ID: repo.OwnerID}).(*models.User) + session := loginUser(b, owner.LoginName) + b.ResetTimer() + b.Run("Create", func(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + branchName := StringWithCharset(5+rand.Intn(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b.StartTimer() + testCreateBranch(b, session, owner.LoginName, repo.Name, "branch/master", branchName, http.StatusFound) + } + }) + b.Run("Access", func(b *testing.B) { + var branches []*api.Branch + req := NewRequestf(b, "GET", "/api/v1/%s/branches", repo.FullName()) + resp := session.MakeRequest(b, req, http.StatusOK) + DecodeJSON(b, resp, &branches) + branchCount := len(branches) + b.ResetTimer() //We measure from here + for i := 0; i < b.N; i++ { + req := NewRequestf(b, "GET", "/%s/%s/commits/%s", owner.Name, repo.Name, branches[i%branchCount]) + session.MakeRequest(b, req, http.StatusOK) + } + }) + }) + } +} + +//TODO list commits /repos/{owner}/{repo}/commits diff --git a/integrations/repo_branch_test.go b/integrations/repo_branch_test.go index c20fd6192..fb33778cd 100644 --- a/integrations/repo_branch_test.go +++ b/integrations/repo_branch_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/assert" ) -func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { +func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { var csrf string if expectedStatus == http.StatusNotFound { csrf = GetCSRF(t, session, path.Join(user, repo, "src/branch/master")) diff --git a/integrations/repo_migrate_test.go b/integrations/repo_migrate_test.go index 9f41cca82..41791c1d0 100644 --- a/integrations/repo_migrate_test.go +++ b/integrations/repo_migrate_test.go @@ -40,29 +40,3 @@ func TestRepoMigrate(t *testing.T) { session := loginUser(t, "user2") testRepoMigrate(t, session, "https://github.com/go-gitea/git.git", "git") } - -func BenchmarkRepoMigrate(b *testing.B) { - samples := []struct { - url string - name string - }{ - {url: "https://github.com/go-gitea/gitea.git", name: "gitea"}, - {url: "https://github.com/ethantkoenig/manyfiles.git", name: "manyfiles"}, - {url: "https://github.com/moby/moby.git", name: "moby"}, - {url: "https://github.com/golang/go.git", name: "go"}, - {url: "https://github.com/torvalds/linux.git", name: "linux"}, - } - - prepareTestEnv(b) - session := loginUser(b, "user2") - b.ResetTimer() - - for _, s := range samples { - b.Run(s.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - testRepoMigrate(b, session, s.url, s.name) - } - - }) - } -} diff --git a/models/unit_tests.go b/models/unit_tests.go index 25808a948..8fdeb0b14 100644 --- a/models/unit_tests.go +++ b/models/unit_tests.go @@ -115,7 +115,7 @@ func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) } // BeanExists for testing, check if a bean exists -func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool { +func BeanExists(t testing.TB, bean interface{}, conditions ...interface{}) bool { exists, err := loadBeanIfExists(bean, conditions...) assert.NoError(t, err) return exists @@ -123,7 +123,7 @@ func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool // AssertExistsAndLoadBean assert that a bean exists and load it from the test // database -func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...interface{}) interface{} { +func AssertExistsAndLoadBean(t testing.TB, bean interface{}, conditions ...interface{}) interface{} { exists, err := loadBeanIfExists(bean, conditions...) assert.NoError(t, err) assert.True(t, exists, @@ -133,7 +133,7 @@ func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...inter } // GetCount get the count of a bean -func GetCount(t *testing.T, bean interface{}, conditions ...interface{}) int { +func GetCount(t testing.TB, bean interface{}, conditions ...interface{}) int { sess := x.NewSession() defer sess.Close() whereConditions(sess, conditions) @@ -143,7 +143,7 @@ func GetCount(t *testing.T, bean interface{}, conditions ...interface{}) int { } // AssertNotExistsBean assert that a bean does not exist in the test database -func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface{}) { +func AssertNotExistsBean(t testing.TB, bean interface{}, conditions ...interface{}) { exists, err := loadBeanIfExists(bean, conditions...) assert.NoError(t, err) assert.False(t, exists) @@ -158,18 +158,18 @@ func AssertExistsIf(t *testing.T, expected bool, bean interface{}, conditions .. } // AssertSuccessfulInsert assert that beans is successfully inserted -func AssertSuccessfulInsert(t *testing.T, beans ...interface{}) { +func AssertSuccessfulInsert(t testing.TB, beans ...interface{}) { _, err := x.Insert(beans...) assert.NoError(t, err) } // AssertCount assert the count of a bean -func AssertCount(t *testing.T, bean interface{}, expected interface{}) { +func AssertCount(t testing.TB, bean interface{}, expected interface{}) { assert.EqualValues(t, expected, GetCount(t, bean)) } // AssertInt64InRange assert value is in range [low, high] -func AssertInt64InRange(t *testing.T, low, high, value int64) { +func AssertInt64InRange(t testing.TB, low, high, value int64) { assert.True(t, value >= low && value <= high, "Expected value in range [%d, %d], found %d", low, high, value) } From f5155b99136b6e19ab494a97adfcee1810a3d5e7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 24 Dec 2017 15:04:22 -0600 Subject: [PATCH 09/12] Small improve on deleting attachements (#3145) * Small improve on deleting attachements * improve the sequence of deletion --- models/attachment.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/models/attachment.go b/models/attachment.go index e7e0dbf5c..acb1f0716 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -135,19 +135,28 @@ func DeleteAttachment(a *Attachment, remove bool) error { // DeleteAttachments deletes the given attachments and optionally the associated files. func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { - for i, a := range attachments { - if remove { + if len(attachments) == 0 { + return 0, nil + } + + var ids = make([]int64, 0, len(attachments)) + for _, a := range attachments { + ids = append(ids, a.ID) + } + + cnt, err := x.In("id", ids).NoAutoCondition().Delete(attachments[0]) + if err != nil { + return 0, err + } + + if remove { + for i, a := range attachments { if err := os.Remove(a.LocalPath()); err != nil { return i, err } } - - if _, err := x.Delete(a); err != nil { - return i, err - } } - - return len(attachments), nil + return int(cnt), nil } // DeleteAttachmentsByIssue deletes all attachments associated with the given issue. From fabf3f2fc29e143dabefd504cda78d3f47807d2c Mon Sep 17 00:00:00 2001 From: Mike Fellows Date: Mon, 25 Dec 2017 17:23:43 -0500 Subject: [PATCH 10/12] Add an option to allow redirect of http port 80 to https. (#1928) * Add an option to allow redirect of http port 80 to https. This is an "opt in" option (default is to not redirect). It will only redirect if protocol is https and the new REDIRECT_PORT_80 option is set to true. The Port to redirect in previous commit was hardcoded to 80, now it can be specified in the app.ini, defaulting to 80. The boolean option to turn redirection on has been changed to REDIRECT_OTHER_PORT to be logically consistent with the new port option. Signed-off-by: Mike Fellows --- cmd/web.go | 23 +++++++++++++++++++++++ custom/conf/app.ini.sample | 6 ++++++ modules/setting/setting.go | 4 ++++ 3 files changed, 33 insertions(+) diff --git a/cmd/web.go b/cmd/web.go index 55546ea48..473688a8b 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -51,6 +51,26 @@ and it takes care of all the other things for you`, }, } +func runHTTPRedirector() { + source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect) + dest := strings.TrimSuffix(setting.AppURL, "/") + log.Info("Redirecting: %s to %s", source, dest) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + target := dest + r.URL.Path + if len(r.URL.RawQuery) > 0 { + target += "?" + r.URL.RawQuery + } + http.Redirect(w, r, target, http.StatusTemporaryRedirect) + }) + + var err = runHTTP(source, context2.ClearHandler(handler)) + + if err != nil { + log.Fatal(4, "Failed to start port redirection: %v", err) + } +} + func runWeb(ctx *cli.Context) error { if ctx.IsSet("config") { setting.CustomConf = ctx.String("config") @@ -124,6 +144,9 @@ func runWeb(ctx *cli.Context) error { case setting.HTTP: err = runHTTP(listenAddr, context2.ClearHandler(m)) case setting.HTTPS: + if setting.RedirectOtherPort { + go runHTTPRedirector() + } err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) case setting.FCGI: listener, err := net.Listen("tcp", listenAddr) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index abe004217..1a295b5df 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -109,6 +109,12 @@ ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ ; Listen address. Either a IPv4/IPv6 address or the path to a unix socket. HTTP_ADDR = 0.0.0.0 HTTP_PORT = 3000 +; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server +; will be started on PORT_TO_REDIRECT and redirect request to the main +; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for +; PORT_TO_REDIRECT. +REDIRECT_OTHER_PORT = false +PORT_TO_REDIRECT = 80 ; Permission for unix socket UNIX_SOCKET_PERMISSION = 666 ; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 587666227..848cdff64 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -96,6 +96,8 @@ var ( HTTPAddr string HTTPPort string LocalURL string + RedirectOtherPort bool + PortToRedirect string OfflineMode bool DisableRouterLog bool CertFile string @@ -741,6 +743,8 @@ func NewContext() { defaultLocalURL += ":" + HTTPPort + "/" } LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL) + RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) + PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") OfflineMode = sec.Key("OFFLINE_MODE").MustBool() DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(AppWorkPath) From 4c9341f689e840df30db3e227498a30fdb5b88ef Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Mon, 25 Dec 2017 18:25:16 -0500 Subject: [PATCH 11/12] Fix bugs in issue dashboard stats (#3073) --- models/fixtures/issue.yml | 7 +- models/issue.go | 122 ++++++++++++++++++++++------------- models/issue_indexer.go | 2 +- models/issue_test.go | 111 +++++++++++++++++++++++++++++++ routers/api/v1/repo/issue.go | 2 +- routers/repo/issue.go | 2 +- routers/user/home.go | 33 ++++++++-- 7 files changed, 224 insertions(+), 55 deletions(-) diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index 2592ad2de..ff514e706 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -47,6 +47,8 @@ content: content for the fourth issue is_closed: true is_pull: false + created_unix: 946684830 + updated_unix: 978307200 - id: 5 @@ -57,6 +59,9 @@ content: content for the fifth issue is_closed: true is_pull: false + created_unix: 946684840 + updated_unix: 978307200 + - id: 6 repo_id: 3 @@ -68,5 +73,5 @@ is_closed: false is_pull: false num_comments: 0 - created_unix: 946684800 + created_unix: 946684850 updated_unix: 978307200 diff --git a/models/issue.go b/models/issue.go index 984e6e31c..13973b7d1 100644 --- a/models/issue.go +++ b/models/issue.go @@ -10,14 +10,15 @@ import ( "sort" "strings" - api "code.gitea.io/sdk/gitea" - "github.com/Unknwon/com" - "github.com/go-xorm/xorm" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + api "code.gitea.io/sdk/gitea" + + "github.com/Unknwon/com" + "github.com/go-xorm/builder" + "github.com/go-xorm/xorm" ) // Issue represents an issue or pull request of repository. @@ -1022,12 +1023,11 @@ func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) { // IssuesOptions represents options of an issue. type IssuesOptions struct { - RepoID int64 + RepoIDs []int64 // include all repos if empty AssigneeID int64 PosterID int64 MentionedID int64 MilestoneID int64 - RepoIDs []int64 Page int PageSize int IsClosed util.OptionalBool @@ -1073,9 +1073,7 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) error { sess.In("issue.id", opts.IssueIDs) } - if opts.RepoID > 0 { - sess.And("issue.repo_id=?", opts.RepoID) - } else if len(opts.RepoIDs) > 0 { + if len(opts.RepoIDs) > 0 { // In case repository IDs are provided but actually no repository has issue. sess.In("issue.repo_id", opts.RepoIDs) } @@ -1339,58 +1337,92 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) { return stats, err } +// UserIssueStatsOptions contains parameters accepted by GetUserIssueStats. +type UserIssueStatsOptions struct { + UserID int64 + RepoID int64 + UserRepoIDs []int64 + FilterMode int + IsPull bool + IsClosed bool +} + // GetUserIssueStats returns issue statistic information for dashboard by given conditions. -func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPull bool) *IssueStats { +func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) { + var err error stats := &IssueStats{} - countSession := func(isClosed, isPull bool, repoID int64, repoIDs []int64) *xorm.Session { - sess := x. - Where("issue.is_closed = ?", isClosed). - And("issue.is_pull = ?", isPull) - - if repoID > 0 { - sess.And("repo_id = ?", repoID) - } else if len(repoIDs) > 0 { - sess.In("repo_id", repoIDs) - } - - return sess + cond := builder.NewCond() + cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull}) + if opts.RepoID > 0 { + cond = cond.And(builder.Eq{"issue.repo_id": opts.RepoID}) } - stats.AssignCount, _ = countSession(false, isPull, repoID, nil). - And("assignee_id = ?", uid). - Count(new(Issue)) - - stats.CreateCount, _ = countSession(false, isPull, repoID, nil). - And("poster_id = ?", uid). - Count(new(Issue)) - - stats.YourRepositoriesCount, _ = countSession(false, isPull, repoID, repoIDs). - Count(new(Issue)) - - switch filterMode { + switch opts.FilterMode { case FilterModeAll: - stats.OpenCount, _ = countSession(false, isPull, repoID, repoIDs). + stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). + And(builder.In("issue.repo_id", opts.UserRepoIDs)). Count(new(Issue)) - stats.ClosedCount, _ = countSession(true, isPull, repoID, repoIDs). + if err != nil { + return nil, err + } + stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). + And(builder.In("issue.repo_id", opts.UserRepoIDs)). Count(new(Issue)) + if err != nil { + return nil, err + } case FilterModeAssign: - stats.OpenCount, _ = countSession(false, isPull, repoID, nil). - And("assignee_id = ?", uid). + stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). + And("assignee_id = ?", opts.UserID). Count(new(Issue)) - stats.ClosedCount, _ = countSession(true, isPull, repoID, nil). - And("assignee_id = ?", uid). + if err != nil { + return nil, err + } + stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). + And("assignee_id = ?", opts.UserID). Count(new(Issue)) + if err != nil { + return nil, err + } case FilterModeCreate: - stats.OpenCount, _ = countSession(false, isPull, repoID, nil). - And("poster_id = ?", uid). + stats.OpenCount, err = x.Where(cond).And("is_closed = ?", false). + And("poster_id = ?", opts.UserID). Count(new(Issue)) - stats.ClosedCount, _ = countSession(true, isPull, repoID, nil). - And("poster_id = ?", uid). + if err != nil { + return nil, err + } + stats.ClosedCount, err = x.Where(cond).And("is_closed = ?", true). + And("poster_id = ?", opts.UserID). Count(new(Issue)) + if err != nil { + return nil, err + } } - return stats + cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed}) + stats.AssignCount, err = x.Where(cond). + And("assignee_id = ?", opts.UserID). + Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.CreateCount, err = x.Where(cond). + And("poster_id = ?", opts.UserID). + Count(new(Issue)) + if err != nil { + return nil, err + } + + stats.YourRepositoriesCount, err = x.Where(cond). + And(builder.In("issue.repo_id", opts.UserRepoIDs)). + Count(new(Issue)) + if err != nil { + return nil, err + } + + return stats, nil } // GetRepoIssueStats returns number of open and closed repository issues by given filter mode. diff --git a/models/issue_indexer.go b/models/issue_indexer.go index 3a2ad157c..26d053a5d 100644 --- a/models/issue_indexer.go +++ b/models/issue_indexer.go @@ -42,7 +42,7 @@ func populateIssueIndexer() error { } for _, repo := range repos { issues, err := Issues(&IssuesOptions{ - RepoID: repo.ID, + RepoIDs: []int64{repo.ID}, IsClosed: util.OptionalBoolNone, IsPull: util.OptionalBoolNone, }) diff --git a/models/issue_test.go b/models/issue_test.go index 47bb4feea..851fe684f 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -168,3 +168,114 @@ func TestUpdateIssueCols(t *testing.T) { assert.EqualValues(t, prevContent, updatedIssue.Content) AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) } + +func TestIssues(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + for _, test := range []struct { + Opts IssuesOptions + ExpectedIssueIDs []int64 + }{ + { + IssuesOptions{ + AssigneeID: 1, + SortType: "oldest", + }, + []int64{1, 6}, + }, + { + IssuesOptions{ + RepoIDs: []int64{1, 3}, + SortType: "oldest", + Page: 1, + PageSize: 4, + }, + []int64{1, 2, 3, 5}, + }, + { + IssuesOptions{ + Labels: "1,2", + Page: 1, + PageSize: 4, + }, + []int64{5, 2, 1}, + }, + } { + issues, err := Issues(&test.Opts) + assert.NoError(t, err) + if assert.Len(t, issues, len(test.ExpectedIssueIDs)) { + for i, issue := range issues { + assert.EqualValues(t, test.ExpectedIssueIDs[i], issue.ID) + } + } + } +} + +func TestGetUserIssueStats(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + for _, test := range []struct { + Opts UserIssueStatsOptions + ExpectedIssueStats IssueStats + }{ + { + UserIssueStatsOptions{ + UserID: 1, + RepoID: 1, + FilterMode: FilterModeAll, + }, + IssueStats{ + YourRepositoriesCount: 0, + AssignCount: 1, + CreateCount: 1, + OpenCount: 0, + ClosedCount: 0, + }, + }, + { + UserIssueStatsOptions{ + UserID: 1, + FilterMode: FilterModeAssign, + }, + IssueStats{ + YourRepositoriesCount: 0, + AssignCount: 2, + CreateCount: 2, + OpenCount: 2, + ClosedCount: 0, + }, + }, + { + UserIssueStatsOptions{ + UserID: 1, + FilterMode: FilterModeCreate, + }, + IssueStats{ + YourRepositoriesCount: 0, + AssignCount: 2, + CreateCount: 2, + OpenCount: 2, + ClosedCount: 0, + }, + }, + { + UserIssueStatsOptions{ + UserID: 2, + UserRepoIDs: []int64{1, 2}, + FilterMode: FilterModeAll, + IsClosed: true, + }, + IssueStats{ + YourRepositoriesCount: 2, + AssignCount: 0, + CreateCount: 2, + OpenCount: 1, + ClosedCount: 2, + }, + }, + } { + stats, err := GetUserIssueStats(test.Opts) + if !assert.NoError(t, err) { + continue + } + assert.Equal(t, test.ExpectedIssueStats, *stats) + } +} diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 08815ee07..c2d4819f0 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -56,7 +56,7 @@ func ListIssues(ctx *context.APIContext) { } issues, err := models.Issues(&models.IssuesOptions{ - RepoID: ctx.Repo.Repository.ID, + RepoIDs: []int64{ctx.Repo.Repository.ID}, Page: ctx.QueryInt("page"), PageSize: setting.UI.IssuePagingNum, IsClosed: isClosed, diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4e12d62f3..530586a14 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -190,8 +190,8 @@ func Issues(ctx *context.Context) { issues = []*models.Issue{} } else { issues, err = models.Issues(&models.IssuesOptions{ + RepoIDs: []int64{repo.ID}, AssigneeID: assigneeID, - RepoID: repo.ID, PosterID: posterID, MentionedID: mentionedID, MilestoneID: milestoneID, diff --git a/routers/user/home.go b/routers/user/home.go index 756da4f57..01a106813 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -9,13 +9,14 @@ import ( "fmt" "sort" - "github.com/Unknwon/paginater" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + + "github.com/Unknwon/com" + "github.com/Unknwon/paginater" ) const ( @@ -231,21 +232,30 @@ func Issues(ctx *context.Context) { return } } - if len(userRepoIDs) <= 0 { userRepoIDs = []int64{-1} } opts := &models.IssuesOptions{ - RepoID: repoID, IsClosed: util.OptionalBoolOf(isShowClosed), IsPull: util.OptionalBoolOf(isPullList), SortType: sortType, } + if repoID > 0 { + opts.RepoIDs = []int64{repoID} + } + switch filterMode { case models.FilterModeAll: - opts.RepoIDs = userRepoIDs + if repoID > 0 { + if !com.IsSliceContainsInt64(userRepoIDs, repoID) { + // force an empty result + opts.RepoIDs = []int64{-1} + } + } else { + opts.RepoIDs = userRepoIDs + } case models.FilterModeAssign: opts.AssigneeID = ctxUser.ID case models.FilterModeCreate: @@ -308,7 +318,18 @@ func Issues(ctx *context.Context) { issue.Repo = showReposMap[issue.RepoID] } - issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList) + issueStats, err := models.GetUserIssueStats(models.UserIssueStatsOptions{ + UserID: ctxUser.ID, + RepoID: repoID, + UserRepoIDs: userRepoIDs, + FilterMode: filterMode, + IsPull: isPullList, + IsClosed: isShowClosed, + }) + if err != nil { + ctx.Handle(500, "GetUserIssueStats", err) + return + } var total int if !isShowClosed { From 08cf7d90efe787ccbccae8573968a47c84cfa54e Mon Sep 17 00:00:00 2001 From: Mike Fellows Date: Wed, 27 Dec 2017 09:35:19 -0500 Subject: [PATCH 12/12] Add docs for REDIRECT_OTHER_PORT and PORT_TO_REDIRECT config options (#3262) --- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index b5517080d..424e4264f 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -87,6 +87,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LFS_START_SERVER`: Enables git-lfs support. `true` or `false`, default is `false`. - `LFS_CONTENT_PATH`: Where your lfs files put on, default is `data/lfs`. - `LFS_JWT_SECRET`: LFS authentication secret, changed this to yourself. +- `REDIRECT_OTHER_PORT`: If true and `PROTOCOL` is https, redirects http requests on another port to `ROOT_URL`, default is `false`. +- `PORT_TO_REDIRECT`: Port used when `REDIRECT_OTHER_PORT` is true, default is `80`. ## Database (`database`)