Fix case check for project name so that symlinks work again
The check for the case of the project name when opening a git
repository that was introduced by change 30015 (commit
a7e928daaf, "Verify the case of the
project name before opening git repository") prevents the usage of
symbolic links under the git base path. There are some scenarios
where symbolic links for git repositories make sense. E.g. if a
project was renamed it may make sense to create a symbolic link
with the old name that points to the new location so that old
references to this project can still be resolved.
The case check for the project name is now implemented in such a
way that it compares the input project name against a cached list of
all existing project names. With this new implementation symbolic
links for git repositories should work again.
Bug: issue 1353
Change-Id: Iad2be143fd7e2057700cc447f3426df732f9b8cb
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
			
			
This commit is contained in:
		@@ -70,11 +70,12 @@ public interface GitRepositoryManager {
 | 
			
		||||
   * @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.
 | 
			
		||||
   * @throws RepositoryCaseMismatchException the name collides with an existing
 | 
			
		||||
   *         repository name, but only in case of a character within the name.
 | 
			
		||||
   * @throws RepositoryNotFoundException the name is invalid.
 | 
			
		||||
   */
 | 
			
		||||
  public abstract Repository createRepository(Project.NameKey name)
 | 
			
		||||
      throws RepositoryNotFoundException;
 | 
			
		||||
      throws RepositoryCaseMismatchException, RepositoryNotFoundException;
 | 
			
		||||
 | 
			
		||||
  /** @return set of all known projects, sorted by natural NameKey order. */
 | 
			
		||||
  public abstract SortedSet<Project.NameKey> list();
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,8 @@ import java.io.IOException;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.SortedSet;
 | 
			
		||||
import java.util.TreeSet;
 | 
			
		||||
import java.util.concurrent.locks.Lock;
 | 
			
		||||
import java.util.concurrent.locks.ReentrantLock;
 | 
			
		||||
 | 
			
		||||
/** Manages Git repositories stored on the local filesystem. */
 | 
			
		||||
@Singleton
 | 
			
		||||
@@ -91,6 +93,8 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final File basePath;
 | 
			
		||||
  private final Lock namesUpdateLock;
 | 
			
		||||
  private volatile SortedSet<Project.NameKey> names;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  LocalDiskRepositoryManager(final SitePaths site,
 | 
			
		||||
@@ -99,6 +103,8 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
    if (basePath == null) {
 | 
			
		||||
      throw new IllegalStateException("gerrit.basePath must be configured");
 | 
			
		||||
    }
 | 
			
		||||
    namesUpdateLock = new ReentrantLock(true /* fair */);
 | 
			
		||||
    names = list();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @return base directory under which all projects are stored. */
 | 
			
		||||
@@ -115,13 +121,10 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
    if (isUnreasonableName(name)) {
 | 
			
		||||
      throw new RepositoryNotFoundException("Invalid name: " + name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
 | 
			
		||||
 | 
			
		||||
    if (!getProjectName(loc.getFile()).equals(name)) {
 | 
			
		||||
    if (!names.contains(name)) {
 | 
			
		||||
      throw new RepositoryNotFoundException(gitDirOf(name));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
 | 
			
		||||
    try {
 | 
			
		||||
      return RepositoryCache.open(loc);
 | 
			
		||||
    } catch (IOException e1) {
 | 
			
		||||
@@ -133,7 +136,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Repository createRepository(final Project.NameKey name)
 | 
			
		||||
      throws RepositoryNotFoundException {
 | 
			
		||||
      throws RepositoryNotFoundException, RepositoryCaseMismatchException {
 | 
			
		||||
    if (isUnreasonableName(name)) {
 | 
			
		||||
      throw new RepositoryNotFoundException("Invalid name: " + name);
 | 
			
		||||
    }
 | 
			
		||||
@@ -145,10 +148,8 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
      //
 | 
			
		||||
      loc = FileKey.exact(dir, FS.DETECTED);
 | 
			
		||||
 | 
			
		||||
      final Project.NameKey nameOfExistingProject =
 | 
			
		||||
          getProjectName(loc.getFile());
 | 
			
		||||
      if (!nameOfExistingProject.equals(name)) {
 | 
			
		||||
        throw new RepositoryCaseMismatchException(name, nameOfExistingProject);
 | 
			
		||||
      if (!names.contains(name)) {
 | 
			
		||||
        throw new RepositoryCaseMismatchException(name);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // It doesn't exist under any of the standard permutations
 | 
			
		||||
@@ -170,6 +171,8 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
        null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
 | 
			
		||||
      config.save();
 | 
			
		||||
 | 
			
		||||
      onCreateProject(name);
 | 
			
		||||
 | 
			
		||||
      return db;
 | 
			
		||||
    } catch (IOException e1) {
 | 
			
		||||
      final RepositoryNotFoundException e2;
 | 
			
		||||
@@ -179,6 +182,17 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void onCreateProject(final Project.NameKey newProjectName) {
 | 
			
		||||
    namesUpdateLock.lock();
 | 
			
		||||
    try {
 | 
			
		||||
      SortedSet<Project.NameKey> n = new TreeSet<Project.NameKey>(names);
 | 
			
		||||
      n.add(newProjectName);
 | 
			
		||||
      names = Collections.unmodifiableSortedSet(n);
 | 
			
		||||
    } finally {
 | 
			
		||||
      namesUpdateLock.unlock();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public String getProjectDescription(final Project.NameKey name)
 | 
			
		||||
      throws RepositoryNotFoundException, IOException {
 | 
			
		||||
    final Repository e = openRepository(name);
 | 
			
		||||
@@ -268,9 +282,18 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public SortedSet<Project.NameKey> list() {
 | 
			
		||||
    SortedSet<Project.NameKey> names = new TreeSet<Project.NameKey>();
 | 
			
		||||
    scanProjects(basePath, "", names);
 | 
			
		||||
    return Collections.unmodifiableSortedSet(names);
 | 
			
		||||
    // The results of this method are cached by ProjectCacheImpl. Control only
 | 
			
		||||
    // enters here if the cache was flushed by the administrator to force
 | 
			
		||||
    // scanning the filesystem. Don't rely on the cached names collection.
 | 
			
		||||
    namesUpdateLock.lock();
 | 
			
		||||
    try {
 | 
			
		||||
      SortedSet<Project.NameKey> n = new TreeSet<Project.NameKey>();
 | 
			
		||||
      scanProjects(basePath, "", n);
 | 
			
		||||
      names = Collections.unmodifiableSortedSet(n);
 | 
			
		||||
      return n;
 | 
			
		||||
    } finally {
 | 
			
		||||
      namesUpdateLock.unlock();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void scanProjects(final File dir, final String prefix,
 | 
			
		||||
@@ -312,16 +335,4 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
 | 
			
		||||
 | 
			
		||||
    return new Project.NameKey(projectName);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Project.NameKey getProjectName(final File gitDir) {
 | 
			
		||||
    String relativeGitPath =
 | 
			
		||||
        getBasePath().toURI().relativize(gitDir.toURI()).getPath();
 | 
			
		||||
    if (!relativeGitPath.endsWith("/")) {
 | 
			
		||||
      relativeGitPath = relativeGitPath + "/";
 | 
			
		||||
    }
 | 
			
		||||
    final String prefix =
 | 
			
		||||
        relativeGitPath.substring(0, relativeGitPath.length() - 1
 | 
			
		||||
            - gitDir.getName().length());
 | 
			
		||||
    return getProjectName(prefix, gitDir.getName());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@
 | 
			
		||||
package com.google.gerrit.server.git;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.Project;
 | 
			
		||||
import com.google.gerrit.reviewdb.Project.NameKey;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
 | 
			
		||||
 | 
			
		||||
@@ -31,21 +30,11 @@ public class RepositoryCaseMismatchException extends
 | 
			
		||||
 | 
			
		||||
  private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
  private final NameKey nameOfExistingProject;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param projectName name of the project that cannot be created
 | 
			
		||||
   * @param nameOfExistingProject name of the project that already exists and
 | 
			
		||||
   *        occupies the name for the git repository in the file system
 | 
			
		||||
   */
 | 
			
		||||
  public RepositoryCaseMismatchException(final Project.NameKey projectName,
 | 
			
		||||
      final Project.NameKey nameOfExistingProject) {
 | 
			
		||||
    super("Name occupied in other case: " + projectName.get() + "; project "
 | 
			
		||||
        + nameOfExistingProject.get() + " exists");
 | 
			
		||||
    this.nameOfExistingProject = nameOfExistingProject;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public NameKey getNameOfExistingProject() {
 | 
			
		||||
    return nameOfExistingProject;
 | 
			
		||||
  public RepositoryCaseMismatchException(final Project.NameKey projectName) {
 | 
			
		||||
    super("Name occupied in other case. Project " + projectName.get()
 | 
			
		||||
        + " cannot be created.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -93,8 +93,8 @@ public class CreateProject {
 | 
			
		||||
 | 
			
		||||
  public void createProject() throws ProjectCreationFailedException {
 | 
			
		||||
    validateParameters();
 | 
			
		||||
    final Project.NameKey nameKey = createProjectArgs.getProject();
 | 
			
		||||
    try {
 | 
			
		||||
      final Project.NameKey nameKey = createProjectArgs.getProject();
 | 
			
		||||
      final String head =
 | 
			
		||||
          createProjectArgs.permissionsOnly ? GitRepositoryManager.REF_CONFIG
 | 
			
		||||
              : createProjectArgs.branch;
 | 
			
		||||
@@ -115,12 +115,30 @@ public class CreateProject {
 | 
			
		||||
      } finally {
 | 
			
		||||
        repo.close();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (IllegalStateException e) {
 | 
			
		||||
      handleRepositoryExistsException(createProjectArgs.getProject(), e);
 | 
			
		||||
    } catch (RepositoryCaseMismatchException ee) {
 | 
			
		||||
      handleRepositoryExistsException(ee.getNameOfExistingProject(), ee);
 | 
			
		||||
    } catch (RepositoryCaseMismatchException e) {
 | 
			
		||||
      throw new ProjectCreationFailedException("Cannot create " + nameKey.get()
 | 
			
		||||
          + " because the name is already occupied by another project."
 | 
			
		||||
          + " The other project has the same name, only spelled in a"
 | 
			
		||||
          + " different case.", e);
 | 
			
		||||
    } catch (RepositoryNotFoundException badName) {
 | 
			
		||||
      throw new ProjectCreationFailedException("Cannot create " + nameKey, badName);
 | 
			
		||||
    } catch (IllegalStateException err) {
 | 
			
		||||
      try {
 | 
			
		||||
        final Repository repo = repoManager.openRepository(nameKey);
 | 
			
		||||
        try {
 | 
			
		||||
          if (repo.getObjectDatabase().exists()) {
 | 
			
		||||
            throw new ProjectCreationFailedException("project \"" + nameKey + "\" exists");
 | 
			
		||||
          }
 | 
			
		||||
        } finally {
 | 
			
		||||
          repo.close();
 | 
			
		||||
        }
 | 
			
		||||
      } catch (RepositoryNotFoundException doesNotExist) {
 | 
			
		||||
        final String msg = "Cannot create " + nameKey;
 | 
			
		||||
        log.error(msg, err);
 | 
			
		||||
        throw new ProjectCreationFailedException(msg, err);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      final String msg = "Cannot create " + createProjectArgs.getProject();
 | 
			
		||||
      final String msg = "Cannot create " + nameKey;
 | 
			
		||||
      log.error(msg, e);
 | 
			
		||||
      throw new ProjectCreationFailedException(msg, e);
 | 
			
		||||
    }
 | 
			
		||||
@@ -245,22 +263,4 @@ public class CreateProject {
 | 
			
		||||
      oi.release();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void handleRepositoryExistsException(final Project.NameKey nameKey,
 | 
			
		||||
      Exception e) throws ProjectCreationFailedException {
 | 
			
		||||
    try {
 | 
			
		||||
      Repository repo = repoManager.openRepository(nameKey);
 | 
			
		||||
      try {
 | 
			
		||||
        if (repo.getObjectDatabase().exists()) {
 | 
			
		||||
          throw new ProjectCreationFailedException("Project \"" + nameKey
 | 
			
		||||
              + "\" exists", e);
 | 
			
		||||
        }
 | 
			
		||||
      } finally {
 | 
			
		||||
        repo.close();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (RepositoryNotFoundException er) {
 | 
			
		||||
      throw new ProjectCreationFailedException("Cannot create \"" + nameKey
 | 
			
		||||
          + "\"", er);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user