blob: d435289ab26fde6929796fd1a54d937a61402c7f [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 Borowitzfa0fef02013-01-07 13:17:53 -080019import com.google.common.base.CharMatcher;
Dave Borowitz9de65952012-08-13 16:09:45 -070020import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Queues;
23
24import org.eclipse.jgit.errors.ConfigInvalidException;
25import org.eclipse.jgit.errors.RepositoryNotFoundException;
26import org.eclipse.jgit.http.server.ServletUtils;
Dave Borowitzded109a2014-03-03 15:25:39 -050027import org.eclipse.jgit.lib.Config;
Dave Borowitz9de65952012-08-13 16:09:45 -070028import org.eclipse.jgit.lib.Ref;
29import org.eclipse.jgit.lib.Repository;
30import org.eclipse.jgit.lib.StoredConfig;
31import org.eclipse.jgit.transport.resolver.FileResolver;
32import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
33import org.eclipse.jgit.util.IO;
34
35import java.io.File;
36import java.io.IOException;
Dave Borowitz9e95e632013-11-27 16:16:24 -050037import java.text.Collator;
Dave Borowitz9de65952012-08-13 16:09:45 -070038import java.util.Collection;
39import java.util.Collections;
40import java.util.List;
Dave Borowitz9e95e632013-11-27 16:16:24 -050041import java.util.Locale;
Dave Borowitz9de65952012-08-13 16:09:45 -070042import java.util.Map;
43import java.util.Queue;
44import java.util.Set;
45
46import javax.servlet.http.HttpServletRequest;
47
48/**
49 * Default implementation of {@link GitilesAccess} with local repositories.
50 * <p>
51 * Repositories are scanned on-demand under the given path, configured by
52 * default from {@code gitiles.basePath}. There is no access control beyond what
53 * user the JVM is running under.
54 */
55public class DefaultAccess implements GitilesAccess {
56 private static final String ANONYMOUS_USER_KEY = "anonymous user";
57
Dave Borowitzfa0fef02013-01-07 13:17:53 -080058 private static final String DEFAULT_DESCRIPTION =
59 "Unnamed repository; edit this file 'description' to name the repository.";
60
Dave Borowitz9e95e632013-11-27 16:16:24 -050061 private static final Collator US_COLLATOR = Collator.getInstance(Locale.US);
62
Dave Borowitz9de65952012-08-13 16:09:45 -070063 public static class Factory implements GitilesAccess.Factory {
64 private final File basePath;
65 private final String canonicalBasePath;
66 private final String baseGitUrl;
Dave Borowitzded109a2014-03-03 15:25:39 -050067 private final Config baseConfig;
Dave Borowitz9de65952012-08-13 16:09:45 -070068 private final FileResolver<HttpServletRequest> resolver;
69
Dave Borowitzded109a2014-03-03 15:25:39 -050070 Factory(File basePath, String baseGitUrl, Config baseConfig,
71 FileResolver<HttpServletRequest> resolver) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070072 this.basePath = checkNotNull(basePath, "basePath");
73 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
Dave Borowitzded109a2014-03-03 15:25:39 -050074 this.baseConfig = checkNotNull(baseConfig, "baseConfig");
Dave Borowitz9de65952012-08-13 16:09:45 -070075 this.resolver = checkNotNull(resolver, "resolver");
76 this.canonicalBasePath = basePath.getCanonicalPath();
77 }
78
79 @Override
80 public GitilesAccess forRequest(HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -070081 return newAccess(basePath, canonicalBasePath, baseGitUrl, resolver, req);
82 }
83
84 protected DefaultAccess newAccess(File basePath, String canonicalBasePath, String baseGitUrl,
85 FileResolver<HttpServletRequest> resolver, HttpServletRequest req) {
Dave Borowitzded109a2014-03-03 15:25:39 -050086 return new DefaultAccess(basePath, canonicalBasePath, baseGitUrl, baseConfig, resolver, req);
Dave Borowitz9de65952012-08-13 16:09:45 -070087 }
88 }
89
90 protected final File basePath;
91 protected final String canonicalBasePath;
92 protected final String baseGitUrl;
Dave Borowitzded109a2014-03-03 15:25:39 -050093 protected final Config baseConfig;
Dave Borowitz9de65952012-08-13 16:09:45 -070094 protected final FileResolver<HttpServletRequest> resolver;
95 protected final HttpServletRequest req;
96
97 protected DefaultAccess(File basePath, String canonicalBasePath, String baseGitUrl,
Dave Borowitzded109a2014-03-03 15:25:39 -050098 Config baseConfig, FileResolver<HttpServletRequest> resolver, HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -070099 this.basePath = checkNotNull(basePath, "basePath");
100 this.canonicalBasePath = checkNotNull(canonicalBasePath, "canonicalBasePath");
101 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
Dave Borowitzded109a2014-03-03 15:25:39 -0500102 this.baseConfig = checkNotNull(baseConfig, "baseConfig");
Dave Borowitz9de65952012-08-13 16:09:45 -0700103 this.resolver = checkNotNull(resolver, "resolver");
104 this.req = checkNotNull(req, "req");
105 }
106
107 @Override
108 public Map<String, RepositoryDescription> listRepositories(Set<String> branches)
109 throws IOException {
Dave Borowitz9e95e632013-11-27 16:16:24 -0500110 Map<String, RepositoryDescription> repos = Maps.newTreeMap(US_COLLATOR);
Dave Borowitz9de65952012-08-13 16:09:45 -0700111 for (Repository repo : scanRepositories(basePath, req)) {
112 repos.put(getRepositoryName(repo), buildDescription(repo, branches));
113 repo.close();
114 }
115 return repos;
116 }
117
118 @Override
119 public Object getUserKey() {
120 // Always return the same anonymous user key (effectively running with the
121 // same user permissions as the JVM). Subclasses may override this behavior.
122 return ANONYMOUS_USER_KEY;
123 }
124
125 @Override
126 public String getRepositoryName() {
127 return getRepositoryName(ServletUtils.getRepository(req));
128 }
129
130 @Override
131 public RepositoryDescription getRepositoryDescription() throws IOException {
132 return buildDescription(ServletUtils.getRepository(req), Collections.<String> emptySet());
133 }
134
Dave Borowitzded109a2014-03-03 15:25:39 -0500135 @Override
136 public Config getConfig() {
137 return baseConfig;
138 }
139
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 private String getRepositoryName(Repository repo) {
141 String path = getRelativePath(repo);
142 if (repo.isBare() && path.endsWith(".git")) {
143 path = path.substring(0, path.length() - 4);
144 }
145 return path;
146 }
147
148 private String getRelativePath(Repository repo) {
149 String path = repo.isBare() ? repo.getDirectory().getPath() : repo.getDirectory().getParent();
150 if (repo.isBare()) {
151 path = repo.getDirectory().getPath();
152 if (path.endsWith(".git")) {
153 path = path.substring(0, path.length() - 4);
154 }
155 } else {
156 path = repo.getDirectory().getParent();
157 }
158 return getRelativePath(path);
159 }
160
161 private String getRelativePath(String path) {
162 String base = basePath.getPath();
163 if (path.startsWith(base)) {
164 return path.substring(base.length() + 1);
165 }
166 if (path.startsWith(canonicalBasePath)) {
167 return path.substring(canonicalBasePath.length() + 1);
168 }
169 throw new IllegalStateException(String.format(
170 "Repository path %s is outside base path %s", path, base));
171 }
172
173 private String loadDescriptionText(Repository repo) throws IOException {
174 String desc = null;
175 StoredConfig config = repo.getConfig();
176 IOException configError = null;
177 try {
178 config.load();
179 desc = config.getString("gitweb", null, "description");
180 } catch (ConfigInvalidException e) {
181 configError = new IOException(e);
182 }
183 if (desc == null) {
184 File descFile = new File(repo.getDirectory(), "description");
185 if (descFile.exists()) {
186 desc = new String(IO.readFully(descFile));
Dave Borowitzfa0fef02013-01-07 13:17:53 -0800187 if (DEFAULT_DESCRIPTION.equals(CharMatcher.WHITESPACE.trimFrom(desc))) {
188 desc = null;
189 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700190 } else if (configError != null) {
191 throw configError;
192 }
193 }
194 return desc;
195 }
196
197 private RepositoryDescription buildDescription(Repository repo, Set<String> branches)
198 throws IOException {
199 RepositoryDescription desc = new RepositoryDescription();
200 desc.name = getRepositoryName(repo);
201 desc.cloneUrl = baseGitUrl + getRelativePath(repo);
202 desc.description = loadDescriptionText(repo);
203 if (!branches.isEmpty()) {
204 desc.branches = Maps.newLinkedHashMap();
205 for (String name : branches) {
206 Ref ref = repo.getRef(normalizeRefName(name));
207 if ((ref != null) && (ref.getObjectId() != null)) {
208 desc.branches.put(name, ref.getObjectId().name());
209 }
210 }
211 }
212 return desc;
213 }
214
215 private static String normalizeRefName(String name) {
216 if (name.startsWith("refs/")) {
217 return name;
218 }
219 return "refs/heads/" + name;
220 }
221
Dave Borowitz1443f512013-08-13 10:22:41 -0700222 private Collection<Repository> scanRepositories(final File basePath, final HttpServletRequest req)
223 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700224 List<Repository> repos = Lists.newArrayList();
225 Queue<File> todo = Queues.newArrayDeque();
226 File[] baseFiles = basePath.listFiles();
227 if (baseFiles == null) {
228 throw new IOException("base path is not a directory: " + basePath.getPath());
229 }
Dave Borowitz27058932014-12-03 15:44:46 -0800230 Collections.addAll(todo, baseFiles);
Dave Borowitz9de65952012-08-13 16:09:45 -0700231 while (!todo.isEmpty()) {
232 File file = todo.remove();
233 try {
234 repos.add(resolver.open(req, getRelativePath(file.getPath())));
235 } catch (RepositoryNotFoundException e) {
236 File[] children = file.listFiles();
237 if (children != null) {
Dave Borowitz27058932014-12-03 15:44:46 -0800238 Collections.addAll(todo, children);
Dave Borowitz9de65952012-08-13 16:09:45 -0700239 }
240 } catch (ServiceNotEnabledException e) {
241 throw new IOException(e);
242 }
243 }
244 return repos;
245 }
246}