IDEA-64835 hg: support auth dialog cancellation

1. HgRemoteChangesetsCommand - common ancestor for HgIncoming/OutgoingCommand. All common functionality moved.
2. HgChangesetsCommand.executeCommand - override for remote commands. No more isAuthenticationNeeded().
3. HgRemoteChangesetsCommand.executeCommand: if user cancelled the dialog, show notification and disable checkboxes for checking incoming/outgoing changes.
4. HgCommandAuthenticator: 3 attempts for authentication error; if other error - return; if auth passed - save credentials and return; if cancelled - return special HgCommandResult.CANCELLED (and show notification in the client - see #3).

TODOs for disabling checkboxes:
1. Disable not for project, but for repository+project.
2. Expire the notification once the problem was fixed.
This commit is contained in:
Kirill Likhodedov
2011-02-07 18:24:47 +03:00
parent d477758b63
commit 5b098b7afd
7 changed files with 109 additions and 71 deletions
@@ -47,12 +47,7 @@ import com.intellij.util.containers.Convertor;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.provider.HgCachingCommitedChangesProvider;
import org.zmlx.hg4idea.provider.HgChangeProvider;
import org.zmlx.hg4idea.provider.HgDiffProvider;
import org.zmlx.hg4idea.provider.HgHistoryProvider;
import org.zmlx.hg4idea.provider.HgMergeProvider;
import org.zmlx.hg4idea.provider.HgRollbackEnvironment;
import org.zmlx.hg4idea.provider.*;
import org.zmlx.hg4idea.provider.annotate.HgAnnotationProvider;
import org.zmlx.hg4idea.provider.commit.HgCheckinEnvironment;
import org.zmlx.hg4idea.provider.update.HgIntegrateEnvironment;
@@ -136,6 +131,10 @@ public class HgVcs extends AbstractVcs<CommittedChangeList> {
return new HgProjectConfigurable(projectSettings);
}
public HgProjectSettings getProjectSettings() {
return projectSettings;
}
@Override
public ChangeProvider getChangeProvider() {
return changeProvider;
@@ -16,6 +16,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.HgRevisionNumber;
import java.nio.charset.Charset;
@@ -33,8 +34,8 @@ public abstract class HgChangesetsCommand {
private static final String SEPARATOR_STRING = "\u0017"; //ascii: end of transmission block
private final Project project;
private final String command;
protected final Project project;
protected final String command;
public HgChangesetsCommand(Project project, String command) {
this.project = project;
@@ -46,9 +47,6 @@ public abstract class HgChangesetsCommand {
}
protected List<HgRevisionNumber> getRevisions(VirtualFile repo) {
HgCommandService commandService = HgCommandService.getInstance(project);
List<String> args = new ArrayList<String>(Arrays.asList(
"--template",
"{rev}|{node|short}|{author}|{desc|firstline}" + SEPARATOR_STRING,
@@ -57,13 +55,7 @@ public abstract class HgChangesetsCommand {
addArguments(args);
HgCommandResult result;
if (isAuthenticationNeeded()) {
String repositoryURL = new HgShowConfigCommand(project).getDefaultPath(repo);
result = new HgCommandAuthenticator().executeCommandAndAuthenticateIfNecessary(project, repo, repositoryURL, command, args, args.size()-1);
} else {
result = commandService.execute(repo, HgCommandService.DEFAULT_OPTIONS, command, args, Charset.defaultCharset(), isSilentCommand());
}
HgCommandResult result = executeCommand(repo, args);
if (result == null) {
return Collections.emptyList();
@@ -89,14 +81,12 @@ public abstract class HgChangesetsCommand {
return revisions;
}
protected boolean isSilentCommand() {
return false;
@Nullable
protected HgCommandResult executeCommand(VirtualFile repo, List<String> args) {
return HgCommandService.getInstance(project).execute(repo, HgCommandService.DEFAULT_OPTIONS, command, args, Charset.defaultCharset(), isSilentCommand());
}
/**
* Return false for local operations, true - for remote, which may require authenticate on the server.
*/
protected boolean isAuthenticationNeeded() {
protected boolean isSilentCommand() {
return false;
}
@@ -33,6 +33,7 @@ import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import static org.zmlx.hg4idea.command.HgErrorUtil.isAbort;
import static org.zmlx.hg4idea.command.HgErrorUtil.isAuthorizationError;
/**
@@ -67,23 +68,27 @@ class HgCommandAuthenticator {
HgUrl hgUrl = new HgUrl(remoteRepository);
if (hgUrl.supportsAuthentication()) {
final GetPasswordRunnable runnable = new GetPasswordRunnable(project, hgUrl);
// first time try to get info from password safe if it's there, or show auth dialog if not
result = tryToAuthenticate(project, localRepository, hgUrl, runnable, command, arguments, urlArgumentPosition);
if (!isAuthorizationError(result)) {
saveCredentials(project, runnable);
} else {
// then twice request auth info from user
runnable.setForceShowDialog(true);
for (int i = 0; i < 2; i++) {
result = tryToAuthenticate(project, localRepository, hgUrl, runnable, command, arguments, urlArgumentPosition);
if (!isAuthorizationError(result)) {
saveCredentials(project, runnable);
for (int i = 0; i < 3; i++) {
if (i == 1) {
runnable.setForceShowDialog(true); // first time try to get info from password safe if it's there, next time don't even try,
// because it means that the saved data didn't pass the authentication
}
result = tryToAuthenticate(project, localRepository, hgUrl, runnable, command, arguments, urlArgumentPosition);
if (result == HgCommandResult.CANCELLED) {
return result;
}
if (isAbort(result)) {
if (isAuthorizationError(result)) {
continue;
} else {
return result;
}
}
HgUtil.notifyError(project, "Authentication failed", "Authentication to " + remoteRepository + " failed");
saveCredentials(project, runnable);
return result;
}
HgUtil.notifyError(project, "Authentication failed", "Authentication to " + remoteRepository + " failed");
return result;
} else {
HgUtil.notifyError(project, "Authentication error", "Authentication was requested, but " + hgUrl.getScheme() + " doesn't support it.");
}
@@ -106,6 +111,8 @@ class HgCommandAuthenticator {
if (runnable.isOk()) {
hgUrl.setUsername(runnable.getUserName());
hgUrl.setPassword(String.valueOf(runnable.getPassword()));
} else {
return HgCommandResult.CANCELLED;
}
arguments.set(urlArgumentPosition, hgUrl.asString());
@@ -20,7 +20,8 @@ import java.util.List;
public final class HgCommandResult {
static final HgCommandResult EMPTY = new HgCommandResult(new StringWriter(), new StringWriter(), 0);
public static final HgCommandResult EMPTY = new HgCommandResult(new StringWriter(), new StringWriter(), 0);
public static final HgCommandResult CANCELLED = new HgCommandResult(new StringWriter(), new StringWriter(), 1);
private final StringWriter out;
private final StringWriter err;
@@ -14,26 +14,10 @@ package org.zmlx.hg4idea.command;
import com.intellij.openapi.project.Project;
import java.util.List;
public class HgIncomingCommand extends HgChangesetsCommand {
public class HgIncomingCommand extends HgRemoteChangesetsCommand {
public HgIncomingCommand(Project project) {
super(project, "incoming");
}
@Override
protected void addArguments(List<String> args) {
args.add("--newest-first");
}
@Override
protected boolean isSilentCommand() {
return true;
}
@Override
protected boolean isAuthenticationNeeded() {
return true;
}
}
@@ -14,26 +14,10 @@ package org.zmlx.hg4idea.command;
import com.intellij.openapi.project.Project;
import java.util.List;
public class HgOutgoingCommand extends HgChangesetsCommand {
public class HgOutgoingCommand extends HgRemoteChangesetsCommand {
public HgOutgoingCommand(Project project) {
super(project, "outgoing");
}
@Override
protected void addArguments(List<String> args) {
args.add("--newest-first");
}
@Override
protected boolean isSilentCommand() {
return true;
}
@Override
protected boolean isAuthenticationNeeded() {
return true;
}
}
@@ -0,0 +1,73 @@
/*
* 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 org.zmlx.hg4idea.command;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.zmlx.hg4idea.HgProjectSettings;
import org.zmlx.hg4idea.HgVcs;
import javax.swing.event.HyperlinkEvent;
import java.util.List;
/**
* Common ancestor for HgIncomingCommand and HgOutgoingCommand - changeset commands which need connection to the server.
* @author Kirill Likhodedov
*/
public abstract class HgRemoteChangesetsCommand extends HgChangesetsCommand {
public HgRemoteChangesetsCommand(Project project, String command) {
super(project, command);
}
@Override
protected void addArguments(List<String> args) {
args.add("--newest-first");
}
@Override
protected boolean isSilentCommand() {
return true;
}
@Override
protected HgCommandResult executeCommand(VirtualFile repo, List<String> args) {
String repositoryURL = new HgShowConfigCommand(project).getDefaultPath(repo);
HgCommandResult result = new HgCommandAuthenticator().executeCommandAndAuthenticateIfNecessary(project, repo, repositoryURL, command, args, args.size()-1);
if (result == HgCommandResult.CANCELLED) {
final HgVcs vcs = HgVcs.getInstance(project);
Notifications.Bus.notify(new Notification(HgVcs.NOTIFICATION_GROUP_ID, "Checking for incoming/outgoing changes disabled",
"Authentication is required to check incoming/outgoing changes in " + repositoryURL +
"<br/>You may enable checking for changes <a href='#'>in the Settings</a>."
, NotificationType.ERROR, new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
ShowSettingsUtil.getInstance().showSettingsDialog(project, vcs.getConfigurable());
}
}), project);
final HgProjectSettings projectSettings = vcs.getProjectSettings();
projectSettings.setCheckIncoming(false);
projectSettings.setCheckOutgoing(false);
}
return result;
}
}