[git] IDEA-102778 Remove unused JGit

* Remove "shouldUseJGit" branches in the Git integration code.
* Remove JGit adapter classes.
* Move GitHttpAuthDataProvider to a more appropriate package.
* Remove jgit lib.
This commit is contained in:
Kirill Likhodedov
2014-06-24 16:57:14 +04:00
parent e4996c88b6
commit 91c86c3799
14 changed files with 14 additions and 1300 deletions
-11
View File
@@ -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" />
+1 -1
View File
@@ -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>
@@ -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
@@ -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;
}
}
@@ -32,7 +32,7 @@ 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;
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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);
}
}
}
}
@@ -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;
@@ -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());
}
@@ -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
@@ -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;
@@ -17,7 +17,7 @@ package org.jetbrains.plugins.github.extensions;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.AuthData;
import git4idea.jgit.GitHttpAuthDataProvider;
import git4idea.remote.GitHttpAuthDataProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.util.GithubAuthData;