package publish import ( "bufio" "bytes" "context" "fmt" "sort" "strings" "sync" "github.com/npfaro/dumbforge/internal/s3store" ) type Ref struct { OID string Peeled string } type Metadata struct { Head string Refs map[string]Ref Packs map[string]struct{} SiteVersion string } func (m Metadata) BranchNames() []string { var names []string for name := range m.Refs { if strings.HasPrefix(name, "refs/heads/") { names = append(names, strings.TrimPrefix(name, "refs/heads/")) } } sort.Strings(names) return names } func LoadMetadata(ctx context.Context, store *s3store.Store) (Metadata, error) { metadata := Metadata{ Refs: map[string]Ref{}, Packs: map[string]struct{}{}, } var refsObject, headObject, packObject, siteVersionObject s3store.Object var refsErr, headErr, packErr, siteVersionErr error var requests sync.WaitGroup requests.Add(4) go func() { defer requests.Done() refsObject, refsErr = store.Get(ctx, "info/refs") }() go func() { defer requests.Done() headObject, headErr = store.Get(ctx, "HEAD") }() go func() { defer requests.Done() packObject, packErr = store.Get(ctx, "objects/info/packs") }() go func() { defer requests.Done() siteVersionObject, siteVersionErr = store.Get(ctx, ".dumbforge/site-version") }() requests.Wait() err := refsErr if err != nil && !s3store.IsNotFound(err) { return Metadata{}, fmt.Errorf("read remote refs: %w", err) } if err == nil { if err := parseInfoRefs(refsObject.Data, metadata.Refs); err != nil { return Metadata{}, err } } err = headErr if err != nil && !s3store.IsNotFound(err) { return Metadata{}, fmt.Errorf("read remote HEAD: %w", err) } if err == nil { line := strings.TrimSpace(string(headObject.Data)) if strings.HasPrefix(line, "ref: ") { metadata.Head = strings.TrimSpace(strings.TrimPrefix(line, "ref: ")) } } err = packErr if err != nil && !s3store.IsNotFound(err) { return Metadata{}, fmt.Errorf("read remote pack list: %w", err) } if err == nil { parsePackList(packObject.Data, metadata.Packs) } err = siteVersionErr if err != nil && !s3store.IsNotFound(err) { return Metadata{}, fmt.Errorf("read site version: %w", err) } if err == nil { metadata.SiteVersion = strings.TrimSpace(string(siteVersionObject.Data)) } return metadata, nil } func parseInfoRefs(data []byte, refs map[string]Ref) error { scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := scanner.Text() parts := strings.SplitN(line, "\t", 2) if len(parts) != 2 { return fmt.Errorf("malformed info/refs line %q", line) } oid, name := parts[0], parts[1] if strings.HasSuffix(name, "^{}") { base := strings.TrimSuffix(name, "^{}") ref := refs[base] ref.Peeled = oid refs[base] = ref continue } ref := refs[name] ref.OID = oid refs[name] = ref } return scanner.Err() } func parsePackList(data []byte, packs map[string]struct{}) { scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, "P ") { packs[strings.TrimSpace(strings.TrimPrefix(line, "P "))] = struct{}{} } } } func renderInfoRefs(refs map[string]Ref) []byte { names := make([]string, 0, len(refs)) for name := range refs { names = append(names, name) } sort.Strings(names) var result strings.Builder for _, name := range names { ref := refs[name] fmt.Fprintf(&result, "%s\t%s\n", ref.OID, name) if ref.Peeled != "" && ref.Peeled != ref.OID { fmt.Fprintf(&result, "%s\t%s^{}\n", ref.Peeled, name) } } return []byte(result.String()) } func renderPackList(packs map[string]struct{}) []byte { names := make([]string, 0, len(packs)) for name := range packs { names = append(names, name) } sort.Strings(names) var result strings.Builder for _, name := range names { fmt.Fprintf(&result, "P %s\n", name) } return []byte(result.String()) }