Skip to content

Commit fd941db

Browse files
denjilunny
authored andcommitted
Protected branches system (#339)
* Protected branches system * Moved default branch to branches section (`:org/:reponame/settings/branches`). * Initial support Protected Branch. - Admin does not restrict - Owner not to limit - To write permission restrictions * reformat tmpl * finished the UI and add/delete protected branch response * remove unused comment * indent all the template files and remove ru translations since we use crowdin * fix the push bug
1 parent fe5ff8e commit fd941db

File tree

17 files changed

+606
-49
lines changed

17 files changed

+606
-49
lines changed

Diff for: cmd/serve.go

+4
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ func runServ(c *cli.Context) error {
342342
} else {
343343
gitcmd = exec.Command(verb, repoPath)
344344
}
345+
346+
os.Setenv(models.ProtectedBranchAccessMode, requestedMode.String())
347+
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))
348+
345349
gitcmd.Dir = setting.RepoRootPath
346350
gitcmd.Stdout = os.Stdout
347351
gitcmd.Stdin = os.Stdin

Diff for: cmd/update.go

+20
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ package cmd
66

77
import (
88
"os"
9+
"strconv"
10+
"strings"
911

1012
"github.com/urfave/cli"
1113

14+
"code.gitea.io/git"
1215
"code.gitea.io/gitea/models"
1316
"code.gitea.io/gitea/modules/log"
1417
"code.gitea.io/gitea/modules/setting"
@@ -48,6 +51,23 @@ func runUpdate(c *cli.Context) error {
4851
log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use")
4952
}
5053

54+
// protected branch check
55+
branchName := strings.TrimPrefix(args[0], git.BranchPrefix)
56+
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
57+
log.GitLogger.Trace("pushing to %d %v", repoID, branchName)
58+
accessMode := models.ParseAccessMode(os.Getenv(models.ProtectedBranchAccessMode))
59+
// skip admin or owner AccessMode
60+
if accessMode == models.AccessModeWrite {
61+
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
62+
if err != nil {
63+
log.GitLogger.Fatal(2, "retrieve protected branches information failed")
64+
}
65+
66+
if protectBranch != nil {
67+
log.GitLogger.Fatal(2, "protected branches can not be pushed to")
68+
}
69+
}
70+
5171
task := models.UpdateTask{
5272
UUID: os.Getenv("GITEA_UUID"),
5373
RefName: args[0],

Diff for: cmd/web.go

+5
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,11 @@ func runWeb(ctx *cli.Context) error {
421421
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
422422
m.Post("/delete", repo.DeleteCollaboration)
423423
})
424+
m.Group("/branches", func() {
425+
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
426+
m.Post("/can_push", repo.ChangeProtectedBranch)
427+
m.Post("/delete", repo.DeleteProtectedBranch)
428+
})
424429

425430
m.Group("/hooks", func() {
426431
m.Get("", repo.Webhooks)

Diff for: models/branches.go

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2016 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
"time"
11+
)
12+
13+
// Protected metadata
14+
const (
15+
// Protected User ID
16+
ProtectedBranchUserID = "GITEA_USER_ID"
17+
// Protected Repo ID
18+
ProtectedBranchRepoID = "GITEA_REPO_ID"
19+
// Protected access mode
20+
ProtectedBranchAccessMode = "GITEA_ACCESS_MODE"
21+
)
22+
23+
// ProtectedBranch struct
24+
type ProtectedBranch struct {
25+
ID int64 `xorm:"pk autoincr"`
26+
RepoID int64 `xorm:"UNIQUE(s)"`
27+
BranchName string `xorm:"UNIQUE(s)"`
28+
CanPush bool
29+
Created time.Time `xorm:"-"`
30+
CreatedUnix int64
31+
Updated time.Time `xorm:"-"`
32+
UpdatedUnix int64
33+
}
34+
35+
// BeforeInsert before protected branch insert create and update time
36+
func (protectBranch *ProtectedBranch) BeforeInsert() {
37+
protectBranch.CreatedUnix = time.Now().Unix()
38+
protectBranch.UpdatedUnix = protectBranch.CreatedUnix
39+
}
40+
41+
// BeforeUpdate before protected branch update time
42+
func (protectBranch *ProtectedBranch) BeforeUpdate() {
43+
protectBranch.UpdatedUnix = time.Now().Unix()
44+
}
45+
46+
// GetProtectedBranchByRepoID getting protected branch by repo ID
47+
func GetProtectedBranchByRepoID(RepoID int64) ([]*ProtectedBranch, error) {
48+
protectedBranches := make([]*ProtectedBranch, 0)
49+
return protectedBranches, x.Where("repo_id = ?", RepoID).Desc("updated_unix").Find(&protectedBranches)
50+
}
51+
52+
// GetProtectedBranchBy getting protected branch by ID/Name
53+
func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, error) {
54+
rel := &ProtectedBranch{RepoID: repoID, BranchName: strings.ToLower(BranchName)}
55+
has, err := x.Get(rel)
56+
if err != nil {
57+
return nil, err
58+
}
59+
if !has {
60+
return nil, nil
61+
}
62+
return rel, nil
63+
}
64+
65+
// GetProtectedBranches get all protected btanches
66+
func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
67+
protectedBranches := make([]*ProtectedBranch, 0)
68+
return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})
69+
}
70+
71+
// AddProtectedBranch add protection to branch
72+
func (repo *Repository) AddProtectedBranch(branchName string, canPush bool) error {
73+
protectedBranch := &ProtectedBranch{
74+
RepoID: repo.ID,
75+
BranchName: branchName,
76+
}
77+
78+
has, err := x.Get(protectedBranch)
79+
if err != nil {
80+
return err
81+
} else if has {
82+
return nil
83+
}
84+
85+
sess := x.NewSession()
86+
defer sessionRelease(sess)
87+
if err = sess.Begin(); err != nil {
88+
return err
89+
}
90+
protectedBranch.CanPush = canPush
91+
if _, err = sess.InsertOne(protectedBranch); err != nil {
92+
return err
93+
}
94+
95+
return sess.Commit()
96+
}
97+
98+
// ChangeProtectedBranch access mode sets new access mode for the ProtectedBranch.
99+
func (repo *Repository) ChangeProtectedBranch(id int64, canPush bool) error {
100+
ProtectedBranch := &ProtectedBranch{
101+
RepoID: repo.ID,
102+
ID: id,
103+
}
104+
has, err := x.Get(ProtectedBranch)
105+
if err != nil {
106+
return fmt.Errorf("get ProtectedBranch: %v", err)
107+
} else if !has {
108+
return nil
109+
}
110+
111+
if ProtectedBranch.CanPush == canPush {
112+
return nil
113+
}
114+
ProtectedBranch.CanPush = canPush
115+
116+
sess := x.NewSession()
117+
defer sessionRelease(sess)
118+
if err = sess.Begin(); err != nil {
119+
return err
120+
}
121+
122+
if _, err = sess.Id(ProtectedBranch.ID).AllCols().Update(ProtectedBranch); err != nil {
123+
return fmt.Errorf("update ProtectedBranch: %v", err)
124+
}
125+
126+
return sess.Commit()
127+
}
128+
129+
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
130+
func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
131+
protectedBranch := &ProtectedBranch{
132+
RepoID: repo.ID,
133+
ID: id,
134+
}
135+
136+
sess := x.NewSession()
137+
defer sessionRelease(sess)
138+
if err = sess.Begin(); err != nil {
139+
return err
140+
}
141+
142+
if affected, err := sess.Delete(protectedBranch); err != nil {
143+
return err
144+
} else if affected != 1 {
145+
return fmt.Errorf("delete protected branch ID(%v) failed", id)
146+
}
147+
148+
return sess.Commit()
149+
}
150+
151+
// newProtectedBranch insert one queue
152+
func newProtectedBranch(protectedBranch *ProtectedBranch) error {
153+
_, err := x.InsertOne(protectedBranch)
154+
return err
155+
}
156+
157+
// UpdateProtectedBranch update queue
158+
func UpdateProtectedBranch(protectedBranch *ProtectedBranch) error {
159+
_, err := x.Update(protectedBranch)
160+
return err
161+
}

