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:
		
							parent
							
								
									fe5ff8e4b2
								
							
						
					
					
						commit
						fd941db246
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			@ -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],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
	// 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,10 +49,12 @@ 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -60,5 +62,3 @@ 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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1402,6 +1438,7 @@ $(document).ready(function () {
 | 
			
		|||
    initEditForm();
 | 
			
		||||
    initEditor();
 | 
			
		||||
    initOrganization();
 | 
			
		||||
    initProtectedBranch();
 | 
			
		||||
    initWebhook();
 | 
			
		||||
    initAdmin();
 | 
			
		||||
    initCodeView();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,10 +42,20 @@ func HTTP(ctx *context.Context) {
 | 
			
		|||
	} else if service == "git-upload-pack" ||
 | 
			
		||||
		strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
 | 
			
		||||
		isPull = true
 | 
			
		||||
	} else if service == "git-upload-archive" ||
 | 
			
		||||
		strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") {
 | 
			
		||||
		isPull = true
 | 
			
		||||
	} else {
 | 
			
		||||
		isPull = (ctx.Req.Method == "GET")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var accessMode models.AccessMode
 | 
			
		||||
	if isPull {
 | 
			
		||||
		accessMode = models.AccessModeRead
 | 
			
		||||
	} else {
 | 
			
		||||
		accessMode = models.AccessModeWrite
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isWiki := false
 | 
			
		||||
	if strings.HasSuffix(reponame, ".wiki") {
 | 
			
		||||
		isWiki = true
 | 
			
		||||
| 
						 | 
				
			
			@ -146,17 +156,12 @@ func HTTP(ctx *context.Context) {
 | 
			
		|||
			}
 | 
			
		||||
 | 
			
		||||
			if !isPublicPull {
 | 
			
		||||
				var tp = models.AccessModeWrite
 | 
			
		||||
				if isPull {
 | 
			
		||||
					tp = models.AccessModeRead
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				has, err := models.HasAccess(authUser, repo, tp)
 | 
			
		||||
				has, err := models.HasAccess(authUser, repo, accessMode)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
 | 
			
		||||
					return
 | 
			
		||||
				} else if !has {
 | 
			
		||||
					if tp == models.AccessModeRead {
 | 
			
		||||
					if accessMode == models.AccessModeRead {
 | 
			
		||||
						has, err = models.HasAccess(authUser, repo, models.AccessModeWrite)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							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{
 | 
			
		||||
		UploadPack:  true,
 | 
			
		||||
		ReceivePack: true,
 | 
			
		||||
		Params:      params,
 | 
			
		||||
		OnSucceed:   callback,
 | 
			
		||||
	})(ctx.Resp, ctx.Req.Request)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -244,6 +260,7 @@ func HTTP(ctx *context.Context) {
 | 
			
		|||
type serviceConfig struct {
 | 
			
		||||
	UploadPack  bool
 | 
			
		||||
	ReceivePack bool
 | 
			
		||||
	Params      map[string]string
 | 
			
		||||
	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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
	now := time.Now().Unix()
 | 
			
		||||
	expires := now + 31536000
 | 
			
		||||
| 
						 | 
				
			
			@ -358,6 +411,7 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		|||
		h.w.WriteHeader(http.StatusUnauthorized)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
| 
						 | 
				
			
			@ -365,6 +419,7 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		|||
		input      []byte
 | 
			
		||||
		br         io.Reader
 | 
			
		||||
		err        error
 | 
			
		||||
		branchName string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Handle GZIP.
 | 
			
		||||
| 
						 | 
				
			
			@ -385,11 +440,31 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		|||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		branchName = h.getBranch(input)
 | 
			
		||||
		br = bytes.NewReader(input)
 | 
			
		||||
	} else {
 | 
			
		||||
		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.Dir = h.dir
 | 
			
		||||
	cmd.Stdout = h.w
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ import (
 | 
			
		|||
const (
 | 
			
		||||
	tplSettingsOptions base.TplName = "repo/settings/options"
 | 
			
		||||
	tplCollaboration   base.TplName = "repo/settings/collaboration"
 | 
			
		||||
	tplBranches        base.TplName = "repo/settings/branches"
 | 
			
		||||
	tplGithooks        base.TplName = "repo/settings/githooks"
 | 
			
		||||
	tplGithookEdit     base.TplName = "repo/settings/githook_edit"
 | 
			
		||||
	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.
 | 
			
		||||
		repo.Name = 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.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) {
 | 
			
		||||
	owner, err := models.GetUserByName(ctx.Params(":username"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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">
 | 
			
		||||
			<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 .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>
 | 
			
		||||
			{{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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,11 @@
 | 
			
		|||
		<a class="{{if .PageIsSettingsCollaboration}}active{{end}} item" href="{{.RepoLink}}/settings/collaboration">
 | 
			
		||||
			{{.i18n.Tr "repo.settings.collaboration"}}
 | 
			
		||||
		</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">
 | 
			
		||||
			{{.i18n.Tr "repo.settings.hooks"}}
 | 
			
		||||
		</a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,30 +17,6 @@
 | 
			
		|||
							<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>
 | 
			
		||||
						</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}}
 | 
			
		||||
							<div class="inline field">
 | 
			
		||||
								<label>{{.i18n.Tr "repo.visibility"}}</label>
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +26,14 @@
 | 
			
		|||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						{{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">
 | 
			
		||||
							<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue