blob: 9b2d044f9fa7405ed5cd07db5442d3a6d812ab4e [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)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700111 Revision oldRevision = null;
112
113 StringBuilder b = new StringBuilder();
114 boolean first = true;
Dave Borowitzcfc1c532015-02-18 13:41:19 -0800115 for (String part : PathUtil.SPLITTER.split(path)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700116 if (part.isEmpty()) {
117 return null; // No valid revision contains empty segments.
118 }
119 if (!first) {
120 b.append('/');
121 }
122
123 if (oldRevision == null) {
124 int dots = part.indexOf("..");
125 int firstParent = part.indexOf("^!");
126 if (dots == 0 || firstParent == 0) {
127 return null;
128 } else if (dots > 0) {
Dave Borowitz27058932014-12-03 15:44:46 -0800129 b.append(part, 0, dots);
Dave Borowitz9de65952012-08-13 16:09:45 -0700130 String oldName = b.toString();
131 if (!isValidRevision(oldName)) {
132 return null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700133 }
David Pursehouseb3b630f2016-06-15 21:51:18 +0900134 RevObject old = resolve(oldName, walk);
135 if (old == null) {
136 return null;
137 }
138 oldRevision = Revision.peel(oldName, old, walk);
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 part = part.substring(dots + 2);
140 b = new StringBuilder();
141 } else if (firstParent > 0) {
142 if (firstParent != part.length() - 2) {
143 return null;
144 }
Dave Borowitz27058932014-12-03 15:44:46 -0800145 b.append(part, 0, part.length() - 2);
Dave Borowitz9de65952012-08-13 16:09:45 -0700146 String name = b.toString();
147 if (!isValidRevision(name)) {
148 return null;
149 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700150 RevObject obj = resolve(name, walk);
151 if (obj == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700152 return null;
153 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700154 while (obj instanceof RevTag) {
155 obj = ((RevTag) obj).getObject();
156 walk.parseHeaders(obj);
157 }
158 if (!(obj instanceof RevCommit)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700159 return null; // Not a commit, ^! is invalid.
160 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700161 RevCommit c = (RevCommit) obj;
Dave Borowitz9de65952012-08-13 16:09:45 -0700162 if (c.getParentCount() > 0) {
163 oldRevision = Revision.peeled(name + "^", c.getParent(0));
164 } else {
165 oldRevision = Revision.NULL;
166 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200167 Result result =
168 new Result(
169 Revision.peeled(name, c), oldRevision, path.substring(name.length() + 2));
Dave Borowitz9de65952012-08-13 16:09:45 -0700170 return isVisible(walk, result) ? result : null;
171 }
172 }
173 b.append(part);
174
175 String name = b.toString();
176 if (!isValidRevision(name)) {
177 return null;
178 }
Dave Borowitz48ca6702013-04-09 11:52:41 -0700179 RevObject obj = resolve(name, walk);
180 if (obj != null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700181 int pathStart;
182 if (oldRevision == null) {
183 pathStart = name.length(); // foo
184 } else {
185 // foo..bar (foo may be empty)
186 pathStart = oldRevision.getName().length() + 2 + name.length();
187 }
Dave Borowitze360d5c2015-09-16 16:53:30 -0400188 Result result =
189 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;
Dave Borowitz9de65952012-08-13 16:09:45 -0700195 }
196 }
197
Dave Borowitz48ca6702013-04-09 11:52:41 -0700198 private RevObject resolve(String name, RevWalk walk) throws IOException {
Dave Borowitz5f7e8b72013-01-07 09:31:41 -0800199 try {
Dave Borowitz48ca6702013-04-09 11:52:41 -0700200 ObjectId id = repo.resolve(name);
201 return id != null ? walk.parseAny(id) : null;
Dave Borowitz93c1fca2013-09-25 16:37:43 -0700202 } catch (AmbiguousObjectException e) {
203 // TODO(dborowitz): Render a helpful disambiguation page.
204 return null;
Dave Borowitz27058932014-12-03 15:44:46 -0800205 } catch (RevisionSyntaxException | MissingObjectException e) {
Dave Borowitz48ca6702013-04-09 11:52:41 -0700206 return null;
Dave Borowitz5f7e8b72013-01-07 09:31:41 -0800207 }
208 }
209
Dave Borowitz9de65952012-08-13 16:09:45 -0700210 private static boolean isValidRevision(String revision) {
211 // Disallow some uncommon but valid revision expressions that either we
212 // don't support or we represent differently in our URLs.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200213 return !revision.contains(":") && !revision.contains("^{") && !revision.contains("@");
Dave Borowitz9de65952012-08-13 16:09:45 -0700214 }
215
216 private boolean isVisible(RevWalk walk, Result result) throws IOException {
217 String maybeRef = OPERATOR_SPLITTER.split(result.getRevision().getName()).iterator().next();
Dave Borowitz14cad732016-05-26 17:34:19 -0400218 if (repo.findRef(maybeRef) != null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700219 // Name contains a visible ref; skip expensive reachability check.
220 return true;
221 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700222 ObjectId id = result.getRevision().getId();
223 if (!cache.isVisible(repo, walk, access, id)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700224 return false;
225 }
226 if (result.getOldRevision() != null && result.getOldRevision() != Revision.NULL) {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700227 return cache.isVisible(repo, walk, access, result.getOldRevision().getId(), id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700228 }
David Pursehouseb3b630f2016-06-15 21:51:18 +0900229 return true;
Dave Borowitz9de65952012-08-13 16:09:45 -0700230 }
231}