Add option to close issues via commit on a non master branch (#5992)

* fixes #5957

* add tests to make sure config option is respected

* use already defined struct

* - use migration to make the flag repo wide not for the entire gitea instance
Also note that the config value can still be set so as to be able to control the value for new repositories that are to be created

- fix copy/paste error in copyright header year and rearrange import

- use repo config instead of server config value to determine if a commit should close an issue

- update testsuite

* use global config only when creating a new repository

* allow repo admin toggle feature via UI

* fix typo and improve testcase

* fix fixtures

* add DEFAULT prefix to config value

* fix test
tokarchuk/v1.17
Lanre Adelowo 6 years ago committed by Lauris BH
parent c0adb5ea8b
commit 9d8178b3ac
  1. 2
      custom/conf/app.ini.sample
  2. 1
      docs/content/doc/advanced/config-cheat-sheet.en-us.md
  3. 3
      models/action.go
  4. 34
      models/action_test.go
  5. 13
      models/fixtures/issue.yml
  6. 3
      models/fixtures/repository.yml
  7. 2
      models/issue_test.go
  8. 2
      models/migrations/migrations.go
  9. 27
      models/migrations/v79.go
  10. 30
      models/repo.go
  11. 5
      modules/auth/repo_form.go
  12. 42
      modules/setting/setting.go
  13. 1
      options/locale/locale_en-US.ini
  14. 16
      routers/repo/setting.go
  15. 4
      templates/repo/settings/options.tmpl

@ -36,6 +36,8 @@ DISABLE_HTTP_GIT = false
ACCESS_CONTROL_ALLOW_ORIGIN = ACCESS_CONTROL_ALLOW_ORIGIN =
; Force ssh:// clone url instead of scp-style uri when default SSH port is used ; Force ssh:// clone url instead of scp-style uri when default SSH port is used
USE_COMPAT_SSH_URI = false USE_COMPAT_SSH_URI = false
; Close issues as long as a commit on any branch marks it as fixed
DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
[repository.editor] [repository.editor]
; List of file extensions for which lines should be wrapped in the CodeMirror editor ; List of file extensions for which lines should be wrapped in the CodeMirror editor

@ -65,6 +65,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `ACCESS_CONTROL_ALLOW_ORIGIN`: **\<empty\>**: Value for Access-Control-Allow-Origin header, - `ACCESS_CONTROL_ALLOW_ORIGIN`: **\<empty\>**: Value for Access-Control-Allow-Origin header,
default is not to present. **WARNING**: This maybe harmful to you website if you do not default is not to present. **WARNING**: This maybe harmful to you website if you do not
give it a right value. give it a right value.
- `DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH`: **false**: Close an issue if a commit on a non default branch marks it as closed.
### Repository - Pull Request (`repository.pull-request`) ### Repository - Pull Request (`repository.pull-request`)
- `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request - `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request

@ -539,7 +539,8 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra
} }
// Change issue status only if the commit has been pushed to the default branch. // Change issue status only if the commit has been pushed to the default branch.
if repo.DefaultBranch != branchName { // and if the repo is configured to allow only that
if repo.DefaultBranch != branchName && !repo.CloseIssuesViaCommitInAnyBranch {
continue continue
} }

