Skip to content

Commit

Permalink
Protected branches system (#339)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
denji authored and lunny committed Feb 21, 2017
1 parent fe5ff8e commit fd941db
Show file tree
Hide file tree
Showing 17 changed files with 606 additions and 49 deletions.
4 changes: 4 additions & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ func runServ(c *cli.Context) error {
} else {
gitcmd = exec.Command(verb, repoPath)
}

os.Setenv(models.ProtectedBranchAccessMode, requestedMode.String())
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))

gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin
Expand Down
20 changes: 20 additions & 0 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ package cmd

import (
"os"
"strconv"
"strings"

"github.com/urfave/cli"

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

// protected branch check
branchName := strings.TrimPrefix(args[0], git.BranchPrefix)
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
log.GitLogger.Trace("pushing to %d %v", repoID, branchName)
accessMode := models.ParseAccessMode(os.Getenv(models.ProtectedBranchAccessMode))
// skip admin or owner AccessMode
if accessMode == models.AccessModeWrite {
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
if err != nil {
log.GitLogger.Fatal(2, "retrieve protected branches information failed")
}

if protectBranch != nil {
log.GitLogger.Fatal(2, "protected branches can not be pushed to")
}
}

task := models.UpdateTask{
UUID: os.Getenv("GITEA_UUID"),
RefName: args[0],
Expand Down
5 changes: 5 additions & 0 deletions cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@ func runWeb(ctx *cli.Context) error {
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
m.Post("/delete", repo.DeleteCollaboration)
})
m.Group("/branches", func() {
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
m.Post("/can_push", repo.ChangeProtectedBranch)
m.Post("/delete", repo.DeleteProtectedBranch)
})

m.Group("/hooks", func() {
m.Get("", repo.Webhooks)
Expand Down
161 changes: 161 additions & 0 deletions models/branches.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright 2016 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 (
"fmt"
"strings"
"time"
)

// Protected metadata
const (
// Protected User ID
ProtectedBranchUserID = "GITEA_USER_ID"
// Protected Repo ID
ProtectedBranchRepoID = "GITEA_REPO_ID"
// Protected access mode
ProtectedBranchAccessMode = "GITEA_ACCESS_MODE"
)

// ProtectedBranch struct
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
}

// BeforeInsert before protected branch insert create and update time
func (protectBranch *ProtectedBranch) BeforeInsert() {
protectBranch.CreatedUnix = time.Now().Unix()
protectBranch.UpdatedUnix = protectBranch.CreatedUnix
}

// BeforeUpdate before protected branch update time
func (protectBranch *ProtectedBranch) BeforeUpdate() {
protectBranch.UpdatedUnix = time.Now().Unix()
}

// GetProtectedBranchByRepoID getting protected branch by repo ID
func GetProtectedBranchByRepoID(RepoID int64) ([]*ProtectedBranch, error) {
protectedBranches := make([]*ProtectedBranch, 0)
return protectedBranches, x.Where("repo_id = ?", RepoID).Desc("updated_unix").Find(&protectedBranches)
}

// GetProtectedBranchBy getting protected branch by ID/Name
func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, error) {
rel := &ProtectedBranch{RepoID: repoID, BranchName: strings.ToLower(BranchName)}
has, err := x.Get(rel)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return rel, nil
}

// GetProtectedBranches get all protected btanches
func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
protectedBranches := make([]*ProtectedBranch, 0)
return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})
}

// AddProtectedBranch add protection to branch
func (repo *Repository) AddProtectedBranch(branchName string, canPush bool) error {
protectedBranch := &ProtectedBranch{
RepoID: repo.ID,
BranchName: branchName,
}

has, err := x.Get(protectedBranch)
if err != nil {
return err
} else if has {
return nil
}

sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
protectedBranch.CanPush = canPush
if _, err = sess.InsertOne(protectedBranch); err != nil {
return err
}

return sess.Commit()
}

// ChangeProtectedBranch access mode sets new access mode for the ProtectedBranch.
func (repo *Repository) ChangeProtectedBranch(id int64, canPush bool) error {
ProtectedBranch := &ProtectedBranch{
RepoID: repo.ID,
ID: id,
}
has, err := x.Get(ProtectedBranch)
if err != nil {
return fmt.Errorf("get ProtectedBranch: %v", err)
} else if !has {
return nil
}

if ProtectedBranch.CanPush == canPush {
return nil
}
ProtectedBranch.CanPush = canPush

sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}

if _, err = sess.Id(ProtectedBranch.ID).AllCols().Update(ProtectedBranch); err != nil {
return fmt.Errorf("update ProtectedBranch: %v", err)
}

return sess.Commit()
}

// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
protectedBranch := &ProtectedBranch{
RepoID: repo.ID,
ID: id,
}

sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}

if affected, err := sess.Delete(protectedBranch); err != nil {
return err
} else if affected != 1 {
return fmt.Errorf("delete protected branch ID(%v) failed", id)
}

return sess.Commit()
}

