vcs: share logic between "Create patch" and "Create patch to clipboard" actions

* enable action for arbitrary selection in Local Changes toolwindow
* reuse default logic when invoked from VCS Log
This commit is contained in:
Aleksey Pivovarov
2017-06-08 14:13:36 +03:00
committed by Aleksey Pivovarov
parent 32c5aea1fd
commit a8cfb9f913
12 changed files with 147 additions and 183 deletions
@@ -1286,6 +1286,8 @@ action.ChangesView.Ignore.text=Ignore...
action.ChangesView.Ignore.description=Do not show selected files as unversioned
action.ChangesView.CreatePatchFromChanges.text=Create Patch...
action.ChangesView.CreatePatchFromChanges.description=Create a patch from the selected changes
action.ChangesView.CreatePatchToClipboard.text=Copy as Patch to Clipboard
action.ChangesView.CreatePatchToClipboard.description=Create patch from changes and copy it to clipboard
action.CommittedChanges.Refresh.text=Refresh
action.CommittedChanges.Refresh.description=Refresh the list of committed changes
action.CommittedChanges.Filter.text=Filter
@@ -80,7 +80,9 @@
<extensionPoint name="openapi.vcs.history.actions.ShowDiffWithLocalAction.ExtensionProvider"
interface="com.intellij.openapi.actionSystem.AnActionExtensionProvider"/>
<extensionPoint name="openapi.vcs.changes.actions.CreatePatchFromChangesAction.ExtensionProvider"
<extensionPoint name="openapi.vcs.changes.actions.CreatePatchFromChangesAction.Dialog.ExtensionProvider"
interface="com.intellij.openapi.actionSystem.AnActionExtensionProvider"/>
<extensionPoint name="openapi.vcs.changes.actions.CreatePatchFromChangesAction.Clipboard.ExtensionProvider"
interface="com.intellij.openapi.actionSystem.AnActionExtensionProvider"/>
</extensionPoints>
</idea-plugin>
@@ -126,7 +126,7 @@
<reference ref="ChangesView.SetDefault"/>
<action id="ChangesView.Rename" class="com.intellij.openapi.vcs.changes.actions.RenameChangeListAction"/>
<reference ref="ChangesView.CreatePatch"/>
<action id="ChangesView.CreatePatchToClipboard" class="com.intellij.openapi.vcs.changes.patch.CreatePatchToClipboardAction"/>
<action id="ChangesView.CreatePatchToClipboard" class="com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction$Clipboard"/>
<reference ref="ChangesView.Shelve"/>
<separator/>
<reference ref="ChangesView.Refresh"/>
@@ -149,7 +149,7 @@
<action id="ShelvedChanges.Restore" class="com.intellij.openapi.vcs.changes.shelf.RestoreShelvedChange"/>
<reference id="Diff.ShowDiff"/>
<reference id="Vcs.ShowDiffWithLocal"/>
<action id="ChangesView.CreatePatchFromChanges" class="com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction"
<action id="ChangesView.CreatePatchFromChanges" class="com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction$Dialog"
icon="AllIcons.Vcs.Patch"/>
<action id="ShelvedChanges.ImportPatches" class="com.intellij.openapi.vcs.changes.shelf.ImportIntoShelfAction"/>
<separator/>
@@ -20,6 +20,9 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.AnActionExtensionProvider;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.ExtendableAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.patch.FilePatch;
import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAware;
@@ -29,56 +32,96 @@ import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.patch.CreatePatchCommitExecutor;
import com.intellij.openapi.vcs.changes.patch.PatchWriter;
import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList;
import com.intellij.openapi.vcs.changes.shelf.ShelvedChangesViewManager;
import com.intellij.openapi.vcs.changes.ui.SessionDialog;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* @author yole
*/
public class CreatePatchFromChangesAction extends ExtendableAction implements DumbAware {
private static final ExtensionPointName<AnActionExtensionProvider> EP_NAME =
ExtensionPointName.create("com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction.ExtensionProvider");
import static com.intellij.openapi.vcs.changes.patch.PatchWriter.writeAsPatchToClipboard;
public CreatePatchFromChangesAction() {
super(EP_NAME);
public abstract class CreatePatchFromChangesAction extends ExtendableAction implements DumbAware {
private static final Logger LOG = Logger.getInstance(CreatePatchFromChangesAction.class);
private static final ExtensionPointName<AnActionExtensionProvider> EP_NAME_DIALOG =
ExtensionPointName.create("com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction.Dialog.ExtensionProvider");
private static final ExtensionPointName<AnActionExtensionProvider> EP_NAME_CLIPBOARD =
ExtensionPointName.create("com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction.Clipboard.ExtensionProvider");
private final boolean mySilentClipboard;
private CreatePatchFromChangesAction(boolean silentClipboard) {
super(silentClipboard ? EP_NAME_CLIPBOARD : EP_NAME_DIALOG);
mySilentClipboard = silentClipboard;
}
public static class Dialog extends CreatePatchFromChangesAction {
public Dialog() {
super(false);
}
}
public static class Clipboard extends CreatePatchFromChangesAction {
public Clipboard() {
super(true);
}
}
public void defaultActionPerformed(@NotNull AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
final Change[] changes = e.getData(VcsDataKeys.CHANGES);
if ((changes == null) || (changes.length == 0)) return;
String commitMessage = null;
List<ShelvedChangeList> shelvedChangeLists = ShelvedChangesViewManager.getShelvedLists(e.getDataContext());
if (!shelvedChangeLists.isEmpty()) {
commitMessage = shelvedChangeLists.get(0).DESCRIPTION;
}
else {
ChangeList[] changeLists = e.getData(VcsDataKeys.CHANGE_LISTS);
if (changeLists != null && changeLists.length > 0) {
commitMessage = changeLists [0].getComment();
}
}
if (commitMessage == null) {
commitMessage = e.getData(VcsDataKeys.PRESET_COMMIT_MESSAGE);
}
if (commitMessage == null) {
commitMessage = "";
}
List<Change> changeCollection = new ArrayList<>();
Collections.addAll(changeCollection, changes);
createPatch(project, commitMessage, changeCollection);
Change[] changes = e.getData(VcsDataKeys.CHANGES);
if (ArrayUtil.isEmpty(changes)) return;
String commitMessage = extractCommitMessage(e);
createPatch(project, commitMessage, Arrays.asList(changes), mySilentClipboard);
}
public static void createPatch(Project project, String commitMessage, @NotNull List<Change> changeCollection) {
@Nullable
private static String extractCommitMessage(@NotNull AnActionEvent e) {
String message = e.getData(VcsDataKeys.PRESET_COMMIT_MESSAGE);
if (message != null) return message;
List<ShelvedChangeList> shelvedChangeLists = ShelvedChangesViewManager.getShelvedLists(e.getDataContext());
if (!shelvedChangeLists.isEmpty()) {
return shelvedChangeLists.get(0).DESCRIPTION;
}
ChangeList[] changeLists = e.getData(VcsDataKeys.CHANGE_LISTS);
if (changeLists != null && changeLists.length > 0) {
return changeLists[0].getComment();
}
return null;
}
public static void createPatch(@Nullable Project project,
@Nullable String commitMessage,
@NotNull List<Change> changes) {
createPatch(project, commitMessage, changes, false);
}
public static void createPatch(@Nullable Project project,
@Nullable String commitMessage,
@NotNull List<Change> changes,
boolean silentClipboard) {
project = project == null ? ProjectManager.getInstance().getDefaultProject() : project;
if (silentClipboard) {
createIntoClipboard(project, changes);
}
else {
createWithDialog(project, commitMessage, changes);
}
}
private static void createWithDialog(@NotNull Project project, @Nullable String commitMessage, @NotNull List<Change> changes) {
final CreatePatchCommitExecutor executor = CreatePatchCommitExecutor.getInstance(project);
CommitSession commitSession = executor.createCommitSession();
if (commitSession instanceof CommitSessionContextAware) {
@@ -87,21 +130,35 @@ public class CreatePatchFromChangesAction extends ExtendableAction implements Du
DialogWrapper sessionDialog = new SessionDialog(executor.getActionText(),
project,
commitSession,
changeCollection,
changes,
commitMessage);
if (!sessionDialog.showAndGet()) {
return;
}
preloadContent(project, changeCollection);
if (!sessionDialog.showAndGet()) return;
commitSession.execute(changeCollection, commitMessage);
preloadContent(project, changes);
commitSession.execute(changes, commitMessage);
}
private static void createIntoClipboard(@NotNull Project project, @NotNull List<Change> changes) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
try {
String base = PatchWriter.calculateBaseForWritingPatch(project, changes).getPath();
List<FilePatch> patches = IdeaTextPatchBuilder.buildPatch(project, changes, base, false);
writeAsPatchToClipboard(project, patches, base, new CommitContext());
VcsNotifier.getInstance(project).notifySuccess("Patch copied to clipboard");
}
catch (IOException | VcsException exception) {
LOG.error("Can't create patch", exception);
VcsNotifier.getInstance(project).notifyWeakError("Patch creation failed");
}
}, VcsBundle.message("create.patch.commit.action.progress"), true, project);
}
private static void preloadContent(final Project project, final List<Change> changes) {
// to avoid multiple progress dialogs, preload content under one progress
ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
public void run() {
for(Change change: changes) {
for (Change change : changes) {
checkLoadContent(change.getBeforeRevision());
checkLoadContent(change.getAfterRevision());
}
@@ -122,14 +179,14 @@ public class CreatePatchFromChangesAction extends ExtendableAction implements Du
}
public void defaultUpdate(@NotNull AnActionEvent e) {
final Boolean haveSelectedChanges = e.getData(VcsDataKeys.HAVE_SELECTED_CHANGES);
Change[] changes;
Boolean haveSelectedChanges = e.getData(VcsDataKeys.HAVE_SELECTED_CHANGES);
ChangeList[] changeLists = e.getData(VcsDataKeys.CHANGE_LISTS);
List<ShelvedChangeList> shelveChangelists = ShelvedChangesViewManager.getShelvedLists(e.getDataContext());
int changelistNum = changeLists == null ? 0 : changeLists.length;
changelistNum += shelveChangelists.size();
e.getPresentation().setEnabled(Boolean.TRUE.equals(haveSelectedChanges) && (changelistNum == 1) &&
((changes = e.getData(VcsDataKeys.CHANGES)) != null && changes.length > 0));
e.getPresentation().setEnabled(!Boolean.FALSE.equals(haveSelectedChanges) &&
changelistNum <= 1 &&
!ArrayUtil.isEmpty(e.getData(VcsDataKeys.CHANGES)));
}
}
@@ -140,7 +140,7 @@ public class CreatePatchCommitExecutor extends LocalCommitExecutor implements Pr
}
@Override
public void execute(@NotNull Collection<Change> changes, String commitMessage) {
public void execute(@NotNull Collection<Change> changes, @Nullable String commitMessage) {
PropertiesComponent.getInstance(myProject).setValue(VCS_PATCH_TO_CLIPBOARD, myPanel.isToClipboard());
try {
if (myPanel.isToClipboard()) {
@@ -1,71 +0,0 @@
/*
* Copyright 2000-2017 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.changes.patch;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.patch.FilePatch;
import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.CommitContext;
import com.intellij.util.ArrayUtil;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static com.intellij.openapi.vcs.changes.patch.PatchWriter.writeAsPatchToClipboard;
public class CreatePatchToClipboardAction extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance(CreatePatchToClipboardAction.class);
public CreatePatchToClipboardAction() {
super(VcsBundle.message("create.patch.to.clipboard.title"), VcsBundle.message("create.patch.to.clipboard.description"), null);
}
@Override
public void update(AnActionEvent e) {
Project project = e.getProject();
e.getPresentation().setEnabled(project != null && !ArrayUtil.isEmpty(e.getData(VcsDataKeys.CHANGES)));
}
@Override
public void actionPerformed(AnActionEvent e) {
final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
final List<Change> changes = Arrays.asList(e.getRequiredData(VcsDataKeys.CHANGES));
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
try {
String base = PatchWriter.calculateBaseForWritingPatch(project, changes).getPath();
List<FilePatch> patches = IdeaTextPatchBuilder.buildPatch(project, changes, base, false);
writeAsPatchToClipboard(project, patches, base, new CommitContext());
VcsNotifier.getInstance(project).notifySuccess("Patch copied to clipboard");
}
catch (IOException | VcsException exception) {
LOG.error("Can't create patch", exception);
VcsNotifier.getInstance(project).notifyWeakError("Patch creation failed");
}
}, VcsBundle.message("create.patch.commit.action.progress"), true, project);
}
}
@@ -574,7 +574,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
}
@NotNull
private File generateUniqueSchemePatchDir(@NotNull final String defaultName, boolean createResourceDirectory) {
private File generateUniqueSchemePatchDir(@Nullable final String defaultName, boolean createResourceDirectory) {
ignoreShelfDirectoryIfFirstShelf();
String uniqueName = UniqueNameGenerator
.generateUniqueName(shortenAndSanitize(defaultName), mySchemeManager.getAllSchemeNames());
@@ -596,7 +596,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
@NotNull
// for create patch only
public static File suggestPatchName(Project project, @NotNull final String commitMessage, final File file, String extension) {
public static File suggestPatchName(Project project, @Nullable final String commitMessage, final File file, String extension) {
@NonNls String defaultPath = shortenAndSanitize(commitMessage);
while (true) {
final File nonexistentFile = FileUtil.findSequentNonexistentFile(file, defaultPath,
@@ -612,8 +612,8 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
}
@NotNull
private static String shortenAndSanitize(@NotNull String commitMessage) {
@NonNls String defaultPath = FileUtil.sanitizeFileName(commitMessage);
private static String shortenAndSanitize(@Nullable String commitMessage) {
@NonNls String defaultPath = FileUtil.sanitizeFileName(StringUtil.notNullize(commitMessage));
if (defaultPath.isEmpty()) {
defaultPath = "unnamed";
}
@@ -24,6 +24,7 @@ import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.CommitSession;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -42,9 +43,12 @@ public class SessionDialog extends DialogWrapper {
private final JPanel myCenterPanel = new JPanel(new BorderLayout());
private final JComponent myConfigurationComponent;
public SessionDialog(String title, Project project,
CommitSession session, List<Change> changes,
String commitMessage, @Nullable JComponent configurationComponent) {
public SessionDialog(String title,
Project project,
@NotNull CommitSession session,
@NotNull List<Change> changes,
@Nullable String commitMessage,
@Nullable JComponent configurationComponent) {
super(project, true);
mySession = session;
myChanges = changes;
@@ -59,9 +63,11 @@ public class SessionDialog extends DialogWrapper {
initValidation();
}
public SessionDialog(String title, Project project,
CommitSession session, List<Change> changes,
String commitMessage) {
public SessionDialog(String title,
Project project,
@NotNull CommitSession session,
@NotNull List<Change> changes,
@Nullable String commitMessage) {
this(title, project, session, changes, commitMessage, null);
}
@@ -1167,12 +1167,12 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton impleme
}
public class MyCreatePatch extends DumbAwareAction {
private final CreatePatchFromChangesAction myUsualDelegate;
private final AnAction myUsualDelegate;
public MyCreatePatch() {
super(VcsBundle.message("action.name.create.patch.for.selected.revisions"),
VcsBundle.message("action.description.create.patch.for.selected.revisions"), AllIcons.Vcs.Patch);
myUsualDelegate = new CreatePatchFromChangesAction();
myUsualDelegate = new CreatePatchFromChangesAction.Dialog();
}
@Override
@@ -38,8 +38,8 @@
<diff.actions.ShowDiffAction.ExtensionProvider implementation="com.intellij.vcs.log.ui.actions.history.CompareRevisionsFromHistoryActionProvider"/>
<openapi.vcs.history.actions.ShowDiffWithLocalAction.ExtensionProvider implementation="com.intellij.vcs.log.ui.actions.history.ShowDiffWithLocalFromHistoryActionProvider"/>
<openapi.vcs.changes.actions.CreatePatchFromChangesAction.ExtensionProvider implementation="com.intellij.vcs.log.ui.actions.history.CreatePatchFromHistoryActionProvider"/>
<openapi.vcs.changes.actions.CreatePatchFromChangesAction.ExtensionProvider implementation="com.intellij.vcs.log.ui.actions.VcsLogCreatePatchActionProvider"/>
<openapi.vcs.changes.actions.CreatePatchFromChangesAction.Dialog.ExtensionProvider implementation="com.intellij.vcs.log.ui.actions.history.CreatePatchFromHistoryActionProvider$Dialog"/>
<openapi.vcs.changes.actions.CreatePatchFromChangesAction.Clipboard.ExtensionProvider implementation="com.intellij.vcs.log.ui.actions.history.CreatePatchFromHistoryActionProvider$Clipboard"/>
</extensions>
<actions>
@@ -1,49 +0,0 @@
/*
* Copyright 2000-2017 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.vcs.log.ui.actions;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.AnActionExtensionProvider;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction;
import com.intellij.vcs.log.VcsLogDataKeys;
import com.intellij.vcs.log.impl.VcsLogUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class VcsLogCreatePatchActionProvider implements AnActionExtensionProvider {
@Override
public boolean isActive(@NotNull AnActionEvent e) {
return e.getData(VcsLogDataKeys.VCS_LOG_UI) != null;
}
@Override
public void update(@NotNull AnActionEvent e) {
Change[] changes = e.getData(VcsDataKeys.CHANGES);
e.getPresentation().setEnabled(changes != null && changes.length > 0);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
VcsLogUtil.triggerUsage(e);
Change[] changes = e.getRequiredData(VcsDataKeys.CHANGES);
String commitMessage = e.getData(VcsDataKeys.PRESET_COMMIT_MESSAGE);
CreatePatchFromChangesAction.createPatch(e.getProject(), commitMessage, Arrays.asList(changes));
}
}
@@ -31,6 +31,23 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
public class CreatePatchFromHistoryActionProvider implements AnActionExtensionProvider {
private final boolean mySilentClipboard;
private CreatePatchFromHistoryActionProvider(boolean silentClipboard) {
mySilentClipboard = silentClipboard;
}
public static class Dialog extends CreatePatchFromHistoryActionProvider {
public Dialog() {
super(false);
}
}
public static class Clipboard extends CreatePatchFromHistoryActionProvider {
public Clipboard() {
super(true);
}
}
@Override
public boolean isActive(@NotNull AnActionEvent e) {
return e.getData(VcsLogInternalDataKeys.FILE_HISTORY_UI) != null;
@@ -61,7 +78,7 @@ public class CreatePatchFromHistoryActionProvider implements AnActionExtensionPr
ui.getVcsLog().requestSelectedDetails(detailsList -> {
List<Change> changes = ui.collectChanges(detailsList, false);
CreatePatchFromChangesAction.createPatch(project, commitMessage, changes);
CreatePatchFromChangesAction.createPatch(project, commitMessage, changes, mySilentClipboard);
});
}
}