569340cf5fa42e3986981d057b955fb893418910 / internal/git/merge_test.go · 4986 bytes · raw
package git
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
)
func writeFile(t *testing.T, dir, rel, content string) {
t.Helper()
full := filepath.Join(dir, rel)
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
t.Fatalf("write %s: %v", rel, err)
}
}
// bareWithMain returns a bare repo and a clone whose main has one commit.
func bareWithMain(t *testing.T) (bare, work string) {
t.Helper()
bare = filepath.Join(t.TempDir(), "m.git")
runGit(t, "", "init", "-q", "--bare", "--initial-branch=main", bare)
work = filepath.Join(t.TempDir(), "work")
runGit(t, "", "clone", "-q", bare, work)
runGit(t, work, "config", "user.email", "t@t")
runGit(t, work, "config", "user.name", "t")
writeFile(t, work, "base.txt", "base\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "base")
runGit(t, work, "push", "-q", "origin", "main")
return bare, work
}
func TestMergeFastForward(t *testing.T) {
bare, work := bareWithMain(t)
runGit(t, work, "checkout", "-qb", "feature")
writeFile(t, work, "feature.txt", "feature\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "feature work")
runGit(t, work, "push", "-q", "origin", "feature")
headSHA, err := ResolveCommit(bare, "refs/heads/feature")
if err != nil {
t.Fatalf("ResolveCommit: %v", err)
}
sha, ff, err := Merge(bare, "main", "feature", "Merge feature", "tester")
if err != nil {
t.Fatalf("Merge: %v", err)
}
if !ff {
t.Error("Merge reported not fast-forward, want fast-forward")
}
if sha != headSHA {
t.Errorf("merge sha = %s, want feature head %s", sha, headSHA)
}
mainSHA, _ := ResolveCommit(bare, "refs/heads/main")
if mainSHA != headSHA {
t.Errorf("main = %s, want advanced to %s", mainSHA, headSHA)
}
}
func TestMergeCreatesMergeCommit(t *testing.T) {
bare, work := bareWithMain(t)
runGit(t, work, "checkout", "-qb", "feature")
writeFile(t, work, "feature.txt", "feature\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "feature work")
runGit(t, work, "push", "-q", "origin", "feature")
runGit(t, work, "checkout", "-q", "main")
writeFile(t, work, "main.txt", "main\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "main work")
runGit(t, work, "push", "-q", "origin", "main")
sha, ff, err := Merge(bare, "main", "feature", "Merge feature", "tester")
if err != nil {
t.Fatalf("Merge: %v", err)
}
if ff {
t.Error("Merge reported fast-forward, want a merge commit")
}
commit, err := CommitAt(bare, sha)
if err != nil {
t.Fatalf("CommitAt merge: %v", err)
}
if len(commit.Parents) != 2 {
t.Errorf("merge parents = %d, want 2", len(commit.Parents))
}
entries, err := LsTree(bare, "refs/heads/main", "")
if err != nil {
t.Fatalf("LsTree: %v", err)
}
var names []string
for _, e := range entries {
names = append(names, filepath.Base(e.Path))
}
joined := strings.Join(names, ",")
if !strings.Contains(joined, "feature.txt") || !strings.Contains(joined, "main.txt") {
t.Errorf("merged tree = %v, want both feature.txt and main.txt", names)
}
}
func TestMergeConflictLeavesBase(t *testing.T) {
bare, work := bareWithMain(t)
writeFile(t, work, "conflict.txt", "original\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "add conflict file")
runGit(t, work, "push", "-q", "origin", "main")
runGit(t, work, "checkout", "-qb", "feature")
writeFile(t, work, "conflict.txt", "feature side\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "feature edit")
runGit(t, work, "push", "-q", "origin", "feature")
runGit(t, work, "checkout", "-q", "main")
writeFile(t, work, "conflict.txt", "main side\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "main edit")
runGit(t, work, "push", "-q", "origin", "main")
before, _ := ResolveCommit(bare, "refs/heads/main")
if _, _, err := Merge(bare, "main", "feature", "Merge feature", "tester"); !errors.Is(err, ErrMergeConflict) {
t.Fatalf("Merge err = %v, want ErrMergeConflict", err)
}
after, _ := ResolveCommit(bare, "refs/heads/main")
if after != before {
t.Errorf("main moved on conflict: %s -> %s", before, after)
}
}
func TestDiffThreeDot(t *testing.T) {
bare, work := bareWithMain(t)
runGit(t, work, "checkout", "-qb", "feature")
writeFile(t, work, "feature.txt", "feature\n")
runGit(t, work, "add", ".")
runGit(t, work, "commit", "-qm", "feature work")
runGit(t, work, "push", "-q", "origin", "feature")
patch, err := Diff(bare, "main", "feature", 1<<20)
if err != nil {
t.Fatalf("Diff: %v", err)
}
if !strings.Contains(string(patch), "feature.txt") {
t.Errorf("diff missing feature.txt: %s", patch)
}
branches, err := Branches(bare)
if err != nil {
t.Fatalf("Branches: %v", err)
}
if len(branches) != 2 || branches[0] != "feature" || branches[1] != "main" {
t.Errorf("branches = %v, want [feature main]", branches)
}
}