package main import ( "bufio" "flag" "fmt" "log" "os" "path/filepath" "strings" ) func main() { // Define the --path flag rootPath := flag.String("path", ".", "Root path for patch application") flag.Parse() // Convert root path to an absolute path absoluteRootPath, err := filepath.Abs(*rootPath) if err != nil { log.Fatalf("Failed to resolve absolute path for root: %v", err) } fmt.Printf("Starting custom patcher...\nRoot path: %s\n", absoluteRootPath) patchDir := "./patches" var successfulPatches, failedPatches []string err = filepath.Walk(patchDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && strings.HasSuffix(info.Name(), ".patch") { fmt.Printf("Applying patch: %s\n", path) err := applyPatch(path, absoluteRootPath) if err != nil { fmt.Printf("Failed to apply patch %s: %v\n", path, err) failedPatches = append(failedPatches, path) } else { fmt.Printf("Successfully applied patch: %s\n", path) successfulPatches = append(successfulPatches, path) } } return nil }) if err != nil { log.Fatalf("Error reading patches: %v", err) } // Print the summary fmt.Println("\nSummary:") fmt.Printf("Successful patches (%d):\n", len(successfulPatches)) for _, patch := range successfulPatches { fmt.Printf(" - %s\n", patch) } fmt.Printf("Failed patches (%d):\n", len(failedPatches)) for _, patch := range failedPatches { fmt.Printf(" - %s\n", patch) } // Exit with appropriate status for Forgejo/Gitea runners if len(failedPatches) > 0 { fmt.Println("\nPatch process completed with failures.") os.Exit(1) } else { fmt.Println("\nAll patches applied successfully.") os.Exit(0) } } // applyPatch processes a single patch file func applyPatch(patchPath, rootPath string) error { // Open the patch file file, err := os.Open(patchPath) if err != nil { return fmt.Errorf("failed to open patch file: %v", err) } defer file.Close() var targetFilePath, tempFilePath string var modifications []string scanner := bufio.NewScanner(file) isReadingChanges := false // Parse the patch file for scanner.Scan() { line := scanner.Text() // Ignore lines starting with // if strings.HasPrefix(strings.TrimSpace(line), "//") { continue } // Detect the target file if strings.HasPrefix(line, "--- ") { relativePath := strings.TrimSpace(strings.TrimPrefix(line, "--- ")) targetFilePath = filepath.Join(rootPath, relativePath) isReadingChanges = true continue } // Start processing modifications if isReadingChanges { modifications = append(modifications, line) } } if err := scanner.Err(); err != nil { return fmt.Errorf("failed to read patch file: %v", err) } // Open the target file for reading targetFile, err := os.Open(targetFilePath) if err != nil { return fmt.Errorf("failed to open target file: %v", err) } defer targetFile.Close() // Prepare a temporary file for output tempFilePath = targetFilePath + ".tmp" tempFile, err := os.Create(tempFilePath) if err != nil { return fmt.Errorf("failed to create temp file: %v", err) } defer tempFile.Close() scanner = bufio.NewScanner(targetFile) var targetLines []string for scanner.Scan() { targetLines = append(targetLines, scanner.Text()) } if err := scanner.Err(); err != nil { return fmt.Errorf("failed to read target file: %v", err) } // Apply the modifications updatedLines, err := applyModifications(targetLines, modifications) if err != nil { return err } // Write the updated lines to the temporary file for _, line := range updatedLines { _, err := fmt.Fprintln(tempFile, line) if err != nil { return fmt.Errorf("failed to write to temp file: %v", err) } } // Replace the original file with the updated file err = os.Rename(tempFilePath, targetFilePath) if err != nil { return fmt.Errorf("failed to replace target file: %v", err) } return nil } // applyModifications applies the changes from the patch file to the target lines func applyModifications(targetLines, modifications []string) ([]string, error) { var result []string i := 0 for _, mod := range modifications { switch { case strings.HasPrefix(mod, "-"): // Delete or replace mod = strings.TrimPrefix(mod, "-") for i < len(targetLines) { if strings.TrimSpace(targetLines[i]) == strings.TrimSpace(mod) { i++ // Skip this line (delete) break } result = append(result, targetLines[i]) i++ } case strings.HasPrefix(mod, "+"): // Add or replace mod = strings.TrimPrefix(mod, "+") result = append(result, mod) default: // Keep existing lines if i < len(targetLines) { result = append(result, targetLines[i]) i++ } } } // Append remaining lines from the original file for i < len(targetLines) { result = append(result, targetLines[i]) i++ } return result, nil }