From ac0fb36c417376f3967d0d98ae5066d8946c41c9 Mon Sep 17 00:00:00 2001
From: Ilya <ilux@cpan.org>
Date: Fri, 1 Nov 2019 03:30:02 +0300
Subject: [PATCH] Allow to merge if file path contains " or \ (#8629)

* if a filename in a repository contains " or \ the owner can't merge pull request with this files
because "git diff-tree" adds double quotes to that filepath
example: filepath is ab"cd but "git diff-tree" returns "ab\"cd"

now, when the owner click "Merge Pull Request" button the server returns 500
this commit fix it

Signed-off-by: Ilya Pavlov <ilux@cpan.org>

* add -z option to getDiffTree
escape spec symbols for sparse-checkout

Signed-off-by: Ilya Pavlov <ilux@cpan.org>

* go fmt

Signed-off-by: Ilya Pavlov <ilux@cpan.org>

* typo

Signed-off-by: Ilya Pavlov <ilux@cpan.org>

* escape '\'
escape all spaces and '!'

* use regexp.ReplaceAllString()

Signed-off-by: Ilya Pavlov <ilux@cpan.org>

* strings.ReplaceAll was added in go 1.12

Signed-off-by: Ilya Pavlov <ilux@cpan.org>

* add '\' to regexp.MustCompile

Signed-off-by: Ilya Pavlov <ilux@cpan.org>
---
 services/pull/merge.go | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/services/pull/merge.go b/services/pull/merge.go
index 5eb8eaa4d4..2e093eef86 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -12,6 +12,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"regexp"
 	"strings"
 	"time"
 
@@ -374,16 +375,31 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
 	return nil
 }
 
+var escapedSymbols = regexp.MustCompile(`([*[?! \\])`)
+
 func getDiffTree(repoPath, baseBranch, headBranch string) (string, error) {
 	getDiffTreeFromBranch := func(repoPath, baseBranch, headBranch string) (string, error) {
 		var outbuf, errbuf strings.Builder
 		// Compute the diff-tree for sparse-checkout
-		if err := git.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "--root", baseBranch, headBranch, "--").RunInDirPipeline(repoPath, &outbuf, &errbuf); err != nil {
+		if err := git.NewCommand("diff-tree", "--no-commit-id", "--name-only", "-r", "-z", "--root", baseBranch, headBranch, "--").RunInDirPipeline(repoPath, &outbuf, &errbuf); err != nil {
 			return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, errbuf.String())
 		}
 		return outbuf.String(), nil
 	}
 
+	scanNullTerminatedStrings := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
+		if atEOF && len(data) == 0 {
+			return 0, nil, nil
+		}
+		if i := bytes.IndexByte(data, '\x00'); i >= 0 {
+			return i + 1, data[0:i], nil
+		}
+		if atEOF {
+			return len(data), data, nil
+		}
+		return 0, nil, nil
+	}
+
 	list, err := getDiffTreeFromBranch(repoPath, baseBranch, headBranch)
 	if err != nil {
 		return "", err
@@ -392,8 +408,14 @@ func getDiffTree(repoPath, baseBranch, headBranch string) (string, error) {
 	// Prefixing '/' for each entry, otherwise all files with the same name in subdirectories would be matched.
 	out := bytes.Buffer{}
 	scanner := bufio.NewScanner(strings.NewReader(list))
+	scanner.Split(scanNullTerminatedStrings)
 	for scanner.Scan() {
-		fmt.Fprintf(&out, "/%s\n", scanner.Text())
+		filepath := scanner.Text()
+		// escape '*', '?', '[', spaces and '!' prefix
+		filepath = escapedSymbols.ReplaceAllString(filepath, `\$1`)
+		// no necessary to escape the first '#' symbol because the first symbol is '/'
+		fmt.Fprintf(&out, "/%s\n", filepath)
 	}
+
 	return out.String(), nil
 }