| // Copyright (C) 2008 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.git; |
| |
| import com.google.gerrit.server.config.GerritServerConfig; |
| import com.google.gerrit.server.config.SitePath; |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.spearce.jgit.errors.RepositoryNotFoundException; |
| import org.spearce.jgit.lib.Config; |
| import org.spearce.jgit.lib.Constants; |
| import org.spearce.jgit.lib.LockFile; |
| import org.spearce.jgit.lib.Repository; |
| import org.spearce.jgit.lib.RepositoryCache; |
| import org.spearce.jgit.lib.RepositoryCache.FileKey; |
| |
| import java.io.File; |
| import java.io.IOException; |
| |
| /** Class managing Git repositories. */ |
| @Singleton |
| public class GitRepositoryManager { |
| private static final Logger log = LoggerFactory.getLogger(GitRepositoryManager.class); |
| private final File sitePath; |
| private final File basepath; |
| |
| @Inject |
| GitRepositoryManager(@SitePath final File path, @GerritServerConfig final Config cfg) { |
| sitePath = path; |
| |
| final String basePath = cfg.getString("gerrit", null, "basepath"); |
| if (basePath != null) { |
| File root = new File(basePath); |
| if (!root.isAbsolute()) { |
| root = new File(sitePath, basePath); |
| } |
| basepath = root; |
| } else { |
| basepath = null; |
| } |
| } |
| |
| /** |
| * Get (or open) a repository by name. |
| * |
| * @param name the repository name, relative to the base directory. |
| * @return the cached Repository instance. Caller must call {@code close()} |
| * when done to decrement the resource handle. |
| * @throws RepositoryNotFoundException the name does not denote an existing |
| * repository, or the name cannot be read as a repository. |
| */ |
| public Repository openRepository(String name) |
| throws RepositoryNotFoundException { |
| if (basepath == null) { |
| throw new RepositoryNotFoundException("No gerrit.basepath configured"); |
| } |
| |
| if (isUnreasonableName(name)) { |
| throw new RepositoryNotFoundException("Invalid name: " + name); |
| } |
| |
| try { |
| final FileKey loc = FileKey.lenient(new File(basepath, name)); |
| return RepositoryCache.open(loc); |
| } catch (IOException e1) { |
| final RepositoryNotFoundException e2; |
| e2 = new RepositoryNotFoundException("Cannot open repository " + name); |
| e2.initCause(e1); |
| throw e2; |
| } |
| } |
| |
| /** |
| * Create (and open) a repository by name. |
| * |
| * @param name the repository name, relative to the base directory. |
| * @return the cached Repository instance. Caller must call {@code close()} |
| * when done to decrement the resource handle. |
| * @throws RepositoryNotFoundException the name does not denote an existing |
| * repository, or the name cannot be read as a repository. |
| */ |
| public Repository createRepository(String name) |
| throws RepositoryNotFoundException { |
| if (basepath == null) { |
| throw new RepositoryNotFoundException("No gerrit.basepath configured"); |
| } |
| |
| if (isUnreasonableName(name)) { |
| throw new RepositoryNotFoundException("Invalid name: " + name); |
| } |
| |
| try { |
| if (!name.endsWith(".git")) { |
| name = name + ".git"; |
| } |
| final FileKey loc = FileKey.exact(new File(basepath, name)); |
| return RepositoryCache.open(loc, false); |
| } catch (IOException e1) { |
| final RepositoryNotFoundException e2; |
| e2 = new RepositoryNotFoundException("Cannot open repository " + name); |
| e2.initCause(e1); |
| throw e2; |
| } |
| } |
| |
| /** |
| * Set the {@code GIT_DIR/description} file for gitweb. |
| * <p> |
| * NB: This code should really be in JGit, as a member of the Repostiory |
| * object. Until it moves there, its here. |
| * |
| * @param name the repository name, relative to the base directory. |
| * @param description new description text for the repository. |
| */ |
| public void setProjectDescription(final String name, final String description) { |
| // Update git's description file, in case gitweb is being used |
| // |
| try { |
| final Repository e; |
| final LockFile f; |
| |
| e = openRepository(name); |
| f = new LockFile(new File(e.getDirectory(), "description")); |
| if (f.lock()) { |
| String d = description; |
| if (d != null) { |
| d = d.trim(); |
| if (d.length() > 0) { |
| d += "\n"; |
| } |
| } else { |
| d = ""; |
| } |
| f.write(Constants.encode(d)); |
| f.commit(); |
| } |
| e.close(); |
| } catch (RepositoryNotFoundException e) { |
| log.error("Cannot update description for " + name, e); |
| } catch (IOException e) { |
| log.error("Cannot update description for " + name, e); |
| } |
| } |
| |
| private boolean isUnreasonableName(final String name) { |
| if (name.length() == 0) return true; // no empty paths |
| |
| if (name.indexOf('\\') >= 0) return true; // no windows/dos stlye paths |
| if (name.charAt(0) == '/') return true; // no absolute paths |
| if (new File(name).isAbsolute()) return true; // no absolute paths |
| |
| if (name.startsWith("../")) return true; // no "l../etc/passwd" |
| if (name.contains("/../")) return true; // no "foo/../etc/passwd" |
| if (name.contains("/./")) return true; // "foo/./foo" is insane to ask |
| if (name.contains("//")) return true; // windows UNC path can be "//..." |
| |
| return false; // is a reasonable name |
| } |
| |
| } |