From 48bf72a53acd593040e3e3bd2e2cc5573b8f957f Mon Sep 17 00:00:00 2001 From: Daniel Hu Date: Wed, 26 Apr 2023 17:04:05 +0800 Subject: [PATCH 1/3] ci: update GitHub Actions workflow for Golang project Signed-off-by: Daniel Hu --- .github/workflows/ci.yaml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..32f895645 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.20 + + - name: Check out code + uses: actions/checkout@v2 + + - name: Install dependencies + run: | + go install golang.org/x/tools/cmd/goimports@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + + - name: Format and vet code + run: | + make fmt + make vet + + - name: Run tests + run: go test ./... + + - name: Build project + run: make build From fe920e660488e2b69897b5bc86912b649ccf78bc Mon Sep 17 00:00:00 2001 From: Daniel Hu Date: Thu, 27 Apr 2023 13:35:02 +0800 Subject: [PATCH 2/3] feat: fixes the patch file if it mixed tab and space indentation Signed-off-by: Daniel Hu --- internal/pkg/patch/patch.go | 151 ++++++++++++++++++++++++++++++- internal/pkg/patch/patch_test.go | 86 +++++++++++++++--- 2 files changed, 221 insertions(+), 16 deletions(-) diff --git a/internal/pkg/patch/patch.go b/internal/pkg/patch/patch.go index dc516847c..7d8c1d562 100644 --- a/internal/pkg/patch/patch.go +++ b/internal/pkg/patch/patch.go @@ -1,19 +1,36 @@ package patch import ( + "bufio" "fmt" "os" "os/exec" + "path/filepath" + "regexp" + "strings" "github.com/devstream-io/devstream/internal/log" ) +const ( + processOptionTabToSpace ProcessOption = "tabToSpace" + processOptionSpaceToTab ProcessOption = "spaceToTab" +) + +type ProcessOption string + // Patch calls the patch command to apply a diff file to an original func Patch(workDir, patchFile string) error { log.Infof("Patching file: %s", patchFile) + // Fix patch file if it mixed tab and space indentation + err := fixPatchFile(workDir, patchFile) + if err != nil { + return fmt.Errorf("patch file fix failed: %w", err) + } + // Check if the patch command exists and is executable - err := checkPatchCommand() + err = checkPatchCommand() if err != nil { return fmt.Errorf("patch command check failed: %w", err) } @@ -50,3 +67,135 @@ func checkPatchCommand() error { return nil } + +// fixPatchFile fixes the patch file if it mixed tab and space indentation. +// The patch file is generated by GPT4, and it may have different indentation with the original file. +// The original file path is contained in the patch file, so we can use the fix the patch file by using the original file. +// If the original file uses tab indentation, we replace all spaces with tabs in the patch file. +// If the original file uses space indentation, we replace all tabs with spaces in the patch file. +func fixPatchFile(workDir, patchFile string) error { + // Read the original file path from the patch file + originalFilePath, err := extractOriginalFilePathFromPatchFile(patchFile) + originalFilePath = filepath.Join(workDir, originalFilePath) + + if err != nil { + return fmt.Errorf("failed to extract original file path from patch string: %w", err) + } + + // Check if the original file contain tabs in the indentation + original, err := os.Open(originalFilePath) + if err != nil { + return fmt.Errorf("failed to open original file: %w", err) + } + defer original.Close() + + hasTab := false + scanner := bufio.NewScanner(original) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "\t") { + hasTab = true + break + } + } + + if err = scanner.Err(); err != nil { + return fmt.Errorf("failed to read original file: %w", err) + } + + // The original file uses tab indentation + if hasTab { + // Replace all space indentation with tabs in the patch file + if err = processTabSpaceSwitch(patchFile, processOptionSpaceToTab); err != nil { + return fmt.Errorf("failed to process tab to space: %w", err) + } + // The original file uses space indentation + } else { + // Replace all tab indentation with spaces in the patch file + if err = processTabSpaceSwitch(patchFile, processOptionTabToSpace); err != nil { + return fmt.Errorf("failed to process space to tab: %w", err) + } + } + + return nil + +} + +// ExtractOriginalFilePathFromPatchString extracts the original file path from a patch string +// e.g. --- pkg/patch/patch.go 2021-08-15 16:00:00.000000000 +0900 -> pkg/patch/patch.go +func extractOriginalFilePathFromPatchFile(patchFile string) (string, error) { + // Read content from the patch file + fileContent, err := os.ReadFile(patchFile) + if err != nil { + return "", fmt.Errorf("failed to read patch file: %w", err) + } + + lines := strings.Split(string(fileContent), "\n") + + for _, line := range lines { + if strings.HasPrefix(line, "--- ") { + fields := strings.Fields(line) + if len(fields) > 1 { + return fields[1], nil + } + } + } + + return "", fmt.Errorf("original file path not found in patch string") +} + +// processTabSpaceSwitch processes the tab/space indentation switch in a file +// If the option is processOptionTabToSpace, it replaces all tabs with spaces +// If the option is processOptionSpaceToTab, it replaces all spaces with tabs +func processTabSpaceSwitch(filePath string, option ProcessOption) error { + file, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + var processedLines []string + + // Matches the start of the string (^) followed by an optional + or - sign, followed by one or more groups of 4 spaces ( {4})+ + spaceRegex := regexp.MustCompile(`^(\+|\-)?( {4})+`) + // Matches the start of the string (^) followed by an optional + or - sign, followed by one or more tabs (\t)+ + tabRegex := regexp.MustCompile(`^(\+|\-)?\t+`) + + for scanner.Scan() { + line := scanner.Text() + if option == processOptionTabToSpace { + line = tabRegex.ReplaceAllStringFunc(line, func(s string) string { + prefix := "" + if s[0] == '+' || s[0] == '-' { + prefix = string(s[0]) + s = s[1:] + } + return prefix + strings.Repeat(" ", len(s)) + }) + } else if option == processOptionSpaceToTab { + line = spaceRegex.ReplaceAllStringFunc(line, func(s string) string { + prefix := "" + if s[0] == '+' || s[0] == '-' { + prefix = string(s[0]) + s = s[1:] + } + return prefix + strings.Repeat("\t", len(s)/4) + }) + } else { + return fmt.Errorf("invalid process option: %s", option) + } + processedLines = append(processedLines, line) + } + + if err = scanner.Err(); err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + err = os.WriteFile(filePath, []byte(strings.Join(processedLines, "\n")+"\n"), 0644) + if err != nil { + return fmt.Errorf("failed to write file: %w", err) + } + + return nil +} diff --git a/internal/pkg/patch/patch_test.go b/internal/pkg/patch/patch_test.go index a39503483..498abd72d 100644 --- a/internal/pkg/patch/patch_test.go +++ b/internal/pkg/patch/patch_test.go @@ -72,32 +72,88 @@ This is the original file. expectedPatchedContent := `Hello, world! This is the patched file. ` - Expect(string(patchedContent)).To(Equal(expectedPatchedContent)) + patchedContentStr := string(patchedContent) + Expect(patchedContentStr).To(Equal(expectedPatchedContent)) }) - It("returns an error if the patch command is not found or not executable", func() { - // Temporarily change PATH to exclude the real patch command - originalPath := os.Getenv("PATH") - err := os.Setenv("PATH", tempDir) + It("returns an error if the patch file is invalid", func() { + originalContent := `Hello, world! +This is the original file. +` + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + invalidPatchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(invalidPatchContent), 0644) Expect(err).NotTo(HaveOccurred()) - defer func() { - err := os.Setenv("PATH", originalPath) - Expect(err).NotTo(HaveOccurred()) - }() err = Patch(tempDir, patchFile.Name()) Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "patch command not found")).To(BeTrue()) + Expect(strings.Contains(err.Error(), "patch command failed")).To(BeTrue()) }) + }) - It("returns an error if the patch file is invalid", func() { - invalidPatchContent := `This is not a valid patch file.` - err := os.WriteFile(patchFile.Name(), []byte(invalidPatchContent), 0644) + Context("when patching a file with inconsistent indentation", func() { + It("successfully applies the patch with spaces to the original file with tabs", func() { + originalContent := "Hello, world!\n\tThis is the original file with tabs.\n" + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + patchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ + Hello, world! +- This is the original file with tabs. ++ This is the patched file with tabs. +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(patchContent), 0644) Expect(err).NotTo(HaveOccurred()) err = Patch(tempDir, patchFile.Name()) - Expect(err).To(HaveOccurred()) - Expect(strings.Contains(err.Error(), "patch command failed")).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + + patchedContent, err := os.ReadFile(originalFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + expectedPatchedContent := "Hello, world!\n\tThis is the patched file with tabs.\n" + Expect(string(patchedContent)).To(Equal(expectedPatchedContent)) + }) + + It("successfully applies the patch with tabs to the original file with spaces", func() { + originalContent := "Hello, world!\n This is the original file with spaces.\n" + + err := os.WriteFile(originalFile.Name(), []byte(originalContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + patchContent := fmt.Sprintf(`--- %s ++++ new-file +@@ -1,2 +1,2 @@ + Hello, world! +- This is the original file with spaces. ++ This is the patched file with spaces. +`, + filepath.Base(originalFile.Name())) + + err = os.WriteFile(patchFile.Name(), []byte(patchContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + err = Patch(tempDir, patchFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + patchedContent, err := os.ReadFile(originalFile.Name()) + Expect(err).NotTo(HaveOccurred()) + + expectedPatchedContent := "Hello, world!\n This is the patched file with spaces.\n" + Expect(string(patchedContent)).To(Equal(expectedPatchedContent)) }) }) }) From aac178c84bd8b8866f91e6ec3f11f564558e07fb Mon Sep 17 00:00:00 2001 From: Daniel Hu Date: Thu, 27 Apr 2023 13:47:46 +0800 Subject: [PATCH 3/3] fix: gopath doesn't be set in github actions runner Signed-off-by: Daniel Hu --- .github/workflows/ci.yaml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 32f895645..7bd42d70c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,25 +13,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: 1.20 + cache: true - name: Check out code - uses: actions/checkout@v2 - - - name: Install dependencies - run: | - go install golang.org/x/tools/cmd/goimports@latest - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - - - name: Format and vet code - run: | - make fmt - make vet + uses: actions/checkout@v3 - name: Run tests - run: go test ./... + run: go test -v ./... - name: Build project run: make build