mirror of https://github.com/gogs/gogs.git
Initial version of protected branches (#776)
- Able to restrict force push and deletion - Able to restrict direct pushpull/4139/head^2
parent
dab768212a
commit
7e09d210ba
|
@ -1,4 +1,4 @@
|
|||
Gogs [](https://travis-ci.org/gogits/gogs) [](https://ci.appveyor.com/project/Unknwon/gogs/branch/master) [](https://crowdin.com/project/gogs) [](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
Gogs [](https://travis-ci.org/gogits/gogs) [](https://ci.appveyor.com/project/Unknwon/gogs/branch/master) [](https://crowdin.com/project/gogs) [](https://sourcegraph.com/github.com/gogits/gogs?badge) [](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
=====================
|
||||
|
||||

|
||||
|
@ -43,7 +43,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
|||
- Add/Remove repository collaborators
|
||||
- Repository/Organization webhooks (including Slack)
|
||||
- Repository Git hooks/deploy keys
|
||||
- Repository issues, pull requests and wiki
|
||||
- Repository issues, pull requests, wiki and protected branches
|
||||
- Migrate and mirror repository and its wiki
|
||||
- Web editor for repository files and wiki
|
||||
- Jupyter Notebook
|
||||
|
|
|
@ -24,7 +24,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
|||
- 支持添加和删除仓库协作者
|
||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
||||
- 支持仓库 Git 钩子和部署密钥
|
||||
- 支持仓库工单(Issue)、合并请求(Pull Request)以及 Wiki
|
||||
- 支持仓库工单(Issue)、合并请求(Pull Request)、Wiki 和保护分支
|
||||
- 支持迁移和镜像仓库以及它的 Wiki
|
||||
- 支持在线编辑仓库文件和 Wiki
|
||||
- 支持自定义源的 Gravatar 和 Federated Avatar
|
||||
|
|
48
cmd/hook.go
48
cmd/hook.go
|
@ -8,6 +8,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -64,13 +65,58 @@ func runHookPreReceive(c *cli.Context) error {
|
|||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
return nil
|
||||
}
|
||||
setup(c, "hooks/pre-receive.log", false)
|
||||
setup(c, "hooks/pre-receive.log", true)
|
||||
|
||||
isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
buf.Write(scanner.Bytes())
|
||||
buf.WriteByte('\n')
|
||||
|
||||
if isWiki {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
oldCommitID := string(fields[0])
|
||||
newCommitID := string(fields[1])
|
||||
branchName := strings.TrimPrefix(string(fields[2]), git.BRANCH_PREFIX)
|
||||
|
||||
// Branch protection
|
||||
repoID := com.StrTo(os.Getenv(http.ENV_REPO_ID)).MustInt64()
|
||||
protectBranch, err := models.GetProtectBranchOfRepoByName(repoID, branchName)
|
||||
if err != nil {
|
||||
if models.IsErrBranchNotExist(err) {
|
||||
continue
|
||||
}
|
||||
fail("Internal error", "GetProtectBranchOfRepoByName [repo_id: %d, branch: %s]: %v", repoID, branchName, err)
|
||||
}
|
||||
if !protectBranch.Protected {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if branch allows direct push
|
||||
if protectBranch.RequirePullRequest {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected and commits must be merged through pull request", branchName), "")
|
||||
}
|
||||
|
||||
// check and deletion
|
||||
if newCommitID == git.EMPTY_SHA {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "")
|
||||
}
|
||||
|
||||
// Check force push
|
||||
output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).Run()
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to detect force push: %v", err)
|
||||
} else if len(output) > 0 {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected from force push", branchName), "")
|
||||
}
|
||||
}
|
||||
|
||||
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "pre-receive")
|
||||
|
|
11
cmd/serv.go
11
cmd/serv.go
|
@ -175,7 +175,7 @@ func runServ(c *cli.Context) error {
|
|||
|
||||
// Prohibit push to mirror repositories.
|
||||
if requestMode > models.ACCESS_MODE_READ && repo.IsMirror {
|
||||
fail("mirror repository is read-only", "")
|
||||
fail("Mirror repository is read-only", "")
|
||||
}
|
||||
|
||||
// Allow anonymous (user is nil) clone for public repositories.
|
||||
|
@ -251,7 +251,14 @@ func runServ(c *cli.Context) error {
|
|||
gitCmd = exec.Command(verb, repoFullName)
|
||||
}
|
||||
if requestMode == models.ACCESS_MODE_WRITE {
|
||||
gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(repo.RepoPath(), owner.Name, owner.Salt, repo.Name, user)...)
|
||||
gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(http.ComposeHookEnvsOptions{
|
||||
AuthUser: user,
|
||||
OwnerName: owner.Name,
|
||||
OwnerSalt: owner.Salt,
|
||||
RepoID: repo.ID,
|
||||
RepoName: repo.Name,
|
||||
RepoPath: repo.RepoPath(),
|
||||
})...)
|
||||
}
|
||||
gitCmd.Dir = setting.RepoRootPath
|
||||
gitCmd.Stdout = os.Stdout
|
||||
|
|
29
cmd/web.go
29
cmd/web.go
|
@ -435,10 +435,21 @@ func runWeb(ctx *cli.Context) error {
|
|||
m.Combo("").Get(repo.Settings).
|
||||
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
||||
m.Group("/collaboration", func() {
|
||||
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
|
||||
m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
|
||||
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
|
||||
m.Post("/delete", repo.DeleteCollaboration)
|
||||
})
|
||||
m.Group("/branches", func() {
|
||||
m.Get("", repo.SettingsBranches)
|
||||
m.Post("/default_branch", repo.UpdateDefaultBranch)
|
||||
m.Combo("/*").Get(repo.SettingsProtectedBranch).
|
||||
Post(bindIgnErr(auth.ProtectBranchForm{}), repo.SettingsProtectedBranchPost)
|
||||
}, func(ctx *context.Context) {
|
||||
if ctx.Repo.Repository.IsMirror {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", repo.Webhooks)
|
||||
|
@ -452,15 +463,15 @@ func runWeb(ctx *cli.Context) error {
|
|||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
|
||||
m.Group("/git", func() {
|
||||
m.Get("", repo.GitHooks)
|
||||
m.Combo("/:name").Get(repo.GitHooksEdit).
|
||||
Post(repo.GitHooksEditPost)
|
||||
m.Get("", repo.SettingsGitHooks)
|
||||
m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
|
||||
Post(repo.SettingsGitHooksEditPost)
|
||||
}, context.GitHookService())
|
||||
})
|
||||
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").Get(repo.DeployKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
|
||||
m.Combo("").Get(repo.SettingsDeployKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.SettingsDeployKeysPost)
|
||||
m.Post("/delete", repo.DeleteDeployKey)
|
||||
})
|
||||
|
||||
|
@ -555,13 +566,13 @@ func runWeb(ctx *cli.Context) error {
|
|||
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
||||
}, func(ctx *context.Context) {
|
||||
if !setting.Repository.Upload.Enabled {
|
||||
ctx.Handle(404, "", nil)
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
})
|
||||
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
|
||||
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
|
||||
ctx.Handle(404, "", nil)
|
||||
if !ctx.Repo.CanEnableEditor() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
|
|
@ -639,6 +639,22 @@ settings.collaboration.admin = Admin
|
|||
settings.collaboration.write = Write
|
||||
settings.collaboration.read = Read
|
||||
settings.collaboration.undefined = Undefined
|
||||
settings.branches = Branches
|
||||
settings.default_branch = Default Branch
|
||||
settings.default_branch_desc = The default branch is considered the "base" branch for code commits, pull requests and online editing.
|
||||
settings.update = Update
|
||||
settings.update_default_branch_success = Default branch of this repository has been updated successfully!
|
||||
settings.protected_branches = Protected Branches
|
||||
settings.protected_branches_desc = Protect branches from force pushing, accidental deletion and whitelist code committers.
|
||||
settings.choose_a_branch = Choose a branch...
|
||||
settings.branch_protection = Branch Protection
|
||||
settings.branch_protection_desc = Please choose protect options for branch <b>%s</b>.
|
||||
settings.protect_this_branch = Protect this branch
|
||||
settings.protect_this_branch_desc = Disable force pushes and prevent from deletion.
|
||||
settings.protect_require_pull_request = Require pull request instead direct pushing
|
||||
settings.protect_require_pull_request_desc = Enable this option to disable direct pushing to this branch. Commits have to be pushed to another non-protected branch and merged to this branch through pull request.
|
||||
settings.protect_whitelist_committers = Whitelist who can push to this branch
|
||||
settings.protect_whitelist_committers_desc = Add people or teams to whitelist of direct push to this branch.
|
||||
settings.hooks = Webhooks
|
||||
settings.githooks = Git Hooks
|
||||
settings.basic_settings = Basic Settings
|
||||
|
|
2
gogs.go
2
gogs.go
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.9.153.0217"
|
||||
const APP_VER = "0.9.154.0217"
|
||||
|
||||
func init() {
|
||||
setting.AppVer = APP_VER
|
||||
|
|
|
@ -460,6 +460,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
|||
opType = ACTION_PUSH_TAG
|
||||
opts.Commits = &PushCommits{}
|
||||
} else {
|
||||
// TODO: detect branch deletion
|
||||
// if not the first commit, set the compare URL.
|
||||
if opts.OldCommitID == git.EMPTY_SHA {
|
||||
isNewBranch = true
|
||||
|
|
|
@ -65,8 +65,8 @@ func init() {
|
|||
new(Watch), new(Star), new(Follow), new(Action),
|
||||
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
||||
new(Label), new(IssueLabel), new(Milestone),
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
||||
new(HookTask),
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask),
|
||||
new(ProtectBranch),
|
||||
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
|
||||
new(Notice), new(EmailAddress))
|
||||
|
||||
|
|
|
@ -441,6 +441,10 @@ func (repo *Repository) AllowsPulls() bool {
|
|||
return repo.CanEnablePulls() && repo.EnablePulls
|
||||
}
|
||||
|
||||
func (repo *Repository) IsBranchRequirePullRequest(name string) bool {
|
||||
return IsBranchOfRepoRequirePullRequest(repo.ID, name)
|
||||
}
|
||||
|
||||
// CanEnableEditor returns true if repository meets the requirements of web editor.
|
||||
func (repo *Repository) CanEnableEditor() bool {
|
||||
return !repo.IsMirror
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogits/git-module"
|
||||
)
|
||||
|
||||
|
@ -36,7 +38,7 @@ func GetBranchesByPath(path string) ([]*Branch, error) {
|
|||
|
||||
func (repo *Repository) GetBranch(br string) (*Branch, error) {
|
||||
if !git.IsBranchExist(repo.RepoPath(), br) {
|
||||
return nil, &ErrBranchNotExist{br}
|
||||
return nil, ErrBranchNotExist{br}
|
||||
}
|
||||
return &Branch{
|
||||
Path: repo.RepoPath(),
|
||||
|
@ -55,3 +57,56 @@ func (br *Branch) GetCommit() (*git.Commit, error) {
|
|||
}
|
||||
return gitRepo.GetBranchCommit(br.Name)
|
||||
}
|
||||
|
||||
// ProtectBranch contains options of a protected branch.
|
||||
type ProtectBranch struct {
|
||||
ID int64
|
||||
RepoID int64 `xorm:"UNIQUE(protect_branch)"`
|
||||
Name string `xorm:"UNIQUE(protect_branch)"`
|
||||
Protected bool
|
||||
RequirePullRequest bool
|
||||
}
|
||||
|
||||
// GetProtectBranchOfRepoByName returns *ProtectBranch by branch name in given repostiory.
|
||||
func GetProtectBranchOfRepoByName(repoID int64, name string) (*ProtectBranch, error) {
|
||||
protectBranch := &ProtectBranch{
|
||||
RepoID: repoID,
|
||||
Name: name,
|
||||
}
|
||||
has, err := x.Get(protectBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrBranchNotExist{name}
|
||||
}
|
||||
return protectBranch, nil
|
||||
}
|
||||
|
||||
// IsBranchOfRepoRequirePullRequest returns true if branch requires pull request in given repository.
|
||||
func IsBranchOfRepoRequirePullRequest(repoID int64, name string) bool {
|
||||
protectBranch, err := GetProtectBranchOfRepoByName(repoID, name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return protectBranch.Protected && protectBranch.RequirePullRequest
|
||||
}
|
||||
|
||||
// UpdateProtectBranch saves branch protection options.
|
||||
// If ID is 0, it creates a new record. Otherwise, updates existing record.
|
||||
func UpdateProtectBranch(protectBranch *ProtectBranch) (err error) {
|
||||
if protectBranch.ID == 0 {
|
||||
if _, err = x.Insert(protectBranch); err != nil {
|
||||
return fmt.Errorf("Insert: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = x.Id(protectBranch.ID).AllCols().Update(protectBranch)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetProtectBranchesByRepoID returns a list of *ProtectBranch in given repostiory.
|
||||
func GetProtectBranchesByRepoID(repoID int64) ([]*ProtectBranch, error) {
|
||||
protectBranches := make([]*ProtectBranch, 0, 2)
|
||||
return protectBranches, x.Where("repo_id = ?", repoID).Asc("name").Find(&protectBranches)
|
||||
}
|
||||
|
|
|
@ -106,6 +106,22 @@ func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
|||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \____________ ____ ____ | |__
|
||||
// | | _/\_ __ \__ \ / \_/ ___\| | \
|
||||
// | | \ | | \// __ \| | \ \___| Y \
|
||||
// |______ / |__| (____ /___| /\___ >___| /
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type ProtectBranchForm struct {
|
||||
Protected bool
|
||||
RequirePullRequest bool
|
||||
}
|
||||
|
||||
func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ .__ __
|
||||
// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -73,7 +73,7 @@ func (r *Repository) HasAccess() bool {
|
|||
|
||||
// CanEnableEditor returns true if repository is editable and user has proper access level.
|
||||
func (r *Repository) CanEnableEditor() bool {
|
||||
return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter()
|
||||
return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter() && !r.Repository.IsBranchRequirePullRequest(r.BranchName)
|
||||
}
|
||||
|
||||
// GetEditorconfig returns the .editorconfig definition if found in the
|
||||
|
|
|
@ -1225,7 +1225,6 @@ footer .ui.language .menu {
|
|||
}
|
||||
.repository.file.list #file-buttons {
|
||||
font-weight: normal;
|
||||
margin-top: -3px;
|
||||
}
|
||||
.repository.file.list #file-buttons .ui.button {
|
||||
padding: 8px 10px;
|
||||
|
@ -2274,6 +2273,24 @@ footer .ui.language .menu {
|
|||
margin-left: 5px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
.repository.settings.branches .protected-branches .selection.dropdown {
|
||||
width: 300px;
|
||||
}
|
||||
.repository.settings.branches .protected-branches .item {
|
||||
border: 1px solid #eaeaea;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.repository.settings.branches .protected-branches .item:not(:last-child) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.repository.settings.branches .branch-protection .help {
|
||||
margin-left: 26px;
|
||||
padding-top: 0;
|
||||
}
|
||||
.repository.settings.branches .branch-protection .fields {
|
||||
margin-left: 20px;
|
||||
display: block;
|
||||
}
|
||||
.repository.settings.webhook .events .column {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -341,6 +341,18 @@ function initRepository() {
|
|||
});
|
||||
}
|
||||
|
||||
// Branches
|
||||
if ($('.repository.settings.branches').length > 0) {
|
||||
initFilterSearchDropdown('.protected-branches .dropdown');
|
||||
$('.enable-protection').change(function () {
|
||||
if (this.checked) {
|
||||
$($(this).data('target')).removeClass('disabled');
|
||||
} else {
|
||||
$($(this).data('target')).addClass('disabled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Labels
|
||||
if ($('.repository.labels').length > 0) {
|
||||
// Create label
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
}
|
||||
#file-buttons {
|
||||
font-weight: normal;
|
||||
margin-top: -3px;
|
||||
|
||||
.ui.button {
|
||||
padding: 8px 10px;
|
||||
font-weight: normal;
|
||||
|
@ -1303,6 +1303,32 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.branches {
|
||||
.protected-branches {
|
||||
.selection.dropdown {
|
||||
width: 300px;
|
||||
}
|
||||
.item {
|
||||
border: 1px solid #eaeaea;
|
||||
padding: 10px 15px;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.branch-protection {
|
||||
.help {
|
||||
margin-left: 26px;
|
||||
padding-top: 0;
|
||||
}
|
||||
.fields {
|
||||
margin-left: 20px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.webhook {
|
||||
.events {
|
||||
.column {
|
||||
|
|
|
@ -28,18 +28,20 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
ENV_AUTH_USER_ID = "AUTH_USER_ID"
|
||||
ENV_AUTH_USER_NAME = "AUTH_USER_NAME"
|
||||
ENV_REPO_OWNER_NAME = "REPO_OWNER_NAME"
|
||||
ENV_REPO_OWNER_SALT_MD5 = "REPO_OWNER_SALT_MD5"
|
||||
ENV_REPO_NAME = "REPO_NAME"
|
||||
ENV_REPO_CUSTOM_HOOKS_PATH = "REPO_CUSTOM_HOOKS_PATH"
|
||||
ENV_AUTH_USER_ID = "GOGS_AUTH_USER_ID"
|
||||
ENV_AUTH_USER_NAME = "GOGS_AUTH_USER_NAME"
|
||||
ENV_REPO_OWNER_NAME = "GOGS_REPO_OWNER_NAME"
|
||||
ENV_REPO_OWNER_SALT_MD5 = "GOGS_REPO_OWNER_SALT_MD5"
|
||||
ENV_REPO_ID = "GOGS_REPO_ID"
|
||||
ENV_REPO_NAME = "GOGS_REPO_NAME"
|
||||
ENV_REPO_CUSTOM_HOOKS_PATH = "GOGS_REPO_CUSTOM_HOOKS_PATH"
|
||||
)
|
||||
|
||||
type HTTPContext struct {
|
||||
*context.Context
|
||||
OwnerName string
|
||||
OwnerSalt string
|
||||
RepoID int64
|
||||
RepoName string
|
||||
AuthUser *models.User
|
||||
}
|
||||
|
@ -143,6 +145,7 @@ func HTTPContexter() macaron.Handler {
|
|||
Context: ctx,
|
||||
OwnerName: ownerName,
|
||||
OwnerSalt: owner.Salt,
|
||||
RepoID: repo.ID,
|
||||
RepoName: repoName,
|
||||
AuthUser: authUser,
|
||||
})
|
||||
|
@ -158,6 +161,7 @@ type serviceHandler struct {
|
|||
authUser *models.User
|
||||
ownerName string
|
||||
ownerSalt string
|
||||
repoID int64
|
||||
repoName string
|
||||
}
|
||||
|
||||
|
@ -189,15 +193,25 @@ func (h *serviceHandler) sendFile(contentType string) {
|
|||
http.ServeFile(h.w, h.r, reqFile)
|
||||
}
|
||||
|
||||
func ComposeHookEnvs(repoPath, ownerName, ownerSalt, repoName string, authUser *models.User) []string {
|
||||
type ComposeHookEnvsOptions struct {
|
||||
AuthUser *models.User
|
||||
OwnerName string
|
||||
OwnerSalt string
|
||||
RepoID int64
|
||||
RepoName string
|
||||
RepoPath string
|
||||
}
|
||||
|
||||
func ComposeHookEnvs(opts ComposeHookEnvsOptions) []string {
|
||||
envs := []string{
|
||||
"SSH_ORIGINAL_COMMAND=1",
|
||||
ENV_AUTH_USER_ID + "=" + com.ToStr(authUser.ID),
|
||||
ENV_AUTH_USER_NAME + "=" + authUser.Name,
|
||||
ENV_REPO_OWNER_NAME + "=" + ownerName,
|
||||
ENV_REPO_OWNER_SALT_MD5 + "=" + base.EncodeMD5(ownerSalt),
|
||||
ENV_REPO_NAME + "=" + repoName,
|
||||
ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(repoPath, "custom_hooks"),
|
||||
ENV_AUTH_USER_ID + "=" + com.ToStr(opts.AuthUser.ID),
|
||||
ENV_AUTH_USER_NAME + "=" + opts.AuthUser.Name,
|
||||
ENV_REPO_OWNER_NAME + "=" + opts.OwnerName,
|
||||
ENV_REPO_OWNER_SALT_MD5 + "=" + base.EncodeMD5(opts.OwnerSalt),
|
||||
ENV_REPO_ID + "=" + com.ToStr(opts.RepoID),
|
||||
ENV_REPO_NAME + "=" + opts.RepoName,
|
||||
ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(opts.RepoPath, "custom_hooks"),
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
@ -229,7 +243,14 @@ func serviceRPC(h serviceHandler, service string) {
|
|||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
|
||||
if service == "receive-pack" {
|
||||
cmd.Env = append(os.Environ(), ComposeHookEnvs(h.dir, h.ownerName, h.ownerSalt, h.repoName, h.authUser)...)
|
||||
cmd.Env = append(os.Environ(), ComposeHookEnvs(ComposeHookEnvsOptions{
|
||||
AuthUser: h.authUser,
|
||||
OwnerName: h.ownerName,
|
||||
OwnerSalt: h.ownerSalt,
|
||||
RepoID: h.repoID,
|
||||
RepoName: h.repoName,
|
||||
RepoPath: h.dir,
|
||||
})...)
|
||||
}
|
||||
cmd.Dir = h.dir
|
||||
cmd.Stdout = h.w
|
||||
|
@ -392,6 +413,7 @@ func HTTP(ctx *HTTPContext) {
|
|||
authUser: ctx.AuthUser,
|
||||
ownerName: ctx.OwnerName,
|
||||
ownerSalt: ctx.OwnerSalt,
|
||||
repoID: ctx.RepoID,
|
||||
repoName: ctx.RepoName,
|
||||
})
|
||||
return
|
||||
|
|
|
@ -711,6 +711,22 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
|
|||
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index))
|
||||
}
|
||||
|
||||
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
|
||||
owner, err := models.GetUserByName(ctx.Params(":username"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetUserByName", models.IsErrUserNotExist, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetRepositoryByName", models.IsErrRepoNotExist, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return owner, repo
|
||||
}
|
||||
|
||||
func TriggerTask(ctx *context.Context) {
|
||||
pusherID := ctx.QueryInt64("pusher")
|
||||
branch := ctx.Query("branch")
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -21,11 +22,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
SETTINGS_OPTIONS base.TplName = "repo/settings/options"
|
||||
COLLABORATION base.TplName = "repo/settings/collaboration"
|
||||
GITHOOKS base.TplName = "repo/settings/githooks"
|
||||
GITHOOK_EDIT base.TplName = "repo/settings/githook_edit"
|
||||
DEPLOY_KEYS base.TplName = "repo/settings/deploy_keys"
|
||||
SETTINGS_OPTIONS base.TplName = "repo/settings/options"
|
||||
SETTINGS_COLLABORATION base.TplName = "repo/settings/collaboration"
|
||||
SETTINGS_BRANCHES base.TplName = "repo/settings/branches"
|
||||
SETTINGS_PROTECTED_BRANCH base.TplName = "repo/settings/protected_branch"
|
||||
SETTINGS_GITHOOKS base.TplName = "repo/settings/githooks"
|
||||
SETTINGS_GITHOOK_EDIT base.TplName = "repo/settings/githook_edit"
|
||||
SETTINGS_DEPLOY_KEYS base.TplName = "repo/settings/deploy_keys"
|
||||
)
|
||||
|
||||
func Settings(ctx *context.Context) {
|
||||
|
@ -74,16 +77,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
|
|||
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
|
||||
|
||||
|
@ -295,7 +288,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
|
|||
}
|
||||
}
|
||||
|
||||
func Collaboration(ctx *context.Context) {
|
||||
func SettingsCollaboration(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings")
|
||||
ctx.Data["PageIsSettingsCollaboration"] = true
|
||||
|
||||
|
@ -306,10 +299,10 @@ func Collaboration(ctx *context.Context) {
|
|||
}
|
||||
ctx.Data["Collaborators"] = users
|
||||
|
||||
ctx.HTML(200, COLLABORATION)
|
||||
ctx.HTML(200, SETTINGS_COLLABORATION)
|
||||
}
|
||||
|
||||
func CollaborationPost(ctx *context.Context) {
|
||||
func SettingsCollaborationPost(ctx *context.Context) {
|
||||
name := strings.ToLower(ctx.Query("collaborator"))
|
||||
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||
ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path)
|
||||
|
@ -374,31 +367,102 @@ func DeleteCollaboration(ctx *context.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
|
||||
owner, err := models.GetUserByName(ctx.Params(":username"))
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
ctx.Handle(404, "GetUserByName", nil)
|
||||
} else {
|
||||
ctx.Handle(500, "GetUserByName", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func SettingsBranches(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
|
||||
ctx.Data["PageIsSettingsBranches"] = true
|
||||
|
||||
repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
|
||||
protectBranches, err := models.GetProtectBranchesByRepoID(ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
ctx.Handle(404, "GetRepositoryByName", nil)
|
||||
} else {
|
||||
ctx.Handle(500, "GetRepositoryByName", err)
|
||||
}
|
||||
return nil, nil
|
||||
ctx.Handle(500, "GetProtectBranchesByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["ProtectBranches"] = protectBranches
|
||||
|
||||
return owner, repo
|
||||
ctx.HTML(200, SETTINGS_BRANCHES)
|
||||
}
|
||||
|
||||
func GitHooks(ctx *context.Context) {
|
||||
func UpdateDefaultBranch(ctx *context.Context) {
|
||||
branch := ctx.Query("branch")
|
||||
if ctx.Repo.GitRepo.IsBranchExist(branch) &&
|
||||
ctx.Repo.Repository.DefaultBranch != branch {
|
||||
ctx.Repo.Repository.DefaultBranch = branch
|
||||
if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
|
||||
if !git.IsErrUnsupportedVersion(err) {
|
||||
ctx.Handle(500, "SetDefaultBranch", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := models.UpdateRepository(ctx.Repo.Repository, false); err != nil {
|
||||
ctx.Handle(500, "UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_default_branch_success"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/branches")
|
||||
}
|
||||
|
||||
func SettingsProtectedBranch(ctx *context.Context) {
|
||||
branch := ctx.Params("*")
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.protected_branches") + " - " + branch
|
||||
ctx.Data["PageIsSettingsBranches"] = true
|
||||
ctx.Data["IsOrgRepo"] = ctx.Repo.Owner.IsOrganization()
|
||||
|
||||
protectBranch, err := models.GetProtectBranchOfRepoByName(ctx.Repo.Repository.ID, branch)
|
||||
if err != nil {
|
||||
if !models.IsErrBranchNotExist(err) {
|
||||
ctx.Handle(500, "GetProtectBranchOfRepoByName", err)
|
||||
return
|
||||
}
|
||||
|
||||
// No options found, create defaults.
|
||||
protectBranch = &models.ProtectBranch{
|
||||
Name: branch,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Branch"] = protectBranch
|
||||
ctx.HTML(200, SETTINGS_PROTECTED_BRANCH)
|
||||
}
|
||||
|
||||
func SettingsProtectedBranchPost(ctx *context.Context, form auth.ProtectBranchForm) {
|
||||
branch := ctx.Params("*")
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
protectBranch, err := models.GetProtectBranchOfRepoByName(ctx.Repo.Repository.ID, branch)
|
||||
if err != nil {
|
||||
if !models.IsErrBranchNotExist(err) {
|
||||
ctx.Handle(500, "GetProtectBranchOfRepoByName", err)
|
||||
return
|
||||
}
|
||||
|
||||
// No options found, create defaults.
|
||||
protectBranch = &models.ProtectBranch{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: branch,
|
||||
}
|
||||
}
|
||||
|
||||
protectBranch.Protected = form.Protected
|
||||
protectBranch.RequirePullRequest = form.RequirePullRequest
|
||||
if err = models.UpdateProtectBranch(protectBranch); err != nil {
|
||||
ctx.Handle(500, "UpdateProtectBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, branch))
|
||||
}
|
||||
|
||||
func SettingsGitHooks(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
|
||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
|
@ -409,10 +473,10 @@ func GitHooks(ctx *context.Context) {
|
|||
}
|
||||
ctx.Data["Hooks"] = hooks
|
||||
|
||||
ctx.HTML(200, GITHOOKS)
|
||||
ctx.HTML(200, SETTINGS_GITHOOKS)
|
||||
}
|
||||
|
||||
func GitHooksEdit(ctx *context.Context) {
|
||||
func SettingsGitHooksEdit(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
|
||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
|
@ -427,10 +491,10 @@ func GitHooksEdit(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
ctx.Data["Hook"] = hook
|
||||
ctx.HTML(200, GITHOOK_EDIT)
|
||||
ctx.HTML(200, SETTINGS_GITHOOK_EDIT)
|
||||
}
|
||||
|
||||
func GitHooksEditPost(ctx *context.Context) {
|
||||
func SettingsGitHooksEditPost(ctx *context.Context) {
|
||||
name := ctx.Params(":name")
|
||||
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
|
@ -449,7 +513,7 @@ func GitHooksEditPost(ctx *context.Context) {
|
|||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
|
||||
}
|
||||
|
||||
func DeployKeys(ctx *context.Context) {
|
||||
func SettingsDeployKeys(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
|
||||
|
@ -460,10 +524,10 @@ func DeployKeys(ctx *context.Context) {
|
|||
}
|
||||
ctx.Data["Deploykeys"] = keys
|
||||
|
||||
ctx.HTML(200, DEPLOY_KEYS)
|
||||
ctx.HTML(200, SETTINGS_DEPLOY_KEYS)
|
||||
}
|
||||
|
||||
func DeployKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
|
||||
func SettingsDeployKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
|
||||
ctx.Data["PageIsSettingsKeys"] = true
|
||||
|
||||
|
@ -475,7 +539,7 @@ func DeployKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
|
|||
ctx.Data["Deploykeys"] = keys
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, DEPLOY_KEYS)
|
||||
ctx.HTML(200, SETTINGS_DEPLOY_KEYS)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -498,10 +562,10 @@ func DeployKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
|
|||
switch {
|
||||
case models.IsErrKeyAlreadyExist(err):
|
||||
ctx.Data["Err_Content"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), DEPLOY_KEYS, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), SETTINGS_DEPLOY_KEYS, &form)
|
||||
case models.IsErrKeyNameAlreadyUsed(err):
|
||||
ctx.Data["Err_Title"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), DEPLOY_KEYS, &form)
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), SETTINGS_DEPLOY_KEYS, &form)
|
||||
default:
|
||||
ctx.Handle(500, "AddDeployKey", err)
|
||||
}
|
||||
|
|
|
@ -108,8 +108,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
|||
ctx.Data["LatestCommit"] = latestCommit
|
||||
ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
|
||||
|
||||
// Check permission to add or upload new file.
|
||||
if ctx.Repo.IsWriter() && ctx.Repo.IsViewBranch {
|
||||
if ctx.Repo.CanEnableEditor() {
|
||||
ctx.Data["CanAddFile"] = true
|
||||
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled
|
||||
}
|
||||
|
@ -142,6 +141,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
|||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
|
||||
}
|
||||
|
||||
canEnableEditor := ctx.Repo.CanEnableEditor()
|
||||
switch {
|
||||
case isTextFile:
|
||||
if blob.Size() >= setting.UI.MaxDisplayFileSize {
|
||||
|
@ -186,7 +186,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
|||
ctx.Data["LineNums"] = gotemplate.HTML(output.String())
|
||||
}
|
||||
|
||||
if ctx.Repo.CanEnableEditor() {
|
||||
if canEnableEditor {
|
||||
ctx.Data["CanEditFile"] = true
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
|
@ -203,7 +203,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
|||
ctx.Data["IsImageFile"] = true
|
||||
}
|
||||
|
||||
if ctx.Repo.CanEnableEditor() {
|
||||
if canEnableEditor {
|
||||
ctx.Data["CanDeleteFile"] = true
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
|
||||
} else if !ctx.Repo.IsViewBranch {
|
||||
|
@ -216,7 +216,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
|||
func setEditorconfigIfExists(ctx *context.Context) {
|
||||
ec, err := ctx.Repo.GetEditorconfig()
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
log.Error(4, "Fail to get '.editorconfig' [%d]: %v", ctx.Repo.Repository.ID, err)
|
||||
log.Trace("setEditorconfigIfExists.GetEditorconfig [%d]: %v", ctx.Repo.Repository.ID, err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Editorconfig"] = ec
|
||||
|
@ -228,6 +228,9 @@ func Home(ctx *context.Context) {
|
|||
title += ": " + ctx.Repo.Repository.Description
|
||||
}
|
||||
ctx.Data["Title"] = title
|
||||
if ctx.Repo.BranchName != ctx.Repo.Repository.DefaultBranch {
|
||||
ctx.Data["Title"] = title + " @ " + ctx.Repo.BranchName
|
||||
}
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["RequireHighlightJS"] = true
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.9.153.0217
|
||||
0.9.154.0217
|
|
@ -0,0 +1,62 @@
|
|||
{{template "base/head" .}}
|
||||
<div class="repository settings branches">
|
||||
{{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.settings.default_branch"}}
|
||||
</h4>
|
||||
<div class="ui attached segment default-branch">
|
||||
<p>{{.i18n.Tr "repo.settings.default_branch_desc"}}</p>
|
||||
<form class="ui form" action="{{.Link}}/default_branch" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required inline field {{if .Repository.IsBare}}disabled{{end}}">
|
||||
<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>
|
||||
<button class="ui green button">{{$.i18n.Tr "repo.settings.update"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "repo.settings.protected_branches"}}
|
||||
</h4>
|
||||
<div class="ui attached segment protected-branches">
|
||||
<p>{{.i18n.Tr "repo.settings.protected_branches_desc"}}</p>
|
||||
<div class="ui form">
|
||||
<div class="required inline field {{if .Repository.IsBare}}disabled{{end}}">
|
||||
<div class="ui selection dropdown">
|
||||
<div class="text">{{.i18n.Tr "repo.settings.choose_a_branch"}}</div>
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-url="{{$.Link}}/{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui protected-branches list">
|
||||
{{range .ProtectBranches}}
|
||||
<div class="item">
|
||||
<a href="{{$.Link}}/{{.Name}}"><code>{{.Name}}</code></a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
|
@ -7,6 +7,11 @@
|
|||
<a class="{{if .PageIsSettingsCollaboration}}active{{end}} item" href="{{.RepoLink}}/settings/collaboration">
|
||||
{{.i18n.Tr "repo.settings.collaboration"}}
|
||||
</a>
|
||||
{{if not .Repository.IsMirror}}
|
||||
<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>
|
||||
|
|
|
@ -26,21 +26,6 @@
|
|||
<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>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
{{template "base/head" .}}
|
||||
<div class="repository settings branches">
|
||||
{{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.settings.branch_protection"}}
|
||||
</h4>
|
||||
<div class="ui attached segment branch-protection">
|
||||
<p>{{.i18n.Tr "repo.settings.branch_protection_desc" .Branch.Name | Str2html}}</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<input class="enable-protection" name="protected" type="checkbox" data-target="#protection_box" {{if .Branch.Protected}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.settings.protect_this_branch"}}</label>
|
||||
<p class="help">{{.i18n.Tr "repo.settings.protect_this_branch_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="protection_box" class="fields {{if not .Branch.Protected}}disabled{{end}}">
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="require_pull_request" type="checkbox" {{if .Branch.RequirePullRequest}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.settings.protect_require_pull_request"}}</label>
|
||||
<p class="help">{{.i18n.Tr "repo.settings.protect_require_pull_request_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{if .IsOrgRepo}}
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="whitelist_committers" type="checkbox" data-target="#whitelist_box">
|
||||
<label>{{.i18n.Tr "repo.settings.protect_whitelist_committers"}}</label>
|
||||
<p class="help">{{.i18n.Tr "repo.settings.protect_whitelist_committers_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="field">
|
||||
<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
Loading…
Reference in New Issue