Restore puts submodules on a branch at their pin

A restored submodule whose pin is a branch tip ends on that branch instead
of a detached HEAD: the local branch tracking the ref, else the ref created
as a tracking branch, else another local branch, else another remote branch
at the pin. No branch is moved; in-sync submodules are included (D28).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 19:17:26 +02:00
co-authored by Claude Opus 5.5
parent 2aeebfe3b1
commit 651b0c2e69
7 changed files with 371 additions and 19 deletions
+18 -8
View File
@@ -327,17 +327,10 @@ func pinState(ctx context.Context, in info, st *submodule.Status, pin, head, bra
return fmt.Errorf("pinned commit %.7s not fetched", pin)
}
// remote branches containing the pin, oldest tip first
out, err := run(ctx, in.dir, "for-each-ref", "--contains", pin, "--sort=committerdate", "--format=%(refname)", "refs/remotes")
containing, err := remoteContaining(ctx, in.dir, pin)
if err != nil {
return err
}
var containing []string
for _, r := range strings.Fields(out) {
if !strings.HasSuffix(r, "/HEAD") {
containing = append(containing, strings.TrimPrefix(r, "refs/remotes/"))
}
}
st.Pin.Pushed = len(containing) > 0
var ref string
@@ -359,6 +352,7 @@ func pinState(ctx context.Context, in info, st *submodule.Status, pin, head, bra
return err
}
st.CheckedOut = st.Pinned
var out string
if head != pin {
if st.CheckedOut, err = commit(ctx, in.dir, head); err != nil {
return err
@@ -417,6 +411,22 @@ func pinState(ctx context.Context, in info, st *submodule.Status, pin, head, bra
return nil
}
// remoteContaining lists the remote branches containing pin, oldest tip
// first, without the origin/HEAD symref.
func remoteContaining(ctx context.Context, dir, pin string) ([]string, error) {
out, err := run(ctx, dir, "for-each-ref", "--contains", pin, "--sort=committerdate", "--format=%(refname)", "refs/remotes")
if err != nil {
return nil, err
}
var containing []string
for _, r := range strings.Fields(out) {
if !strings.HasSuffix(r, "/HEAD") {
containing = append(containing, strings.TrimPrefix(r, "refs/remotes/"))
}
}
return containing, nil
}
// resolve finds the ref the pin is compared with, following the order in
// docs/state-model.md: the target (see targetRef) if the pin is on it, else the
// branch containing the pin that is closest to it, else the target anyway,