diff options
author | Jimmy Praet <jimmy.praet@telenet.be> | 2021-09-11 16:21:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-11 16:21:17 +0200 |
commit | 3d6cb25e315c0d4249c5c749a2eb8c64ec463aad (patch) | |
tree | bf9ba30a34e0e97b6a52584caffa1d3d9fe2506d /modules | |
parent | eb03e819d323f6374d0a99a5b80d4674a18fa957 (diff) | |
download | gitea-3d6cb25e315c0d4249c5c749a2eb8c64ec463aad.tar.gz gitea-3d6cb25e315c0d4249c5c749a2eb8c64ec463aad.zip |
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
Diffstat (limited to 'modules')
-rw-r--r-- | modules/convert/convert.go | 1 | ||||
-rw-r--r-- | modules/git/diff.go | 44 | ||||
-rw-r--r-- | modules/repofiles/delete.go | 33 | ||||
-rw-r--r-- | modules/repofiles/update.go | 73 | ||||
-rw-r--r-- | modules/structs/repo_branch.go | 3 |
5 files changed, 92 insertions, 62 deletions
diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 9a4714b4e0..404786ec9c 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -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(), } diff --git a/modules/git/diff.go b/modules/git/diff.go index 20f25c1bee..b473dc73f6 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -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 +} diff --git a/modules/repofiles/delete.go b/modules/repofiles/delete.go index 2b8ddf3cc2..5ae418b7f6 100644 --- a/modules/repofiles/delete.go +++ b/modules/repofiles/delete.go @@ -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) diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index ad984c465a..dc2893cb1c 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -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 +} diff --git a/modules/structs/repo_branch.go b/modules/structs/repo_branch.go index efb4caecb0..1f3bc04e86 100644 --- a/modules/structs/repo_branch.go +++ b/modules/structs/repo_branch.go @@ -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"` } |