blob: 47c44708d10e1397a3da45e2cb516cec2627ee72 [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
Dave Borowitz3b744b12016-08-19 16:11:10 -040021import java.io.IOException;
22import java.util.ArrayDeque;
23import java.util.Deque;
24import java.util.HashMap;
25import java.util.Iterator;
26import java.util.Map;
27import javax.annotation.Nullable;
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040028import org.eclipse.jgit.diff.DiffEntry;
Dave Borowitz9de65952012-08-13 16:09:45 -070029import org.eclipse.jgit.errors.IncorrectObjectTypeException;
30import org.eclipse.jgit.errors.MissingObjectException;
31import org.eclipse.jgit.errors.RevWalkException;
32import org.eclipse.jgit.lib.ObjectId;
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040033import org.eclipse.jgit.revwalk.FollowFilter;
34import org.eclipse.jgit.revwalk.RenameCallback;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import org.eclipse.jgit.revwalk.RevCommit;
36import org.eclipse.jgit.revwalk.RevWalk;
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040037import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz9de65952012-08-13 16:09:45 -070038
Dave Borowitz9de65952012-08-13 16:09:45 -070039/**
40 * Wrapper around {@link RevWalk} that paginates for Gitiles.
41 *
Dave Borowitz40255d52016-08-19 16:16:22 -040042 * <p>A single page of a shortlog is defined by a revision range, such as "master" or
43 * "master..next", a page size, and a start commit, such as "c0ffee". The distance between the first
44 * commit in the walk ("next") and the first commit in the page may be arbitrarily long, but in
45 * order to present the commit list in a stable way, we must always start from the first commit in
46 * the walk. This is because there may be arbitrary merge commits between "c0ffee" and "next" that
47 * effectively insert arbitrary commits into the history starting from "c0ffee".
Dave Borowitz9de65952012-08-13 16:09:45 -070048 */
49class Paginator implements Iterable<RevCommit> {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040050 private static class RenameWatcher extends RenameCallback {
51 private DiffEntry entry;
52
53 @Override
54 public void renamed(DiffEntry entry) {
55 this.entry = entry;
56 }
57
58 private DiffEntry getAndClear() {
59 DiffEntry e = entry;
60 entry = null;
61 return e;
62 }
63 }
64
Dave Borowitz9de65952012-08-13 16:09:45 -070065 private final RevWalk walk;
Dave Borowitz9de65952012-08-13 16:09:45 -070066 private final int limit;
Dave Borowitz7756efb2014-07-30 08:45:20 -070067 private final ObjectId prevStart;
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040068 private final RenameWatcher renameWatcher;
Dave Borowitz9de65952012-08-13 16:09:45 -070069
Dave Borowitz7756efb2014-07-30 08:45:20 -070070 private RevCommit first;
Dave Borowitz9de65952012-08-13 16:09:45 -070071 private boolean done;
Dave Borowitz9de65952012-08-13 16:09:45 -070072 private int n;
Dave Borowitz9de65952012-08-13 16:09:45 -070073 private ObjectId nextStart;
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040074 private Map<ObjectId, DiffEntry> renamed;
Dave Borowitz9de65952012-08-13 16:09:45 -070075
76 /**
Dave Borowitz7756efb2014-07-30 08:45:20 -070077 * Construct a paginator and walk eagerly to the first returned commit.
78 *
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040079 * @param walk revision walk; must be fully initialized before calling.
Dave Borowitz9de65952012-08-13 16:09:45 -070080 * @param limit page size.
Dave Borowitz40255d52016-08-19 16:16:22 -040081 * @param start commit at which to start the walk, or null to start at the beginning.
Dave Borowitz9de65952012-08-13 16:09:45 -070082 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020083 Paginator(RevWalk walk, int limit, @Nullable ObjectId start)
84 throws MissingObjectException, IncorrectObjectTypeException, IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070085 this.walk = checkNotNull(walk, "walk");
Dave Borowitz9de65952012-08-13 16:09:45 -070086 checkArgument(limit > 0, "limit must be positive: %s", limit);
87 this.limit = limit;
Dave Borowitz7756efb2014-07-30 08:45:20 -070088
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040089 TreeFilter filter = walk.getTreeFilter();
90 if (filter instanceof FollowFilter) {
91 renameWatcher = new RenameWatcher();
92 ((FollowFilter) filter).setRenameCallback(renameWatcher);
93 } else {
94 renameWatcher = null;
95 }
Dave Borowitz7756efb2014-07-30 08:45:20 -070096
Dave Borowitz96a6f472014-11-04 16:38:20 -080097 Deque<ObjectId> prevBuffer = new ArrayDeque<>(start != null ? limit : 0);
Dave Borowitz7756efb2014-07-30 08:45:20 -070098 while (true) {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -040099 RevCommit commit = nextWithRename();
Dave Borowitz7756efb2014-07-30 08:45:20 -0700100 if (commit == null) {
101 done = true;
102 break;
103 }
104 if (start == null || start.equals(commit)) {
105 first = commit;
106 break;
107 }
108 if (prevBuffer.size() == limit) {
109 prevBuffer.remove();
110 }
111 prevBuffer.add(commit);
112 }
113 prevStart = prevBuffer.pollFirst();
Dave Borowitz9de65952012-08-13 16:09:45 -0700114 }
115
116 /**
117 * Get the next element in this page of the walk.
118 *
119 * @return the next element, or null if the walk is finished.
Dave Borowitz9de65952012-08-13 16:09:45 -0700120 * @throws MissingObjectException See {@link RevWalk#next()}.
121 * @throws IncorrectObjectTypeException See {@link RevWalk#next()}.
122 * @throws IOException See {@link RevWalk#next()}.
123 */
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200124 public RevCommit next() throws MissingObjectException, IncorrectObjectTypeException, IOException {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700125 if (done) {
126 return null;
127 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700128 RevCommit commit;
Dave Borowitz7756efb2014-07-30 08:45:20 -0700129 if (first != null) {
130 commit = first;
131 first = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700132 } else {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400133 commit = nextWithRename();
Dave Borowitz9de65952012-08-13 16:09:45 -0700134 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 if (++n == limit) {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400136 nextStart = nextWithRename();
Dave Borowitz9de65952012-08-13 16:09:45 -0700137 done = true;
Dave Borowitz7756efb2014-07-30 08:45:20 -0700138 } else if (commit == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 done = true;
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 }
141 return commit;
142 }
143
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400144 private RevCommit nextWithRename() throws IOException {
145 RevCommit next = walk.next();
146 if (renameWatcher != null) {
147 // The commit that triggered the rename isn't available to RenameWatcher,
148 // so we can't populate the map from the callback directly. Instead, we
149 // need to check after each call to walk.next() whether a rename occurred
150 // due to this commit.
151 DiffEntry entry = renameWatcher.getAndClear();
152 if (entry != null) {
153 if (renamed == null) {
154 renamed = new HashMap<>();
155 }
156 renamed.put(next.copy(), entry);
157 }
158 }
159 return next;
160 }
161
Dave Borowitz9de65952012-08-13 16:09:45 -0700162 /**
Matthias Sohna0d00c52023-09-30 21:27:05 +0200163 * Get previous start.
164 *
Dave Borowitz40255d52016-08-19 16:16:22 -0400165 * @return the ID at the start of the page of results preceding this one, or null if this is the
166 * first page.
Dave Borowitz9de65952012-08-13 16:09:45 -0700167 */
168 public ObjectId getPreviousStart() {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700169 return prevStart;
Dave Borowitz9de65952012-08-13 16:09:45 -0700170 }
171
172 /**
Matthias Sohna0d00c52023-09-30 21:27:05 +0200173 * Get next start.
174 *
Dave Borowitz40255d52016-08-19 16:16:22 -0400175 * @return the ID at the start of the page of results after this one, or null if this is the last
176 * page.
Dave Borowitz9de65952012-08-13 16:09:45 -0700177 */
178 public ObjectId getNextStart() {
179 checkState(done, "getNextStart() invalid before walk done");
180 return nextStart;
181 }
182
Matthias Sohna0d00c52023-09-30 21:27:05 +0200183 /**
184 * Get rename.
185 *
186 * @return entry corresponding to a rename or copy at the given commit.
187 */
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400188 public DiffEntry getRename(ObjectId commitId) {
189 return renamed != null ? renamed.get(commitId) : null;
190 }
191
192 /**
Matthias Sohna0d00c52023-09-30 21:27:05 +0200193 * Get iterator over the commits in this walk.
194 *
Dave Borowitz9de65952012-08-13 16:09:45 -0700195 * @return an iterator over the commits in this walk.
Dave Borowitz40255d52016-08-19 16:16:22 -0400196 * @throws RevWalkException if an error occurred, wrapping the checked exception from {@link
197 * #next()}.
Dave Borowitz9de65952012-08-13 16:09:45 -0700198 */
199 @Override
200 public Iterator<RevCommit> iterator() {
201 return new Iterator<RevCommit>() {
202 RevCommit next = nextUnchecked();
203
204 @Override
205 public boolean hasNext() {
206 return next != null;
207 }
208
209 @Override
210 public RevCommit next() {
211 RevCommit r = next;
212 next = nextUnchecked();
213 return r;
214 }
215
216 @Override
217 public void remove() {
218 throw new UnsupportedOperationException();
219 }
220 };
221 }
222
Dave Borowitz27fada42013-11-01 11:09:49 -0700223 public int getLimit() {
224 return limit;
225 }
226
227 public RevWalk getWalk() {
228 return walk;
229 }
230
Dave Borowitz9de65952012-08-13 16:09:45 -0700231 private RevCommit nextUnchecked() {
232 try {
233 return next();
Dave Borowitz9de65952012-08-13 16:09:45 -0700234 } catch (IOException e) {
235 throw new RevWalkException(e);
236 }
237 }
238}