Diff for: models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ var migrations = []Migration{
8282
NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn),
8383
// V16 -> v17
8484
NewMigration("create repo unit table and add units for all repos", addUnitsToTables),
85+
// v17 -> v18
86+
NewMigration("set protect branches updated with created", setProtectedBranchUpdatedWithCreated),
8587
}
8688

8789
// Migrate database to current version

Diff for: models/migrations/v17.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2016 Gitea. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
"time"
10+
11+
"github.com/go-xorm/xorm"
12+
)
13+
14+
func setProtectedBranchUpdatedWithCreated(x *xorm.Engine) (err error) {
15+
type ProtectedBranch struct {
16+
ID int64 `xorm:"pk autoincr"`
17+
RepoID int64 `xorm:"UNIQUE(s)"`
18+
BranchName string `xorm:"UNIQUE(s)"`
19+
CanPush bool
20+
Created time.Time `xorm:"-"`
21+
CreatedUnix int64
22+
Updated time.Time `xorm:"-"`
23+
UpdatedUnix int64
24+
}
25+
if err = x.Sync2(new(ProtectedBranch)); err != nil {
26+
return fmt.Errorf("Sync2: %v", err)
27+
}
28+
return nil
29+
}

Diff for: models/repo.go

+6
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,12 @@ func (repo *Repository) HasAccess(u *User) bool {
524524
return has
525525
}
526526

