blob: a1cdafccebb43195e359d772685c578db1f0b77d [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.checkNotNull;
18
Dave Borowitzfe8fdab2014-11-04 16:19:33 -080019import com.google.common.annotations.VisibleForTesting;
20import com.google.common.base.CharMatcher;
21import com.google.common.base.Objects;
22import com.google.common.base.Splitter;
Dave Borowitz9de65952012-08-13 16:09:45 -070023
Dave Borowitz93c1fca2013-09-25 16:37:43 -070024import org.eclipse.jgit.errors.AmbiguousObjectException;
Dave Borowitz48ca6702013-04-09 11:52:41 -070025import org.eclipse.jgit.errors.MissingObjectException;
Dave Borowitz5f7e8b72013-01-07 09:31:41 -080026import org.eclipse.jgit.errors.RevisionSyntaxException;
Dave Borowitz9de65952012-08-13 16:09:45 -070027import org.eclipse.jgit.lib.ObjectId;
28import org.eclipse.jgit.lib.Repository;
29import org.eclipse.jgit.revwalk.RevCommit;
Dave Borowitz48ca6702013-04-09 11:52:41 -070030import org.eclipse.jgit.revwalk.RevObject;
31import org.eclipse.jgit.revwalk.RevTag;
Dave Borowitz9de65952012-08-13 16:09:45 -070032import org.eclipse.jgit.revwalk.RevWalk;
33
Dave Borowitzfe8fdab2014-11-04 16:19:33 -080034import java.io.IOException;
Dave Borowitz9de65952012-08-13 16:09:45 -070035
36/** Object to parse revisions out of Gitiles paths. */
37class RevisionParser {
Dave Borowitz9de65952012-08-13 16:09:45 -070038 private static final Splitter OPERATOR_SPLITTER = Splitter.on(CharMatcher.anyOf("^~"));
39
40 static class Result {
41 private final Revision revision;
42 private final Revision oldRevision;
Dave Borowitz06b88d22013-06-19 15:19:14 -070043 private final String path;
Dave Borowitz9de65952012-08-13 16:09:45 -070044
45 @VisibleForTesting
46 Result(Revision revision) {
Dave Borowitz06b88d22013-06-19 15:19:14 -070047 this(revision, null, "");
Dave Borowitz9de65952012-08-13 16:09:45 -070048 }
49
50 @VisibleForTesting
Dave Borowitz06b88d22013-06-19 15:19:14 -070051 Result(Revision revision, Revision oldRevision, String path) {
Dave Borowitz9de65952012-08-13 16:09:45 -070052 this.revision = revision;
53 this.oldRevision = oldRevision;
Dave Borowitz06b88d22013-06-19 15:19:14 -070054 this.path = path;
Dave Borowitz9de65952012-08-13 16:09:45 -070055 }
56
57 public Revision getRevision() {
58 return revision;
59 }
60
61 public Revision getOldRevision() {
62 return oldRevision;
63 }
64
Dave Borowitz06b88d22013-06-19 15:19:14 -070065 public String getPath() {
66 return path;
67 }
68
Dave Borowitz9de65952012-08-13 16:09:45 -070069 @Override
70 public boolean equals(Object o) {
71 if (o instanceof Result) {
72 Result r = (Result) o;
73 return Objects.equal(revision, r.revision)
74 && Objects.equal(oldRevision, r.oldRevision)
Dave Borowitz06b88d22013-06-19 15:19:14 -070075 && Objects.equal(path, r.path);
Dave Borowitz9de65952012-08-13 16:09:45 -070076 }
77 return false;
78 }
79
80 @Override
81 public int hashCode() {
Dave Borowitz06b88d22013-06-19 15:19:14 -070082 return Objects.hashCode(revision, oldRevision, path);
Dave Borowitz9de65952012-08-13 16:09:45 -070083 }
84
85 @Override
86 public String toString() {
87 return Objects.toStringHelper(this)
88 .omitNullValues()
Dave Borowitz06b88d22013-06-19 15:19:14 -070089 .add("revision", revision.getName())
90 .add("oldRevision", oldRevision != null ? oldRevision.getName() : null)
91 .add("path", path)
Dave Borowitz9de65952012-08-13 16:09:45 -070092 .toString();
93 }
Dave Borowitz9de65952012-08-13 16:09:45 -070094 }
95
96 private final Repository repo;
97 private final GitilesAccess access;
98 private final VisibilityCache cache;
99
100 RevisionParser(Repository repo, GitilesAccess access, VisibilityCache cache) {
101 this.repo = checkNotNull(repo, "repo");
102 this.access = checkNotNull(access, "access");
103 this.cache = checkNotNull(cache, "cache");
104 }
105
106 Result parse(String path) throws IOException {
Dave Borowitz06b88d22013-06-19 15:19:14 -0700107 if (path.startsWith("/")) {
108 path = path.substring(1);
109 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700110 RevWalk walk = new RevWalk(repo);
111 try {
112 Revision oldRevision = null;
113
114 StringBuilder b = new StringBuilder();
115 boolean first = true;
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800116 for (String part : Paths.SPLITTER.split(path)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700117 if (part.isEmpty()) {
118 return null; // No valid revision contains empty segments.
119 }
120 if (!first) {
121 b.append('/');
122 }
123
124 if (oldRevision == null) {
125 int dots = part.indexOf("..");
126 int firstParent = part.indexOf("^!");
127 if (dots == 0 || firstParent == 0) {
128 return null;
129 } else if (dots > 0) {
130 b.append(part.substring(0, dots));
131 String oldName = b.toString();
132 if (!isValidRevision(oldName)) {
133 return null;
134 } else {
Dave Borowitz48ca6702013-04-09 11:52:41 -0700135 RevObject old = resolve(oldName, walk);
Dave Borowitz9de65952012-08-13 16:09:45 -0700136 if (old == null) {
137 return null;
138 }
139 oldRevision = Revision.peel(oldName, old, walk);
140 }
141 part = part.substring(dots + 2);
142 b = new StringBuilder();
143 } else if (firstParent > 0) {
144 if (firstParent != part.length() - 2) {
145 return null;
146 }
147 b.append(part.substring(0, part.length() - 2));
148 String name = b.toString();
149 if (!isValidRevision(name)) {
150 return null;
151 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700152 RevObject obj = resolve(name, walk);
153 if (obj == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700154 return null;
155 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700156 while (obj instanceof RevTag) {
157 obj = ((RevTag) obj).getObject();
158 walk.parseHeaders(obj);
159 }
160 if (!(obj instanceof RevCommit)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700161 return null; // Not a commit, ^! is invalid.
162 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700163 RevCommit c = (RevCommit) obj;
Dave Borowitz9de65952012-08-13 16:09:45 -0700164 if (c.getParentCount() > 0) {
165 oldRevision = Revision.peeled(name + "^", c.getParent(0));
166 } else {
167 oldRevision = Revision.NULL;
168 }
Dave Borowitz1443f512013-08-13 10:22:41 -0700169 Result result = new Result(Revision.peeled(name, c), oldRevision,
170 path.substring(name.length() + 2));
Dave Borowitz9de65952012-08-13 16:09:45 -0700171 return isVisible(walk, result) ? result : null;
172 }
173 }
174 b.append(part);
175
176 String name = b.toString();
177 if (!isValidRevision(name)) {
178 return null;
179 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700180 RevObject obj = resolve(name, walk);
181 if (obj != null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700182 int pathStart;
183 if (oldRevision == null) {
184 pathStart = name.length(); // foo
185 } else {
186 // foo..bar (foo may be empty)
187 pathStart = oldRevision.getName().length() + 2 + name.length();
188 }
Dave Borowitz06b88d22013-06-19 15:19:14 -0700189 Result result = new Result(Revision.peel(name, obj, walk), oldRevision, path.substring(pathStart));
Dave Borowitz9de65952012-08-13 16:09:45 -0700190 return isVisible(walk, result) ? result : null;
191 }
192 first = false;
193 }
194 return null;
195 } finally {
196 walk.release();
197 }
198 }
199
Dave Borowitz48ca6702013-04-09 11:52:41 -0700200 private RevObject resolve(String name, RevWalk walk) throws IOException {
Dave Borowitz5f7e8b72013-01-07 09:31:41 -0800201 try {
Dave Borowitz48ca6702013-04-09 11:52:41 -0700202 ObjectId id = repo.resolve(name);
203 return id != null ? walk.parseAny(id) : null;
Dave Borowitz93c1fca2013-09-25 16:37:43 -0700204 } catch (AmbiguousObjectException e) {
205 // TODO(dborowitz): Render a helpful disambiguation page.
206 return null;
Dave Borowitz5f7e8b72013-01-07 09:31:41 -0800207 } catch (RevisionSyntaxException e) {
208 return null;
Dave Borowitz48ca6702013-04-09 11:52:41 -0700209 } catch (MissingObjectException e) {
210 return null;
Dave Borowitz5f7e8b72013-01-07 09:31:41 -0800211 }
212 }
213
Dave Borowitz9de65952012-08-13 16:09:45 -0700214 private static boolean isValidRevision(String revision) {
215 // Disallow some uncommon but valid revision expressions that either we
216 // don't support or we represent differently in our URLs.
217 return revision.indexOf(':') < 0
218 && revision.indexOf("^{") < 0
219 && revision.indexOf('@') < 0;
220 }
221
222 private boolean isVisible(RevWalk walk, Result result) throws IOException {
223 String maybeRef = OPERATOR_SPLITTER.split(result.getRevision().getName()).iterator().next();
224 if (repo.getRef(maybeRef) != null) {
225 // Name contains a visible ref; skip expensive reachability check.
226 return true;
227 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700228 ObjectId id = result.getRevision().getId();
229 if (!cache.isVisible(repo, walk, access, id)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700230 return false;
231 }
232 if (result.getOldRevision() != null && result.getOldRevision() != Revision.NULL) {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700233 return cache.isVisible(repo, walk, access, result.getOldRevision().getId(), id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700234 } else {
235 return true;
236 }
237 }
238}