mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Custom Show Diff action handlers; implement for Git to fix IDEA-89160.
* Introduce VcsHistoryProvider#getHistoryDiffHandler and DiffFromHistoryHandler to let VCSs implement custom behavior on "Show Diff" invocation from the file history panel. Return null as default. * Implement the StandardDiffFromHistoryHandler, which completely retains the old functionality. * For Git return the GitDiffFromHistoryHandler which works as usual when comparing two revisions, but has different code when 1 commit is selected, and it is a merge commit. * If a merge commit is selected, show a popup where user can choose the parent commit to compare with. * Additionally, if the file was not actually modified in the merge commit (i.e. the merge happened without any conflicts requiring manual resolution), display a short notification about that to make it clear that the file was not touched in this revision (this solves the confusion problem described in IDEA-89160). * Add information about parents to the GitFileRevision: it is collected in GitHistoryUtils#history anyway.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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.openapi.vcs.history;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This handler is called when the user selects one or two revisions in the file history and invokes "Show Diff'.
|
||||
* Default handler is implemented in {@code vcs-impl}.
|
||||
* Custom handlers should be returned via {@link VcsHistoryProvider#getHistoryDiffHandler()}.
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public interface DiffFromHistoryHandler {
|
||||
|
||||
/**
|
||||
* Show diff when a single revision is selected in the file history panel.
|
||||
*
|
||||
* @param e AnActionEvent which happened, when user invoked "Show Diff".
|
||||
* @param filePath the file which history is shown.
|
||||
* @param revision the revision selected in the file history panel.
|
||||
*/
|
||||
void showDiff(@NotNull AnActionEvent e, @NotNull FilePath filePath, @NotNull VcsFileRevision revision);
|
||||
|
||||
/**
|
||||
* Show diff for 2 revisions selected from the file history panel.
|
||||
* The order of selected revisions is not defined.
|
||||
*
|
||||
* @param filePath the file which history is shown.
|
||||
* @param revision1 one of the selected revisions.
|
||||
* @param revision2 another selected revision.
|
||||
*/
|
||||
void showDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2);
|
||||
|
||||
}
|
||||
@@ -56,4 +56,12 @@ public interface VcsHistoryProvider extends VcsProviderMarker {
|
||||
void reportAppendableHistory(final FilePath path, final VcsAppendableHistorySessionPartner partner) throws VcsException;
|
||||
|
||||
boolean supportsHistoryForDirectories();
|
||||
|
||||
/**
|
||||
* The returned {@link DiffFromHistoryHandler} will be called, when user calls "Show Diff" from the file history panel.
|
||||
* If {@code null} is returned, the standard handler will be used, which is suitable for most cases.
|
||||
*/
|
||||
@Nullable
|
||||
DiffFromHistoryHandler getHistoryDiffHandler();
|
||||
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ import java.util.List;
|
||||
public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.cvsSupport2.ui.FileHistoryDialog");
|
||||
|
||||
@NotNull private final Project myProject;
|
||||
private final JEditorPane myComments;
|
||||
private JComponent myAdditionalDetails;
|
||||
private Consumer<VcsFileRevision> myListener;
|
||||
@@ -115,6 +116,8 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
private VcsFileRevision myBottomRevisionForShowDiff;
|
||||
private final DualView myDualView;
|
||||
|
||||
@NotNull private final DiffFromHistoryHandler myDiffHandler;
|
||||
|
||||
private final Alarm myUpdateAlarm;
|
||||
|
||||
private volatile boolean myInRefresh;
|
||||
@@ -126,6 +129,7 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
|
||||
private final Map<VcsRevisionNumber, Integer> myRevisionsOrder;
|
||||
private boolean myIsStaticAndEmbedded;
|
||||
private final Splitter myDetailsSplitter = new Splitter(false, 0.5f);
|
||||
|
||||
private final Comparator<VcsFileRevision> myRevisionsInOrderComparator = new Comparator<VcsFileRevision>() {
|
||||
@Override
|
||||
@@ -175,7 +179,6 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
}
|
||||
|
||||
};
|
||||
private final Splitter myDetailsSplitter = new Splitter(false, 0.5f);
|
||||
|
||||
public void scheduleRefresh() {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@@ -350,6 +353,7 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
VcsHistoryProvider provider,
|
||||
ContentManager contentManager, final FileHistoryRefresherI refresherI, final boolean isStaticEmbedded) {
|
||||
super(contentManager, provider.getHelpId() != null ? provider.getHelpId() : "reference.versionControl.toolwindow.history", ! isStaticEmbedded);
|
||||
myProject = vcs.getProject();
|
||||
myIsStaticAndEmbedded = false;
|
||||
myVcs = vcs;
|
||||
myProvider = provider;
|
||||
@@ -358,6 +362,9 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
myHistorySession = session;
|
||||
myFilePath = filePath;
|
||||
|
||||
DiffFromHistoryHandler customDiffHandler = provider.getHistoryDiffHandler();
|
||||
myDiffHandler = customDiffHandler == null ? new StandardDiffFromHistoryHandler() : customDiffHandler;
|
||||
|
||||
final DualViewColumnInfo[] columns = createColumnList(myVcs.getProject(), provider, session);
|
||||
|
||||
myComments = new JEditorPane(UIUtil.HTML_MIME, "");
|
||||
@@ -643,43 +650,6 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
}
|
||||
|
||||
|
||||
private void showDifferences(final Project project, final VcsFileRevision revision1, final VcsFileRevision revision2) {
|
||||
new Task.Backgroundable(project, "Loading revisions to compare") {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
VcsFileRevision left = revision1;
|
||||
VcsFileRevision right = revision2;
|
||||
if (VcsHistoryUtil.compare(revision1, revision2) > 0) {
|
||||
left = revision2;
|
||||
right = revision1;
|
||||
}
|
||||
|
||||
try {
|
||||
final String leftTitle = left.getRevisionNumber().asString() +
|
||||
(left instanceof CurrentRevision ? " (" + VcsBundle.message("diff.title.local") + ")" : "");
|
||||
final String rightTitle = right.getRevisionNumber().asString() +
|
||||
(right instanceof CurrentRevision ? " (" + VcsBundle.message("diff.title.local") + ")" : "");
|
||||
VcsHistoryUtil.showDiff(project, myFilePath, left, right, leftTitle, rightTitle);
|
||||
}
|
||||
catch (final VcsException e) {
|
||||
LOG.info(e);
|
||||
WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
|
||||
public void run() {
|
||||
Messages.showErrorDialog(VcsBundle.message("message.text.cannot.show.differences", e.getLocalizedMessage()),
|
||||
VcsBundle.message("message.title.show.differences"));
|
||||
}
|
||||
}, null, project);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
catch (ProcessCanceledException ex) {
|
||||
LOG.info(ex);
|
||||
}
|
||||
}
|
||||
}.queue();
|
||||
}
|
||||
|
||||
protected JComponent createCenterPanel() {
|
||||
mySplitter = new Splitter(true, getSplitterProportion());
|
||||
mySplitter.setDividerWidth(4);
|
||||
@@ -854,22 +824,17 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
FileHistoryPanelImpl.this);
|
||||
}
|
||||
|
||||
protected void actionPerformed() {
|
||||
protected void executeAction(AnActionEvent e) {
|
||||
List<TreeNodeOnVcsRevision> sel = getSelection();
|
||||
|
||||
int selectionSize = sel.size();
|
||||
if (selectionSize > 1) {
|
||||
showDifferences(myVcs.getProject(), sel.get(0), sel.get(sel.size() - 1));
|
||||
myDiffHandler.showDiff(e, myFilePath, sel.get(0).getRevision());
|
||||
}
|
||||
else if (selectionSize == 1) {
|
||||
final TableView<TreeNodeOnVcsRevision> flatView = myDualView.getFlatView();
|
||||
final int selectedRow = flatView.getSelectedRow();
|
||||
if (selectedRow == (flatView.getRowCount() - 1)) {
|
||||
// no previous
|
||||
showDifferences(myVcs.getProject(), myBottomRevisionForShowDiff != null ? myBottomRevisionForShowDiff : VcsFileRevision.NULL,
|
||||
getFirstSelectedRevision());
|
||||
} else {
|
||||
showDifferences(myVcs.getProject(), flatView.getRow(selectedRow + 1), getFirstSelectedRevision());
|
||||
VcsFileRevision revision = getFirstSelectedRevision();
|
||||
if (revision != null) {
|
||||
myDiffHandler.showDiff(e, myFilePath, revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -905,13 +870,16 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed() {
|
||||
protected void executeAction(AnActionEvent e) {
|
||||
final List<TreeNodeOnVcsRevision> selection = getSelection();
|
||||
if (selection.size() != 1) return;
|
||||
if (ChangeListManager.getInstance(myVcs.getProject()).isFreezedWithNotification(null)) return;
|
||||
final VcsRevisionNumber currentRevisionNumber = myHistorySession.getCurrentRevisionNumber();
|
||||
if (currentRevisionNumber != null) {
|
||||
showDifferences(myVcs.getProject(), getFirstSelectedRevision(), new CurrentRevision(myFilePath.getVirtualFile(), currentRevisionNumber));
|
||||
VcsHistoryUtil.showDifferencesInBackground(myVcs.getProject(), myFilePath,
|
||||
getFirstSelectedRevision(),
|
||||
new CurrentRevision(myFilePath.getVirtualFile(), currentRevisionNumber),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,7 +921,7 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void actionPerformed() {
|
||||
protected void executeAction(AnActionEvent e) {
|
||||
if (ChangeListManager.getInstance(myVcs.getProject()).isFreezedWithNotification(null)) return;
|
||||
final VcsFileRevision revision = getFirstSelectedRevision();
|
||||
if (getVirtualFile() != null) {
|
||||
@@ -1431,7 +1399,7 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
mySelectionProvider = tableProvider;
|
||||
}
|
||||
|
||||
protected abstract void actionPerformed();
|
||||
protected abstract void executeAction(AnActionEvent e);
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mySelectionProvider.getSelection().size() == mySuitableSelectedElements;
|
||||
@@ -1439,7 +1407,7 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
if (!isEnabled()) return;
|
||||
actionPerformed();
|
||||
executeAction(e);
|
||||
}
|
||||
|
||||
public void update(AnActionEvent e) {
|
||||
@@ -1798,4 +1766,30 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton {
|
||||
e.getPresentation().setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
private class StandardDiffFromHistoryHandler implements DiffFromHistoryHandler {
|
||||
|
||||
@Override
|
||||
public void showDiff(@NotNull AnActionEvent e, @NotNull FilePath filePath, @NotNull VcsFileRevision revision) {
|
||||
final TableView<TreeNodeOnVcsRevision> flatView = myDualView.getFlatView();
|
||||
final int selectedRow = flatView.getSelectedRow();
|
||||
if (selectedRow == (flatView.getRowCount() - 1)) {
|
||||
// no previous
|
||||
VcsHistoryUtil.showDifferencesInBackground(myVcs.getProject(), filePath,
|
||||
myBottomRevisionForShowDiff != null ? myBottomRevisionForShowDiff : VcsFileRevision.NULL,
|
||||
revision, true);
|
||||
} else {
|
||||
VcsHistoryUtil.showDifferencesInBackground(myVcs.getProject(), myFilePath, flatView.getRow(selectedRow + 1), revision, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) {
|
||||
VcsHistoryUtil.showDifferencesInBackground(myProject, myFilePath, revision1, revision2, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,14 +17,20 @@ package com.intellij.openapi.vcs.history;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.diff.*;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.VcsBundle;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
@@ -32,6 +38,7 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.encoding.EncodingManager;
|
||||
import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
|
||||
import com.intellij.util.WaitForProgressToShow;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -41,6 +48,8 @@ import java.nio.charset.Charset;
|
||||
|
||||
public class VcsHistoryUtil {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(VcsHistoryUtil.class);
|
||||
|
||||
private VcsHistoryUtil() {
|
||||
}
|
||||
|
||||
@@ -77,7 +86,9 @@ public class VcsHistoryUtil {
|
||||
* @throws com.intellij.openapi.vcs.VcsException
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static void showDiff(final Project project, FilePath filePath, VcsFileRevision revision1, VcsFileRevision revision2, String title1, String title2) throws VcsException, IOException {
|
||||
public static void showDiff(@NotNull final Project project, @NotNull FilePath filePath,
|
||||
@NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2,
|
||||
@NotNull String title1, @NotNull String title2) throws VcsException, IOException {
|
||||
final byte[] content1 = loadRevisionContent(revision1);
|
||||
final byte[] content2 = loadRevisionContent(revision2);
|
||||
|
||||
@@ -139,7 +150,7 @@ public class VcsHistoryUtil {
|
||||
}, null, project);
|
||||
}
|
||||
|
||||
public static byte[] loadRevisionContent(VcsFileRevision revision) throws VcsException, IOException {
|
||||
public static byte[] loadRevisionContent(@NotNull VcsFileRevision revision) throws VcsException, IOException {
|
||||
byte[] content = revision.getContent();
|
||||
if (content == null) {
|
||||
revision.loadContent();
|
||||
@@ -149,7 +160,7 @@ public class VcsHistoryUtil {
|
||||
return content;
|
||||
}
|
||||
|
||||
public static String loadRevisionContentGuessEncoding(final VcsFileRevision revision, @Nullable final VirtualFile file,
|
||||
public static String loadRevisionContentGuessEncoding(@NotNull final VcsFileRevision revision, @Nullable final VirtualFile file,
|
||||
@Nullable final Project project) throws VcsException, IOException {
|
||||
final byte[] bytes = loadRevisionContent(revision);
|
||||
if (file != null) {
|
||||
@@ -183,4 +194,54 @@ public class VcsHistoryUtil {
|
||||
return revision instanceof CurrentRevision;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows difference between two revisions of a file in a diff tool.
|
||||
* The content of revisions is queried in a background thread.
|
||||
* If {@code findOlderNewer} is set to {@code true}, revisions may be specified in any order:
|
||||
* this method will sort them so that the older revision is at the left, and the newer one is at the right.
|
||||
* @param findOlderNewer specify {@code true} to let method compare revisions, and put the older revision at the left, and newer revision
|
||||
* at the right.<br/>
|
||||
* Specify {@code false} to put {@code revision1} at the left, and {@code revision2} at the right.
|
||||
* @see #showDiff(Project, FilePath, VcsFileRevision, VcsFileRevision, String, String)
|
||||
*/
|
||||
public static void showDifferencesInBackground(@NotNull final Project project, @NotNull final FilePath filePath,
|
||||
@NotNull final VcsFileRevision revision1, @NotNull final VcsFileRevision revision2,
|
||||
final boolean findOlderNewer) {
|
||||
new Task.Backgroundable(project, "Loading revisions to compare") {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
VcsFileRevision left = revision1;
|
||||
VcsFileRevision right = revision2;
|
||||
if (findOlderNewer && compare(revision1, revision2) > 0) {
|
||||
left = revision2;
|
||||
right = revision1;
|
||||
}
|
||||
|
||||
try {
|
||||
final String leftTitle = left.getRevisionNumber().asString() +
|
||||
(left instanceof CurrentRevision ? " (" + VcsBundle.message("diff.title.local") + ")" : "");
|
||||
final String rightTitle = right.getRevisionNumber().asString() +
|
||||
(right instanceof CurrentRevision ? " (" + VcsBundle.message("diff.title.local") + ")" : "");
|
||||
showDiff(project, filePath, left, right, leftTitle, rightTitle);
|
||||
}
|
||||
catch (final VcsException e) {
|
||||
LOG.info(e);
|
||||
WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
|
||||
public void run() {
|
||||
Messages.showErrorDialog(VcsBundle.message("message.text.cannot.show.differences", e.getLocalizedMessage()),
|
||||
VcsBundle.message("message.title.show.differences"));
|
||||
}
|
||||
}, null, project);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
}
|
||||
catch (ProcessCanceledException ex) {
|
||||
LOG.info(ex);
|
||||
}
|
||||
}
|
||||
}.queue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -252,6 +252,11 @@ public class CvsHistoryProvider implements VcsHistoryProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiffFromHistoryHandler getHistoryDiffHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class MyHistoryAsTreeProvider implements HistoryAsTreeProvider {
|
||||
private static final MyHistoryAsTreeProvider ourInstance = new MyHistoryAsTreeProvider();
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -52,9 +54,10 @@ public class GitFileRevision extends VcsFileRevisionEx implements Comparable<Vcs
|
||||
private final String branch;
|
||||
private final Date myAuthorTime;
|
||||
private final boolean myNoCache;
|
||||
@NotNull private final Collection<String> myParents;
|
||||
|
||||
public GitFileRevision(@NotNull Project project, @NotNull FilePath path, @NotNull GitRevisionNumber revision, boolean noCache) {
|
||||
this(project, path, revision, null, null, null, null, noCache);
|
||||
this(project, path, revision, null, null, null, null, noCache, Collections.<String>emptyList());
|
||||
}
|
||||
|
||||
public GitFileRevision(@NotNull Project project,
|
||||
@@ -62,7 +65,7 @@ public class GitFileRevision extends VcsFileRevisionEx implements Comparable<Vcs
|
||||
@NotNull GitRevisionNumber revision,
|
||||
@Nullable Pair<Pair<String, String>, Pair<String, String>> authorAndCommitter,
|
||||
@Nullable String message,
|
||||
@Nullable String branch, final Date authorTime, boolean noCache) {
|
||||
@Nullable String branch, @Nullable final Date authorTime, boolean noCache, @NotNull Collection<String> parents) {
|
||||
this.project = project;
|
||||
this.path = path;
|
||||
this.revision = revision;
|
||||
@@ -71,6 +74,7 @@ public class GitFileRevision extends VcsFileRevisionEx implements Comparable<Vcs
|
||||
this.branch = branch;
|
||||
myAuthorTime = authorTime;
|
||||
myNoCache = noCache;
|
||||
myParents = parents;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,4 +156,15 @@ public class GitFileRevision extends VcsFileRevisionEx implements Comparable<Vcs
|
||||
public String toString() {
|
||||
return path.getName() + ":" + revision.getShortRev();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<String> getParents() {
|
||||
return myParents;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getHash() {
|
||||
return revision.getRev();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -106,13 +106,7 @@ public class GitRevisionNumber implements ShortVcsRevisionNumber {
|
||||
*/
|
||||
@NotNull
|
||||
public String getShortRev() {
|
||||
if (myRevisionHash.length() == 0) return "";
|
||||
if (myRevisionHash.length() == 40) return myRevisionHash.substring(0, 8);
|
||||
if (myRevisionHash.length() > 40) // revision string encoded with date too
|
||||
{
|
||||
return myRevisionHash.substring(myRevisionHash.indexOf("[") + 1, 8);
|
||||
}
|
||||
return myRevisionHash;
|
||||
return GitUtil.getShortHash(myRevisionHash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -86,6 +86,7 @@ public class GitUtil {
|
||||
public static final String DOT_GIT = ".git";
|
||||
|
||||
private final static Logger LOG = Logger.getInstance(GitUtil.class);
|
||||
private static final int SHORT_HASH_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* A private constructor to suppress instance creation
|
||||
@@ -844,4 +845,14 @@ public class GitUtil {
|
||||
}, "\n");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getShortHash(@NotNull String hash) {
|
||||
if (hash.length() == 0) return "";
|
||||
if (hash.length() == 40) return hash.substring(0, SHORT_HASH_LENGTH);
|
||||
if (hash.length() > 40) // revision string encoded with date too
|
||||
{
|
||||
return hash.substring(hash.indexOf("[") + 1, SHORT_HASH_LENGTH);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,9 @@ public interface Git {
|
||||
GitCommandResult push(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult show(@NotNull GitRepository repository, @NotNull String revision);
|
||||
|
||||
@NotNull
|
||||
GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit,
|
||||
@NotNull GitLineHandlerListener... listeners);
|
||||
|
||||
@@ -312,6 +312,13 @@ public class GitImpl implements Git {
|
||||
return push(repository, remote.getName(), pushSpec.getSource().getName() + ":" + destination, listeners);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GitCommandResult show(@NotNull GitRepository repository, @NotNull String revision) {
|
||||
final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.SHOW);
|
||||
return run(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit,
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2000-2012 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.history;
|
||||
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
|
||||
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.ui.MessageType;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.popup.ListPopup;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vcs.FilePath;
|
||||
import com.intellij.openapi.vcs.VcsException;
|
||||
import com.intellij.openapi.vcs.history.DiffFromHistoryHandler;
|
||||
import com.intellij.openapi.vcs.history.VcsFileRevision;
|
||||
import com.intellij.openapi.vcs.history.VcsHistoryUtil;
|
||||
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import git4idea.GitFileRevision;
|
||||
import git4idea.GitRevisionNumber;
|
||||
import git4idea.GitUtil;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.commands.GitCommandResult;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.repo.GitRepositoryManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* {@link DiffFromHistoryHandler#showDiff(FilePath, VcsFileRevision, VcsFileRevision) "Show Diff" for 2 revision} calls the common code.
|
||||
* {@link DiffFromHistoryHandler#showDiff(AnActionEvent, FilePath, VcsFileRevision) "Show diff" for 1 revision}
|
||||
* behaves differently for merge commits: for them it shown a popup displaying the parents of the selected commit. Selecting a parent
|
||||
* from the popup shows the difference with this parent.
|
||||
* If an ordinary (not merge) revision with 1 parent, it is the same as usual: just compare with the parent;
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
class GitDiffFromHistoryHandler implements DiffFromHistoryHandler {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(GitDiffFromHistoryHandler.class);
|
||||
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final Git myGit;
|
||||
@NotNull private final GitRepositoryManager myRepositoryManager;
|
||||
|
||||
GitDiffFromHistoryHandler(@NotNull Project project) {
|
||||
myProject = project;
|
||||
myGit = ServiceManager.getService(project, Git.class);
|
||||
myRepositoryManager = GitUtil.getRepositoryManager(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDiff(@NotNull AnActionEvent e, @NotNull FilePath filePath, @NotNull VcsFileRevision revision) {
|
||||
GitFileRevision rev = (GitFileRevision)revision;
|
||||
Collection<String> parents = rev.getParents();
|
||||
if (parents.size() < 2) {
|
||||
showDiffWithParent(revision, filePath, parents);
|
||||
}
|
||||
else { // merge
|
||||
showDiffForMergeCommit(e, filePath, rev, parents);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) {
|
||||
doShowDiff(filePath, revision1, revision2, true);
|
||||
}
|
||||
|
||||
private void doShowDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2,
|
||||
boolean autoSort) {
|
||||
VcsHistoryUtil.showDifferencesInBackground(myProject, filePath, revision1, revision2, autoSort);
|
||||
}
|
||||
|
||||
private void showDiffForMergeCommit(@NotNull final AnActionEvent event, @NotNull final FilePath filePath, @NotNull final GitFileRevision rev,
|
||||
@NotNull final Collection<String> parents) {
|
||||
final AtomicBoolean fileTouched = new AtomicBoolean();
|
||||
new Task.Backgroundable(myProject, "Retrieving revision changes", false) {
|
||||
@Override public void run(@NotNull ProgressIndicator indicator) {
|
||||
try {
|
||||
fileTouched.set(wasFileTouched(rev, filePath));
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.info("Error happened while executing git show " + rev + ":" + filePath, e);
|
||||
VcsBalloonProblemNotifier.showOverVersionControlView(GitDiffFromHistoryHandler.this.myProject, e.getMessage(), MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (fileTouched.get()) {
|
||||
String message = filePath.getName() + " did not change in this merge commit";
|
||||
VcsBalloonProblemNotifier.showOverVersionControlView(GitDiffFromHistoryHandler.this.myProject, message, MessageType.INFO);
|
||||
}
|
||||
showPopup(event, rev, filePath, parents);
|
||||
}
|
||||
}.queue();
|
||||
}
|
||||
|
||||
private void showPopup(@NotNull AnActionEvent event, @NotNull GitFileRevision rev, @NotNull FilePath filePath,
|
||||
@NotNull Collection<String> parents) {
|
||||
ActionGroup parentActions = createActionGroup(rev, filePath, parents);
|
||||
DataContext dataContext = SimpleDataContext.getProjectContext(myProject);
|
||||
ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Choose parent to compare", parentActions, dataContext,
|
||||
JBPopupFactory.ActionSelectionAid.NUMBERING, true);
|
||||
showPopupInBestPosition(popup, event, dataContext);
|
||||
}
|
||||
|
||||
private static void showPopupInBestPosition(@NotNull ListPopup popup, @NotNull AnActionEvent event, @NotNull DataContext dataContext) {
|
||||
if (event.getInputEvent() instanceof MouseEvent) {
|
||||
if (!event.getPlace().equals(ActionPlaces.UPDATE_POPUP)) {
|
||||
popup.show(new RelativePoint((MouseEvent)event.getInputEvent()));
|
||||
}
|
||||
else { // quick fix for invoking from the context menu: coordinates are calculated incorrectly there.
|
||||
popup.showInBestPositionFor(dataContext);
|
||||
}
|
||||
}
|
||||
else {
|
||||
popup.showInBestPositionFor(dataContext);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ActionGroup createActionGroup(@NotNull GitFileRevision rev, @NotNull FilePath filePath, @NotNull Collection<String> parents) {
|
||||
Collection<AnAction> actions = new ArrayList<AnAction>(2);
|
||||
for (String parent : parents) {
|
||||
actions.add(createParentAction(rev, filePath, parent));
|
||||
}
|
||||
return new DefaultActionGroup(ArrayUtil.toObjectArray(actions, AnAction.class));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AnAction createParentAction(@NotNull GitFileRevision rev, @NotNull FilePath filePath, @NotNull String parent) {
|
||||
return new ShowDiffWithParentAction(filePath, rev, parent);
|
||||
}
|
||||
|
||||
private void showDiffWithParent(@NotNull VcsFileRevision revision, @NotNull FilePath filePath, @NotNull Collection<String> parents) {
|
||||
VcsFileRevision parentRevision;
|
||||
if (parents.size() == 1) {
|
||||
String parent = parents.iterator().next();
|
||||
parentRevision = makeRevisionFromHash(filePath, parent);
|
||||
}
|
||||
else {
|
||||
parentRevision = VcsFileRevision.NULL;
|
||||
}
|
||||
doShowDiff(filePath, parentRevision, revision, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private GitFileRevision makeRevisionFromHash(@NotNull FilePath filePath, @NotNull String hash) {
|
||||
return new GitFileRevision(myProject, filePath, new GitRevisionNumber(hash), false);
|
||||
}
|
||||
|
||||
private boolean wasFileTouched(@NotNull GitFileRevision rev, @NotNull FilePath path) throws VcsException {
|
||||
VirtualFile file = path.getVirtualFile();
|
||||
LOG.assertTrue(file != null, "VirtualFile can't be null for " + path); // we clicked on a file and asked its history => VF must exist.
|
||||
GitRepository repository = myRepositoryManager.getRepositoryForFile(file);
|
||||
LOG.assertTrue(repository != null, "Repository is null for " + file);
|
||||
GitCommandResult result = myGit.show(repository, rev + ":" + path);
|
||||
if (result.success()) {
|
||||
return isFilePresentInOutput(repository, path, result.getOutput());
|
||||
}
|
||||
throw new VcsException(result.getErrorOutputAsJoinedString());
|
||||
}
|
||||
|
||||
private static boolean isFilePresentInOutput(@NotNull GitRepository repository, @NotNull FilePath path, @NotNull List<String> output) {
|
||||
String relativePath = FileUtil.getRelativePath(repository.getRoot().getPath(), path.getPath(), '/');
|
||||
for (String line : output) {
|
||||
if (line.startsWith("---") || line.startsWith("+++")) {
|
||||
if (line.contains(relativePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class ShowDiffWithParentAction extends AnAction {
|
||||
|
||||
@NotNull private final FilePath myFilePath;
|
||||
@NotNull private final GitFileRevision myRevision;
|
||||
@NotNull private final String myParentRevision;
|
||||
|
||||
public ShowDiffWithParentAction(@NotNull FilePath filePath, @NotNull GitFileRevision rev, @NotNull String parent) {
|
||||
super(GitUtil.getShortHash(parent));
|
||||
myFilePath = filePath;
|
||||
myRevision = rev;
|
||||
myParentRevision = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
doShowDiff(myFilePath, makeRevisionFromHash(myFilePath, myParentRevision), myRevision, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(AnActionEvent e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,49 +47,26 @@ import java.util.List;
|
||||
*/
|
||||
public class GitHistoryProvider implements VcsHistoryProvider, VcsCacheableHistorySessionFactory<Boolean, VcsAbstractHistorySession>,
|
||||
VcsBaseRevisionAdviser {
|
||||
/**
|
||||
* logger instance
|
||||
*/
|
||||
private static final Logger log = Logger.getInstance(GitHistoryProvider.class.getName());
|
||||
/**
|
||||
* the current project instance
|
||||
*/
|
||||
private final Project myProject;
|
||||
|
||||
/**
|
||||
* A constructor
|
||||
*
|
||||
* @param project a context project
|
||||
*/
|
||||
@NotNull private final Project myProject;
|
||||
|
||||
public GitHistoryProvider(@NotNull Project project) {
|
||||
this.myProject = project;
|
||||
myProject = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public VcsDependentHistoryComponents getUICustomization(final VcsHistorySession session, JComponent forShortcutRegistration) {
|
||||
return VcsDependentHistoryComponents.createOnlyColumns(new ColumnInfo[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @param refresher
|
||||
*/
|
||||
public AnAction[] getAdditionalActions(Runnable refresher) {
|
||||
return new AnAction[]{new GitShowAllSubmittedFilesAction(), new GitCopyHistoryRevisionNumberAction()};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public boolean isDateOmittable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
public String getHelpId() {
|
||||
return null;
|
||||
@@ -113,9 +90,6 @@ public class GitHistoryProvider implements VcsHistoryProvider, VcsCacheableHisto
|
||||
return createSession(filePath, revisions, currentRevision);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
public VcsHistorySession createSessionFor(final FilePath filePath) throws VcsException {
|
||||
List<VcsFileRevision> revisions = null;
|
||||
@@ -206,4 +180,9 @@ public class GitHistoryProvider implements VcsHistoryProvider, VcsCacheableHisto
|
||||
public boolean supportsHistoryForDirectories() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiffFromHistoryHandler getHistoryDiffHandler() {
|
||||
return new GitDiffFromHistoryHandler(myProject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +321,8 @@ public class GitHistoryUtils {
|
||||
// adjust path using change manager
|
||||
path = getLastCommitName(project, path);
|
||||
final VirtualFile finalRoot = (root == null ? GitUtil.getGitRoot(path) : root);
|
||||
final GitLogParser logParser = new GitLogParser(project, GitLogParser.NameStatus.NAME, HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL, PARENTS,
|
||||
final GitLogParser logParser = new GitLogParser(project, GitLogParser.NameStatus.NAME,
|
||||
HASH, COMMIT_TIME, AUTHOR_NAME, AUTHOR_EMAIL, COMMITTER_NAME, COMMITTER_EMAIL, PARENTS,
|
||||
SUBJECT, BODY, RAW_BODY, AUTHOR_TIME);
|
||||
|
||||
final AtomicReference<String> firstCommit = new AtomicReference<String>("HEAD");
|
||||
@@ -358,8 +359,9 @@ public class GitHistoryUtils {
|
||||
|
||||
final Pair<String, String> authorPair = Pair.create(record.getAuthorName(), record.getAuthorEmail());
|
||||
final Pair<String, String> committerPair = record.getCommitterName() == null ? null : Pair.create(record.getCommitterName(), record.getCommitterEmail());
|
||||
consumer.consume(new GitFileRevision(project, revisionPath, revision, Pair.create(authorPair, committerPair), message, null, new Date(record.getAuthorTimeStamp() * 1000),
|
||||
false));
|
||||
Collection<String> parents = parentHashes == null ? Collections.<String>emptyList() : Arrays.asList(parentHashes);
|
||||
consumer.consume(new GitFileRevision(project, revisionPath, revision, Pair.create(authorPair, committerPair), message, null,
|
||||
new Date(record.getAuthorTimeStamp() * 1000), false, parents));
|
||||
} catch (VcsException e) {
|
||||
exceptionConsumer.consume(e);
|
||||
}
|
||||
|
||||
@@ -159,6 +159,12 @@ class MockGit implements Git {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult show(@NotNull GitRepository repository, @NotNull String revision) {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
GitCommandResult cherryPick(@NotNull GitRepository repository, @NotNull String hash, boolean autoCommit, @NotNull GitLineHandlerListener... listeners) {
|
||||
|
||||
@@ -112,4 +112,9 @@ public class HgHistoryProvider implements VcsHistoryProvider {
|
||||
public boolean supportsHistoryForDirectories() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiffFromHistoryHandler getHistoryDiffHandler() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ public class SvnHistoryProvider
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiffFromHistoryHandler getHistoryDiffHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public VcsDependentHistoryComponents getUICustomization(final VcsHistorySession session, JComponent forShortcutRegistration) {
|
||||
final ColumnInfo[] columns;
|
||||
final Consumer<VcsFileRevision> listener;
|
||||
|
||||
Reference in New Issue
Block a user