New feature: replace already existing license header based on pattern (#98)

diff --git a/README.md b/README.md
index c9739f7..560c377 100644
--- a/README.md
+++ b/README.md
@@ -463,7 +463,7 @@
 2. The [SPDX ID](https://spdx.org/licenses/) of the license, it’s convenient when your license is standard SPDX license, so that you can simply specify this identifier without copying the whole license `content` or `pattern`. This will be used as the content when `fix` command needs to insert a license header.
 3. The copyright owner to replace the `[owner]` in the `SPDX-ID` license template.
 4. If you are not using the standard license text, you can paste your license text here, this will be used as the content when `fix` command needs to insert a license header, if both `license` and `SPDX-ID` are specified, `license` wins.
-5. The `pattern` is an optional regexp. You don’t need this if all the file headers are the same as `license` or the license of `SPDX-ID`, otherwise you need to compose a pattern that matches your license texts.
+5. The `pattern` is an optional regexp. You don’t need this if all the file headers are the same as `license` or the license of `SPDX-ID`, otherwise you need to compose a pattern that matches your existing license texts so that `license-eye` won't complain about the existing license headers. If you want to replace your existing license headers, you can compose a `pattern` that matches your existing license headers, and modify the `content` to what you want to have, then `license-eye header fix` would rewrite all the existing license headers to the wanted `content`.
 6. The `paths` are the path list that will be checked (and fixed) by license-eye, default is `['**']`. Formats like `**/*`.md and `**/bin/**` are supported.
 7. The `paths-ignore` are the path list that will be ignored by license-eye. By default, `.git` and the content in `.gitignore` will be inflated into the `paths-ignore` list.
 8. On what condition License-Eye will comment the check results on the pull request, `on-failure`, `always` or `never`. Options other than `never` require the environment variable `GITHUB_TOKEN` to be set.
diff --git a/commands/header_fix.go b/commands/header_fix.go
index 3b0b828..7473706 100644
--- a/commands/header_fix.go
+++ b/commands/header_fix.go
@@ -53,7 +53,7 @@
 		logger.Log.Infoln(result.String())
 
 		if len(errors) > 0 {
-			return fmt.Errorf(strings.Join(errors, "\n"))
+			return fmt.Errorf("%s", strings.Join(errors, "\n"))
 		}
 
 		return nil
diff --git a/pkg/header/config.go b/pkg/header/config.go
index b2f98e3..fa62c27 100644
--- a/pkg/header/config.go
+++ b/pkg/header/config.go
@@ -53,7 +53,6 @@
 
 type ConfigHeader struct {
 	License     LicenseConfig `yaml:"license"`
-	Pattern     string        `yaml:"pattern"`
 	Paths       []string      `yaml:"paths"`
 	PathsIgnore []string      `yaml:"paths-ignore"`
 	Comment     CommentOption `yaml:"comment"`
@@ -70,8 +69,41 @@
 	return license.Normalize(config.GetLicenseContent())
 }
 
+func (config *ConfigHeader) LicensePattern(style *comments.CommentStyle) *regexp.Regexp {
+	pattern := config.License.Pattern
+
+	if pattern == "" || strings.TrimSpace(pattern) == "" {
+		return nil
+	}
+
+	// Trim leading and trailing newlines
+	pattern = strings.TrimSpace(pattern)
+	lines := strings.Split(pattern, "\n")
+	for i, line := range lines {
+		if line != "" {
+			lines[i] = fmt.Sprintf("%v %v", style.Middle, line)
+		} else {
+			lines[i] = style.Middle
+		}
+	}
+
+	lines = append(lines, "(("+style.Middle+"\n)*|\n*)")
+
+	if style.Start != style.Middle {
+		lines = append([]string{style.Start}, lines...)
+	}
+
+	if style.End != style.Middle {
+		lines = append(lines, style.End)
+	}
+
+	pattern = strings.Join(lines, "\n")
+
+	return regexp.MustCompile("(?s)" + pattern)
+}
+
 func (config *ConfigHeader) NormalizedPattern() *regexp.Regexp {
-	pattern := config.Pattern
+	pattern := config.License.Pattern
 
 	if pattern == "" || strings.TrimSpace(pattern) == "" {
 		return nil
diff --git a/pkg/header/fix.go b/pkg/header/fix.go
index 55936b1..984f560 100644
--- a/pkg/header/fix.go
+++ b/pkg/header/fix.go
@@ -61,7 +61,7 @@
 		return err
 	}
 
-	content = rewriteContent(style, content, licenseHeader)
+	content = rewriteContent(style, content, licenseHeader, config.LicensePattern(style))
 
 	if err := os.WriteFile(file, content, stat.Mode()); err != nil {
 		return err
@@ -72,7 +72,12 @@
 	return nil
 }
 
-func rewriteContent(style *comments.CommentStyle, content []byte, licenseHeader string) []byte {
+func rewriteContent(style *comments.CommentStyle, content []byte, licenseHeader string, licensePattern *regexp.Regexp) []byte {
+	// Remove previous license header version to allow update it
+	if licensePattern != nil {
+		content = licensePattern.ReplaceAll(content, []byte(""))
+	}
+
 	if style.After == "" {
 		return append([]byte(licenseHeader), content...)
 	}
diff --git a/pkg/header/fix_test.go b/pkg/header/fix_test.go
index 670814d..8b55898 100644
--- a/pkg/header/fix_test.go
+++ b/pkg/header/fix_test.go
@@ -19,6 +19,7 @@
 
 import (
 	"fmt"
+	"regexp"
 	"testing"
 
 	"github.com/apache/skywalking-eyes/pkg/comments"
@@ -73,6 +74,7 @@
 		style           *comments.CommentStyle
 		content         string
 		licenseHeader   string
+		licensePattern  string
 		expectedContent string
 	}{
 		{
@@ -362,7 +364,11 @@
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			content := rewriteContent(test.style, []byte(test.content), test.licenseHeader)
+			var r *regexp.Regexp
+			if len(test.licensePattern) > 0 {
+				r = regexp.MustCompile(test.licensePattern)
+			}
+			content := rewriteContent(test.style, []byte(test.content), test.licenseHeader, r)
 			require.Equal(t, test.expectedContent, string(content), fmt.Sprintf("style: %+v", test.style))
 		})
 	}
diff --git a/pkg/review/header.go b/pkg/review/header.go
index eccf295..bc00800 100644
--- a/pkg/review/header.go
+++ b/pkg/review/header.go
@@ -66,9 +66,7 @@
 		return
 	}
 	if !IsGHA() {
-		panic(fmt.Errorf(fmt.Sprintf(
-			`this must be run on GitHub Actions or you have to set the environment variables %v manually.`, requiredEnvVars,
-		)))
+		panic(fmt.Errorf("this must be run on GitHub Actions or you have to set the environment variables %v manually", requiredEnvVars))
 	}
 
 	s, err := GetSha()