forked from forgejo/forgejo
Support unprotected file patterns (#16395)
Fixes #16381 Note that changes to unprotected files via the web editor still cannot be pushed directly to the protected branch. I could easily add such support for edits and deletes if needed. But for adding, uploading or renaming unprotected files, it is not trivial. * Extract & Move GetAffectedFiles to modules/git
This commit is contained in:
parent
eb03e819d3
commit
3d6cb25e31
17 changed files with 254 additions and 126 deletions
|
@ -127,6 +127,7 @@ func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection {
|
|||
DismissStaleApprovals: bp.DismissStaleApprovals,
|
||||
RequireSignedCommits: bp.RequireSignedCommits,
|
||||
ProtectedFilePatterns: bp.ProtectedFilePatterns,
|
||||
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
|
||||
Created: bp.CreatedUnix.AsTime(),
|
||||
Updated: bp.UpdatedUnix.AsTime(),
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
@ -273,3 +274,46 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
|
|||
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
|
||||
return strings.Join(newHunk, "\n"), nil
|
||||
}
|
||||
|
||||
// GetAffectedFiles returns the affected files between two commits
|
||||
func GetAffectedFiles(oldCommitID, newCommitID string, env []string, repo *Repository) ([]string, error) {
|
||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
log.Error("Unable to create os.Pipe for %s", repo.Path)
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = stdoutReader.Close()
|
||||
_ = stdoutWriter.Close()
|
||||
}()
|
||||
|
||||
affectedFiles := make([]string, 0, 32)
|
||||
|
||||
// Run `git diff --name-only` to get the names of the changed files
|
||||
err = NewCommand("diff", "--name-only", oldCommitID, newCommitID).
|
||||
RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
|
||||
stdoutWriter, nil, nil,
|
||||
func(ctx context.Context, cancel context.CancelFunc) error {
|
||||
// Close the writer end of the pipe to begin processing
|
||||
_ = stdoutWriter.Close()
|
||||
defer func() {
|
||||
// Close the reader on return to terminate the git command if necessary
|
||||
_ = stdoutReader.Close()
|
||||
}()
|
||||
// Now scan the output from the command
|
||||
scanner := bufio.NewScanner(stdoutReader)
|
||||
for scanner.Scan() {
|
||||
path := strings.TrimSpace(scanner.Text())
|
||||
if len(path) == 0 {
|
||||
continue
|
||||
}
|
||||
affectedFiles = append(affectedFiles, path)
|
||||
}
|
||||
return scanner.Err()
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Unable to get affected files for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err)
|
||||
}
|
||||
|
||||
return affectedFiles, err
|
||||
}
|
||||
|
|
|
@ -56,37 +56,8 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo
|
|||
BranchName: opts.NewBranch,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
protectedBranch, err := repo.GetBranchProtection(opts.OldBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if protectedBranch != nil {
|
||||
if !protectedBranch.CanUserPush(doer.ID) {
|
||||
return nil, models.ErrUserCannotCommit{
|
||||
UserName: doer.LowerName,
|
||||
}
|
||||
}
|
||||
if protectedBranch.RequireSignedCommits {
|
||||
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
|
||||
if err != nil {
|
||||
if !models.IsErrWontSign(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, models.ErrUserCannotCommit{
|
||||
UserName: doer.LowerName,
|
||||
}
|
||||
}
|
||||
}
|
||||
patterns := protectedBranch.GetProtectedFilePatterns()
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(strings.ToLower(opts.TreePath)) {
|
||||
return nil, models.ErrFilePathProtected{
|
||||
Path: opts.TreePath,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check that the path given in opts.treeName is valid (not a git path)
|
||||
|
|
|
@ -148,37 +148,8 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
|
|||
if err != nil && !git.IsErrBranchNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
protectedBranch, err := repo.GetBranchProtection(opts.OldBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if protectedBranch != nil {
|
||||
if !protectedBranch.CanUserPush(doer.ID) {
|
||||
return nil, models.ErrUserCannotCommit{
|
||||
UserName: doer.LowerName,
|
||||
}
|
||||
}
|
||||
if protectedBranch.RequireSignedCommits {
|
||||
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
|
||||
if err != nil {
|
||||
if !models.IsErrWontSign(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, models.ErrUserCannotCommit{
|
||||
UserName: doer.LowerName,
|
||||
}
|
||||
}
|
||||
}
|
||||
patterns := protectedBranch.GetProtectedFilePatterns()
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(strings.ToLower(opts.TreePath)) {
|
||||
return nil, models.ErrFilePathProtected{
|
||||
Path: opts.TreePath,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err := VerifyBranchProtection(repo, doer, opts.OldBranch, opts.TreePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If FromTreePath is not set, set it to the opts.TreePath
|
||||
|
@ -465,3 +436,43 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
|
|||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
|
||||
func VerifyBranchProtection(repo *models.Repository, doer *models.User, branchName string, treePath string) error {
|
||||
protectedBranch, err := repo.GetBranchProtection(branchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if protectedBranch != nil {
|
||||
isUnprotectedFile := false
|
||||
glob := protectedBranch.GetUnprotectedFilePatterns()
|
||||
if len(glob) != 0 {
|
||||
isUnprotectedFile = protectedBranch.IsUnprotectedFile(glob, treePath)
|
||||
}
|
||||
if !protectedBranch.CanUserPush(doer.ID) && !isUnprotectedFile {
|
||||
return models.ErrUserCannotCommit{
|
||||
UserName: doer.LowerName,
|
||||
}
|
||||
}
|
||||
if protectedBranch.RequireSignedCommits {
|
||||
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), branchName)
|
||||
if err != nil {
|
||||
if !models.IsErrWontSign(err) {
|
||||
return err
|
||||
}
|
||||
return models.ErrUserCannotCommit{
|
||||
UserName: doer.LowerName,
|
||||
}
|
||||
}
|
||||
}
|
||||
patterns := protectedBranch.GetProtectedFilePatterns()
|
||||
for _, pat := range patterns {
|
||||
if pat.Match(strings.ToLower(treePath)) {
|
||||
return models.ErrFilePathProtected{
|
||||
Path: treePath,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ type BranchProtection struct {
|
|||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
||||
// swagger:strfmt date-time
|
||||
Created time.Time `json:"created_at"`
|
||||
// swagger:strfmt date-time
|
||||
|
@ -73,6 +74,7 @@ type CreateBranchProtectionOption struct {
|
|||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
||||
}
|
||||
|
||||
// EditBranchProtectionOption options for editing a branch protection
|
||||
|
@ -97,4 +99,5 @@ type EditBranchProtectionOption struct {
|
|||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits *bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
||||
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue