blob: 89311a354a2d2d891f73e4f1a7eb18da8a92dda9 [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
Dave Borowitzc410f962014-09-23 10:49:26 -070017import static com.google.common.base.MoreObjects.toStringHelper;
Dave Borowitz9de65952012-08-13 16:09:45 -070018import static com.google.common.base.Preconditions.checkNotNull;
Dave Borowitzc410f962014-09-23 10:49:26 -070019import static java.util.Objects.hash;
Dave Borowitz9de65952012-08-13 16:09:45 -070020
Dave Borowitzfe8fdab2014-11-04 16:19:33 -080021import com.google.common.annotations.VisibleForTesting;
22import com.google.common.base.CharMatcher;
Dave Borowitzfe8fdab2014-11-04 16:19:33 -080023import com.google.common.base.Splitter;
Dave Borowitz3b744b12016-08-19 16:11:10 -040024import java.io.IOException;
25import java.util.Objects;
Dave Borowitz93c1fca2013-09-25 16:37:43 -070026import org.eclipse.jgit.errors.AmbiguousObjectException;
Dave Borowitz48ca6702013-04-09 11:52:41 -070027import org.eclipse.jgit.errors.MissingObjectException;
Dave Borowitz5f7e8b72013-01-07 09:31:41 -080028import org.eclipse.jgit.errors.RevisionSyntaxException;
Dave Borowitz9de65952012-08-13 16:09:45 -070029import org.eclipse.jgit.lib.ObjectId;
30import org.eclipse.jgit.lib.Repository;
31import org.eclipse.jgit.revwalk.RevCommit;
Dave Borowitz48ca6702013-04-09 11:52:41 -070032import org.eclipse.jgit.revwalk.RevObject;
33import org.eclipse.jgit.revwalk.RevTag;
Dave Borowitz9de65952012-08-13 16:09:45 -070034import org.eclipse.jgit.revwalk.RevWalk;
35
Dave Borowitz9de65952012-08-13 16:09:45 -070036/** 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;
Dave Borowitzc410f962014-09-23 10:49:26 -070073 return Objects.equals(revision, r.revision)
74 && Objects.equals(oldRevision, r.oldRevision)
75 && Objects.equals(path, r.path);
Dave Borowitz9de65952012-08-13 16:09:45 -070076 }
77 return false;
78 }
79
80 @Override
81 public int hashCode() {
Dave Borowitzc410f962014-09-23 10:49:26 -070082 return hash(revision, oldRevision, path);
Dave Borowitz9de65952012-08-13 16:09:45 -070083 }
84
85 @Override
86 public String toString() {
Dave Borowitzc410f962014-09-23 10:49:26 -070087 return toStringHelper(this)
Dave Borowitz9de65952012-08-13 16:09:45 -070088 .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 }
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700110 try (RevWalk walk = new RevWalk(repo)) {
Jonathan Nieder67662fb2019-03-07 14:40:49 -0800111 walk.setRetainBody(false);
112
Dave Borowitz9de65952012-08-13 16:09:45 -0700113 Revision oldRevision = null;
114
115 StringBuilder b = new StringBuilder();
116 boolean first = true;
Dave Borowitzcfc1c532015-02-18 13:41:19 -0800117 for (String part : PathUtil.SPLITTER.split(path)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700118 if (part.isEmpty()) {
119 return null; // No valid revision contains empty segments.
120 }
121 if (!first) {
122 b.append('/');
123 }
124
125 if (oldRevision == null) {
126 int dots = part.indexOf("..");
127 int firstParent = part.indexOf("^!");
128 if (dots == 0 || firstParent == 0) {
129 return null;
130 } else if (dots > 0) {
Dave Borowitz27058932014-12-03 15:44:46 -0800131 b.append(part, 0, dots);
Dave Borowitz9de65952012-08-13 16:09:45 -0700132 String oldName = b.toString();
133 if (!isValidRevision(oldName)) {
134 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 }
David Pursehouseb3b630f2016-06-15 21:51:18 +0900136 RevObject old = resolve(oldName, walk);
137 if (old == null) {
138 return null;
139 }
140 oldRevision = Revision.peel(oldName, old, walk);
Dave Borowitz9de65952012-08-13 16:09:45 -0700141 part = part.substring(dots + 2);
142 b = new StringBuilder();
143 } else if (firstParent > 0) {
144 if (firstParent != part.length() - 2) {
145 return null;
146 }
Dave Borowitz27058932014-12-03 15:44:46 -0800147 b.append(part, 0, part.length() - 2);
Dave Borowitz9de65952012-08-13 16:09:45 -0700148 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 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200169 Result result =
170 new Result(
171 Revision.peeled(name, c), oldRevision, path.substring(name.length() + 2));
Dave Borowitz9de65952012-08-13 16:09:45 -0700172 return isVisible(walk, result) ? result : null;
173 }
174 }
175 b.append(part);
176
177 String name = b.toString();
178 if (!isValidRevision(name)) {
179 return null;
180 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700181 RevObject obj = resolve(name, walk);
182 if (obj != null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700183 int pathStart;
184 if (oldRevision == null) {
185 pathStart = name.length(); // foo
186 } else {
187 // foo..bar (foo may be empty)
188 pathStart = oldRevision.getName().length() + 2 + name.length();
189 }
Dave Borowitze360d5c2015-09-16 16:53:30 -0400190 Result result =
191 new Result(Revision.peel(name, obj, walk), oldRevision, path.substring(pathStart));
Dave Borowitz9de65952012-08-13 16:09:45 -0700192 return isVisible(walk, result) ? result : null;
193 }
194 first = false;
195 }
196 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700197 }
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 Borowitz27058932014-12-03 15:44:46 -0800207 } catch (RevisionSyntaxException | MissingObjectException e) {
Dave Borowitz48ca6702013-04-09 11:52:41 -0700208 return null;
Dave Borowitz5f7e8b72013-01-07 09:31:41 -0800209 }
210 }
211
Dave Borowitz9de65952012-08-13 16:09:45 -0700212 private static boolean isValidRevision(String revision) {
213 // Disallow some uncommon but valid revision expressions that either we
214 // don't support or we represent differently in our URLs.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200215 return !revision.contains(":") && !revision.contains("^{") && !revision.contains("@");
Dave Borowitz9de65952012-08-13 16:09:45 -0700216 }
217
218 private boolean isVisible(RevWalk walk, Result result) throws IOException {
219 String maybeRef = OPERATOR_SPLITTER.split(result.getRevision().getName()).iterator().next();
Dave Borowitz14cad732016-05-26 17:34:19 -0400220 if (repo.findRef(maybeRef) != null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700221 // Name contains a visible ref; skip expensive reachability check.
222 return true;
223 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700224 ObjectId id = result.getRevision().getId();
225 if (!cache.isVisible(repo, walk, access, id)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700226 return false;
227 }
228 if (result.getOldRevision() != null && result.getOldRevision() != Revision.NULL) {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700229 return cache.isVisible(repo, walk, access, result.getOldRevision().getId(), id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700230 }
David Pursehouseb3b630f2016-06-15 21:51:18 +0900231 return true;
Dave Borowitz9de65952012-08-13 16:09:45 -0700232 }
233}