Skip to content

Commit 7e09d21

Browse files
committed
Initial version of protected branches (#776)
- Able to restrict force push and deletion - Able to restrict direct push
1 parent dab7682 commit 7e09d21

File tree

26 files changed

+524
-103
lines changed

26 files changed

+524
-103
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Gogs [![Build Status](https://travis-ci.org/gogits/gogs.svg?branch=master)](https://travis-ci.org/gogits/gogs) [![Build status](https://ci.appveyor.com/api/projects/status/b9uu5ejl933e2wlt/branch/master?svg=true)](https://ci.appveyor.com/project/Unknwon/gogs/branch/master) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/gogs/localized.svg)](https://crowdin.com/project/gogs) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
1+
Gogs [![Build Status](https://travis-ci.org/gogits/gogs.svg?branch=master)](https://travis-ci.org/gogits/gogs) [![Build status](https://ci.appveyor.com/api/projects/status/b9uu5ejl933e2wlt/branch/master?svg=true)](https://ci.appveyor.com/project/Unknwon/gogs/branch/master) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/gogs/localized.svg)](https://crowdin.com/project/gogs) [![Sourcegraph](https://sourcegraph.com/github.com/gogits/gogs/-/badge.svg)](https://sourcegraph.com/github.com/gogits/gogs?badge) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
22
=====================
33

44
![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
@@ -43,7 +43,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
4343
- Add/Remove repository collaborators
4444
- Repository/Organization webhooks (including Slack)
4545
- Repository Git hooks/deploy keys
46-
- Repository issues, pull requests and wiki
46+
- Repository issues, pull requests, wiki and protected branches
4747
- Migrate and mirror repository and its wiki
4848
- Web editor for repository files and wiki
4949
- Jupyter Notebook

README_ZH.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
2424
- 支持添加和删除仓库协作者
2525
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
2626
- 支持仓库 Git 钩子和部署密钥
27-
- 支持仓库工单(Issue)、合并请求(Pull Request)以及 Wiki
27+
- 支持仓库工单(Issue)、合并请求(Pull Request)Wiki 和保护分支
2828
- 支持迁移和镜像仓库以及它的 Wiki
2929
- 支持在线编辑仓库文件和 Wiki
3030
- 支持自定义源的 Gravatar 和 Federated Avatar

cmd/hook.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bufio"
99
"bytes"
1010
"crypto/tls"
11+
"fmt"
1112
"os"
1213
"os/exec"
1314
"path/filepath"
@@ -64,13 +65,58 @@ func runHookPreReceive(c *cli.Context) error {
6465
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
6566
return nil
6667
}
67-
setup(c, "hooks/pre-receive.log", false)
68+
setup(c, "hooks/pre-receive.log", true)
69+
70+
isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")
6871

6972
buf := bytes.NewBuffer(nil)
7073
scanner := bufio.NewScanner(os.Stdin)
7174
for scanner.Scan() {
7275
buf.Write(scanner.Bytes())
7376
buf.WriteByte('\n')
77+
78+
if isWiki {
79+
continue
80+
}
81+
82+
fields := bytes.Fields(scanner.Bytes())
83+
if len(fields) != 3 {
84+
continue
85+
}
86+
oldCommitID := string(fields[0])
87+
newCommitID := string(fields[1])
88+
branchName := strings.TrimPrefix(string(fields[2]), git.BRANCH_PREFIX)
89+
90+
// Branch protection
91+
repoID := com.StrTo(os.Getenv(http.ENV_REPO_ID)).MustInt64()
92+
protectBranch, err := models.GetProtectBranchOfRepoByName(repoID, branchName)
93+
if err != nil {
94+
if models.IsErrBranchNotExist(err) {
95+
continue
96+
}
97+
fail("Internal error", "GetProtectBranchOfRepoByName [repo_id: %d, branch: %s]: %v", repoID, branchName, err)
98+
}
99+
if !protectBranch.Protected {
100+
continue
101+
}
102+
103+
// Check if branch allows direct push
104+
if protectBranch.RequirePullRequest {
105+
fail(fmt.Sprintf("Branch '%s' is protected and commits must be merged through pull request", branchName), "")
106+
}
107+
108+
// check and deletion
109+
if newCommitID == git.EMPTY_SHA {
110+
fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "")
111+
}
112+
113+
// Check force push
114+
output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).Run()
115+
if err != nil {
116+
fail("Internal error", "Fail to detect force push: %v", err)
117+
} else if len(output) > 0 {
118+
fail(fmt.Sprintf("Branch '%s' is protected from force push", branchName), "")
119+
}
74120
}
75121

76122
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "pre-receive")

cmd/serv.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func runServ(c *cli.Context) error {
175175

176176
// Prohibit push to mirror repositories.
177177
if requestMode > models.ACCESS_MODE_READ && repo.IsMirror {
178-
fail("mirror repository is read-only", "")
178+
fail("Mirror repository is read-only", "")
179179
}
180180

181181
// Allow anonymous (user is nil) clone for public repositories.
@@ -251,7 +251,14 @@ func runServ(c *cli.Context) error {
251251
gitCmd = exec.Command(verb, repoFullName)
252252
}
253253
if requestMode == models.ACCESS_MODE_WRITE {
254-
gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(repo.RepoPath(), owner.Name, owner.Salt, repo.Name, user)...)
254+
gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(http.ComposeHookEnvsOptions{
255+
AuthUser: user,
256+
OwnerName: owner.Name,
257+
OwnerSalt: owner.Salt,
258+
RepoID: repo.ID,
259+
RepoName: repo.Name,
260+
RepoPath: repo.RepoPath(),
261+
})...)
255262
}
256263
gitCmd.Dir = setting.RepoRootPath
257264
gitCmd.Stdout = os.Stdout

cmd/web.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -435,10 +435,21 @@ func runWeb(ctx *cli.Context) error {
435435
m.Combo("").Get(repo.Settings).
436436
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
437437
m.Group("/collaboration", func() {
438-
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
438+
m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
439439
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
440440
m.Post("/delete", repo.DeleteCollaboration)
441441
})
442+
m.Group("/branches", func() {
443+
m.Get("", repo.SettingsBranches)
444+
m.Post("/default_branch", repo.UpdateDefaultBranch)
445+
m.Combo("/*").Get(repo.SettingsProtectedBranch).
446+
Post(bindIgnErr(auth.ProtectBranchForm{}), repo.SettingsProtectedBranchPost)
447+
}, func(ctx *context.Context) {
448+
if ctx.Repo.Repository.IsMirror {
449+
ctx.NotFound()
450+
return
451+
}
452+
})
442453

443454
m.Group("/hooks", func() {
444455
m.Get("", repo.Webhooks)
@@ -452,15 +463,15 @@ func runWeb(ctx *cli.Context) error {
452463
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
453464

454465
m.Group("/git", func() {
455-
m.Get("", repo.GitHooks)
456-
m.Combo("/:name").Get(repo.GitHooksEdit).
457-
Post(repo.GitHooksEditPost)
466+
m.Get("", repo.SettingsGitHooks)
467+
m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
468+
Post(repo.SettingsGitHooksEditPost)
458469
}, context.GitHookService())
459470
})
460471

461472
m.Group("/keys", func() {
462-
m.Combo("").Get(repo.DeployKeys).
463-
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
473+
m.Combo("").Get(repo.SettingsDeployKeys).
474+
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.SettingsDeployKeysPost)
464475
m.Post("/delete", repo.DeleteDeployKey)
465476
})
466477

@@ -555,13 +566,13 @@ func runWeb(ctx *cli.Context) error {
555566
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
556567
}, func(ctx *context.Context) {
557568
if !setting.Repository.Upload.Enabled {
558-
ctx.Handle(404, "", nil)
569+
ctx.NotFound()
559570
return
560571
}
561572
})
562573
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
563-
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
564-
ctx.Handle(404, "", nil)
574+
if !ctx.Repo.CanEnableEditor() {
575+
ctx.NotFound()
565576
return
566577
}
567578
})

