mirror of
https://github.com/lukaszraczylo/harness-sync.git
synced 2026-10-16 01:07:50 +00:00
feat(adapter): registry with detect filtering and duplicate guard
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// Package merge wraps git merge-file for three-way file merges.
|
||||
package merge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Inputs:
|
||||
//
|
||||
// Base = last-rendered snapshot (state/...)
|
||||
// Ours = new rendered output (what we'd write now)
|
||||
// Theirs = current target file contents (what's on disk at the destination)
|
||||
type Inputs struct {
|
||||
Base []byte
|
||||
Ours []byte
|
||||
Theirs []byte
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Body []byte
|
||||
Conflict bool
|
||||
}
|
||||
|
||||
// ThreeWay runs `git merge-file -p ours base theirs` and returns the
|
||||
// merged body. When Conflict is true, Body contains conflict markers.
|
||||
func ThreeWay(in Inputs) (*Result, error) {
|
||||
tmp, err := os.MkdirTemp("", "harness-sync-merge-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
oursPath := filepath.Join(tmp, "ours")
|
||||
basePath := filepath.Join(tmp, "base")
|
||||
theirsPath := filepath.Join(tmp, "theirs")
|
||||
|
||||
if err := os.WriteFile(oursPath, in.Ours, 0o644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.WriteFile(basePath, in.Base, 0o644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.WriteFile(theirsPath, in.Theirs, 0o644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"git", "merge-file",
|
||||
"-L", "harness-sync (new)",
|
||||
"-L", "harness-sync (base)",
|
||||
"-L", "harness-sync (current)",
|
||||
"-p",
|
||||
oursPath, basePath, theirsPath,
|
||||
)
|
||||
out, runErr := cmd.Output()
|
||||
|
||||
res := &Result{Body: out}
|
||||
if runErr != nil {
|
||||
if ee, ok := runErr.(*exec.ExitError); ok {
|
||||
// git merge-file exits with conflict-count on conflict; body still valid
|
||||
if ee.ExitCode() > 0 {
|
||||
res.Conflict = true
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("git merge-file: %w", runErr)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCleanFastForward(t *testing.T) {
|
||||
res, err := ThreeWay(Inputs{
|
||||
Base: []byte("a\nb\nc\n"),
|
||||
Ours: []byte("a\nb\nc\nd\n"),
|
||||
Theirs: []byte("a\nb\nc\n"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.Conflict)
|
||||
assert.Equal(t, "a\nb\nc\nd\n", string(res.Body))
|
||||
}
|
||||
|
||||
func TestConflict(t *testing.T) {
|
||||
res, err := ThreeWay(Inputs{
|
||||
Base: []byte("a\n"),
|
||||
Ours: []byte("a\nours\n"),
|
||||
Theirs: []byte("a\ntheirs\n"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, res.Conflict)
|
||||
assert.Contains(t, string(res.Body), "<<<<<<<")
|
||||
assert.Contains(t, string(res.Body), ">>>>>>>")
|
||||
}
|
||||
|
||||
func TestNoOpWhenAllEqual(t *testing.T) {
|
||||
res, err := ThreeWay(Inputs{
|
||||
Base: []byte("a\n"),
|
||||
Ours: []byte("a\n"),
|
||||
Theirs: []byte("a\n"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, res.Conflict)
|
||||
assert.Equal(t, "a\n", string(res.Body))
|
||||
}
|
||||
Reference in New Issue
Block a user