@ -260,6 +260,40 @@ func TestUpdateIssuesCommit(t *testing.T) {
CheckConsistencyFor(t, &Action{}) CheckConsistencyFor(t, &Action{})
} }
func TestUpdateIssuesCommit_Issue5957(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
// Test that push to a non-default branch closes an issue.
pushCommits := []*PushCommit{
{
Sha1: "abcdef1",
CommitterEmail: "user2@example.com",
CommitterName: "User Two",
AuthorEmail: "user4@example.com",
AuthorName: "User Four",
Message: "close #2",
},
}
repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
commentBean := &Comment{
Type: CommentTypeCommitRef,
CommitSHA: "abcdef1",
PosterID: user.ID,
IssueID: 7,
}
issueBean := &Issue{RepoID: repo.ID, Index: 2, ID: 7}
AssertNotExistsBean(t, commentBean)
AssertNotExistsBean(t, issueBean, "is_closed=1")
assert.NoError(t, UpdateIssuesCommit(user, repo, pushCommits, "non-existing-branch"))
AssertExistsAndLoadBean(t, commentBean)
AssertExistsAndLoadBean(t, issueBean, "is_closed=1")
CheckConsistencyFor(t, &Action{})
}
func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) { func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) {
AssertNotExistsBean(t, actionBean) AssertNotExistsBean(t, actionBean)
assert.NoError(t, CommitRepoAction(opts)) assert.NoError(t, CommitRepoAction(opts))

@ -73,3 +73,16 @@
num_comments: 0 num_comments: 0
created_unix: 946684850 created_unix: 946684850
updated_unix: 978307200 updated_unix: 978307200
-
id: 7
repo_id: 2
index: 2
poster_id: 2
name: issue7
content: content for the seventh issue
is_closed: false
is_pull: false
created_unix: 946684830
updated_unix: 978307200

@ -17,11 +17,12 @@
lower_name: repo2 lower_name: repo2
name: repo2 name: repo2
is_private: true is_private: true
num_issues: 1 num_issues: 2
num_closed_issues: 1 num_closed_issues: 1
num_pulls: 0 num_pulls: 0
num_closed_pulls: 0 num_closed_pulls: 0
num_stars: 1 num_stars: 1
close_issues_via_commit_in_any_branch: true
- -
id: 3 id: 3

@ -275,7 +275,7 @@ func TestGetUserIssueStats(t *testing.T) {
YourRepositoriesCount: 2, YourRepositoriesCount: 2,
AssignCount: 0, AssignCount: 0,
CreateCount: 2, CreateCount: 2,
OpenCount: 1, OpenCount: 2,
ClosedCount: 2, ClosedCount: 2,
}, },
}, },

@ -210,6 +210,8 @@ var migrations = []Migration{
NewMigration("add theme to users", addUserDefaultTheme), NewMigration("add theme to users", addUserDefaultTheme),
// v78 -> v79 // v78 -> v79
NewMigration("rename repo is_bare to repo is_empty", renameRepoIsBareToIsEmpty), NewMigration("rename repo is_bare to repo is_empty", renameRepoIsBareToIsEmpty),
// v79 -> v80
NewMigration("add can close issues via commit in any branch", addCanCloseIssuesViaCommitInAnyBranch),
} }
// Migrate database to current version // Migrate database to current version

@ -0,0 +1,27 @@
// 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 migrations
import (
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
)
func addCanCloseIssuesViaCommitInAnyBranch(x *xorm.Engine) error {
type Repository struct {
ID int64 `xorm:"pk autoincr"`
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
}
if err := x.Sync2(new(Repository)); err != nil {
return err
}
_, err := x.Exec("UPDATE repository SET close_issues_via_commit_in_any_branch = ?",
setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch)
return err
}

@ -197,13 +197,14 @@ type Repository struct {
ExternalMetas map[string]string `xorm:"-"` ExternalMetas map[string]string `xorm:"-"`
Units []*RepoUnit `xorm:"-"` Units []*RepoUnit `xorm:"-"`
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
ForkID int64 `xorm:"INDEX"` ForkID int64 `xorm:"INDEX"`
BaseRepo *Repository `xorm:"-"` BaseRepo *Repository `xorm:"-"`
Size int64 `xorm:"NOT NULL DEFAULT 0"` Size int64 `xorm:"NOT NULL DEFAULT 0"`
IndexerStatus *RepoIndexerStatus `xorm:"-"` IndexerStatus *RepoIndexerStatus `xorm:"-"`
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
Topics []string `xorm:"TEXT JSON"` CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"` CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
@ -1373,13 +1374,14 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
} }
repo := &Repository{ repo := &Repository{
OwnerID: u.ID, OwnerID: u.ID,
Owner: u, Owner: u,
Name: opts.Name, Name: opts.Name,
LowerName: strings.ToLower(opts.Name), LowerName: strings.ToLower(opts.Name),
Description: opts.Description, Description: opts.Description,
IsPrivate: opts.IsPrivate, IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror, IsFsckEnabled: !opts.IsMirror,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
} }
sess := x.NewSession() sess := x.NewSession()

