Verify the case of the project name before opening git repository
When a project is not in the project cache, Gerrit checks for the corresponding git repository in the file system and adds the project to the cache if the git repository is found. On Windows the paths are case-insensitive, which means that the lookup for the git repository in the file system is even successfull if the name of the project was given in an incorrect case. E.g. lets assume the project 'test/myProject' exists, then asking the project cache for 'test/myProject', 'TEST/myProject', 'test/MyProject' would all succeed. Even worse for each variant of the case a new instance of the project would be added to the cache. These instances of the same project in the project cache might even become inconsistent with each other. This problem is relevant for all SSH commands where the user can specify a project name, e.g. for 'set-project-parent'. If for the '--parent' parameter the project is specified in an incorrect case, this incorrect case is then even persisted in the 'project.config' file of the child projects. To avoid these problems, with this change the case of the project name is now verified and the project is only added to the cache if the project name was specified in the correct case. If the project name was specified in an incorrect case the lookup would fail with an RepositoryNotFoundException. Change-Id: Icf826d16e05a8c19537e86fe302b158bcb407bba Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -100,8 +100,13 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
|
|||||||
throw new RepositoryNotFoundException("Invalid name: " + name);
|
throw new RepositoryNotFoundException("Invalid name: " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
|
||||||
|
|
||||||
|
if (!getProjectName(loc.getFile()).equals(name)) {
|
||||||
|
throw new RepositoryNotFoundException(gitDirOf(name));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final FileKey loc = FileKey.lenient(gitDirOf(name), FS.DETECTED);
|
|
||||||
return RepositoryCache.open(loc);
|
return RepositoryCache.open(loc);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
final RepositoryNotFoundException e2;
|
final RepositoryNotFoundException e2;
|
||||||
@@ -117,24 +122,30 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
|
|||||||
throw new RepositoryNotFoundException("Invalid name: " + name);
|
throw new RepositoryNotFoundException("Invalid name: " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
File dir = FileKey.resolve(gitDirOf(name), FS.DETECTED);
|
||||||
File dir = FileKey.resolve(gitDirOf(name), FS.DETECTED);
|
FileKey loc;
|
||||||
FileKey loc;
|
if (dir != null) {
|
||||||
if (dir != null) {
|
// Already exists on disk, use the repository we found.
|
||||||
// Already exists on disk, use the repository we found.
|
//
|
||||||
//
|
loc = FileKey.exact(dir, FS.DETECTED);
|
||||||
loc = FileKey.exact(dir, FS.DETECTED);
|
|
||||||
} else {
|
|
||||||
// It doesn't exist under any of the standard permutations
|
|
||||||
// of the repository name, so prefer the standard bare name.
|
|
||||||
//
|
|
||||||
String n = name.get();
|
|
||||||
if (!n.endsWith(Constants.DOT_GIT_EXT)) {
|
|
||||||
n = n + Constants.DOT_GIT_EXT;
|
|
||||||
}
|
|
||||||
loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final Project.NameKey nameOfExistingProject =
|
||||||
|
getProjectName(loc.getFile());
|
||||||
|
if (!nameOfExistingProject.equals(name)) {
|
||||||
|
throw new RepositoryCaseMismatchException(name, nameOfExistingProject);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It doesn't exist under any of the standard permutations
|
||||||
|
// of the repository name, so prefer the standard bare name.
|
||||||
|
//
|
||||||
|
String n = name.get();
|
||||||
|
if (!n.endsWith(Constants.DOT_GIT_EXT)) {
|
||||||
|
n = n + Constants.DOT_GIT_EXT;
|
||||||
|
}
|
||||||
|
loc = FileKey.exact(new File(basePath, n), FS.DETECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
Repository db = RepositoryCache.open(loc, false);
|
Repository db = RepositoryCache.open(loc, false);
|
||||||
db.create(true /* bare */);
|
db.create(true /* bare */);
|
||||||
|
|
||||||
@@ -256,19 +267,7 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
|
|||||||
for (File f : ls) {
|
for (File f : ls) {
|
||||||
String fileName = f.getName();
|
String fileName = f.getName();
|
||||||
if (FileKey.isGitRepository(f, FS.DETECTED)) {
|
if (FileKey.isGitRepository(f, FS.DETECTED)) {
|
||||||
String projectName;
|
Project.NameKey nameKey = getProjectName(prefix, fileName);
|
||||||
if (fileName.equals(Constants.DOT_GIT)) {
|
|
||||||
projectName = prefix.substring(0, prefix.length() - 1);
|
|
||||||
|
|
||||||
} else if (fileName.endsWith(Constants.DOT_GIT_EXT)) {
|
|
||||||
int newLen = fileName.length() - Constants.DOT_GIT_EXT.length();
|
|
||||||
projectName = prefix + fileName.substring(0, newLen);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
projectName = prefix + fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Project.NameKey nameKey = new Project.NameKey(projectName);
|
|
||||||
if (isUnreasonableName(nameKey)) {
|
if (isUnreasonableName(nameKey)) {
|
||||||
log.warn("Ignoring unreasonably named repository " + f.getAbsolutePath());
|
log.warn("Ignoring unreasonably named repository " + f.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
@@ -280,4 +279,30 @@ public class LocalDiskRepositoryManager implements GitRepositoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Project.NameKey getProjectName(final String prefix,
|
||||||
|
final String fileName) {
|
||||||
|
final String projectName;
|
||||||
|
if (fileName.equals(Constants.DOT_GIT)) {
|
||||||
|
projectName = prefix.substring(0, prefix.length() - 1);
|
||||||
|
|
||||||
|
} else if (fileName.endsWith(Constants.DOT_GIT_EXT)) {
|
||||||
|
int newLen = fileName.length() - Constants.DOT_GIT_EXT.length();
|
||||||
|
projectName = prefix + fileName.substring(0, newLen);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
projectName = prefix + fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Project.NameKey(projectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Project.NameKey getProjectName(final File gitDir) {
|
||||||
|
final String relativeGitPath =
|
||||||
|
getBasePath().toURI().relativize(gitDir.toURI()).getPath();
|
||||||
|
final String prefix =
|
||||||
|
relativeGitPath.substring(0, relativeGitPath.length() - 1
|
||||||
|
- gitDir.getName().length());
|
||||||
|
return getProjectName(prefix, gitDir.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2011 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.server.git;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.Project;
|
||||||
|
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown if a project cannot be created because a project
|
||||||
|
* with the same name in a different case already exists. This can only happen
|
||||||
|
* if the OS has a case insensitive file system (e.g. Windows), because in this
|
||||||
|
* case the name for the git repository in the file system is already occupied
|
||||||
|
* by the existing project.
|
||||||
|
*/
|
||||||
|
public class RepositoryCaseMismatchException extends
|
||||||
|
RepositoryNotFoundException {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
|
|||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||||
import com.google.gerrit.server.git.ProjectConfig;
|
import com.google.gerrit.server.git.ProjectConfig;
|
||||||
import com.google.gerrit.server.git.ReplicationQueue;
|
import com.google.gerrit.server.git.ReplicationQueue;
|
||||||
|
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gerrit.server.project.ProjectControl;
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
import com.google.gerrit.sshd.BaseCommand;
|
import com.google.gerrit.sshd.BaseCommand;
|
||||||
@@ -178,18 +179,9 @@ final class CreateProject extends BaseCommand {
|
|||||||
repo.close();
|
repo.close();
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException err) {
|
} catch (IllegalStateException err) {
|
||||||
try {
|
handleRepositoryExistsException(nameKey);
|
||||||
Repository repo = repoManager.openRepository(nameKey);
|
} catch (RepositoryCaseMismatchException err) {
|
||||||
try {
|
handleRepositoryExistsException(err.getNameOfExistingProject());
|
||||||
if (repo.getObjectDatabase().exists()) {
|
|
||||||
throw new UnloggedFailure(1, "fatal: project \"" + projectName + "\" exists");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
repo.close();
|
|
||||||
}
|
|
||||||
} catch (RepositoryNotFoundException doesNotExist) {
|
|
||||||
throw new Failure(1, "fatal: Cannot create " + projectName, err);
|
|
||||||
}
|
|
||||||
} catch (RepositoryNotFoundException badName) {
|
} catch (RepositoryNotFoundException badName) {
|
||||||
throw new UnloggedFailure(1, "fatal: " + badName.getMessage());
|
throw new UnloggedFailure(1, "fatal: " + badName.getMessage());
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
@@ -297,4 +289,21 @@ final class CreateProject extends BaseCommand {
|
|||||||
throw new Failure(1, "--branch \"" + branch + "\" is not a valid name");
|
throw new Failure(1, "--branch \"" + branch + "\" is not a valid name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleRepositoryExistsException(final Project.NameKey projectName)
|
||||||
|
throws Failure {
|
||||||
|
try {
|
||||||
|
Repository repo = repoManager.openRepository(projectName);
|
||||||
|
try {
|
||||||
|
if (repo.getObjectDatabase().exists()) {
|
||||||
|
throw new UnloggedFailure(1, "fatal: project \"" + projectName
|
||||||
|
+ "\" exists");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
repo.close();
|
||||||
|
}
|
||||||
|
} catch (RepositoryNotFoundException err) {
|
||||||
|
throw new Failure(1, "fatal: Cannot create " + projectName, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user