blob: eed7b75c342aad17901cc272178815f4bb6b467b [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 */
Matthias Sohnc156c962023-09-30 22:15:23 +0200124 public @Nullable RevCommit next()
125 throws MissingObjectException, IncorrectObjectTypeException, IOException {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700126 if (done) {
127 return null;
128 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700129 RevCommit commit;
Dave Borowitz7756efb2014-07-30 08:45:20 -0700130 if (first != null) {
131 commit = first;
132 first = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700133 } else {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400134 commit = nextWithRename();
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700136 if (++n == limit) {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400137 nextStart = nextWithRename();
Dave Borowitz9de65952012-08-13 16:09:45 -0700138 done = true;
Dave Borowitz7756efb2014-07-30 08:45:20 -0700139 } else if (commit == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 done = true;
Dave Borowitz9de65952012-08-13 16:09:45 -0700141 }
142 return commit;
143 }
144
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400145 private RevCommit nextWithRename() throws IOException {
146 RevCommit next = walk.next();
147 if (renameWatcher != null) {
148 // The commit that triggered the rename isn't available to RenameWatcher,
149 // so we can't populate the map from the callback directly. Instead, we
150 // need to check after each call to walk.next() whether a rename occurred
151 // due to this commit.
152 DiffEntry entry = renameWatcher.getAndClear();
153 if (entry != null) {
154 if (renamed == null) {
155 renamed = new HashMap<>();
156 }
157 renamed.put(next.copy(), entry);
158 }
159 }
160 return next;
161 }
162
Dave Borowitz9de65952012-08-13 16:09:45 -0700163 /**
Matthias Sohna0d00c52023-09-30 21:27:05 +0200164 * Get previous start.
165 *
Dave Borowitz40255d52016-08-19 16:16:22 -0400166 * @return the ID at the start of the page of results preceding this one, or null if this is the
167 * first page.
Dave Borowitz9de65952012-08-13 16:09:45 -0700168 */
169 public ObjectId getPreviousStart() {
Dave Borowitz7756efb2014-07-30 08:45:20 -0700170 return prevStart;
Dave Borowitz9de65952012-08-13 16:09:45 -0700171 }
172
173 /**
Matthias Sohna0d00c52023-09-30 21:27:05 +0200174 * Get next start.
175 *
Dave Borowitz40255d52016-08-19 16:16:22 -0400176 * @return the ID at the start of the page of results after this one, or null if this is the last
177 * page.
Dave Borowitz9de65952012-08-13 16:09:45 -0700178 */
179 public ObjectId getNextStart() {
180 checkState(done, "getNextStart() invalid before walk done");
181 return nextStart;
182 }
183
Matthias Sohna0d00c52023-09-30 21:27:05 +0200184 /**
185 * Get rename.
186 *
187 * @return entry corresponding to a rename or copy at the given commit.
188 */
Matthias Sohnc156c962023-09-30 22:15:23 +0200189 public @Nullable DiffEntry getRename(ObjectId commitId) {
Dave Borowitzcb88d4d2015-10-26 13:58:17 -0400190 return renamed != null ? renamed.get(commitId) : null;
191 }
192
193 /**
Matthias Sohna0d00c52023-09-30 21:27:05 +0200194 * Get iterator over the commits in this walk.
195 *
Dave Borowitz9de65952012-08-13 16:09:45 -0700196 * @return an iterator over the commits in this walk.
Dave Borowitz40255d52016-08-19 16:16:22 -0400197 * @throws RevWalkException if an error occurred, wrapping the checked exception from {@link
198 * #next()}.
Dave Borowitz9de65952012-08-13 16:09:45 -0700199 */
200 @Override
201 public Iterator<RevCommit> iterator() {
202 return new Iterator<RevCommit>() {
203 RevCommit next = nextUnchecked();
204
205 @Override
206 public boolean hasNext() {
207 return next != null;
208 }
209
210 @Override
211 public RevCommit next() {
212 RevCommit r = next;
213 next = nextUnchecked();
214 return r;
215 }
216
217 @Override
218 public void remove() {
219 throw new UnsupportedOperationException();
220 }
221 };
222 }
223
Dave Borowitz27fada42013-11-01 11:09:49 -0700224 public int getLimit() {
225 return limit;
226 }
227
228 public RevWalk getWalk() {
229 return walk;
230 }
231
Dave Borowitz9de65952012-08-13 16:09:45 -0700232 private RevCommit nextUnchecked() {
233 try {
234 return next();
Dave Borowitz9de65952012-08-13 16:09:45 -0700235 } catch (IOException e) {
236 throw new RevWalkException(e);
237 }
238 }
239}