blob: 83c9cb97b269f92283318a5f3ab3f5bb387bb7bf [file] [log] [blame]
// Copyright (C) 2015 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.googlesource.gerrit.plugins.importer;
import static com.googlesource.gerrit.plugins.importer.ProgressMonitorUtil.updateAndEnd;
import static java.lang.String.format;
import com.google.common.base.Strings;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.importer.GerritApi.Version;
import com.googlesource.gerrit.plugins.importer.ImportProject.Input;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
@RequiresCapability(ImportCapability.ID)
class ImportProject implements RestModifyView<ConfigResource, Input> {
public static class Input {
public String from;
public String name;
public String user;
public String pass;
public String parent;
private void validateImport() throws BadRequestException {
if (Strings.isNullOrEmpty(from)) {
throw new BadRequestException("from is required");
}
if (Strings.isNullOrEmpty(user)) {
throw new BadRequestException("user is required");
}
if (Strings.isNullOrEmpty(pass)) {
throw new BadRequestException("pass is required");
}
}
private void validateCopy() throws BadRequestException {
from = Strings.emptyToNull(from);
user = Strings.emptyToNull(user);
pass = Strings.emptyToNull(pass);
if (from != null) {
throw new BadRequestException("from must not be set");
}
if (user != null) {
throw new BadRequestException("user must not be set");
}
if (pass != null) {
throw new BadRequestException("pass must not be set");
}
}
}
interface Factory {
ImportProject create(Project.NameKey targetProject);
}
private static Logger log = LoggerFactory.getLogger(ImportProject.class);
private static Version v2_11_2 = new Version("2.11.2");
private final ProjectCache projectCache;
private final OpenRepositoryStep openRepoStep;
private final ConfigureRepositoryStep configRepoStep;
private final GitFetchStep gitFetchStep;
private final ConfigureProjectStep configProjectStep;
private final ReplayChangesStep.Factory replayChangesFactory;
private final ImportGroupsStep.Factory importGroupsStepFactory;
private final GerritApi.Factory apiFactory;
private final Provider<CurrentUser> currentUser;
private final ImportJson importJson;
private final ImportLog importLog;
private final ProjectsCollection projects;
private final Project.NameKey targetProject;
private Project.NameKey srcProject;
private Project.NameKey parent;
private boolean force;
private GerritApi api;
private boolean copy;
private Writer err;
@Inject
ImportProject(
ProjectCache projectCache,
OpenRepositoryStep openRepoStep,
ConfigureRepositoryStep configRepoStep,
GitFetchStep gitFetchStep,
ConfigureProjectStep configProjectStep,
ReplayChangesStep.Factory replayChangesFactory,
ImportGroupsStep.Factory importGroupsStepFactory,
GerritApi.Factory apiFactory,
Provider<CurrentUser> currentUser,
ImportJson importJson,
ImportLog importLog,
ProjectsCollection projects,
@Assisted Project.NameKey targetProject) {
this.projectCache = projectCache;
this.openRepoStep = openRepoStep;
this.configRepoStep = configRepoStep;
this.gitFetchStep = gitFetchStep;
this.configProjectStep = configProjectStep;
this.replayChangesFactory = replayChangesFactory;
this.importGroupsStepFactory = importGroupsStepFactory;
this.apiFactory = apiFactory;
this.currentUser = currentUser;
this.importJson = importJson;
this.importLog = importLog;
this.projects = projects;
this.targetProject = targetProject;
}
ImportProject setCopy(boolean copy) {
this.copy = copy;
return this;
}
ImportProject setErr(Writer err) {
this.err = err;
return this;
}
@Override
public ImportStatistic apply(ConfigResource rsrc, Input input)
throws RestApiException, OrmException, IOException, ValidationException,
GitAPIException, NoSuchChangeException, NoSuchAccountException,
UpdateException, ConfigInvalidException {
if (input == null) {
input = new Input();
}
LockFile lockFile = lockForImport();
try {
return apply(lockFile, input, null);
} finally {
lockFile.unlock();
}
}
public ResumeImportStatistic resume(String user, String pass, boolean force,
File importStatus) throws RestApiException, OrmException, IOException,
GitAPIException, NoSuchChangeException, NoSuchAccountException,
UpdateException, ConfigInvalidException {
LockFile lockFile = lockForImport();
try {
ImportProjectInfo info = ImportJson.parse(importStatus);
ImportProject.Input input = new ImportProject.Input();
input.user = user;
input.pass = pass;
input.from = info.from;
input.name = info.name;
input.parent = info.parent;
this.force = force;
return apply(lockFile, input, info);
} finally {
lockFile.unlock();
}
}
private ResumeImportStatistic apply(LockFile lockFile, Input input,
ImportProjectInfo info) throws RestApiException, OrmException,
IOException, GitAPIException, NoSuchChangeException,
NoSuchAccountException, UpdateException, ConfigInvalidException {
boolean resume = info != null;
api = apiFactory.create(input.from, input.user, input.pass);
if (copy) {
input.validateCopy();
} else {
input.validateImport();
Version v = api.getVersion();
if (v.compareTo(v2_11_2) < 0) {
throw new BadRequestException(String.format(
"The version of the source Gerrit server %s is too old. "
+ "Its version is %s, but required is a version >= %s.",
input.from, v.formatted, v2_11_2));
}
}
ProgressMonitor pm = err != null ? new TextProgressMonitor(err) :
NullProgressMonitor.INSTANCE;
ResumeImportStatistic statistic = new ResumeImportStatistic();
try {
srcProject = !Strings.isNullOrEmpty(input.name)
? new Project.NameKey(input.name) : targetProject;
checkProjectInSource(pm);
setParentProjectName(input, pm);
checkPreconditions(pm);
try (Repository repo = openRepoStep.open(targetProject, resume, pm,
parent)) {
ImportJson.persist(lockFile, importJson.format(input, info), pm);
configRepoStep.configure(repo, srcProject, input.from, pm);
gitFetchStep.fetch(input.user, input.pass, repo, pm);
configProjectStep.configure(targetProject, parent, pm);
replayChangesFactory.create(input.from, api, repo,
srcProject, targetProject, force, resume, statistic, pm).replay();
if (!copy) {
importGroupsStepFactory.create(input.from, input.user, input.pass,
targetProject, pm).importGroups();
}
}
importLog.onImport((IdentifiedUser) currentUser.get(), srcProject,
targetProject, input.from);
} catch (BadRequestException e) {
throw e;
} catch (Exception e) {
importLog.onImport((IdentifiedUser) currentUser.get(), srcProject,
targetProject, input.from, e);
String msg = input.from != null
? format("Unable to transfer project '%s' from"
+ " source gerrit host '%s'.",
srcProject.get(), input.from)
: format("Unable to copy project '%s'.",
srcProject.get());
log.error(msg, e);
throw e;
}
return statistic;
}
private void checkProjectInSource(ProgressMonitor pm)
throws IOException, BadRequestException {
pm.beginTask("Check source project", 1);
api.getProject(srcProject.get());
updateAndEnd(pm);
}
private void setParentProjectName(Input input, ProgressMonitor pm)
throws IOException, BadRequestException {
pm.beginTask("Set parent project", 1);
if (parent == null) {
if (!Strings.isNullOrEmpty(input.parent)) {
parent = new Project.NameKey(input.parent);
} else {
parent = new Project.NameKey(
api.getProject(srcProject.get()).parent);
}
}
updateAndEnd(pm);
}
private void checkPreconditions(ProgressMonitor pm) throws BadRequestException {
pm.beginTask("Check preconditions", 1);
if (parent == null) {
throw new BadRequestException(
"The project has no parent in the source system. "
+ "It can only be imported if a parent project is specified.");
}
ProjectState p = projectCache.get(parent);
if (p == null) {
throw new BadRequestException(format(
"Parent project '%s' does not exist in target.", parent.get()));
}
updateAndEnd(pm);
}
private LockFile lockForImport() throws ResourceConflictException {
File importStatus = projects.FS_LAYOUT.getImportStatusFile(targetProject.get());
LockFile lockFile = new LockFile(importStatus);
try {
if (lockFile.lock()) {
return lockFile;
} else {
throw new ResourceConflictException(
"project is being imported from another session");
}
} catch (IOException e1) {
throw new ResourceConflictException("failed to lock project for import");
}
}
}