Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ public void testCurrentUserComesFromUsernameHeader() throws IOException {
not(hasItem(containsString("users/current")))); //$NON-NLS-1$
}

@Test
public void testContextPathIsDetectedAutomatically() throws IOException {
String contextProperties = "/bitbucket" + PROPERTIES_PATH; //$NON-NLS-1$
server.on(contextProperties, new Response(200, "OK", //$NON-NLS-1$
"{\"version\":\"9.4.2\"}") //$NON-NLS-1$
.header("X-AUSERNAME", "john.doe")); //$NON-NLS-1$ //$NON-NLS-2$

assertThat(client().getCurrentUser(), equalTo("john.doe")); //$NON-NLS-1$
assertThat(server.requestedPaths(),
hasItem(equalTo(contextProperties)));
}

@Test
public void testCurrentUserIsUrlDecoded() throws IOException {
serveApplicationProperties("john.doe%40example.com"); //$NON-NLS-1$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;

import java.util.List;

import org.junit.Test;

/**
Expand All @@ -28,6 +31,26 @@ public void testClientConstruction() {
assertThat(client, notNullValue());
}

@Test
public void testTokenOnlyClientConstruction() {
assertThat(new GitHubClient("test-token"), notNullValue()); //$NON-NLS-1$
}

@Test
public void testParsePullRequestPathsFromSearch() {
String json = "{\"total_count\":2,\"items\":[" //$NON-NLS-1$
+ "{\"pull_request\":{\"url\":\"https://api.github.com/repos/alice/one/pulls/7\"}}," //$NON-NLS-1$
+ "{\"pull_request\":{\"url\":\"https://api.github.com/repos/acme/two/pulls/12\"}}" //$NON-NLS-1$
+ "]}"; //$NON-NLS-1$

List<String> paths = GitHubJsonParser
.parseSearchPullRequestPaths(json);

assertThat(paths, hasSize(2));
assertThat(paths.get(0), equalTo("/repos/alice/one/pulls/7")); //$NON-NLS-1$
assertThat(paths.get(1), equalTo("/repos/acme/two/pulls/12")); //$NON-NLS-1$
}

@Test
public void testExtractStringFromGitHubJsonParser() {
String json = "{\"state\":\"APPROVED\",\"id\":123}"; //$NON-NLS-1$
Expand Down
1 change: 1 addition & 0 deletions org.eclipse.egit.pullrequest/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Import-Package: org.eclipse.core.resources.mapping,
org.eclipse.egit.ui.internal.synchronize;version="[6.0.0,8.0.0)",
org.eclipse.egit.ui.internal.synchronize.model;version="[6.0.0,8.0.0)",
org.eclipse.jgit.annotations;version="[6.0.0,8.0.0)",
org.eclipse.jgit.api;version="[6.0.0,8.0.0)",
org.eclipse.jgit.lib;version="[6.0.0,8.0.0)",
org.eclipse.jgit.revwalk;version="[6.0.0,8.0.0)",
org.eclipse.jgit.transport;version="[6.0.0,8.0.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,27 @@ public class PRText extends NLS {
/** */
public static String PullRequestSynchronizeLauncher_NoProjectsMessage;

/** */
public static String CloneProject_Button;

/** */
public static String CloneProject_Cancel;

/** */
public static String CloneProject_JobName;

/** */
public static String CloneProject_ErrorTitle;

/** */
public static String CloneProject_Error;

/** */
public static String CloneProject_MissingUrl;

/** */
public static String CloneProject_DestinationExists;

/** */
public static String PreferencePage_DiagnosticsGroup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ public class BitbucketClient implements IPullRequestClient {

private static final int MAX_REPORTED_BODY = 400;

private final String serverUrl;
private final String configuredServerUrl;

private volatile String serverUrl;

private volatile boolean contextPathResolved;

private final String projectKey;

Expand All @@ -85,7 +89,8 @@ public class BitbucketClient implements IPullRequestClient {
public BitbucketClient(@NonNull String serverUrl,
@NonNull String projectKey, @NonNull String repositorySlug,
@NonNull String token) {
this.serverUrl = serverUrl.endsWith("/") ? serverUrl.substring(0, serverUrl.length() - 1) : serverUrl; //$NON-NLS-1$
this.serverUrl = trimTrailingSlash(serverUrl);
this.configuredServerUrl = this.serverUrl;
this.projectKey = projectKey;
this.repositorySlug = repositorySlug;
this.token = token;
Expand Down Expand Up @@ -687,7 +692,9 @@ public List<PullRequestCommit> getPullRequestCommits(long pullRequestId)
*/
private HttpURLConnection openConnection(String urlString, String method,
String accept) throws IOException {
URL url = new URL(urlString);
resolveContextPath();
String resolvedUrl = replaceConfiguredServerUrl(urlString);
URL url = new URL(resolvedUrl);
Proxy proxy = HttpProxySupport.select(url);
HttpURLConnection connection = (HttpURLConnection) (proxy == null
? url.openConnection()
Expand All @@ -697,11 +704,114 @@ private HttpURLConnection openConnection(String urlString, String method,
connection.setRequestProperty("Authorization", "Bearer " + token); //$NON-NLS-1$ //$NON-NLS-2$
connection.setConnectTimeout(DEFAULT_TIMEOUT);
connection.setReadTimeout(DEFAULT_TIMEOUT);
Activator.logDebug(method + ' ' + urlString
Activator.logDebug(method + ' ' + resolvedUrl
+ (proxy == null ? "" : " (via " + proxy + ')')); //$NON-NLS-1$ //$NON-NLS-2$
return connection;
}

private synchronized void resolveContextPath() {
if (contextPathResolved) {
return;
}
contextPathResolved = true;

Probe root = probeServerBase(configuredServerUrl);
if (root.status == HttpURLConnection.HTTP_OK
|| root.status == HttpURLConnection.HTTP_UNAUTHORIZED
|| root.status == HttpURLConnection.HTTP_FORBIDDEN) {
return;
}

String redirectedBase = contextBaseFromRedirect(root.location);
if (redirectedBase != null
&& isBitbucketApi(redirectedBase)) {
serverUrl = redirectedBase;
return;
}

String conventionalBase = configuredServerUrl + "/bitbucket"; //$NON-NLS-1$
if (isBitbucketApi(conventionalBase)) {
serverUrl = conventionalBase;
Activator.logInfo("Detected Bitbucket context path: " + serverUrl); //$NON-NLS-1$
}
}

private boolean isBitbucketApi(String baseUrl) {
Probe probe = probeServerBase(baseUrl);
return probe.status == HttpURLConnection.HTTP_OK
|| probe.status == HttpURLConnection.HTTP_UNAUTHORIZED
|| probe.status == HttpURLConnection.HTTP_FORBIDDEN;
}

private Probe probeServerBase(String baseUrl) {
Probe result = new Probe();
HttpURLConnection connection = null;
try {
URL url = new URL(baseUrl + API_BASE_PATH
+ "/application-properties"); //$NON-NLS-1$
Proxy proxy = HttpProxySupport.select(url);
connection = (HttpURLConnection) (proxy == null
? url.openConnection()
: url.openConnection(proxy));
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("GET"); //$NON-NLS-1$
connection.setRequestProperty("Accept", "application/json"); //$NON-NLS-1$ //$NON-NLS-2$
connection.setRequestProperty("Authorization", //$NON-NLS-1$
"Bearer " + token); //$NON-NLS-1$
connection.setConnectTimeout(PROBE_TIMEOUT);
connection.setReadTimeout(PROBE_TIMEOUT);
result.status = connection.getResponseCode();
result.location = connection.getHeaderField("Location"); //$NON-NLS-1$
} catch (IOException e) {
result.failure = e.getMessage();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return result;
}

private String contextBaseFromRedirect(String location) {
if (location == null || location.isBlank()) {
return null;
}
try {
URI configured = URI.create(configuredServerUrl);
URI redirect = configured.resolve(location);
if (!configured.getHost().equalsIgnoreCase(redirect.getHost())) {
return null;
}
String path = redirect.getPath();
if (path == null || path.length() < 2) {
return null;
}
int secondSlash = path.indexOf('/', 1);
String context = secondSlash < 0 ? path
: path.substring(0, secondSlash);
return trimTrailingSlash(configuredServerUrl + context);
} catch (IllegalArgumentException e) {
return null;
}
}

private String replaceConfiguredServerUrl(String urlString) {
if (!serverUrl.equals(configuredServerUrl)
&& urlString.startsWith(configuredServerUrl)) {
return serverUrl
+ urlString.substring(configuredServerUrl.length());
}
return urlString;
}

private static String trimTrailingSlash(String url) {
String result = url.trim();
while (result.endsWith("/")) { //$NON-NLS-1$
result = result.substring(0, result.length() - 1);
}
return result;
}

/**
* Reads the status code, translating transport level failures into a
* message that names the likely cause.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@
public interface IPullRequestClient {

/**
* Retrieves pull requests for the configured repository
* Selects the repository associated with a pull request. Repository-scoped
* providers may ignore this; providers that aggregate several repositories
* use it to route subsequent operations.
*
* @param pullRequest
* the pull request that subsequent operations concern
*/
default void setActivePullRequest(PullRequest pullRequest) {
// Most provider clients are permanently scoped to one repository.
}

/**
* Retrieves pull requests visible in the configured provider scope.
*
* @param state
* the PR state filter (e.g., "OPEN", "MERGED", "DECLINED" for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.eclipse.egit.pullrequest.internal.PRPreferences;
import org.eclipse.egit.pullrequest.internal.bitbucket.BitbucketClient;
import org.eclipse.egit.pullrequest.internal.github.GitHubClient;
import org.eclipse.egit.pullrequest.internal.model.PullRequest;

/**
* Factory for creating pull request client instances based on configured
Expand Down Expand Up @@ -80,6 +81,21 @@ public static IPullRequestClient createClient() {
return client;
}

/**
* Creates a client and selects the repository for a pull request.
*
* @param pullRequest
* pull request that subsequent client operations concern
* @return the configured client, or null if not properly configured
*/
public static IPullRequestClient createClient(PullRequest pullRequest) {
IPullRequestClient client = createClient();
if (client != null) {
client.setActivePullRequest(pullRequest);
}
return client;
}

/**
* Describes which configuration values are present, without revealing the
* access tokens.
Expand All @@ -90,8 +106,7 @@ public static IPullRequestClient createClient() {
*/
private static String describe(ClientConfig config) {
if (config.providerType == PullRequestProviderType.GITHUB) {
return "owner=" + quote(config.githubOwner) + ", repository=" //$NON-NLS-1$ //$NON-NLS-2$
+ quote(config.githubRepo) + ", token=" //$NON-NLS-1$
return "token=" //$NON-NLS-1$
+ (isBlank(config.githubAccessToken) ? "missing" : "set"); //$NON-NLS-1$ //$NON-NLS-2$
}
return "server URL=" + quote(config.bitbucketServerUrl) //$NON-NLS-1$
Expand Down Expand Up @@ -130,12 +145,10 @@ public static IPullRequestClient createClient(ClientConfig config) {
config.bitbucketAccessToken);

case GITHUB:
if (isBlank(config.githubOwner) || isBlank(config.githubRepo)
|| isBlank(config.githubAccessToken)) {
if (isBlank(config.githubAccessToken)) {
return null;
}
return new GitHubClient(config.githubOwner, config.githubRepo,
config.githubAccessToken);
return new GitHubClient(config.githubAccessToken);

default:
return null;
Expand Down
Loading