IDEA-133270 git: allow to pass "detect code movements" options to blame

* try to reopen closed annotations on change (ex: after commit)
* allow to disable "ignore whitespaces" option IDEA-183867
This commit is contained in:
Aleksey Pivovarov
2018-06-24 16:24:19 +03:00
parent 00fe2c2442
commit 72ec344b57
11 changed files with 238 additions and 35 deletions
@@ -25,7 +25,7 @@ import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import java.util.HashMap;
import org.jetbrains.annotations.CalledInAwt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -195,9 +195,10 @@ public abstract class FileAnnotation {
* If `this` is visible, hide it and show new one instead.
* If `this` is not visible, do nothing.
*
* @param newFileAnnotation annotations to be shown
* @param newFileAnnotation annotations to be shown or `null` to load annotations again
*/
public synchronized final void reload(@NotNull FileAnnotation newFileAnnotation) {
@CalledInAwt
public synchronized final void reload(@Nullable FileAnnotation newFileAnnotation) {
if (myReloader != null) myReloader.consume(newFileAnnotation);
}
@@ -121,6 +121,12 @@ public class VcsHistoryCache {
}
}
public void clearAnnotations() {
synchronized (myLock) {
myAnnotationCache.clear();
}
}
public static class CachedHistory {
private final FilePath myPath;
private final List<VcsFileRevision> myRevisions;
@@ -142,8 +142,7 @@ public class AnnotateDiffViewerAction {
EventData data = collectEventData(e);
assert data != null;
boolean annotationShown = data.annotator.isAnnotationShown();
if (annotationShown) {
if (!selected) {
data.annotator.hideAnnotation();
}
else {
@@ -15,11 +15,9 @@
*/
package com.intellij.openapi.vcs.actions;
import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.extensions.ExtensionPointName;
@@ -142,9 +140,24 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware {
});
fileAnnotation.setReloader(newFileAnnotation -> {
if (project.isDisposed()) return;
if (editor.getGutter().isAnnotationsShown()) {
assert Comparing.equal(fileAnnotation.getFile(), newFileAnnotation.getFile());
doAnnotate(editor, project, currentFile, newFileAnnotation, vcs, upToDateLineNumbers, false);
if (newFileAnnotation != null) {
assert Comparing.equal(fileAnnotation.getFile(), newFileAnnotation.getFile());
doAnnotate(editor, project, currentFile, newFileAnnotation, vcs, upToDateLineNumbers, false);
}
else {
DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent());
AnActionEvent event = AnActionEvent.createFromDataContext(ActionPlaces.UNKNOWN, null, dataContext);
Provider provider = getProvider(event);
if (provider != null && provider.isEnabled(event) && !provider.isSuspended(event)) {
provider.perform(event, true);
}
else {
editor.getGutter().closeAllAnnotations();
}
}
}
});
@@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.changes;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ZipperUpdater;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
@@ -39,7 +40,11 @@ import org.jetbrains.annotations.TestOnly;
import java.io.File;
import java.util.*;
import static com.intellij.openapi.diagnostic.Logger.getInstance;
public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnnotationLocalChangesListener {
private static final Logger LOG = getInstance(VcsAnnotationLocalChangesListenerImpl.class);
private final ZipperUpdater myUpdater;
private final MessageBusConnection myConnection;
@@ -132,11 +137,8 @@ public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnn
final VcsRevisionNumber number = fromDiffProvider(key);
if (number == null) continue;
final Collection<FileAnnotation> fileAnnotations = entry.getValue();
for (FileAnnotation annotation : fileAnnotations) {
if (annotation.isBaseRevisionChanged(number)) {
annotation.close();
}
}
List<FileAnnotation> copy = ContainerUtil.filter(fileAnnotations, it -> it.isBaseRevisionChanged(number));
invalidateAnnotations(copy);
}
}
}
@@ -162,11 +164,9 @@ public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnn
}
if (number == null) return;
for (FileAnnotation annotation : annotations) {
if (annotation.isBaseRevisionChanged(number)) {
annotation.close();
}
}
VcsRevisionNumber finalNumber = number;
List<FileAnnotation> copy = ContainerUtil.filter(annotations, it -> it.isBaseRevisionChanged(finalNumber));
invalidateAnnotations(copy);
}
}
@@ -181,18 +181,25 @@ public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnn
private void closeForVcs(final Set<VcsKey> refresh) {
if (refresh.isEmpty()) return;
final Set<FileAnnotation> copy = new HashSet<>();
synchronized (myLock) {
for (FileAnnotation annotation : myFileAnnotationMap.values()) {
final VcsKey key = annotation.getVcsKey();
if (key != null && refresh.contains(key)) {
copy.add(annotation);
List<FileAnnotation> copy = ContainerUtil.filter(myFileAnnotationMap.values(), it -> {
return it.getVcsKey() != null && refresh.contains(it.getVcsKey());
});
invalidateAnnotations(copy);
}
}
private static void invalidateAnnotations(@NotNull Collection<FileAnnotation> annotations) {
ApplicationManager.getApplication().invokeLater(() -> {
for (FileAnnotation annotation: annotations) {
try {
annotation.reload(null);
}
catch (Exception e) {
LOG.error(e);
}
}
}
for (FileAnnotation annotation : copy) {
annotation.close();
}
});
}
// annotations for already committed revisions should not register with this method - they are not subject to refresh
+1
View File
@@ -165,6 +165,7 @@
<vcsChangesViewRefresher implementation="git4idea.changes.GitChangesViewRefresher" />
<pushSupport implementation="git4idea.push.GitPushSupport"/>
<cherryPicker implementation="git4idea.cherrypick.GitCherryPicker"/>
<vcsAnnotationGutterActionProvider implementation="git4idea.actions.GitToggleAnnotationOptionsActionProvider" />
<vcsAnnotationGutterActionProvider implementation="git4idea.actions.GitShowCommitInLogAnnotationGutterActionProvider" />
<statistics.projectUsagesCollector implementation="git4idea.GitStatisticsCollector"/>
<statistics.projectUsagesCollector implementation="git4idea.GitUsagesTriggerCollector"/>
@@ -0,0 +1,128 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.actions;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.progress.util.BackgroundTaskUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.annotate.AnnotationGutterActionProvider;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.changes.VcsAnnotationRefresher;
import git4idea.GitVcs;
import git4idea.annotate.GitFileAnnotation;
import git4idea.config.GitVcsApplicationSettings;
import git4idea.config.GitVcsApplicationSettings.AnnotateDetectMovementsOption;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class GitToggleAnnotationOptionsActionProvider implements AnnotationGutterActionProvider {
private static final GitVcsApplicationSettings SETTINGS = GitVcsApplicationSettings.getInstance();
@NotNull
@Override
public AnAction createAction(@NotNull final FileAnnotation annotation) {
return new MyGroup(annotation);
}
private static void resetAllAnnotations(@NotNull Project project) {
ProjectLevelVcsManager.getInstance(project).getVcsHistoryCache().clearAnnotations();
BackgroundTaskUtil.syncPublisher(project, VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED).configurationChanged(GitVcs.getKey());
}
private static class MyGroup extends ActionGroup {
private final FileAnnotation myAnnotation;
public MyGroup(@NotNull FileAnnotation annotation) {
super("Options", true);
myAnnotation = annotation;
}
@NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent e) {
if (myAnnotation instanceof GitFileAnnotation) {
return new AnAction[]{
new ToggleIgnoreWhitespaces(myAnnotation.getProject()),
new ToggleInnerMovementsWhitespaces(myAnnotation.getProject()),
new ToggleOuterMovementsWhitespaces(myAnnotation.getProject()),
};
}
return AnAction.EMPTY_ARRAY;
}
}
private static class ToggleIgnoreWhitespaces extends ToggleAction implements DumbAware {
@NotNull private final Project myProject;
public ToggleIgnoreWhitespaces(@NotNull Project project) {
super("Ignore Whitespaces");
myProject = project;
}
@Override
public boolean isSelected(AnActionEvent e) {
return SETTINGS.isIgnoreWhitespaces();
}
@Override
public void setSelected(AnActionEvent e, boolean enabled) {
SETTINGS.setIgnoreWhitespaces(enabled);
resetAllAnnotations(myProject);
}
}
private static class ToggleInnerMovementsWhitespaces extends ToggleAction implements DumbAware {
@NotNull private final Project myProject;
public ToggleInnerMovementsWhitespaces(@NotNull Project project) {
super("Detect Movements Within File");
myProject = project;
}
@Override
public boolean isSelected(AnActionEvent e) {
return SETTINGS.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.INNER ||
SETTINGS.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.OUTER;
}
@Override
public void setSelected(AnActionEvent e, boolean enabled) {
if (enabled) {
SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.INNER);
}
else {
SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.NONE);
}
resetAllAnnotations(myProject);
}
}
private static class ToggleOuterMovementsWhitespaces extends ToggleAction implements DumbAware {
@NotNull private final Project myProject;
public ToggleOuterMovementsWhitespaces(@NotNull Project project) {
super("Detect Movements Across Files");
myProject = project;
}
@Override
public boolean isSelected(AnActionEvent e) {
return SETTINGS.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.OUTER;
}
@Override
public void setSelected(AnActionEvent e, boolean enabled) {
if (enabled) {
SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.OUTER);
}
else {
SETTINGS.setAnnotateDetectMovementsOption(AnnotateDetectMovementsOption.INNER);
}
resetAllAnnotations(myProject);
}
}
}
@@ -47,6 +47,8 @@ import git4idea.annotate.GitFileAnnotation.LineInfo;
import git4idea.commands.Git;
import git4idea.commands.GitCommand;
import git4idea.commands.GitLineHandler;
import git4idea.config.GitVcsApplicationSettings;
import git4idea.config.GitVcsApplicationSettings.AnnotateDetectMovementsOption;
import git4idea.history.GitFileHistory;
import git4idea.history.GitHistoryProvider;
import git4idea.history.GitHistoryUtils;
@@ -155,8 +157,20 @@ public class GitAnnotationProvider implements AnnotationProviderEx {
VirtualFile root = GitUtil.getGitRoot(repositoryFilePath);
GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.BLAME);
h.setStdoutSuppressed(true);
h.addParameters("--porcelain", "-l", "-t", "-w");
h.addParameters("--porcelain", "-l", "-t");
h.addParameters("--encoding=UTF-8");
GitVcsApplicationSettings settings = GitVcsApplicationSettings.getInstance();
if (settings.isIgnoreWhitespaces()) {
h.addParameters("-w");
}
if (settings.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.INNER) {
h.addParameters("-M");
}
else if (settings.getAnnotateDetectMovementsOption() == AnnotateDetectMovementsOption.OUTER) {
h.addParameters("-C");
}
if (revision == null) {
h.addParameters("HEAD");
}
@@ -16,6 +16,7 @@
package git4idea.annotate;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsKey;
@@ -36,7 +37,6 @@ import git4idea.GitContentRevision;
import git4idea.GitFileRevision;
import git4idea.GitRevisionNumber;
import git4idea.GitVcs;
import git4idea.i18n.GitBundle;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -150,9 +150,17 @@ public class GitFileAnnotation extends FileAnnotation {
fileRevision = myRevisions.get(myRevisionMap.get(revisionNumber));
}
String path = null;
if (!VcsUtil.getFilePath(myFile).equals(lineInfo.myFilePath)) {
path = FileUtil.getLocationRelativeToUserHome(lineInfo.myFilePath.getPresentableUrl());
}
String commitMessage = fileRevision != null ? fileRevision.getCommitMessage() : lineInfo.getSubject() + "\n...";
return GitBundle.message("annotation.tool.tip", revisionNumber.asString(), lineInfo.getAuthor(),
DateFormatUtil.formatDateTime(lineInfo.getAuthorDate()), commitMessage);
return "commit " + revisionNumber.asString() +
"\nAuthor: " + lineInfo.getAuthor() +
"\nDate: " + DateFormatUtil.formatDateTime(lineInfo.getAuthorDate()) +
(path != null ? "\nPath: " + path : "") +
"\n\n" + commitMessage;
}
@Nullable
@@ -29,6 +29,9 @@ public class GitVcsApplicationSettings implements PersistentStateComponent<GitVc
public static class State {
public String myPathToGit = null;
public SshExecutable SSH_EXECUTABLE = null;
public boolean ANNOTATE_IGNORE_SPACES = true;
public AnnotateDetectMovementsOption ANNOTATE_DETECT_INNER_MOVEMENTS = AnnotateDetectMovementsOption.NONE;
}
public static GitVcsApplicationSettings getInstance() {
@@ -73,4 +76,28 @@ public class GitVcsApplicationSettings implements PersistentStateComponent<GitVc
SshExecutable getIdeaSsh() {
return myState.SSH_EXECUTABLE;
}
public boolean isIgnoreWhitespaces() {
return myState.ANNOTATE_IGNORE_SPACES;
}
public void setIgnoreWhitespaces(boolean value) {
myState.ANNOTATE_IGNORE_SPACES = value;
}
@NotNull
public AnnotateDetectMovementsOption getAnnotateDetectMovementsOption() {
return myState.ANNOTATE_DETECT_INNER_MOVEMENTS;
}
public void setAnnotateDetectMovementsOption(@NotNull AnnotateDetectMovementsOption value) {
myState.ANNOTATE_DETECT_INNER_MOVEMENTS = value;
}
public enum AnnotateDetectMovementsOption {
NONE,
INNER,
OUTER
}
}
@@ -14,7 +14,6 @@ addrefspec.node.tags=Tags
addrefspec.reference.chooser.tooltip=Select remote branch and tag references
addrefspec.title=Add Reference(s)
annotate.action.name=Annotate
annotation.tool.tip=commit {0}\nAuthor: {1}\nDate: {2}\n\n{3}
changes.retrieving=Retrieving changes for revision {0}
checking.out=Checkout {0}
checkout.action.name=Checkout