mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-25 07:12:44 +01: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:
parent
fe5ff8e4b2
commit
fd941db246
17 changed files with 606 additions and 49 deletions
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue