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 RepoBaseData
23 PaginationData
24 Branches []BranchInfo
25 TotalBranches int
26}
27
28// repoBranches handles branches listing page.
29func repoBranches(w http.ResponseWriter, r *http.Request) {
30 ctx := r.Context()
31 logger := log.FromContext(ctx)
32 cfg := config.FromContext(ctx)
33 repo := proto.RepositoryFromContext(ctx)
34
35 gr, err := openRepository(repo)
36 if err != nil {
37 logger.Debug("failed to open repository", "repo", repo.Name(), "err", err)
38 renderInternalServerError(w, r)
39 return
40 }
41
42 defaultBranch := getDefaultBranch(gr)
43
44 page := 1
45 if pageStr := r.URL.Query().Get("page"); pageStr != "" {
46 if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
47 page = p
48 }
49 }
50
51 // First fetch to get total count and calculate pages
52 _, totalBranches, err := FetchRefsPaginated(gr, RefTypeBranch, 0, 1, defaultBranch)
53 if err != nil {
54 logger.Debug("failed to fetch branches", "repo", repo.Name(), "err", err)
55 renderInternalServerError(w, r)
56 return
57 }
58
59 totalPages := int(math.Ceil(float64(totalBranches) / float64(defaultBranchesPerPage)))
60 if totalPages < 1 {
61 totalPages = 1
62 }
63
64 // Clamp page before computing offset
65 if page > totalPages {
66 page = totalPages
67 }
68 if page < 1 {
69 page = 1
70 }
71
72 // Calculate offset for pagination
73 offset := (page - 1) * defaultBranchesPerPage
74
75 // Fetch only the branches we need for this page, pre-sorted
76 paginatedRefItems, _, err := FetchRefsPaginated(gr, RefTypeBranch, offset, defaultBranchesPerPage, defaultBranch)
77 if err != nil {
78 logger.Debug("failed to fetch branches", "repo", repo.Name(), "err", err)
79 renderInternalServerError(w, r)
80 return
81 }
82
83 var paginatedBranches []BranchInfo
84 for _, refItem := range paginatedRefItems {
85 paginatedBranches = append(paginatedBranches, BranchInfo{
86 Ref: refItem.Reference,
87 Commit: refItem.Commit,
88 })
89 }
90
91 repoDisplayName := repo.ProjectName()
92 if repoDisplayName == "" {
93 repoDisplayName = repo.Name()
94 }
95
96 description := getRepoDescriptionOrFallback(repo, "Branches in "+repoDisplayName)
97
98 data := BranchesData{
99 RepoBaseData: RepoBaseData{
100 BaseData: BaseData{
101 ServerName: cfg.Name,
102 ActiveTab: "branches",
103 Title: "Branches | " + repoDisplayName,
104 Description: description,
105 },
106 Repo: repo,
107 DefaultBranch: defaultBranch,
108 },
109 PaginationData: PaginationData{
110 Page: page,
111 TotalPages: totalPages,
112 HasPrevPage: page > 1,
113 HasNextPage: page < totalPages,
114 },
115 Branches: paginatedBranches,
116 TotalBranches: totalBranches,
117 }
118
119 renderHTML(w, "branches.html", data)
120}