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 } }