/* * 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.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; 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 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 getBranchNamesExceptCurrent(GitRepository repository) { List localBranches = new ArrayList(repository.getBranches().getLocalBranches()); Collections.sort(localBranches); List remoteBranches = new ArrayList(repository.getBranches().getRemoteBranches()); Collections.sort(remoteBranches); if (repository.isOnBranch()) { localBranches.remove(repository.getCurrentBranch()); } final List branchNames = new ArrayList(); 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 %s doesn't exist in branch %s", GitUtil.fileOrFolder(file), file.getPresentableUrl(), branchToCompare), false, null); } } }