fix(client): merge concurrent appends at end of file

This commit is contained in:
hh committed 2026-10-02 23:32:44 +00:00
1 parent 30cd772f08
commit 331c1f5b6f
2 files changed
+58 -30

No files matched your search

+42 -29
View File
@@ -203,37 +203,50 @@ func TestLaterEditWinsAndLoserStaysInHistory(t *testing.T) {
}
}
func TestConcurrentEditsOfDifferentLinesMerge(t *testing.T) {
mac, dell := setup(t)
path := "docs/agents/plan.md"
write(t, mac.root, path, "# plan\n\n## todo\n- a\n\n## notes\n- x\n")
mac.sync()
dell.sync()
func TestConcurrentEditsMerge(t *testing.T) {
for name, versions := range map[string][4]string{
"different lines": {
"# plan\n\n## todo\n- a\n\n## notes\n- x\n",
"# plan\n\n## todo\n- a\n- from mac\n\n## notes\n- x\n",
"# plan\n\n## todo\n- a\n\n## notes\n- x\n- from dell\n",
"# plan\n\n## todo\n- a\n- from mac\n\n## notes\n- x\n- from dell\n",
},
"appends": {"# log\n- a\n", "# log\n- a\n- from mac\n", "# log\n- a\n- from dell\n", "# log\n- a\n- from dell\n- from mac\n"},
"appends without newline": {"# log\n- a", "# log\n- a\n- from mac", "# log\n- a\n- from dell", "# log\n- a\n- from dell\n- from mac"},
"appends to unfinished line": {"# log\n- a", "# log\n- a b", "# log\n- a\n- from dell", "# log\n- a\n- from dell\n b"},
} {
t.Run(name, func(t *testing.T) {
mac, dell := setup(t)
path := "docs/agents/plan.md"
write(t, mac.root, path, versions[0])
mac.sync()
dell.sync()
write(t, mac.root, path, "# plan\n\n## todo\n- a\n- from mac\n\n## notes\n- x\n")
write(t, dell.root, path, "# plan\n\n## todo\n- a\n\n## notes\n- x\n- from dell\n")
dell.sync()
mac.sync()
dell.sync()
write(t, mac.root, path, versions[1])
write(t, dell.root, path, versions[2])
dell.sync()
mac.sync()
dell.sync()
merged := "# plan\n\n## todo\n- a\n- from mac\n\n## notes\n- x\n- from dell\n"
mac.expect(path, merged)
dell.expect(path, merged)
state, err := mac.client.State(context.Background(), repoKey(t, mac), "")
if err != nil {
t.Fatal(err)
}
if got, _ := mac.client.Blob(context.Background(), state.Spaces[protocol.SharedSpace][path].Hash); string(got) != merged {
t.Fatalf("server has %q, want %q", got, merged)
}
for _, m := range []*machine{mac, dell, mac} {
if res := m.sync(); res.Pushed != 0 || res.Pulled != 0 {
t.Fatalf("merged file keeps syncing: %+v", res)
}
}
revisions, err := mac.client.Revisions(context.Background(), repoKey(t, mac), path)
if err != nil || len(revisions) != 3 {
t.Fatalf("want base, dell and one merged revision, got %d (%v)", len(revisions), err)
mac.expect(path, versions[3])
dell.expect(path, versions[3])
state, err := mac.client.State(context.Background(), repoKey(t, mac), "")
if err != nil {
t.Fatal(err)
}
if got, _ := mac.client.Blob(context.Background(), state.Spaces[protocol.SharedSpace][path].Hash); string(got) != versions[3] {
t.Fatalf("server has %q, want %q", got, versions[3])
}
for _, m := range []*machine{mac, dell, mac} {
if res := m.sync(); res.Pushed != 0 || res.Pulled != 0 {
t.Fatalf("merged file keeps syncing: %+v", res)
}
}
revisions, err := mac.client.Revisions(context.Background(), repoKey(t, mac), path)
if err != nil || len(revisions) != 3 {
t.Fatalf("want base, dell and one merged revision, got %d (%v)", len(revisions), err)
}
})
}
}