mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	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
This commit is contained in:
		
				
					committed by
					
						
						Lunny Xiao
					
				
			
			
				
	
			
			
			
						parent
						
							fe5ff8e4b2
						
					
				
				
					commit
					fd941db246
				
			@@ -342,6 +342,10 @@ func runServ(c *cli.Context) error {
 | 
				
			|||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		gitcmd = exec.Command(verb, repoPath)
 | 
							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.Dir = setting.RepoRootPath
 | 
				
			||||||
	gitcmd.Stdout = os.Stdout
 | 
						gitcmd.Stdout = os.Stdout
 | 
				
			||||||
	gitcmd.Stdin = os.Stdin
 | 
						gitcmd.Stdin = os.Stdin
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,12 @@ package cmd
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/git"
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
@@ -48,6 +51,23 @@ func runUpdate(c *cli.Context) error {
 | 
				
			|||||||
		log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use")
 | 
							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{
 | 
						task := models.UpdateTask{
 | 
				
			||||||
		UUID:        os.Getenv("GITEA_UUID"),
 | 
							UUID:        os.Getenv("GITEA_UUID"),
 | 
				
			||||||
		RefName:     args[0],
 | 
							RefName:     args[0],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -421,6 +421,11 @@ func runWeb(ctx *cli.Context) error {
 | 
				
			|||||||
				m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
 | 
									m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
 | 
				
			||||||
				m.Post("/delete", repo.DeleteCollaboration)
 | 
									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.Group("/hooks", func() {
 | 
				
			||||||
				m.Get("", repo.Webhooks)
 | 
									m.Get("", repo.Webhooks)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										161
									
								
								models/branches.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								models/branches.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -82,6 +82,8 @@ var migrations = []Migration{
 | 
				
			|||||||
	NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn),
 | 
						NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn),
 | 
				
			||||||
	// V16 -> v17
 | 
						// V16 -> v17
 | 
				
			||||||
	NewMigration("create repo unit table and add units for all repos", addUnitsToTables),
 | 
						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
 | 
					// Migrate database to current version
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								models/migrations/v17.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								models/migrations/v17.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -524,6 +524,12 @@ func (repo *Repository) HasAccess(u *User) bool {
 | 
				
			|||||||
	return has
 | 
						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
 | 
					// IsOwnedBy returns true when user owns this repository
 | 
				
			||||||
func (repo *Repository) IsOwnedBy(userID int64) bool {
 | 
					func (repo *Repository) IsOwnedBy(userID int64) bool {
 | 
				
			||||||
	return repo.OwnerID == userID
 | 
						return repo.OwnerID == userID
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,7 +88,6 @@ type RepoSettingForm struct {
 | 
				
			|||||||
	RepoName      string `binding:"Required;AlphaDashDot;MaxSize(100)"`
 | 
						RepoName      string `binding:"Required;AlphaDashDot;MaxSize(100)"`
 | 
				
			||||||
	Description   string `binding:"MaxSize(255)"`
 | 
						Description   string `binding:"MaxSize(255)"`
 | 
				
			||||||
	Website       string `binding:"Url;MaxSize(255)"`
 | 
						Website       string `binding:"Url;MaxSize(255)"`
 | 
				
			||||||
	Branch        string
 | 
					 | 
				
			||||||
	Interval      int
 | 
						Interval      int
 | 
				
			||||||
	MirrorAddress string
 | 
						MirrorAddress string
 | 
				
			||||||
	Private       bool
 | 
						Private       bool
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,10 +49,12 @@ Muhammad Fawwaz Orabi <mfawwaz93 AT gmail DOT com>
 | 
				
			|||||||
Nakao Takamasa <at.mattenn AT gmail DOT com>
 | 
					Nakao Takamasa <at.mattenn AT gmail DOT com>
 | 
				
			||||||
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
 | 
					Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
 | 
				
			||||||
Odilon Junior <odilon DOT junior93 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>
 | 
					Richard Bukovansky <richard DOT bukovansky @ gmail DOT com>
 | 
				
			||||||
Robert Nuske <robert DOT nuske AT web DOT de>
 | 
					Robert Nuske <robert DOT nuske AT web DOT de>
 | 
				
			||||||
Robin Hübner <profan AT prfn DOT se>
 | 
					Robin Hübner <profan AT prfn DOT se>
 | 
				
			||||||
SeongJae Park <sj38 DOT park AT gmail DOT com>
 | 
					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>
 | 
					Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
 | 
				
			||||||
Tilmann Bach <tilmann AT outlook DOT com>
 | 
					Tilmann Bach <tilmann AT outlook DOT com>
 | 
				
			||||||
Toni Villena Jiménez <tonivj5 AT gmail DOT com>
 | 
					Toni Villena Jiménez <tonivj5 AT gmail DOT com>
 | 
				
			||||||
@@ -60,5 +62,3 @@ Vladimir Jigulin mogaika AT yandex DOT ru
 | 
				
			|||||||
Vladimir Vissoultchev <wqweto AT gmail DOT com>
 | 
					Vladimir Vissoultchev <wqweto AT gmail DOT com>
 | 
				
			||||||
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
 | 
					YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
 | 
				
			||||||
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
 | 
					Łukasz Jan Niemier <lukasz AT niemier DOT pl>
 | 
				
			||||||
Pablo Saavedra <psaavedra AT igalia DOT com>
 | 
					 | 
				
			||||||
Thiago Avelino <thiago AT avelino DOT xxx>
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 = 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_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.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.browse_source = Browse Source
 | 
				
			||||||
diff.parent = parent
 | 
					diff.parent = parent
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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() {
 | 
					function initRepositoryCollaboration() {
 | 
				
			||||||
    console.log('initRepositoryCollaboration');
 | 
					    console.log('initRepositoryCollaboration');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1402,6 +1438,7 @@ $(document).ready(function () {
 | 
				
			|||||||
    initEditForm();
 | 
					    initEditForm();
 | 
				
			||||||
    initEditor();
 | 
					    initEditor();
 | 
				
			||||||
    initOrganization();
 | 
					    initOrganization();
 | 
				
			||||||
 | 
					    initProtectedBranch();
 | 
				
			||||||
    initWebhook();
 | 
					    initWebhook();
 | 
				
			||||||
    initAdmin();
 | 
					    initAdmin();
 | 
				
			||||||
    initCodeView();
 | 
					    initCodeView();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,10 +42,20 @@ func HTTP(ctx *context.Context) {
 | 
				
			|||||||
	} else if service == "git-upload-pack" ||
 | 
						} else if service == "git-upload-pack" ||
 | 
				
			||||||
		strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
 | 
							strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
 | 
				
			||||||
		isPull = true
 | 
							isPull = true
 | 
				
			||||||
 | 
						} else if service == "git-upload-archive" ||
 | 
				
			||||||
 | 
							strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
 | 
				
			||||||
 | 
							isPull = true
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		isPull = (ctx.Req.Method == "GET")
 | 
							isPull = (ctx.Req.Method == "GET")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var accessMode models.AccessMode
 | 
				
			||||||
 | 
						if isPull {
 | 
				
			||||||
 | 
							accessMode = models.AccessModeRead
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							accessMode = models.AccessModeWrite
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	isWiki := false
 | 
						isWiki := false
 | 
				
			||||||
	if strings.HasSuffix(reponame, ".wiki") {
 | 
						if strings.HasSuffix(reponame, ".wiki") {
 | 
				
			||||||
		isWiki = true
 | 
							isWiki = true
 | 
				
			||||||
@@ -146,17 +156,12 @@ func HTTP(ctx *context.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !isPublicPull {
 | 
								if !isPublicPull {
 | 
				
			||||||
				var tp = models.AccessModeWrite
 | 
									has, err := models.HasAccess(authUser, repo, accessMode)
 | 
				
			||||||
				if isPull {
 | 
					 | 
				
			||||||
					tp = models.AccessModeRead
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				has, err := models.HasAccess(authUser, repo, tp)
 | 
					 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
 | 
										ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				} else if !has {
 | 
									} else if !has {
 | 
				
			||||||
					if tp == models.AccessModeRead {
 | 
										if accessMode == models.AccessModeRead {
 | 
				
			||||||
						has, err = models.HasAccess(authUser, repo, models.AccessModeWrite)
 | 
											has, err = models.HasAccess(authUser, repo, models.AccessModeWrite)
 | 
				
			||||||
						if err != nil {
 | 
											if err != nil {
 | 
				
			||||||
							ctx.Handle(http.StatusInternalServerError, "HasAccess2", err)
 | 
												ctx.Handle(http.StatusInternalServerError, "HasAccess2", err)
 | 
				
			||||||
@@ -232,9 +237,20 @@ func HTTP(ctx *context.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params := make(map[string]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if askAuth {
 | 
				
			||||||
 | 
							params[models.ProtectedBranchUserID] = fmt.Sprintf("%d", authUser.ID)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								params[models.ProtectedBranchAccessMode] = accessMode.String()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							params[models.ProtectedBranchRepoID] = fmt.Sprintf("%d", repo.ID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	HTTPBackend(ctx, &serviceConfig{
 | 
						HTTPBackend(ctx, &serviceConfig{
 | 
				
			||||||
		UploadPack:  true,
 | 
							UploadPack:  true,
 | 
				
			||||||
		ReceivePack: true,
 | 
							ReceivePack: true,
 | 
				
			||||||
 | 
							Params:      params,
 | 
				
			||||||
		OnSucceed:   callback,
 | 
							OnSucceed:   callback,
 | 
				
			||||||
	})(ctx.Resp, ctx.Req.Request)
 | 
						})(ctx.Resp, ctx.Req.Request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -244,6 +260,7 @@ func HTTP(ctx *context.Context) {
 | 
				
			|||||||
type serviceConfig struct {
 | 
					type serviceConfig struct {
 | 
				
			||||||
	UploadPack  bool
 | 
						UploadPack  bool
 | 
				
			||||||
	ReceivePack bool
 | 
						ReceivePack bool
 | 
				
			||||||
 | 
						Params      map[string]string
 | 
				
			||||||
	OnSucceed   func(rpc string, input []byte)
 | 
						OnSucceed   func(rpc string, input []byte)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -261,6 +278,42 @@ func (h *serviceHandler) setHeaderNoCache() {
 | 
				
			|||||||
	h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
 | 
						h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *serviceHandler) getBranch(input []byte) string {
 | 
				
			||||||
 | 
						var lastLine int64
 | 
				
			||||||
 | 
						var branchName string
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							head := input[lastLine : lastLine+2]
 | 
				
			||||||
 | 
							if head[0] == '0' && head[1] == '0' {
 | 
				
			||||||
 | 
								size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Error(4, "%v", err)
 | 
				
			||||||
 | 
									return branchName
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if size == 0 {
 | 
				
			||||||
 | 
									//fmt.Println(string(input[lastLine:]))
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								line := input[lastLine : lastLine+size]
 | 
				
			||||||
 | 
								idx := bytes.IndexRune(line, '\000')
 | 
				
			||||||
 | 
								if idx > -1 {
 | 
				
			||||||
 | 
									line = line[:idx]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								fields := strings.Fields(string(line))
 | 
				
			||||||
 | 
								if len(fields) >= 3 {
 | 
				
			||||||
 | 
									refFullName := fields[2]
 | 
				
			||||||
 | 
									branchName = strings.TrimPrefix(refFullName, git.BranchPrefix)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								lastLine = lastLine + size
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return branchName
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *serviceHandler) setHeaderCacheForever() {
 | 
					func (h *serviceHandler) setHeaderCacheForever() {
 | 
				
			||||||
	now := time.Now().Unix()
 | 
						now := time.Now().Unix()
 | 
				
			||||||
	expires := now + 31536000
 | 
						expires := now + 31536000
 | 
				
			||||||
@@ -358,13 +411,15 @@ func serviceRPC(h serviceHandler, service string) {
 | 
				
			|||||||
		h.w.WriteHeader(http.StatusUnauthorized)
 | 
							h.w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
 | 
						h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		reqBody = h.r.Body
 | 
							reqBody    = h.r.Body
 | 
				
			||||||
		input   []byte
 | 
							input      []byte
 | 
				
			||||||
		br      io.Reader
 | 
							br         io.Reader
 | 
				
			||||||
		err     error
 | 
							err        error
 | 
				
			||||||
 | 
							branchName string
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Handle GZIP.
 | 
						// Handle GZIP.
 | 
				
			||||||
@@ -385,11 +440,31 @@ func serviceRPC(h serviceHandler, service string) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							branchName = h.getBranch(input)
 | 
				
			||||||
		br = bytes.NewReader(input)
 | 
							br = bytes.NewReader(input)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		br = reqBody
 | 
							br = reqBody
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check protected branch
 | 
				
			||||||
 | 
						repoID, _ := strconv.ParseInt(h.cfg.Params[models.ProtectedBranchRepoID], 10, 64)
 | 
				
			||||||
 | 
						accessMode := models.ParseAccessMode(h.cfg.Params[models.ProtectedBranchAccessMode])
 | 
				
			||||||
 | 
						// skip admin or owner AccessMode
 | 
				
			||||||
 | 
						if accessMode == models.AccessModeWrite {
 | 
				
			||||||
 | 
							protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.GitLogger.Error(2, "fail to get protected branch information: %v", err)
 | 
				
			||||||
 | 
								h.w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if protectBranch != nil {
 | 
				
			||||||
 | 
								log.GitLogger.Error(2, "protected branches can not be pushed to")
 | 
				
			||||||
 | 
								h.w.WriteHeader(http.StatusForbidden)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
 | 
						cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
 | 
				
			||||||
	cmd.Dir = h.dir
 | 
						cmd.Dir = h.dir
 | 
				
			||||||
	cmd.Stdout = h.w
 | 
						cmd.Stdout = h.w
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
const (
 | 
					const (
 | 
				
			||||||
	tplSettingsOptions base.TplName = "repo/settings/options"
 | 
						tplSettingsOptions base.TplName = "repo/settings/options"
 | 
				
			||||||
	tplCollaboration   base.TplName = "repo/settings/collaboration"
 | 
						tplCollaboration   base.TplName = "repo/settings/collaboration"
 | 
				
			||||||
 | 
						tplBranches        base.TplName = "repo/settings/branches"
 | 
				
			||||||
	tplGithooks        base.TplName = "repo/settings/githooks"
 | 
						tplGithooks        base.TplName = "repo/settings/githooks"
 | 
				
			||||||
	tplGithookEdit     base.TplName = "repo/settings/githook_edit"
 | 
						tplGithookEdit     base.TplName = "repo/settings/githook_edit"
 | 
				
			||||||
	tplDeployKeys      base.TplName = "repo/settings/deploy_keys"
 | 
						tplDeployKeys      base.TplName = "repo/settings/deploy_keys"
 | 
				
			||||||
@@ -78,17 +79,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
 | 
				
			|||||||
		// In case it's just a case change.
 | 
							// In case it's just a case change.
 | 
				
			||||||
		repo.Name = newRepoName
 | 
							repo.Name = newRepoName
 | 
				
			||||||
		repo.LowerName = strings.ToLower(newRepoName)
 | 
							repo.LowerName = strings.ToLower(newRepoName)
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if ctx.Repo.GitRepo.IsBranchExist(form.Branch) &&
 | 
					 | 
				
			||||||
			repo.DefaultBranch != form.Branch {
 | 
					 | 
				
			||||||
			repo.DefaultBranch = form.Branch
 | 
					 | 
				
			||||||
			if err := ctx.Repo.GitRepo.SetDefaultBranch(form.Branch); err != nil {
 | 
					 | 
				
			||||||
				if !git.IsErrUnsupportedVersion(err) {
 | 
					 | 
				
			||||||
					ctx.Handle(500, "SetDefaultBranch", err)
 | 
					 | 
				
			||||||
					return
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		repo.Description = form.Description
 | 
							repo.Description = form.Description
 | 
				
			||||||
		repo.Website = form.Website
 | 
							repo.Website = form.Website
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -429,6 +419,142 @@ func DeleteCollaboration(ctx *context.Context) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ProtectedBranch render the page to protect the repository
 | 
				
			||||||
 | 
					func ProtectedBranch(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["Title"] = ctx.Tr("repo.settings")
 | 
				
			||||||
 | 
						ctx.Data["PageIsSettingsBranches"] = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protectedBranches, err := ctx.Repo.Repository.GetProtectedBranches()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Handle(500, "GetProtectedBranches", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Data["ProtectedBranches"] = protectedBranches
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branches := ctx.Data["Branches"].([]string)
 | 
				
			||||||
 | 
						leftBranches := make([]string, 0, len(branches)-len(protectedBranches))
 | 
				
			||||||
 | 
						for _, b := range branches {
 | 
				
			||||||
 | 
							var protected bool
 | 
				
			||||||
 | 
							for _, pb := range protectedBranches {
 | 
				
			||||||
 | 
								if b == pb.BranchName {
 | 
				
			||||||
 | 
									protected = true
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !protected {
 | 
				
			||||||
 | 
								leftBranches = append(leftBranches, b)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["LeftBranches"] = leftBranches
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.HTML(200, tplBranches)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ProtectedBranchPost response for protect for a branch of a repository
 | 
				
			||||||
 | 
					func ProtectedBranchPost(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["Title"] = ctx.Tr("repo.settings")
 | 
				
			||||||
 | 
						ctx.Data["PageIsSettingsBranches"] = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repo := ctx.Repo.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch ctx.Query("action") {
 | 
				
			||||||
 | 
						case "default_branch":
 | 
				
			||||||
 | 
							if ctx.HasError() {
 | 
				
			||||||
 | 
								ctx.HTML(200, tplBranches)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							branch := strings.ToLower(ctx.Query("branch"))
 | 
				
			||||||
 | 
							if ctx.Repo.GitRepo.IsBranchExist(branch) &&
 | 
				
			||||||
 | 
								repo.DefaultBranch != branch {
 | 
				
			||||||
 | 
								repo.DefaultBranch = branch
 | 
				
			||||||
 | 
								if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
 | 
				
			||||||
 | 
									if !git.IsErrUnsupportedVersion(err) {
 | 
				
			||||||
 | 
										ctx.Handle(500, "SetDefaultBranch", err)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := repo.UpdateDefaultBranch(); err != nil {
 | 
				
			||||||
 | 
									ctx.Handle(500, "SetDefaultBranch", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
 | 
				
			||||||
 | 
							ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
 | 
				
			||||||
 | 
						case "protected_branch":
 | 
				
			||||||
 | 
							if ctx.HasError() {
 | 
				
			||||||
 | 
								ctx.JSON(200, map[string]string{
 | 
				
			||||||
 | 
									"redirect": setting.AppSubURL + ctx.Req.URL.Path,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							branchName := strings.ToLower(ctx.Query("branchName"))
 | 
				
			||||||
 | 
							if len(branchName) == 0 || !ctx.Repo.GitRepo.IsBranchExist(branchName) {
 | 
				
			||||||
 | 
								ctx.JSON(200, map[string]string{
 | 
				
			||||||
 | 
									"redirect": setting.AppSubURL + ctx.Req.URL.Path,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							canPush := ctx.QueryBool("canPush")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if canPush {
 | 
				
			||||||
 | 
								if err := ctx.Repo.Repository.AddProtectedBranch(branchName, canPush); err != nil {
 | 
				
			||||||
 | 
									ctx.Flash.Error(ctx.Tr("repo.settings.add_protected_branch_failed", branchName))
 | 
				
			||||||
 | 
									ctx.JSON(200, map[string]string{
 | 
				
			||||||
 | 
										"status": "ok",
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ctx.Flash.Success(ctx.Tr("repo.settings.add_protected_branch_success", branchName))
 | 
				
			||||||
 | 
								ctx.JSON(200, map[string]string{
 | 
				
			||||||
 | 
									"redirect": setting.AppSubURL + ctx.Req.URL.Path,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if err := ctx.Repo.Repository.DeleteProtectedBranch(ctx.QueryInt64("id")); err != nil {
 | 
				
			||||||
 | 
									ctx.Flash.Error("DeleteProtectedBranch: " + err.Error())
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", branchName))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 | 
									"status": "ok",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							ctx.Handle(404, "", nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ChangeProtectedBranch response for changing access of a protect branch
 | 
				
			||||||
 | 
					func ChangeProtectedBranch(ctx *context.Context) {
 | 
				
			||||||
 | 
						if err := ctx.Repo.Repository.ChangeProtectedBranch(
 | 
				
			||||||
 | 
							ctx.QueryInt64("id"),
 | 
				
			||||||
 | 
							ctx.QueryBool("canPush")); err != nil {
 | 
				
			||||||
 | 
							log.Error(4, "ChangeProtectedBranch: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteProtectedBranch delete a protection for a branch of a repository
 | 
				
			||||||
 | 
					func DeleteProtectedBranch(ctx *context.Context) {
 | 
				
			||||||
 | 
						if err := ctx.Repo.Repository.DeleteProtectedBranch(ctx.QueryInt64("id")); err != nil {
 | 
				
			||||||
 | 
							ctx.Flash.Error("DeleteProtectedBranch: " + err.Error())
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 | 
							"redirect": ctx.Repo.RepoLink + "/settings/branches",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parseOwnerAndRepo get repos by owner
 | 
				
			||||||
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
 | 
					func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
 | 
				
			||||||
	owner, err := models.GetUserByName(ctx.Params(":username"))
 | 
						owner, err := models.GetUserByName(ctx.Params(":username"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										91
									
								
								templates/repo/settings/branches.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								templates/repo/settings/branches.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					{{template "base/head" .}}
 | 
				
			||||||
 | 
					<div class="repository settings edit">
 | 
				
			||||||
 | 
						{{template "repo/header" .}}
 | 
				
			||||||
 | 
						<div class="ui container">
 | 
				
			||||||
 | 
							<div class="ui grid">
 | 
				
			||||||
 | 
								{{template "repo/settings/navbar" .}}
 | 
				
			||||||
 | 
								<div class="twelve wide column content">
 | 
				
			||||||
 | 
									{{template "base/alert" .}}
 | 
				
			||||||
 | 
									<h4 class="ui top attached header">
 | 
				
			||||||
 | 
										{{.i18n.Tr "repo.default_branch"}}
 | 
				
			||||||
 | 
									</h4>
 | 
				
			||||||
 | 
									<div class="ui attached table segment">
 | 
				
			||||||
 | 
										<form class="ui hook list form" action="{{.Link}}" method="post">
 | 
				
			||||||
 | 
											{{.CsrfTokenHtml}}
 | 
				
			||||||
 | 
											<input type="hidden" name="action" value="default_branch">
 | 
				
			||||||
 | 
											<div class="item">
 | 
				
			||||||
 | 
												The default branch is considered the "base" branch in your repository,
 | 
				
			||||||
 | 
												against which all pull requests and code commits are automatically made,
 | 
				
			||||||
 | 
												unless you specify a different branch.
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											{{if not .Repository.IsBare}}
 | 
				
			||||||
 | 
											<div class="ui grid padded">
 | 
				
			||||||
 | 
												<div class="eight wide column">
 | 
				
			||||||
 | 
													<div class="ui fluid dropdown selection visible" tabindex="0">
 | 
				
			||||||
 | 
														<select name="branch">
 | 
				
			||||||
 | 
															<option value="{{.Repository.DefaultBranch}}">{{.Repository.DefaultBranch}}</option>
 | 
				
			||||||
 | 
															{{range .Branches}}
 | 
				
			||||||
 | 
																<option value="{{.}}">{{.}}</option>
 | 
				
			||||||
 | 
															{{end}}
 | 
				
			||||||
 | 
					          							</select><i class="dropdown icon"></i>
 | 
				
			||||||
 | 
														<div class="default text">{{.Repository.DefaultBranch}}</div>
 | 
				
			||||||
 | 
														<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
 | 
				
			||||||
 | 
															{{range .Branches}}
 | 
				
			||||||
 | 
																<div class="item" data-value="{{.}}">{{.}}</div>
 | 
				
			||||||
 | 
															{{end}}
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
													</div>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											{{end}}
 | 
				
			||||||
 | 
											<div class="item field">
 | 
				
			||||||
 | 
												<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</form>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									<h4 class="ui top attached header">
 | 
				
			||||||
 | 
										{{.i18n.Tr "repo.settings.protected_branch"}}
 | 
				
			||||||
 | 
									</h4>
 | 
				
			||||||
 | 
									<div class="ui attached table segment">
 | 
				
			||||||
 | 
										<div class="ui grid padded">
 | 
				
			||||||
 | 
											<div class="eight wide column">
 | 
				
			||||||
 | 
												<div class="ui fluid dropdown selection visible" tabindex="0">
 | 
				
			||||||
 | 
													<select id="protectedBranch" name="branch" data-url="{{.Repository.Link}}/settings/branches?action=protected_branch">
 | 
				
			||||||
 | 
														{{range .LeftBranches}}
 | 
				
			||||||
 | 
															<option value="">Choose a branch...</option>
 | 
				
			||||||
 | 
															<option value="{{.}}">{{.}}</option>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
													</select><i class="dropdown icon"></i>
 | 
				
			||||||
 | 
													<div class="default text">Choose a branch...</div>
 | 
				
			||||||
 | 
													<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
 | 
				
			||||||
 | 
														{{range .LeftBranches}}
 | 
				
			||||||
 | 
															<div class="item" data-value="{{.}}">{{.}}</div>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
													</div>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										<div class="ui grid padded">
 | 
				
			||||||
 | 
											<div class="sixteen wide column">
 | 
				
			||||||
 | 
												<table class="ui single line table padded">
 | 
				
			||||||
 | 
													<tbody>
 | 
				
			||||||
 | 
														{{range .ProtectedBranches}}
 | 
				
			||||||
 | 
															<tr>
 | 
				
			||||||
 | 
																<td><div class="ui large label">{{.BranchName}}</div></td>
 | 
				
			||||||
 | 
																<td class="right aligned"><button class="rm ui red button" data-url="{{$.Repository.Link}}/settings/branches?action=protected_branch&id={{.ID}}" data-val="{{.BranchName}}">Delete</button></td>
 | 
				
			||||||
 | 
															</tr>
 | 
				
			||||||
 | 
														{{else}}
 | 
				
			||||||
 | 
															<tr class="center aligned"><td>There is no protected branch</td></tr>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
													</tbody>
 | 
				
			||||||
 | 
												</table>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{{template "base/footer" .}}
 | 
				
			||||||
@@ -4,6 +4,7 @@
 | 
				
			|||||||
		<ul class="menu menu-vertical switching-list grid-1-5 left">
 | 
							<ul class="menu menu-vertical switching-list grid-1-5 left">
 | 
				
			||||||
			<li {{if .PageIsSettingsOptions}}class="current"{{end}}><a href="{{.RepoLink}}/settings">{{.i18n.Tr "repo.settings.options"}}</a></li>
 | 
								<li {{if .PageIsSettingsOptions}}class="current"{{end}}><a href="{{.RepoLink}}/settings">{{.i18n.Tr "repo.settings.options"}}</a></li>
 | 
				
			||||||
			<li {{if .PageIsSettingsCollaboration}}class="current"{{end}}><a href="{{.RepoLink}}/settings/collaboration">{{.i18n.Tr "repo.settings.collaboration"}}</a></li>
 | 
								<li {{if .PageIsSettingsCollaboration}}class="current"{{end}}><a href="{{.RepoLink}}/settings/collaboration">{{.i18n.Tr "repo.settings.collaboration"}}</a></li>
 | 
				
			||||||
 | 
								<li {{if .PageIsSettingsBranches}}class="current"{{end}}><a href="{{.RepoLink}}/settings/branches">{{.i18n.Tr "repo.settings.branches"}}</a></li>
 | 
				
			||||||
			<li {{if .PageIsSettingsHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks">{{.i18n.Tr "repo.settings.hooks"}}</a></li>
 | 
								<li {{if .PageIsSettingsHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks">{{.i18n.Tr "repo.settings.hooks"}}</a></li>
 | 
				
			||||||
			{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}}
 | 
								{{if or .SignedUser.AllowGitHook .SignedUser.IsAdmin}}
 | 
				
			||||||
				<li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.i18n.Tr "repo.settings.githooks"}}</a></li>
 | 
									<li {{if .PageIsSettingsGitHooks}}class="current"{{end}}><a href="{{.RepoLink}}/settings/hooks/git">{{.i18n.Tr "repo.settings.githooks"}}</a></li>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,11 @@
 | 
				
			|||||||
		<a class="{{if .PageIsSettingsCollaboration}}active{{end}} item" href="{{.RepoLink}}/settings/collaboration">
 | 
							<a class="{{if .PageIsSettingsCollaboration}}active{{end}} item" href="{{.RepoLink}}/settings/collaboration">
 | 
				
			||||||
			{{.i18n.Tr "repo.settings.collaboration"}}
 | 
								{{.i18n.Tr "repo.settings.collaboration"}}
 | 
				
			||||||
		</a>
 | 
							</a>
 | 
				
			||||||
 | 
							{{if not .Repository.IsBare}}
 | 
				
			||||||
 | 
								<a class="{{if .PageIsSettingsBranches}}active{{end}} item" href="{{.RepoLink}}/settings/branches">
 | 
				
			||||||
 | 
									{{.i18n.Tr "repo.settings.branches"}}
 | 
				
			||||||
 | 
								</a>
 | 
				
			||||||
 | 
							{{end}}
 | 
				
			||||||
		<a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks">
 | 
							<a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks">
 | 
				
			||||||
			{{.i18n.Tr "repo.settings.hooks"}}
 | 
								{{.i18n.Tr "repo.settings.hooks"}}
 | 
				
			||||||
		</a>
 | 
							</a>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,30 +17,6 @@
 | 
				
			|||||||
							<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
 | 
												<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
 | 
				
			||||||
							<input id="repo_name" name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required>
 | 
												<input id="repo_name" name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
						<div class="field {{if .Err_Description}}error{{end}}">
 | 
					 | 
				
			||||||
							<label for="description">{{$.i18n.Tr "repo.repo_desc"}}</label>
 | 
					 | 
				
			||||||
							<textarea id="description" name="description" rows="2">{{.Repository.Description}}</textarea>
 | 
					 | 
				
			||||||
						</div>
 | 
					 | 
				
			||||||
						<div class="field {{if .Err_Website}}error{{end}}">
 | 
					 | 
				
			||||||
							<label for="website">{{.i18n.Tr "repo.settings.site"}}</label>
 | 
					 | 
				
			||||||
							<input id="website" name="website" type="url" value="{{.Repository.Website}}">
 | 
					 | 
				
			||||||
						</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						{{if not .Repository.IsBare}}
 | 
					 | 
				
			||||||
							<div class="required inline field">
 | 
					 | 
				
			||||||
								<label>{{.i18n.Tr "repo.default_branch"}}</label>
 | 
					 | 
				
			||||||
								<div class="ui selection dropdown">
 | 
					 | 
				
			||||||
									<input type="hidden" id="branch" name="branch" value="{{.Repository.DefaultBranch}}">
 | 
					 | 
				
			||||||
									<div class="text">{{.Repository.DefaultBranch}}</div>
 | 
					 | 
				
			||||||
									<i class="dropdown icon"></i>
 | 
					 | 
				
			||||||
									<div class="menu">
 | 
					 | 
				
			||||||
										{{range .Branches}}
 | 
					 | 
				
			||||||
											<div class="item" data-value="{{.}}">{{.}}</div>
 | 
					 | 
				
			||||||
										{{end}}
 | 
					 | 
				
			||||||
									</div>
 | 
					 | 
				
			||||||
								</div>
 | 
					 | 
				
			||||||
							</div>
 | 
					 | 
				
			||||||
						{{end}}
 | 
					 | 
				
			||||||
						{{if not .Repository.IsFork}}
 | 
											{{if not .Repository.IsFork}}
 | 
				
			||||||
							<div class="inline field">
 | 
												<div class="inline field">
 | 
				
			||||||
								<label>{{.i18n.Tr "repo.visibility"}}</label>
 | 
													<label>{{.i18n.Tr "repo.visibility"}}</label>
 | 
				
			||||||
@@ -50,6 +26,14 @@
 | 
				
			|||||||
								</div>
 | 
													</div>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
						{{end}}
 | 
											{{end}}
 | 
				
			||||||
 | 
											<div class="field {{if .Err_Description}}error{{end}}">
 | 
				
			||||||
 | 
												<label for="description">{{$.i18n.Tr "repo.repo_desc"}}</label>
 | 
				
			||||||
 | 
												<textarea id="description" name="description" rows="2">{{.Repository.Description}}</textarea>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											<div class="field {{if .Err_Website}}error{{end}}">
 | 
				
			||||||
 | 
												<label for="website">{{.i18n.Tr "repo.settings.site"}}</label>
 | 
				
			||||||
 | 
												<input id="website" name="website" type="url" value="{{.Repository.Website}}">
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						<div class="field">
 | 
											<div class="field">
 | 
				
			||||||
							<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
 | 
												<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user