Repository navigation
Expand file tree
/
Copy pathcell_branch.go
More file actions
89 lines (80 loc) · 2.89 KB
/
Copy pathcell_branch.go
File metadata and controls
89 lines (80 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main
import (
"context"
"fmt"
"io"
"github.com/Agent-Field/codeaf/internal/cell"
"github.com/Agent-Field/codeaf/internal/cellsync"
"github.com/Agent-Field/codeaf/internal/directory"
)
// A death branch (L12) is a cell that holds the turns a chat lost its lease
// with. `codeaf cell discard` lets it go: the branch is archived, not deleted.
// It runs on the device that drives the chat, and proves it still holds the
// lease before it changes anything.
//
// THERE IS NO `cell merge` IN STAGE 1, and none is offered anywhere. Folding a
// branch back needs a merge of two sealed trees, and the engine's merge works
// on a live workspace and a snapshot it holds, not on a branch head that is
// still only in the store; a merge that sealed a wrong head would be worse than
// no merge. The terminal verb discards; home's continuation door also opens
// the kept turns as a separate chat.
// driverOf answers how this device drives a chat: its fence and last durable head.
type driverOf interface {
Driving(ctx context.Context, c cell.Cell) (*cellsync.Driving, error)
}
// branchDoor is everything the verb needs, behind interfaces so tests use fakes.
type branchDoor struct {
Dir directory.Client
Driver driverOf
}
// openBranchDoor builds the door for a device. Until a device has sync set up
// the verb says so instead of guessing.
var openBranchDoor = func() (branchDoor, error) {
s, err := syncOf()
if err != nil {
return branchDoor{}, err
}
return branchDoor{Dir: s.Dir, Driver: s}, nil
}
func cellDiscard(c cell.Cell, args []string, out io.Writer) error {
door, err := openBranchDoor()
if err != nil {
return err
}
return door.discard(context.Background(), c, args[0], out)
}
// discard archives the branch and nothing else.
func (b branchDoor) discard(ctx context.Context, c cell.Cell, branchID string, out io.Writer) error {
if err := b.prove(ctx, c, branchID); err != nil {
return err
}
if err := b.Dir.Archive(ctx, branchID); err != nil {
return err
}
_, err := fmt.Fprintf(out, "discarded %s\n", short(branchID))
return err
}
// prove checks that this device holds the chat's lease (a heartbeat with its
// fence, which the directory refuses for a superseded one) and that branchID is
// a live branch of that chat.
func (b branchDoor) prove(ctx context.Context, c cell.Cell, branchID string) error {
d, err := b.Driver.Driving(ctx, c)
if err != nil {
return err
}
parent, err := b.Dir.Cell(ctx, d.ID())
if err != nil {
return err
}
if _, err := b.Dir.Heartbeat(ctx, d.ID(), directory.Beat{Fence: d.Fence, Pending: parent.Cell.Lease.Pending}); err != nil {
return fmt.Errorf("this device is not the one working on %s: %w", short(d.ID()), err)
}
branch, err := b.Dir.Cell(ctx, branchID)
if err != nil {
return err
}
if branch.Cell.ParentCell != d.ID() || branch.Cell.Archived {
return fmt.Errorf("%s is not a live branch of %s", short(branchID), short(d.ID()))
}
return nil
}