blob: 1b46c6735f8469430ec09cc6ff80e385cd7c3712 [file] [log] [blame]
/*
* Copyright 2000-2014 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 git4idea.branch;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.*;
import com.intellij.vcs.log.data.DataPack;
import com.intellij.vcs.log.data.VcsLogDataHolder;
import com.intellij.vcs.log.data.VcsLogRefreshListener;
import com.intellij.vcs.log.impl.HashImpl;
import git4idea.GitBranch;
import git4idea.commands.GitCommand;
import git4idea.commands.GitLineHandler;
import git4idea.commands.GitLineHandlerAdapter;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.Map;
public class DeepComparator implements Disposable {
private static final Logger LOG = Logger.getInstance(DeepComparator.class);
@NotNull private final Project myProject;
@NotNull private final GitRepositoryManager myRepositoryManager;
@NotNull private final VcsLogUi myUi;
@NotNull private final VcsLogFilterChangeListener myFilterChangeListener;
@Nullable private VcsLogHighlighter myHighlighter;
@Nullable private MyTask myTask;
@NotNull
public static DeepComparator getInstance(@NotNull Project project, @NotNull VcsLogUi ui) {
DeepComparatorHolder holder = ServiceManager.getService(project, DeepComparatorHolder.class);
return holder.getInstance(ui);
}
DeepComparator(@NotNull Project project, @NotNull GitRepositoryManager manager, @NotNull VcsLogUi ui, @NotNull Disposable parent) {
myProject = project;
myRepositoryManager = manager;
myUi = ui;
Disposer.register(parent, this);
myFilterChangeListener = new VcsLogFilterChangeListener() {
@Override
public void filtersPossiblyChanged() {
if (myTask == null) { // no task in progress => not interested in filter changes
return;
}
VcsLogBranchFilter branchFilter = myUi.getFilterUi().getFilters().getBranchFilter();
if (branchFilter == null ||
branchFilter.getBranchNames().size() != 1 ||
!branchFilter.getBranchNames().iterator().next().equals(myTask.myComparedBranch)) {
stopAndUnhighlight();
}
}
};
myUi.addFilterChangeListener(myFilterChangeListener);
project.getMessageBus().connect(project).subscribe(VcsLogDataHolder.REFRESH_COMPLETED, new VcsLogRefreshListener() {
@Override
public void refresh(@NotNull DataPack dataPack) {
if (myTask == null) { // no task in progress => not interested in refresh events
return;
}
// collect data
String comparedBranch = myTask.myComparedBranch;
Map<GitRepository, GitBranch> repositoriesWithCurrentBranches = myTask.myRepositoriesWithCurrentBranches;
VcsLogDataProvider provider = myTask.myProvider;
stopAndUnhighlight();
// highlight again
Map<GitRepository, GitBranch> repositories = getRepositories(myUi.getDataPack().getLogProviders(), comparedBranch);
if (repositories.equals(repositoriesWithCurrentBranches)) { // but not if current branch changed
highlightInBackground(comparedBranch, provider);
}
}
});
}
public void highlightInBackground(@NotNull String branchToCompare, @NotNull VcsLogDataProvider dataProvider) {
if (myTask != null) {
LOG.error("Shouldn't be possible");
return;
}
Map<GitRepository, GitBranch> repositories = getRepositories(myUi.getDataPack().getLogProviders(), branchToCompare);
if (repositories.isEmpty()) {
return;
}
myTask = new MyTask(myProject, myUi, repositories, dataProvider, branchToCompare);
myTask.queue();
}
@NotNull
private Map<GitRepository, GitBranch> getRepositories(@NotNull Map<VirtualFile, VcsLogProvider> providers,
@NotNull String branchToCompare) {
Map<GitRepository, GitBranch> repos = ContainerUtil.newHashMap();
for (VirtualFile root : providers.keySet()) {
GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
if (repository == null || repository.getCurrentBranch() == null ||
repository.getBranches().findBranchByName(branchToCompare) == null) {
continue;
}
repos.put(repository, repository.getCurrentBranch());
}
return repos;
}
public void stopAndUnhighlight() {
if (myTask != null) {
myTask.cancel();
myTask = null;
}
if (myHighlighter != null) {
myUi.removeHighlighter(myHighlighter);
}
}
@Override
public void dispose() {
stopAndUnhighlight();
myUi.removeFilterChangeListener(myFilterChangeListener);
}
public boolean hasHighlightingOrInProgress() {
return myTask != null;
}
private class MyTask extends Task.Backgroundable {
@NotNull private final Project myProject;
@NotNull private final VcsLogUi myUi;
@NotNull private final Map<GitRepository, GitBranch> myRepositoriesWithCurrentBranches;
@NotNull private final VcsLogDataProvider myProvider;
@NotNull private final String myComparedBranch;
@NotNull private final TIntHashSet myNonPickedCommits = new TIntHashSet();
@Nullable private VcsException myException;
private boolean myCancelled;
public MyTask(@NotNull Project project, @NotNull VcsLogUi ui, @NotNull Map<GitRepository, GitBranch> repositoriesWithCurrentBranches,
@NotNull VcsLogDataProvider dataProvider, @NotNull String branchToCompare) {
super(project, "Comparing branches...");
myProject = project;
myUi = ui;
myRepositoriesWithCurrentBranches = repositoriesWithCurrentBranches;
myProvider = dataProvider;
myComparedBranch = branchToCompare;
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
for (Map.Entry<GitRepository, GitBranch> entry : myRepositoriesWithCurrentBranches.entrySet()) {
GitRepository repo = entry.getKey();
GitBranch currentBranch = entry.getValue();
myNonPickedCommits.addAll(getNonPickedCommitsFromGit(myProject, repo.getRoot(), myProvider,
currentBranch.getName(), myComparedBranch).toArray());
}
}
catch (VcsException e) {
LOG.warn(e);
myException = e;
}
}
@Override
public void onSuccess() {
if (myCancelled) {
return;
}
if (myException != null) {
VcsNotifier.getInstance(myProject).notifyError("Couldn't compare with branch " + myComparedBranch, myException.getMessage());
return;
}
if (myHighlighter != null) {
myUi.removeHighlighter(myHighlighter);
}
myHighlighter = new VcsLogHighlighter() {
@Nullable
@Override
public Color getForeground(int commitIndex, boolean isSelected) {
return !myNonPickedCommits.contains(commitIndex) ? JBColor.GRAY : null;
}
};
myUi.addHighlighter(myHighlighter);
}
public void cancel() {
myCancelled = true;
}
@NotNull
private TIntHashSet getNonPickedCommitsFromGit(@NotNull Project project, @NotNull VirtualFile root,
@NotNull final VcsLogDataProvider dataProvider,
@NotNull String currentBranch, @NotNull String comparedBranch) throws VcsException {
GitLineHandler handler = new GitLineHandler(project, root, GitCommand.CHERRY);
handler.addParameters(currentBranch, comparedBranch); // upstream - current branch; head - compared branch
final TIntHashSet pickedCommits = new TIntHashSet();
handler.addLineListener(new GitLineHandlerAdapter() {
@Override
public void onLineAvailable(String line, Key outputType) {
// + 645caac042ff7fb1a5e3f7d348f00e9ceea5c317
// - c3b9b90f6c26affd7e597ebf65db96de8f7e5860
if (line.startsWith("+")) {
try {
line = line.substring(2).trim();
int firstSpace = line.indexOf(' ');
if (firstSpace > 0) {
line = line.substring(0, firstSpace); // safety-check: take just the first word for sure
}
Hash hash = HashImpl.build(line);
pickedCommits.add(dataProvider.getCommitIndex(hash));
}
catch (Exception e) {
LOG.error("Couldn't parse line [" + line + "]");
}
}
}
});
handler.runInCurrentThread(null);
return pickedCommits;
}
}
}