blob: f1969a0d02cbb9fc7e7e55a5f7192c10d2d9a10d [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles;
16
17import static com.google.common.base.Preconditions.checkArgument;
18import static com.google.common.base.Preconditions.checkNotNull;
19import static com.google.common.base.Preconditions.checkState;
20
21import org.eclipse.jgit.errors.IncorrectObjectTypeException;
22import org.eclipse.jgit.errors.MissingObjectException;
23import org.eclipse.jgit.errors.RevWalkException;
24import org.eclipse.jgit.lib.ObjectId;
25import org.eclipse.jgit.revwalk.RevCommit;
26import org.eclipse.jgit.revwalk.RevWalk;
27
28import java.io.IOException;
29import java.util.ArrayDeque;
30import java.util.Deque;
31import java.util.Iterator;
32
33import javax.annotation.Nullable;
34
35/**
36 * Wrapper around {@link RevWalk} that paginates for Gitiles.
37 *
38 * A single page of a shortlog is defined by a revision range, such as "master"
39 * or "master..next", a page size, and a start commit, such as "c0ffee". The
40 * distance between the first commit in the walk ("next") and the first commit
41 * in the page may be arbitrarily long, but in order to present the commit list
42 * in a stable way, we must always start from the first commit in the walk. This
43 * is because there may be arbitrary merge commits between "c0ffee" and "next"
44 * that effectively insert arbitrary commits into the history starting from
45 * "c0ffee".
46 */
47class Paginator implements Iterable<RevCommit> {
48 private final RevWalk walk;
Dave Borowitz9de65952012-08-13 16:09:45 -070049 private final int limit;
Dave Borowitz7756efb2014-07-30 08:45:20 -070050 private final ObjectId prevStart;
Dave Borowitz9de65952012-08-13 16:09:45 -070051
Dave Borowitz7756efb2014-07-30 08:45:20 -070052 private RevCommit first;
Dave Borowitz9de65952012-08-13 16:09:45 -070053 private boolean done;
Dave Borowitz9de65952012-08-13 16:09:45 -070054 private int n;
Dave Borowitz9de65952012-08-13 16:09:45 -070055 private ObjectId nextStart;
56
57 /**
Dave Borowitz7756efb2014-07-30 08:45:20 -070058 * Construct a paginator and walk eagerly to the first returned commit.
59 *
Dave Borowitz9de65952012-08-13 16:09:45 -070060 * @param walk revision walk.
61 * @param limit page size.
62 * @param start commit at which to start the walk, or null to start at the
63 * beginning.
64 */
Dave Borowitz7756efb2014-07-30 08:45:20 -070065 Paginator(RevWalk walk, int limit, @Nullable ObjectId start) throws MissingObjectException,
66 IncorrectObjectTypeException, IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070067 this.walk = checkNotNull(walk, "walk");
Dave Borowitz9de65952012-08-13 16:09:45 -070068 checkArgument(limit > 0, "limit must be positive: %s", limit);
69 this.limit = limit;
Dave Borowitz7756efb2014-07-30 08:45:20 -070070
71
72 Deque<ObjectId> prevBuffer = new ArrayDeque<ObjectId>(start != null ? limit : 0);
73 while (true) {
74 RevCommit commit = walk.next();
75 if (commit == null) {
76 done = true;
77 break;
78 }
79 if (start == null || start.equals(commit)) {
80 first = commit;
81 break;
82 }
83 if (prevBuffer.size() == limit) {
84 prevBuffer.remove();
85 }
86 prevBuffer.add(commit);
87 }
88 prevStart = prevBuffer.pollFirst();
Dave Borowitz9de65952012-08-13 16:09:45 -070089 }
90
91 /**
92 * Get the next element in this page of the walk.
93 *
94 * @return the next element, or null if the walk is finished.
95 *
96 * @throws MissingObjectException See {@link RevWalk#next()}.
97 * @throws IncorrectObjectTypeException See {@link RevWalk#next()}.
98 * @throws IOException See {@link RevWalk#next()}.
99 */
100 public RevCommit next() throws MissingObjectException, IncorrectObjectTypeException,
101 IOException {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700102 if (done) {
103 return null;
104 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700105 RevCommit commit;
Dave Borowitz7756efb2014-07-30 08:45:20 -0700106 if (first != null) {
107 commit = first;
108 first = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700109 } else {
110 commit = walk.next();
111 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700112 if (++n == limit) {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700113 nextStart = walk.next();
Dave Borowitz9de65952012-08-13 16:09:45 -0700114 done = true;
Dave Borowitz7756efb2014-07-30 08:45:20 -0700115 } else if (commit == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700116 done = true;
Dave Borowitz9de65952012-08-13 16:09:45 -0700117 }
118 return commit;
119 }
120
121 /**
122 * @return the ID at the start of the page of results preceding this one, or
123 * null if this is the first page.
124 */
125 public ObjectId getPreviousStart() {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700126 return prevStart;
Dave Borowitz9de65952012-08-13 16:09:45 -0700127 }
128
129 /**
130 * @return the ID at the start of the page of results after this one, or null
131 * if this is the last page.
132 */
133 public ObjectId getNextStart() {
134 checkState(done, "getNextStart() invalid before walk done");
135 return nextStart;
136 }
137
138 /**
139 * @return an iterator over the commits in this walk.
140 * @throws RevWalkException if an error occurred, wrapping the checked
141 * exception from {@link #next()}.
142 */
143 @Override
144 public Iterator<RevCommit> iterator() {
145 return new Iterator<RevCommit>() {
146 RevCommit next = nextUnchecked();
147
148 @Override
149 public boolean hasNext() {
150 return next != null;
151 }
152
153 @Override
154 public RevCommit next() {
155 RevCommit r = next;
156 next = nextUnchecked();
157 return r;
158 }
159
160 @Override
161 public void remove() {
162 throw new UnsupportedOperationException();
163 }
164 };
165 }
166
Dave Borowitz27fada42013-11-01 11:09:49 -0700167 public int getLimit() {
168 return limit;
169 }
170
171 public RevWalk getWalk() {
172 return walk;
173 }
174
Dave Borowitz9de65952012-08-13 16:09:45 -0700175 private RevCommit nextUnchecked() {
176 try {
177 return next();
178 } catch (MissingObjectException e) {
179 throw new RevWalkException(e);
180 } catch (IncorrectObjectTypeException e) {
181 throw new RevWalkException(e);
182 } catch (IOException e) {
183 throw new RevWalkException(e);
184 }
185 }
186}