From c00917a7b832302f70c8be4a5252775e5164950e Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Sun, 9 Jun 2019 11:07:08 +0100 Subject: [PATCH] git: support old-school Pull & Merge actions to show a log tab with new commits instead of the file tree: IDEA-52643 There are several limitations for these actions to avoid complicating the code for not very frequent cases. If the restrictions are not met, the code just falls back to the previous behavior and shows a file tree. * Support only the case when a single branch is pulled from, i.e. octopus merges are not supported. * Don't support pulling to a detached HEAD, or to an empty repository. GitOrigin-RevId: e84c76f02f5cc21a37eef7f6cd47620d251dab36 --- .../src/git4idea/actions/GitMerge.java | 2 +- .../src/git4idea/actions/GitMergeAction.java | 80 +++++++++++++++---- .../src/git4idea/actions/GitPull.java | 3 +- .../src/git4idea/merge/GitMergeDialog.java | 8 +- .../src/git4idea/merge/GitPullDialog.java | 9 ++- .../src/git4idea/update/GitUpdateSession.kt | 23 ++++-- 6 files changed, 95 insertions(+), 30 deletions(-) diff --git a/plugins/git4idea/src/git4idea/actions/GitMerge.java b/plugins/git4idea/src/git4idea/actions/GitMerge.java index 7f2c3d93972b..531a6caef764 100644 --- a/plugins/git4idea/src/git4idea/actions/GitMerge.java +++ b/plugins/git4idea/src/git4idea/actions/GitMerge.java @@ -51,6 +51,6 @@ public class GitMerge extends GitMergeAction { return null; } return new DialogState(dialog.getSelectedRoot(), GitBundle.message("merging.title", dialog.getSelectedRoot().getPath()), - () -> dialog.handler()); + () -> dialog.handler(), dialog.getSelectedBranches()); } } diff --git a/plugins/git4idea/src/git4idea/actions/GitMergeAction.java b/plugins/git4idea/src/git4idea/actions/GitMergeAction.java index f287f8ccd0be..50167dcfeeac 100644 --- a/plugins/git4idea/src/git4idea/actions/GitMergeAction.java +++ b/plugins/git4idea/src/git4idea/actions/GitMergeAction.java @@ -4,15 +4,21 @@ package git4idea.actions; import com.intellij.dvcs.DvcsUtil; import com.intellij.history.Label; import com.intellij.history.LocalHistory; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationAction; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ModalityState; +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.util.Computable; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx; +import com.intellij.openapi.vcs.update.AbstractCommonUpdateAction; import com.intellij.openapi.vcs.update.ActionInfo; import com.intellij.openapi.vcs.update.UpdateInfoTree; import com.intellij.openapi.vcs.update.UpdatedFiles; @@ -20,9 +26,8 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.GuiUtils; import com.intellij.vcs.ViewUpdateInfoNotification; -import git4idea.GitRevisionNumber; -import git4idea.GitUtil; -import git4idea.GitVcs; +import git4idea.*; +import git4idea.branch.GitBranchPair; import git4idea.commands.*; import git4idea.merge.GitConflictResolver; import git4idea.merge.GitMergeCommittingConflictResolver; @@ -30,6 +35,8 @@ import git4idea.merge.GitMerger; import git4idea.merge.MergeChangeCollector; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; +import git4idea.update.GitUpdateInfoAsLog; +import git4idea.update.GitUpdatedRanges; import git4idea.util.GitUIUtil; import git4idea.util.GitUntrackedFilesHelper; import git4idea.util.LocalChangesWouldBeOverwrittenHelper; @@ -39,19 +46,30 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import static com.intellij.notification.NotificationType.INFORMATION; import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.MERGE; +import static git4idea.update.GitUpdateSessionKt.getBodyForUpdateNotification; +import static git4idea.update.GitUpdateSessionKt.getTitleForUpdateNotification; import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; abstract class GitMergeAction extends GitRepositoryAction { + private static final Logger LOG = Logger.getInstance(GitMergeAction.class); protected static class DialogState { final VirtualFile selectedRoot; final String progressTitle; final Computable handlerProvider; - DialogState(@NotNull VirtualFile root, @NotNull String title, @NotNull Computable provider) { + @NotNull private final List selectedBranches; + + DialogState(@NotNull VirtualFile root, + @NotNull String title, + @NotNull Computable provider, + @NotNull List selectedBranches) { selectedRoot = root; progressTitle = title; handlerProvider = provider; + this.selectedBranches = selectedBranches; } } @@ -79,6 +97,22 @@ abstract class GitMergeAction extends GitRepositoryAction { new GitUntrackedFilesOverwrittenByOperationDetector(selectedRoot); GitSimpleEventDetector mergeConflict = new GitSimpleEventDetector(GitSimpleEventDetector.Event.MERGE_CONFLICT); + GitRepository repository = repositoryManager.getRepositoryForRoot(selectedRoot); + assert repository != null : "Repository can't be null for root " + selectedRoot; + + GitUpdatedRanges updatedRanges = null; + if (repository.getCurrentBranch() != null && dialogState.selectedBranches.size() == 1) { + String selectedBranch = StringUtil.trimStart(dialogState.selectedBranches.get(0), "remotes/"); + GitBranch targetBranch = repository.getBranches().findBranchByName(selectedBranch); + if (targetBranch != null) { + GitBranchPair refPair = new GitBranchPair(repository.getCurrentBranch(), targetBranch); + updatedRanges = GitUpdatedRanges.calcInitialPositions(project, singletonMap(repository, refPair)); + } + else { + LOG.warn("Couldn't find the branch with name [" + selectedBranch + "]"); + } + } + try (AccessToken ignore = DvcsUtil.workingTreeChangeStarted(project, getActionName())) { GitCommandResult result = git.runCommand(() -> { GitLineHandler handler = handlerProvider.compute(); @@ -88,27 +122,28 @@ abstract class GitMergeAction extends GitRepositoryAction { return handler; }); - GitRepository repository = repositoryManager.getRepositoryForRoot(selectedRoot); - assert repository != null : "Repository can't be null for root " + selectedRoot; String revision = repository.getCurrentRevision(); if (revision == null) { return; } + GitRevisionNumber currentRev = new GitRevisionNumber(revision); - handleResult(result, project, mergeConflict, localChangesDetector, untrackedFilesDetector, repository, currentRev, beforeLabel); + handleResult(result, project, mergeConflict, localChangesDetector, untrackedFilesDetector, repository, currentRev, beforeLabel, + updatedRanges); } } }.queue(); } private void handleResult(@NotNull GitCommandResult result, - @NotNull Project project, - @NotNull GitSimpleEventDetector mergeConflictDetector, - @NotNull GitLocalChangesWouldBeOverwrittenDetector localChangesDetector, - @NotNull GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector, - @NotNull GitRepository repository, - @NotNull GitRevisionNumber currentRev, - @NotNull Label beforeLabel) { + @NotNull Project project, + @NotNull GitSimpleEventDetector mergeConflictDetector, + @NotNull GitLocalChangesWouldBeOverwrittenDetector localChangesDetector, + @NotNull GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector, + @NotNull GitRepository repository, + @NotNull GitRevisionNumber currentRev, + @NotNull Label beforeLabel, + @Nullable GitUpdatedRanges updatedRanges) { VirtualFile root = repository.getRoot(); if (mergeConflictDetector.hasHappened()) { @@ -118,10 +153,21 @@ abstract class GitMergeAction extends GitRepositoryAction { if (result.success() || mergeConflictDetector.hasHappened()) { VfsUtil.markDirtyAndRefresh(false, true, false, root); - List exceptions = new ArrayList<>(); - showUpdates(project, exceptions, root, currentRev, beforeLabel, getActionName()); repository.update(); - GitVcs.getInstance(project).showErrors(exceptions, getActionName()); + if (updatedRanges != null && AbstractCommonUpdateAction.showsCustomNotification(singletonList(GitVcs.getInstance(project)))) { + new GitUpdateInfoAsLog(project, updatedRanges.calcCurrentPositions(), (filesCount, commitCount, filteredCommits, viewCommits) -> { + String title = getTitleForUpdateNotification(filesCount, commitCount); + String content = getBodyForUpdateNotification(filesCount, commitCount, filteredCommits); + Notification notification = VcsNotifier.STANDARD_NOTIFICATION.createNotification(title, content, INFORMATION, null); + notification.addAction(NotificationAction.createSimple("View Commits", viewCommits)); + return notification; + }).buildAndShowNotification(); + } + else { + List exceptions = new ArrayList<>(); + showUpdates(project, exceptions, root, currentRev, beforeLabel, getActionName()); + GitVcs.getInstance(project).showErrors(exceptions, getActionName()); + } } else if (localChangesDetector.wasMessageDetected()) { LocalChangesWouldBeOverwrittenHelper.showErrorNotification(project, repository.getRoot(), getActionName(), diff --git a/plugins/git4idea/src/git4idea/actions/GitPull.java b/plugins/git4idea/src/git4idea/actions/GitPull.java index 81bc16221c01..40efb93055c8 100644 --- a/plugins/git4idea/src/git4idea/actions/GitPull.java +++ b/plugins/git4idea/src/git4idea/actions/GitPull.java @@ -57,7 +57,8 @@ public class GitPull extends GitMergeAction { GitRemote remote = GitUtil.findRemoteByName(repository, remoteOrUrl); final List urls = remote == null ? Collections.singletonList(remoteOrUrl) : remote.getUrls(); Computable handlerProvider = () -> dialog.makeHandler(urls); - return new DialogState(dialog.gitRoot(), GitBundle.message("pulling.title", dialog.getRemote()), handlerProvider); + return new DialogState(dialog.gitRoot(), GitBundle.message("pulling.title", dialog.getRemote()), handlerProvider, + dialog.getSelectedBranches()); } } diff --git a/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java b/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java index 974fd93de7fa..1ae146f1253e 100644 --- a/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java +++ b/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java @@ -118,7 +118,7 @@ public class GitMergeDialog extends DialogWrapper { final ElementsChooser.ElementsMarkListener listener = new ElementsChooser.ElementsMarkListener() { @Override public void elementMarkChanged(final String element, final boolean isMarked) { - setOKActionEnabled(myBranchChooser.getMarkedElements().size() != 0); + setOKActionEnabled(getSelectedBranches().size() != 0); } }; listener.elementMarkChanged(null, true); @@ -177,12 +177,16 @@ public class GitMergeDialog extends DialogWrapper { if (!GitMergeUtil.DEFAULT_STRATEGY.equals(strategy)) { h.addParameters("--strategy", strategy); } - for (String branch : myBranchChooser.getMarkedElements()) { + for (String branch : getSelectedBranches()) { h.addParameters(branch); } return h; } + @NotNull + public List getSelectedBranches() { + return myBranchChooser.getMarkedElements(); + } @Override protected JComponent createCenterPanel() { diff --git a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java index e97de8ea48a4..fa31cde6d141 100644 --- a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java +++ b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java @@ -156,7 +156,7 @@ public class GitPullDialog extends DialogWrapper { setOKActionEnabled(false); return; } - setOKActionEnabled(myBranchChooser.getMarkedElements().size() != 0); + setOKActionEnabled(getSelectedBranches().size() != 0); } public GitLineHandler makeHandler(@NotNull List urls) { @@ -189,7 +189,7 @@ public class GitPullDialog extends DialogWrapper { h.addParameters("--progress"); } - final List markedBranches = myBranchChooser.getMarkedElements(); + final List markedBranches = getSelectedBranches(); String remote = getRemote(); LOG.assertTrue(remote != null, "Selected remote can't be null here."); // git pull origin master (remote branch name in the format local to that remote) @@ -200,6 +200,11 @@ public class GitPullDialog extends DialogWrapper { return h; } + @NotNull + public List getSelectedBranches() { + return myBranchChooser.getMarkedElements(); + } + @NotNull private static String removeRemotePrefix(@NotNull String branch, @NotNull String remote) { String prefix = remote + "/"; diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateSession.kt b/plugins/git4idea/src/git4idea/update/GitUpdateSession.kt index 0e5b8240be14..38c81fc192f4 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdateSession.kt +++ b/plugins/git4idea/src/git4idea/update/GitUpdateSession.kt @@ -71,8 +71,7 @@ class GitUpdateSession(private val project: Project, val title: String var content: String? val type: NotificationType - val mainMessage = "$updatedFilesNumber ${pluralize("file", updatedFilesNumber)} " + - "updated in $updatedCommitsNumber ${pluralize("commit", updatedCommitsNumber)}" + val mainMessage = getTitleForUpdateNotification(updatedFilesNumber, updatedCommitsNumber) if (isCanceled) { title = "Project Partially Updated" content = mainMessage @@ -80,11 +79,7 @@ class GitUpdateSession(private val project: Project, } else { title = mainMessage - content = when (filteredCommitsNumber) { - null -> "" - 0 -> "No commits matching filters" - else -> "$filteredCommitsNumber ${pluralize("commit", filteredCommitsNumber)} matching filters" - } + content = getBodyForUpdateNotification(updatedFilesNumber, updatedCommitsNumber, filteredCommitsNumber) type = NotificationType.INFORMATION } @@ -99,3 +94,17 @@ class GitUpdateSession(private val project: Project, return VcsNotifier.STANDARD_NOTIFICATION.createNotification(title, content, type, null) } } + +fun getTitleForUpdateNotification(updatedFilesNumber: Int, updatedCommitsNumber: Int): String { + val files = pluralize("file", updatedFilesNumber) + val commits = pluralize("commit", updatedCommitsNumber) + return "$updatedFilesNumber $files updated in $updatedCommitsNumber $commits" +} + +fun getBodyForUpdateNotification(updatedFilesNumber: Int, updatedCommitsNumber: Int, filteredCommitsNumber: Int?): String { + return when (filteredCommitsNumber) { + null -> "" + 0 -> "No commits matching filters" + else -> "$filteredCommitsNumber ${pluralize("commit", filteredCommitsNumber)} matching filters" + } +}