mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-20 20:39:10 +00:00
121 lines
3.2 KiB
Go
121 lines
3.2 KiB
Go
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"bdev/internal/config"
|
|
"bdev/internal/git"
|
|
"bdev/internal/patch"
|
|
)
|
|
|
|
type CloneOpts struct {
|
|
VerifyBase bool
|
|
Clean bool
|
|
DryRun bool
|
|
}
|
|
|
|
func Clone(ctx *config.Context, opts CloneOpts) (*patch.PullResult, error) {
|
|
result := &patch.PullResult{}
|
|
|
|
// Verify HEAD matches BASE if requested
|
|
if opts.VerifyBase {
|
|
head, err := git.RevParse(ctx.ChromiumDir, "HEAD")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone: getting HEAD: %w", err)
|
|
}
|
|
base, err := git.RevParse(ctx.ChromiumDir, ctx.BaseCommit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone: resolving BASE_COMMIT %s: %w", ctx.BaseCommit, err)
|
|
}
|
|
if head != base {
|
|
return nil, fmt.Errorf("clone: HEAD (%s) does not match BASE_COMMIT (%s) — use --verify-base=false to skip", head[:12], base[:12])
|
|
}
|
|
}
|
|
|
|
// Clean: reset all modified files to base before applying
|
|
if opts.Clean && !opts.DryRun {
|
|
nameStatus, err := git.DiffNameStatus(ctx.ChromiumDir, ctx.BaseCommit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone: discovering local changes: %w", err)
|
|
}
|
|
|
|
if len(nameStatus) > 0 {
|
|
var checkoutFiles []string
|
|
for path := range nameStatus {
|
|
if git.FileExistsInCommit(ctx.ChromiumDir, ctx.BaseCommit, path) {
|
|
checkoutFiles = append(checkoutFiles, path)
|
|
} else {
|
|
// File doesn't exist in base — remove it
|
|
_ = os.Remove(filepath.Join(ctx.ChromiumDir, path))
|
|
}
|
|
}
|
|
if len(checkoutFiles) > 0 {
|
|
if err := git.CheckoutFiles(ctx.ChromiumDir, ctx.BaseCommit, checkoutFiles); err != nil {
|
|
return nil, fmt.Errorf("clone: resetting to base: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read all patches from repo
|
|
repoPatchSet, err := patch.ReadPatchSet(ctx.PatchesDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone: reading patches: %w", err)
|
|
}
|
|
|
|
if opts.DryRun {
|
|
for path, fp := range repoPatchSet.Patches {
|
|
if fp.Op == patch.OpDeleted {
|
|
result.Deleted = append(result.Deleted, path)
|
|
} else {
|
|
result.Applied = append(result.Applied, path)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Apply all patches
|
|
for path, fp := range repoPatchSet.Patches {
|
|
switch fp.Op {
|
|
case patch.OpDeleted:
|
|
target := filepath.Join(ctx.ChromiumDir, path)
|
|
if _, err := os.Stat(target); err == nil {
|
|
if err := os.Remove(target); err != nil {
|
|
return nil, fmt.Errorf("clone: deleting %s: %w", path, err)
|
|
}
|
|
result.Deleted = append(result.Deleted, path)
|
|
}
|
|
|
|
case patch.OpBinary:
|
|
// Skip binary files with no content
|
|
continue
|
|
|
|
default:
|
|
if fp.Content == nil {
|
|
continue
|
|
}
|
|
// Remove existing file if it's not in BASE (untracked new-file).
|
|
// git diff can't see untracked files, so --clean misses them.
|
|
if !git.FileExistsInCommit(ctx.ChromiumDir, ctx.BaseCommit, path) {
|
|
_ = os.Remove(filepath.Join(ctx.ChromiumDir, path))
|
|
}
|
|
patchFile := filepath.Join(ctx.PatchesDir, path)
|
|
conflict, err := git.Apply(ctx.ChromiumDir, fp.Content, patchFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("clone: applying %s: %w", path, err)
|
|
}
|
|
if conflict != nil {
|
|
conflict.File = path
|
|
conflict.RejectFile = path + ".rej"
|
|
result.Conflicts = append(result.Conflicts, *conflict)
|
|
} else {
|
|
result.Applied = append(result.Applied, path)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|