blob: 4c624d9c9d4d0f29fbf340a0bd0772b16e3318c0 [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;
27import org.eclipse.jgit.lib.Ref;
28import org.eclipse.jgit.lib.Repository;
29import org.eclipse.jgit.lib.StoredConfig;
30import org.eclipse.jgit.transport.resolver.FileResolver;
31import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
32import org.eclipse.jgit.util.IO;
33
34import java.io.File;
35import java.io.IOException;
36import java.util.Arrays;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.List;
40import java.util.Map;
41import java.util.Queue;
42import java.util.Set;
43
44import javax.servlet.http.HttpServletRequest;
45
46/**
47 * Default implementation of {@link GitilesAccess} with local repositories.
48 * <p>
49 * Repositories are scanned on-demand under the given path, configured by
50 * default from {@code gitiles.basePath}. There is no access control beyond what
51 * user the JVM is running under.
52 */
53public class DefaultAccess implements GitilesAccess {
54 private static final String ANONYMOUS_USER_KEY = "anonymous user";
55
Dave Borowitzfa0fef02013-01-07 13:17:53 -080056 private static final String DEFAULT_DESCRIPTION =
57 "Unnamed repository; edit this file 'description' to name the repository.";
58
Dave Borowitz9de65952012-08-13 16:09:45 -070059 public static class Factory implements GitilesAccess.Factory {
60 private final File basePath;
61 private final String canonicalBasePath;
62 private final String baseGitUrl;
63 private final FileResolver<HttpServletRequest> resolver;
64
65 Factory(File basePath, String baseGitUrl, FileResolver<HttpServletRequest> resolver)
66 throws IOException {
67 this.basePath = checkNotNull(basePath, "basePath");
68 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
69 this.resolver = checkNotNull(resolver, "resolver");
70 this.canonicalBasePath = basePath.getCanonicalPath();
71 }
72
73 @Override
74 public GitilesAccess forRequest(HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -070075 return newAccess(basePath, canonicalBasePath, baseGitUrl, resolver, req);
76 }
77
78 protected DefaultAccess newAccess(File basePath, String canonicalBasePath, String baseGitUrl,
79 FileResolver<HttpServletRequest> resolver, HttpServletRequest req) {
80 return new DefaultAccess(basePath, canonicalBasePath, baseGitUrl, resolver, req);
81 }
82 }
83
84 protected final File basePath;
85 protected final String canonicalBasePath;
86 protected final String baseGitUrl;
87 protected final FileResolver<HttpServletRequest> resolver;
88 protected final HttpServletRequest req;
89
90 protected DefaultAccess(File basePath, String canonicalBasePath, String baseGitUrl,
91 FileResolver<HttpServletRequest> resolver, HttpServletRequest req) {
92 this.basePath = checkNotNull(basePath, "basePath");
93 this.canonicalBasePath = checkNotNull(canonicalBasePath, "canonicalBasePath");
94 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
95 this.resolver = checkNotNull(resolver, "resolver");
96 this.req = checkNotNull(req, "req");
97 }
98
99 @Override
100 public Map<String, RepositoryDescription> listRepositories(Set<String> branches)
101 throws IOException {
102 Map<String, RepositoryDescription> repos = Maps.newTreeMap();
103 for (Repository repo : scanRepositories(basePath, req)) {
104 repos.put(getRepositoryName(repo), buildDescription(repo, branches));
105 repo.close();
106 }
107 return repos;
108 }
109
110 @Override
111 public Object getUserKey() {
112 // Always return the same anonymous user key (effectively running with the
113 // same user permissions as the JVM). Subclasses may override this behavior.
114 return ANONYMOUS_USER_KEY;
115 }
116
117 @Override
118 public String getRepositoryName() {
119 return getRepositoryName(ServletUtils.getRepository(req));
120 }
121
122 @Override
123 public RepositoryDescription getRepositoryDescription() throws IOException {
124 return buildDescription(ServletUtils.getRepository(req), Collections.<String> emptySet());
125 }
126
127 private String getRepositoryName(Repository repo) {
128 String path = getRelativePath(repo);
129 if (repo.isBare() && path.endsWith(".git")) {
130 path = path.substring(0, path.length() - 4);
131 }
132 return path;
133 }
134
135 private String getRelativePath(Repository repo) {
136 String path = repo.isBare() ? repo.getDirectory().getPath() : repo.getDirectory().getParent();
137 if (repo.isBare()) {
138 path = repo.getDirectory().getPath();
139 if (path.endsWith(".git")) {
140 path = path.substring(0, path.length() - 4);
141 }
142 } else {
143 path = repo.getDirectory().getParent();
144 }
145 return getRelativePath(path);
146 }
147
148 private String getRelativePath(String path) {
149 String base = basePath.getPath();
150 if (path.startsWith(base)) {
151 return path.substring(base.length() + 1);
152 }
153 if (path.startsWith(canonicalBasePath)) {
154 return path.substring(canonicalBasePath.length() + 1);
155 }
156 throw new IllegalStateException(String.format(
157 "Repository path %s is outside base path %s", path, base));
158 }
159
160 private String loadDescriptionText(Repository repo) throws IOException {
161 String desc = null;
162 StoredConfig config = repo.getConfig();
163 IOException configError = null;
164 try {
165 config.load();
166 desc = config.getString("gitweb", null, "description");
167 } catch (ConfigInvalidException e) {
168 configError = new IOException(e);
169 }
170 if (desc == null) {
171 File descFile = new File(repo.getDirectory(), "description");
172 if (descFile.exists()) {
173 desc = new String(IO.readFully(descFile));
Dave Borowitzfa0fef02013-01-07 13:17:53 -0800174 if (DEFAULT_DESCRIPTION.equals(CharMatcher.WHITESPACE.trimFrom(desc))) {
175 desc = null;
176 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700177 } else if (configError != null) {
178 throw configError;
179 }
180 }
181 return desc;
182 }
183
184 private RepositoryDescription buildDescription(Repository repo, Set<String> branches)
185 throws IOException {
186 RepositoryDescription desc = new RepositoryDescription();
187 desc.name = getRepositoryName(repo);
188 desc.cloneUrl = baseGitUrl + getRelativePath(repo);
189 desc.description = loadDescriptionText(repo);
190 if (!branches.isEmpty()) {
191 desc.branches = Maps.newLinkedHashMap();
192 for (String name : branches) {
193 Ref ref = repo.getRef(normalizeRefName(name));
194 if ((ref != null) && (ref.getObjectId() != null)) {
195 desc.branches.put(name, ref.getObjectId().name());
196 }
197 }
198 }
199 return desc;
200 }
201
202 private static String normalizeRefName(String name) {
203 if (name.startsWith("refs/")) {
204 return name;
205 }
206 return "refs/heads/" + name;
207 }
208
Dave Borowitz1443f512013-08-13 10:22:41 -0700209 private Collection<Repository> scanRepositories(final File basePath, final HttpServletRequest req)
210 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700211 List<Repository> repos = Lists.newArrayList();
212 Queue<File> todo = Queues.newArrayDeque();
213 File[] baseFiles = basePath.listFiles();
214 if (baseFiles == null) {
215 throw new IOException("base path is not a directory: " + basePath.getPath());
216 }
217 todo.addAll(Arrays.asList(baseFiles));
218 while (!todo.isEmpty()) {
219 File file = todo.remove();
220 try {
221 repos.add(resolver.open(req, getRelativePath(file.getPath())));
222 } catch (RepositoryNotFoundException e) {
223 File[] children = file.listFiles();
224 if (children != null) {
225 todo.addAll(Arrays.asList(children));
226 }
227 } catch (ServiceNotEnabledException e) {
228 throw new IOException(e);
229 }
230 }
231 return repos;
232 }
233}