1package web
  2
  3import (
  4	"math"
  5	"net/http"
  6	"strconv"
  7
  8	"github.com/charmbracelet/log/v2"
  9	"github.com/charmbracelet/soft-serve/git"
 10	"github.com/charmbracelet/soft-serve/pkg/config"
 11	"github.com/charmbracelet/soft-serve/pkg/proto"
 12)
 13
 14// BranchInfo contains branch reference and its latest commit.
 15type BranchInfo struct {
 16	Ref    *git.Reference
 17	Commit *git.Commit
 18}
 19
 20// BranchesData contains data for rendering branches listing.
 21type BranchesData struct {
 22	Repo          proto.Repository
 23	DefaultBranch string
 24	Branches      []BranchInfo
 25	ActiveTab     string
 26	Page          int
 27	TotalPages    int
 28	TotalBranches int
 29	HasPrevPage   bool
 30	HasNextPage   bool
 31	ServerName    string
 32}
 33
 34// repoBranches handles branches listing page.
 35func repoBranches(w http.ResponseWriter, r *http.Request) {
 36	ctx := r.Context()
 37	logger := log.FromContext(ctx)
 38	cfg := config.FromContext(ctx)
 39	repo := proto.RepositoryFromContext(ctx)
 40
 41	gr, err := openRepository(repo)
 42	if err != nil {
 43		logger.Debug("failed to open repository", "repo", repo.Name(), "err", err)
 44		renderInternalServerError(w, r)
 45		return
 46	}
 47
 48	defaultBranch := getDefaultBranch(gr)
 49
 50	page := 1
 51	if pageStr := r.URL.Query().Get("page"); pageStr != "" {
 52		if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
 53			page = p
 54		}
 55	}
 56
 57	// First fetch to get total count and calculate pages
 58	paginatedRefItems, totalBranches, err := FetchRefsPaginated(gr, RefTypeBranch, 0, 1, defaultBranch)
 59	if err != nil {
 60		logger.Debug("failed to fetch branches", "repo", repo.Name(), "err", err)
 61		renderInternalServerError(w, r)
 62		return
 63	}
 64
 65	totalPages := int(math.Ceil(float64(totalBranches) / float64(defaultBranchesPerPage)))
 66	if totalPages < 1 {
 67		totalPages = 1
 68	}
 69
 70	// Clamp page before computing offset
 71	if page > totalPages {
 72		page = totalPages
 73	}
 74	if page < 1 {
 75		page = 1
 76	}
 77
 78	// Calculate offset for pagination
 79	offset := (page - 1) * defaultBranchesPerPage
 80
 81	// Fetch only the branches we need for this page, pre-sorted
 82	paginatedRefItems, totalBranches, err = FetchRefsPaginated(gr, RefTypeBranch, offset, defaultBranchesPerPage, defaultBranch)
 83	if err != nil {
 84		logger.Debug("failed to fetch branches", "repo", repo.Name(), "err", err)
 85		renderInternalServerError(w, r)
 86		return
 87	}
 88
 89	var paginatedBranches []BranchInfo
 90	for _, refItem := range paginatedRefItems {
 91		paginatedBranches = append(paginatedBranches, BranchInfo{
 92			Ref:    refItem.Reference,
 93			Commit: refItem.Commit,
 94		})
 95	}
 96
 97	data := BranchesData{
 98		Repo:          repo,
 99		DefaultBranch: defaultBranch,
100		Branches:      paginatedBranches,
101		ActiveTab:     "branches",
102		Page:          page,
103		TotalPages:    totalPages,
104		TotalBranches: totalBranches,
105		HasPrevPage:   page > 1,
106		HasNextPage:   page < totalPages,
107		ServerName:    cfg.Name,
108	}
109
110	renderHTML(w, "branches.html", data)
111}