conf/locale/locale_en-US.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,22 @@ settings.collaboration.admin = Admin
639639
settings.collaboration.write = Write
640640
settings.collaboration.read = Read
641641
settings.collaboration.undefined = Undefined
642+
settings.branches = Branches
643+
settings.default_branch = Default Branch
644+
settings.default_branch_desc = The default branch is considered the "base" branch for code commits, pull requests and online editing.
645+
settings.update = Update
646+
settings.update_default_branch_success = Default branch of this repository has been updated successfully!
647+
settings.protected_branches = Protected Branches
648+
settings.protected_branches_desc = Protect branches from force pushing, accidental deletion and whitelist code committers.
649+
settings.choose_a_branch = Choose a branch...
650+
settings.branch_protection = Branch Protection
651+
settings.branch_protection_desc = Please choose protect options for branch <b>%s</b>.
652+
settings.protect_this_branch = Protect this branch
653+
settings.protect_this_branch_desc = Disable force pushes and prevent from deletion.
654+
settings.protect_require_pull_request = Require pull request instead direct pushing
655+
settings.protect_require_pull_request_desc = Enable this option to disable direct pushing to this branch. Commits have to be pushed to another non-protected branch and merged to this branch through pull request.
656+
settings.protect_whitelist_committers = Whitelist who can push to this branch
657+
settings.protect_whitelist_committers_desc = Add people or teams to whitelist of direct push to this branch.
642658
settings.hooks = Webhooks
643659
settings.githooks = Git Hooks
644660
settings.basic_settings = Basic Settings

gogs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/gogits/gogs/modules/setting"
1717
)
1818

19-
const APP_VER = "0.9.153.0217"
19+
const APP_VER = "0.9.154.0217"
2020

2121
func init() {
2222
setting.AppVer = APP_VER

models/action.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
460460
opType = ACTION_PUSH_TAG
461461
opts.Commits = &PushCommits{}
462462
} else {
463+
// TODO: detect branch deletion
463464
// if not the first commit, set the compare URL.
464465
if opts.OldCommitID == git.EMPTY_SHA {
465466
isNewBranch = true

models/models.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ func init() {
6565
new(Watch), new(Star), new(Follow), new(Action),
6666
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
6767
new(Label), new(IssueLabel), new(Milestone),
68-
new(Mirror), new(Release), new(LoginSource), new(Webhook),
69-
new(HookTask),
68+
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask),
69+
new(ProtectBranch),
7070
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
7171
new(Notice), new(EmailAddress))
7272

models/repo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ func (repo *Repository) AllowsPulls() bool {
441441
return repo.CanEnablePulls() && repo.EnablePulls
442442
}
443443

444+
func (repo *Repository) IsBranchRequirePullRequest(name string) bool {
445+
return IsBranchOfRepoRequirePullRequest(repo.ID, name)
446+
}
447+
444448
// CanEnableEditor returns true if repository meets the requirements of web editor.
445449
func (repo *Repository) CanEnableEditor() bool {
446450
return !repo.IsMirror

0 commit comments

Comments
 (0)