diff --git a/.drone.yml b/.drone.yml index 1cddc80d40794..ffcb1d3fd7604 100644 --- a/.drone.yml +++ b/.drone.yml @@ -396,6 +396,7 @@ steps: environment: GOPROXY: off TAGS: bindata sqlite sqlite_unlock_notify + RACE_ENABLED: "0" - name: gpg-sign pull: always @@ -498,6 +499,7 @@ steps: environment: GOPROXY: off TAGS: bindata sqlite sqlite_unlock_notify + RACE_ENABLED: "0" - name: gpg-sign pull: always diff --git a/Makefile b/Makefile index 84fb7e1c96add..04befec0ef8d9 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,11 @@ EXTRA_GOFLAGS ?= MAKE_VERSION := $(shell make -v | head -n 1) +ifneq ($(RACE_ENABLED),"0") +GOFLAGS += -race +GOTESTFLAGS += -race +endif + ifneq ($(DRONE_TAG),) VERSION ?= $(subst v,,$(DRONE_TAG)) GITEA_VERSION ?= $(VERSION) @@ -166,7 +171,7 @@ fmt-check: .PHONY: test test: - GO111MODULE=on $(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' $(PACKAGES) + GO111MODULE=on $(GO) test $(GOTESTFLAGS) -mod=vendor -tags='sqlite sqlite_unlock_notify' $(PACKAGES) .PHONY: coverage coverage: @@ -177,7 +182,7 @@ coverage: .PHONY: unit-test-coverage unit-test-coverage: - $(GO) test -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 + $(GO) test $(GOTESTFLAGS) -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 .PHONY: vendor vendor: @@ -281,21 +286,21 @@ integration-test-coverage: integrations.cover.test generate-ini GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out integrations.test: $(SOURCES) - GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.test + GO111MODULE=on $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.test integrations.sqlite.test: $(SOURCES) - GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags 'sqlite sqlite_unlock_notify' + GO111MODULE=on $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -o integrations.sqlite.test -tags 'sqlite sqlite_unlock_notify' integrations.cover.test: $(SOURCES) - GO111MODULE=on $(GO) test -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(PACKAGES) | tr ' ' ',') -o integrations.cover.test + GO111MODULE=on $(GO) test $(GOTESTFLAGS) -mod=vendor -c code.gitea.io/gitea/integrations -coverpkg $(shell echo $(PACKAGES) | tr ' ' ',') -o integrations.cover.test .PHONY: migrations.test migrations.test: $(SOURCES) - $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.test + $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.test .PHONY: migrations.sqlite.test migrations.sqlite.test: $(SOURCES) - $(GO) test -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags 'sqlite sqlite_unlock_notify' + $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags 'sqlite sqlite_unlock_notify' .PHONY: check check: test diff --git a/models/issue_milestone.go b/models/issue_milestone.go index b1505c12cb18e..f8f414e7166e6 100644 --- a/models/issue_milestone.go +++ b/models/issue_milestone.go @@ -7,7 +7,6 @@ package models import ( "fmt" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -394,46 +393,6 @@ func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err if err = sess.Commit(); err != nil { return fmt.Errorf("Commit: %v", err) } - - var hookAction api.HookIssueAction - if issue.MilestoneID > 0 { - hookAction = api.HookIssueMilestoned - } else { - hookAction = api.HookIssueDemilestoned - } - - if err = issue.LoadAttributes(); err != nil { - return err - } - - mode, _ := AccessLevel(doer, issue.Repo) - if issue.IsPull { - err = issue.PullRequest.LoadIssue() - if err != nil { - log.Error("LoadIssue: %v", err) - return - } - err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ - Action: hookAction, - Index: issue.Index, - PullRequest: issue.PullRequest.APIFormat(), - Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), - }) - } else { - err = PrepareWebhooks(issue.Repo, HookEventIssues, &api.IssuePayload{ - Action: hookAction, - Index: issue.Index, - Issue: issue.APIFormat(), - Repository: issue.Repo.APIFormat(mode), - Sender: doer.APIFormat(), - }) - } - if err != nil { - log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) - } else { - go HookQueue.Add(issue.RepoID) - } return nil } diff --git a/models/repo.go b/models/repo.go index 3e593ce4ed4f2..da0552e665cfa 100644 --- a/models/repo.go +++ b/models/repo.go @@ -161,7 +161,7 @@ type Repository struct { *Mirror `xorm:"-"` ExternalMetas map[string]string `xorm:"-"` - Units []*RepoUnit `xorm:"-"` + Units *RepoUnitList `xorm:"-"` IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` ForkID int64 `xorm:"INDEX"` @@ -340,8 +340,12 @@ func (repo *Repository) getUnits(e Engine) (err error) { return nil } - repo.Units, err = getUnitsByRepoID(e, repo.ID) - return err + units, err := getUnitsByRepoID(e, repo.ID) + if err != nil { + return err + } + repo.Units = NewRepoUnitList(units) + return nil } // CheckUnitUser check whether user could visit the unit of this repository @@ -370,12 +374,15 @@ func (repo *Repository) UnitEnabled(tp UnitType) bool { if err := repo.getUnits(x); err != nil { log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) } - for _, unit := range repo.Units { + result := false + repo.Units.Range(func(i int, unit *RepoUnit) bool { if unit.Type == tp { - return true + result = true + return false } - } - return false + return true + }) + return result } // ErrUnitTypeNotExist represents a "UnitTypeNotExist" kind of error. @@ -436,10 +443,17 @@ func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { if err := repo.getUnits(e); err != nil { return nil, err } - for _, unit := range repo.Units { + + var result *RepoUnit + repo.Units.Range(func(i int, unit *RepoUnit) bool { if unit.Type == tp { - return unit, nil + result = unit + return false } + return true + }) + if result != nil { + return result, nil } return nil, ErrUnitTypeNotExist{tp} } diff --git a/models/repo_permission.go b/models/repo_permission.go index 916678d16859e..8c9d18d28acf1 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -13,7 +13,7 @@ import ( // Permission contains all the permissions related variables to a repository for a user type Permission struct { AccessMode AccessMode - Units []*RepoUnit + Units *RepoUnitList UnitsMode map[UnitType]AccessMode } @@ -38,12 +38,15 @@ func (p *Permission) HasAccess() bool { // UnitAccessMode returns current user accessmode to the specify unit of the repository func (p *Permission) UnitAccessMode(unitType UnitType) AccessMode { if p.UnitsMode == nil { - for _, u := range p.Units { + result := AccessModeNone + p.Units.Range(func(i int, u *RepoUnit) bool { if u.Type == unitType { - return p.AccessMode + result = p.AccessMode + return false } - } - return AccessModeNone + return true + }) + return result } return p.UnitsMode[unitType] } @@ -103,11 +106,11 @@ func (p *Permission) ColorFormat(s fmt.State) { format := "AccessMode: %-v, %d Units, %d UnitsMode(s): [ " args := []interface{}{ p.AccessMode, - log.NewColoredValueBytes(len(p.Units), &noColor), + log.NewColoredValueBytes(p.Units.Len(), &noColor), log.NewColoredValueBytes(len(p.UnitsMode), &noColor), } if s.Flag('+') { - for i, unit := range p.Units { + p.Units.Range(func(i int, unit *RepoUnit) bool { config := "" if unit.Config != nil { configBytes, err := unit.Config.ToDB() @@ -123,7 +126,8 @@ func (p *Permission) ColorFormat(s fmt.State) { log.NewColoredIDValue(unit.RepoID), unit.Type, config) - } + return true + }) for key, value := range p.UnitsMode { format += "\nUnitMode[%-v]: %-v" args = append(args, @@ -218,9 +222,10 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss // Collaborators on organization if isCollaborator { - for _, u := range repo.Units { + repo.Units.Range(func(i int, u *RepoUnit) bool { perm.UnitsMode[u.Type] = perm.AccessMode - } + return true + }) } // get units mode from teams @@ -238,7 +243,7 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss } } - for _, u := range repo.Units { + repo.Units.Range(func(i int, u *RepoUnit) bool { var found bool for _, team := range teams { if team.unitEnabled(e, u.Type) { @@ -256,16 +261,18 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss perm.UnitsMode[u.Type] = AccessModeRead } } - } + return true + }) // remove no permission units - perm.Units = make([]*RepoUnit, 0, len(repo.Units)) + perm.Units = NewRepoUnitList(make([]*RepoUnit, 0, repo.Units.Len())) for t := range perm.UnitsMode { - for _, u := range repo.Units { + repo.Units.Range(func(_ int, u *RepoUnit) bool { if u.Type == t { - perm.Units = append(perm.Units, u) + perm.Units = AppendRepoUnitList(perm.Units, u) } - } + return true + }) } return diff --git a/models/repo_permission_test.go b/models/repo_permission_test.go index 0f350e62aad98..854607d3077b4 100644 --- a/models/repo_permission_test.go +++ b/models/repo_permission_test.go @@ -21,46 +21,51 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // change to collaborator assert.NoError(t, repo.AddCollaborator(user)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // collaborator collaborator := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) perm, err = GetUserRepoPermission(repo, collaborator) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // owner owner := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // admin admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) } func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { @@ -74,45 +79,50 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.False(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // change to collaborator to default write access assert.NoError(t, repo.AddCollaborator(user)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // owner owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // admin admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) } func TestRepoPermissionPublicOrgRepo(t *testing.T) { @@ -126,44 +136,49 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // change to collaborator to default write access assert.NoError(t, repo.AddCollaborator(user)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // org member team owner owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // org member team tester member := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) perm, err = GetUserRepoPermission(repo, member) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) - } + return true + }) assert.True(t, perm.CanWrite(UnitTypeIssues)) assert.False(t, perm.CanWrite(UnitTypeCode)) @@ -171,10 +186,11 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) { admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) } func TestRepoPermissionPrivateOrgRepo(t *testing.T) { @@ -188,36 +204,40 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) perm, err := GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.False(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // change to collaborator to default write access assert.NoError(t, repo.AddCollaborator(user)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) perm, err = GetUserRepoPermission(repo, user) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.False(t, perm.CanWrite(unit.Type)) - } + return true + }) // org member team owner owner := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // update team information and then check permission team := AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team) @@ -225,10 +245,11 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { assert.NoError(t, err) perm, err = GetUserRepoPermission(repo, owner) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) // org member team tester tester := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) @@ -250,8 +271,9 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) { admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) perm, err = GetUserRepoPermission(repo, admin) assert.NoError(t, err) - for _, unit := range repo.Units { + repo.Units.Range(func(i int, unit *RepoUnit) bool { assert.True(t, perm.CanRead(unit.Type)) assert.True(t, perm.CanWrite(unit.Type)) - } + return true + }) } diff --git a/models/repo_test.go b/models/repo_test.go index bf10de8d99c59..45444fe0c8293 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -36,7 +36,7 @@ func TestRepo(t *testing.T) { } testSuccess := func(expectedStyle string) { - repo.Units = []*RepoUnit{&externalTracker} + repo.Units = NewRepoUnitList([]*RepoUnit{&externalTracker}) repo.ExternalMetas = nil metas := repo.ComposeMetas() assert.Equal(t, expectedStyle, metas["style"]) diff --git a/models/repo_unit_list.go b/models/repo_unit_list.go new file mode 100644 index 0000000000000..838e2882fd542 --- /dev/null +++ b/models/repo_unit_list.go @@ -0,0 +1,63 @@ +package models + +import "sync" + +// RepoUnitList is a thread-safe container for a list of RepoUnit +type RepoUnitList struct { + sync.RWMutex + list []*RepoUnit +} + +// NewRepoUnitList creates a RepoUnitList from a slice of *UnitRepo. +func NewRepoUnitList(us []*RepoUnit) *RepoUnitList { + return &RepoUnitList{ + list: us, + } +} + +// AppendRepoUnitList appends one or more elements to the list +func AppendRepoUnitList(l *RepoUnitList, us ...*RepoUnit) *RepoUnitList { + if l != nil { + l.Lock() + defer l.Unlock() + l.list = append(l.list, us...) + return l + } + return NewRepoUnitList(us) +} + +// Load reads i-th element from the list +func (l *RepoUnitList) Load(i int) *RepoUnit { + l.RLock() + defer l.RUnlock() + return l.list[i] +} + +// Len returns the length of the list +func (l *RepoUnitList) Len() int { + l.RLock() + defer l.RUnlock() + return len(l.list) +} + +// Range iterates through the elements of the list like sync.Map.Range. +func (l *RepoUnitList) Range(f func(i int, u *RepoUnit) bool) { + if l == nil { + return + } + + l.RLock() + for i, v := range l.list { + // Despite the cost of calling lock/unlock in a loop, + // we have to release the lock during the execution of the callback. + // Otherwise, if f tries to acquire the lock, a deadlock will happen. + l.RUnlock() + ok := f(i, v) + l.RLock() + + if !ok { + break + } + } + l.RUnlock() +} diff --git a/models/webhook_msteams.go b/models/webhook_msteams.go index f42defbd1af70..bdbcdbc9d361f 100644 --- a/models/webhook_msteams.go +++ b/models/webhook_msteams.go @@ -236,6 +236,7 @@ func getMSTeamsPushPayload(p *api.PushPayload) (*MSTeamsPayload, error) { ActivityTitle: p.Sender.FullName, ActivitySubtitle: p.Sender.UserName, ActivityImage: p.Sender.AvatarURL, + Text: text, Facts: []MSTeamsFact{ { Name: "Repository:", diff --git a/public/css/index.css b/public/css/index.css index 113fdbf6318a8..8f24e7b3e465c 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -526,6 +526,7 @@ footer .ui.left,footer .ui.right{line-height:40px} .repository.options #interval{width:100px!important;min-width:100px} .repository.options .danger .item{padding:20px 15px} .repository.options .danger .ui.divider{margin:0} +.repository .comment textarea{max-height:none!important} .repository.new.issue .comment.form .comment .avatar{width:3em} .repository.new.issue .comment.form .content{margin-left:4em} .repository.new.issue .comment.form .content:after,.repository.new.issue .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} diff --git a/public/less/_repository.less b/public/less/_repository.less index 74ca683c4e53b..4823d1000aeed 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -536,6 +536,10 @@ @comment-avatar-width: 3em; + .comment textarea { + max-height: none !important; + } + &.new.issue { .comment.form { .comment { diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 402da82f07817..6f502a5abd303 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -19,6 +19,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + milestone_service "code.gitea.io/gitea/services/milestone" ) // ListIssues list the issues of a repository @@ -345,7 +346,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { issue.MilestoneID != *form.Milestone { oldMilestoneID := issue.MilestoneID issue.MilestoneID = *form.Milestone - if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { + if err = milestone_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { ctx.Error(500, "ChangeMilestoneAssign", err) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 5ac542ec207b2..7be34c656e8bf 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/pull" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + milestone_service "code.gitea.io/gitea/services/milestone" ) // ListPullRequests returns a list of all PRs @@ -402,7 +403,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { issue.MilestoneID != form.Milestone { oldMilestoneID := issue.MilestoneID issue.MilestoneID = form.Milestone - if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { + if err = milestone_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { ctx.Error(500, "ChangeMilestoneAssign", err) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index dc09a650fed7f..4ae791221cf04 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" + milestone_service "code.gitea.io/gitea/services/milestone" "github.com/unknwon/com" ) @@ -1086,7 +1087,7 @@ func UpdateIssueMilestone(ctx *context.Context) { continue } issue.MilestoneID = milestoneID - if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { + if err := milestone_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil { ctx.ServerError("ChangeMilestoneAssign", err) return } diff --git a/routers/repo/view.go b/routers/repo/view.go index 00790a4ef35be..ff1d2124a780f 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -356,19 +356,20 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st // Home render repository home page func Home(ctx *context.Context) { - if len(ctx.Repo.Units) > 0 { + if ctx.Repo.Units.Len() > 0 { var firstUnit *models.Unit - for _, repoUnit := range ctx.Repo.Units { + ctx.Repo.Units.Range(func(i int, repoUnit *models.RepoUnit) bool { if repoUnit.Type == models.UnitTypeCode { renderCode(ctx) - return + return false } unit, ok := models.Units[repoUnit.Type] if ok && (firstUnit == nil || !firstUnit.IsLessThan(unit)) { firstUnit = &unit } - } + return true + }) if firstUnit != nil { ctx.Redirect(fmt.Sprintf("%s/%s%s", setting.AppSubURL, ctx.Repo.Repository.FullName(), firstUnit.URI)) diff --git a/services/milestone/milestone.go b/services/milestone/milestone.go new file mode 100644 index 0000000000000..68052e5a6ceff --- /dev/null +++ b/services/milestone/milestone.go @@ -0,0 +1,59 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + api "code.gitea.io/gitea/modules/structs" +) + +// ChangeMilestoneAssign changes assignment of milestone for issue. +func ChangeMilestoneAssign(issue *models.Issue, doer *models.User, oldMilestoneID int64) (err error) { + if err = models.ChangeMilestoneAssign(issue, doer, oldMilestoneID); err != nil { + return + } + + var hookAction api.HookIssueAction + if issue.MilestoneID > 0 { + hookAction = api.HookIssueMilestoned + } else { + hookAction = api.HookIssueDemilestoned + } + + if err = issue.LoadAttributes(); err != nil { + return err + } + + mode, _ := models.AccessLevel(doer, issue.Repo) + if issue.IsPull { + err = issue.PullRequest.LoadIssue() + if err != nil { + log.Error("LoadIssue: %v", err) + return + } + err = models.PrepareWebhooks(issue.Repo, models.HookEventPullRequest, &api.PullRequestPayload{ + Action: hookAction, + Index: issue.Index, + PullRequest: issue.PullRequest.APIFormat(), + Repository: issue.Repo.APIFormat(mode), + Sender: doer.APIFormat(), + }) + } else { + err = models.PrepareWebhooks(issue.Repo, models.HookEventIssues, &api.IssuePayload{ + Action: hookAction, + Index: issue.Index, + Issue: issue.APIFormat(), + Repository: issue.Repo.APIFormat(mode), + Sender: doer.APIFormat(), + }) + } + if err != nil { + log.Error("PrepareWebhooks [is_pull: %v]: %v", issue.IsPull, err) + } else { + go models.HookQueue.Add(issue.RepoID) + } + return nil +}