Jaypore CI

> Jaypore CI: Minimal, Offline, Local CI system.
Log | Files | Refs | README | LICENSE

commit dd2990ee488d38e5747b9c42bd90f2254385cf4b
parent 7eaa765f0b4887eeaf5574ad2b0ee62ff9d150e5
Author: Arjoonn Sharma <arjoonn@midpathsoftware.com>
Date:   Sat, 28 Feb 2026 13:33:19 +0530

x

Diffstat:
Minternal/jci/web.go | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mscripts/check_go.sh | 4++--
2 files changed, 147 insertions(+), 12 deletions(-)

diff --git a/internal/jci/web.go b/internal/jci/web.go @@ -12,9 +12,8 @@ import ( // BranchInfo holds branch data for the UI type BranchInfo struct { - Name string `json:"name"` - IsRemote bool `json:"isRemote"` - Commits []CommitInfo `json:"commits"` + Name string `json:"name"` + IsRemote bool `json:"isRemote"` } // CommitInfo holds commit data for the UI @@ -60,12 +59,18 @@ func Web(args []string) error { func handleRequest(w http.ResponseWriter, r *http.Request, repoRoot string) { path := r.URL.Path - // API endpoint for branch data + // API endpoint for branch data (names only, no commits) if path == "/api/branches" { serveBranchesAPI(w) return } + // API endpoint for paginated commits for a branch + if path == "/api/commits" { + serveCommitsAPI(w, r) + return + } + // API endpoint for commit info if strings.HasPrefix(path, "/api/commit/") { commit := strings.TrimPrefix(path, "/api/commit/") @@ -332,7 +337,7 @@ func serveCommitAPI(w http.ResponseWriter, commit string) { json.NewEncoder(w).Encode(detail) } -// serveBranchesAPI returns branch/commit data as JSON +// serveBranchesAPI returns branch names as JSON (no commits for fast load) func serveBranchesAPI(w http.ResponseWriter) { branches, err := getLocalBranches() if err != nil { @@ -342,14 +347,9 @@ func serveBranchesAPI(w http.ResponseWriter) { var branchInfos []BranchInfo for _, branch := range branches { - commits, err := getBranchCommits(branch, 20) - if err != nil { - continue - } branchInfos = append(branchInfos, BranchInfo{ Name: branch, IsRemote: false, - Commits: commits, }) } @@ -357,6 +357,141 @@ func serveBranchesAPI(w http.ResponseWriter) { json.NewEncoder(w).Encode(branchInfos) } +// CommitsPage holds a page of commits for the paginated API +type CommitsPage struct { + Branch string `json:"branch"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + HasMore bool `json:"hasMore"` + Commits []CommitInfo `json:"commits"` +} + +const commitsPageSize = 100 + +// serveCommitsAPI returns a paginated list of commits for a branch. +// Query params: branch (required), page (optional, 0-indexed, default 0) +func serveCommitsAPI(w http.ResponseWriter, r *http.Request) { + branch := r.URL.Query().Get("branch") + if branch == "" { + http.Error(w, "branch query parameter is required", 400) + return + } + + page := 0 + if p := r.URL.Query().Get("page"); p != "" { + fmt.Sscanf(p, "%d", &page) + if page < 0 { + page = 0 + } + } + + // Fetch one extra commit beyond the page size to detect whether more pages exist + offset := page * commitsPageSize + limit := commitsPageSize + 1 + + commits, err := getBranchCommitsPaginated(branch, offset, limit) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + hasMore := len(commits) > commitsPageSize + if hasMore { + commits = commits[:commitsPageSize] + } + + result := CommitsPage{ + Branch: branch, + Page: page, + PageSize: commitsPageSize, + HasMore: hasMore, + Commits: commits, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} + +// getBranchCommitsPaginated returns commits for a branch starting at the given offset. +func getBranchCommitsPaginated(branch string, offset, limit int) ([]CommitInfo, error) { + out, err := git("log", branch, + fmt.Sprintf("--skip=%d", offset), + fmt.Sprintf("--max-count=%d", limit), + "--format=%H|%s") + if err != nil { + return nil, err + } + if out == "" { + return nil, nil + } + + // Get local JCI refs (single-run) + jciRefs, _ := ListJCIRefs() + jciSet := make(map[string]bool) + for _, ref := range jciRefs { + commit := strings.TrimPrefix(ref, "jci/") + jciSet[commit] = true + } + + // Get local JCI run refs (multi-run): commit -> list of run refs + jciRuns := make(map[string][]string) + runRefs, _ := ListJCIRunRefs() + for _, ref := range runRefs { + parts := strings.Split(strings.TrimPrefix(ref, "refs/jci-runs/"), "/") + if len(parts) >= 2 { + commit := parts[0] + jciRuns[commit] = append(jciRuns[commit], ref) + } + } + + // Get remote JCI refs for CI push status + remoteCI := getRemoteJCIRefs("origin") + + var commits []CommitInfo + for _, line := range strings.Split(out, "\n") { + parts := strings.SplitN(line, "|", 2) + if len(parts) != 2 { + continue + } + hash := parts[0] + msg := parts[1] + + hasCI := jciSet[hash] || len(jciRuns[hash]) > 0 + commit := CommitInfo{ + Hash: hash, + ShortHash: hash[:7], + Message: msg, + HasCI: hasCI, + CIPushed: remoteCI["refs/jci/"+hash], + } + + if jciSet[hash] { + commit.CIStatus = getCIStatus(hash) + } + + for _, runRef := range jciRuns[hash] { + rparts := strings.Split(strings.TrimPrefix(runRef, "refs/jci-runs/"), "/") + if len(rparts) >= 2 { + runID := rparts[1] + status := getCIStatusFromRef(runRef) + commit.Runs = append(commit.Runs, RunInfo{ + RunID: runID, + Status: status, + Ref: runRef, + }) + } + } + + if commit.CIStatus == "" && len(commit.Runs) > 0 { + commit.CIStatus = commit.Runs[len(commit.Runs)-1].Status + } + + commits = append(commits, commit) + } + + return commits, nil +} + func showMainPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") fmt.Fprint(w, `<!DOCTYPE html> diff --git a/scripts/check_go.sh b/scripts/check_go.sh @@ -11,8 +11,8 @@ if ! command -v go >/dev/null 2>&1; then fi echo "Running gofmt checks..." -gofmt -e cmd -gofmt -e internal +gofmt -e cmd > /dev/null +gofmt -e internal > /dev/null echo "Running go vet..." go vet ./...