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" + } +}