diff options
Diffstat (limited to 'plugins/git4idea/src')
42 files changed, 791 insertions, 482 deletions
diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index 32d254f6fb67..dc12d0e03ad0 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -92,12 +92,13 @@ <separator/> </group> - <action id="Git.CherryPick" class="git4idea.cherrypick.GitCherryPickAction" text="Cherry-Pick" icon="Git4ideaIcons.CherryPick"/> + <action id="Git.CherryPick" class="git4idea.cherrypick.GitCherryPickAction" icon="Git4ideaIcons.CherryPick"/> <action class="git4idea.actions.GitCheckoutRevisionAction" id="Git.CheckoutRevision" text="Checkout Revision"/> - <action class="git4idea.actions.GitCreateNewBranchAction" id="Git.CreateNewBranch" text="New Branch" + <action class="git4idea.actions.GitCreateNewBranchAction" id="Git.CreateNewBranch" text="New Branch..." description="Create new branch starting from the selected commit"/> - <action class="git4idea.actions.GitCreateTagAction" id="Git.CreateNewTag" text="New Tag" + <action class="git4idea.actions.GitCreateTagAction" id="Git.CreateNewTag" text="New Tag..." description="Create new tag pointing to this commit"/> + <action id="Git.Reset.In.Log" class="git4idea.reset.GitResetAction" text="Reset Current Branch to Here..." /> <group id="Git.Log.ContextMenu"> <separator/> @@ -105,6 +106,8 @@ <reference id="Git.CheckoutRevision"/> <reference id="Git.CreateNewBranch" /> <reference id="Git.CreateNewTag" /> + <separator/> + <reference id="Git.Reset.In.Log" /> <add-to-group group-id="Vcs.Log.ContextMenu" /> </group> @@ -121,7 +124,9 @@ <project-components> <component> + <interface-class>git4idea.repo.GitRepositoryManager</interface-class> <implementation-class>git4idea.repo.GitRepositoryManager</implementation-class> + <headless-implementation-class>git4idea.test.GitTestRepositoryManager</headless-implementation-class> </component> </project-components> diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 5f56de5674e1..4ff61fb4a68b 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -37,7 +37,6 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vcs.changes.ChangeListManagerEx; -import com.intellij.openapi.vcs.changes.FilePathsHelper; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vcs.vfs.AbstractVcsVirtualFile; import com.intellij.openapi.vfs.LocalFileSystem; @@ -778,6 +777,8 @@ public class GitUtil { /** * Returns absolute paths which have changed remotely comparing to the current branch, i.e. performs * <code>git diff --name-only master..origin/master</code> + * <p/> + * Paths are absolute, Git-formatted (i.e. with forward slashes). */ @NotNull public static Collection<String> getPathsDiffBetweenRefs(@NotNull Git git, @NotNull GitRepository repository, @@ -797,7 +798,7 @@ public class GitUtil { continue; } final String path = repository.getRoot().getPath() + "/" + unescapePath(relative); - remoteChanges.add(FilePathsHelper.convertPath(path)); + remoteChanges.add(path); } return remoteChanges; } @@ -1024,4 +1025,14 @@ public class GitUtil { ApplicationManager.getApplication().getMessageBus().syncPublisher(BatchFileChangeListener.TOPIC).batchChangeCompleted(project); } + @NotNull + public static String cleanupErrorPrefixes(@NotNull String msg) { + final String[] PREFIXES = { "fatal:", "error:" }; + for (String prefix : PREFIXES) { + if (msg.startsWith(prefix)) { + return msg.substring(prefix.length()).trim(); + } + } + return msg; + } } diff --git a/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java b/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java index 39cfb2a625ad..83512de71d1f 100644 --- a/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java +++ b/plugins/git4idea/src/git4idea/actions/GitLogSingleCommitAction.java @@ -15,77 +15,21 @@ */ package git4idea.actions; -import com.intellij.dvcs.DvcsUtil; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.dvcs.ui.VcsLogSingleCommitAction; import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.vcs.log.VcsFullCommitDetails; -import com.intellij.vcs.log.VcsLog; -import com.intellij.vcs.log.VcsLogDataKeys; -import git4idea.GitVcs; +import com.intellij.openapi.vfs.VirtualFile; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.List; - -/** - * @author Kirill Likhodedov - */ -public abstract class GitLogSingleCommitAction extends DumbAwareAction { - - private static final Logger LOG = Logger.getInstance(GitLogSingleCommitAction.class); - - protected abstract void actionPerformed(@NotNull GitRepository repository, @NotNull VcsFullCommitDetails commit); - - @Override - public void actionPerformed(AnActionEvent e) { - Data data = Data.collect(e); - if (!data.isValid()) { - return; - } - - List<VcsFullCommitDetails> details = data.log.getSelectedDetails(); - if (details.size() != 1) { - return; - } - VcsFullCommitDetails commit = details.get(0); - - GitRepositoryManager repositoryManager = ServiceManager.getService(data.project, GitRepositoryManager.class); - final GitRepository repository = repositoryManager.getRepositoryForRoot(commit.getRoot()); - if (repository == null) { - DvcsUtil.noVcsRepositoryForRoot(LOG, commit.getRoot(), data.project, repositoryManager, GitVcs.getInstance(data.project)); - return; - } - - actionPerformed(repository, commit); - } +public abstract class GitLogSingleCommitAction extends VcsLogSingleCommitAction<GitRepository> { @Override - public void update(AnActionEvent e) { - Data data = Data.collect(e); - boolean enabled = data.isValid() && data.log.getSelectedCommits().size() == 1; - e.getPresentation().setVisible(data.isValid()); - e.getPresentation().setEnabled(enabled); - } - - private static class Data { - Project project; - VcsLog log; - - static Data collect(AnActionEvent e) { - Data data = new Data(); - data.project = e.getData(CommonDataKeys.PROJECT); - data.log = e.getData(VcsLogDataKeys.VSC_LOG); - return data; - } - - boolean isValid() { - return project != null && log != null && DvcsUtil.logHasRootForVcs(log, GitVcs.getKey()); - } + @Nullable + protected GitRepository getRepositoryForRoot(@NotNull Project project, @NotNull VirtualFile root) { + return ServiceManager.getService(project, GitRepositoryManager.class).getRepositoryForRoot(root); } } diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java index 7c2f77eeebd2..9147054ba7b9 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java @@ -15,6 +15,7 @@ */ package git4idea.branch; +import com.intellij.dvcs.repo.RepositoryUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; @@ -25,6 +26,9 @@ import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import git4idea.GitLocalBranch; import git4idea.GitPlatformFacade; import git4idea.GitUtil; import git4idea.commands.Git; @@ -32,6 +36,7 @@ import git4idea.commands.GitMessageWithFilesDetector; import git4idea.config.GitVcsSettings; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; @@ -50,7 +55,7 @@ abstract class GitBranchOperation { @NotNull protected final Git myGit; @NotNull protected final GitBranchUiHandler myUiHandler; @NotNull private final Collection<GitRepository> myRepositories; - @NotNull protected final String myCurrentBranchOrRev; + @NotNull protected final Map<GitRepository, String> myCurrentHeads; private final GitVcsSettings mySettings; @NotNull private final Collection<GitRepository> mySuccessfulRepositories; @@ -63,7 +68,13 @@ abstract class GitBranchOperation { myGit = git; myUiHandler = uiHandler; myRepositories = repositories; - myCurrentBranchOrRev = GitBranchUtil.getCurrentBranchOrRev(repositories); + myCurrentHeads = ContainerUtil.map2Map(repositories, new Function<GitRepository, Pair<GitRepository, String>>() { + @Override + public Pair<GitRepository, String> fun(GitRepository repository) { + GitLocalBranch currentBranch = repository.getCurrentBranch(); + return Pair.create(repository, currentBranch == null ? repository.getCurrentRevision() : currentBranch.getName()); + } + }); mySuccessfulRepositories = new ArrayList<GitRepository>(); myRemainingRepositories = new ArrayList<GitRepository>(myRepositories); mySettings = myFacade.getSettings(myProject); @@ -220,11 +231,28 @@ abstract class GitBranchOperation { protected void updateRecentBranch() { if (getRepositories().size() == 1) { GitRepository repository = myRepositories.iterator().next(); - mySettings.setRecentBranchOfRepository(repository.getRoot().getPath(), myCurrentBranchOrRev); + mySettings.setRecentBranchOfRepository(repository.getRoot().getPath(), myCurrentHeads.get(repository)); } else { - mySettings.setRecentCommonBranch(myCurrentBranchOrRev); + String recentCommonBranch = getRecentCommonBranch(); + if (recentCommonBranch != null) { + mySettings.setRecentCommonBranch(recentCommonBranch); + } + } + } + + @Nullable + private String getRecentCommonBranch() { + String recentCommonBranch = null; + for (String branch : myCurrentHeads.values()) { + if (recentCommonBranch == null) { + recentCommonBranch = branch; + } + else if (!recentCommonBranch.equals(branch)) { + return null; + } } + return recentCommonBranch; } private void showUnmergedFilesDialogWithRollback() { @@ -340,4 +368,35 @@ abstract class GitBranchOperation { return Pair.create(allConflictingRepositories, affectedChanges); } + + @NotNull + protected static String stringifyBranchesByRepos(@NotNull Map<GitRepository, String> heads) { + MultiMap<String, VirtualFile> grouped = groupByBranches(heads); + if (grouped.size() == 1) { + return grouped.keySet().iterator().next(); + } + return StringUtil.join(grouped.entrySet(), new Function<Map.Entry<String, Collection<VirtualFile>>, String>() { + @Override + public String fun(Map.Entry<String, Collection<VirtualFile>> entry) { + String roots = StringUtil.join(entry.getValue(), new Function<VirtualFile, String>() { + @Override + public String fun(VirtualFile file) { + return file.getName(); + } + }, ", "); + return entry.getKey() + " (in " + roots + ")"; + } + }, "<br/>"); + } + + @NotNull + private static MultiMap<String, VirtualFile> groupByBranches(@NotNull Map<GitRepository, String> heads) { + MultiMap<String, VirtualFile> result = MultiMap.createLinked(); + List<GitRepository> sortedRepos = RepositoryUtil.sortRepositories(heads.keySet()); + for (GitRepository repo : sortedRepos) { + result.putValue(heads.get(repo), repo.getRoot()); + } + return result; + } + } diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java index 3b694c455c7b..95546f0547e8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java @@ -22,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitCommit; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; @@ -68,14 +69,15 @@ public interface GitBranchUiHandler { * Shows the dialog proposing to execute the operation (checkout or merge) smartly, i.e. stash-execute-unstash. * * @param project - * @param changes local changes that would be overwritten by checkout or merge. - * @param paths paths reported by Git (in most cases this is covered by {@code changes}. - * @param operation operation name: checkout or merge - * @param isForcePossible can the operation be executed force (force checkout is possible, force merge - not). + * @param changes local changes that would be overwritten by checkout or merge. + * @param paths paths reported by Git (in most cases this is covered by {@code changes}. + * @param operation operation name: checkout or merge + * @param forceButtonTitle if the operation can be executed force (force checkout is possible), + * specify the title of the force button; otherwise (force merge is not possible) pass null. * @return the code of the decision. */ int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull Collection<String> paths, - @NotNull String operation, boolean isForcePossible); + @NotNull String operation, @Nullable String forceButtonTitle); boolean showBranchIsNotFullyMergedDialog(@NotNull Project project, @NotNull Map<GitRepository, List<GitCommit>> history, @NotNull String unmergedBranch, @NotNull List<String> mergedToBranches, diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java index cea2b55596e0..20e207d14ba1 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java @@ -172,7 +172,7 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { @Override public int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull Collection<String> paths, - @NotNull String operation, boolean isForcePossible) { + @NotNull String operation, @Nullable String forceButtonTitle) { JComponent fileBrowser; if (!changes.isEmpty()) { fileBrowser = new ChangesBrowserWithRollback(project, changes); @@ -180,7 +180,7 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { else { fileBrowser = new GitSimplePathsBrowser(project, paths); } - return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, isForcePossible); + return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, forceButtonTitle); } @Override diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java index 22e486e6eab0..cdc4f7711801 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutNewBranchOperation.java @@ -89,7 +89,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation { protected String getRollbackProposal() { return "However checkout has succeeded for the following " + repositories() + ":<br/>" + successfulRepositoriesJoined() + - "<br/>You may rollback (checkout back to " + myCurrentBranchOrRev + " and delete " + myNewBranchName + ") not to let branches diverge."; + "<br/>You may rollback (checkout previous branch back, and delete " + myNewBranchName + ") not to let branches diverge."; } @NotNull @@ -104,7 +104,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation { GitCompoundResult deleteResult = new GitCompoundResult(myProject); Collection<GitRepository> repositories = getSuccessfulRepositories(); for (GitRepository repository : repositories) { - GitCommandResult result = myGit.checkout(repository, myCurrentBranchOrRev, null, true); + GitCommandResult result = myGit.checkout(repository, myCurrentHeads.get(repository), null, true); checkoutResult.append(repository, result); if (result.success()) { deleteResult.append(repository, myGit.branchDelete(repository, myNewBranchName, false)); @@ -113,7 +113,7 @@ class GitCheckoutNewBranchOperation extends GitBranchOperation { } if (checkoutResult.totalSuccess() && deleteResult.totalSuccess()) { VcsNotifier.getInstance(myProject).notifySuccess("Rollback successful", String - .format("Checked out %s and deleted %s on %s %s", code(myCurrentBranchOrRev), code(myNewBranchName), + .format("Checked out %s and deleted %s on %s %s", stringifyBranchesByRepos(myCurrentHeads), code(myNewBranchName), StringUtil.pluralize("root", repositories.size()), successfulRepositoriesJoined())); } else { diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java index 2441bf7d46cf..b772434959e8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java @@ -47,7 +47,7 @@ import static git4idea.util.GitUIUtil.code; */ class GitCheckoutOperation extends GitBranchOperation { - public static final String ROLLBACK_PROPOSAL_FORMAT = "You may rollback (checkout back to %s) not to let branches diverge."; + public static final String ROLLBACK_PROPOSAL_FORMAT = "You may rollback (checkout back to previous branch) not to let branches diverge."; @NotNull private final String myStartPointReference; @Nullable private final String myNewBranch; @@ -115,12 +115,14 @@ class GitCheckoutOperation extends GitBranchOperation { private boolean smartCheckoutOrNotify(@NotNull GitRepository repository, @NotNull GitMessageWithFilesDetector localChangesOverwrittenByCheckout) { Pair<List<GitRepository>, List<Change>> conflictingRepositoriesAndAffectedChanges = - getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByCheckout, myCurrentBranchOrRev, myStartPointReference); + getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByCheckout, myCurrentHeads.get(repository), + myStartPointReference); List<GitRepository> allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst(); List<Change> affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByCheckout.getRelativeFilePaths()); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", true); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", + "&Force Checkout"); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { boolean smartCheckedOutSuccessfully = smartCheckout(allConflictingRepositories, myStartPointReference, myNewBranch, getIndicator()); if (smartCheckedOutSuccessfully) { @@ -153,8 +155,7 @@ class GitCheckoutOperation extends GitBranchOperation { @Override protected String getRollbackProposal() { return "However checkout has succeeded for the following " + repositories() + ":<br/>" + - successfulRepositoriesJoined() + - "<br/>" + String.format(ROLLBACK_PROPOSAL_FORMAT, myCurrentBranchOrRev); + successfulRepositoriesJoined() + "<br/>" + ROLLBACK_PROPOSAL_FORMAT; } @NotNull @@ -168,7 +169,7 @@ class GitCheckoutOperation extends GitBranchOperation { GitCompoundResult checkoutResult = new GitCompoundResult(myProject); GitCompoundResult deleteResult = new GitCompoundResult(myProject); for (GitRepository repository : getSuccessfulRepositories()) { - GitCommandResult result = myGit.checkout(repository, myCurrentBranchOrRev, null, true); + GitCommandResult result = myGit.checkout(repository, myCurrentHeads.get(repository), null, true); checkoutResult.append(repository, result); if (result.success() && myNewBranch != null) { /* @@ -183,7 +184,7 @@ class GitCheckoutOperation extends GitBranchOperation { if (!checkoutResult.totalSuccess() || !deleteResult.totalSuccess()) { StringBuilder message = new StringBuilder(); if (!checkoutResult.totalSuccess()) { - message.append("Errors during checking out ").append(myCurrentBranchOrRev).append(": "); + message.append("Errors during checkout: "); message.append(checkoutResult.getErrorOutputWithReposIndication()); } if (!deleteResult.totalSuccess()) { diff --git a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java index d3501caf7089..1dba1e07f6ac 100644 --- a/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitDeleteBranchOperation.java @@ -68,7 +68,7 @@ class GitDeleteBranchOperation extends GitBranchOperation { else if (notFullyMergedDetector.hasHappened()) { String baseBranch = notMergedToUpstreamDetector.getBaseBranch(); if (baseBranch == null) { // GitBranchNotMergedToUpstreamDetector didn't happen - baseBranch = myCurrentBranchOrRev; + baseBranch = myCurrentHeads.get(repository); } Collection<GitRepository> remainingRepositories = getRemainingRepositories(); diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java index 89a50cfd6054..1b8f382d1dd8 100644 --- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java @@ -34,6 +34,7 @@ import git4idea.commands.*; import git4idea.merge.GitMergeCommittingConflictResolver; import git4idea.merge.GitMerger; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import git4idea.util.GitPreservingProcess; import org.jetbrains.annotations.NotNull; @@ -189,12 +190,13 @@ class GitMergeOperation extends GitBranchOperation { private boolean proposeSmartMergePerformAndNotify(@NotNull GitRepository repository, @NotNull GitMessageWithFilesDetector localChangesOverwrittenByMerge) { Pair<List<GitRepository>, List<Change>> conflictingRepositoriesAndAffectedChanges = - getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByMerge, myCurrentBranchOrRev, myBranchToMerge); + getConflictingRepositoriesAndAffectedChanges(repository, localChangesOverwrittenByMerge, myCurrentHeads.get(repository), + myBranchToMerge); List<GitRepository> allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst(); List<Change> affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByMerge.getRelativeFilePaths()); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", false); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", null); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { return doSmartMerge(allConflictingRepositories); } @@ -322,7 +324,7 @@ class GitMergeOperation extends GitBranchOperation { @NotNull private GitCommandResult rollback(@NotNull GitRepository repository) { - return myGit.resetHard(repository, myCurrentRevisionsBeforeMerge.get(repository)); + return myGit.reset(repository, GitResetMode.HARD, myCurrentRevisionsBeforeMerge.get(repository)); } @NotNull @@ -339,7 +341,8 @@ class GitMergeOperation extends GitBranchOperation { @NotNull @Override public String getSuccessMessage() { - return String.format("Merged <b><code>%s</code></b> to <b><code>%s</code></b>", myBranchToMerge, myCurrentBranchOrRev); + return String.format("Merged <b><code>%s</code></b> to <b><code>%s</code></b>", + myBranchToMerge, stringifyBranchesByRepos(myCurrentHeads)); } @NotNull diff --git a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java index 9c2ef4a8d107..4f5073027624 100644 --- a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java +++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java @@ -24,6 +24,7 @@ import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.UIUtil; import git4idea.GitPlatformFacade; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ActionEvent; @@ -44,18 +45,18 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull private final JComponent myFileBrowser; @NotNull private final String myOperationTitle; - private final boolean myShowForceButton; + @Nullable private final String myForceButton; /** * Shows the dialog with the list of local changes preventing merge/checkout and returns the dialog exit code. */ static int showAndGetAnswer(@NotNull final Project project, @NotNull final JComponent fileBrowser, - @NotNull final String operationTitle, final boolean showForceButton) { + @NotNull final String operationTitle, @Nullable final String forceButtonTitle) { final AtomicInteger exitCode = new AtomicInteger(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { - GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, showForceButton); + GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, forceButtonTitle); ServiceManager.getService(project, GitPlatformFacade.class).showDialog(dialog); exitCode.set(dialog.getExitCode()); } @@ -64,11 +65,11 @@ public class GitSmartOperationDialog extends DialogWrapper { } private GitSmartOperationDialog(@NotNull Project project, @NotNull JComponent fileBrowser, @NotNull String operationTitle, - boolean showForceButton) { + @Nullable String forceButton) { super(project); myFileBrowser = fileBrowser; myOperationTitle = operationTitle; - myShowForceButton = showForceButton; + myForceButton = forceButton; String capitalizedOperation = capitalize(myOperationTitle); setTitle("Git " + capitalizedOperation + " Problem"); @@ -82,8 +83,8 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull @Override protected Action[] createLeftSideActions() { - if (myShowForceButton) { - return new Action[] {new ForceCheckoutAction(myOperationTitle) }; + if (myForceButton != null) { + return new Action[]{new ForceCheckoutAction(myForceButton, myOperationTitle)}; } return new Action[0]; } @@ -110,8 +111,8 @@ public class GitSmartOperationDialog extends DialogWrapper { private class ForceCheckoutAction extends AbstractAction { - ForceCheckoutAction(@NotNull String operationTitle) { - super("&Force " + capitalize(operationTitle)); + ForceCheckoutAction(@NotNull String buttonTitle, @NotNull String operationTitle) { + super(buttonTitle); putValue(Action.SHORT_DESCRIPTION, capitalize(operationTitle) + " and overwrite local changes"); } diff --git a/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java b/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java index f1ab1d476c6f..31e731f44937 100644 --- a/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java +++ b/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java @@ -22,7 +22,7 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.ChangesUtil; import com.intellij.openapi.vcs.changes.committed.DecoratorManager; import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipper; import com.intellij.openapi.vcs.changes.committed.VcsCommittedViewAuxiliary; @@ -199,13 +199,7 @@ public class GitCommittedChangeListProvider implements CommittedChangesProvider< final Collection<Change> changes = commit.getChanges(); if (changes.size() == 1) { Change change = changes.iterator().next(); - ContentRevision revision = change.getAfterRevision(); - if (revision == null) { - revision = change.getBeforeRevision(); - } - assert revision != null : "Revision can't be null in " + change; - FilePath filePathInRevision = revision.getFile(); - return Pair.create(commit, filePathInRevision); + return Pair.create(commit, ChangesUtil.getFilePath(change)); } for (Change change : changes) { if (change.getAfterRevision() != null && FileUtil.filesEqual(filePath.getIOFile(), change.getAfterRevision().getFile().getIOFile())) { diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java index 3bb008a2f95f..448dd25ef7ab 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -226,10 +226,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { @NotNull private static VcsException cleanupExceptionText(VcsException original) { String msg = original.getMessage(); - final String FATAL_PREFIX = "fatal:"; - if (msg.startsWith(FATAL_PREFIX)) { - msg = msg.substring(FATAL_PREFIX.length()); - } + msg = GitUtil.cleanupErrorPrefixes(msg); final String DURING_EXECUTING_SUFFIX = GitSimpleHandler.DURING_EXECUTING_ERROR_MESSAGE; int suffix = msg.indexOf(DURING_EXECUTING_SUFFIX); if (suffix > 0) { diff --git a/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java b/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java index e46e98a7887f..ac9834efc3ce 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java @@ -35,7 +35,7 @@ class GitCommitAuthorCorrector { if (at < 0) { return author; } - int email = author.substring(0, at).lastIndexOf(' '); + int email = author.lastIndexOf(' ', at - 1); if (email < 0) { return author; } diff --git a/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java b/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java index aa3e1e440277..b4250a0d142b 100644 --- a/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java +++ b/plugins/git4idea/src/git4idea/cherrypick/GitCherryPickAction.java @@ -49,6 +49,8 @@ import java.util.*; * @author Kirill Likhodedov */ public class GitCherryPickAction extends DumbAwareAction { + + private static final String NAME = "Cherry-Pick"; private static final Logger LOG = Logger.getInstance(GitCherryPickAction.class); @NotNull private final GitPlatformFacade myPlatformFacade; @@ -56,7 +58,7 @@ public class GitCherryPickAction extends DumbAwareAction { @NotNull private final Set<Hash> myIdsInProgress; public GitCherryPickAction() { - super("Cherry-pick", "Cherry-pick", Git4ideaIcons.CherryPick); + super(NAME, null, Git4ideaIcons.CherryPick); myGit = ServiceManager.getService(Git.class); myPlatformFacade = ServiceManager.getService(GitPlatformFacade.class); myIdsInProgress = ContainerUtil.newHashSet(); @@ -81,9 +83,8 @@ public class GitCherryPickAction extends DumbAwareAction { new Task.Backgroundable(project, "Cherry-picking", false) { public void run(@NotNull ProgressIndicator indicator) { try { - boolean autoCommit = GitVcsSettings.getInstance(myProject).isAutoCommitOnCherryPick(); Map<GitRepository, List<VcsFullCommitDetails>> commitsInRoots = sortCommits(groupCommitsByRoots(project, commits)); - new GitCherryPicker(myProject, myGit, myPlatformFacade, autoCommit).cherryPick(commitsInRoots); + new GitCherryPicker(project, myGit, myPlatformFacade, isAutoCommit(project)).cherryPick(commitsInRoots); } finally { ApplicationManager.getApplication().invokeLater(new Runnable() { @@ -130,15 +131,21 @@ public class GitCherryPickAction extends DumbAwareAction { return groupedCommits; } + private static boolean isAutoCommit(@NotNull Project project) { + return GitVcsSettings.getInstance(project).isAutoCommitOnCherryPick(); + } + @Override public void update(AnActionEvent e) { super.update(e); - final VcsLog log = getVcsLog(e); - if (log != null && !DvcsUtil.logHasRootForVcs(log, GitVcs.getKey())) { + VcsLog log = getVcsLog(e); + Project project = getEventProject(e); + if (project == null || log == null || !DvcsUtil.logHasRootForVcs(log, GitVcs.getKey())) { e.getPresentation().setEnabledAndVisible(false); } else { e.getPresentation().setEnabled(enabled(e)); + e.getPresentation().setText(isAutoCommit(project) ? NAME : NAME + "..."); } } diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java index 284ccc1e20c5..830f73b4508f 100644 --- a/plugins/git4idea/src/git4idea/commands/Git.java +++ b/plugins/git4idea/src/git4idea/commands/Git.java @@ -22,6 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitCommit; import git4idea.push.GitPushSpec; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -93,7 +94,8 @@ public interface Git { GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName); @NotNull - GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision); + GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target, + @NotNull GitLineHandlerListener... listeners); @NotNull GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision); diff --git a/plugins/git4idea/src/git4idea/commands/GitCommandResult.java b/plugins/git4idea/src/git4idea/commands/GitCommandResult.java index f89a4890a76b..0683d738a9bf 100644 --- a/plugins/git4idea/src/git4idea/commands/GitCommandResult.java +++ b/plugins/git4idea/src/git4idea/commands/GitCommandResult.java @@ -16,10 +16,14 @@ package git4idea.commands; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -64,9 +68,10 @@ public class GitCommandResult { @NotNull public String getErrorOutputAsHtmlString() { - return StringUtil.join(myErrorOutput, "<br/>"); + return StringUtil.join(cleanup(myErrorOutput), "<br/>"); } - + + @NotNull public String getErrorOutputAsJoinedString() { return StringUtil.join(myErrorOutput, "\n"); } @@ -90,4 +95,14 @@ public class GitCommandResult { return false; // will be implemented later } + @NotNull + private static Collection<String> cleanup(@NotNull Collection<String> errorOutput) { + return ContainerUtil.map(errorOutput, new Function<String, String>() { + @Override + public String fun(String errorMessage) { + return GitUtil.cleanupErrorPrefixes(errorMessage); + } + }); + } + } diff --git a/plugins/git4idea/src/git4idea/commands/GitHandler.java b/plugins/git4idea/src/git4idea/commands/GitHandler.java index 3350b74b1c6a..c2d85b7e3126 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitHandler.java @@ -18,7 +18,6 @@ package git4idea.commands; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -103,7 +102,6 @@ public abstract class GitHandler { private long myStartTime; // git execution start timestamp private static final long LONG_TIME = 10 * 1000; - @Nullable private ModalityState myState; @Nullable private String myUrl; private boolean myHttpAuthFailed; @@ -423,7 +421,7 @@ public abstract class GitHandler { } else { LOG.debug("cd " + myWorkingDirectory); - LOG.debug(printableCommandLine()); + LOG.debug("[" + myWorkingDirectory.getName() + "] " + printableCommandLine()); } // setup environment @@ -431,7 +429,7 @@ public abstract class GitHandler { if (remoteProtocol == GitRemoteProtocol.SSH && myProjectSettings.isIdeaSsh()) { GitXmlRpcSshService ssh = ServiceManager.getService(GitXmlRpcSshService.class); myEnv.put(GitSSHHandler.GIT_SSH_ENV, ssh.getScriptPath().getPath()); - myHandlerNo = ssh.registerHandler(new GitSSHGUIHandler(myProject, myState)); + myHandlerNo = ssh.registerHandler(new GitSSHGUIHandler(myProject)); myEnvironmentCleanedUp = false; myEnv.put(GitSSHHandler.SSH_HANDLER_ENV, Integer.toString(myHandlerNo)); int port = ssh.getXmlRcpPort(); @@ -458,7 +456,7 @@ public abstract class GitHandler { GitHttpAuthService service = ServiceManager.getService(GitHttpAuthService.class); myEnv.put(GitAskPassXmlRpcHandler.GIT_ASK_PASS_ENV, service.getScriptPath().getPath()); assert myUrl != null : "myUrl can't be null here"; - GitHttpAuthenticator httpAuthenticator = service.createAuthenticator(myProject, myState, myCommand, myUrl); + GitHttpAuthenticator httpAuthenticator = service.createAuthenticator(myProject, myCommand, myUrl); myHandlerNo = service.registerHandler(httpAuthenticator); myEnvironmentCleanedUp = false; myEnv.put(GitAskPassXmlRpcHandler.GIT_ASK_PASS_HANDLER_ENV, Integer.toString(myHandlerNo)); @@ -474,7 +472,9 @@ public abstract class GitHandler { startHandlingStreams(); } catch (Throwable t) { - LOG.error(t); + if (!ApplicationManager.getApplication().isUnitTestMode() || !myProject.isDisposed()) { + LOG.error(t); // will surely happen if called during unit test disposal, because the working dir is simply removed then + } cleanupEnv(); myListeners.getMulticaster().startFailed(t); } @@ -715,10 +715,6 @@ public abstract class GitHandler { myResumeAction.run(); } - public void setModalityState(@Nullable ModalityState state) { - myState = state; - } - /** * @return true if the command line is too big */ diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java b/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java index 69be489be44b..712582339405 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java @@ -15,10 +15,8 @@ */ package git4idea.commands; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.git4idea.http.GitAskPassApp; import org.jetbrains.git4idea.http.GitAskPassXmlRpcHandler; import org.jetbrains.git4idea.ssh.GitXmlRpcHandlerService; @@ -47,8 +45,7 @@ public abstract class GitHttpAuthService extends GitXmlRpcHandlerService<GitHttp * Creates new {@link GitHttpAuthenticator} that will be requested to handle username and password requests from Git. */ @NotNull - public abstract GitHttpAuthenticator createAuthenticator(@NotNull Project project, @Nullable ModalityState state, - @NotNull GitCommand command, @NotNull String url); + public abstract GitHttpAuthenticator createAuthenticator(@NotNull Project project, @NotNull GitCommand command, @NotNull String url); /** * Internal handler implementation class, it is made public to be accessible via XML RPC. diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpAuthServiceImpl.java b/plugins/git4idea/src/git4idea/commands/GitHttpAuthServiceImpl.java index 06374bf78864..cda188d64e4b 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpAuthServiceImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpAuthServiceImpl.java @@ -15,10 +15,8 @@ */ package git4idea.commands; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * @author Kirill Likhodedov @@ -27,9 +25,8 @@ class GitHttpAuthServiceImpl extends GitHttpAuthService { @Override @NotNull - public GitHttpAuthenticator createAuthenticator(@NotNull Project project, @Nullable ModalityState state, @NotNull GitCommand command, - @NotNull String url) { - return new GitHttpGuiAuthenticator(project, state, command, url); + public GitHttpAuthenticator createAuthenticator(@NotNull Project project, @NotNull GitCommand command, @NotNull String url) { + return new GitHttpGuiAuthenticator(project, command, url); } } diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java index e0312e5471fc..c5931f98df10 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java @@ -57,7 +57,6 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { private static final Class<GitHttpAuthenticator> PASS_REQUESTER = GitHttpAuthenticator.class; @NotNull private final Project myProject; - @Nullable private final ModalityState myModalityState; @NotNull private final String myTitle; @NotNull private final String myUrlFromCommand; @@ -69,10 +68,8 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { @Nullable private GitHttpAuthDataProvider myDataProvider; private boolean myWasCancelled; - GitHttpGuiAuthenticator(@NotNull Project project, @Nullable ModalityState modalityState, @NotNull GitCommand command, - @NotNull String url) { + GitHttpGuiAuthenticator(@NotNull Project project, @NotNull GitCommand command, @NotNull String url) { myProject = project; - myModalityState = modalityState; myTitle = "Git " + StringUtil.capitalize(command.name()); myUrlFromCommand = url; } @@ -87,7 +84,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { return ""; } url = adjustUrl(url); - Pair<GitHttpAuthDataProvider, AuthData> authData = findBestAuthData(url, myModalityState); + Pair<GitHttpAuthDataProvider, AuthData> authData = findBestAuthData(url); if (authData != null && authData.second.getPassword() != null) { String password = authData.second.getPassword(); myDataProvider = authData.first; @@ -97,7 +94,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { String prompt = "Enter the password for " + url; myPasswordKey = url; - String password = PasswordSafePromptDialog.askPassword(myProject, myModalityState, myTitle, prompt, PASS_REQUESTER, url, false, null); + String password = PasswordSafePromptDialog.askPassword(myProject, myTitle, prompt, PASS_REQUESTER, url, false, null); if (password == null) { myWasCancelled = true; return ""; @@ -114,7 +111,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { @NotNull public String askUsername(@NotNull String url) { url = adjustUrl(url); - Pair<GitHttpAuthDataProvider, AuthData> authData = findBestAuthData(url, myModalityState); + Pair<GitHttpAuthDataProvider, AuthData> authData = findBestAuthData(url); String login = null; String password = null; if (authData != null) { @@ -152,7 +149,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { dialog.set(new AuthDialog(myProject, myTitle, "Enter credentials for " + url, login, null, true)); dialog.get().show(); } - }, myModalityState == null ? ModalityState.defaultModalityState() : myModalityState); + }, ModalityState.any()); return dialog.get(); } @@ -223,10 +220,10 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { // return the first that knows username + password; otherwise return the first that knows just the username @Nullable - private Pair<GitHttpAuthDataProvider, AuthData> findBestAuthData(@NotNull String url, @Nullable ModalityState modalityState) { + private Pair<GitHttpAuthDataProvider, AuthData> findBestAuthData(@NotNull String url) { Pair<GitHttpAuthDataProvider, AuthData> candidate = null; for (GitHttpAuthDataProvider provider : getProviders()) { - AuthData data = provider.getAuthData(url, modalityState); + AuthData data = provider.getAuthData(url); if (data != null) { Pair<GitHttpAuthDataProvider, AuthData> pair = Pair.create(provider, data); if (data.getPassword() != null) { @@ -268,12 +265,12 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { @Nullable @Override - public AuthData getAuthData(@NotNull String url, @Nullable ModalityState modalityState) { + public AuthData getAuthData(@NotNull String url) { String userName = getUsername(url); String key = makeKey(url, userName); final PasswordSafe passwordSafe = PasswordSafe.getInstance(); try { - String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key, modalityState); + String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key); return new AuthData(StringUtil.notNullize(userName), password); } catch (PasswordSafeException e) { diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index c873e71ff94b..09e254f5392b 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -32,6 +32,7 @@ import git4idea.history.GitHistoryUtils; import git4idea.push.GitPushSpec; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; +import git4idea.reset.GitResetMode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -338,20 +339,26 @@ public class GitImpl implements Git { @Override @NotNull - public GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision) { - final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET); - handler.addParameters("--hard", revision); - return run(handler); + public GitCommandResult reset(@NotNull GitRepository repository, @NotNull GitResetMode mode, @NotNull String target, + @NotNull GitLineHandlerListener... listeners) { + return reset(repository, mode.getArgument(), target, listeners); } @Override @NotNull public GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) { + return reset(repository, "--merge", revision); + } + + @NotNull + private static GitCommandResult reset(@NotNull GitRepository repository, @NotNull String argument, @Nullable String target, + @NotNull GitLineHandlerListener... listeners) { final GitLineHandler handler = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.RESET); - handler.addParameters("--merge"); - if (revision != null) { - handler.addParameters(revision); + handler.addParameters(argument); + if (target != null) { + handler.addParameters(target); } + addListeners(handler, listeners); return run(handler); } diff --git a/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java b/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java index b62f12806761..e9d8df89614a 100644 --- a/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java +++ b/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java @@ -40,6 +40,13 @@ public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFil ".*Your local changes to '(.*)' would be overwritten by merge.*" ); + private static final Pattern[] RESET_PATTERNS = new Pattern[]{Pattern.compile( + ".*Entry '(.*)' not uptodate. Cannot merge.*" + ), + Pattern.compile( + ".*Entry '(.*)' would be overwritten by merge.*" + )}; + // common for checkout and merge public static final Event NEW_PATTERN = new Event( "Your local changes to the following files would be overwritten by", @@ -49,17 +56,18 @@ public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFil public enum Operation { CHECKOUT(OLD_CHECKOUT_PATTERN), - MERGE(OLD_MERGE_PATTERN); + MERGE(OLD_MERGE_PATTERN), + RESET(RESET_PATTERNS); - @NotNull private final Pattern myPattern; + @NotNull private final Pattern[] myPatterns; - Operation(@NotNull Pattern pattern) { - myPattern = pattern; + Operation(@NotNull Pattern... patterns) { + myPatterns = patterns; } @NotNull - public Pattern getPattern() { - return myPattern; + Pattern[] getPatterns() { + return myPatterns; } } @@ -71,10 +79,13 @@ public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFil @Override public void onLineAvailable(@NotNull String line, @NotNull Key outputType) { super.onLineAvailable(line, outputType); - Matcher m = myOperation.getPattern().matcher(line); - if (m.matches()) { - myMessageDetected = true; - myAffectedFiles.add(m.group(1)); + for (Pattern pattern : myOperation.getPatterns()) { + Matcher m = pattern.matcher(line); + if (m.matches()) { + myMessageDetected = true; + myAffectedFiles.add(m.group(1)); + break; + } } } } diff --git a/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java b/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java index 982270421968..4d1c13415ec2 100644 --- a/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitSSHGUIHandler.java @@ -16,7 +16,6 @@ package git4idea.commands; import com.intellij.ide.passwordSafe.ui.PasswordSafePromptDialog; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; @@ -39,17 +38,9 @@ import java.util.concurrent.atomic.AtomicReference; */ public class GitSSHGUIHandler { @Nullable private final Project myProject; - @Nullable private final ModalityState myState; - /** - * A constructor - * - * @param project a project to use - * @param state modality state using which any prompts initiated by the git process should be shown in the UI. - */ - GitSSHGUIHandler(@Nullable Project project, @Nullable ModalityState state) { + GitSSHGUIHandler(@Nullable Project project) { myProject = project; - myState = state; } public boolean verifyServerHostKey(final String hostname, @@ -76,7 +67,7 @@ public class GitSSHGUIHandler { @Nullable public String askPassphrase(final String username, final String keyPath, boolean resetPassword, final String lastError) { String error = processLastError(resetPassword, lastError); - return PasswordSafePromptDialog.askPassphrase(myProject, myState, GitBundle.getString("ssh.ask.passphrase.title"), + return PasswordSafePromptDialog.askPassphrase(myProject, GitBundle.getString("ssh.ask.passphrase.title"), GitBundle.message("ssh.askPassphrase.message", keyPath, username), GitSSHGUIHandler.class, "PASSPHRASE:" + keyPath, resetPassword, error ); @@ -165,7 +156,7 @@ public class GitSSHGUIHandler { @Nullable public String askPassword(final String username, boolean resetPassword, final String lastError) { String error = processLastError(resetPassword, lastError); - return PasswordSafePromptDialog.askPassword(myProject, myState, GitBundle.getString("ssh.password.title"), + return PasswordSafePromptDialog.askPassword(myProject, GitBundle.getString("ssh.password.title"), GitBundle.message("ssh.password.message", username), GitSSHGUIHandler.class, "PASSWORD:" + username, resetPassword, error); } diff --git a/plugins/git4idea/src/git4idea/commands/GitTask.java b/plugins/git4idea/src/git4idea/commands/GitTask.java index c22ce07819c9..79f8e33ef55e 100644 --- a/plugins/git4idea/src/git4idea/commands/GitTask.java +++ b/plugins/git4idea/src/git4idea/commands/GitTask.java @@ -324,7 +324,6 @@ public class GitTask { @Override public final void run(@NotNull ProgressIndicator indicator) { - myHandler.setModalityState(indicator.getModalityState()); myDelegate.run(indicator); } diff --git a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java index 4f74f7ca94f3..1b5860631e59 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java @@ -19,6 +19,7 @@ import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.components.*; import com.intellij.openapi.project.Project; import com.intellij.util.ArrayUtil; +import git4idea.reset.GitResetMode; import git4idea.ui.branch.GitBranchSyncSetting; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,6 +62,7 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S public String RECENT_COMMON_BRANCH = null; public boolean AUTO_COMMIT_ON_CHERRY_PICK = false; public boolean WARN_ABOUT_CRLF = true; + public GitResetMode RESET_MODE = null; } public GitVcsSettings(GitVcsApplicationSettings appSettings) { @@ -177,6 +179,15 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S myState.WARN_ABOUT_CRLF = warn; } + @Nullable + public GitResetMode getResetMode() { + return myState.RESET_MODE; + } + + public void setResetMode(@NotNull GitResetMode mode) { + myState.RESET_MODE = mode; + } + /** * Provides migration from project settings. * This method is to be removed in IDEA 13: it should be moved to {@link GitVcsApplicationSettings} diff --git a/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java b/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java index 87edd30ae077..a6488d4ce3d0 100644 --- a/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java +++ b/plugins/git4idea/src/git4idea/history/GitDiffFromHistoryHandler.java @@ -22,25 +22,22 @@ 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.DialogBuilder; 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.Couple; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; -import com.intellij.openapi.vcs.history.CurrentRevision; +import com.intellij.openapi.vcs.history.BaseDiffFromHistoryHandler; 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.ui.awt.RelativePoint; import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; +import com.intellij.util.containers.ContainerUtil; import git4idea.GitFileRevision; import git4idea.GitRevisionNumber; import git4idea.GitUtil; @@ -67,71 +64,59 @@ import java.util.List; * * @author Kirill Likhodedov */ -public class GitDiffFromHistoryHandler implements DiffFromHistoryHandler { +public class GitDiffFromHistoryHandler extends BaseDiffFromHistoryHandler<GitFileRevision> { private static final Logger LOG = Logger.getInstance(GitDiffFromHistoryHandler.class); - @NotNull private final Project myProject; @NotNull private final Git myGit; @NotNull private final GitRepositoryManager myRepositoryManager; public GitDiffFromHistoryHandler(@NotNull Project project) { - myProject = project; + super(project); myGit = ServiceManager.getService(project, Git.class); myRepositoryManager = GitUtil.getRepositoryManager(project); } @Override - public void showDiffForOne(@NotNull AnActionEvent e, @NotNull FilePath filePath, - @NotNull VcsFileRevision previousRevision, @NotNull VcsFileRevision revision) { + public void showDiffForOne(@NotNull AnActionEvent e, + @NotNull FilePath filePath, + @NotNull VcsFileRevision previousRevision, + @NotNull VcsFileRevision revision) { GitFileRevision rev = (GitFileRevision)revision; Collection<String> parents = rev.getParents(); if (parents.size() < 2) { - doShowDiff(filePath, previousRevision, revision, false); + super.showDiffForOne(e, filePath, previousRevision, revision); } else { // merge showDiffForMergeCommit(e, filePath, rev, parents); } } + @NotNull @Override - public void showDiffForTwo(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) { - doShowDiff(filePath, revision1, revision2, true); - } + protected List<Change> getChangesBetweenRevisions(@NotNull FilePath path, @NotNull GitFileRevision rev1, @Nullable GitFileRevision rev2) + throws VcsException { + GitRepository repository = getRepository(path); + String hash1 = rev1.getHash(); + String hash2 = rev2 != null ? rev2.getHash() : null; - private void doShowDiff(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2, - boolean autoSort) { - if (!filePath.isDirectory()) { - VcsHistoryUtil.showDifferencesInBackground(myProject, filePath, revision1, revision2, autoSort); - } - else if (revision2 instanceof CurrentRevision) { - GitFileRevision left = (GitFileRevision)revision1; - showDiffForDirectory(filePath, left.getHash(), null); - } - else if (revision1.equals(VcsFileRevision.NULL)) { - GitFileRevision right = (GitFileRevision)revision2; - showDiffForDirectory(filePath, null, right.getHash()); - } - else { - GitFileRevision left = (GitFileRevision)revision1; - GitFileRevision right = (GitFileRevision)revision2; - if (autoSort) { - Couple<VcsFileRevision> pair = VcsHistoryUtil.sortRevisions(revision1, revision2); - left = (GitFileRevision)pair.first; - right = (GitFileRevision)pair.second; - } - showDiffForDirectory(filePath, left.getHash(), right.getHash()); - } + return ContainerUtil + .newArrayList(GitChangeUtils.getDiff(repository.getProject(), repository.getRoot(), hash1, hash2, Collections.singletonList(path))); } - private void showDiffForDirectory(@NotNull final FilePath path, @Nullable final String hash1, @Nullable final String hash2) { + @NotNull + @Override + protected List<Change> getAffectedChanges(@NotNull FilePath path, @NotNull GitFileRevision rev) throws VcsException { GitRepository repository = getRepository(path); - calculateDiffInBackground(repository, path, hash1, hash2, new Consumer<List<Change>>() { - @Override - public void consume(List<Change> changes) { - showDirDiffDialog(path, hash1, hash2, changes); - } - }); + + return ContainerUtil.newArrayList( + GitChangeUtils.getRevisionChanges(repository.getProject(), repository.getRoot(), rev.getHash(), false, true, true).getChanges()); + } + + @NotNull + @Override + protected String getPresentableName(@NotNull GitFileRevision revision) { + return GitUtil.getShortHash(revision.getHash()); } @NotNull @@ -141,63 +126,6 @@ public class GitDiffFromHistoryHandler implements DiffFromHistoryHandler { return repository; } - // hash1 == null => hash2 is the initial commit - // hash2 == null => comparing hash1 with local - private void calculateDiffInBackground(@NotNull final GitRepository repository, @NotNull final FilePath path, - @Nullable final String hash1, @Nullable final String hash2, - final Consumer<List<Change>> successHandler) { - new Task.Backgroundable(myProject, "Comparing revisions...") { - private List<Change> myChanges; - @Override - public void run(@NotNull ProgressIndicator indicator) { - try { - if (hash1 != null) { - // diff - myChanges = new ArrayList<Change>(GitChangeUtils.getDiff(repository.getProject(), repository.getRoot(), hash1, hash2, - Collections.singletonList(path))); - } - else { - // show the initial commit - myChanges = new ArrayList<Change>(GitChangeUtils.getRevisionChanges(repository.getProject(), repository.getRoot(), hash2, false, - true, true).getChanges()); - } - } - catch (VcsException e) { - showError(e, "Error during requesting diff for directory"); - } - } - - @Override - public void onSuccess() { - successHandler.consume(myChanges); - } - }.queue(); - } - - private void showDirDiffDialog(@NotNull FilePath path, @Nullable String hash1, @Nullable String hash2, @NotNull List<Change> diff) { - DialogBuilder dialogBuilder = new DialogBuilder(myProject); - String title; - if (hash2 != null) { - if (hash1 != null) { - title = String.format("Difference between %s and %s in %s", GitUtil.getShortHash(hash1), GitUtil.getShortHash(hash2), path.getName()); - } - else { - title = String.format("Initial commit %s in %s", GitUtil.getShortHash(hash2), path.getName()); - } - } - else { - LOG.assertTrue(hash1 != null, "hash1 and hash2 can't both be null. Path: " + path); - title = String.format("Difference between %s and local version in %s", GitUtil.getShortHash(hash1), path.getName()); - } - dialogBuilder.setTitle(title); - dialogBuilder.setActionDescriptors(new DialogBuilder.ActionDescriptor[] { new DialogBuilder.CloseDialogAction()}); - final ChangesBrowser changesBrowser = new ChangesBrowser(myProject, null, diff, null, false, true, - null, ChangesBrowser.MyUseCase.COMMITTED_CHANGES, null); - changesBrowser.setChangesToDisplay(diff); - dialogBuilder.setCenterPanel(changesBrowser); - dialogBuilder.showNotModal(); - } - private void showDiffForMergeCommit(@NotNull final AnActionEvent event, @NotNull final FilePath filePath, @NotNull final GitFileRevision rev, @NotNull final Collection<String> parents) { @@ -299,12 +227,6 @@ public class GitDiffFromHistoryHandler implements DiffFromHistoryHandler { return makeRevisionFromHash(currentRevisionPath, parentHash); } - - private void showError(VcsException e, String logMessage) { - LOG.info(logMessage, e); - VcsBalloonProblemNotifier.showOverVersionControlView(this.myProject, e.getMessage(), MessageType.ERROR); - } - private void showPopup(@NotNull AnActionEvent event, @NotNull GitFileRevision rev, @NotNull FilePath filePath, @NotNull Collection<GitFileRevision> parents) { ActionGroup parentActions = createActionGroup(rev, filePath, parents); diff --git a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java index 114b4beb5435..8273c3e1abdc 100644 --- a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java +++ b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java @@ -520,9 +520,19 @@ public class GitHistoryUtils { @NotNull VirtualFile root, @NotNull final Consumer<VcsUser> userRegistry, @NotNull List<String> parameters) throws VcsException { + List<TimedVcsCommit> collector = ContainerUtil.newArrayList(); + readCommits(project, root, userRegistry, parameters, new CollectConsumer<TimedVcsCommit>(collector)); + return collector; + } + + public static void readCommits(@NotNull final Project project, + @NotNull VirtualFile root, + @NotNull final Consumer<VcsUser> userRegistry, + @NotNull List<String> parameters, + @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException { final VcsLogObjectsFactory factory = getObjectsFactoryWithDisposeCheck(project); if (factory == null) { - return Collections.emptyList(); + return; } final int COMMIT_BUFFER = 1000; @@ -536,8 +546,6 @@ public class GitHistoryUtils { h.addParameters(parameters); h.endOptions(); - final List<TimedVcsCommit> commits = ContainerUtil.newArrayList(); - final StringBuilder record = new StringBuilder(); final AtomicInteger records = new AtomicInteger(); final Ref<VcsException> ex = new Ref<VcsException>(); @@ -560,7 +568,10 @@ public class GitHistoryUtils { afterParseRemainder = line.substring(recordEnd + 1); } if (afterParseRemainder != null && records.incrementAndGet() > COMMIT_BUFFER) { // null means can't parse now - commits.addAll(parseCommit(parser, record, userRegistry, factory)); + List<TimedVcsCommit> commits = parseCommit(parser, record, userRegistry, factory); + for (TimedVcsCommit commit : commits) { + commitConsumer.consume(commit); + } record.setLength(0); record.append(afterParseRemainder); } @@ -573,7 +584,10 @@ public class GitHistoryUtils { @Override public void processTerminated(int exitCode) { try { - commits.addAll(parseCommit(parser, record, userRegistry, factory)); + List<TimedVcsCommit> commits = parseCommit(parser, record, userRegistry, factory); + for (TimedVcsCommit commit : commits) { + commitConsumer.consume(commit); + } } catch (Exception e) { ex.set(new VcsException(e)); @@ -589,7 +603,6 @@ public class GitHistoryUtils { if (!ex.isNull()) { throw ex.get(); } - return commits; } @NotNull diff --git a/plugins/git4idea/src/git4idea/i18n/GitBundle.properties b/plugins/git4idea/src/git4idea/i18n/GitBundle.properties index 79e7f70df250..03803fbf985c 100644 --- a/plugins/git4idea/src/git4idea/i18n/GitBundle.properties +++ b/plugins/git4idea/src/git4idea/i18n/GitBundle.properties @@ -491,9 +491,9 @@ git.executable.filechooser.description=Specify the full path to Git executable git.push.active.close=Close git.unstash.clear.confirmation.message=Remove all stashes? This cannot be undone. -git.unstash.clear.confirmation.title=Remove all stashes? +git.unstash.clear.confirmation.title=Remove All Stashes? git.unstash.drop.confirmation.message=<html>Do you want to remove {0}?<br/>"{1}"</html> -git.unstash.drop.confirmation.title=Remove stash {0}? +git.unstash.drop.confirmation.title=Remove Stash {0}? branch.delete.not_fully_merged.description=The branch <code><b>{0}</b></code> is not fully merged to the branch <code><b>{1}</b></code>.<br/>Below is the list of unmerged commits. branch.delete.not_fully_merged.description.not_on_branch=You are currently not on the branch (<code>{1}</code>). <br>\ diff --git a/plugins/git4idea/src/git4idea/log/GitBekParentFixer.java b/plugins/git4idea/src/git4idea/log/GitBekParentFixer.java index e469e61ca315..054ed9f90b48 100644 --- a/plugins/git4idea/src/git4idea/log/GitBekParentFixer.java +++ b/plugins/git4idea/src/git4idea/log/GitBekParentFixer.java @@ -27,49 +27,34 @@ import org.jetbrains.annotations.Nullable; import java.util.*; class GitBekParentFixer { - @NotNull - private final static String MAGIC_TEXT = "Merge remote"; - @NotNull - private final VcsLogFilterCollection MAGIC_FILTER = createVcsLogFilterCollection(); + @NotNull private static final String MAGIC_TEXT = "Merge remote"; + @NotNull private static final VcsLogFilterCollection MAGIC_FILTER = createVcsLogFilterCollection(); - @NotNull - private final VirtualFile myRoot; - @NotNull - private final GitLogProvider myGitLogProvider; - @NotNull - private final List<TimedVcsCommit> myAllCommits; + @NotNull private final Set<Hash> myWrongCommits; - GitBekParentFixer(@NotNull VirtualFile root, @NotNull GitLogProvider gitLogProvider, @NotNull List<TimedVcsCommit> allCommits) { - myRoot = root; - myGitLogProvider = gitLogProvider; - myAllCommits = allCommits; + private GitBekParentFixer(@NotNull Set<Hash> wrongCommits) { + myWrongCommits = wrongCommits; } @NotNull - List<TimedVcsCommit> getCorrectCommits() throws VcsException { - if (!BekSorter.isBekEnabled()) - return myAllCommits; - - final Set<Hash> wrongCommits = getWrongCommits(); - return new AbstractList<TimedVcsCommit>() { - @Override - public TimedVcsCommit get(int index) { - TimedVcsCommit commit = myAllCommits.get(index); - if (!wrongCommits.contains(commit.getId())) - return commit; - - return reverseParents(commit); - } + static GitBekParentFixer prepare(@NotNull VirtualFile root, @NotNull GitLogProvider provider) throws VcsException { + if (!BekSorter.isBekEnabled()) { + return new GitBekParentFixer(Collections.<Hash>emptySet()); + } + return new GitBekParentFixer(getWrongCommits(provider, root)); + } - @Override - public int size() { - return myAllCommits.size(); - } - }; + @NotNull + TimedVcsCommit fixCommit(@NotNull TimedVcsCommit commit) { + if (!myWrongCommits.contains(commit.getId())) { + return commit; + } + return reverseParents(commit); } - private Set<Hash> getWrongCommits() throws VcsException { - List<TimedVcsCommit> commitsMatchingFilter = myGitLogProvider.getCommitsMatchingFilter(myRoot, MAGIC_FILTER, -1); + @NotNull + private static Set<Hash> getWrongCommits(@NotNull GitLogProvider provider, @NotNull VirtualFile root) throws VcsException { + List<TimedVcsCommit> commitsMatchingFilter = provider.getCommitsMatchingFilter(root, MAGIC_FILTER, -1); return ContainerUtil.map2Set(commitsMatchingFilter, new Function<TimedVcsCommit, Hash>() { @Override public Hash fun(TimedVcsCommit timedVcsCommit) { diff --git a/plugins/git4idea/src/git4idea/log/GitLogProvider.java b/plugins/git4idea/src/git4idea/log/GitLogProvider.java index ecc4709be5d3..435313fa5c7d 100644 --- a/plugins/git4idea/src/git4idea/log/GitLogProvider.java +++ b/plugins/git4idea/src/git4idea/log/GitLogProvider.java @@ -154,18 +154,23 @@ public class GitLogProvider implements VcsLogProvider { }); } - @NotNull @Override - public List<TimedVcsCommit> readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<VcsUser> userRegistry) throws VcsException { + public void readAllHashes(@NotNull VirtualFile root, @NotNull Consumer<VcsUser> userRegistry, + @NotNull final Consumer<TimedVcsCommit> commitConsumer) throws VcsException { if (!isRepositoryReady(root)) { - return Collections.emptyList(); + return; } List<String> parameters = new ArrayList<String>(GitHistoryUtils.LOG_ALL); parameters.add("--sparse"); - List<TimedVcsCommit> timedVcsCommits = GitHistoryUtils.readCommits(myProject, root, userRegistry, parameters); - return new GitBekParentFixer(root, this, timedVcsCommits).getCorrectCommits(); + final GitBekParentFixer parentFixer = GitBekParentFixer.prepare(root, this); + GitHistoryUtils.readCommits(myProject, root, userRegistry, parameters, new Consumer<TimedVcsCommit>() { + @Override + public void consume(TimedVcsCommit commit) { + commitConsumer.consume(parentFixer.fixCommit(commit)); + } + }); } @NotNull diff --git a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java index f9d8085cfab8..68b16164b335 100644 --- a/plugins/git4idea/src/git4idea/merge/GitPullDialog.java +++ b/plugins/git4idea/src/git4idea/merge/GitPullDialog.java @@ -44,66 +44,23 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -/** - * Git pull dialog - */ public class GitPullDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance(GitPullDialog.class); - /** - * root panel - */ private JPanel myPanel; - /** - * The selected git root - */ private JComboBox myGitRoot; - /** - * Current branch label - */ private JLabel myCurrentBranch; - /** - * The merge strategy - */ private JComboBox myStrategy; - /** - * No commit option - */ private JCheckBox myNoCommitCheckBox; - /** - * Squash commit option - */ private JCheckBox mySquashCommitCheckBox; - /** - * No fast forward option - */ private JCheckBox myNoFastForwardCheckBox; - /** - * Add log info to commit option - */ private JCheckBox myAddLogInformationCheckBox; - /** - * Selected remote option - */ private JComboBox myRemote; - /** - * The branch chooser - */ private ElementsChooser<String> myBranchChooser; - /** - * The context project - */ private final Project myProject; private final GitRepositoryManager myRepositoryManager; - /** - * A constructor - * - * @param project a project to select - * @param roots a git repository roots for the project - * @param defaultRoot a guessed default root - */ public GitPullDialog(Project project, List<VirtualFile> roots, VirtualFile defaultRoot) { super(project, true); setTitle(GitBundle.getString("pull.title")); @@ -138,9 +95,6 @@ public class GitPullDialog extends DialogWrapper { init(); } - /** - * Validate dialog and enable buttons - */ private void validateDialog() { String selectedRemote = getRemote(); if (StringUtil.isEmptyOrSpaces(selectedRemote)) { @@ -150,9 +104,6 @@ public class GitPullDialog extends DialogWrapper { setOKActionEnabled(myBranchChooser.getMarkedElements().size() != 0); } - /** - * @return a pull handler configured according to dialog options - */ public GitLineHandler makeHandler(@NotNull String url) { GitLineHandler h = new GitLineHandler(myProject, gitRoot(), GitCommand.PULL); // ignore merge failure for the pull @@ -232,9 +183,6 @@ public class GitPullDialog extends DialogWrapper { return branch.getName().startsWith(remote + "/"); } - /** - * Update remotes for the git root - */ private void updateRemotes() { GitRepository repository = getRepository(); if (repository == null) { @@ -331,39 +279,23 @@ public class GitPullDialog extends DialogWrapper { }; } - /** - * @return a currently selected git root - */ public VirtualFile gitRoot() { return (VirtualFile)myGitRoot.getSelectedItem(); } - - /** - * Create branch chooser - */ private void createUIComponents() { myBranchChooser = new ElementsChooser<String>(true); } - /** - * {@inheritDoc} - */ protected JComponent createCenterPanel() { return myPanel; } - /** - * {@inheritDoc} - */ @Override protected String getDimensionServiceKey() { return getClass().getName(); } - /** - * {@inheritDoc} - */ @Override protected String getHelpId() { return "reference.VersionControl.Git.Pull"; diff --git a/plugins/git4idea/src/git4idea/remote/GitHttpAuthDataProvider.java b/plugins/git4idea/src/git4idea/remote/GitHttpAuthDataProvider.java index 281d69b772c3..6c0860ccf20a 100644 --- a/plugins/git4idea/src/git4idea/remote/GitHttpAuthDataProvider.java +++ b/plugins/git4idea/src/git4idea/remote/GitHttpAuthDataProvider.java @@ -15,7 +15,6 @@ */ package git4idea.remote; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.util.AuthData; import org.jetbrains.annotations.NotNull; @@ -32,7 +31,7 @@ public interface GitHttpAuthDataProvider { ExtensionPointName<GitHttpAuthDataProvider> EP_NAME = ExtensionPointName.create("Git4Idea.GitHttpAuthDataProvider"); @Nullable - AuthData getAuthData(@NotNull String url, @Nullable ModalityState modalityState); + AuthData getAuthData(@NotNull String url); void forgetPassword(@NotNull String url); diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java index 29f2c4cf3e2e..e401334416d6 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryImpl.java @@ -34,7 +34,7 @@ import java.util.Collection; /** * @author Kirill Likhodedov */ -public class GitRepositoryImpl extends RepositoryImpl implements GitRepository, Disposable { +public class GitRepositoryImpl extends RepositoryImpl implements GitRepository { @NotNull private final GitPlatformFacade myPlatformFacade; @NotNull private final GitRepositoryReader myReader; diff --git a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java index 0cf82d9a521f..4ad12d8a121f 100644 --- a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java +++ b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java @@ -237,9 +237,6 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { break; } String path = event.getPath(); - if (path == null) { - continue; - } if (totalRefreshNeeded(path)) { allChanged = true; } @@ -253,7 +250,7 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { // if index has changed, no need to refresh specific files - we get the full status of all files if (allChanged) { - LOG.info(String.format("GitUntrackedFilesHolder: Index has changed, marking %s recursively dirty", myRoot)); + LOG.debug(String.format("GitUntrackedFilesHolder: Index has changed, marking %s recursively dirty", myRoot)); myDirtyScopeManager.dirDirtyRecursively(myRoot); synchronized (LOCK) { myReady = false; diff --git a/plugins/git4idea/src/git4idea/reset/GitNewResetDialog.java b/plugins/git4idea/src/git4idea/reset/GitNewResetDialog.java new file mode 100644 index 000000000000..5e2cb18e7c7e --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitNewResetDialog.java @@ -0,0 +1,144 @@ +/* + * Copyright 2000-2014 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.reset; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBRadioButton; +import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.RadioButtonEnumModel; +import com.intellij.util.ui.UIUtil; +import com.intellij.vcs.log.VcsFullCommitDetails; +import com.intellij.xml.util.XmlStringUtil; +import git4idea.GitUtil; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.Map; + +import static com.intellij.dvcs.DvcsUtil.getShortRepositoryName; + +public class GitNewResetDialog extends DialogWrapper { + + private static final String DIALOG_ID = "git.new.reset.dialog"; + + @NotNull private final Project myProject; + @NotNull private final Map<GitRepository, VcsFullCommitDetails> myCommits; + @NotNull private final GitResetMode myDefaultMode; + @NotNull private final ButtonGroup myButtonGroup; + + private RadioButtonEnumModel<GitResetMode> myEnumModel; + + protected GitNewResetDialog(@NotNull Project project, @NotNull Map<GitRepository, VcsFullCommitDetails> commits, + @NotNull GitResetMode defaultMode) { + super(project); + myProject = project; + myCommits = commits; + myDefaultMode = defaultMode; + myButtonGroup = new ButtonGroup(); + + init(); + setTitle("Git Reset"); + setOKButtonText("Reset"); + setOKButtonMnemonic('R'); + setResizable(false); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBag gb = new GridBag(). + setDefaultAnchor(GridBagConstraints.LINE_START). + setDefaultInsets(0, UIUtil.DEFAULT_HGAP, UIUtil.LARGE_VGAP, 0); + + String description = prepareDescription(myProject, myCommits); + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(description)), gb.nextLine().next().coverLine()); + + String explanation = "This will reset the current branch head to the selected commit, <br/>" + + "and update the working tree and the index according to the selected mode:"; + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(explanation), UIUtil.ComponentStyle.SMALL), gb.nextLine().next().coverLine()); + + for (GitResetMode mode : GitResetMode.values()) { + JBRadioButton button = new JBRadioButton(mode.getName()); + button.setMnemonic(mode.getName().charAt(0)); + myButtonGroup.add(button); + panel.add(button, gb.nextLine().next()); + panel.add(new JBLabel(XmlStringUtil.wrapInHtml(mode.getDescription()), UIUtil.ComponentStyle.SMALL), gb.next()); + } + + myEnumModel = RadioButtonEnumModel.bindEnum(GitResetMode.class, myButtonGroup); + myEnumModel.setSelected(myDefaultMode); + return panel; + } + + @Nullable + @Override + protected String getHelpId() { + return DIALOG_ID; + } + + @NotNull + private static String prepareDescription(@NotNull Project project, @NotNull Map<GitRepository, VcsFullCommitDetails> commits) { + if (commits.size() == 1 && !isMultiRepo(project)) { + Map.Entry<GitRepository, VcsFullCommitDetails> entry = commits.entrySet().iterator().next(); + return String.format("%s -> %s", getSourceText(entry.getKey()), getTargetText(entry.getValue())); + } + + StringBuilder desc = new StringBuilder(""); + for (Map.Entry<GitRepository, VcsFullCommitDetails> entry : commits.entrySet()) { + GitRepository repository = entry.getKey(); + VcsFullCommitDetails commit = entry.getValue(); + desc.append(String.format("%s in %s -> %s<br/>", getSourceText(repository), + getShortRepositoryName(repository), getTargetText(commit))); + } + return desc.toString(); + } + + @NotNull + private static String getTargetText(@NotNull VcsFullCommitDetails commit) { + String commitMessage = StringUtil.shortenTextWithEllipsis(commit.getSubject(), 20, 0); + return String.format("<code><b>%s</b> \"%s\"</code> by <code>%s</code>", + commit.getId().toShortString(), commitMessage, commit.getAuthor().getName()); + } + + @NotNull + private static String getSourceText(@NotNull GitRepository repository) { + String currentRevision = repository.getCurrentRevision(); + assert currentRevision != null; + String text = repository.getCurrentBranch() == null ? + "HEAD (" + GitUtil.getShortHash(currentRevision) + ")" : + repository.getCurrentBranch().getName(); + return "<b>" + text + "</b>"; + } + + private static boolean isMultiRepo(@NotNull Project project) { + return ServiceManager.getService(project, GitRepositoryManager.class).moreThanOneRoot(); + } + + @NotNull + public GitResetMode getResetMode() { + return myEnumModel.getSelected(); + } + +} diff --git a/plugins/git4idea/src/git4idea/reset/GitResetAction.java b/plugins/git4idea/src/git4idea/reset/GitResetAction.java new file mode 100644 index 000000000000..749acd7e7c7c --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitResetAction.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2014 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.reset; + +import com.intellij.dvcs.ui.VcsLogOneCommitPerRepoAction; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ObjectUtils; +import com.intellij.vcs.log.VcsFullCommitDetails; +import git4idea.config.GitVcsSettings; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class GitResetAction extends VcsLogOneCommitPerRepoAction<GitRepository> { + + @Nullable + @Override + protected GitRepository getRepositoryForRoot(@NotNull Project project, @NotNull VirtualFile root) { + return getRepoManager(project).getRepositoryForRoot(root); + } + + @Override + protected void actionPerformed(@NotNull final Project project, @NotNull final Map<GitRepository, VcsFullCommitDetails> commits) { + GitVcsSettings settings = GitVcsSettings.getInstance(project); + GitResetMode defaultMode = ObjectUtils.notNull(settings.getResetMode(), GitResetMode.getDefault()); + GitNewResetDialog dialog = new GitNewResetDialog(project, commits, defaultMode); + dialog.show(); + if (dialog.isOK()) { + final GitResetMode selectedMode = dialog.getResetMode(); + settings.setResetMode(selectedMode); + new Task.Backgroundable(project, "Git reset", false) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + new GitResetOperation(project, commits, selectedMode, indicator).execute(); + } + }.queue(); + } + } + + @NotNull + private static GitRepositoryManager getRepoManager(@NotNull Project project) { + return ServiceManager.getService(project, GitRepositoryManager.class); + } + +} diff --git a/plugins/git4idea/src/git4idea/reset/GitResetMode.java b/plugins/git4idea/src/git4idea/reset/GitResetMode.java new file mode 100644 index 000000000000..c10d95707073 --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitResetMode.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 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.reset; + +import org.jetbrains.annotations.NotNull; + +public enum GitResetMode { + + SOFT("Soft", "--soft", "Files won't change, differences will be staged for commit."), + MIXED("Mixed", "--mixed", "Files won't change, differences won't be staged."), + HARD("Hard", "--hard", "Files will be reverted to the state of the selected commit.<br/>" + + "Warning: any local changes will be lost."), + KEEP("Keep", "--keep", "Files will be reverted to the state of the selected commit,<br/>" + + "but local changes will be kept intact."); + + @NotNull private final String myName; + @NotNull private final String myArgument; + @NotNull private final String myDescription; + + GitResetMode(@NotNull String name, @NotNull String argument, @NotNull String description) { + myName = name; + myArgument = argument; + myDescription = description; + } + + @NotNull + public static GitResetMode getDefault() { + return MIXED; + } + + @NotNull + public String getName() { + return myName; + } + + @NotNull + public String getArgument() { + return myArgument; + } + + @NotNull + public String getDescription() { + return myDescription; + } + +} diff --git a/plugins/git4idea/src/git4idea/reset/GitResetOperation.java b/plugins/git4idea/src/git4idea/reset/GitResetOperation.java new file mode 100644 index 000000000000..9c66a5225a52 --- /dev/null +++ b/plugins/git4idea/src/git4idea/reset/GitResetOperation.java @@ -0,0 +1,191 @@ +/* + * Copyright 2000-2014 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.reset; + +import com.intellij.dvcs.repo.RepositoryUtil; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsNotifier; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.ui.UIUtil; +import com.intellij.vcs.log.VcsFullCommitDetails; +import git4idea.GitPlatformFacade; +import git4idea.GitUtil; +import git4idea.branch.GitBranchUiHandlerImpl; +import git4idea.branch.GitSmartOperationDialog; +import git4idea.commands.Git; +import git4idea.commands.GitCommandResult; +import git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector; +import git4idea.repo.GitRepository; +import git4idea.util.GitPreservingProcess; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.RESET; + +public class GitResetOperation { + + @NotNull private final Project myProject; + @NotNull private final Map<GitRepository, VcsFullCommitDetails> myCommits; + @NotNull private final GitResetMode myMode; + @NotNull private final ProgressIndicator myIndicator; + @NotNull private final Git myGit; + @NotNull private final VcsNotifier myNotifier; + @NotNull private final GitPlatformFacade myFacade; + @NotNull private final GitBranchUiHandlerImpl myUiHandler; + + public GitResetOperation(@NotNull Project project, @NotNull Map<GitRepository, VcsFullCommitDetails> targetCommits, + @NotNull GitResetMode mode, @NotNull ProgressIndicator indicator) { + myProject = project; + myCommits = targetCommits; + myMode = mode; + myIndicator = indicator; + myGit = ServiceManager.getService(Git.class); + myNotifier = VcsNotifier.getInstance(project); + myFacade = ServiceManager.getService(GitPlatformFacade.class); + myUiHandler = new GitBranchUiHandlerImpl(myProject, myFacade, myGit, indicator); + } + + public void execute() { + saveAllDocuments(); + GitUtil.workingTreeChangeStarted(myProject); + Map<GitRepository, GitCommandResult> results = ContainerUtil.newHashMap(); + try { + for (Map.Entry<GitRepository, VcsFullCommitDetails> entry : myCommits.entrySet()) { + GitRepository repository = entry.getKey(); + VirtualFile root = repository.getRoot(); + String target = entry.getValue().getId().asString(); + GitLocalChangesWouldBeOverwrittenDetector detector = new GitLocalChangesWouldBeOverwrittenDetector(root, RESET); + + GitCommandResult result = myGit.reset(repository, myMode, target, detector); + if (!result.success() && detector.wasMessageDetected()) { + GitCommandResult smartResult = proposeSmartReset(detector, repository, target); + if (smartResult != null) { + result = smartResult; + } + } + results.put(repository, result); + repository.update(); + VfsUtil.markDirtyAndRefresh(true, true, false, root); + } + } + finally { + GitUtil.workingTreeChangeFinished(myProject); + } + notifyResult(results); + } + + private GitCommandResult proposeSmartReset(@NotNull GitLocalChangesWouldBeOverwrittenDetector detector, + @NotNull final GitRepository repository, @NotNull final String target) { + Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), detector.getRelativeFilePaths()); + List<Change> affectedChanges = GitUtil.findLocalChangesForPaths(myProject, repository.getRoot(), absolutePaths, false); + int choice = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "reset", "&Hard Reset"); + if (choice == GitSmartOperationDialog.SMART_EXIT_CODE) { + final Ref<GitCommandResult> result = Ref.create(); + new GitPreservingProcess(myProject, myFacade, myGit, Collections.singleton(repository), "reset", target, myIndicator, new Runnable() { + @Override + public void run() { + result.set(myGit.reset(repository, myMode, target)); + } + }).execute(); + return result.get(); + } + if (choice == GitSmartOperationDialog.FORCE_EXIT_CODE) { + return myGit.reset(repository, GitResetMode.HARD, target); + } + return null; + } + + private void notifyResult(@NotNull Map<GitRepository, GitCommandResult> results) { + Map<GitRepository, GitCommandResult> successes = ContainerUtil.newHashMap(); + Map<GitRepository, GitCommandResult> errors = ContainerUtil.newHashMap(); + for (Map.Entry<GitRepository, GitCommandResult> entry : results.entrySet()) { + GitCommandResult result = entry.getValue(); + GitRepository repository = entry.getKey(); + if (result.success()) { + successes.put(repository, result); + } + else { + errors.put(repository, result); + } + } + + if (errors.isEmpty()) { + myNotifier.notifySuccess("", "Reset successful"); + } + else if (!successes.isEmpty()) { + myNotifier.notifyImportantWarning("Reset partially failed", + "Reset was successful for " + joinRepos(successes.keySet()) + + "<br/>but failed for " + joinRepos(errors.keySet()) + ": <br/>" + formErrorReport(errors)); + } + else { + myNotifier.notifyError("Reset Failed", formErrorReport(errors)); + } + } + + @NotNull + private static String formErrorReport(@NotNull Map<GitRepository, GitCommandResult> errorResults) { + MultiMap<String, GitRepository> grouped = groupByResult(errorResults); + if (grouped.size() == 1) { + return "<code>" + grouped.keySet().iterator().next() + "</code>"; + } + return StringUtil.join(grouped.entrySet(), new Function<Map.Entry<String, Collection<GitRepository>>, String>() { + @NotNull + @Override + public String fun(@NotNull Map.Entry<String, Collection<GitRepository>> entry) { + return joinRepos(entry.getValue()) + ":<br/><code>" + entry.getKey() + "</code>"; + } + }, "<br/>"); + } + + // to avoid duplicate error reports if they are the same for different repositories + @NotNull + private static MultiMap<String, GitRepository> groupByResult(@NotNull Map<GitRepository, GitCommandResult> results) { + MultiMap<String, GitRepository> grouped = MultiMap.create(); + for (Map.Entry<GitRepository, GitCommandResult> entry : results.entrySet()) { + grouped.putValue(entry.getValue().getErrorOutputAsHtmlString(), entry.getKey()); + } + return grouped; + } + + @NotNull + private static String joinRepos(@NotNull Collection<GitRepository> repositories) { + return StringUtil.join(RepositoryUtil.sortRepositories(repositories), ", "); + } + + private static void saveAllDocuments() { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + FileDocumentManager.getInstance().saveAllDocuments(); + } + }); + } + +} diff --git a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java index 9b13bb6cdb65..eccaa446b0ed 100644 --- a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java +++ b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java @@ -72,65 +72,25 @@ import java.util.concurrent.atomic.AtomicBoolean; * The unstash dialog */ public class GitUnstashDialog extends DialogWrapper { - /** - * Git root selector - */ private JComboBox myGitRootComboBox; - /** - * The current branch label - */ private JLabel myCurrentBranch; - /** - * The view stash button - */ private JButton myViewButton; - /** - * The drop stash button - */ private JButton myDropButton; - /** - * The clear stashes button - */ private JButton myClearButton; - /** - * The pop stash checkbox - */ private JCheckBox myPopStashCheckBox; - /** - * The branch text field - */ private JTextField myBranchTextField; - /** - * The root panel of the dialog - */ private JPanel myPanel; - /** - * The stash list - */ private JList myStashList; - /** - * If this checkbox is selected, the index is reinstated as well as working tree - */ private JCheckBox myReinstateIndexCheckBox; /** * Set of branches for the current root */ private final HashSet<String> myBranches = new HashSet<String>(); - /** - * The project - */ private final Project myProject; private GitVcs myVcs; private static final Logger LOG = Logger.getInstance(GitUnstashDialog.class); - /** - * A constructor - * - * @param project the project - * @param roots the list of the roots - * @param defaultRoot the default root to select - */ public GitUnstashDialog(final Project project, final List<VirtualFile> roots, final VirtualFile defaultRoot) { super(project, true); setModal(false); @@ -310,9 +270,6 @@ public class GitUnstashDialog extends DialogWrapper { setOKActionEnabled(true); } - /** - * Refresh stash list - */ private void refreshStashList() { final DefaultListModel listModel = (DefaultListModel)myStashList.getModel(); listModel.clear(); @@ -334,16 +291,10 @@ public class GitUnstashDialog extends DialogWrapper { myStashList.setSelectedIndex(0); } - /** - * @return the selected git root - */ private VirtualFile getGitRoot() { return (VirtualFile)myGitRootComboBox.getSelectedItem(); } - /** - * @return unstash handler - */ private GitLineHandler handler() { GitLineHandler h = new GitLineHandler(myProject, getGitRoot(), GitCommand.STASH); String branch = myBranchTextField.getText(); @@ -361,32 +312,19 @@ public class GitUnstashDialog extends DialogWrapper { return h; } - /** - * @return selected stash - * @throws NullPointerException if no stash is selected - */ private StashInfo getSelectedStash() { return (StashInfo)myStashList.getSelectedValue(); } - /** - * {@inheritDoc} - */ protected JComponent createCenterPanel() { return myPanel; } - /** - * {@inheritDoc} - */ @Override protected String getDimensionServiceKey() { return getClass().getName(); } - /** - * {@inheritDoc} - */ @Override protected String getHelpId() { return "reference.VersionControl.Git.Unstash"; @@ -418,6 +356,7 @@ public class GitUnstashDialog extends DialogWrapper { final Ref<GitCommandResult> result = Ref.create(); ProgressManager.getInstance().run(new Task.Modal(h.project(), GitBundle.getString("unstash.unstashing"), false) { public void run(@NotNull final ProgressIndicator indicator) { + indicator.setIndeterminate(true); h.addLineListener(new GitHandlerUtil.GitLineHandlerListenerProgress(indicator, h, "stash", false)); Git git = ServiceManager.getService(Git.class); result.set(git.runCommand(new Computable.PredefinedValueComputable<GitLineHandler>(h))); diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchWidget.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchWidget.java index 80074fb64a28..4d3497fae299 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchWidget.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchWidget.java @@ -136,7 +136,7 @@ public class GitBranchWidget extends EditorBasedWidget implements StatusBarWidge @Override public void run() { Project project = getProject(); - if (project == null) { + if (project == null || project.isDisposed()) { emptyTextAndTooltip(); return; } diff --git a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java index 4315e65227a3..08c5103007d2 100644 --- a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java @@ -19,15 +19,21 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog; import com.intellij.openapi.vcs.update.UpdatedFiles; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import git4idea.GitUtil; import git4idea.branch.GitBranchPair; @@ -173,9 +179,14 @@ public class GitMergeUpdater extends GitUpdater { final Collection<String> remotelyChanged = GitUtil.getPathsDiffBetweenRefs(ServiceManager.getService(Git.class), repository, currentBranch, remoteBranch); final List<File> locallyChanged = myChangeListManager.getAffectedPaths(); - for (File localPath : locallyChanged) { - if (remotelyChanged.contains(FilePathsHelper.convertPath(localPath.getPath()))) { - // found a file which was changed locally and remotely => need to save + for (final File localPath : locallyChanged) { + if (ContainerUtil.exists(remotelyChanged, new Condition<String>() { + @Override + public boolean value(String remotelyChangedPath) { + return FileUtil.pathsEqual(localPath.getPath(), remotelyChangedPath); + } + })) { + // found a file which was changed locally and remotely => need to save return true; } } |