Files
2026-03-05 10:06:41 -08:00

160 lines
3.3 KiB
Go

package patch
import (
"context"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"golang.org/x/sync/errgroup"
)
// ReadPatchSet reads all patches from the chromium_patches/ directory.
func ReadPatchSet(patchesDir string) (*PatchSet, error) {
ps := NewPatchSet("")
// Collect file paths
var filePaths []string
err := filepath.Walk(patchesDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
filePaths = append(filePaths, path)
}
return nil
})
if err != nil {
return nil, err
}
g, _ := errgroup.WithContext(context.Background())
g.SetLimit(runtime.NumCPU())
var mu sync.Mutex
for _, path := range filePaths {
path := path
g.Go(func() error {
content, err := os.ReadFile(path)
if err != nil {
return err
}
rel, err := filepath.Rel(patchesDir, path)
if err != nil {
return err
}
fp := classifyPatchFile(rel, content)
mu.Lock()
if existing, ok := ps.Patches[fp.Path]; ok {
ps.Patches[fp.Path] = mergePatchEntry(existing, fp)
} else {
ps.Patches[fp.Path] = fp
}
mu.Unlock()
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return ps, nil
}
// ReadPatchFiles returns a map of chromium paths to true for all patches in the directory.
// Lighter than ReadPatchSet — only collects paths, not content.
func ReadPatchFiles(patchesDir string) (map[string]bool, error) {
result := make(map[string]bool)
err := filepath.Walk(patchesDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() {
return nil
}
rel, err := filepath.Rel(patchesDir, path)
if err != nil {
return nil
}
chromPath := rel
chromPath = strings.TrimSuffix(chromPath, ".deleted")
chromPath = strings.TrimSuffix(chromPath, ".binary")
chromPath = strings.TrimSuffix(chromPath, ".rename")
result[chromPath] = true
return nil
})
return result, err
}
func classifyPatchFile(rel string, content []byte) *FilePatch {
fp := &FilePatch{
Path: rel,
Content: content,
Op: OpModified,
}
switch {
case strings.HasSuffix(rel, ".deleted"):
fp.Path = strings.TrimSuffix(rel, ".deleted")
fp.Op = OpDeleted
fp.Content = nil
case strings.HasSuffix(rel, ".binary"):
fp.Path = strings.TrimSuffix(rel, ".binary")
fp.Op = OpBinary
fp.IsBinary = true
fp.Content = nil
case strings.HasSuffix(rel, ".rename"):
fp.Path = strings.TrimSuffix(rel, ".rename")
fp.Op = OpRenamed
// Parse rename_from from content
for _, line := range strings.Split(string(content), "\n") {
if strings.HasPrefix(line, "rename_from: ") {
fp.OldPath = strings.TrimPrefix(line, "rename_from: ")
}
}
fp.Content = nil
default:
// Check if content looks like a diff with "new file mode"
if strings.Contains(string(content), "new file mode") {
fp.Op = OpAdded
}
}
return fp
}
func mergePatchEntry(existing, incoming *FilePatch) *FilePatch {
switch incoming.Op {
case OpDeleted, OpBinary:
return incoming
case OpRenamed:
merged := *incoming
if len(existing.Content) > 0 {
merged.Content = existing.Content
}
return &merged
}
switch existing.Op {
case OpDeleted, OpBinary:
return existing
case OpRenamed:
merged := *existing
merged.Content = incoming.Content
return &merged
default:
return incoming
}
}