1diff --git a/pkg/git/git.go b/pkg/git/git.go
2index 6221e33dc5d8e7aca9682f8d22c830225b1e306b..b33afa7bf1f8a453ddc4d2da48273b199f821b96 100644
3--- a/pkg/git/git.go
4+++ b/pkg/git/git.go
5@@ -16,8 +16,6 @@ "github.com/go-git/go-git/v5/plumbing"
6 "github.com/go-git/go-git/v5/plumbing/object"
7 )
8
9-var ()
10-
11 var (
12 MissingRefErr = errors.New("Reference not found")
13 TreeForFileErr = errors.New("Trying to get tree of a file")
14@@ -96,30 +94,46 @@ }
15 return c, nil
16 }
17
18-func (g *GitRepository) Commits(count int) ([]*object.Commit, error) {
19+func (g *GitRepository) Commits(count int, from string) ([]*object.Commit, *object.Commit, error) {
20 err := g.validateRef()
21 if err != nil {
22- return nil, err
23+ return nil, nil, err
24+ }
25+
26+ opts := &git.LogOptions{Order: git.LogOrderCommitterTime}
27+
28+ if from != "" {
29+ hash, err := g.repository.ResolveRevision(plumbing.Revision(from))
30+ if err != nil {
31+ return nil, nil, errors.Join(MissingRefErr, err)
32+ }
33+ opts.From = *hash
34 }
35
36- ci, err := g.repository.Log(&git.LogOptions{From: g.ref})
37+ ci, err := g.repository.Log(opts)
38 if err != nil {
39- return nil, fmt.Errorf("commits from ref: %w", err)
40+ return nil, nil, fmt.Errorf("commits from ref: %w", err)
41 }
42
43 commits := []*object.Commit{}
44- // TODO: for now only load first 1000
45- for x := 0; x < count; x++ {
46+ var next *object.Commit
47+
48+ // iterate one more item so we can fetch the next commit
49+ for x := 0; x < (count + 1); x++ {
50 c, err := ci.Next()
51 if err != nil && errors.Is(err, io.EOF) {
52 break
53 } else if err != nil {
54- return nil, err
55+ return nil, nil, err
56+ }
57+ if x == count {
58+ next = c
59+ } else {
60+ commits = append(commits, c)
61 }
62- commits = append(commits, c)
63 }
64
65- return commits, nil
66+ return commits, next, nil
67 }
68
69 func (g *GitRepository) Head() (*plumbing.Reference, error) {
70@@ -438,7 +452,6 @@ if t.tag != nil {
71 return t.tag.Message
72 }
73 return ""
74-
75 }
76
77 func (self *tagList) Len() int {
78diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
79index 6225b1a444f98701f9049c856c2b63e5c248adc6..436d36430c4c952af21c7fb95c9af8018cdd414f 100644
80--- a/pkg/handler/git/handler.go
81+++ b/pkg/handler/git/handler.go
82@@ -130,7 +130,7 @@ if err != nil {
83 return err
84 }
85
86- commits, err := g.gitService.ListCommits(name, "", 10)
87+ commits, _, err := g.gitService.ListCommits(name, "", "", 10)
88 if err != nil {
89 return err
90 }
91@@ -324,8 +324,9 @@ func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
92 ext.SetHTML(w)
93 name := r.PathValue("name")
94 ref := r.PathValue("ref")
95+ from := r.URL.Query().Get("from")
96
97- commits, err := g.gitService.ListCommits(name, ref, 1000)
98+ commits, next, err := g.gitService.ListCommits(name, ref, from, 100)
99 if err != nil {
100 return err
101 }
102@@ -335,6 +336,7 @@ Name: name,
103 Ref: ref,
104 GitItemBase: &templates.GitItemLogPage{
105 Commits: commits,
106+ Next: next,
107 },
108 }
109 templates.WritePageTemplate(w, gitList, r.Context())
110diff --git a/pkg/service/git.go b/pkg/service/git.go
111index 12d238e9dc388f2b25e7dc4fb417c3c4d7579de8..2d00715f57ab9553c54a3d02a88bb8feb34cb817 100644
112--- a/pkg/service/git.go
113+++ b/pkg/service/git.go
114@@ -81,22 +81,22 @@
115 return repos, nil
116 }
117
118-func (g *GitService) ListCommits(name, ref string, count int) ([]*object.Commit, error) {
119+func (g *GitService) ListCommits(name, ref, from string, count int) ([]*object.Commit, *object.Commit, error) {
120 r := g.configRepo.GetByName(name)
121 if r == nil {
122- return nil, ErrRepositoryNotFound
123+ return nil, nil, ErrRepositoryNotFound
124 }
125
126 repo, err := git.OpenRepository(r.Path)
127 if err != nil {
128- return nil, err
129+ return nil, nil, err
130 }
131
132 err = repo.SetRef(ref)
133 if err != nil {
134- return nil, err
135+ return nil, nil, err
136 }
137- return repo.Commits(count)
138+ return repo.Commits(count, from)
139 }
140
141 func (g *GitService) LastCommit(name, ref string) (*object.Commit, error) {
142diff --git a/templates/gititemlog.qtpl b/templates/gititemlog.qtpl
143index e5bfc1b7f2e76c0fa7e666fd480f03e20bb7bad6..b0c8cec1f2ee8ea9f8cae4dfca45e51e12b1c51e 100644
144--- a/templates/gititemlog.qtpl
145+++ b/templates/gititemlog.qtpl
146@@ -3,6 +3,7 @@
147 {% code
148 type GitItemLogPage struct {
149 Commits []*object.Commit
150+ Next *object.Commit
151 }
152 %}
153
154@@ -13,5 +14,9 @@ <div class="event-list">
155 {% for _, c := range g.Commits %}
156 {%= Commit(name, c, false) %}
157 {% endfor %}
158+ {% if g.Next != nil %}
159+ <a href="/{%s name %}/log/{%s ref %}/?from={%s g.Next.Hash.String() %}" class="btn btn-primary">Next</a>
160+ {% endif %}
161+
162 </div>
163 {% endfunc %}
164diff --git a/templates/gititemlog.qtpl.go b/templates/gititemlog.qtpl.go
165index 20fc1f54a9f6ce2ea4a7a14cbb6dc6e6d60b5d6e..719b71f9bb4f74125415288c3438eca26a55624c 100644
166--- a/templates/gititemlog.qtpl.go
167+++ b/templates/gititemlog.qtpl.go
168@@ -23,88 +23,113 @@
169 //line templates/gititemlog.qtpl:4
170 type GitItemLogPage struct {
171 Commits []*object.Commit
172+ Next *object.Commit
173 }
174
175-//line templates/gititemlog.qtpl:9
176+//line templates/gititemlog.qtpl:10
177 func (g *GitItemLogPage) StreamNav(qw422016 *qt422016.Writer, name, ref string) {
178-//line templates/gititemlog.qtpl:9
179+//line templates/gititemlog.qtpl:10
180 StreamGitItemNav(qw422016, name, ref, Log)
181-//line templates/gititemlog.qtpl:9
182+//line templates/gititemlog.qtpl:10
183 }
184
185-//line templates/gititemlog.qtpl:9
186+//line templates/gititemlog.qtpl:10
187 func (g *GitItemLogPage) WriteNav(qq422016 qtio422016.Writer, name, ref string) {
188-//line templates/gititemlog.qtpl:9
189+//line templates/gititemlog.qtpl:10
190 qw422016 := qt422016.AcquireWriter(qq422016)
191-//line templates/gititemlog.qtpl:9
192+//line templates/gititemlog.qtpl:10
193 g.StreamNav(qw422016, name, ref)
194-//line templates/gititemlog.qtpl:9
195+//line templates/gititemlog.qtpl:10
196 qt422016.ReleaseWriter(qw422016)
197-//line templates/gititemlog.qtpl:9
198+//line templates/gititemlog.qtpl:10
199 }
200
201-//line templates/gititemlog.qtpl:9
202+//line templates/gititemlog.qtpl:10
203 func (g *GitItemLogPage) Nav(name, ref string) string {
204-//line templates/gititemlog.qtpl:9
205+//line templates/gititemlog.qtpl:10
206 qb422016 := qt422016.AcquireByteBuffer()
207-//line templates/gititemlog.qtpl:9
208+//line templates/gititemlog.qtpl:10
209 g.WriteNav(qb422016, name, ref)
210-//line templates/gititemlog.qtpl:9
211+//line templates/gititemlog.qtpl:10
212 qs422016 := string(qb422016.B)
213-//line templates/gititemlog.qtpl:9
214+//line templates/gititemlog.qtpl:10
215 qt422016.ReleaseByteBuffer(qb422016)
216-//line templates/gititemlog.qtpl:9
217+//line templates/gititemlog.qtpl:10
218 return qs422016
219-//line templates/gititemlog.qtpl:9
220+//line templates/gititemlog.qtpl:10
221 }
222
223-//line templates/gititemlog.qtpl:11
224+//line templates/gititemlog.qtpl:12
225 func (g *GitItemLogPage) StreamGitContent(qw422016 *qt422016.Writer, name, ref string) {
226-//line templates/gititemlog.qtpl:11
227+//line templates/gititemlog.qtpl:12
228 qw422016.N().S(`
229 <div class="event-list">
230 `)
231-//line templates/gititemlog.qtpl:13
232+//line templates/gititemlog.qtpl:14
233 for _, c := range g.Commits {
234-//line templates/gititemlog.qtpl:13
235+//line templates/gititemlog.qtpl:14
236 qw422016.N().S(`
237 `)
238-//line templates/gititemlog.qtpl:14
239+//line templates/gititemlog.qtpl:15
240 StreamCommit(qw422016, name, c, false)
241-//line templates/gititemlog.qtpl:14
242+//line templates/gititemlog.qtpl:15
243+ qw422016.N().S(`
244+ `)
245+//line templates/gititemlog.qtpl:16
246+ }
247+//line templates/gititemlog.qtpl:16
248+ qw422016.N().S(`
249+ `)
250+//line templates/gititemlog.qtpl:17
251+ if g.Next != nil {
252+//line templates/gititemlog.qtpl:17
253 qw422016.N().S(`
254+ <a href="/`)
255+//line templates/gititemlog.qtpl:18
256+ qw422016.E().S(name)
257+//line templates/gititemlog.qtpl:18
258+ qw422016.N().S(`/log/`)
259+//line templates/gititemlog.qtpl:18
260+ qw422016.E().S(ref)
261+//line templates/gititemlog.qtpl:18
262+ qw422016.N().S(`/?from=`)
263+//line templates/gititemlog.qtpl:18
264+ qw422016.E().S(g.Next.Hash.String())
265+//line templates/gititemlog.qtpl:18
266+ qw422016.N().S(`" class="btn btn-primary">Next</a>
267 `)
268-//line templates/gititemlog.qtpl:15
269+//line templates/gititemlog.qtpl:19
270 }
271-//line templates/gititemlog.qtpl:15
272+//line templates/gititemlog.qtpl:19
273 qw422016.N().S(`
274+
275 </div>
276 `)
277-//line templates/gititemlog.qtpl:17
278+//line templates/gititemlog.qtpl:22
279 }
280
281-//line templates/gititemlog.qtpl:17
282+//line templates/gititemlog.qtpl:22
283 func (g *GitItemLogPage) WriteGitContent(qq422016 qtio422016.Writer, name, ref string) {
284-//line templates/gititemlog.qtpl:17
285+//line templates/gititemlog.qtpl:22
286 qw422016 := qt422016.AcquireWriter(qq422016)
287-//line templates/gititemlog.qtpl:17
288+//line templates/gititemlog.qtpl:22
289 g.StreamGitContent(qw422016, name, ref)
290-//line templates/gititemlog.qtpl:17
291+//line templates/gititemlog.qtpl:22
292 qt422016.ReleaseWriter(qw422016)
293-//line templates/gititemlog.qtpl:17
294+//line templates/gititemlog.qtpl:22
295 }
296
297-//line templates/gititemlog.qtpl:17
298+//line templates/gititemlog.qtpl:22
299 func (g *GitItemLogPage) GitContent(name, ref string) string {
300-//line templates/gititemlog.qtpl:17
301+//line templates/gititemlog.qtpl:22
302 qb422016 := qt422016.AcquireByteBuffer()
303-//line templates/gititemlog.qtpl:17
304+//line templates/gititemlog.qtpl:22
305 g.WriteGitContent(qb422016, name, ref)
306-//line templates/gititemlog.qtpl:17
307+//line templates/gititemlog.qtpl:22
308 qs422016 := string(qb422016.B)
309-//line templates/gititemlog.qtpl:17
310+//line templates/gititemlog.qtpl:22
311 qt422016.ReleaseByteBuffer(qb422016)
312-//line templates/gititemlog.qtpl:17
313+//line templates/gititemlog.qtpl:22
314 return qs422016
315-//line templates/gititemlog.qtpl:17
316+//line templates/gititemlog.qtpl:22
317 }