Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions cmd/internal/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"sync"

"github.com/containerd/console"
"github.com/muesli/termenv"
Expand Down Expand Up @@ -32,6 +34,20 @@ func checkConsole() (size console.WinSize, err error) {
// FileProcessor processes the file content and returns the modified content.
type FileProcessor func(content string) string

var (
fileIncludePatterns []string
fileExcludePatterns []string
fileFilterMu sync.RWMutex
)

// SetFileFilters sets the include and exclude patterns used by ChangeFileContent.
func SetFileFilters(include, exclude []string) {
fileFilterMu.Lock()
fileIncludePatterns = include
fileExcludePatterns = exclude
fileFilterMu.Unlock()
}

// ChangeFileContent walks through cwd and applies the processorFn to every Go
// file found. Files in a vendor directory are skipped. It returns true if any
// file content was modified.
Expand All @@ -51,6 +67,21 @@ func ChangeFileContent(cwd string, processorFn FileProcessor) (bool, error) {
if info.IsDir() || !strings.HasSuffix(info.Name(), ".go") {
return nil
}

rel, err := filepath.Rel(cwd, path)
if err != nil {
rel = path
}
fileFilterMu.RLock()
include := fileIncludePatterns
exclude := fileExcludePatterns
fileFilterMu.RUnlock()
if len(include) > 0 && !matchPatternList(rel, include) {
return nil
}
if len(exclude) > 0 && matchPatternList(rel, exclude) {
return nil
}
fileContent, err := os.ReadFile(path) // #nosec G304
if err != nil {
return fmt.Errorf("read file %s: %w", path, err)
Expand All @@ -77,3 +108,31 @@ func ChangeFileContent(cwd string, processorFn FileProcessor) (bool, error) {

return changed, nil
}

func matchPatternList(name string, patterns []string) bool {
for _, p := range patterns {
if matchPattern(name, p) {
return true
}
}
return false
}

func matchPattern(name, pattern string) bool {
if ok, err := filepath.Match(pattern, name); err == nil {
if ok {
return true
}
if !isRegexPattern(pattern) {
return false
}
}
if re, err := regexp.Compile(pattern); err == nil {
return re.MatchString(name)
}
return name == pattern
}

func isRegexPattern(p string) bool {
return strings.ContainsAny(p, "^$[]()|+?\\")
}
70 changes: 70 additions & 0 deletions cmd/internal/migrations/file_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package migrations_test

import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"

semver "github.com/Masterminds/semver/v3"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gofiber/cli/cmd/internal"
"github.com/gofiber/cli/cmd/internal/migrations"
)

func replaceFoo(cmd *cobra.Command, cwd string, curr, target *semver.Version) error {
changed, err := internal.ChangeFileContent(cwd, func(content string) string {
return strings.ReplaceAll(content, "foo", "bar")
})
if changed {
cmd.Println("replaceFoo")
}
return err //nolint:wrapcheck // returning raw error is fine for tests
}

func Test_DoMigration_FileIncludeExclude(t *testing.T) {
orig := migrations.Migrations
migrations.Migrations = []migrations.Migration{
{From: ">=0.0.0", To: ">=0.0.0", Functions: []migrations.MigrationFn{replaceFoo}},
}
t.Cleanup(func() { migrations.Migrations = orig })

curr := semver.MustParse("0.0.0")
target := semver.MustParse("1.0.0")

t.Run("include glob", func(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "foo.go"), []byte("package main\nvar foo = 1\n"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(dir, "bar.go"), []byte("package main\nvar foo = 1\n"), 0o600))
var buf bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&buf)
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, false, []string{"*foo.go"}, nil))
b, err := os.ReadFile(filepath.Join(dir, "foo.go")) // #nosec G304
require.NoError(t, err)
assert.Contains(t, string(b), "var bar = 1")
b, err = os.ReadFile(filepath.Join(dir, "bar.go")) // #nosec G304
require.NoError(t, err)
assert.Contains(t, string(b), "var foo = 1")
})