527+
// UpdateDefaultBranch updates the default branch
528+
func (repo *Repository) UpdateDefaultBranch() error {
529+
_, err := x.ID(repo.ID).Cols("default_branch").Update(repo)
530+
return err
531+
}
532+
527533
// IsOwnedBy returns true when user owns this repository
528534
func (repo *Repository) IsOwnedBy(userID int64) bool {
529535
return repo.OwnerID == userID

Diff for: modules/auth/repo_form.go

-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ type RepoSettingForm struct {
8888
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
8989
Description string `binding:"MaxSize(255)"`
9090
Website string `binding:"Url;MaxSize(255)"`
91-
Branch string
9291
Interval int
9392
MirrorAddress string
9493
Private bool

Diff for: options/locale/TRANSLATORS

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,16 @@ Muhammad Fawwaz Orabi <mfawwaz93 AT gmail DOT com>
4949
Nakao Takamasa <at.mattenn AT gmail DOT com>
5050
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
5151
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
52+
Pablo Saavedra <psaavedra AT igalia DOT com>
5253
Richard Bukovansky <richard DOT bukovansky @ gmail DOT com>
5354
Robert Nuske <robert DOT nuske AT web DOT de>
5455
Robin Hübner <profan AT prfn DOT se>
5556
SeongJae Park <sj38 DOT park AT gmail DOT com>
57+
Thiago Avelino <thiago AT avelino DOT xxx>
5658
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
5759
Tilmann Bach <tilmann AT outlook DOT com>
5860
Toni Villena Jiménez <tonivj5 AT gmail DOT com>
5961
Vladimir Jigulin mogaika AT yandex DOT ru
6062
Vladimir Vissoultchev <wqweto AT gmail DOT com>
6163
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
6264
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
63-
Pablo Saavedra <psaavedra AT igalia DOT com>
64-
Thiago Avelino <thiago AT avelino DOT xxx>

Diff for: options/locale/locale_en-US.ini

+12
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,18 @@ settings.add_key_success = New deploy key '%s' has been added successfully!
814814
settings.deploy_key_deletion = Delete Deploy Key
815815
settings.deploy_key_deletion_desc = Deleting this deploy key will remove all related accesses for this repository. Do you want to continue?
816816
settings.deploy_key_deletion_success = Deploy key has been deleted successfully!
817+
settings.branches=Branches
818+
settings.protected_branch=Branch Protection
819+
settings.protected_branch_can_push=Allow push?
820+
settings.protected_branch_can_push_yes=You can push
821+
settings.protected_branch_can_push_no=You can not push
822+
settings.add_protected_branch=Enable protection
823+
settings.delete_protected_branch=Disable protection
824+
settings.add_protected_branch_success=%s Locked successfully
825+
settings.add_protected_branch_failed= %s Locked failed
826+
settings.remove_protected_branch_success=%s Unlocked successfully
827+
settings.protected_branch_deletion=To delete a protected branch
828+
settings.protected_branch_deletion_desc=Anyone with write permissions will be able to push directly to this branch. Are you sure?
817829

818830
diff.browse_source = Browse Source
819831
diff.parent = parent

Diff for: public/js/index.js

+37
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,42 @@ function initRepository() {
580580
}
581581
}
582582

583+
function initProtectedBranch() {
584+
$('#protectedBranch').change(function () {
585+
var $this = $(this);
586+
$.post($this.data('url'), {
587+
"_csrf": csrf,
588+
"canPush": true,
589+
"branchName": $this.val(),
590+
},
591+
function (data) {
592+
if (data.redirect) {
593+
window.location.href = data.redirect;
594+
} else {
595+
location.reload();
596+
}
597+
}
598+
);
599+
});
600+
601+
$('.rm').click(function () {
602+
var $this = $(this);
603+
$.post($this.data('url'), {
604+
"_csrf": csrf,
605+
"canPush": false,
606+
"branchName": $this.data('val'),
607+
},
608+
function (data) {
609+
if (data.redirect) {
610+
window.location.href = data.redirect;
611+
} else {
612+
location.reload();
613+
}
614+
}
615+
);
616+
});
617+
}
618+
583619
function initRepositoryCollaboration() {
584620
console.log('initRepositoryCollaboration');
585621

@@ -1402,6 +1438,7 @@ $(document).ready(function () {
14021438
initEditForm();
14031439
initEditor();
14041440
initOrganization();
1441+
initProtectedBranch();
14051442
initWebhook();
14061443
initAdmin();
14071444
initCodeView();

0 commit comments

Comments
 (0)