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
|
- Add/Remove repository collaborators
|
||||||
- Repository/Organization webhooks (including Slack)
|
- Repository/Organization webhooks (including Slack)
|
||||||
- Repository Git hooks/deploy keys
|
- 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
|
- Migrate and mirror repository and its wiki
|
||||||
- Web editor for repository files and wiki
|
- Web editor for repository files and wiki
|
||||||
- Jupyter Notebook
|
- Jupyter Notebook
|
||||||
|
|
|
@ -24,7 +24,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||||
- 支持添加和删除仓库协作者
|
- 支持添加和删除仓库协作者
|
||||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
||||||
- 支持仓库 Git 钩子和部署密钥
|
- 支持仓库 Git 钩子和部署密钥
|
||||||
- 支持仓库工单(Issue)、合并请求(Pull Request)以及 Wiki
|
- 支持仓库工单(Issue)、合并请求(Pull Request)、Wiki 和保护分支
|
||||||
- 支持迁移和镜像仓库以及它的 Wiki
|
- 支持迁移和镜像仓库以及它的 Wiki
|
||||||
- 支持在线编辑仓库文件和 Wiki
|
- 支持在线编辑仓库文件和 Wiki
|
||||||
- 支持自定义源的 Gravatar 和 Federated Avatar
|
- 支持自定义源的 Gravatar 和 Federated Avatar
|
||||||
|
|
48
cmd/hook.go
48
cmd/hook.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -64,13 +65,58 @@ func runHookPreReceive(c *cli.Context) error {
|
||||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||||
return nil
|
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)
|
buf := bytes.NewBuffer(nil)
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
buf.Write(scanner.Bytes())
|
buf.Write(scanner.Bytes())
|
||||||
buf.WriteByte('\n')
|
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")
|
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.
|
// Prohibit push to mirror repositories.
|
||||||
if requestMode > models.ACCESS_MODE_READ && repo.IsMirror {
|
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.
|
// Allow anonymous (user is nil) clone for public repositories.
|
||||||
|
@ -251,7 +251,14 @@ func runServ(c *cli.Context) error {
|
||||||
gitCmd = exec.Command(verb, repoFullName)
|
gitCmd = exec.Command(verb, repoFullName)
|
||||||
}
|
}
|
||||||
if requestMode == models.ACCESS_MODE_WRITE {
|
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.Dir = setting.RepoRootPath
|
||||||
gitCmd.Stdout = os.Stdout
|
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).
|
m.Combo("").Get(repo.Settings).
|
||||||
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
||||||
m.Group("/collaboration", func() {
|
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("/access_mode", repo.ChangeCollaborationAccessMode)
|
||||||
m.Post("/delete", repo.DeleteCollaboration)
|
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.Group("/hooks", func() {
|
||||||
m.Get("", repo.Webhooks)
|
m.Get("", repo.Webhooks)
|
||||||
|
@ -452,15 +463,15 @@ func runWeb(ctx *cli.Context) error {
|
||||||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||||
|
|
||||||
m.Group("/git", func() {
|
m.Group("/git", func() {
|
||||||
m.Get("", repo.GitHooks)
|
m.Get("", repo.SettingsGitHooks)
|
||||||
m.Combo("/:name").Get(repo.GitHooksEdit).
|
m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
|
||||||
Post(repo.GitHooksEditPost)
|
Post(repo.SettingsGitHooksEditPost)
|
||||||
}, context.GitHookService())
|
}, context.GitHookService())
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Group("/keys", func() {
|
m.Group("/keys", func() {
|
||||||
m.Combo("").Get(repo.DeployKeys).
|
m.Combo("").Get(repo.SettingsDeployKeys).
|
||||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
|
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.SettingsDeployKeysPost)
|
||||||
m.Post("/delete", repo.DeleteDeployKey)
|
m.Post("/delete", repo.DeleteDeployKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -555,13 +566,13 @@ func runWeb(ctx *cli.Context) error {
|
||||||
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
||||||
}, func(ctx *context.Context) {
|
}, func(ctx *context.Context) {
|
||||||
if !setting.Repository.Upload.Enabled {
|
if !setting.Repository.Upload.Enabled {
|
||||||
ctx.Handle(404, "", nil)
|
ctx.NotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
|
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
|
||||||
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
|
if !ctx.Repo.CanEnableEditor() {
|
||||||
ctx.Handle(404, "", nil)
|
ctx.NotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -639,6 +639,22 @@ settings.collaboration.admin = Admin
|
||||||
settings.collaboration.write = Write
|
settings.collaboration.write = Write
|
||||||
settings.collaboration.read = Read
|
settings.collaboration.read = Read
|
||||||
settings.collaboration.undefined = Undefined
|
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.hooks = Webhooks
|
||||||
settings.githooks = Git Hooks
|
settings.githooks = Git Hooks
|
||||||
settings.basic_settings = Basic Settings
|
settings.basic_settings = Basic Settings
|
||||||
|
|
2
gogs.go
2
gogs.go
|
@ -16,7 +16,7 @@ import (
|
||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APP_VER = "0.9.153.0217"
|
const APP_VER = "0.9.154.0217"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
setting.AppVer = APP_VER
|
setting.AppVer = APP_VER
|
||||||
|
|
|
@ -460,6 +460,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||||
opType = ACTION_PUSH_TAG
|
opType = ACTION_PUSH_TAG
|
||||||
opts.Commits = &PushCommits{}
|
opts.Commits = &PushCommits{}
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: detect branch deletion
|
||||||
// if not the first commit, set the compare URL.
|
// if not the first commit, set the compare URL.
|
||||||
if opts.OldCommitID == git.EMPTY_SHA {
|
if opts.OldCommitID == git.EMPTY_SHA {
|
||||||
isNewBranch = true
|
isNewBranch = true
|
||||||
|
|
|
@ -65,8 +65,8 @@ func init() {
|
||||||
new(Watch), new(Star), new(Follow), new(Action),
|
new(Watch), new(Star), new(Follow), new(Action),
|
||||||
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
||||||
new(Label), new(IssueLabel), new(Milestone),
|
new(Label), new(IssueLabel), new(Milestone),
|
||||||
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask),
|
||||||
new(HookTask),
|
new(ProtectBranch),
|
||||||
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
|
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
|
||||||
new(Notice), new(EmailAddress))
|
new(Notice), new(EmailAddress))
|
||||||
|
|
||||||
|
|
|
@ -441,6 +441,10 @@ func (repo *Repository) AllowsPulls() bool {
|
||||||
return repo.CanEnablePulls() && repo.EnablePulls
|
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.
|
// CanEnableEditor returns true if repository meets the requirements of web editor.
|
||||||
func (repo *Repository) CanEnableEditor() bool {
|
func (repo *Repository) CanEnableEditor() bool {
|
||||||
return !repo.IsMirror
|
return !repo.IsMirror
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gogits/git-module"
|
"github.com/gogits/git-module"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,7 +38,7 @@ func GetBranchesByPath(path string) ([]*Branch, error) {
|
||||||
|
|
||||||
func (repo *Repository) GetBranch(br string) (*Branch, error) {
|
func (repo *Repository) GetBranch(br string) (*Branch, error) {
|
||||||
if !git.IsBranchExist(repo.RepoPath(), br) {
|
if !git.IsBranchExist(repo.RepoPath(), br) {
|
||||||
return nil, &ErrBranchNotExist{br}
|
return nil, ErrBranchNotExist{br}
|
||||||
}
|
}
|
||||||
return &Branch{
|
return &Branch{
|
||||||
Path: repo.RepoPath(),
|
Path: repo.RepoPath(),
|
||||||
|
@ -55,3 +57,56 @@ func (br *Branch) GetCommit() (*git.Commit, error) {
|
||||||
}
|
}
|
||||||
return gitRepo.GetBranchCommit(br.Name)
|
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)
|
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.
|
// CanEnableEditor returns true if repository is editable and user has proper access level.
|
||||||
func (r *Repository) CanEnableEditor() bool {
|
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
|
// GetEditorconfig returns the .editorconfig definition if found in the
|
||||||
|
|
|
@ -1225,7 +1225,6 @@ footer .ui.language .menu {
|
||||||
}
|
}
|
||||||
.repository.file.list #file-buttons {
|
.repository.file.list #file-buttons {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-top: -3px;
|
|
||||||
}
|
}
|
||||||
.repository.file.list #file-buttons .ui.button {
|
.repository.file.list #file-buttons .ui.button {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
|
@ -2274,6 +2273,24 @@ footer .ui.language .menu {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: -3px;
|
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 {
|
.repository.settings.webhook .events .column {
|
||||||
padding-bottom: 0;
|
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
|
// Labels
|
||||||
if ($('.repository.labels').length > 0) {
|
if ($('.repository.labels').length > 0) {
|
||||||
// Create label
|
// Create label
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
}
|
}
|
||||||
#file-buttons {
|
#file-buttons {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-top: -3px;
|
|
||||||
.ui.button {
|
.ui.button {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
font-weight: normal;
|
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 {
|
&.webhook {
|
||||||
.events {
|
.events {
|
||||||
.column {
|
.column {
|
||||||
|
|
|
@ -28,18 +28,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ENV_AUTH_USER_ID = "AUTH_USER_ID"
|
ENV_AUTH_USER_ID = "GOGS_AUTH_USER_ID"
|
||||||
ENV_AUTH_USER_NAME = "AUTH_USER_NAME"
|
ENV_AUTH_USER_NAME = "GOGS_AUTH_USER_NAME"
|
||||||
ENV_REPO_OWNER_NAME = "REPO_OWNER_NAME"
|
ENV_REPO_OWNER_NAME = "GOGS_REPO_OWNER_NAME"
|
||||||
ENV_REPO_OWNER_SALT_MD5 = "REPO_OWNER_SALT_MD5"
|
ENV_REPO_OWNER_SALT_MD5 = "GOGS_REPO_OWNER_SALT_MD5"
|
||||||
ENV_REPO_NAME = "REPO_NAME"
|
ENV_REPO_ID = "GOGS_REPO_ID"
|
||||||
ENV_REPO_CUSTOM_HOOKS_PATH = "REPO_CUSTOM_HOOKS_PATH"
|
ENV_REPO_NAME = "GOGS_REPO_NAME"
|
||||||
|
ENV_REPO_CUSTOM_HOOKS_PATH = "GOGS_REPO_CUSTOM_HOOKS_PATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPContext struct {
|
type HTTPContext struct {
|
||||||
*context.Context
|
*context.Context
|
||||||
OwnerName string
|
OwnerName string
|
||||||
OwnerSalt string
|
OwnerSalt string
|
||||||
|
RepoID int64
|
||||||
RepoName string
|
RepoName string
|
||||||
AuthUser *models.User
|
AuthUser *models.User
|
||||||
}
|
}
|
||||||
|
@ -143,6 +145,7 @@ func HTTPContexter() macaron.Handler {
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
OwnerName: ownerName,
|
OwnerName: ownerName,
|
||||||
OwnerSalt: owner.Salt,
|
OwnerSalt: owner.Salt,
|
||||||
|
RepoID: repo.ID,
|
||||||
RepoName: repoName,
|
RepoName: repoName,
|
||||||
AuthUser: authUser,
|
AuthUser: authUser,
|
||||||
})
|
})
|
||||||
|
@ -158,6 +161,7 @@ type serviceHandler struct {
|
||||||
authUser *models.User
|
authUser *models.User
|
||||||
ownerName string
|
ownerName string
|
||||||
ownerSalt string
|
ownerSalt string
|
||||||
|
repoID int64
|
||||||
repoName string
|
repoName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,15 +193,25 @@ func (h *serviceHandler) sendFile(contentType string) {
|
||||||
http.ServeFile(h.w, h.r, reqFile)
|
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{
|
envs := []string{
|
||||||
"SSH_ORIGINAL_COMMAND=1",
|
"SSH_ORIGINAL_COMMAND=1",
|
||||||
ENV_AUTH_USER_ID + "=" + com.ToStr(authUser.ID),
|
ENV_AUTH_USER_ID + "=" + com.ToStr(opts.AuthUser.ID),
|
||||||
ENV_AUTH_USER_NAME + "=" + authUser.Name,
|
ENV_AUTH_USER_NAME + "=" + opts.AuthUser.Name,
|
||||||
ENV_REPO_OWNER_NAME + "=" + ownerName,
|
ENV_REPO_OWNER_NAME + "=" + opts.OwnerName,
|
||||||
ENV_REPO_OWNER_SALT_MD5 + "=" + base.EncodeMD5(ownerSalt),
|
ENV_REPO_OWNER_SALT_MD5 + "=" + base.EncodeMD5(opts.OwnerSalt),
|
||||||
ENV_REPO_NAME + "=" + repoName,
|
ENV_REPO_ID + "=" + com.ToStr(opts.RepoID),
|
||||||
ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(repoPath, "custom_hooks"),
|
ENV_REPO_NAME + "=" + opts.RepoName,
|
||||||
|
ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(opts.RepoPath, "custom_hooks"),
|
||||||
}
|
}
|
||||||
return envs
|
return envs
|
||||||
}
|
}
|
||||||
|
@ -229,7 +243,14 @@ func serviceRPC(h serviceHandler, service string) {
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
|
cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
|
||||||
if service == "receive-pack" {
|
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.Dir = h.dir
|
||||||
cmd.Stdout = h.w
|
cmd.Stdout = h.w
|
||||||
|
@ -392,6 +413,7 @@ func HTTP(ctx *HTTPContext) {
|
||||||
authUser: ctx.AuthUser,
|
authUser: ctx.AuthUser,
|
||||||
ownerName: ctx.OwnerName,
|
ownerName: ctx.OwnerName,
|
||||||
ownerSalt: ctx.OwnerSalt,
|
ownerSalt: ctx.OwnerSalt,
|
||||||
|
repoID: ctx.RepoID,
|
||||||
repoName: ctx.RepoName,
|
repoName: ctx.RepoName,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
|
@ -711,6 +711,22 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index))
|
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) {
|
func TriggerTask(ctx *context.Context) {
|
||||||
pusherID := ctx.QueryInt64("pusher")
|
pusherID := ctx.QueryInt64("pusher")
|
||||||
branch := ctx.Query("branch")
|
branch := ctx.Query("branch")
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -22,10 +23,12 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SETTINGS_OPTIONS base.TplName = "repo/settings/options"
|
SETTINGS_OPTIONS base.TplName = "repo/settings/options"
|
||||||
COLLABORATION base.TplName = "repo/settings/collaboration"
|
SETTINGS_COLLABORATION base.TplName = "repo/settings/collaboration"
|
||||||
GITHOOKS base.TplName = "repo/settings/githooks"
|
SETTINGS_BRANCHES base.TplName = "repo/settings/branches"
|
||||||
GITHOOK_EDIT base.TplName = "repo/settings/githook_edit"
|
SETTINGS_PROTECTED_BRANCH base.TplName = "repo/settings/protected_branch"
|
||||||
DEPLOY_KEYS base.TplName = "repo/settings/deploy_keys"
|
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) {
|
func Settings(ctx *context.Context) {
|
||||||
|
@ -74,16 +77,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
|
||||||
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
|
||||||
|
|
||||||
|
@ -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["Title"] = ctx.Tr("repo.settings")
|
||||||
ctx.Data["PageIsSettingsCollaboration"] = true
|
ctx.Data["PageIsSettingsCollaboration"] = true
|
||||||
|
|
||||||
|
@ -306,10 +299,10 @@ func Collaboration(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["Collaborators"] = users
|
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"))
|
name := strings.ToLower(ctx.Query("collaborator"))
|
||||||
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||||
ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path)
|
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) {
|
func SettingsBranches(ctx *context.Context) {
|
||||||
owner, err := models.GetUserByName(ctx.Params(":username"))
|
ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
|
||||||
if err != nil {
|
ctx.Data["PageIsSettingsBranches"] = true
|
||||||
if models.IsErrUserNotExist(err) {
|
|
||||||
ctx.Handle(404, "GetUserByName", nil)
|
|
||||||
} else {
|
|
||||||
ctx.Handle(500, "GetUserByName", err)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
|
protectBranches, err := models.GetProtectBranchesByRepoID(ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if models.IsErrRepoNotExist(err) {
|
ctx.Handle(500, "GetProtectBranchesByRepoID", err)
|
||||||
ctx.Handle(404, "GetRepositoryByName", nil)
|
return
|
||||||
} else {
|
|
||||||
ctx.Handle(500, "GetRepositoryByName", err)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
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["Title"] = ctx.Tr("repo.settings.githooks")
|
||||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||||
|
|
||||||
|
@ -409,10 +473,10 @@ func GitHooks(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["Hooks"] = hooks
|
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["Title"] = ctx.Tr("repo.settings.githooks")
|
||||||
ctx.Data["PageIsSettingsGitHooks"] = true
|
ctx.Data["PageIsSettingsGitHooks"] = true
|
||||||
|
|
||||||
|
@ -427,10 +491,10 @@ func GitHooksEdit(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["Hook"] = hook
|
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")
|
name := ctx.Params(":name")
|
||||||
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
hook, err := ctx.Repo.GitRepo.GetHook(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -449,7 +513,7 @@ func GitHooksEditPost(ctx *context.Context) {
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
|
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["Title"] = ctx.Tr("repo.settings.deploy_keys")
|
||||||
ctx.Data["PageIsSettingsKeys"] = true
|
ctx.Data["PageIsSettingsKeys"] = true
|
||||||
|
|
||||||
|
@ -460,10 +524,10 @@ func DeployKeys(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["Deploykeys"] = keys
|
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["Title"] = ctx.Tr("repo.settings.deploy_keys")
|
||||||
ctx.Data["PageIsSettingsKeys"] = true
|
ctx.Data["PageIsSettingsKeys"] = true
|
||||||
|
|
||||||
|
@ -475,7 +539,7 @@ func DeployKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
|
||||||
ctx.Data["Deploykeys"] = keys
|
ctx.Data["Deploykeys"] = keys
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
ctx.HTML(200, DEPLOY_KEYS)
|
ctx.HTML(200, SETTINGS_DEPLOY_KEYS)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,10 +562,10 @@ func DeployKeysPost(ctx *context.Context, form auth.AddSSHKeyForm) {
|
||||||
switch {
|
switch {
|
||||||
case models.IsErrKeyAlreadyExist(err):
|
case models.IsErrKeyAlreadyExist(err):
|
||||||
ctx.Data["Err_Content"] = true
|
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):
|
case models.IsErrKeyNameAlreadyUsed(err):
|
||||||
ctx.Data["Err_Title"] = true
|
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:
|
default:
|
||||||
ctx.Handle(500, "AddDeployKey", err)
|
ctx.Handle(500, "AddDeployKey", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,8 +108,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||||
ctx.Data["LatestCommit"] = latestCommit
|
ctx.Data["LatestCommit"] = latestCommit
|
||||||
ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
|
ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
|
||||||
|
|
||||||
// Check permission to add or upload new file.
|
if ctx.Repo.CanEnableEditor() {
|
||||||
if ctx.Repo.IsWriter() && ctx.Repo.IsViewBranch {
|
|
||||||
ctx.Data["CanAddFile"] = true
|
ctx.Data["CanAddFile"] = true
|
||||||
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled
|
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")
|
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canEnableEditor := ctx.Repo.CanEnableEditor()
|
||||||
switch {
|
switch {
|
||||||
case isTextFile:
|
case isTextFile:
|
||||||
if blob.Size() >= setting.UI.MaxDisplayFileSize {
|
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())
|
ctx.Data["LineNums"] = gotemplate.HTML(output.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Repo.CanEnableEditor() {
|
if canEnableEditor {
|
||||||
ctx.Data["CanEditFile"] = true
|
ctx.Data["CanEditFile"] = true
|
||||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
|
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
|
||||||
} else if !ctx.Repo.IsViewBranch {
|
} else if !ctx.Repo.IsViewBranch {
|
||||||
|
@ -203,7 +203,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
||||||
ctx.Data["IsImageFile"] = true
|
ctx.Data["IsImageFile"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Repo.CanEnableEditor() {
|
if canEnableEditor {
|
||||||
ctx.Data["CanDeleteFile"] = true
|
ctx.Data["CanDeleteFile"] = true
|
||||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
|
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
|
||||||
} else if !ctx.Repo.IsViewBranch {
|
} 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) {
|
func setEditorconfigIfExists(ctx *context.Context) {
|
||||||
ec, err := ctx.Repo.GetEditorconfig()
|
ec, err := ctx.Repo.GetEditorconfig()
|
||||||
if err != nil && !git.IsErrNotExist(err) {
|
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
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["Editorconfig"] = ec
|
ctx.Data["Editorconfig"] = ec
|
||||||
|
@ -228,6 +228,9 @@ func Home(ctx *context.Context) {
|
||||||
title += ": " + ctx.Repo.Repository.Description
|
title += ": " + ctx.Repo.Repository.Description
|
||||||
}
|
}
|
||||||
ctx.Data["Title"] = title
|
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["PageIsViewCode"] = true
|
||||||
ctx.Data["RequireHighlightJS"] = 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">
|
<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.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">
|
<a class="{{if .PageIsSettingsHooks}}active{{end}} item" href="{{.RepoLink}}/settings/hooks">
|
||||||
{{.i18n.Tr "repo.settings.hooks"}}
|
{{.i18n.Tr "repo.settings.hooks"}}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -26,21 +26,6 @@
|
||||||
<input id="website" name="website" type="url" value="{{.Repository.Website}}">
|
<input id="website" name="website" type="url" value="{{.Repository.Website}}">
|
||||||
</div>
|
</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>
|
||||||
|
|
|
@ -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