Archived
A tip, checkout or committed pin that has diverged from the pin gets its own lane, which joins the main lane at a "base" node: the merge base of the two, with its SHA and subject. Only one diverged commit is drawn as a fork; with two, or without a merge base, the nodes stay in a line with the "diverged" gap. Add Status.Bases, filled by the git provider with `merge-base` for each diverged commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
181 lines
6.1 KiB
Go
181 lines
6.1 KiB
Go
package submodule
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func TestSpine(t *testing.T) {
|
|
c := func(sha string) *Commit { return &Commit{SHA: sha} }
|
|
tests := []struct {
|
|
name string
|
|
st Status
|
|
nodes []string // "sha:roles"
|
|
links []Link // Below of every node but the last
|
|
}{
|
|
{
|
|
name: "in sync",
|
|
st: Status{Pinned: c("a"), CheckedOut: c("a"), Tip: c("a")},
|
|
nodes: []string{"a:pin,tip,you"},
|
|
},
|
|
{
|
|
name: "pin behind tip, checkout on pin",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("p"), Tip: c("t"), Pin: PinInfo{Behind: 4}},
|
|
nodes: []string{"t:tip", "p:pin,you"},
|
|
links: []Link{{Above: 4}},
|
|
},
|
|
{
|
|
name: "pin on tip, checkout behind",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("p"), Drift: Distance{Behind: 2}},
|
|
nodes: []string{"p:pin,tip", "y:you"},
|
|
links: []Link{{Above: 2}},
|
|
},
|
|
{
|
|
name: "all apart",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("t"), Pin: PinInfo{Behind: 4}, Drift: Distance{Behind: 2}},
|
|
nodes: []string{"t:tip", "p:pin", "y:you"},
|
|
links: []Link{{Above: 4}, {Above: 2}},
|
|
},
|
|
{
|
|
name: "checkout ahead of the pin",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("t"), Pin: PinInfo{Behind: 5}, Drift: Distance{Ahead: 2}},
|
|
nodes: []string{"t:tip", "y:you", "p:pin"},
|
|
links: []Link{{Above: 3}, {Above: 2}},
|
|
},
|
|
{
|
|
name: "checkout on the tip",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("t"), Tip: c("t"), Pin: PinInfo{Behind: 3}, Drift: Distance{Ahead: 3}},
|
|
nodes: []string{"t:tip,you", "p:pin"},
|
|
links: []Link{{Above: 3}},
|
|
},
|
|
{
|
|
name: "pin ahead of its ref",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("p"), Tip: c("t"), Pin: PinInfo{Ahead: 1}},
|
|
nodes: []string{"p:pin,you", "t:tip"},
|
|
links: []Link{{Above: 1}},
|
|
},
|
|
{
|
|
name: "pin diverged from tip",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("p"), Tip: c("t"), Pin: PinInfo{Ahead: 2, Behind: 3}},
|
|
nodes: []string{"t:tip", "p:pin,you"},
|
|
links: []Link{{Above: 3, Below: 2}},
|
|
},
|
|
{
|
|
name: "checkout diverged from pin",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("p"), Drift: Distance{Ahead: 1, Behind: 2}},
|
|
nodes: []string{"p:pin,tip", "y:you"},
|
|
links: []Link{{Above: 2, Below: 1}},
|
|
},
|
|
{
|
|
name: "staged pin above the committed one, checkout on the staged pin",
|
|
st: Status{Pinned: c("s"), CheckedOut: c("s"), Tip: c("s"), PinStaged: true,
|
|
Committed: c("p"), CommittedDrift: Distance{Behind: 3}},
|
|
nodes: []string{"s:staged,tip,you", "p:pin"},
|
|
links: []Link{{Above: 3}},
|
|
},
|
|
{
|
|
name: "checkout stayed on the committed pin",
|
|
st: Status{Pinned: c("s"), CheckedOut: c("p"), Tip: c("t"), PinStaged: true, Pin: PinInfo{Behind: 1},
|
|
Committed: c("p"), CommittedDrift: Distance{Behind: 2}, Drift: Distance{Behind: 2}},
|
|
nodes: []string{"t:tip", "s:staged", "p:pin,you"},
|
|
links: []Link{{Above: 1}, {Above: 2}},
|
|
},
|
|
{
|
|
name: "tip diverged from the pin: a fork",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("p"), Tip: c("t"), Pin: PinInfo{Ahead: 1, Behind: 2},
|
|
Bases: Bases{Tip: c("b")}},
|
|
nodes: []string{"t:tip", "p:pin,you", "b:base"},
|
|
links: []Link{{Above: 2}, {Above: 1}},
|
|
},
|
|
{
|
|
name: "checkout diverged from the pin: a fork",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("p"), Drift: Distance{Ahead: 3, Behind: 2},
|
|
Bases: Bases{Checkout: c("b")}},
|
|
nodes: []string{"y:you", "p:pin,tip", "b:base"},
|
|
links: []Link{{Above: 3}, {Above: 2}},
|
|
},
|
|
{
|
|
name: "fork with the tip above the pin on the main lane",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("t"), Pin: PinInfo{Behind: 2},
|
|
Drift: Distance{Ahead: 1, Behind: 1}, Bases: Bases{Checkout: c("b")}},
|
|
nodes: []string{"y:you", "t:tip", "p:pin", "b:base"},
|
|
links: []Link{{Above: 1}, {Above: 2}, {Above: 1}},
|
|
},
|
|
{
|
|
name: "something older than the base continues below it",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("t"), Pin: PinInfo{Ahead: 1, Behind: 2},
|
|
Bases: Bases{Tip: c("b")}, Drift: Distance{Behind: 3}},
|
|
nodes: []string{"t:tip", "p:pin", "b:base", "y:you"},
|
|
links: []Link{{Above: 2}, {Above: 1}, {Above: 2}},
|
|
},
|
|
{
|
|
name: "the base is also the checkout",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("b"), Tip: c("t"), Pin: PinInfo{Ahead: 1, Behind: 2},
|
|
Bases: Bases{Tip: c("b")}, Drift: Distance{Behind: 1}},
|
|
nodes: []string{"t:tip", "p:pin", "b:you,base"},
|
|
links: []Link{{Above: 2}, {Above: 1}},
|
|
},
|
|
{
|
|
name: "two diverged commits are not drawn as a fork",
|
|
st: Status{Pinned: c("p"), CheckedOut: c("y"), Tip: c("t"), Pin: PinInfo{Ahead: 1, Behind: 2},
|
|
Drift: Distance{Ahead: 1, Behind: 1}, Bases: Bases{Tip: c("b"), Checkout: c("b")}},
|
|
nodes: []string{"t:tip", "p:pin", "y:you"},
|
|
links: []Link{{Above: 2, Below: 1}, {Above: 1, Below: 1}},
|
|
},
|
|
{
|
|
name: "no tip, checkout not loaded",
|
|
st: Status{Pinned: c("p")},
|
|
nodes: []string{"p:pin"},
|
|
},
|
|
{
|
|
name: "superproject",
|
|
st: Status{Root: true, Pinned: c("h"), CheckedOut: c("h"), Tip: c("t"), Pin: PinInfo{Behind: 2}},
|
|
nodes: []string{"t:tip", "h:head"},
|
|
links: []Link{{Above: 2}},
|
|
},
|
|
}
|
|
words := map[Role]string{RolePin: "pin", RoleStaged: "staged", RoleHead: "head", RoleTip: "tip", RoleYou: "you", RoleBase: "base"}
|
|
for _, tt := range tests {
|
|
var nodes []string
|
|
var links []Link
|
|
spine := tt.st.Spine()
|
|
for i, n := range spine {
|
|
s := n.Commit.SHA + ":"
|
|
for j, r := range n.Roles {
|
|
if j > 0 {
|
|
s += ","
|
|
}
|
|
s += words[r]
|
|
}
|
|
nodes = append(nodes, s)
|
|
if i < len(spine)-1 {
|
|
links = append(links, n.Below)
|
|
}
|
|
}
|
|
if !slices.Equal(nodes, tt.nodes) {
|
|
t.Errorf("%s: nodes %v, want %v", tt.name, nodes, tt.nodes)
|
|
}
|
|
if !slices.Equal(links, tt.links) {
|
|
t.Errorf("%s: links %v, want %v", tt.name, links, tt.links)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSpineNodeInSync(t *testing.T) {
|
|
tests := []struct {
|
|
roles []Role
|
|
want bool
|
|
}{
|
|
{[]Role{RolePin, RoleTip, RoleYou}, true},
|
|
{[]Role{RoleHead, RoleTip}, true},
|
|
{[]Role{RolePin, RoleTip}, false},
|
|
{[]Role{RolePin, RoleYou}, false},
|
|
{[]Role{RoleTip}, false},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := (SpineNode{Roles: tt.roles}).InSync(); got != tt.want {
|
|
t.Errorf("%v: InSync = %v, want %v", tt.roles, got, tt.want)
|
|
}
|
|
}
|
|
}
|