diff options
Diffstat (limited to 'plugins/git4idea')
53 files changed, 1100 insertions, 1893 deletions
diff --git a/plugins/git4idea/git4idea.iml b/plugins/git4idea/git4idea.iml index 75efba73b775..1b6091a77419 100644 --- a/plugins/git4idea/git4idea.iml +++ b/plugins/git4idea/git4idea.iml @@ -48,17 +48,6 @@ </SOURCES> </library> </orderEntry> - <orderEntry type="module-library"> - <library name="jgit-2.1.0"> - <CLASSES> - <root url="jar://$MODULE_DIR$/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r.jar!/" /> - </CLASSES> - <JAVADOC /> - <SOURCES> - <root url="jar://$MODULE_DIR$/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r_source.zip!/" /> - </SOURCES> - </library> - </orderEntry> <orderEntry type="library" scope="TEST" name="Groovy" level="project" /> <orderEntry type="module" module-name="dvcs" exported="" /> <orderEntry type="library" scope="TEST" name="cucumber-jvm" level="project" /> diff --git a/plugins/git4idea/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r.jar b/plugins/git4idea/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r.jar Binary files differdeleted file mode 100644 index 2257a22a8fb6..000000000000 --- a/plugins/git4idea/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r.jar +++ /dev/null diff --git a/plugins/git4idea/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r_source.zip b/plugins/git4idea/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r_source.zip Binary files differdeleted file mode 100644 index 78cb17ebe989..000000000000 --- a/plugins/git4idea/lib/jgit/org.eclipse.jgit-2.1.0.201209190230-r_source.zip +++ /dev/null diff --git a/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java b/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java index 43a1c6cbe349..874360a86c5b 100644 --- a/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java +++ b/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java @@ -5,14 +5,13 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; @@ -30,12 +29,15 @@ import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import git4idea.util.GitFileUtils; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -48,9 +50,79 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { private static final String COMMIT_MESSAGE = "Deploy"; + private static final CommitSession NO_COMMIT = new CommitSession() { + + @Nullable + @Override + public JComponent getAdditionalConfigurationUI() { + return null; + } + + @Nullable + @Override + public JComponent getAdditionalConfigurationUI(Collection<Change> changes, String commitMessage) { + return null; + } + + @Override + public boolean canExecute(Collection<Change> changes, String commitMessage) { + return true; + } + + @Override + public void execute(Collection<Change> changes, String commitMessage) { + + } + + @Override + public void executionCanceled() { + + } + + @Override + public String getHelpId() { + return null; + } + }; + + private static final List<CommitExecutor> ourCommitExecutors = Arrays.asList( + new CommitExecutor() { + + @Nls + @Override + public String getActionText() { + return "Commit and Push"; + } + + @NotNull + @Override + public CommitSession createCommitSession() { + return CommitSession.VCS_COMMIT; + } + }, + new CommitExecutorBase() { + + @Nls + @Override + public String getActionText() { + return "Push without Commit"; + } + + @NotNull + @Override + public CommitSession createCommitSession() { + return NO_COMMIT; + } + + @Override + public boolean areChangesRequired() { + return false; + } + } + ); + private final GitRepositoryManager myGitRepositoryManager; private final Git myGit; - private final AbstractVcsHelper myVcsHelper; private final VirtualFile myContentRoot; private final File myRepositoryRootFile; @@ -87,7 +159,6 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { throw new ServerRuntimeException("Can't initialize GIT"); } GitPlatformFacade gitPlatformFacade = ServiceManager.getService(GitPlatformFacade.class); - myVcsHelper = gitPlatformFacade.getVcsHelper(project); myChangeListManager = gitPlatformFacade.getChangeListManager(project); } @@ -124,32 +195,6 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { relevantChanges.add(change); } } - if (relevantChanges.isEmpty()) { - Integer showCommitDialogChoice = runOnEdt(new Computable<Integer>() { - - @Override - public Integer compute() { - return Messages.showYesNoCancelDialog(getProject(), - "Active changelist does not contain a relevant change.\n" - + "Do you want to show commit dialog anyway?\n\n" - + "Yes - show commit dialog\n" - + "No - push immediately\n" - + "Cancel - interrupt deploy", - "Deploy", - Messages.getWarningIcon()); - } - }); - if (showCommitDialogChoice == Messages.YES) { - relevantChanges.addAll(changes); - } - else if (showCommitDialogChoice == Messages.NO) { - pushApplication(application); - return; - } - else { - throw new ServerRuntimeException("Deploy interrupted"); - } - } final Semaphore commitSemaphore = new Semaphore(); commitSemaphore.down(); @@ -159,20 +204,26 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { @Override public Boolean compute() { - return myVcsHelper.commitChanges(relevantChanges, activeChangeList, COMMIT_MESSAGE, - new CommitResultHandler() { - - @Override - public void onSuccess(@NotNull String commitMessage) { - commitSucceeded.set(true); - commitSemaphore.up(); - } - - @Override - public void onFailure() { - commitSemaphore.up(); - } - }); + return CommitChangeListDialog.commitChanges(getProject(), + relevantChanges, + activeChangeList, + ourCommitExecutors, + false, + COMMIT_MESSAGE, + new CommitResultHandler() { + + @Override + public void onSuccess(@NotNull String commitMessage) { + commitSucceeded.set(true); + commitSemaphore.up(); + } + + @Override + public void onFailure() { + commitSemaphore.up(); + } + }, + false); } }); if (commitStarted != null && commitStarted) { @@ -183,7 +234,7 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { } } else { - getLoggingHandler().println("Commit canceled"); + throw new ServerRuntimeException("Deploy interrupted"); } repository.update(); @@ -211,17 +262,6 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { return result.get(); } - public void undeploy() throws ServerRuntimeException { - getAgentTaskExecutor().execute(new Computable<Object>() { - - @Override - public Object compute() { - getDeployment().deleteApplication(); - return null; - } - }); - } - public boolean isDeployed() throws ServerRuntimeException { return findApplication() != null; } diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index 80fa47fa1a86..32d254f6fb67 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -209,6 +209,6 @@ </extensions> <extensionPoints> - <extensionPoint qualifiedName="Git4Idea.GitHttpAuthDataProvider" interface="git4idea.jgit.GitHttpAuthDataProvider"/> + <extensionPoint qualifiedName="Git4Idea.GitHttpAuthDataProvider" interface="git4idea.remote.GitHttpAuthDataProvider"/> </extensionPoints> </idea-plugin> diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 7204f61aa0ab..4286aee1536b 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -23,6 +23,8 @@ 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.ex.MultiLineLabel; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -30,6 +32,9 @@ import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.ProjectLevelVcsManager; 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; @@ -37,6 +42,7 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import com.intellij.vcsUtil.VcsFileUtil; import com.intellij.vcsUtil.VcsUtil; @@ -50,6 +56,7 @@ import git4idea.repo.GitBranchTrackInfo; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; +import git4idea.util.GitSimplePathsBrowser; import git4idea.util.GitUIUtil; import git4idea.util.StringScanner; import org.jetbrains.annotations.NotNull; @@ -934,4 +941,73 @@ public class GitUtil { return !output.trim().isEmpty(); } + @Nullable + public static VirtualFile findRefreshFileOrLog(@NotNull String absolutePath) { + VirtualFile file = LocalFileSystem.getInstance().findFileByPath(absolutePath); + if (file == null) { + file = LocalFileSystem.getInstance().refreshAndFindFileByPath(absolutePath); + } + if (file == null) { + LOG.warn("VirtualFile not found for " + absolutePath); + } + return file; + } + + @NotNull + public static String toAbsolute(@NotNull VirtualFile root, @NotNull String relativePath) { + return StringUtil.trimEnd(root.getPath(), "/") + "/" + StringUtil.trimStart(relativePath, "/"); + } + + @NotNull + public static Collection<String> toAbsolute(@NotNull final VirtualFile root, @NotNull Collection<String> relativePaths) { + return ContainerUtil.map(relativePaths, new Function<String, String>() { + @Override + public String fun(String s) { + return toAbsolute(root, s); + } + }); + } + + /** + * Given the list of paths converts them to the list of {@link Change Changes} found in the {@link ChangeListManager}, + * i.e. this works only for local changes. </br> + * Paths can be absolute or relative to the repository. + * If a path is not found in the local changes, it is ignored, but the fact is logged. + */ + @NotNull + public static List<Change> findLocalChangesForPaths(@NotNull Project project, @NotNull VirtualFile root, + @NotNull Collection<String> affectedPaths, boolean relativePaths) { + ChangeListManagerEx changeListManager = (ChangeListManagerEx)ChangeListManager.getInstance(project); + List<Change> affectedChanges = new ArrayList<Change>(); + for (String path : affectedPaths) { + String absolutePath = relativePaths ? toAbsolute(root, path) : path; + VirtualFile file = findRefreshFileOrLog(absolutePath); + if (file != null) { + Change change = changeListManager.getChange(file); + if (change != null) { + affectedChanges.add(change); + } + else { + String message = "Change is not found for " + file.getPath(); + if (changeListManager.isInUpdate()) { + message += " because ChangeListManager is being updated."; + } + LOG.warn(message); + } + } + } + return affectedChanges; + } + + public static void showPathsInDialog(@NotNull Project project, @NotNull Collection<String> absolutePaths, @NotNull String title, + @Nullable String description) { + DialogBuilder builder = new DialogBuilder(project); + builder.setCenterPanel(new GitSimplePathsBrowser(project, absolutePaths)); + if (description != null) { + builder.setNorthPanel(new MultiLineLabel(description)); + } + builder.addOkAction(); + builder.setTitle(title); + builder.show(); + } } diff --git a/plugins/git4idea/src/git4idea/GitVcs.java b/plugins/git4idea/src/git4idea/GitVcs.java index 6d1762117c0a..5b3aa4dc0f97 100644 --- a/plugins/git4idea/src/git4idea/GitVcs.java +++ b/plugins/git4idea/src/git4idea/GitVcs.java @@ -56,6 +56,7 @@ import com.intellij.util.containers.ComparatorDelegate; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.UIUtil; import com.intellij.vcs.log.VcsLog; +import com.intellij.vcs.log.VcsUserRegistry; import git4idea.annotate.GitAnnotationProvider; import git4idea.annotate.GitRepositoryForAnnotationsListener; import git4idea.changes.GitCommittedChangeListProvider; @@ -68,7 +69,6 @@ import git4idea.config.*; import git4idea.diff.GitDiffProvider; import git4idea.diff.GitTreeDiffProvider; import git4idea.history.GitHistoryProvider; -import git4idea.history.NewGitUsersComponent; import git4idea.history.browser.GitHeavyCommit; import git4idea.history.browser.GitProjectLogManager; import git4idea.history.wholeTree.GitCommitDetailsProvider; @@ -328,7 +328,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> { if (myVFSListener == null) { myVFSListener = new GitVFSListener(myProject, this, myGit); } - NewGitUsersComponent.getInstance(myProject).activate(); + ServiceManager.getService(myProject, VcsUserRegistry.class); // make sure to read the registry before opening commit dialog if (!Registry.is("git.new.log")) { GitProjectLogManager.getInstance(myProject).activate(); } @@ -368,7 +368,6 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> { Disposer.dispose(myVFSListener); myVFSListener = null; } - NewGitUsersComponent.getInstance(myProject).deactivate(); GitProjectLogManager.getInstance(myProject).deactivate(); if (myBranchWidget != null) { diff --git a/plugins/git4idea/src/git4idea/actions/GitMerge.java b/plugins/git4idea/src/git4idea/actions/GitMerge.java index a19568cb4912..5eb1714f6d7c 100644 --- a/plugins/git4idea/src/git4idea/actions/GitMerge.java +++ b/plugins/git4idea/src/git4idea/actions/GitMerge.java @@ -15,54 +15,36 @@ */ package git4idea.actions; -import com.intellij.history.Label; -import com.intellij.history.LocalHistory; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.update.ActionInfo; import com.intellij.openapi.vfs.VirtualFile; -import git4idea.GitRevisionNumber; -import git4idea.GitUtil; import git4idea.GitVcs; -import git4idea.commands.GitHandlerUtil; import git4idea.commands.GitLineHandler; import git4idea.i18n.GitBundle; import git4idea.merge.GitMergeDialog; -import git4idea.merge.GitMergeUtil; -import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; -import java.util.Set; -/** - * Git "merge" action - */ -public class GitMerge extends GitRepositoryAction { +public class GitMerge extends GitMergeAction { - /** - * {@inheritDoc} - */ @Override @NotNull protected String getActionName() { return GitBundle.getString("merge.action.name"); } - /** - * {@inheritDoc} - */ - protected void perform(@NotNull final Project project, - @NotNull final List<VirtualFile> gitRoots, - @NotNull final VirtualFile defaultRoot, - final Set<VirtualFile> affectedRoots, - final List<VcsException> exceptions) throws VcsException { + @Nullable + @Override + protected DialogState displayDialog(@NotNull Project project, @NotNull List<VirtualFile> gitRoots, @NotNull VirtualFile defaultRoot) { GitVcs vcs = GitVcs.getInstance(project); if (vcs == null) { - return; + return null; } - GitMergeDialog dialog = new GitMergeDialog(project, gitRoots, defaultRoot); + final GitMergeDialog dialog = new GitMergeDialog(project, gitRoots, defaultRoot); try { dialog.updateBranches(); } @@ -70,28 +52,18 @@ public class GitMerge extends GitRepositoryAction { if (vcs.getExecutableValidator().checkExecutableAndShowMessageIfNeeded(null)) { vcs.showErrors(Collections.singletonList(e), GitBundle.getString("merge.retrieving.branches")); } - return; + return null; } dialog.show(); if (!dialog.isOK()) { - return; - } - Label beforeLabel = LocalHistory.getInstance().putSystemLabel(project, "Before update"); - GitLineHandler h = dialog.handler(); - final VirtualFile root = dialog.getSelectedRoot(); - affectedRoots.add(root); - GitRevisionNumber currentRev = GitRevisionNumber.resolve(project, root, "HEAD"); - try { - GitHandlerUtil.doSynchronously(h, GitBundle.message("merging.title", dialog.getSelectedRoot().getPath()), h.printableCommandLine()); + return null; } - finally { - exceptions.addAll(h.errors()); - GitRepositoryManager manager = GitUtil.getRepositoryManager(project); - manager.updateRepository(root); - } - if (exceptions.size() != 0) { - return; - } - GitMergeUtil.showUpdates(this, project, exceptions, root, currentRev, beforeLabel, getActionName(), ActionInfo.INTEGRATE); + return new DialogState(dialog.getSelectedRoot(), GitBundle.message("merging.title", dialog.getSelectedRoot().getPath()), + new Computable<GitLineHandler>() { + @Override + public GitLineHandler compute() { + return dialog.handler(); + } + }); } } diff --git a/plugins/git4idea/src/git4idea/actions/GitMergeAction.java b/plugins/git4idea/src/git4idea/actions/GitMergeAction.java new file mode 100644 index 000000000000..1d46335cc1b1 --- /dev/null +++ b/plugins/git4idea/src/git4idea/actions/GitMergeAction.java @@ -0,0 +1,141 @@ +/* + * 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.actions; + +import com.intellij.history.Label; +import com.intellij.history.LocalHistory; +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.util.Computable; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.update.ActionInfo; +import com.intellij.openapi.vfs.VirtualFile; +import git4idea.GitRevisionNumber; +import git4idea.GitUtil; +import git4idea.GitVcs; +import git4idea.commands.*; +import git4idea.merge.GitMergeUtil; +import git4idea.repo.GitRepository; +import git4idea.repo.GitRepositoryManager; +import git4idea.util.GitUIUtil; +import git4idea.util.LocalChangesWouldBeOverwrittenHelper; +import git4idea.util.UntrackedFilesNotifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.MERGE; + +abstract class GitMergeAction extends GitRepositoryAction { + + protected static class DialogState { + final VirtualFile selectedRoot; + final String progressTitle; + final Computable<GitLineHandler> handlerProvider; + DialogState(@NotNull VirtualFile root, @NotNull String title, @NotNull Computable<GitLineHandler> provider) { + selectedRoot = root; + progressTitle = title; + handlerProvider = provider; + } + } + + @Nullable + protected abstract DialogState displayDialog(@NotNull Project project, @NotNull List<VirtualFile> gitRoots, + @NotNull VirtualFile defaultRoot); + + protected void perform(@NotNull final Project project, + @NotNull final List<VirtualFile> gitRoots, + @NotNull final VirtualFile defaultRoot, + final Set<VirtualFile> affectedRoots, + final List<VcsException> exceptions) throws VcsException { + final DialogState dialogState = displayDialog(project, gitRoots, defaultRoot); + if (dialogState == null) { + return; + } + final VirtualFile selectedRoot = dialogState.selectedRoot; + final Computable<GitLineHandler> handlerProvider = dialogState.handlerProvider; + final Label beforeLabel = LocalHistory.getInstance().putSystemLabel(project, "Before update"); + + new Task.Backgroundable(project, dialogState.progressTitle, false) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + final GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); + final Git git = ServiceManager.getService(Git.class); + final GitLocalChangesWouldBeOverwrittenDetector localChangesDetector = + new GitLocalChangesWouldBeOverwrittenDetector(selectedRoot, MERGE); + final GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector = + new GitUntrackedFilesOverwrittenByOperationDetector(selectedRoot); + GitCommandResult result = git.runCommand(new Computable<GitLineHandler>() { + @Override + public GitLineHandler compute() { + GitLineHandler handler = handlerProvider.compute(); + handler.addLineListener(localChangesDetector); + handler.addLineListener(untrackedFilesDetector); + return handler; + } + }); + affectedRoots.add(selectedRoot); + + GitRepository repository = repositoryManager.getRepositoryForRoot(selectedRoot); + assert repository != null : "Repository can't be null for root " + selectedRoot; + String revision = repository.getCurrentRevision(); + if (revision == null) { + return; + } + final GitRevisionNumber currentRev = new GitRevisionNumber(revision); + handleResult(result, project, localChangesDetector, untrackedFilesDetector, repository, currentRev, affectedRoots, beforeLabel); + } + + }.queue(); + } + + private void handleResult(GitCommandResult result, Project project, GitLocalChangesWouldBeOverwrittenDetector localChangesDetector, + GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector, GitRepository repository, + GitRevisionNumber currentRev, Set<VirtualFile> affectedRoots, Label beforeLabel) { + GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); + VirtualFile root = repository.getRoot(); + if (result.success()) { + root.refresh(false, true); + List<VcsException> exceptions = new ArrayList<VcsException>(); + GitMergeUtil.showUpdates(this, project, exceptions, root, currentRev, beforeLabel, getActionName(), ActionInfo.UPDATE); + repositoryManager.updateRepository(root); + runFinalTasks(project, GitVcs.getInstance(project), affectedRoots, getActionName(), exceptions); + } + else if (localChangesDetector.wasMessageDetected()) { + LocalChangesWouldBeOverwrittenHelper.showErrorNotification(project, repository.getRoot(), getActionName(), + localChangesDetector.getRelativeFilePaths()); + } + else if (untrackedFilesDetector.wasMessageDetected()) { + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(project, root, untrackedFilesDetector.getRelativeFilePaths(), + getActionName(), null); + } + else { + GitUIUtil.notifyError(project, "Git " + getActionName() + " Failed", result.getErrorOutputAsJoinedString(), true, null); + repositoryManager.updateRepository(root); + } + } + + @Override + protected boolean executeFinalTasksSynchronously() { + return false; + } + +} diff --git a/plugins/git4idea/src/git4idea/actions/GitPull.java b/plugins/git4idea/src/git4idea/actions/GitPull.java index 1831be7c37d9..1df7e93abf7e 100644 --- a/plugins/git4idea/src/git4idea/actions/GitPull.java +++ b/plugins/git4idea/src/git4idea/actions/GitPull.java @@ -15,38 +15,21 @@ */ package git4idea.actions; -import com.intellij.history.Label; -import com.intellij.history.LocalHistory; -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.util.Computable; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.update.ActionInfo; import com.intellij.openapi.vfs.VirtualFile; -import git4idea.GitRevisionNumber; import git4idea.GitUtil; -import git4idea.GitVcs; -import git4idea.commands.Git; -import git4idea.commands.GitCommandResult; import git4idea.commands.GitLineHandler; import git4idea.i18n.GitBundle; -import git4idea.merge.GitMergeUtil; import git4idea.merge.GitPullDialog; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; -import git4idea.util.GitUIUtil; import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Set; -/** - * Git "pull" action - */ -public class GitPull extends GitRepositoryAction { +public class GitPull extends GitMergeAction { @Override @NotNull @@ -54,64 +37,33 @@ public class GitPull extends GitRepositoryAction { return GitBundle.getString("pull.action.name"); } - protected void perform(@NotNull final Project project, - @NotNull final List<VirtualFile> gitRoots, - @NotNull final VirtualFile defaultRoot, - final Set<VirtualFile> affectedRoots, - final List<VcsException> exceptions) throws VcsException { + @Override + protected DialogState displayDialog(@NotNull Project project, @NotNull List<VirtualFile> gitRoots, + @NotNull VirtualFile defaultRoot) { final GitPullDialog dialog = new GitPullDialog(project, gitRoots, defaultRoot); dialog.show(); if (!dialog.isOK()) { - return; + return null; } - final Label beforeLabel = LocalHistory.getInstance().putSystemLabel(project, "Before update"); - - new Task.Backgroundable(project, GitBundle.message("pulling.title", dialog.getRemote()), true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - final GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); - GitRepository repository = repositoryManager.getRepositoryForRoot(dialog.gitRoot()); - assert repository != null : "Repository can't be null for root " + dialog.gitRoot(); - String remoteOrUrl = dialog.getRemote(); - - - GitRemote remote = GitUtil.findRemoteByName(repository, remoteOrUrl); - final String url = (remote == null) ? remoteOrUrl : remote.getFirstUrl(); - if (url == null) { - return; - } + GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); + GitRepository repository = repositoryManager.getRepositoryForRoot(dialog.gitRoot()); + assert repository != null : "Repository can't be null for root " + dialog.gitRoot(); + String remoteOrUrl = dialog.getRemote(); + + GitRemote remote = GitUtil.findRemoteByName(repository, remoteOrUrl); + final String url = (remote == null) ? remoteOrUrl : remote.getFirstUrl(); + if (url == null) { + return null; + } - final Git git = ServiceManager.getService(Git.class); - GitCommandResult result = git.runRemoteCommand(new Computable<GitLineHandler>() { - @Override - public GitLineHandler compute() { - return dialog.makeHandler(url); - } - }); - final VirtualFile root = dialog.gitRoot(); - affectedRoots.add(root); - String revision = repository.getCurrentRevision(); - if (revision == null) { - return; - } - final GitRevisionNumber currentRev = new GitRevisionNumber(revision); - if (result.success()) { - root.refresh(false, true); - GitMergeUtil.showUpdates(GitPull.this, project, exceptions, root, currentRev, beforeLabel, getActionName(), ActionInfo.UPDATE); - repositoryManager.updateRepository(root); - runFinalTasks(project, GitVcs.getInstance(project), affectedRoots, getActionName(), exceptions); - } - else { - GitUIUtil.notifyError(project, "Error pulling " + dialog.getRemote(), result.getErrorOutputAsJoinedString(), true, null); - repositoryManager.updateRepository(root); - } + Computable<GitLineHandler> handlerProvider = new Computable<GitLineHandler>() { + @Override + public GitLineHandler compute() { + return dialog.makeHandler(url); } - }.queue(); + }; + return new DialogState(dialog.gitRoot(), GitBundle.message("pulling.title", dialog.getRemote()), handlerProvider); } - @Override - protected boolean executeFinalTasksSynchronously() { - return false; - } } diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java index b65d4cc6097e..7c2f77eeebd2 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchOperation.java @@ -19,7 +19,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsNotifier; @@ -261,21 +260,21 @@ abstract class GitBranchOperation { * If some repositories succeeded, shows a dialog with the list of these files and a proposal to rollback the operation of those * repositories. */ - protected void fatalUntrackedFilesError(@NotNull Collection<VirtualFile> untrackedFiles) { + protected void fatalUntrackedFilesError(@NotNull VirtualFile root, @NotNull Collection<String> relativePaths) { if (wereSuccessful()) { - showUntrackedFilesDialogWithRollback(untrackedFiles); + showUntrackedFilesDialogWithRollback(root, relativePaths); } else { - showUntrackedFilesNotification(untrackedFiles); + showUntrackedFilesNotification(root, relativePaths); } } - private void showUntrackedFilesNotification(@NotNull Collection<VirtualFile> untrackedFiles) { - myUiHandler.showUntrackedFilesNotification(getOperationName(), untrackedFiles); + private void showUntrackedFilesNotification(@NotNull VirtualFile root, @NotNull Collection<String> relativePaths) { + myUiHandler.showUntrackedFilesNotification(getOperationName(), root, relativePaths); } - private void showUntrackedFilesDialogWithRollback(@NotNull Collection<VirtualFile> untrackedFiles) { - boolean ok = myUiHandler.showUntrackedFilesDialogWithRollback(getOperationName(), getRollbackProposal(), untrackedFiles); + private void showUntrackedFilesDialogWithRollback(@NotNull VirtualFile root, @NotNull Collection<String> relativePaths) { + boolean ok = myUiHandler.showUntrackedFilesDialogWithRollback(getOperationName(), getRollbackProposal(), root, relativePaths); if (ok) { rollback(); } @@ -293,7 +292,7 @@ abstract class GitBranchOperation { for (GitRepository repository : repositories) { try { Collection<String> diff = GitUtil.getPathsDiffBetweenRefs(myGit, repository, currentBranch, otherBranch); - List<Change> changesInRepo = convertPathsToChanges(repository, diff, false); + List<Change> changesInRepo = GitUtil.findLocalChangesForPaths(myProject, repository.getRoot(), diff, false); if (!changesInRepo.isEmpty()) { changes.put(repository, changesInRepo); } @@ -324,7 +323,9 @@ abstract class GitBranchOperation { String currentBranch, String nextBranch) { // get changes overwritten by checkout from the error message captured from Git - List<Change> affectedChanges = convertPathsToChanges(currentRepository, localChangesOverwrittenBy.getRelativeFilePaths(), true); + List<Change> affectedChanges = GitUtil.findLocalChangesForPaths(myProject, currentRepository.getRoot(), + localChangesOverwrittenBy.getRelativeFilePaths(), true + ); // get all other conflicting changes // get changes in all other repositories (except those which already have succeeded) to avoid multiple dialogs proposing smart checkout Map<GitRepository, List<Change>> conflictingChangesInRepositories = @@ -339,34 +340,4 @@ abstract class GitBranchOperation { return Pair.create(allConflictingRepositories, affectedChanges); } - - /** - * Given the list of paths converts them to the list of {@link com.intellij.openapi.vcs.changes.Change Changes} found in the {@link com.intellij.openapi.vcs.changes.ChangeListManager}, - * i.e. this works only for local changes. - * Paths can be absolute or relative to the repository. - * If a path is not in the local changes, it is ignored. - */ - @NotNull - private List<Change> convertPathsToChanges(@NotNull GitRepository repository, - @NotNull Collection<String> affectedPaths, boolean relativePaths) { - List<Change> affectedChanges = new ArrayList<Change>(); - for (String path : affectedPaths) { - VirtualFile file; - if (relativePaths) { - file = repository.getRoot().findFileByRelativePath(FileUtil.toSystemIndependentName(path)); - } - else { - file = myFacade.getVirtualFileByPath(path); - } - - if (file != null) { - Change change = myFacade.getChangeListManager(myProject).getChange(file); - if (change != null) { - affectedChanges.add(change); - } - } - } - return affectedChanges; - } - } diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java index 9a153bb9dfed..3b694c455c7b 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandler.java @@ -58,22 +58,24 @@ public interface GitBranchUiHandler { /** * Show notification about "untracked files would be overwritten by merge/checkout". - * @param untrackedFiles */ - void showUntrackedFilesNotification(@NotNull String operationName, @NotNull Collection<VirtualFile> untrackedFiles); + void showUntrackedFilesNotification(@NotNull String operationName, @NotNull VirtualFile root, @NotNull Collection<String> relativePaths); boolean showUntrackedFilesDialogWithRollback(@NotNull String operationName, @NotNull String rollbackProposal, - @NotNull Collection<VirtualFile> untrackedFiles); + @NotNull VirtualFile root, @NotNull Collection<String> relativePaths); /** * 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 operation operation name - * @param force 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 isForcePossible can the operation be executed force (force checkout is possible, force merge - not). * @return the code of the decision. */ - int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull String operation, boolean force); + int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull Collection<String> paths, + @NotNull String operation, boolean isForcePossible); 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 0c1878a5ec82..cea2b55596e0 100644 --- a/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java +++ b/plugins/git4idea/src/git4idea/branch/GitBranchUiHandlerImpl.java @@ -19,14 +19,19 @@ import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.MultiLineLabelUI; import com.intellij.openapi.ui.VerticalFlowLayout; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.components.JBLabel; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import com.intellij.xml.util.XmlStringUtil; import git4idea.GitCommit; @@ -36,12 +41,15 @@ import git4idea.MessageManager; import git4idea.commands.Git; import git4idea.merge.GitConflictResolver; import git4idea.repo.GitRepository; +import git4idea.ui.ChangesBrowserWithRollback; +import git4idea.util.GitSimplePathsBrowser; import git4idea.util.UntrackedFilesNotifier; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.border.EmptyBorder; import javax.swing.event.HyperlinkEvent; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -119,26 +127,41 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { } @Override - public void showUntrackedFilesNotification(@NotNull String operationName, @NotNull Collection<VirtualFile> untrackedFiles) { - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, - untrackedFiles, operationName, null); + public void showUntrackedFilesNotification(@NotNull String operationName, @NotNull VirtualFile root, @NotNull Collection<String> relativePaths) { + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, root, relativePaths, operationName, null); } @Override - public boolean showUntrackedFilesDialogWithRollback(@NotNull String operationName, @NotNull String rollbackProposal, - @NotNull Collection<VirtualFile> untrackedFiles) { - String title = "Could not " + StringUtil.capitalize(operationName); - String description = UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription(operationName, false); + public boolean showUntrackedFilesDialogWithRollback(@NotNull String operationName, @NotNull final String rollbackProposal, + @NotNull VirtualFile root, @NotNull final Collection<String> relativePaths) { + final String title = "Could not " + StringUtil.capitalize(operationName); + final String description = UntrackedFilesNotifier.createUntrackedFilesOverwrittenDescription(operationName, false); - final SelectFilesDialog dialog = new UntrackedFilesDialog(myProject, untrackedFiles, StringUtil.stripHtml(description, true), rollbackProposal); - dialog.setTitle(title); - UIUtil.invokeAndWaitIfNeeded(new Runnable() { + final Collection<String> absolutePaths = GitUtil.toAbsolute(root, relativePaths); + final List<VirtualFile> untrackedFiles = ContainerUtil.mapNotNull(absolutePaths, new Function<String, VirtualFile>() { @Override - public void run() { + public VirtualFile fun(String absolutePath) { + return GitUtil.findRefreshFileOrLog(absolutePath); + } + }); + + return UIUtil.invokeAndWaitIfNeeded(new Computable<Boolean>() { + @Override + public Boolean compute() { + JComponent filesBrowser; + if (untrackedFiles.isEmpty()) { + filesBrowser = new GitSimplePathsBrowser(myProject, absolutePaths); + } + else { + filesBrowser = new SelectFilesDialog.VirtualFileList(myProject, untrackedFiles, false, false); + } + DialogWrapper dialog = new UntrackedFilesRollBackDialog(myProject, filesBrowser, StringUtil.stripHtml(description, true), + rollbackProposal); + dialog.setTitle(title); myFacade.showDialog(dialog); + return dialog.isOK(); } }); - return dialog.isOK(); } @NotNull @@ -148,8 +171,16 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { } @Override - public int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull String operation, boolean force) { - return GitSmartOperationDialog.showAndGetAnswer(myProject, changes, operation, true); + public int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull Collection<String> paths, + @NotNull String operation, boolean isForcePossible) { + JComponent fileBrowser; + if (!changes.isEmpty()) { + fileBrowser = new ChangesBrowserWithRollback(project, changes); + } + else { + fileBrowser = new GitSimplePathsBrowser(project, paths); + } + return GitSmartOperationDialog.showAndGetAnswer(myProject, fileBrowser, operation, isForcePossible); } @Override @@ -177,13 +208,17 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { "After resolving conflicts you also probably would want to commit your files to the current branch."; } - private static class UntrackedFilesDialog extends SelectFilesDialog { + private static class UntrackedFilesRollBackDialog extends DialogWrapper { + @NotNull private final JComponent myFilesBrowser; + @NotNull private final String myPrompt; @NotNull private final String myRollbackProposal; - public UntrackedFilesDialog(@NotNull Project project, @NotNull Collection<VirtualFile> originalFiles, @NotNull String prompt, - @NotNull String rollbackProposal) { - super(project, new ArrayList<VirtualFile>(originalFiles), prompt, null, false, false, false); + public UntrackedFilesRollBackDialog(@NotNull Project project, @NotNull JComponent filesBrowser, @NotNull String prompt, + @NotNull String rollbackProposal) { + super(project); + myFilesBrowser = filesBrowser; + myPrompt = prompt; myRollbackProposal = rollbackProposal; setOKButtonText("Rollback"); setCancelButtonText("Don't rollback"); @@ -198,5 +233,20 @@ public class GitBranchUiHandlerImpl implements GitBranchUiHandler { panel.add(buttons); return panel; } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myFilesBrowser; + } + + @Nullable + @Override + protected JComponent createNorthPanel() { + JLabel label = new JLabel(myPrompt); + label.setUI(new MultiLineLabelUI()); + label.setBorder(new EmptyBorder(5, 1, 5, 1)); + return label; + } } } diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java index a48582680fa0..d49dc15827b7 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java @@ -23,6 +23,7 @@ import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; import git4idea.GitPlatformFacade; +import git4idea.GitUtil; import git4idea.commands.*; import git4idea.repo.GitRepository; import git4idea.util.GitPreservingProcess; @@ -90,7 +91,7 @@ class GitCheckoutOperation extends GitBranchOperation { } } else if (untrackedOverwrittenByCheckout.wasMessageDetected()) { - fatalUntrackedFilesError(untrackedOverwrittenByCheckout.getFiles()); + fatalUntrackedFilesError(repository.getRoot(), untrackedOverwrittenByCheckout.getRelativeFilePaths()); fatalErrorHappened = true; } else { @@ -112,7 +113,8 @@ class GitCheckoutOperation extends GitBranchOperation { List<GitRepository> allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst(); List<Change> affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, "checkout", true); + Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByCheckout.getRelativeFilePaths()); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "checkout", true); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { boolean smartCheckedOutSuccessfully = smartCheckout(allConflictingRepositories, myStartPointReference, myNewBranch, getIndicator()); if (smartCheckedOutSuccessfully) { diff --git a/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java b/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java index 5ce85f114892..46630d0e1125 100644 --- a/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitDeleteRemoteBranchOperation.java @@ -23,12 +23,10 @@ import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.util.ui.UIUtil; -import git4idea.GitBranch; import git4idea.GitPlatformFacade; import git4idea.commands.Git; import git4idea.commands.GitCommandResult; import git4idea.commands.GitCompoundResult; -import git4idea.jgit.GitHttpAdapter; import git4idea.push.GitSimplePushResult; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; @@ -166,15 +164,7 @@ class GitDeleteRemoteBranchOperation extends GitBranchOperation { LOG.warn("No urls are defined for remote: " + remote); return GitCommandResult.error("There is no urls defined for remote " + remote.getName()); } - if (GitHttpAdapter.shouldUseJGit(remoteUrl)) { - String fullBranchName = branchName.startsWith(GitBranch.REFS_HEADS_PREFIX) ? branchName : GitBranch.REFS_HEADS_PREFIX + branchName; - String spec = ":" + fullBranchName; - GitSimplePushResult simplePushResult = GitHttpAdapter.push(repository, remote.getName(), remoteUrl, spec); - return convertSimplePushResultToCommandResult(simplePushResult); - } - else { - return pushDeletionNatively(repository, remoteName, remoteUrl, branchName); - } + return pushDeletionNatively(repository, remoteName, remoteUrl, branchName); } @NotNull @@ -255,6 +245,7 @@ class GitDeleteRemoteBranchOperation extends GitBranchOperation { return false; } + @NotNull @Override public String getDoNotShowMessage() { return checkboxMessage; diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java index 81e5cc35f750..e90d1eb808ef 100644 --- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java @@ -116,7 +116,7 @@ class GitMergeOperation extends GitBranchOperation { } else if (untrackedOverwrittenByMerge.wasMessageDetected()) { LOG.info("Untracked files would be overwritten by merge!"); - fatalUntrackedFilesError(untrackedOverwrittenByMerge.getFiles()); + fatalUntrackedFilesError(repository.getRoot(), untrackedOverwrittenByMerge.getRelativeFilePaths()); fatalErrorHappened = true; } else { @@ -187,7 +187,8 @@ class GitMergeOperation extends GitBranchOperation { List<GitRepository> allConflictingRepositories = conflictingRepositoriesAndAffectedChanges.getFirst(); List<Change> affectedChanges = conflictingRepositoriesAndAffectedChanges.getSecond(); - int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, "merge", false); + Collection<String> absolutePaths = GitUtil.toAbsolute(repository.getRoot(), localChangesOverwrittenByMerge.getRelativeFilePaths()); + int smartCheckoutDecision = myUiHandler.showSmartOperationDialog(myProject, affectedChanges, absolutePaths, "merge", false); if (smartCheckoutDecision == GitSmartOperationDialog.SMART_EXIT_CODE) { return doSmartMerge(allConflictingRepositories); } diff --git a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java index c7f7f9ab476e..9c2ef4a8d107 100644 --- a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java +++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java @@ -19,8 +19,6 @@ import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.UIUtil; @@ -29,7 +27,6 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.event.ActionEvent; -import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static com.intellij.openapi.util.text.StringUtil.capitalize; @@ -39,29 +36,26 @@ import static com.intellij.openapi.util.text.StringUtil.capitalize; * "Your local changes to the following files would be overwritten by merge/checkout" * happens. * Displays the list of these files and proposes to make a "smart" merge or checkout. - * - * @author Kirill Likhodedov */ public class GitSmartOperationDialog extends DialogWrapper { public static final int SMART_EXIT_CODE = OK_EXIT_CODE; public static final int FORCE_EXIT_CODE = NEXT_USER_EXIT_CODE; - - private final Project myProject; - private final List<Change> myChanges; + + @NotNull private final JComponent myFileBrowser; @NotNull private final String myOperationTitle; - private final boolean myForceButton; + private final boolean myShowForceButton; /** * 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 List<Change> changes, @NotNull final String operationTitle, - final boolean forceButton) { + static int showAndGetAnswer(@NotNull final Project project, @NotNull final JComponent fileBrowser, + @NotNull final String operationTitle, final boolean showForceButton) { final AtomicInteger exitCode = new AtomicInteger(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { - GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, changes, operationTitle, forceButton); + GitSmartOperationDialog dialog = new GitSmartOperationDialog(project, fileBrowser, operationTitle, showForceButton); ServiceManager.getService(project, GitPlatformFacade.class).showDialog(dialog); exitCode.set(dialog.getExitCode()); } @@ -69,15 +63,18 @@ public class GitSmartOperationDialog extends DialogWrapper { return exitCode.get(); } - private GitSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull String operationTitle, - boolean forceButton) { + private GitSmartOperationDialog(@NotNull Project project, @NotNull JComponent fileBrowser, @NotNull String operationTitle, + boolean showForceButton) { super(project); - myProject = project; - myChanges = changes; + myFileBrowser = fileBrowser; myOperationTitle = operationTitle; - myForceButton = forceButton; - setOKButtonText("Smart " + capitalize(myOperationTitle)); - setCancelButtonText("Don't " + capitalize(myOperationTitle)); + myShowForceButton = showForceButton; + String capitalizedOperation = capitalize(myOperationTitle); + setTitle("Git " + capitalizedOperation + " Problem"); + + setOKButtonText("Smart " + capitalizedOperation); + getOKAction().putValue(Action.SHORT_DESCRIPTION, "Stash local changes, " + operationTitle + ", unstash"); + setCancelButtonText("Don't " + capitalizedOperation); getCancelAction().putValue(FOCUSED_ACTION, Boolean.TRUE); init(); } @@ -85,8 +82,8 @@ public class GitSmartOperationDialog extends DialogWrapper { @NotNull @Override protected Action[] createLeftSideActions() { - if (myForceButton) { - return new Action[] {new ForceCheckoutAction(myOperationTitle) }; + if (myShowForceButton) { + return new Action[] {new ForceCheckoutAction(myOperationTitle) }; } return new Action[0]; } @@ -102,10 +99,7 @@ public class GitSmartOperationDialog extends DialogWrapper { @Override protected JComponent createCenterPanel() { - ChangesBrowser changesBrowser = - new ChangesBrowser(myProject, null, myChanges, null, false, true, null, ChangesBrowser.MyUseCase.LOCAL_CHANGES, null); - changesBrowser.setChangesToDisplay(myChanges); - return changesBrowser; + return myFileBrowser; } @Override @@ -118,6 +112,7 @@ public class GitSmartOperationDialog extends DialogWrapper { ForceCheckoutAction(@NotNull String operationTitle) { super("&Force " + capitalize(operationTitle)); + putValue(Action.SHORT_DESCRIPTION, capitalize(operationTitle) + " and overwrite local changes"); } @Override diff --git a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java index e23f8ed91c30..157373e4f8b8 100644 --- a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java +++ b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java @@ -186,18 +186,23 @@ public class GitChangeUtils { @NotNull public static GitRevisionNumber resolveReference(@NotNull Project project, @NotNull VirtualFile vcsRoot, @NotNull String reference) throws VcsException { - GitSimpleHandler handler = new GitSimpleHandler(project, vcsRoot, GitCommand.REV_LIST); - handler.addParameters("--timestamp", "--max-count=1", reference); - handler.endOptions(); - handler.setSilent(true); + GitSimpleHandler handler = createRefResolveHandler(project, vcsRoot, reference); String output = handler.run(); StringTokenizer stk = new StringTokenizer(output, "\n\r \t", false); if (!stk.hasMoreTokens()) { - GitSimpleHandler dh = new GitSimpleHandler(project, vcsRoot, GitCommand.LOG); - dh.addParameters("-1", "HEAD"); - dh.setSilent(true); - String out = dh.run(); - LOG.info("Diagnostic output from 'git log -1 HEAD': [" + out + "]"); + try { + GitSimpleHandler dh = new GitSimpleHandler(project, vcsRoot, GitCommand.LOG); + dh.addParameters("-1", "HEAD"); + dh.setSilent(true); + String out = dh.run(); + LOG.info("Diagnostic output from 'git log -1 HEAD': [" + out + "]"); + dh = createRefResolveHandler(project, vcsRoot, reference); + out = dh.run(); + LOG.info("Diagnostic output from 'git rev-list -1 --timestamp HEAD': [" + out + "]"); + } + catch (VcsException e) { + LOG.info("Exception while trying to get some diagnostics info", e); + } throw new VcsException(String.format("The string '%s' does not represent a revision number. Output: [%s]\n Root: %s", reference, output, vcsRoot)); } @@ -205,6 +210,15 @@ public class GitChangeUtils { return new GitRevisionNumber(stk.nextToken(), timestamp); } + @NotNull + private static GitSimpleHandler createRefResolveHandler(@NotNull Project project, @NotNull VirtualFile root, @NotNull String reference) { + GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.REV_LIST); + handler.addParameters("--timestamp", "--max-count=1", reference); + handler.endOptions(); + handler.setSilent(true); + return handler; + } + /** * Check if the exception means that HEAD is missing for the current repository. * diff --git a/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java b/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java index baf835820ddb..f1ab1d476c6f 100644 --- a/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java +++ b/plugins/git4idea/src/git4idea/changes/GitCommittedChangeListProvider.java @@ -22,6 +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.committed.DecoratorManager; import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipper; import com.intellij.openapi.vcs.changes.committed.VcsCommittedViewAuxiliary; @@ -197,7 +198,14 @@ public class GitCommittedChangeListProvider implements CommittedChangesProvider< final Collection<Change> changes = commit.getChanges(); if (changes.size() == 1) { - return Pair.create(commit, changes.iterator().next().getAfterRevision().getFile()); + 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); } 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 170dffff0492..2eb0219485b6 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -20,6 +20,8 @@ import com.intellij.dvcs.DvcsCommitAdditionalComponent; import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.Ref; @@ -27,7 +29,6 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.CheckinProjectPanel; import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.ObjectsConvertor; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.ui.SelectFilePathsDialog; @@ -35,15 +36,15 @@ import com.intellij.openapi.vcs.checkin.CheckinChangeListSpecificComponent; import com.intellij.openapi.vcs.checkin.CheckinEnvironment; import com.intellij.openapi.vcs.ui.RefreshableOnComponent; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.spellchecker.ui.SpellCheckingEditorCustomization; import com.intellij.ui.GuiUtils; -import com.intellij.util.ArrayUtil; -import com.intellij.util.FunctionUtil; -import com.intellij.util.NullableFunction; -import com.intellij.util.PairConsumer; +import com.intellij.ui.StringComboboxEditor; +import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.Convertor; import com.intellij.util.ui.UIUtil; import com.intellij.vcs.log.VcsFullCommitDetails; +import com.intellij.vcs.log.VcsUser; +import com.intellij.vcs.log.VcsUserRegistry; import com.intellij.vcsUtil.VcsFileUtil; import com.intellij.vcsUtil.VcsUtil; import git4idea.GitPlatformFacade; @@ -54,7 +55,6 @@ import git4idea.commands.GitSimpleHandler; import git4idea.config.GitConfigUtil; import git4idea.config.GitVcsSettings; import git4idea.config.GitVersionSpecialty; -import git4idea.history.NewGitUsersComponent; import git4idea.i18n.GitBundle; import git4idea.push.GitPusher; import git4idea.repo.GitRepositoryFiles; @@ -71,9 +71,6 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.List; -/** - * Git environment for commit operations. - */ public class GitCheckinEnvironment implements CheckinEnvironment { private static final Logger log = Logger.getInstance(GitCheckinEnvironment.class.getName()); @NonNls private static final String GIT_COMMIT_MSG_FILE_PREFIX = "git-commit-msg-"; // the file name prefix for commit message file @@ -207,7 +204,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } } catch (VcsException e) { - exceptions.add(e); + exceptions.add(cleanupExceptionText(e)); } } catch (IOException ex) { @@ -226,8 +223,23 @@ public class GitCheckinEnvironment implements CheckinEnvironment { return exceptions; } + @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()); + } + final String DURING_EXECUTING_SUFFIX = GitSimpleHandler.DURING_EXECUTING_ERROR_MESSAGE; + int suffix = msg.indexOf(DURING_EXECUTING_SUFFIX); + if (suffix > 0) { + msg = msg.substring(0, suffix); + } + return new VcsException(msg.trim(), original.getCause()); + } + public List<VcsException> commit(List<Change> changes, String preparedComment) { - return commit(changes, preparedComment, FunctionUtil.<Object, Object>nullConstant(), null); + return commit(changes, preparedComment, FunctionUtil.nullConstant(), null); } /** @@ -424,9 +436,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment { return file; } - /** - * {@inheritDoc} - */ public List<VcsException> scheduleMissingFileForDeletion(List<FilePath> files) { ArrayList<VcsException> rc = new ArrayList<VcsException>(); Map<VirtualFile, List<FilePath>> sortedFiles; @@ -450,21 +459,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment { return rc; } - /** - * Prepare delete files handler. - * - * - * - * @param project the project - * @param root a vcs root - * @param files a files to commit - * @param message a message file to use - * @param nextCommitAuthor a author for the next commit - * @param nextCommitAmend true, if the commit should be amended - * @param nextCommitAuthorDate Author date timestamp to override the date of the commit or null if this overriding is not needed. - * @return a simple handler that does the task - * @throws VcsException in case of git problem - */ private static void commit(Project project, VirtualFile root, Collection<FilePath> files, @@ -499,10 +493,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } } - - /** - * {@inheritDoc} - */ public List<VcsException> scheduleUnversionedFilesForAddition(List<VirtualFile> files) { ArrayList<VcsException> rc = new ArrayList<VcsException>(); Map<VirtualFile, List<VirtualFile>> sortedFiles; @@ -542,13 +532,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } } - /** - * Sort changes by roots - * - * @param changes a change list - * @param exceptions exceptions to collect - * @return sorted changes - */ private static Map<VirtualFile, Collection<Change>> sortChangesByGitRoot(@NotNull List<Change> changes, List<VcsException> exceptions) { Map<VirtualFile, Collection<Change>> result = new HashMap<VirtualFile, Collection<Change>>(); for (Change change : changes) { @@ -579,11 +562,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment { return result; } - /** - * Mark root as dirty - * - * @param root a vcs root to rescan - */ private void markRootDirty(final VirtualFile root) { // Note that the root is invalidated because changes are detected per-root anyway. // Otherwise it is not possible to detect moves. @@ -597,14 +575,8 @@ public class GitCheckinEnvironment implements CheckinEnvironment { myNextCommitAuthorDate = null; } - /** - * Checkin options for git - */ private class GitCheckinOptions extends DvcsCommitAdditionalComponent implements CheckinChangeListSpecificComponent { private final GitVcs myVcs; - /** - * The author ComboBox, the combobox contains previously selected authors. - */ private final ComboBox myAuthor; private Date myAuthorDate; @@ -630,17 +602,26 @@ public class GitCheckinEnvironment implements CheckinEnvironment { c.weightx = 1; c.fill = GridBagConstraints.HORIZONTAL; final List<String> usersList = getUsersList(project); - final Set<String> authors = usersList == null ? new HashSet<String>() : new HashSet<String>(usersList); + final Set<String> authors = new HashSet<String>(usersList); ContainerUtil.addAll(authors, mySettings.getCommitAuthors()); List<String> list = new ArrayList<String>(authors); Collections.sort(list); - list = ObjectsConvertor.convert(list, new Convertor<String, String>() { + + myAuthor = new ComboBox(ArrayUtil.toObjectArray(list)) { @Override - public String convert(String o) { - return StringUtil.shortenTextWithEllipsis(o, 30, 0); + public void addNotify() { + super.addNotify(); + + // adding in addNotify to make sure the editor is ready for further customization + StringComboboxEditor comboboxEditor = new StringComboboxEditor(project, FileTypes.PLAIN_TEXT, myAuthor, true); + myAuthor.setEditor(comboboxEditor); + EditorEx editor = (EditorEx)comboboxEditor.getEditor(); + assert editor != null; + SpellCheckingEditorCustomization.getInstance(false).customize(editor); } - }); - myAuthor = new ComboBox(ArrayUtil.toObjectArray(list)); + }; + myAuthor.setMinimumAndPreferredWidth(100); + myAuthor.insertItemAt("", 0); myAuthor.setSelectedItem(""); myAuthor.setEditable(true); @@ -673,8 +654,15 @@ public class GitCheckinEnvironment implements CheckinEnvironment { return h.run(); } - private List<String> getUsersList(final Project project) { - return NewGitUsersComponent.getInstance(project).get(); + @NotNull + private List<String> getUsersList(@NotNull Project project) { + VcsUserRegistry userRegistry = ServiceManager.getService(project, VcsUserRegistry.class); + return ContainerUtil.map(userRegistry.getUsers(), new Function<VcsUser, String>() { + @Override + public String fun(VcsUser user) { + return user.getName() + " <" + user.getEmail() + ">"; + } + }); } @Override @@ -687,13 +675,12 @@ public class GitCheckinEnvironment implements CheckinEnvironment { @Override public void saveState() { String author = (String)myAuthor.getEditor().getItem(); - myNextCommitAuthor = author.length() == 0 ? null : author; - if (author.length() == 0) { + if (StringUtil.isEmptyOrSpaces(author)) { myNextCommitAuthor = null; } else { - myNextCommitAuthor = author; - mySettings.saveCommitAuthor(author); + myNextCommitAuthor = GitCommitAuthorCorrector.correct(author); + mySettings.saveCommitAuthor(myNextCommitAuthor); } myNextCommitAmend = myAmend.isSelected(); myNextCommitAuthorDate = myAuthorDate; @@ -716,6 +703,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } } + public void setNextCommitIsPushed(Boolean nextCommitIsPushed) { myNextCommitIsPushed = nextCommitIsPushed; } diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java index 75a6df3e8f67..8bfc52ca261a 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java @@ -22,7 +22,9 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Couple; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.CheckinProjectPanel; import com.intellij.openapi.vcs.FilePath; @@ -131,14 +133,17 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory { } if (crlfHelper.get().shouldWarn()) { - final GitCrlfDialog dialog = new GitCrlfDialog(myProject); - UIUtil.invokeAndWaitIfNeeded(new Runnable() { + Pair<Integer, Boolean> codeAndDontWarn = UIUtil.invokeAndWaitIfNeeded(new Computable<Pair<Integer, Boolean>>() { @Override - public void run() { + public Pair<Integer, Boolean> compute() { + final GitCrlfDialog dialog = new GitCrlfDialog(myProject); dialog.show(); + return Pair.create(dialog.getExitCode(), dialog.dontWarnAgain()); } }); - int decision = dialog.getExitCode(); + int decision = codeAndDontWarn.first; + boolean dontWarnAgain = codeAndDontWarn.second; + if (decision == GitCrlfDialog.CANCEL) { return ReturnResult.CANCEL; } @@ -148,7 +153,7 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory { setCoreAutoCrlfAttribute(anyRoot); } else { - if (dialog.dontWarnAgain()) { + if (dontWarnAgain) { settings.setWarnAboutCrlf(false); } } diff --git a/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java b/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java new file mode 100644 index 000000000000..e46e98a7887f --- /dev/null +++ b/plugins/git4idea/src/git4idea/checkin/GitCommitAuthorCorrector.java @@ -0,0 +1,55 @@ +/* + * 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.checkin; + +import org.jetbrains.annotations.NotNull; + +/** + * Corrects some simple but popular mistakes on the author format.<p/> + * The required format is: {@code author name <author.name@email.com>} + */ +class GitCommitAuthorCorrector { + + @NotNull + public static String correct(@NotNull String author) { + author = author.trim(); + + int openBrace = author.indexOf('<'); + int closeBrace = author.indexOf('>'); + + if (openBrace < 0) { // email should open with "<" + int at = author.lastIndexOf("@"); + if (at < 0) { + return author; + } + int email = author.substring(0, at).lastIndexOf(' '); + if (email < 0) { + return author; + } + author = author.substring(0, email + 1) + "<" + author.substring(email + 1); + } + else if (openBrace > 0 && author.charAt(openBrace - 1) != ' ') { // insert space before email + author = author.substring(0, openBrace) + " " + author.substring(openBrace); + } + + if (closeBrace < 0) { // email should close with ">" + author += ">"; + } + + return author; + } + +} diff --git a/plugins/git4idea/src/git4idea/checkout/GitCheckoutProvider.java b/plugins/git4idea/src/git4idea/checkout/GitCheckoutProvider.java index a8b78a151423..5629a93bac73 100644 --- a/plugins/git4idea/src/git4idea/checkout/GitCheckoutProvider.java +++ b/plugins/git4idea/src/git4idea/checkout/GitCheckoutProvider.java @@ -30,9 +30,6 @@ import git4idea.commands.Git; import git4idea.commands.GitCommandResult; import git4idea.commands.GitLineHandlerListener; import git4idea.commands.GitStandardProgressAnalyzer; -import git4idea.jgit.GitHttpAdapter; -import git4idea.update.GitFetchResult; -import git4idea.update.GitFetcher; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -109,26 +106,13 @@ public class GitCheckoutProvider implements CheckoutProvider { public static boolean doClone(@NotNull Project project, @NotNull ProgressIndicator indicator, @NotNull Git git, @NotNull String directoryName, @NotNull String parentDirectory, @NotNull String sourceRepositoryURL) { - if (GitHttpAdapter.shouldUseJGit(sourceRepositoryURL)) { - GitFetchResult result = GitHttpAdapter.cloneRepository(project, new File(parentDirectory, directoryName), sourceRepositoryURL); - GitFetcher.displayFetchResult(project, result, "Clone failed", result.getErrors()); - return result.isSuccess(); - } - else { - return cloneNatively(project, indicator, git, new File(parentDirectory), sourceRepositoryURL, directoryName); - } - } - - private static boolean cloneNatively(@NotNull Project project, @NotNull final ProgressIndicator indicator, - @NotNull Git git, @NotNull File directory, @NotNull String url, @NotNull String cloneDirectoryName) { indicator.setIndeterminate(false); GitLineHandlerListener progressListener = GitStandardProgressAnalyzer.createListener(indicator); - GitCommandResult result = git.clone(project, directory, url, cloneDirectoryName, progressListener); + GitCommandResult result = git.clone(project, new File(parentDirectory), sourceRepositoryURL, directoryName, progressListener); if (result.success()) { return true; } VcsNotifier.getInstance(project).notifyError("Clone failed", result.getErrorOutputAsHtmlString()); return false; } - } diff --git a/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java b/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java index 1f03a81c3fa4..831f833e0db5 100644 --- a/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java +++ b/plugins/git4idea/src/git4idea/checkout/GitCloneDialog.java @@ -40,8 +40,7 @@ public class GitCloneDialog extends CloneDvcsDialog { } /* - * JGit doesn't have ls-remote command independent from repository yet. - * That way, we have a hack here: if http response asked for a password, then the url is at least valid and existant, and we consider + * We have a hack here: if http response asked for a password, then the url is at least valid and existent, and we consider * that the test passed. */ protected boolean test(@NotNull String url) { diff --git a/plugins/git4idea/src/git4idea/cherrypick/GitCherryPicker.java b/plugins/git4idea/src/git4idea/cherrypick/GitCherryPicker.java index d4b3a0fbf1d8..bd8635db89be 100644 --- a/plugins/git4idea/src/git4idea/cherrypick/GitCherryPicker.java +++ b/plugins/git4idea/src/git4idea/cherrypick/GitCherryPicker.java @@ -140,8 +140,8 @@ public class GitCherryPicker { "Please move, remove or add them before you can cherry-pick. <a href='view'>View them</a>"; description += getSuccessfulCommitDetailsIfAny(successfulCommits); - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, untrackedFilesDetector.getFiles(), - "cherry-pick", description); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, repository.getRoot(), + untrackedFilesDetector.getRelativeFilePaths(), "cherry-pick", description); return false; } else if (localChangesOverwrittenDetector.hasHappened()) { diff --git a/plugins/git4idea/src/git4idea/commands/Git.java b/plugins/git4idea/src/git4idea/commands/Git.java index eaf58ff824d2..284ccc1e20c5 100644 --- a/plugins/git4idea/src/git4idea/commands/Git.java +++ b/plugins/git4idea/src/git4idea/commands/Git.java @@ -36,13 +36,13 @@ import java.util.Set; public interface Git { /** - * A generic method to run a Git remote command, when existing methods like {@link #fetch(GitRepository, String, String, List, String...)} + * A generic method to run a Git command, when existing methods like {@link #fetch(GitRepository, String, String, List, String...)} * are not sufficient. * @param handlerConstructor this is needed, since the operation may need to repeat (e.g. in case of authentication failure). * make sure to supply a stateless constructor. */ @NotNull - GitCommandResult runRemoteCommand(@NotNull Computable<GitLineHandler> handlerConstructor); + GitCommandResult runCommand(@NotNull Computable<GitLineHandler> handlerConstructor); @NotNull GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners); diff --git a/plugins/git4idea/src/git4idea/commands/GitHandlerUtil.java b/plugins/git4idea/src/git4idea/commands/GitHandlerUtil.java index 03cfab25bc84..fbc84ca1a7a2 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHandlerUtil.java +++ b/plugins/git4idea/src/git4idea/commands/GitHandlerUtil.java @@ -15,7 +15,6 @@ */ package git4idea.commands; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; @@ -28,21 +27,13 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.awt.EventQueue; -import java.util.Collection; +import java.awt.*; /** * Handler utilities that allow running handlers with progress indicators */ public class GitHandlerUtil { - /** - * The logger instance - */ - private static final Logger LOG = Logger.getInstance(GitHandlerUtil.class.getName()); - /** - * a private constructor for utility class - */ private GitHandlerUtil() { } @@ -55,7 +46,7 @@ public class GitHandlerUtil { * @return A stdout content or null if there was error (exit code != 0 or exception during start). */ @Nullable - public static String doSynchronously(final GitSimpleHandler handler, String operationTitle, @NonNls final String operationName) { + public static String doSynchronously(final GitSimpleHandler handler, final String operationTitle, @NonNls final String operationName) { handler.addListener(new GitHandlerListenerBase(handler, operationName) { protected String getErrorText() { String text = handler.getStderr(); @@ -65,7 +56,13 @@ public class GitHandlerUtil { return text; } }); - runHandlerSynchronously(handler, operationTitle, ProgressManager.getInstance(), true); + final ProgressManager manager = ProgressManager.getInstance(); + manager.runProcessWithProgressSynchronously(new Runnable() { + public void run() { + runInCurrentThread(handler, manager.getProgressIndicator(), true, + operationTitle); + } + }, operationTitle, false, handler.project()); if (!handler.isStarted() || handler.getExitCode() != 0) { return null; } @@ -80,47 +77,12 @@ public class GitHandlerUtil { * @param operationName an operation name shown in failure dialog * @return An exit code */ - public static int doSynchronously(final GitLineHandler handler, String operationTitle, @NonNls final String operationName) { - return doSynchronously(handler, operationTitle, operationName, true); - } - - /** - * Execute simple process synchronously with progress - * - * @param handler a handler - * @param operationTitle an operation title shown in progress dialog - * @param operationName an operation name shown in failure dialog - * @param showErrors if true, the errors are shown when process is terminated - * @return An exit code - */ - public static int doSynchronously(final GitLineHandler handler, - String operationTitle, - @NonNls final String operationName, - boolean showErrors) { - return doSynchronously(handler, operationTitle, operationName, showErrors, true); - } - - - /** - * Execute simple process synchronously with progress - * - * @param handler a handler - * @param operationTitle an operation title shown in progress dialog - * @param operationName an operation name shown in failure dialog - * @param showErrors if true, the errors are shown when process is terminated - * @param setIndeterminateFlag a flag indicating that progress should be configured as indeterminate - * @return An exit code - */ - public static int doSynchronously(final GitLineHandler handler, - final String operationTitle, - @NonNls final String operationName, - final boolean showErrors, - final boolean setIndeterminateFlag) { + public static int doSynchronously(final GitLineHandler handler, final String operationTitle, @NonNls final String operationName) { final ProgressManager manager = ProgressManager.getInstance(); manager.run(new Task.Modal(handler.project(), operationTitle, false) { public void run(@NotNull final ProgressIndicator indicator) { - handler.addLineListener(new GitLineHandlerListenerProgress(indicator, handler, operationName, showErrors)); - runInCurrentThread(handler, indicator, setIndeterminateFlag, operationTitle); + handler.addLineListener(new GitLineHandlerListenerProgress(indicator, handler, operationName, true)); + runInCurrentThread(handler, indicator, true, operationTitle); } }); if (!handler.isStarted()) { @@ -131,26 +93,6 @@ public class GitHandlerUtil { /** - * Run handler synchronously. The method assumes that all listeners are set up. - * - * @param handler a handler to run - * @param operationTitle operation title - * @param manager a progress manager - * @param setIndeterminateFlag if true handler is configured as indeterminate - */ - private static void runHandlerSynchronously(final GitHandler handler, - final String operationTitle, - final ProgressManager manager, - final boolean setIndeterminateFlag) { - manager.runProcessWithProgressSynchronously(new Runnable() { - public void run() { - runInCurrentThread(handler, manager.getProgressIndicator(), setIndeterminateFlag, - operationTitle); - } - }, operationTitle, false, handler.project()); - } - - /** * Run handler in the current thread * * @param handler a handler to run @@ -185,33 +127,6 @@ public class GitHandlerUtil { handler.runInCurrentThread(postStartAction); } - /** - * Run synchronously using progress indicator, but collect exceptions instead of showing error dialog - * - * @param handler a handler to use - * @return the collection of exception collected during operation - */ - public static Collection<VcsException> doSynchronouslyWithExceptions(final GitLineHandler handler) { - final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - return doSynchronouslyWithExceptions(handler, progressIndicator, null); - } - - /** - * Run synchronously using progress indicator, but collect exception instead of showing error dialog - * - * @param handler a handler to use - * @param progressIndicator a progress indicator - * @param operationName - * @return the collection of exception collected during operation - */ - public static Collection<VcsException> doSynchronouslyWithExceptions(final GitLineHandler handler, - final ProgressIndicator progressIndicator, - @Nullable String operationName) { - handler.addLineListener(new GitLineHandlerListenerProgress(progressIndicator, handler, operationName, false)); - runInCurrentThread(handler, progressIndicator, false, operationName); - return handler.errors(); - } - public static String formatOperationName(String operation, @NotNull VirtualFile root) { return operation + " '" + root.getName() + "'..."; } diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java index 141adca79937..5b3a2422254f 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java @@ -25,13 +25,14 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.AuthData; import com.intellij.util.UriUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.URLUtil; import com.intellij.vcsUtil.AuthDialog; -import git4idea.jgit.GitHttpAuthDataProvider; +import git4idea.remote.GitHttpAuthDataProvider; import git4idea.remote.GitRememberedInputs; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -126,15 +127,8 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { return login; } - final AuthDialog dialog = new AuthDialog(myProject, myTitle, "Enter credentials for " + url, login, null, true); - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - @Override - public void run() { - dialog.show(); - } - }, myModalityState == null ? ModalityState.defaultModalityState() : myModalityState); - - if (!dialog.isOK()) { + AuthDialog dialog = showAuthDialog(url, login); + if (dialog == null || !dialog.isOK()) { myWasCancelled = true; return ""; } @@ -149,6 +143,19 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { return myLogin; } + @Nullable + private AuthDialog showAuthDialog(final String url, final String login) { + final Ref<AuthDialog> dialog = Ref.create(); + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + dialog.set(new AuthDialog(myProject, myTitle, "Enter credentials for " + url, login, null, true)); + dialog.get().show(); + } + }, myModalityState == null ? ModalityState.defaultModalityState() : myModalityState); + return dialog.get(); + } + @Override public void saveAuthData() { // save login and url diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index b94cef0004ac..3e14a6414704 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -373,7 +373,7 @@ public class GitImpl implements Git { public GitCommandResult push(@NotNull final GitRepository repository, @NotNull final String remote, @NotNull final String url, @NotNull final String spec, final boolean updateTracking, @NotNull final GitLineHandlerListener... listeners) { - return runRemoteCommand(new Computable<GitLineHandler>() { + return runCommand(new Computable<GitLineHandler>() { @Override public GitLineHandler compute() { final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(repository.getProject(), repository.getRoot(), @@ -451,7 +451,7 @@ public class GitImpl implements Git { @NotNull public GitCommandResult fetch(@NotNull final GitRepository repository, @NotNull final String url, @NotNull final String remote, @NotNull final List<GitLineHandlerListener> listeners, final String... params) { - return runRemoteCommand(new Computable<GitLineHandler>() { + return runCommand(new Computable<GitLineHandler>() { @Override public GitLineHandler compute() { final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(repository.getProject(), repository.getRoot(), @@ -543,7 +543,7 @@ public class GitImpl implements Git { @Override @NotNull - public GitCommandResult runRemoteCommand(@NotNull Computable<GitLineHandler> handlerConstructor) { + public GitCommandResult runCommand(@NotNull Computable<GitLineHandler> handlerConstructor) { return run(handlerConstructor); } diff --git a/plugins/git4idea/src/git4idea/commands/GitSimpleHandler.java b/plugins/git4idea/src/git4idea/commands/GitSimpleHandler.java index 91114f05cdfd..5e66246805d0 100644 --- a/plugins/git4idea/src/git4idea/commands/GitSimpleHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitSimpleHandler.java @@ -34,6 +34,9 @@ import java.io.File; * simple commands. */ public class GitSimpleHandler extends GitTextHandler { + + public static final String DURING_EXECUTING_ERROR_MESSAGE = "during executing"; + /** * Stderr output */ @@ -235,7 +238,7 @@ public class GitSimpleHandler extends GitTextHandler { }); runInCurrentThread(null); if (ex[0] != null) { - throw new VcsException(ex[0].getMessage() + " during executing " + printableCommandLine(), ex[0]); + throw new VcsException(ex[0].getMessage() + " " + DURING_EXECUTING_ERROR_MESSAGE + " " + printableCommandLine(), ex[0]); } if (result[0] == null) { throw new VcsException("The git command returned null: " + printableCommandLine()); diff --git a/plugins/git4idea/src/git4idea/jgit/GitHttpAdapter.java b/plugins/git4idea/src/git4idea/jgit/GitHttpAdapter.java deleted file mode 100644 index dc468653b6f5..000000000000 --- a/plugins/git4idea/src/git4idea/jgit/GitHttpAdapter.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright 2000-2011 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.jgit; - -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; -import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.AuthData; -import com.intellij.util.proxy.CommonProxy; -import git4idea.GitBranch; -import git4idea.GitUtil; -import git4idea.GitVcs; -import git4idea.push.GitSimplePushResult; -import git4idea.remote.GitRememberedInputs; -import git4idea.repo.GitRemote; -import git4idea.repo.GitRepository; -import git4idea.update.GitFetchResult; -import git4idea.update.GitFetcher; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.InvalidRemoteException; -import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.errors.NoRemoteRepositoryException; -import org.eclipse.jgit.errors.NotSupportedException; -import org.eclipse.jgit.errors.TransportException; -import org.eclipse.jgit.lib.ConfigConstants; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.StoredConfig; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.transport.RefSpec; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Handles remote operations over HTTP via JGit library. - * - * @author Kirill Likhodedov - */ -public final class GitHttpAdapter { - - private static final Logger LOG = Logger.getInstance(GitHttpAdapter.class); - - private static final String IGNORECASE_SETTING = "ignorecase"; - - public static boolean shouldUseJGit(@NotNull String url) { - return "jgit".equals(System.getProperty("git.http")); - } - - private enum GeneralResult { - SUCCESS, - CANCELLED, - NOT_AUTHORIZED - } - - private GitHttpAdapter() { - } - - /** - * Fetches the given remote in the given Git repository. - * Asks username and password if needed. - */ - @NotNull - public static GitFetchResult fetch(@NotNull final GitRepository repository, @NotNull final GitRemote remote, - @NotNull String remoteUrl, @Nullable String remoteBranch) { - GitFetchResult.Type resultType; - try { - final Git git = convertToGit(repository); - final GitHttpCredentialsProvider provider = new GitHttpCredentialsProvider(repository.getProject(), remoteUrl); - - List<String> specs; - if (remoteBranch == null) { - specs = remote.getFetchRefSpecs(); - } - else { - specs = Collections.singletonList(GitFetcher.getFetchSpecForBranch(remoteBranch, remote.getName())); - } - - GeneralResult result = callWithAuthRetry(new GitHttpRemoteCommand.Fetch(git, provider, remoteUrl, convertRefSpecs(specs)), - repository.getProject()); - resultType = convertToFetchResultType(result); - } catch (IOException e) { - logException(repository, remote.getName(), remoteUrl, e, "fetching"); - return GitFetchResult.error(e); - } - catch (InvalidRemoteException e) { - logException(repository, remote.getName(), remoteUrl, e, "fetching"); - return GitFetchResult.error(e); - } - catch (URISyntaxException e) { - logException(repository, remote.getName(), remoteUrl, e, "fetching"); - return GitFetchResult.error(e); - } - return new GitFetchResult(resultType); - } - - @NotNull - private static List<RefSpec> convertRefSpecs(@NotNull List<String> refSpecs) { - List<RefSpec> jgitSpecs = new ArrayList<RefSpec>(); - for (String spec : refSpecs) { - jgitSpecs.add(new RefSpec(spec)); - } - return jgitSpecs; - } - - private static void logException(GitRepository repository, String remoteName, String remoteUrl, Exception e, String operation) { - LOG.error("Exception while " + operation + " " + remoteName + "(" + remoteUrl + ")" + " in " + repository.toLogString(), e); - } - - private static GitFetchResult.Type convertToFetchResultType(GeneralResult result) { - switch (result) { - case CANCELLED: return GitFetchResult.Type.CANCELLED; - case SUCCESS: return GitFetchResult.Type.SUCCESS; - case NOT_AUTHORIZED: return GitFetchResult.Type.NOT_AUTHORIZED; - } - return GitFetchResult.Type.CANCELLED; - } - - @NotNull - public static GitSimplePushResult push(@NotNull final GitRepository repository, @NotNull final String remoteName, - @NotNull final String remoteUrl, @NotNull String pushSpec) { - try { - final Git git = convertToGit(repository); - final GitHttpCredentialsProvider provider = new GitHttpCredentialsProvider(repository.getProject(), remoteUrl); - GitHttpRemoteCommand.Push pushCommand = new GitHttpRemoteCommand.Push(git, provider, remoteName, remoteUrl, - convertRefSpecs(Collections.singletonList(pushSpec))); - GeneralResult result = callWithAuthRetry(pushCommand, repository.getProject()); - GitSimplePushResult pushResult = pushCommand.getResult(); - if (pushResult == null) { - return convertToPushResultType(result); - } else { - return pushResult; - } - } - catch (SmartPushNotSupportedException e) { - return GitSimplePushResult.error("Remote <code>" + remoteUrl + "</code> doesn't support <a href=\"http://progit.org/2010/03/04/smart-http.html\">" + - "smart HTTP push. </a><br/>" + - "Please set the server to use smart push or use other protocol (SSH for example). <br/>" + - "If neither is possible, as a workaround you may add authentication data directly to the remote url in <code>.git/config</code>."); - } - catch (InvalidRemoteException e) { - logException(repository, remoteName, remoteUrl, e, "pushing"); - return makeErrorResultFromException(e); - } - catch (IOException e) { - logException(repository, remoteName, remoteUrl, e, "pushing"); - return makeErrorResultFromException(e); - } - catch (URISyntaxException e) { - logException(repository, remoteName, remoteUrl, e, "pushing"); - return makeErrorResultFromException(e); - } - } - - @NotNull - public static Collection<String> lsRemote(@NotNull GitRepository repository, @NotNull String remoteName, @NotNull String remoteUrl) { - try { - final Git git = convertToGit(repository); - final GitHttpCredentialsProvider provider = new GitHttpCredentialsProvider(repository.getProject(), remoteUrl); - GitHttpRemoteCommand.LsRemote lsRemoteCommand = new GitHttpRemoteCommand.LsRemote(git, provider, remoteUrl); - callWithAuthRetry(lsRemoteCommand, repository.getProject()); - return convertRefsToStrings(lsRemoteCommand.getRefs()); - } catch (IOException e) { - logException(repository, remoteName, remoteUrl, e, "ls-remote"); - } - catch (InvalidRemoteException e) { - logException(repository, remoteName, remoteUrl, e, "ls-remote"); - } - catch (URISyntaxException e) { - logException(repository, remoteName, remoteUrl, e, "ls-remote"); - } - return Collections.emptyList(); - } - - @NotNull - private static Collection<String> convertRefsToStrings(@NotNull Collection<Ref> lsRemoteCommandRefs) { - Collection<String> refs = new ArrayList<String>(); - for (Ref ref : lsRemoteCommandRefs) { - String refName = ref.getName(); - if (refName.startsWith(GitBranch.REFS_HEADS_PREFIX)) { - refName = refName.substring(GitBranch.REFS_HEADS_PREFIX.length()); - } - refs.add(refName); - } - return refs; - } - - @NotNull - public static GitFetchResult cloneRepository(@NotNull Project project, @NotNull final File directory, @NotNull final String url) { - GitFetchResult.Type resultType; - try { - final GitHttpCredentialsProvider provider = new GitHttpCredentialsProvider(project, url); - GitHttpRemoteCommand.Clone command = new GitHttpRemoteCommand.Clone(directory, provider, url); - GeneralResult result = callWithAuthRetry(command, project); - resultType = convertToFetchResultType(result); - if (resultType.equals(GitFetchResult.Type.SUCCESS)) { - updateCoreIgnoreCaseSetting(command.getGit()); - } - return new GitFetchResult(resultType); - } - catch (InvalidRemoteException e) { - LOG.info("Exception while cloning " + url + " to " + directory, e); - return GitFetchResult.error(e); - } - catch (IOException e) { - LOG.info("Exception while cloning " + url + " to " + directory, e); - return GitFetchResult.error(e); - } - catch (URISyntaxException e) { - LOG.info("Exception while cloning " + url + " to " + directory, e); - return GitFetchResult.error(e); - } - } - - private static void updateCoreIgnoreCaseSetting(@Nullable Git git) { - if (SystemInfo.isFileSystemCaseSensitive) { - return; - } - if (git == null) { - LOG.info("jgit.Git is null, the command should have failed. Not updating the settings."); - return; - } - StoredConfig config = git.getRepository().getConfig(); - config.setString(ConfigConstants.CONFIG_CORE_SECTION, null, IGNORECASE_SETTING, Boolean.TRUE.toString()); - try { - config.save(); - } - catch (IOException e) { - LOG.info("Couldn't save config for " + git.getRepository().getDirectory().getPath(), e); - } - } - - @NotNull - private static GitSimplePushResult convertToPushResultType(GeneralResult result) { - switch (result) { - case SUCCESS: - return GitSimplePushResult.success(); - case CANCELLED: - return GitSimplePushResult.cancel(); - case NOT_AUTHORIZED: - return GitSimplePushResult.notAuthorized(); - default: - return GitSimplePushResult.cancel(); - } - } - - - @NotNull - private static GitSimplePushResult makeErrorResultFromException(Exception e) { - return GitSimplePushResult.error(e.toString()); - } - - /** - * Calls the given runnable. - * If user cancels the authentication dialog, returns. - * If user enters incorrect data, he has 2 more attempts to go before failure. - * Cleanups are executed after each incorrect attempt to enter password, and after other retriable actions. - */ - private static GeneralResult callWithAuthRetry(@NotNull GitHttpRemoteCommand command, @NotNull Project project) throws InvalidRemoteException, IOException, URISyntaxException { - boolean httpTransportErrorFixTried = false; - boolean noRemoteWithoutGitErrorFixTried = false; - - String url = command.getUrl(); - GitHttpCredentialsProvider provider = command.getCredentialsProvider(); - try { - for (int i = 0; i < 3; i++) { - try { - AuthData authData = getUsernameAndPassword(provider.getProject(), provider.getUrl()); - if (authData != null) { - provider.fillAuthDataIfNotFilled(authData.getLogin(), authData.getPassword()); - } - if (i == 0) { - provider.setAlwaysShowDialog(false); // if username and password are supplied, no need to show the dialog - } else { - provider.setAlwaysShowDialog(true); // unless these values fail authentication - } - command.run(); - rememberPassword(provider); - return GeneralResult.SUCCESS; - } - catch (GitAPIException e) { - if (!noRemoteWithoutGitErrorFixTried && isNoRemoteWithoutDotGitError(e, url)) { - url = addDotGitToUrl(url); - command.setUrl(url); - provider.setUrl(url); - noRemoteWithoutGitErrorFixTried = true; - // don't "eat" one password entering attempt - //noinspection AssignmentToForLoopParameter - i--; - } - command.cleanup(); - } - catch (JGitInternalException e) { - try { - if (authError(e)) { - if (provider.wasCancelled()) { // if user cancels the dialog, just return - return GeneralResult.CANCELLED; - } - // otherwise give more tries to enter password - } - else if (!httpTransportErrorFixTried && isTransportExceptionForHttp(e, url)) { - url = url.replaceFirst("http", "https"); - command.setUrl(url); - provider.setUrl(url); - httpTransportErrorFixTried = true; - // don't "eat" one password entering attempt - //noinspection AssignmentToForLoopParameter - i--; - } - else if (!noRemoteWithoutGitErrorFixTried && isNoRemoteWithoutDotGitError(e, url)) { - url = addDotGitToUrl(url); - command.setUrl(url); - provider.setUrl(url); - noRemoteWithoutGitErrorFixTried = true; - // don't "eat" one password entering attempt - //noinspection AssignmentToForLoopParameter - i--; - } - else if (smartHttpPushNotSupported(e)) { - throw new SmartPushNotSupportedException(e.getCause().getMessage()); - } - else { - throw e; - } - } - finally { - command.cleanup(); - } - } - } - return GeneralResult.NOT_AUTHORIZED; - } - finally { - log(command, project); - } - } - - private static CommonProxy.HostInfo getHostInfo(String url) throws URISyntaxException { - final boolean isSecure = url.startsWith("https"); - final String protocol = isSecure ? "https" : "http"; - final URI uri = new URI(url); - int port = uri.getPort(); - port = port < 0 ? (isSecure ? 443 : 80) : port; - return new CommonProxy.HostInfo(protocol, uri.getHost(), port); - } - - @NotNull - private static String addDotGitToUrl(@NotNull String url) { - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - return url + GitUtil.DOT_GIT; - } - - private static void log(@NotNull GitHttpRemoteCommand command, @NotNull Project project) { - GitVcs vcs = GitVcs.getInstance(project); - if (vcs != null) { - vcs.showCommandLine(command.getCommandString()); - } - LOG.info(command.getLogString()); - } - - private static boolean smartHttpPushNotSupported(JGitInternalException e) { - if (e.getCause() instanceof NotSupportedException) { - NotSupportedException nse = (NotSupportedException)e.getCause(); - String message = nse.getMessage(); - return message != null && message.toLowerCase().contains("smart http push"); - } - return false; - } - - private static boolean isNoRemoteWithoutDotGitError(Throwable e, String url) { - Throwable cause = e.getCause(); - if (cause == null || (!(cause instanceof NoRemoteRepositoryException) && !(cause.getCause() instanceof NoRemoteRepositoryException))) { - return false; - } - return !url.toLowerCase().endsWith(GitUtil.DOT_GIT); - } - - private static boolean isTransportExceptionForHttp(@NotNull JGitInternalException e, @NotNull String url) { - if (!(e.getCause() instanceof TransportException)) { - return false; - } - return url.toLowerCase().startsWith("http") && !url.toLowerCase().startsWith("https"); - } - - private static void rememberPassword(@NotNull GitHttpCredentialsProvider credentialsProvider) { - if (!credentialsProvider.wasDialogShown()) { // the dialog is not shown => everything is already stored - return; - } - final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); - if (passwordSafe.getSettings().getProviderType() == PasswordSafeSettings.ProviderType.DO_NOT_STORE) { - return; - } - String login = credentialsProvider.getUserName(); - if (login == null || credentialsProvider.getPassword() == null) { - return; - } - - String url = adjustHttpUrl(credentialsProvider.getUrl()); - String key = keyForUrlAndLogin(url, login); - try { - // store in memory always - storePassword(passwordSafe.getMemoryProvider(), credentialsProvider, key); - if (credentialsProvider.isRememberPassword()) { - storePassword(passwordSafe.getMasterKeyProvider(), credentialsProvider, key); - } - GitRememberedInputs.getInstance().addUrl(url, login); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't store the password for key [" + key + "]", e); - } - } - - private static void storePassword(PasswordSafeProvider passwordProvider, GitHttpCredentialsProvider credentialsProvider, String key) throws PasswordSafeException { - passwordProvider.storePassword(credentialsProvider.getProject(), GitHttpCredentialsProvider.class, key, credentialsProvider.getPassword()); - } - - @Nullable - private static AuthData getUsernameAndPassword(Project project, String url) { - url = adjustHttpUrl(url); - String userName = GitRememberedInputs.getInstance().getUserNameForUrl(url); - if (userName == null) { - return trySavedAuthDataFromProviders(url); - } - String key = keyForUrlAndLogin(url, userName); - final PasswordSafe passwordSafe = PasswordSafe.getInstance(); - try { - String password = passwordSafe.getPassword(project, GitHttpCredentialsProvider.class, key); - if (password != null) { - return new AuthData(userName, password); - } - return null; - } - catch (PasswordSafeException e) { - LOG.info("Couldn't get the password for key [" + key + "]", e); - return null; - } - } - - @Nullable - private static AuthData trySavedAuthDataFromProviders(@NotNull String url) { - GitHttpAuthDataProvider[] extensions = GitHttpAuthDataProvider.EP_NAME.getExtensions(); - for (GitHttpAuthDataProvider provider : extensions) { - AuthData authData = provider.getAuthData(url); - if (authData != null) { - return authData; - } - } - return null; - } - - /** - * If url is HTTPS, store it as HTTP in the password database, not to make user enter and remember same credentials twice. - */ - @NotNull - private static String adjustHttpUrl(@NotNull String url) { - if (url.startsWith("https")) { - return url.replaceFirst("https", "http"); - } - return url; - } - - @NotNull - private static String keyForUrlAndLogin(@NotNull String stringUrl, @NotNull String login) { - return login + ":" + stringUrl; - } - - private static boolean authError(@NotNull JGitInternalException e) { - Throwable cause = e.getCause(); - return (cause instanceof TransportException && cause.getMessage().contains("not authorized")); - } - - /** - * Converts {@link GitRepository} to JGit's {@link Repository}. - */ - @NotNull - private static Repository convert(@NotNull GitRepository repository) throws IOException { - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - return builder.setGitDir(new File(repository.getRoot().getPath(), GitUtil.DOT_GIT)) - .readEnvironment() // scan environment GIT_* variables - .findGitDir() // scan up the file system tree - .build(); - } - - /** - * Converts {@link GitRepository} to JGit's {@link Git} object. - */ - private static Git convertToGit(@NotNull GitRepository repository) throws IOException { - return Git.wrap(convert(repository)); - } - - private static class SmartPushNotSupportedException extends NotSupportedException { - private SmartPushNotSupportedException(String message) { - super(message); - } - } -} diff --git a/plugins/git4idea/src/git4idea/jgit/GitHttpCredentialsProvider.java b/plugins/git4idea/src/git4idea/jgit/GitHttpCredentialsProvider.java deleted file mode 100644 index e37796636ea2..000000000000 --- a/plugins/git4idea/src/git4idea/jgit/GitHttpCredentialsProvider.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2000-2011 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.jgit; - -import com.intellij.openapi.project.Project; -import com.intellij.util.ui.UIUtil; -import com.intellij.vcsUtil.AuthDialog; -import org.eclipse.jgit.errors.UnsupportedCredentialItem; -import org.eclipse.jgit.transport.CredentialItem; -import org.eclipse.jgit.transport.CredentialsProvider; -import org.eclipse.jgit.transport.URIish; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @author Kirill Likhodedov - */ -public class GitHttpCredentialsProvider extends CredentialsProvider { - - private static final Pattern HTTP_URL_PATTERN = Pattern.compile("http(?:s?)://(?:([\\S^@\\.]*)@)?.*"); - - private final Project myProject; - private String myRemoteUrl; - - private boolean myCancelled; - private boolean myRememberPassword; - private String myPassword; - private String myUserName; - private boolean myShowDialog; - private boolean myDialogShown; - - public GitHttpCredentialsProvider(@NotNull Project project, @NotNull String remoteUrl) { - myProject = project; - myRemoteUrl = remoteUrl; - } - - @Override - public boolean isInteractive() { - return true; - } - - @Override - public boolean supports(CredentialItem... items) { - for (CredentialItem item : items) { - if (item instanceof CredentialItem.Password) { - continue; - } - if (item instanceof CredentialItem.Username) { - continue; - } - return false; - } - return true; - } - - @Override - public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { - CredentialItem.Username userNameItem = null; - CredentialItem.Password passwordItem = null; - for (CredentialItem item : items) { - if (item instanceof CredentialItem.Username) { - userNameItem = (CredentialItem.Username)item; - } else if (item instanceof CredentialItem.Password) { - passwordItem = (CredentialItem.Password)item; - } - } - - if (userNameItem != null || passwordItem != null) { - String username = getUserNameFromUrl(myRemoteUrl); - String password = null; - if (username == null) { // username is not in the url => reading pre-filled value from the password storage - username = myUserName; - password = myPassword; - } else if (username.equals(myUserName)) { // username is in url => read password only if it is for the same user - password = myPassword; - } - - boolean rememberPassword = myRememberPassword; - boolean ok; - if (username != null && password != null && !myShowDialog) { - ok = true; - myDialogShown = false; - } else { - final AuthDialog dialog = new AuthDialog(myProject, "Login required", "Login to " + myRemoteUrl, username, password, false); - UIUtil.invokeAndWaitIfNeeded(new Runnable() { - @Override - public void run() { - dialog.show(); - } - }); - ok = dialog.isOK(); - myDialogShown = true; - if (ok) { - username = dialog.getUsername(); - password = dialog.getPassword(); - rememberPassword = dialog.isRememberPassword(); - } - } - - if (ok) { - if (userNameItem != null) { - userNameItem.setValue(username); - } - if (passwordItem != null) { - passwordItem.setValue(password.toCharArray()); - } - myRememberPassword = rememberPassword; - myPassword = password; - myUserName = username; - } - else { - myCancelled = true; - myRememberPassword = false; // in case of re-usage of the provider - } - return ok; - } - return true; - } - - public boolean isRememberPassword() { - return myRememberPassword; - } - - @NotNull - public Project getProject() { - return myProject; - } - - @Nullable - public String getPassword() { - return myPassword; - } - - @Nullable - public String getUserName() { - return myUserName; - } - - @NotNull - public String getUrl() { - return myRemoteUrl; - } - - public void setUrl(@NotNull String url) { - myRemoteUrl = url; - } - - public void fillAuthDataIfNotFilled(@NotNull String login, @Nullable String password) { - if (myUserName == null) { - myUserName = login; - myPassword = password; - } else if (myPassword != null) { - myPassword = password; - } - } - - public void setAlwaysShowDialog(boolean showDialog) { - myShowDialog = showDialog; - } - - public boolean wasDialogShown() { - return myDialogShown; - } - - @Nullable - private static String getUserNameFromUrl(@NotNull String url) { - Matcher matcher = HTTP_URL_PATTERN.matcher(url); - if (matcher.matches()) { - return matcher.group(1); - } - return null; - } - - public boolean wasCancelled() { - return myCancelled; - } -} diff --git a/plugins/git4idea/src/git4idea/jgit/GitHttpRemoteCommand.java b/plugins/git4idea/src/git4idea/jgit/GitHttpRemoteCommand.java deleted file mode 100644 index 3d2a9653440b..000000000000 --- a/plugins/git4idea/src/git4idea/jgit/GitHttpRemoteCommand.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright 2000-2011 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.jgit; - -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.Function; -import git4idea.push.GitSimplePushResult; -import org.eclipse.jgit.api.CloneCommand; -import org.eclipse.jgit.api.FetchCommand; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.PushCommand; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.InvalidRemoteException; -import org.eclipse.jgit.api.errors.JGitInternalException; -import org.eclipse.jgit.errors.NotSupportedException; -import org.eclipse.jgit.errors.TransportException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.transport.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.text.MessageFormat; -import java.util.*; - -/** - * @author Kirill Likhodedov - */ -interface GitHttpRemoteCommand { - - String getUrl(); - void setUrl(String url); - void run() throws GitAPIException, URISyntaxException, TransportException; - void cleanup(); - GitHttpCredentialsProvider getCredentialsProvider(); - String getLogString(); - String getCommandString(); - - class Fetch implements GitHttpRemoteCommand { - - private final Git myGit; - private final GitHttpCredentialsProvider myCredentialsProvider; - private String myUrl; - private final List<RefSpec> myRefSpecs; - - Fetch(@NotNull Git git, @NotNull GitHttpCredentialsProvider credentialsProvider, @NotNull String url, @NotNull List<RefSpec> refSpecs) { - myGit = git; - myCredentialsProvider = credentialsProvider; - myUrl = url; - myRefSpecs = refSpecs; - } - - @Override - public void run() throws GitAPIException { - FetchCommand fetchCommand = myGit.fetch(); - fetchCommand.setRemote(myUrl); - fetchCommand.setRefSpecs(myRefSpecs); - fetchCommand.setCredentialsProvider(myCredentialsProvider); - fetchCommand.call(); - } - - @Override - public void setUrl(@NotNull String url) { - myUrl = url; - } - - @Override - public String getUrl() { - return myUrl; - } - - @Override - public GitHttpCredentialsProvider getCredentialsProvider() { - return myCredentialsProvider; - } - - @Override - public String getLogString() { - return getCommandString(); - } - - @Override - public String getCommandString() { - return String.format("git fetch %s %s", myUrl, getRefspecsAsString(myRefSpecs)); - } - - static String getRefspecsAsString(@NotNull List<RefSpec> refSpecs) { - return StringUtil.join(refSpecs, new Function<RefSpec, String>() { - @Override - public String fun(RefSpec spec) { - return spec.toString(); - } - }, " "); - } - - @Override - public void cleanup() { - } - } - - class Clone implements GitHttpRemoteCommand { - - private final File myTargetDirectory; - private final GitHttpCredentialsProvider myCredentialsProvider; - private String myUrl; - @Nullable private Git myGit; - - Clone(@NotNull File targetDirectory, @NotNull GitHttpCredentialsProvider credentialsProvider, String url) { - myTargetDirectory = targetDirectory; - myCredentialsProvider = credentialsProvider; - myUrl = url; - } - - @Override - public void run() throws GitAPIException { - CloneCommand cloneCommand = Git.cloneRepository(); - cloneCommand.setDirectory(myTargetDirectory); - cloneCommand.setURI(myUrl); - cloneCommand.setCredentialsProvider(myCredentialsProvider); - myGit = cloneCommand.call(); - } - - @Override - public void setUrl(@NotNull String url) { - myUrl = url; - } - - @Override - public String getUrl() { - return myUrl; - } - - @Override - public GitHttpCredentialsProvider getCredentialsProvider() { - return myCredentialsProvider; - } - - @Override - public String getLogString() { - return getCommandString(); - } - - @Override - public String getCommandString() { - return String.format("git clone %s %s", myUrl, myTargetDirectory.getPath()); - } - - @Override - public void cleanup() { - if (myTargetDirectory.exists()) { - FileUtil.delete(myTargetDirectory); - } - } - - @Nullable - public Git getGit() { - return myGit; - } - } - - class Push implements GitHttpRemoteCommand { - - private final Git myGit; - private final GitHttpCredentialsProvider myCredentialsProvider; - private GitSimplePushResult myPushResult; - private String myRemoteName; - private String myUrl; - private final List<RefSpec> myPushSpecs; - - Push(@NotNull Git git, @NotNull GitHttpCredentialsProvider credentialsProvider, @NotNull String remoteName, @NotNull String url, @NotNull List<RefSpec> pushSpecs) { - myGit = git; - myCredentialsProvider = credentialsProvider; - myRemoteName = remoteName; - myUrl = url; - myPushSpecs = pushSpecs; - } - - @Override - public void run() throws InvalidRemoteException, URISyntaxException, org.eclipse.jgit.api.errors.TransportException { - PushCommand pushCommand = myGit.push(); - pushCommand.setRemote(myRemoteName); - pushCommand.setRefSpecs(myPushSpecs); - pushCommand.setCredentialsProvider(myCredentialsProvider); - - /* - Need to push to remote NAME (to let push update the remote reference), but to probably another URL. - So constructing RemoteConfig based on the original config for the remote, but with other url. - No need in fetch urls => just removing them. - Remove all push urls (we don't support pushing to multiple urls anyway yet), leaving only single correct url. - Then pass the url to the push command. - */ - RemoteConfig rc = new RemoteConfig(myGit.getRepository().getConfig(), myRemoteName); - List<URIish> uris = new ArrayList<URIish>(rc.getURIs()); - for (URIish uri : uris) { - rc.removeURI(uri); - } - uris = new ArrayList<URIish>(rc.getPushURIs()); - for (URIish uri : uris) { - rc.removePushURI(uri); - } - rc.addPushURI(new URIish(myUrl)); - - Iterable<PushResult> results = call(pushCommand, rc); - myPushResult = analyzeResults(results); - } - - @Override - public void setUrl(@NotNull String url) { - myUrl = url; - } - - @Override - public String getUrl() { - return myUrl; - } - - @Override - public GitHttpCredentialsProvider getCredentialsProvider() { - return myCredentialsProvider; - } - - @Override - public String getLogString() { - return String.format("git push %s (%s) %s", myRemoteName, myUrl, GitHttpRemoteCommand.Fetch.getRefspecsAsString(myPushSpecs)); - } - - @Override - public String getCommandString() { - return String.format("git push %s %s", myRemoteName, GitHttpRemoteCommand.Fetch.getRefspecsAsString(myPushSpecs)); - } - - @Override - public void cleanup() { - } - - @Nullable - GitSimplePushResult getResult() { - return myPushResult; - } - - @NotNull - private static GitSimplePushResult analyzeResults(@NotNull Iterable<PushResult> results) { - Collection<String> rejectedBranches = new ArrayList<String>(); - StringBuilder errorReport = new StringBuilder(); - - for (PushResult result : results) { - for (RemoteRefUpdate update : result.getRemoteUpdates()) { - switch (update.getStatus()) { - case REJECTED_NONFASTFORWARD: - rejectedBranches.add(update.getSrcRef()); - // no break: add reject to the output - case NON_EXISTING: - case REJECTED_NODELETE: - case REJECTED_OTHER_REASON: - case REJECTED_REMOTE_CHANGED: - errorReport.append(update.getSrcRef() + ": " + update.getStatus() + "<br/>"); - default: - // on success do nothing - } - } - } - - if (!rejectedBranches.isEmpty()) { - return GitSimplePushResult.reject(rejectedBranches); - } - else if (errorReport.toString().isEmpty()) { - return GitSimplePushResult.success(); - } - else { - return GitSimplePushResult.error(errorReport.toString()); - } - } - - - /* - A copy-paste from org.eclipse.jgit.api.PushCommand#call with the following differences: - 1. Fields are not accessible, so they are substituted by getters, except for credentialsProvider, which we have stored as an instance field. - 2. checkCallable() won't fail (according to the PushCommand code), so it's safe to remove it. - 3. Actual push is performed via - Transport.openAll(repo, remoteConfig, Transport.Operation.PUSH) - instead of - Transport.openAll(repo, remote, Transport.Operation.PUSH) - where remoteConfig is passed to the method. - Original code constructs the remoteConfig based on .git/config. - */ - @NotNull - private Iterable<PushResult> call(PushCommand pushCommand, RemoteConfig remoteConfig) - throws JGitInternalException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException - { - ArrayList<PushResult> pushResults = new ArrayList<PushResult>(3); - - List<RefSpec> refSpecs = pushCommand.getRefSpecs(); - Repository repo = pushCommand.getRepository(); - boolean force = pushCommand.isForce(); - int timeout = pushCommand.getTimeout(); - CredentialsProvider credentialsProvider = myCredentialsProvider; - String receivePack = pushCommand.getReceivePack(); - boolean thin = pushCommand.isThin(); - boolean dryRun = pushCommand.isDryRun(); - String remote = pushCommand.getRemote(); - ProgressMonitor monitor = pushCommand.getProgressMonitor(); - - try { - if (refSpecs.isEmpty()) { - RemoteConfig config = new RemoteConfig(repo.getConfig(), pushCommand.getRemote()); - refSpecs.addAll(config.getPushRefSpecs()); - } - if (refSpecs.isEmpty()) { - Ref head = repo.getRef(Constants.HEAD); - if (head != null && head.isSymbolic()) { - refSpecs.add(new RefSpec(head.getLeaf().getName())); - } - } - - if (force) { - for (int i = 0; i < refSpecs.size(); i++) { - refSpecs.set(i, refSpecs.get(i).setForceUpdate(true)); - } - } - - final List<Transport> transports; - transports = Transport.openAll(repo, remoteConfig, Transport.Operation.PUSH); - for (final Transport transport : transports) { - if (0 <= timeout) { - transport.setTimeout(timeout); - } - transport.setPushThin(thin); - if (receivePack != null) { - transport.setOptionReceivePack(receivePack); - } - transport.setDryRun(dryRun); - if (credentialsProvider != null) { - transport.setCredentialsProvider(credentialsProvider); - } - - final Collection<RemoteRefUpdate> toPush = transport - .findRemoteRefUpdatesFor(refSpecs); - - try { - PushResult result = transport.push(monitor, toPush); - pushResults.add(result); - } - catch (TransportException e) { - throw new org.eclipse.jgit.api.errors.TransportException(e.getMessage(), e); - } - finally { - transport.close(); - } - } - } - catch (URISyntaxException e) { - throw new InvalidRemoteException(MessageFormat.format( - JGitText.get().invalidRemote, remote)); - } catch (TransportException e) { - throw new org.eclipse.jgit.api.errors.TransportException( - e.getMessage(), e); - } - catch (NotSupportedException e) { - throw new JGitInternalException( - JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, - e); - } - catch (IOException e) { - throw new JGitInternalException( - JGitText.get().exceptionCaughtDuringExecutionOfPushCommand, - e); - } - - return pushResults; - } - } - - class LsRemote implements GitHttpRemoteCommand { - - private final Git myGit; - private final GitHttpCredentialsProvider myCredentialsProvider; - private String myUrl; - private Collection<Ref> myResultRefs; - - public LsRemote(@NotNull Git git, @NotNull GitHttpCredentialsProvider credentialsProvider, @NotNull String url) { - myGit = git; - myCredentialsProvider = credentialsProvider; - myUrl = url; - } - - @Override - public void run() throws InvalidRemoteException, TransportException { - myResultRefs = call(); - } - - @Override - public void cleanup() { - } - - @Override - public GitHttpCredentialsProvider getCredentialsProvider() { - return myCredentialsProvider; - } - - @Override - public String getLogString() { - return getCommandString(); - } - - @Override - public String getCommandString() { - return String.format("git ls-remote --heads %s ", myUrl); - } - - @Override - public String getUrl() { - return myUrl; - } - - @Override - public void setUrl(@NotNull String url) { - myUrl = url; - } - - @NotNull - public Collection<Ref> getRefs() { - return myResultRefs == null ? Collections.<Ref>emptyList() : myResultRefs; - } - - /* - Copy-paste of org.eclipse.jgit.api.LsRemote#call with the following changes: - 1. More specific exceptions declaration. - 2. Use CredentialsProvider. - 3. We don't need --tags, we always need --heads. - */ - private Collection<Ref> call() throws TransportException, InvalidRemoteException { - try { - Transport transport = Transport.open(myGit.getRepository(), myUrl); - - try { - Collection<RefSpec> refSpecs = new ArrayList<RefSpec>(1); - refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); - Collection<Ref> refs; - Map<String, Ref> refmap = new HashMap<String, Ref>(); - transport.setCredentialsProvider(myCredentialsProvider); - FetchConnection fc = transport.openFetch(); - try { - refs = fc.getRefs(); - if (refSpecs.isEmpty()) { - for (Ref r : refs) { - refmap.put(r.getName(), r); - } - } - else { - for (Ref r : refs) { - for (RefSpec rs : refSpecs) { - if (rs.matchSource(r)) { - refmap.put(r.getName(), r); - break; - } - } - } - } - } - finally { - fc.close(); - } - return refmap.values(); - } - catch (TransportException e) { - throw new JGitInternalException( - JGitText.get().exceptionCaughtDuringExecutionOfLsRemoteCommand, - e); - } - finally { - transport.close(); - } - } - catch (URISyntaxException e) { - throw new InvalidRemoteException(MessageFormat.format( - JGitText.get().invalidRemote, myUrl)); - } - catch (NotSupportedException e) { - throw new JGitInternalException( - JGitText.get().exceptionCaughtDuringExecutionOfLsRemoteCommand, - e); - } - } - } -} - - diff --git a/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java b/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java index 294227718670..f8a05816ce26 100644 --- a/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java +++ b/plugins/git4idea/src/git4idea/merge/GitMergeDialog.java @@ -27,6 +27,7 @@ import git4idea.commands.GitSimpleHandler; import git4idea.i18n.GitBundle; import git4idea.util.GitUIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -88,15 +89,6 @@ public class GitMergeDialog extends DialogWrapper { @NotNull private final Project myProject; private final GitVcs myVcs; - - - /** - * A constructor - * - * @param project a project to select - * @param roots a git repository roots for the project - * @param defaultRoot a guessed default root - */ public GitMergeDialog(@NotNull Project project, List<VirtualFile> roots, VirtualFile defaultRoot) { super(project, true); setTitle(GitBundle.getString("merge.branch.title")); @@ -125,9 +117,6 @@ public class GitMergeDialog extends DialogWrapper { init(); } - /** - * Initialize {@link #myBranchChooser} component - */ private void initBranchChooser() { myBranchChooser = new ElementsChooser<String>(true); myBranchChooser.setToolTipText(GitBundle.getString("merge.branches.tooltip")); @@ -149,6 +138,11 @@ public class GitMergeDialog extends DialogWrapper { myBranchChooser.addElementsMarkListener(listener); } + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myBranchChooser.getComponent(); + } /** * Setup branches for git root, this method should be called when root is changed. @@ -204,32 +198,20 @@ public class GitMergeDialog extends DialogWrapper { } - /** - * {@inheritDoc} - */ protected JComponent createCenterPanel() { return myPanel; } - /** - * {@inheritDoc} - */ @Override protected String getDimensionServiceKey() { return getClass().getName(); } - /** - * {@inheritDoc} - */ @Override protected String getHelpId() { return "reference.VersionControl.Git.MergeBranches"; } - /** - * @return selected root - */ public VirtualFile getSelectedRoot() { return (VirtualFile)myGitRoot.getSelectedItem(); } diff --git a/plugins/git4idea/src/git4idea/push/GitPusher.java b/plugins/git4idea/src/git4idea/push/GitPusher.java index 31e289dba806..778d2dcd0033 100644 --- a/plugins/git4idea/src/git4idea/push/GitPusher.java +++ b/plugins/git4idea/src/git4idea/push/GitPusher.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -36,7 +36,6 @@ import git4idea.config.GitConfigUtil; import git4idea.config.GitVcsSettings; import git4idea.config.UpdateMethod; import git4idea.history.GitHistoryUtils; -import git4idea.jgit.GitHttpAdapter; import git4idea.repo.GitBranchTrackInfo; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; @@ -48,7 +47,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * Collects information to push and performs the push. @@ -321,13 +320,8 @@ public final class GitPusher { } String url = pushUrls.iterator().next(); GitSimplePushResult pushResult; - if (GitHttpAdapter.shouldUseJGit(url)) { - pushResult = GitHttpAdapter.push(repository, remote.getName(), url, formPushSpec(pushSpec, remote)); - } - else { - pushResult = pushNatively(repository, pushSpec, url); - } - + pushResult = pushNatively(repository, pushSpec, url); + if (pushResult.getType() == GitSimplePushResult.Type.SUCCESS) { setUpstream(repository, pushSpec.getSource(), pushSpec.getRemote(), pushSpec.getDest()); } @@ -478,9 +472,7 @@ public final class GitPusher { // and don't show the dialog again if user has chosen not to ask again updateSettings = readUpdateSettings(); if (!mySettings.autoUpdateIfPushRejected()) { - final GitRejectedPushUpdateDialog dialog = new GitRejectedPushUpdateDialog(myProject, rejectedPushesForCurrentBranch.keySet(), updateSettings); - final int exitCode = showDialogAndGetExitCode(dialog); - updateSettings = new UpdateSettings(dialog.shouldUpdateAll(), getUpdateMethodFromDialogExitCode(exitCode)); + updateSettings = showDialogAndGetExitCode(rejectedPushesForCurrentBranch, updateSettings); saveUpdateSettings(updateSettings); } } @@ -519,20 +511,23 @@ public final class GitPusher { return new UpdateSettings(updateAllRoots, updateMethod); } - private int showDialogAndGetExitCode(@NotNull final GitRejectedPushUpdateDialog dialog) { - final AtomicInteger exitCode = new AtomicInteger(); + private UpdateSettings showDialogAndGetExitCode(final Map<GitRepository, GitBranch> rejectedPushesForCurrentBranch, + final UpdateSettings initialSettings) { + final AtomicReference<UpdateSettings> updateSettings = new AtomicReference<UpdateSettings>(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { + final GitRejectedPushUpdateDialog dialog = new GitRejectedPushUpdateDialog(myProject, rejectedPushesForCurrentBranch.keySet(), initialSettings); dialog.show(); - exitCode.set(dialog.getExitCode()); - } + final int exitCode = dialog.getExitCode(); + if (exitCode != DialogWrapper.CANCEL_EXIT_CODE) { + mySettings.setAutoUpdateIfPushRejected(dialog.shouldAutoUpdateInFuture()); + } + updateSettings.set(new UpdateSettings(dialog.shouldUpdateAll(), getUpdateMethodFromDialogExitCode(exitCode))); + + } }); - int code = exitCode.get(); - if (code != DialogWrapper.CANCEL_EXIT_CODE) { - mySettings.setAutoUpdateIfPushRejected(dialog.shouldAutoUpdateInFuture()); - } - return code; + return updateSettings.get(); } /** diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java index e0421b7bf67e..a85a328fc114 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java @@ -128,8 +128,8 @@ public class GitRebaser { return allMerged ? GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS : GitUpdateResult.INCOMPLETE; } else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) { LOG.info("handleRebaseFailure: untracked files would be overwritten by checkout"); - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, - untrackedWouldBeOverwrittenDetector.getFiles(), "rebase", null); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, root, + untrackedWouldBeOverwrittenDetector.getRelativeFilePaths(), "rebase", null); return GitUpdateResult.ERROR; } else { LOG.info("handleRebaseFailure error " + pullHandler.errors()); @@ -382,8 +382,8 @@ public class GitRebaser { return allMerged ? GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS : GitUpdateResult.INCOMPLETE; } else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) { LOG.info("handleRebaseFailure: untracked files would be overwritten by checkout"); - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, - untrackedWouldBeOverwrittenDetector.getFiles(), "rebase", null); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, root, + untrackedWouldBeOverwrittenDetector.getRelativeFilePaths(), "rebase", null); return GitUpdateResult.ERROR; } else { LOG.info("handleRebaseFailure error " + handler.errors()); diff --git a/plugins/git4idea/src/git4idea/jgit/GitHttpAuthDataProvider.java b/plugins/git4idea/src/git4idea/remote/GitHttpAuthDataProvider.java index af4229d3ac25..6c0860ccf20a 100644 --- a/plugins/git4idea/src/git4idea/jgit/GitHttpAuthDataProvider.java +++ b/plugins/git4idea/src/git4idea/remote/GitHttpAuthDataProvider.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package git4idea.jgit; +package git4idea.remote; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.util.AuthData; @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Provides authentication information to the {@link GitHttpAdapter} on attempt to connect an HTTP remote. + * Provides authentication information to the {@link git4idea.commands.GitHttpAuthenticator} on attempt to connect an HTTP remote. * Useful for reusing Github credentials stored in the settings to connect the github remote (IDEA-87530). * * @author Kirill Likhodedov diff --git a/plugins/git4idea/src/git4idea/repo/GitConfig.java b/plugins/git4idea/src/git4idea/repo/GitConfig.java index a88e7754b78e..4b0bdeadc8f9 100644 --- a/plugins/git4idea/src/git4idea/repo/GitConfig.java +++ b/plugins/git4idea/src/git4idea/repo/GitConfig.java @@ -149,7 +149,7 @@ public class GitConfig { ini.load(configFile); } catch (IOException e) { - LOG.error(new RepoStateException("Couldn't load .git/config file at " + configFile.getPath(), e)); + LOG.warn(new RepoStateException("Couldn't load .git/config file at " + configFile.getPath(), e)); return emptyConfig; } diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java index 366bbde88050..5109d0600b69 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryReader.java @@ -221,7 +221,8 @@ class GitRepositoryReader { return hashAndName.name.endsWith(ref); } }); - return hashAndNames.get(0).hash; + HashAndName item = ContainerUtil.getFirstItem(hashAndNames); + return item == null ? null : item.hash; } /** diff --git a/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java b/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java index a0ae58667c70..6bfa7aa090eb 100644 --- a/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java +++ b/plugins/git4idea/src/git4idea/stash/GitShelveChangesSaver.java @@ -107,7 +107,7 @@ public class GitShelveChangesSaver extends GitChangesSaver { @Override protected boolean wereChangesSaved() { - return myShelvedLists != null; + return myShelvedLists != null && !myShelvedLists.isEmpty(); } @Override diff --git a/plugins/git4idea/src/git4idea/ui/ChangesBrowserWithRollback.java b/plugins/git4idea/src/git4idea/ui/ChangesBrowserWithRollback.java new file mode 100644 index 000000000000..a33a7d13704a --- /dev/null +++ b/plugins/git4idea/src/git4idea/ui/ChangesBrowserWithRollback.java @@ -0,0 +1,67 @@ +/* + * 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.ui; + +import com.intellij.openapi.actionSystem.EmptyAction; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.actions.RollbackDialogAction; +import com.intellij.openapi.vcs.changes.ui.ChangesBrowser; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; + +/** + * {@link ChangesBrowser} extension with Rollback/Revert action added to the toolbar. + * After the revert completes, the changes list is automatically refreshed according to the actual changes + * retrieved from the {@link ChangeListManager}. + */ +public class ChangesBrowserWithRollback extends ChangesBrowser { + private final List<Change> myOriginalChanges; + + public ChangesBrowserWithRollback(@NotNull Project project, @NotNull List<Change> changes) { + super(project, null, changes, null, false, true, null, MyUseCase.LOCAL_CHANGES, null); + myOriginalChanges = changes; + RollbackDialogAction rollback = new RollbackDialogAction(); + EmptyAction.setupAction(rollback, IdeActions.CHANGES_VIEW_ROLLBACK, this); + addToolbarAction(rollback); + setChangesToDisplay(changes); + } + + @Override + public void rebuildList() { + if (myOriginalChanges != null) { // null is possible because rebuildList is called during initialization + myChangesToDisplay = filterActualChanges(myProject, myOriginalChanges); + } + super.rebuildList(); + } + + @NotNull + private static List<Change> filterActualChanges(@NotNull Project project, @NotNull List<Change> originalChanges) { + final Collection<Change> allChanges = ChangeListManager.getInstance(project).getAllChanges(); + return ContainerUtil.filter(originalChanges, new Condition<Change>() { + @Override + public boolean value(Change change) { + return allChanges.contains(change); + } + }); + } +} diff --git a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java index 63b51a8c4b56..3c8d7100c50c 100644 --- a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java +++ b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java @@ -29,7 +29,9 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.history.VcsRevisionNumber; @@ -49,6 +51,7 @@ import git4idea.merge.GitConflictResolver; import git4idea.repo.GitRepository; import git4idea.stash.GitStashUtils; import git4idea.util.GitUIUtil; +import git4idea.util.UntrackedFilesNotifier; import git4idea.validators.GitBranchNameValidator; import org.jetbrains.annotations.NotNull; @@ -397,7 +400,7 @@ public class GitUnstashDialog extends DialogWrapper { @Override protected void doOKAction() { VirtualFile root = getGitRoot(); - GitLineHandler h = handler(); + final GitLineHandler h = handler(); final AtomicBoolean conflict = new AtomicBoolean(); h.addLineListener(new GitLineHandlerAdapter() { @@ -407,13 +410,27 @@ public class GitUnstashDialog extends DialogWrapper { } } }); - int rc = GitHandlerUtil.doSynchronously(h, GitBundle.getString("unstash.unstashing"), h.printableCommandLine(), false); - ServiceManager.getService(myProject, GitPlatformFacade.class).hardRefresh(root); + GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector = new GitUntrackedFilesOverwrittenByOperationDetector(root); + h.addLineListener(untrackedFilesDetector); + 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) { + h.addLineListener(new GitHandlerUtil.GitLineHandlerListenerProgress(indicator, h, "stash", false)); + Git git = ServiceManager.getService(Git.class); + result.set(git.runCommand(new Computable.PredefinedValueComputable<GitLineHandler>(h))); + } + }); + + ServiceManager.getService(myProject, GitPlatformFacade.class).hardRefresh(root); + GitCommandResult res = result.get(); if (conflict.get()) { boolean conflictsResolved = new UnstashConflictResolver(myProject, root, getSelectedStash()).merge(); LOG.info("loadRoot " + root + ", conflictsResolved: " + conflictsResolved); - } else if (rc != 0) { + } else if (untrackedFilesDetector.wasMessageDetected()) { + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, root, untrackedFilesDetector.getRelativeFilePaths(), + "unstash", null); + } else if (!res.success()) { GitUIUtil.showOperationErrors(myProject, h.errors(), h.printableCommandLine()); } super.doOKAction(); diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java index 645fb24bcd1e..bd9c8fb3a1b3 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java @@ -29,6 +29,8 @@ import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.util.Condition; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.util.containers.ContainerUtil; +import git4idea.GitLocalBranch; import git4idea.GitUtil; import git4idea.GitVcs; import git4idea.branch.GitBranchUtil; @@ -184,7 +186,7 @@ class GitBranchPopup { GitRepositoryManager repositoryManager = myRepositoryManager; if (repositoryManager.moreThanOneRoot()) { - if (!myMultiRootBranchConfig.diverged() && userWantsSyncControl()) { + if (userWantsSyncControl()) { fillWithCommonRepositoryActions(popupGroup, repositoryManager); } else { @@ -204,26 +206,37 @@ class GitBranchPopup { } private void fillWithCommonRepositoryActions(DefaultActionGroup popupGroup, GitRepositoryManager repositoryManager) { - List<GitRepository> repositories = repositoryManager.getRepositories(); - String currentBranch = myMultiRootBranchConfig.getCurrentBranch(); - assert currentBranch != null : "Current branch can't be null if branches have not diverged"; - popupGroup.add(new GitBranchPopupActions.GitNewBranchAction(myProject, repositories)); + List<GitRepository> allRepositories = repositoryManager.getRepositories(); + popupGroup.add(new GitBranchPopupActions.GitNewBranchAction(myProject, allRepositories)); popupGroup.addAll(createRepositoriesActions()); popupGroup.addSeparator("Common Local Branches"); for (String branch : myMultiRootBranchConfig.getLocalBranches()) { - if (!branch.equals(currentBranch)) { + List<GitRepository> repositories = filterRepositoriesNotOnThisBranch(branch, allRepositories); + if (!repositories.isEmpty()) { popupGroup.add(new GitBranchPopupActions.LocalBranchActions(myProject, repositories, branch, myCurrentRepository)); } } popupGroup.addSeparator("Common Remote Branches"); for (String branch : myMultiRootBranchConfig.getRemoteBranches()) { - popupGroup.add(new GitBranchPopupActions.RemoteBranchActions(myProject, repositories, branch, myCurrentRepository)); + popupGroup.add(new GitBranchPopupActions.RemoteBranchActions(myProject, allRepositories, branch, myCurrentRepository)); } } + @NotNull + private static List<GitRepository> filterRepositoriesNotOnThisBranch(@NotNull final String branch, + @NotNull List<GitRepository> allRepositories) { + return ContainerUtil.filter(allRepositories, new Condition<GitRepository>() { + @Override + public boolean value(GitRepository repository) { + GitLocalBranch currentBranch = repository.getCurrentBranch(); + return currentBranch == null || !branch.equals(currentBranch.getName()); + } + }); + } + private void warnThatBranchesDivergedIfNeeded() { if (myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl()) { myPopup.setWarning("Branches have diverged"); diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitCompareBranchesDialog.java b/plugins/git4idea/src/git4idea/ui/branch/GitCompareBranchesDialog.java index d9cbbd781f1c..89ff9f47268f 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitCompareBranchesDialog.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitCompareBranchesDialog.java @@ -18,7 +18,7 @@ package git4idea.ui.branch; import com.intellij.dvcs.DvcsUtil; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.FrameWrapper; import com.intellij.ui.TabbedPaneImpl; import git4idea.GitUtil; import git4idea.repo.GitRepository; @@ -30,25 +30,22 @@ import javax.swing.*; /** * Dialog for comparing two Git branches. - * @author Kirill Likhodedov */ -public class GitCompareBranchesDialog extends DialogWrapper { +public class GitCompareBranchesDialog extends FrameWrapper { - private final Project myProject; - private final String myBranchName; - private final String myCurrentBranchName; - private final GitCommitCompareInfo myCompareInfo; - private final GitRepository myInitialRepo; - private JPanel myLogPanel; + @NotNull private final Project myProject; + @NotNull private final String myBranchName; + @NotNull private final String myCurrentBranchName; + @NotNull private final GitCommitCompareInfo myCompareInfo; + @NotNull private final JPanel myLogPanel; public GitCompareBranchesDialog(@NotNull Project project, @NotNull String branchName, @NotNull String currentBranchName, @NotNull GitCommitCompareInfo compareInfo, @NotNull GitRepository initialRepo) { - super(project, false); + super(project, GitCompareBranchesDialog.class.getName()); myCurrentBranchName = currentBranchName; myCompareInfo = compareInfo; myProject = project; myBranchName = branchName; - myInitialRepo = initialRepo; String rootString; if (compareInfo.getRepositories().size() == 1 && GitUtil.getRepositoryManager(myProject).moreThanOneRoot()) { @@ -58,13 +55,15 @@ public class GitCompareBranchesDialog extends DialogWrapper { rootString = ""; } setTitle(String.format("Comparing %s with %s%s", currentBranchName, branchName, rootString)); - setModal(false); - init(); + + myLogPanel = new GitCompareBranchesLogPanel(myProject, myBranchName, myCurrentBranchName, myCompareInfo, initialRepo); + setPreferredFocusedComponent(myLogPanel); + setComponent(createCenterPanel()); + closeOnEsc(); } - @Override + @NotNull protected JComponent createCenterPanel() { - myLogPanel = new GitCompareBranchesLogPanel(myProject, myBranchName, myCurrentBranchName, myCompareInfo, myInitialRepo); JPanel diffPanel = new GitCompareBranchesDiffPanel(myProject, myBranchName, myCurrentBranchName, myCompareInfo); TabbedPaneImpl tabbedPane = new TabbedPaneImpl(SwingConstants.TOP); @@ -74,20 +73,4 @@ public class GitCompareBranchesDialog extends DialogWrapper { return tabbedPane; } - // it is information dialog - no need to OK or Cancel. Close the dialog by clicking the cross button or pressing Esc. - @NotNull - @Override - protected Action[] createActions() { - return new Action[0]; - } - - @Override - protected String getDimensionServiceKey() { - return GitCompareBranchesDialog.class.getName(); - } - - @Override - public JComponent getPreferredFocusedComponent() { - return myLogPanel; - } } diff --git a/plugins/git4idea/src/git4idea/update/GitFetcher.java b/plugins/git4idea/src/git4idea/update/GitFetcher.java index 44dbb54343a3..f9c65632d9be 100644 --- a/plugins/git4idea/src/git4idea/update/GitFetcher.java +++ b/plugins/git4idea/src/git4idea/update/GitFetcher.java @@ -34,7 +34,6 @@ import git4idea.commands.Git; import git4idea.commands.GitCommandResult; import git4idea.commands.GitLineHandlerAdapter; import git4idea.commands.GitLineHandlerListener; -import git4idea.jgit.GitHttpAdapter; import git4idea.repo.GitBranchTrackInfo; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; @@ -132,9 +131,6 @@ public class GitFetcher { @NotNull GitRemote remote, @NotNull String url, @Nullable String branch) { - if (GitHttpAdapter.shouldUseJGit(url)) { - return GitHttpAdapter.fetch(repository, remote, url, branch); - } return fetchNatively(repository, remote, url, branch); } @@ -150,9 +146,6 @@ public class GitFetcher { GitRemote remote = fetchParams.getRemote(); String remoteBranch = fetchParams.getRemoteBranch().getNameForRemoteOperations(); String url = fetchParams.getUrl(); - if (GitHttpAdapter.shouldUseJGit(url)) { - return GitHttpAdapter.fetch(repository, remote, url, remoteBranch); - } return fetchNatively(repository, remote, url, remoteBranch); } @@ -191,22 +184,11 @@ public class GitFetcher { LOG.error("URL is null for remote " + remote.getName()); continue; } - if (GitHttpAdapter.shouldUseJGit(url)) { - GitFetchResult res = GitHttpAdapter.fetch(repository, remote, url, null); - res.addPruneInfo(fetchResult.getPrunedRefs()); - fetchResult = res; - myErrors.addAll(fetchResult.getErrors()); - if (!fetchResult.isSuccess()) { - break; - } - } - else { - GitFetchResult res = fetchNatively(repository, remote, url, null); - res.addPruneInfo(fetchResult.getPrunedRefs()); - fetchResult = res; - if (!fetchResult.isSuccess()) { - break; - } + GitFetchResult res = fetchNatively(repository, remote, url, null); + res.addPruneInfo(fetchResult.getPrunedRefs()); + fetchResult = res; + if (!fetchResult.isSuccess()) { + break; } } return fetchResult; diff --git a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java index 4815eb781fce..4315e65227a3 100644 --- a/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java +++ b/plugins/git4idea/src/git4idea/update/GitMergeUpdater.java @@ -119,15 +119,15 @@ public class GitMergeUpdater extends GitUpdater { LOG.info("Local changes would be overwritten by merge"); final List<FilePath> paths = getFilesOverwrittenByMerge(mergeLineListener.getOutput()); final Collection<Change> changes = getLocalChangesFilteredByFiles(paths); - final ChangeListViewerDialog dialog = new ChangeListViewerDialog(myProject, changes, false) { - @Override protected String getDescription() { - return "Your local changes to the following files would be overwritten by merge.<br/>" + - "Please, commit your changes or stash them before you can merge."; - } - }; UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { + ChangeListViewerDialog dialog = new ChangeListViewerDialog(myProject, changes, false) { + @Override protected String getDescription() { + return "Your local changes to the following files would be overwritten by merge.<br/>" + + "Please, commit your changes or stash them before you can merge."; + } + }; dialog.show(); } }); @@ -135,8 +135,9 @@ public class GitMergeUpdater extends GitUpdater { } else if (untrackedFilesWouldBeOverwrittenByMergeDetector.wasMessageDetected()) { LOG.info("handleMergeFailure: untracked files would be overwritten by merge"); - UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, - untrackedFilesWouldBeOverwrittenByMergeDetector.getFiles(), "merge", null); + UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, myRoot, + untrackedFilesWouldBeOverwrittenByMergeDetector.getRelativeFilePaths(), + "merge", null); return GitUpdateResult.ERROR; } else { diff --git a/plugins/git4idea/src/git4idea/util/GitSimplePathsBrowser.java b/plugins/git4idea/src/git4idea/util/GitSimplePathsBrowser.java new file mode 100644 index 000000000000..fc37dcce3cd7 --- /dev/null +++ b/plugins/git4idea/src/git4idea/util/GitSimplePathsBrowser.java @@ -0,0 +1,71 @@ +/* + * 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.util; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.ActionToolbar; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.changes.ui.FilePathChangesTreeList; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.util.Collection; +import java.util.List; + +public class GitSimplePathsBrowser extends JPanel { + + public GitSimplePathsBrowser(@NotNull Project project, @NotNull Collection<String> absolutePaths) { + super(new BorderLayout()); + + FilePathChangesTreeList browser = createBrowser(project, absolutePaths); + ActionToolbar toolbar = createToolbar(browser); + + add(toolbar.getComponent(), BorderLayout.NORTH); + add(browser); + } + + @NotNull + private static FilePathChangesTreeList createBrowser(@NotNull Project project, @NotNull Collection<String> absolutePaths) { + List<FilePath> filePaths = toFilePaths(absolutePaths); + FilePathChangesTreeList browser = new FilePathChangesTreeList(project, filePaths, false, false, null, null); + browser.setChangesToDisplay(filePaths); + return browser; + } + + @NotNull + private static ActionToolbar createToolbar(@NotNull FilePathChangesTreeList browser) { + DefaultActionGroup actionGroup = new DefaultActionGroup(browser.getTreeActions()); + return ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actionGroup, true); + } + + @NotNull + private static List<FilePath> toFilePaths(@NotNull Collection<String> absolutePaths) { + return ContainerUtil.map(absolutePaths, new Function<String, FilePath>() { + @Override + public FilePath fun(String path) { + return new FilePathImpl(new File(path), false); + } + }); + } +} diff --git a/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java b/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java new file mode 100644 index 000000000000..021ce824b9a7 --- /dev/null +++ b/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java @@ -0,0 +1,85 @@ +/* + * 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.util; + +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogBuilder; +import com.intellij.openapi.ui.ex.MultiLineLabel; +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.VirtualFile; +import git4idea.GitUtil; +import git4idea.ui.ChangesBrowserWithRollback; +import org.jetbrains.annotations.NotNull; + +import javax.swing.event.HyperlinkEvent; +import java.util.Collection; +import java.util.List; + +public class LocalChangesWouldBeOverwrittenHelper { + + @NotNull + public static String getErrorNotificationDescription() { + return getErrorDescription(true); + } + + @NotNull + public static String getErrorDialogDescription() { + return getErrorDescription(false); + } + + @NotNull + private static String getErrorDescription(boolean forNotification) { + String line1 = "Your local changes would be overwritten by merge."; + String line2 = "Commit, stash or revert them to proceed."; + if (forNotification) { + return line1 + "<br/>" + line2 + " <a href='view'>View them</a>"; + } + else { + return line1 + "\n" + line2; + } + } + + public static void showErrorNotification(@NotNull final Project project, @NotNull VirtualFile root, @NotNull final String operationName, + @NotNull final Collection<String> relativeFilePaths) { + final Collection<String> absolutePaths = GitUtil.toAbsolute(root, relativeFilePaths); + final List<Change> changes = GitUtil.findLocalChangesForPaths(project, root, absolutePaths, false); + String notificationTitle = "Git " + StringUtil.capitalize(operationName) + " Failed"; + VcsNotifier.getInstance(project).notifyError(notificationTitle, getErrorNotificationDescription(), + new NotificationListener.Adapter() { + @Override + protected void hyperlinkActivated(@NotNull Notification notification, + @NotNull HyperlinkEvent e) { + String title = "Local Changes Prevent from " + StringUtil.capitalize(operationName); + String description = getErrorDialogDescription(); + if (changes.isEmpty()) { + GitUtil.showPathsInDialog(project, absolutePaths, title, description); + } + else { + DialogBuilder builder = new DialogBuilder(project); + builder.setNorthPanel(new MultiLineLabel(description)); + builder.setCenterPanel(new ChangesBrowserWithRollback(project, changes)); + builder.addOkAction(); + builder.setTitle(title); + builder.show(); + } + } + }); + } +} diff --git a/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java b/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java index 6a867933e6aa..382d51b42032 100644 --- a/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java +++ b/plugins/git4idea/src/git4idea/util/UntrackedFilesNotifier.java @@ -18,10 +18,14 @@ package git4idea.util; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,6 +33,7 @@ import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.util.ArrayList; import java.util.Collection; +import java.util.List; public class UntrackedFilesNotifier { @@ -38,24 +43,40 @@ public class UntrackedFilesNotifier { /** * Displays notification about {@code untracked files would be overwritten by checkout} error. * Clicking on the link in the notification opens a simple dialog with the list of these files. + * @param root + * @param relativePaths * @param operation the name of the Git operation that caused the error: {@code rebase, merge, checkout}. * @param description the content of the notification or null if the deafult content is to be used. */ public static void notifyUntrackedFilesOverwrittenBy(@NotNull final Project project, - @NotNull final Collection<VirtualFile> untrackedFiles, + @NotNull final VirtualFile root, @NotNull Collection<String> relativePaths, @NotNull final String operation, @Nullable String description) { final String notificationTitle = StringUtil.capitalize(operation) + " failed"; final String notificationDesc = description == null ? createUntrackedFilesOverwrittenDescription(operation, true) : description; - VcsNotifier.getInstance(project).notifyError(notificationTitle, notificationDesc, - new NotificationListener() { + final Collection<String> absolutePaths = GitUtil.toAbsolute(root, relativePaths); + final List<VirtualFile> untrackedFiles = ContainerUtil.mapNotNull(absolutePaths, new Function<String, VirtualFile>() { + @Override + public VirtualFile fun(String absolutePath) { + return GitUtil.findRefreshFileOrLog(absolutePath); + } + }); + + VcsNotifier.getInstance(project).notifyError(notificationTitle, notificationDesc, new NotificationListener() { @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { final String dialogDesc = createUntrackedFilesOverwrittenDescription(operation, false); - SelectFilesDialog dlg = new UntrackedFilesDialog(project, untrackedFiles, dialogDesc); - dlg.setTitle("Untracked Files Preventing " + StringUtil.capitalize(operation)); - dlg.show(); + String title = "Untracked Files Preventing " + StringUtil.capitalize(operation); + if (untrackedFiles.isEmpty()) { + GitUtil.showPathsInDialog(project, absolutePaths, title, dialogDesc); + } + else { + DialogWrapper dialog; + dialog = new UntrackedFilesDialog(project, untrackedFiles, dialogDesc); + dialog.setTitle(title); + dialog.show(); + } } } }); diff --git a/plugins/git4idea/src/org/hanuna/gitalk/git/reader/util/GitException.java b/plugins/git4idea/src/org/hanuna/gitalk/git/reader/util/GitException.java deleted file mode 100644 index fae6082502c9..000000000000 --- a/plugins/git4idea/src/org/hanuna/gitalk/git/reader/util/GitException.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.hanuna.gitalk.git.reader.util; - -/** - * @author erokhins - */ -public class GitException extends RuntimeException { - public GitException(String message) { - super(message); - } - -} diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy b/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy index e00f0ddafe19..35acc41fc3cc 100644 --- a/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy +++ b/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy @@ -75,10 +75,6 @@ class GitBranchWorkerTest extends GitPlatformTest { } } - public void tearDown() { - super.tearDown(); - } - public void "test create new branch without problems"() { checkoutNewBranch "feature", [ ] @@ -205,7 +201,7 @@ class GitBranchWorkerTest extends GitPlatformTest { boolean notificationShown = false; checkoutOrMerge operation, "feature", [ - showUntrackedFilesNotification : { String s, Collection c -> notificationShown = true } + showUntrackedFilesNotification: { String s, VirtualFile root, Collection c -> notificationShown = true } ] assertTrue "Untracked files notification was not shown", notificationShown @@ -224,15 +220,15 @@ class GitBranchWorkerTest extends GitPlatformTest { def untracked = ["untracked.txt"] untrackedFileOverwrittenBy(myCommunity, "feature", untracked) - Collection<VirtualFile> untrackedFiles = null; + Collection<String> untrackedPaths = null; checkoutOrMerge operation, "feature", [ - showUntrackedFilesDialogWithRollback : { String s, String p, Collection files -> untrackedFiles = files; false } + showUntrackedFilesDialogWithRollback: { + String s, String p, VirtualFile root, Collection<String> files -> untrackedPaths = files; false + } ] - assertTrue "Untracked files dialog was not shown", untrackedFiles != null - assertEquals "Incorrect set of untracked files was shown in the dialog", - untracked, - untrackedFiles.collect { FileUtil.getRelativePath(myCommunity.root.path, it.path, '/'.toCharacter()) } + assertTrue "Untracked files dialog was not shown", untrackedPaths != null + assertEquals "Incorrect set of untracked files was shown in the dialog", untracked.asList(), untrackedPaths.asList() } public void "test checkout with local changes overwritten by checkout should show smart checkout dialog"() { @@ -252,7 +248,7 @@ class GitBranchWorkerTest extends GitPlatformTest { List<Change> changes = null; checkoutOrMerge(operation, "feature", [ - showSmartOperationDialog: { Project p, List<Change> cs, String op, boolean force -> + showSmartOperationDialog: { Project p, List<Change> cs, Collection<String> paths, String op, boolean force -> changes = cs DialogWrapper.CANCEL_EXIT_CODE } @@ -349,7 +345,9 @@ class GitBranchWorkerTest extends GitPlatformTest { prepareLocalChangesOverwrittenBy(myUltimate) checkoutOrMerge(operation, "feature", [ - showSmartOperationDialog : { Project p, List<Change> cs, String op, boolean f -> GitSmartOperationDialog.CANCEL_EXIT_CODE }, + showSmartOperationDialog: { Project p, List<Change> cs, Collection<String> paths, String op, boolean f + -> GitSmartOperationDialog.CANCEL_EXIT_CODE + }, ] as GitBranchUiHandler ) assertNull "Notification was unexpectedly shown:" + myVcsNotifier.lastNotification, myVcsNotifier.lastNotification @@ -372,8 +370,10 @@ class GitBranchWorkerTest extends GitPlatformTest { def rollbackMsg = null checkoutOrMerge(operation, "feature", [ - showSmartOperationDialog : { Project p, List<Change> cs, String op, boolean f -> GitSmartOperationDialog.CANCEL_EXIT_CODE }, - notifyErrorWithRollbackProposal: { String t, String m, String rp -> rollbackMsg = m ; false } + showSmartOperationDialog : { + Project p, List<Change> cs, Collection<String> paths, String op, boolean f -> GitSmartOperationDialog.CANCEL_EXIT_CODE + }, + notifyErrorWithRollbackProposal: { String t, String m, String rp -> rollbackMsg = m; false } ] as GitBranchUiHandler ) assertNotNull "Rollback proposal was not shown", rollbackMsg @@ -384,7 +384,7 @@ class GitBranchWorkerTest extends GitPlatformTest { prepareLocalChangesOverwrittenBy(myUltimate) def uiHandler = [ - showSmartOperationDialog: { Project p, List<Change> cs, String op, boolean force -> + showSmartOperationDialog: { Project p, List<Change> cs, Collection<String> paths, String op, boolean force -> GitSmartOperationDialog.FORCE_EXIT_CODE; }, ] as GitBranchUiHandler @@ -711,7 +711,12 @@ class GitBranchWorkerTest extends GitPlatformTest { } @Override - int showSmartOperationDialog(@NotNull Project project, @NotNull List<Change> changes, @NotNull String operation, boolean force) { + int showSmartOperationDialog( + @NotNull Project project, + @NotNull List<Change> changes, + @NotNull Collection<String> paths, + @NotNull String operation, + boolean isForcePossible) { GitSmartOperationDialog.SMART_EXIT_CODE } @@ -741,12 +746,13 @@ class GitBranchWorkerTest extends GitPlatformTest { } @Override - void showUntrackedFilesNotification(@NotNull String operationName, @NotNull Collection<VirtualFile> untrackedFiles) { + void showUntrackedFilesNotification(@NotNull String operationName, @NotNull VirtualFile root, @NotNull Collection<String> relativePaths) { throw new UnsupportedOperationException() } @Override - boolean showUntrackedFilesDialogWithRollback(@NotNull String operationName, @NotNull String rollbackProposal, @NotNull Collection<VirtualFile> untrackedFiles) { + boolean showUntrackedFilesDialogWithRollback( + @NotNull String operationName, @NotNull String rollbackProposal, @NotNull VirtualFile root, @NotNull Collection<String> relativePaths) { throw new UnsupportedOperationException() } diff --git a/plugins/git4idea/tests/git4idea/checkin/GitCommitAuthorCorrectorTest.java b/plugins/git4idea/tests/git4idea/checkin/GitCommitAuthorCorrectorTest.java new file mode 100644 index 000000000000..9b0789d21809 --- /dev/null +++ b/plugins/git4idea/tests/git4idea/checkin/GitCommitAuthorCorrectorTest.java @@ -0,0 +1,56 @@ +/* + * 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.checkin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class GitCommitAuthorCorrectorTest { + + @Test + public void add_space_before_email_in_brackets() { + assertCorrection("foo<foo@email.com>", "foo <foo@email.com>"); + } + + @Test + public void add_brackets() { + String expected = "John Smith <john.smith@email.com>"; + assertCorrection("John Smith john.smith@email.com", expected); + assertCorrection("John Smith <john.smith@email.com", expected); + assertCorrection("John Smith john.smith@email.com>", expected); + } + + @Test + public void no_correction_needed() { + assertDoNothing("John Smith <john.smith@email.com>"); + } + + @Test + public void correction_not_possible() { + assertDoNothing("foo"); + assertDoNothing("foo bar"); + } + + private static void assertCorrection(String source, String expected) { + assertEquals(expected, GitCommitAuthorCorrector.correct(source)); + } + + private static void assertDoNothing(String source) { + assertCorrection(source, source); + } + +}
\ No newline at end of file diff --git a/plugins/git4idea/tests/git4idea/test/GitPlatformTest.java b/plugins/git4idea/tests/git4idea/test/GitPlatformTest.java index a073ceda0c96..ecef2b2cdcf5 100644 --- a/plugins/git4idea/tests/git4idea/test/GitPlatformTest.java +++ b/plugins/git4idea/tests/git4idea/test/GitPlatformTest.java @@ -125,6 +125,10 @@ public abstract class GitPlatformTest extends UsefulTestCase { myDialogManager.cleanup(); myVcsNotifier.cleanup(); myProjectFixture.tearDown(); + + String tempTestIndicator = myTestStartedIndicator; + clearFields(this); + myTestStartedIndicator = tempTestIndicator; } finally { super.tearDown(); |