IDEA-78086 Git: Push to HTTP/HTTPS repositories doesn't work

Root cause:
1. git push <url> <spec> used incorrect fetch spec (containing refs/remotes/...)
2. git push <url> doesn't update remote ref

Solution:
Instead of using PushCommand.call use the almost copy of this method, but calling Transport.openAll not with remote name, but with pre-generated remote config.
 This remote config is a default remote config read from .git/config, with the change in URL.

[reviewed by irengrig]
This commit is contained in:
Kirill Likhodedov
2011-12-05 17:59:56 +03:00
parent 508b055ed6
commit 99cd41e323
2 changed files with 148 additions and 15 deletions

View File

@@ -42,6 +42,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.ProxySelector;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@@ -91,6 +92,10 @@ public final class GitHttpAdapter {
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);
}
@@ -121,7 +126,7 @@ public final class GitHttpAdapter {
try {
final Git git = convertToGit(repository);
final GitHttpCredentialsProvider provider = new GitHttpCredentialsProvider(repository.getProject(), remoteUrl);
GitHttpRemoteCommand.Push pushCommand = new GitHttpRemoteCommand.Push(git, provider, remoteUrl, convertRefSpecs(remote.getPushRefSpecs()));
GitHttpRemoteCommand.Push pushCommand = new GitHttpRemoteCommand.Push(git, provider, remote.getName(), remoteUrl);
GeneralResult result = callWithAuthRetry(pushCommand);
GitSimplePushResult pushResult = pushCommand.getResult();
if (pushResult == null) {
@@ -144,6 +149,10 @@ public final class GitHttpAdapter {
logException(repository, remote.getName(), remoteUrl, e, "pushing");
return makeErrorResultFromException(e);
}
catch (URISyntaxException e) {
logException(repository, remote.getName(), remoteUrl, e, "pushing");
return makeErrorResultFromException(e);
}
}
@NotNull
@@ -162,6 +171,10 @@ public final class GitHttpAdapter {
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);
}
return new GitFetchResult(resultType);
}
@@ -191,7 +204,7 @@ public final class GitHttpAdapter {
* 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) throws InvalidRemoteException, IOException {
private static GeneralResult callWithAuthRetry(@NotNull GitHttpRemoteCommand command) throws InvalidRemoteException, IOException, URISyntaxException {
ProxySelector defaultProxySelector = ProxySelector.getDefault();
if (GitHttpProxySupport.shouldUseProxy()) {
ProxySelector.setDefault(GitHttpProxySupport.newProxySelector());

View File

@@ -17,18 +17,27 @@ package git4idea.jgit;
import com.intellij.openapi.util.io.FileUtil;
import git4idea.push.GitSimplePushResult;
import org.eclipse.jgit.JGitText;
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.InvalidRemoteException;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
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.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -40,7 +49,7 @@ interface GitHttpRemoteCommand {
String getUrl();
void setUrl(String url);
void run() throws InvalidRemoteException;
void run() throws InvalidRemoteException, URISyntaxException;
void cleanup();
GitHttpCredentialsProvider getCredentialsProvider();
@@ -136,25 +145,41 @@ interface GitHttpRemoteCommand {
private final Git myGit;
private final GitHttpCredentialsProvider myCredentialsProvider;
private GitSimplePushResult myPushResult;
private String myRemoteName;
private String myUrl;
private final List<RefSpec> myRefSpecs;
Push(@NotNull Git git, @NotNull GitHttpCredentialsProvider credentialsProvider, String url, List<RefSpec> refSpecs) {
Push(@NotNull Git git, @NotNull GitHttpCredentialsProvider credentialsProvider, @NotNull String remoteName, @NotNull String url) {
myGit = git;
myCredentialsProvider = credentialsProvider;
myRemoteName = remoteName;
myUrl = url;
myRefSpecs = refSpecs;
}
@Override
public void run() throws InvalidRemoteException {
public void run() throws InvalidRemoteException, URISyntaxException {
PushCommand pushCommand = myGit.push();
if (myUrl != null) {
pushCommand.setRemote(myUrl);
pushCommand.setRefSpecs(myRefSpecs);
}
pushCommand.setRemote(myRemoteName);
pushCommand.setCredentialsProvider(myCredentialsProvider);
Iterable<PushResult> results = pushCommand.call();
/*
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);
}
@@ -214,6 +239,101 @@ interface GitHttpRemoteCommand {
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 {
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 JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
e);
}
finally {
transport.close();
}
}
}
catch (URISyntaxException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote));
}
catch (NotSupportedException e) {
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
e);
}
catch (IOException e) {
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfPushCommand,
e);
}
return pushResults;
}
}
}