blob: 231fd8e6d5d1c9ef31862670e6e3c89d22420f4d [file] [log] [blame]
/*
* Copyright 2000-2010 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.actions;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.history.CurrentRevision;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.components.JBList;
import git4idea.GitBranch;
import git4idea.GitFileRevision;
import git4idea.GitRevisionNumber;
import git4idea.GitUtil;
import git4idea.history.GitDiffFromHistoryHandler;
import git4idea.history.GitHistoryUtils;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.util.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Compares selected file with itself in another branch.
* @author Kirill Likhodedov
*/
public class GitCompareWithBranchAction extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance(GitCompareWithBranchAction.class.getName());
@Override
public void actionPerformed(final AnActionEvent event) {
final Project project = event.getProject();
assert project != null;
final VirtualFile file = getAffectedFile(event);
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
GitRepository repository = manager.getRepositoryForFile(file);
assert repository != null;
GitBranch currentBranch = repository.getCurrentBranch();
final String head;
if (currentBranch == null) {
String currentRevision = repository.getCurrentRevision();
LOG.assertTrue(currentRevision != null,
"Current revision is null for " + repository + ". Compare with branch shouldn't be available for fresh repository");
head = GitUtil.getShortHash(currentRevision);
}
else {
head = currentBranch.getName();
}
final List<String> branchNames = getBranchNamesExceptCurrent(repository);
// prepare and invoke popup
final JBList list = new JBList(branchNames);
JBPopupFactory.getInstance()
.createListPopupBuilder(list)
.setTitle("Select branch to compare")
.setItemChoosenCallback(new OnBranchChooseRunnable(project, file, head, list))
.setAutoselectOnMouseMove(true)
.createPopup()
.showInBestPositionFor(event.getDataContext());
}
private static List<String> getBranchNamesExceptCurrent(GitRepository repository) {
List<GitBranch> localBranches = new ArrayList<GitBranch>(repository.getBranches().getLocalBranches());
Collections.sort(localBranches);
List<GitBranch> remoteBranches = new ArrayList<GitBranch>(repository.getBranches().getRemoteBranches());
Collections.sort(remoteBranches);
if (repository.isOnBranch()) {
localBranches.remove(repository.getCurrentBranch());
}
final List<String> branchNames = new ArrayList<String>();
for (GitBranch branch : localBranches) {
branchNames.add(branch.getName());
}
for (GitBranch branch : remoteBranches) {
branchNames.add(branch.getName());
}
return branchNames;
}
private static VirtualFile getAffectedFile(AnActionEvent event) {
final VirtualFile[] vFiles = event.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
assert vFiles != null && vFiles.length == 1 && vFiles[0] != null : "Illegal virtual files selected: " + Arrays.toString(vFiles);
return vFiles[0];
}
@Override
public void update(AnActionEvent e) {
super.update(e);
Presentation presentation = e.getPresentation();
Project project = e.getProject();
if (project == null) {
presentation.setEnabled(false);
presentation.setVisible(false);
return;
}
VirtualFile[] vFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
if (vFiles == null || vFiles.length != 1 || vFiles[0] == null) { // only 1 file for now
presentation.setEnabled(false);
presentation.setVisible(true);
return;
}
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
GitRepository repository = manager.getRepositoryForFile(vFiles[0]);
if (repository == null || repository.isFresh() || noBranchesToCompare(repository)) {
presentation.setEnabled(false);
presentation.setVisible(true);
return;
}
presentation.setEnabled(true);
presentation.setVisible(true);
}
private static boolean noBranchesToCompare(@NotNull GitRepository repository) {
int locals = repository.getBranches().getLocalBranches().size();
boolean haveRemotes = !repository.getBranches().getRemoteBranches().isEmpty();
if (repository.isOnBranch()) { // there are other branches to compare
return locals < 2 && !haveRemotes;
}
return locals == 0 && !haveRemotes; // there are at least 1 branch to compare
}
private static class OnBranchChooseRunnable implements Runnable {
private final Project myProject;
private final VirtualFile myFile;
private final String myHead;
private final JBList myList;
public OnBranchChooseRunnable(Project project, VirtualFile file, String head, JBList list) {
myProject = project;
myFile = file;
myHead = head;
myList = list;
}
@Override
public void run() {
String branchToCompare = myList.getSelectedValue().toString();
try {
showDiffWithBranch(myProject, myFile, myHead, branchToCompare);
}
catch (VcsException e) {
if (e.getMessage().contains("exists on disk, but not in")) {
fileDoesntExistInBranchError(myProject, myFile, branchToCompare);
} else {
GitUIUtil.notifyError(myProject, "Couldn't compare with branch",
String.format("Couldn't compare file [%s] with selected branch [%s]", myFile, myList.getSelectedValue()),
false, e);
}
}
}
private static void showDiffWithBranch(@NotNull Project project, @NotNull VirtualFile file, @NotNull String head,
@NotNull String branchToCompare) throws VcsException {
final FilePath filePath = new FilePathImpl(file);
// we could use something like GitRepository#getCurrentRevision here,
// but this way we can easily identify if the file is available in the branch
final GitRevisionNumber currentRevisionNumber = (GitRevisionNumber)GitHistoryUtils.getCurrentRevision(project, filePath, head);
final GitRevisionNumber compareRevisionNumber =
(GitRevisionNumber)GitHistoryUtils.getCurrentRevision(project, filePath, branchToCompare);
if (compareRevisionNumber == null) {
fileDoesntExistInBranchError(project, file, branchToCompare);
return;
}
LOG.assertTrue(currentRevisionNumber != null,
String.format("Current revision number is null for file [%s] and branch [%s]", filePath, head));
// constructing the revision with human readable name (will work for files comparison however).
final VcsFileRevision compareRevision =
new GitFileRevision(project, filePath, new GitRevisionNumber(branchToCompare, compareRevisionNumber.getTimestamp()));
CurrentRevision currentRevision = new CurrentRevision(file, new GitRevisionNumber(head, currentRevisionNumber.getTimestamp()));
new GitDiffFromHistoryHandler(project).showDiffForTwo(new FilePathImpl(file), compareRevision, currentRevision);
}
private static void fileDoesntExistInBranchError(@NotNull Project project, @NotNull VirtualFile file, @NotNull String branchToCompare) {
GitUIUtil.notifyMessage(project, GitUtil.fileOrFolder(file) + " doesn't exist in branch",
String.format("%s <code>%s</code> doesn't exist in branch <code>%s</code>",
GitUtil.fileOrFolder(file), file.getPresentableUrl(), branchToCompare),
NotificationType.WARNING, true, null);
}
}
}