Archived
Draw diverged commits on the spine as a fork
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>
This commit is contained in:
@@ -241,9 +241,24 @@ func committedState(ctx context.Context, in info, st *submodule.Status, committe
|
||||
return err
|
||||
}
|
||||
st.Committed = c
|
||||
if st.CommittedDrift.Ahead > 0 && st.CommittedDrift.Behind > 0 {
|
||||
if st.Bases.Committed, err = mergeBase(ctx, in.dir, committed, pin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeBase returns the commit where the histories of a and b split; nil if
|
||||
// they have none in common.
|
||||
func mergeBase(ctx context.Context, dir, a, b string) (*submodule.Commit, error) {
|
||||
base, ok, err := try(ctx, dir, "merge-base", a, b)
|
||||
if err != nil || !ok || base == "" {
|
||||
return nil, err
|
||||
}
|
||||
return commit(ctx, dir, base)
|
||||
}
|
||||
|
||||
// rootStatus describes the superproject. Its "pin" is HEAD, compared with the
|
||||
// upstream of the current branch; submodules are ignored, their rows show
|
||||
// them.
|
||||
@@ -346,6 +361,11 @@ func pinState(ctx context.Context, in info, st *submodule.Status, pin, head, bra
|
||||
if st.Drift.Ahead, st.Drift.Behind, err = parseLeftRight(out); err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Drift.Ahead > 0 && st.Drift.Behind > 0 {
|
||||
if st.Bases.Checkout, err = mergeBase(ctx, in.dir, pin, head); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if source == submodule.RefFromSHA {
|
||||
st.Pin.Ref = st.Pinned.SHA
|
||||
@@ -361,6 +381,11 @@ func pinState(ctx context.Context, in info, st *submodule.Status, pin, head, bra
|
||||
if st.Pin.Ahead, st.Pin.Behind, err = parseLeftRight(out); err != nil {
|
||||
return err
|
||||
}
|
||||
if st.Pin.Ahead > 0 && st.Pin.Behind > 0 {
|
||||
if st.Bases.Tip, err = mergeBase(ctx, in.dir, pin, ref); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -294,3 +294,50 @@ func TestCommittedPinAheadOfStaged(t *testing.T) {
|
||||
t.Errorf("spine = %+v, want the committed pin directly above the staged one", spine)
|
||||
}
|
||||
}
|
||||
|
||||
// A pin and its ref that each have a commit the other lacks, and a checkout
|
||||
// that did the same to the pin: both get the merge base.
|
||||
func TestBasesOfDivergedCommits(t *testing.T) {
|
||||
p, root := ownFixture(t)
|
||||
ctx := context.Background()
|
||||
super := filepath.Join(root, "super")
|
||||
|
||||
// src/net: the pin is unpushed; upstream moved on
|
||||
pushUpstream(t, root, "net")
|
||||
if err := p.Fetch(ctx, "src/net"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := p.Status(ctx, "src/net")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Pin.Ahead != 1 || st.Pin.Behind != 1 {
|
||||
t.Fatalf("src/net: ↑%d↓%d, want ↑1↓1", st.Pin.Ahead, st.Pin.Behind)
|
||||
}
|
||||
if b := st.Bases.Tip; b == nil || b.SHA == st.Pinned.SHA || b.SHA == st.Tip.SHA {
|
||||
t.Errorf("src/net: Bases.Tip = %+v, want a commit before the pin and the tip", b)
|
||||
}
|
||||
if sp := st.Spine(); len(sp) != 3 || !sp[0].Fork || sp[0].Commit.SHA != st.Tip.SHA {
|
||||
t.Errorf("src/net: spine = %+v, want a fork headed by the tip", sp)
|
||||
}
|
||||
|
||||
// src/core: a commit on top of a checkout that is behind the pin
|
||||
git(t, filepath.Join(super, "src", "core"), "commit", "--allow-empty", "-m", "Local")
|
||||
st, err = p.Status(ctx, "src/core")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Drift != (submodule.Distance{Ahead: 1, Behind: 1}) {
|
||||
t.Fatalf("src/core: drift %+v, want 1/1", st.Drift)
|
||||
}
|
||||
if b := st.Bases.Checkout; b == nil || b.SHA == st.Pinned.SHA || b.SHA == st.CheckedOut.SHA {
|
||||
t.Errorf("src/core: Bases.Checkout = %+v", b)
|
||||
}
|
||||
|
||||
// in sync and linear: no bases
|
||||
if st, err = p.Status(ctx, "src/ui"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if st.Bases != (submodule.Bases{}) {
|
||||
t.Errorf("src/ui: Bases = %+v, want none", st.Bases)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user