@ -14,7 +14,7 @@ import (
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-macaron/binding" "github.com/go-macaron/binding"
"gopkg.in/macaron.v1" macaron "gopkg.in/macaron.v1"
) )
// _______________________________________ _________.______________________ _______________.___. // _______________________________________ _________.______________________ _______________.___.
@ -120,7 +120,8 @@ type RepoSettingForm struct {
IsArchived bool IsArchived bool
// Admin settings // Admin settings
EnableHealthCheck bool EnableHealthCheck bool
EnableCloseIssuesViaCommitInAnyBranch bool
} }
// Validate validates the fields // Validate validates the fields

@ -190,16 +190,17 @@ var (
// Repository settings // Repository settings
Repository = struct { Repository = struct {
AnsiCharset string AnsiCharset string
ForcePrivate bool ForcePrivate bool
DefaultPrivate string DefaultPrivate string
MaxCreationLimit int MaxCreationLimit int
MirrorQueueLength int MirrorQueueLength int
PullRequestQueueLength int PullRequestQueueLength int
PreferredLicenses []string PreferredLicenses []string
DisableHTTPGit bool DisableHTTPGit bool
AccessControlAllowOrigin string AccessControlAllowOrigin string
UseCompatSSHURI bool UseCompatSSHURI bool
DefaultCloseIssuesViaCommitsInAnyBranch bool
// Repository editor settings // Repository editor settings
Editor struct { Editor struct {
@ -227,16 +228,17 @@ var (
WorkInProgressPrefixes []string WorkInProgressPrefixes []string
} `ini:"repository.pull-request"` } `ini:"repository.pull-request"`
}{ }{
AnsiCharset: "", AnsiCharset: "",
ForcePrivate: false, ForcePrivate: false,
DefaultPrivate: RepoCreatingLastUserVisibility, DefaultPrivate: RepoCreatingLastUserVisibility,
MaxCreationLimit: -1, MaxCreationLimit: -1,
MirrorQueueLength: 1000, MirrorQueueLength: 1000,
PullRequestQueueLength: 1000, PullRequestQueueLength: 1000,
PreferredLicenses: []string{"Apache License 2.0,MIT License"}, PreferredLicenses: []string{"Apache License 2.0,MIT License"},
DisableHTTPGit: false, DisableHTTPGit: false,
AccessControlAllowOrigin: "", AccessControlAllowOrigin: "",
UseCompatSSHURI: false, UseCompatSSHURI: false,
DefaultCloseIssuesViaCommitsInAnyBranch: false,
// Repository editor settings // Repository editor settings
Editor: struct { Editor: struct {

@ -1035,6 +1035,7 @@ settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge c
settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits
settings.admin_settings = Administrator Settings settings.admin_settings = Administrator Settings
settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) settings.admin_enable_health_check = Enable Repository Health Checks (git fsck)
settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch
settings.danger_zone = Danger Zone settings.danger_zone = Danger Zone
settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name.
settings.convert = Convert to Regular Repository settings.convert = Convert to Regular Repository

@ -250,13 +250,19 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
if repo.IsFsckEnabled != form.EnableHealthCheck { if repo.IsFsckEnabled != form.EnableHealthCheck {
repo.IsFsckEnabled = form.EnableHealthCheck repo.IsFsckEnabled = form.EnableHealthCheck
if err := models.UpdateRepository(repo, false); err != nil {
ctx.ServerError("UpdateRepository", err)
return
}
log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
} }
if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch {
repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch
}
if err := models.UpdateRepository(repo, false); err != nil {
ctx.ServerError("UpdateRepository", err)
return
}
log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings") ctx.Redirect(ctx.Repo.RepoLink + "/settings")

@ -263,6 +263,10 @@
<label>{{.i18n.Tr "repo.settings.admin_enable_health_check"}}</label> <label>{{.i18n.Tr "repo.settings.admin_enable_health_check"}}</label>
</div> </div>
</div> </div>
<div class="ui checkbox">
<input name="enable_close_issues_via_commit_in_any_branch" type="checkbox" {{ if .Repository.CloseIssuesViaCommitInAnyBranch }}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.admin_enable_close_issues_via_commit_in_any_branch"}}</label>
</div>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="field"> <div class="field">

Loading…
Cancel
Save