mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00: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:
		 Denis Denisov
					Denis Denisov
				
			
				
					committed by
					
						 Lunny Xiao
						Lunny Xiao
					
				
			
			
				
	
			
			
			 Lunny Xiao
						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,6 +411,7 @@ 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 ( | ||||||
| @@ -365,6 +419,7 @@ func serviceRPC(h serviceHandler, service string) { | |||||||
| 		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