// newProtectedBranch insert one queue
func newProtectedBranch(protectedBranch *ProtectedBranch) error {
_, err := x.InsertOne(protectedBranch)
return err
}

// UpdateProtectedBranch update queue
func UpdateProtectedBranch(protectedBranch *ProtectedBranch) error {
_, err := x.Update(protectedBranch)
return err
}
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ var migrations = []Migration{
NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn),
// V16 -> v17
NewMigration("create repo unit table and add units for all repos", addUnitsToTables),
// v17 -> v18
NewMigration("set protect branches updated with created", setProtectedBranchUpdatedWithCreated),
}

// Migrate database to current version
Expand Down
29 changes: 29 additions & 0 deletions models/migrations/v17.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2016 Gitea. 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 (
"fmt"
"time"

"github.com/go-xorm/xorm"
)

func setProtectedBranchUpdatedWithCreated(x *xorm.Engine) (err error) {
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
BranchName string `xorm:"UNIQUE(s)"`
CanPush bool
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
}
if err = x.Sync2(new(ProtectedBranch)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}
6 changes: 6 additions & 0 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ func (repo *Repository) HasAccess(u *User) bool {
return has
}

// UpdateDefaultBranch updates the default branch
func (repo *Repository) UpdateDefaultBranch() error {
_, err := x.ID(repo.ID).Cols("default_branch").Update(repo)
return err
}

// IsOwnedBy returns true when user owns this repository
func (repo *Repository) IsOwnedBy(userID int64) bool {
return repo.OwnerID == userID
Expand Down
1 change: 0 additions & 1 deletion modules/auth/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ type RepoSettingForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
Description string `binding:"MaxSize(255)"`
Website string `binding:"Url;MaxSize(255)"`
Branch string
Interval int
MirrorAddress string
Private bool
Expand Down
4 changes: 2 additions & 2 deletions options/locale/TRANSLATORS
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ Muhammad Fawwaz Orabi <mfawwaz93 AT gmail DOT com>
Nakao Takamasa <at.mattenn AT gmail DOT com>
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
Pablo Saavedra <psaavedra AT igalia DOT com>
Richard Bukovansky <richard DOT bukovansky @ gmail DOT com>
Robert Nuske <robert DOT nuske AT web DOT de>
Robin Hübner <profan AT prfn DOT se>
SeongJae Park <sj38 DOT park AT gmail DOT com>
Thiago Avelino <thiago AT avelino DOT xxx>
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
Tilmann Bach <tilmann AT outlook DOT com>
Toni Villena Jiménez <tonivj5 AT gmail DOT com>
Vladimir Jigulin mogaika AT yandex DOT ru
Vladimir Vissoultchev <wqweto AT gmail DOT com>
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
Pablo Saavedra <psaavedra AT igalia DOT com>
Thiago Avelino <thiago AT avelino DOT xxx>
12 changes: 12 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,18 @@ settings.add_key_success = New deploy key '%s' has been added successfully!
settings.deploy_key_deletion = Delete Deploy Key
settings.deploy_key_deletion_desc = Deleting this deploy key will remove all related accesses for this repository. Do you want to continue?
settings.deploy_key_deletion_success = Deploy key has been deleted successfully!
settings.branches=Branches
settings.protected_branch=Branch Protection
settings.protected_branch_can_push=Allow push?
settings.protected_branch_can_push_yes=You can push
settings.protected_branch_can_push_no=You can not push
settings.add_protected_branch=Enable protection
settings.delete_protected_branch=Disable protection
settings.add_protected_branch_success=%s Locked successfully
settings.add_protected_branch_failed= %s Locked failed
settings.remove_protected_branch_success=%s Unlocked successfully
settings.protected_branch_deletion=To delete a protected branch
settings.protected_branch_deletion_desc=Anyone with write permissions will be able to push directly to this branch. Are you sure?

diff.browse_source = Browse Source
diff.parent = parent
Expand Down
37 changes: 37 additions & 0 deletions public/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,42 @@ function initRepository() {
}
}

function initProtectedBranch() {
$('#protectedBranch').change(function () {
var $this = $(this);
$.post($this.data('url'), {
"_csrf": csrf,
"canPush": true,
"branchName": $this.val(),
},
function (data) {
if (data.redirect) {
window.location.href = data.redirect;
} else {
location.reload();
}
}
);
});

$('.rm').click(function () {
var $this = $(this);
$.post($this.data('url'), {
"_csrf": csrf,
"canPush": false,
"branchName": $this.data('val'),
},
function (data) {
if (data.redirect) {
window.location.href = data.redirect;
} else {
location.reload();
}
}
);
});
}

function initRepositoryCollaboration() {
console.log('initRepositoryCollaboration');

Expand Down Expand Up @@ -1402,6 +1438,7 @@ $(document).ready(function () {
initEditForm();
initEditor();
initOrganization();
initProtectedBranch();
initWebhook();
initAdmin();
initCodeView();
Expand Down
Loading

0 comments on commit fd941db

Please sign in to comment.