1package xrpc
2
3import (
4 "encoding/json"
5 "net/http"
6 "net/url"
7 "strconv"
8
9 "tangled.sh/tangled.sh/core/knotserver/git"
10 "tangled.sh/tangled.sh/core/types"
11 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
12)
13
14func (x *Xrpc) RepoLog(w http.ResponseWriter, r *http.Request) {
15 repo := r.URL.Query().Get("repo")
16 repoPath, err := x.parseRepoParam(repo)
17 if err != nil {
18 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
19 return
20 }
21
22 refParam := r.URL.Query().Get("ref")
23 if refParam == "" {
24 writeError(w, xrpcerr.NewXrpcError(
25 xrpcerr.WithTag("InvalidRequest"),
26 xrpcerr.WithMessage("missing ref parameter"),
27 ), http.StatusBadRequest)
28 return
29 }
30
31 path := r.URL.Query().Get("path")
32 cursor := r.URL.Query().Get("cursor")
33
34 limit := 50 // default
35 if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
36 if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
37 limit = l
38 }
39 }
40
41 ref, err := url.QueryUnescape(refParam)
42 if err != nil {
43 writeError(w, xrpcerr.NewXrpcError(
44 xrpcerr.WithTag("InvalidRequest"),
45 xrpcerr.WithMessage("invalid ref parameter"),
46 ), http.StatusBadRequest)
47 return
48 }
49
50 gr, err := git.Open(repoPath, ref)
51 if err != nil {
52 writeError(w, xrpcerr.NewXrpcError(
53 xrpcerr.WithTag("RefNotFound"),
54 xrpcerr.WithMessage("repository or ref not found"),
55 ), http.StatusNotFound)
56 return
57 }
58
59 offset := 0
60 if cursor != "" {
61 if o, err := strconv.Atoi(cursor); err == nil && o >= 0 {
62 offset = o
63 }
64 }
65
66 commits, err := gr.Commits(offset, limit)
67 if err != nil {
68 x.Logger.Error("fetching commits", "error", err.Error())
69 writeError(w, xrpcerr.NewXrpcError(
70 xrpcerr.WithTag("PathNotFound"),
71 xrpcerr.WithMessage("failed to read commit log"),
72 ), http.StatusNotFound)
73 return
74 }
75
76 total, err := gr.TotalCommits()
77 if err != nil {
78 x.Logger.Error("fetching total commits", "error", err.Error())
79 writeError(w, xrpcerr.NewXrpcError(
80 xrpcerr.WithTag("InternalServerError"),
81 xrpcerr.WithMessage("failed to fetch total commits"),
82 ), http.StatusNotFound)
83 return
84 }
85
86 // Create response using existing types.RepoLogResponse
87 response := types.RepoLogResponse{
88 Commits: commits,
89 Ref: ref,
90 Page: (offset / limit) + 1,
91 PerPage: limit,
92 Total: total,
93 }
94
95 if path != "" {
96 response.Description = path
97 }
98
99 response.Log = true
100
101 // Write JSON response directly
102 w.Header().Set("Content-Type", "application/json")
103 if err := json.NewEncoder(w).Encode(response); err != nil {
104 x.Logger.Error("failed to encode response", "error", err)
105 writeError(w, xrpcerr.NewXrpcError(
106 xrpcerr.WithTag("InternalServerError"),
107 xrpcerr.WithMessage("failed to encode response"),
108 ), http.StatusInternalServerError)
109 return
110 }
111}