t.Run("exclude regex", func(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "foo.go"), []byte("package main\nvar foo = 1\n"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(dir, "bar.go"), []byte("package main\nvar foo = 1\n"), 0o600))
var buf bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&buf)
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, false, nil, []string{"^bar\\.go$"}))
b, err := os.ReadFile(filepath.Join(dir, "foo.go")) // #nosec G304
require.NoError(t, err)
assert.Contains(t, string(b), "var bar = 1")
b, err = os.ReadFile(filepath.Join(dir, "bar.go")) // #nosec G304
require.NoError(t, err)
assert.Contains(t, string(b), "var foo = 1")
})
}
8 changes: 5 additions & 3 deletions cmd/internal/migrations/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ func migrationName(fn MigrationFn) string {

// DoMigration runs all migrations
// It will run all migrations that match the current and target version
func DoMigration(cmd *cobra.Command, cwd string, curr, target *semver.Version, skipGoMod, verbose bool) error {
func DoMigration(cmd *cobra.Command, cwd string, curr, target *semver.Version, skipGoMod, verbose bool, includeFiles, excludeFiles []string) error {
internal.SetFileFilters(includeFiles, excludeFiles)
defer internal.SetFileFilters(nil, nil)
var errs []error
var origDeps map[string]map[string]*semver.Version
if !skipGoMod {
Expand All @@ -114,13 +116,13 @@ func DoMigration(cmd *cobra.Command, cwd string, curr, target *semver.Version, s

if fromC.Check(curr) && toC.Check(target) {
for _, fn := range m.Functions {
name := migrationName(fn)
if verbose {
var buf bytes.Buffer
origOut := cmd.OutOrStdout()
cmd.SetOut(io.MultiWriter(origOut, &buf))
err := fn(cmd, cwd, curr, target)
cmd.SetOut(origOut)
name := migrationName(fn)
if buf.Len() == 0 {
cmd.Printf("%s: no changes\n", name)
} else {
Expand All @@ -131,7 +133,7 @@ func DoMigration(cmd *cobra.Command, cwd string, curr, target *semver.Version, s
}
} else {
if err := fn(cmd, cwd, curr, target); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", migrationName(fn), err))
errs = append(errs, fmt.Errorf("%s: %w", name, err))
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/internal/migrations/lists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func Test_DoMigration_Verbose(t *testing.T) {
var buf bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&buf)
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, false))
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, false, nil, nil))
assert.Equal(t, "", buf.String())
})

Expand All @@ -36,7 +36,7 @@ func Test_DoMigration_Verbose(t *testing.T) {
var buf bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&buf)
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, true))
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, true, nil, nil))
out := buf.String()
assert.Contains(t, out, "Skipping migration from >=1.0.0-0 to >=0.0.0-0")
assert.Contains(t, out, "Skipping migration from >=2.0.0-0 to <4.0.0-0")
Expand Down Expand Up @@ -64,7 +64,7 @@ require github.com/valyala/fasthttp v1.0.0`
var buf bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&buf)
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, true))
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, true, nil, nil))
out := buf.String()
assert.Contains(t, out, "MigrateGoPkgs: no changes")
assert.Contains(t, out, "Skipping migration from >=2.0.0-0 to <4.0.0-0")
Expand All @@ -88,7 +88,7 @@ require github.com/valyala/fasthttp v1.0.0`
var buf bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&buf)
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, true))
require.NoError(t, migrations.DoMigration(cmd, dir, curr, target, true, true, nil, nil))
out := buf.String()
assert.Contains(t, out, "Migrating Go packages")
assert.Contains(t, out, "MigrateGoPkgs: changed")
Expand Down
10 changes: 9 additions & 1 deletion cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func newMigrateCmd() *cobra.Command {
var skipGoMod bool
var verbose bool
var thirdParty []string
var includeFiles []string
var excludeFiles []string

cmd := &cobra.Command{
Use: "migrate",
Expand All @@ -39,6 +41,8 @@ func newMigrateCmd() *cobra.Command {
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output")
cmd.Flags().StringVar(&targetHash, "hash", "", "Commit hash for Fiber version")
cmd.Flags().StringSliceVar(&thirdParty, "third-party", nil, "Refresh third-party modules (contrib,storage,template). Use a comma-separated list like --third-party=contrib,storage and append @<commit> to pin a commit")
cmd.Flags().StringSliceVar(&includeFiles, "include", nil, "Comma-separated list of files to include. Supports glob and regex patterns")
cmd.Flags().StringSliceVar(&excludeFiles, "exclude", nil, "Comma-separated list of files to exclude. Supports glob and regex patterns")

cmd.RunE = func(cmd *cobra.Command, _ []string) error {
tps := make([]ThirdPartyParam, 0, len(thirdParty))
Expand All @@ -61,6 +65,8 @@ func newMigrateCmd() *cobra.Command {
SkipGoMod: skipGoMod,
Verbose: verbose,
ThirdParty: tps,
IncludeFiles: includeFiles,
ExcludeFiles: excludeFiles,
})
}

Expand All @@ -74,6 +80,8 @@ type MigrateOptions struct {
TargetVersionS string
TargetHash string
ThirdParty []ThirdPartyParam
IncludeFiles []string
ExcludeFiles []string
Force bool
SkipGoMod bool
Verbose bool
Expand Down Expand Up @@ -138,7 +146,7 @@ func migrateRunE(cmd *cobra.Command, opts MigrateOptions) error {
migrateFromS = migrateFrom.String()
}

err = migrations.DoMigration(cmd, wd, migrateFrom, targetVersion, opts.SkipGoMod, opts.Verbose)
err = migrations.DoMigration(cmd, wd, migrateFrom, targetVersion, opts.SkipGoMod, opts.Verbose, opts.IncludeFiles, opts.ExcludeFiles)
if err != nil {
return fmt.Errorf("migration failed %w", err)
}
Expand Down
8 changes: 8 additions & 0 deletions docs/guide/migrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ fiber migrate --to 3.0.0
- `-f`, `--force` – Force migration even if already on the target version
- `-s`, `--skip_go_mod` – Skip running `go mod tidy`, `go mod download`, and `go mod vendor`
- `-v`, `--verbose` – Enable verbose output during migration
- `--include` – Comma-separated list of files to include in the migration. Supports glob and regex patterns
- `--exclude` – Comma-separated list of files to exclude from the migration. Supports glob and regex patterns

## Examples

Expand Down Expand Up @@ -79,3 +81,9 @@ Verbose mode prints detailed progress and executed steps:
```bash
fiber migrate --verbose
```

Limit the migration to specific files:

```bash
fiber migrate --include=internal/** --exclude="*_test.go"
```
Loading