/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* 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.intellij.dvcs.cherrypick;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsKey;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.ChangeListManagerEx;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.vcs.log.CommitId;
import com.intellij.vcs.log.VcsFullCommitDetails;
import com.intellij.vcs.log.VcsLog;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class VcsCherryPickManager {
private static final Logger LOG = Logger.getInstance(VcsCherryPickManager.class);
@NotNull private final Project myProject;
@NotNull private final ProjectLevelVcsManager myProjectLevelVcsManager;
@NotNull private final Set<CommitId> myIdsInProgress = ContainerUtil.newConcurrentSet();
public VcsCherryPickManager(@NotNull Project project, @NotNull ProjectLevelVcsManager projectLevelVcsManager) {
myProject = project;
myProjectLevelVcsManager = projectLevelVcsManager;
}
public void cherryPick(@NotNull VcsLog log) {
log.requestSelectedDetails(new Consumer<List<VcsFullCommitDetails>>() {
@Override
public void consume(List<VcsFullCommitDetails> details) {
ProgressManager.getInstance().run(new CherryPickingTask(myProject, ContainerUtil.reverse(details)));
}
}, null);
}
public boolean isCherryPickAlreadyStartedFor(@NotNull List<CommitId> commits) {
for (CommitId commit : commits) {
if (myIdsInProgress.contains(commit)) {
return true;
}
}
return false;
}
@Nullable
private VcsCherryPicker getCherryPickerForCommit(@NotNull VcsFullCommitDetails commitDetails) {
AbstractVcs vcs = myProjectLevelVcsManager.getVcsFor(commitDetails.getRoot());
if (vcs == null) return null;
VcsKey key = vcs.getKeyInstanceMethod();
return getCherryPickerFor(key);
}
@Nullable
public VcsCherryPicker getCherryPickerFor(@NotNull final VcsKey key) {
return ContainerUtil.find(Extensions.getExtensions(VcsCherryPicker.EXTENSION_POINT_NAME, myProject), new Condition<VcsCherryPicker>() {
@Override
public boolean value(VcsCherryPicker picker) {
return picker.getSupportedVcs().equals(key);
}
});
}
private class CherryPickingTask extends Task.Backgroundable {
@NotNull private final List<VcsFullCommitDetails> myAllDetailsInReverseOrder;
@NotNull private final ChangeListManagerEx myChangeListManager;
public CherryPickingTask(@NotNull Project project, @NotNull List<VcsFullCommitDetails> detailsInReverseOrder) {
super(project, "Cherry-Picking");
myAllDetailsInReverseOrder = detailsInReverseOrder;
myChangeListManager = (ChangeListManagerEx)ChangeListManager.getInstance(myProject);
myChangeListManager.blockModalNotifications();
}
@Nullable
private VcsCherryPicker getCherryPickerOrReportError(@NotNull VcsFullCommitDetails details) {
CommitId commitId = new CommitId(details.getId(), details.getRoot());
if (myIdsInProgress.contains(commitId)) {
showError("Cherry pick process is already started for commit " +
commitId.getHash().toShortString() +
" from root " +
commitId.getRoot().getName());
return null;
}
myIdsInProgress.add(commitId);
VcsCherryPicker cherryPicker = getCherryPickerForCommit(details);
if (cherryPicker == null) {
showError(
"Cherry pick is not supported for commit " + details.getId().toShortString() + " from root " + details.getRoot().getName());
return null;
}
return cherryPicker;
}
public void showError(@NotNull String message) {
VcsNotifier.getInstance(myProject).notifyWeakError(message);
LOG.warn(message);
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
boolean isOk = true;
MultiMap<VcsCherryPicker, VcsFullCommitDetails> groupedCommits = createArrayMultiMap();
for (VcsFullCommitDetails details : myAllDetailsInReverseOrder) {
VcsCherryPicker cherryPicker = getCherryPickerOrReportError(details);
if (cherryPicker == null) {
isOk = false;
break;
}
groupedCommits.putValue(cherryPicker, details);
}
if (isOk) {
for (Map.Entry<VcsCherryPicker, Collection<VcsFullCommitDetails>> entry : groupedCommits.entrySet()) {
entry.getKey().cherryPick(Lists.newArrayList(entry.getValue()));
}
}
}
finally {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
myChangeListManager.unblockModalNotifications();
for (VcsFullCommitDetails details : myAllDetailsInReverseOrder) {
myIdsInProgress.remove(new CommitId(details.getId(), details.getRoot()));
}
}
});
}
}
@NotNull
public MultiMap<VcsCherryPicker, VcsFullCommitDetails> createArrayMultiMap() {
return new MultiMap<VcsCherryPicker, VcsFullCommitDetails>() {
@NotNull
@Override
protected Collection<VcsFullCommitDetails> createCollection() {
return new ArrayList<>();
}
};
}
}
public static VcsCherryPickManager getInstance(@NotNull Project project) {
return ServiceManager.getService(project, VcsCherryPickManager.class);
}
}