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:
Jimmy Praet 2021-09-11 16:21:17 +02:00 committed by GitHub
parent eb03e819d3
commit 3d6cb25e31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 254 additions and 126 deletions

View file

@ -199,6 +199,7 @@ type ProtectBranchForm struct {
DismissStaleApprovals bool
RequireSignedCommits bool
ProtectedFilePatterns string
UnprotectedFilePatterns string
}
// Validate validates the fields

View file

@ -245,70 +245,59 @@ func checkConflicts(pr *models.PullRequest, gitRepo *git.Repository, tmpBasePath
// CheckFileProtection check file Protection
func CheckFileProtection(oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string, repo *git.Repository) ([]string, error) {
// 1. If there are no patterns short-circuit and just return nil
if len(patterns) == 0 {
return nil, nil
}
// 2. Prep the pipe
stdoutReader, stdoutWriter, err := os.Pipe()
affectedFiles, err := git.GetAffectedFiles(oldCommitID, newCommitID, env, repo)
if err != nil {
log.Error("Unable to create os.Pipe for %s", repo.Path)
return nil, err
}
defer func() {
_ = stdoutReader.Close()
_ = stdoutWriter.Close()
}()
changedProtectedFiles := make([]string, 0, limit)
// 3. Run `git diff --name-only` to get the names of the changed files
err = git.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
}
lpath := strings.ToLower(path)
for _, pat := range patterns {
if pat.Match(lpath) {
changedProtectedFiles = append(changedProtectedFiles, path)
break
}
}
if len(changedProtectedFiles) >= limit {
break
}
}
if len(changedProtectedFiles) > 0 {
return models.ErrFilePathProtected{
Path: changedProtectedFiles[0],
}
}
return scanner.Err()
})
// 4. log real errors if there are any...
if err != nil && !models.IsErrFilePathProtected(err) {
log.Error("Unable to check file protection for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err)
for _, affectedFile := range affectedFiles {
lpath := strings.ToLower(affectedFile)
for _, pat := range patterns {
if pat.Match(lpath) {
changedProtectedFiles = append(changedProtectedFiles, lpath)
break
}
}
if len(changedProtectedFiles) >= limit {
break
}
}
if len(changedProtectedFiles) > 0 {
err = models.ErrFilePathProtected{
Path: changedProtectedFiles[0],
}
}
return changedProtectedFiles, err
}
// CheckUnprotectedFiles check if the commit only touches unprotected files
func CheckUnprotectedFiles(oldCommitID, newCommitID string, patterns []glob.Glob, env []string, repo *git.Repository) (bool, error) {
if len(patterns) == 0 {
return false, nil
}
affectedFiles, err := git.GetAffectedFiles(oldCommitID, newCommitID, env, repo)
if err != nil {
return false, err
}
for _, affectedFile := range affectedFiles {
lpath := strings.ToLower(affectedFile)
unprotected := false
for _, pat := range patterns {
if pat.Match(lpath) {
unprotected = true
break
}
}
if !unprotected {
return false, nil
}
}
return true, nil
}
// checkPullFilesProtection check if pr changed protected files and save results
func checkPullFilesProtection(pr *models.PullRequest, gitRepo *git.Repository) error {
if err := pr.LoadProtectedBranch(); err != nil {