Avoid refresh on up-to-date pull operation
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / op / ConfigureGerritAfterCloneTask.java
blob5c106cd39a568eda2cf6d8df39ecfc8089a4421a
1 /*******************************************************************************
2 * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
3 * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
5 * All rights reserved. This program and the accompanying materials
6 * are made available under the terms of the Eclipse Public License 2.0
7 * which accompanies this distribution, and is available at
8 * https://www.eclipse.org/legal/epl-2.0/
10 * SPDX-License-Identifier: EPL-2.0
11 *******************************************************************************/
12 package org.eclipse.egit.core.op;
14 import java.io.ByteArrayOutputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.net.HttpURLConnection;
18 import java.net.MalformedURLException;
19 import java.net.URISyntaxException;
20 import java.net.URL;
21 import java.util.regex.Pattern;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IProgressMonitor;
25 import org.eclipse.egit.core.Activator;
26 import org.eclipse.egit.core.NetUtil;
27 import org.eclipse.egit.core.internal.gerrit.GerritUtil;
28 import org.eclipse.egit.core.op.CloneOperation.PostCloneTask;
29 import org.eclipse.jgit.errors.CommandFailedException;
30 import org.eclipse.jgit.lib.Constants;
31 import org.eclipse.jgit.lib.Repository;
32 import org.eclipse.jgit.lib.StoredConfig;
33 import org.eclipse.jgit.transport.CredentialsProvider;
34 import org.eclipse.jgit.transport.RemoteConfig;
35 import org.eclipse.jgit.transport.URIish;
36 import org.eclipse.jgit.util.SshSupport;
38 /**
39 * Configure Gerrit if repository was cloned from a Gerrit server
41 public class ConfigureGerritAfterCloneTask implements PostCloneTask {
43 private static final String GIT_ECLIPSE_ORG = "git.eclipse.org"; //$NON-NLS-1$
45 private static final String GERRIT_CONTEXT_ROOT = "/r/"; //$NON-NLS-1$
47 private static final String HTTP = "http"; //$NON-NLS-1$
49 private static final String HTTPS = "https"; //$NON-NLS-1$
51 private static final String SSH = "ssh"; //$NON-NLS-1$
53 private static final String GERRIT_CONFIG_SERVER_VERSION_API = "/config/server/version"; //$NON-NLS-1$
55 private static final int GERRIT_SSHD_DEFAULT_PORT = 29418;
57 private static final String GERRIT_SSHD_VERSION_API = "gerrit version"; //$NON-NLS-1$
59 /**
60 * Pattern to match the sshd reply[1] against to determine whether it's a
61 * Gerrit.
62 * <p>
63 * [1]<a href=
64 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-version.html">
65 * Gerrit 2.11 gerrit version ssh command</a>
66 * </p>
67 * <p>
68 * We match the whole reply from Gerrit's sshd (as opposed to a prefix match
69 * for "gerrit version") just in case a non-Gerrit has the great idea to
70 * return an error message like "gerrit version: unknown command" or some
71 * such on its stdout...
72 * </p>
74 private static final Pattern GERRIT_SSHD_REPLY = Pattern
75 .compile(GERRIT_SSHD_VERSION_API
76 + "\\s+(?:\\d+(?:\\.\\d+)+|.+-\\d+-g[0-9a-fA-F]{7,})"); //$NON-NLS-1$
78 /**
79 * To prevent against Cross Site Script Inclusion (XSSI) attacks, the Gerrit
80 * JSON response body starts with a magic prefix line we can use as a second
81 * marker beyond the get version endpoint [1] to detect a Gerrit server
82 * <p/>
83 * [1] <a href=
84 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
85 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
86 * endpoint</a>
88 private static final String GERRIT_XSSI_MAGIC_STRING = ")]}\'\n"; //$NON-NLS-1$
90 private final String uri;
92 private final String remoteName;
94 private final CredentialsProvider credentialsProvider;
96 private int timeout;
98 /**
99 * @param uri
100 * not null
101 * @param remoteName
102 * not null
103 * @param credentialsProvider
104 * {@link CredentialsProvider} to use for remote communication;
105 * if {@code null} auto-configuration for repositories cloned
106 * over ssh will work only for git.eclipse.org
107 * @param timeout
108 * timeout for remote communication in seconds
110 public ConfigureGerritAfterCloneTask(String uri, String remoteName,
111 CredentialsProvider credentialsProvider, int timeout) {
112 this.uri = uri;
113 this.remoteName = remoteName;
114 this.timeout = timeout;
115 this.credentialsProvider = credentialsProvider;
118 @Override
119 public void execute(Repository repository, IProgressMonitor monitor)
120 throws CoreException {
121 try {
122 if (isGerrit(repository)) {
123 Activator.logInfo(uri
124 + " was detected to be hosted by a Gerrit server"); //$NON-NLS-1$
125 configureGerrit(repository);
127 } catch (Exception e) {
128 throw new CoreException(Activator.error(e.getMessage(), e));
133 * Try to use Gerrit's "Get Version" REST API endpoint [1] to detect if the
134 * repository is hosted on a Gerrit server.
135 * <p/>
136 * [1] <a href=
137 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
138 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
139 * endpoint</a>
141 * @param repo
142 * the repository to be configured
144 * @return {@code true} if the repository is hosted on a Gerrit server
145 * @throws IOException
146 * @throws MalformedURLException
147 * @throws URISyntaxException
149 private boolean isGerrit(Repository repo) throws MalformedURLException,
150 IOException,
151 URISyntaxException {
152 URIish u = new URIish(uri);
153 final String s = u.getScheme();
154 final String host = u.getHost();
155 final String path = u.getPath();
157 // shortcut for Eclipse Gerrit server
158 if (host != null && host.equals(GIT_ECLIPSE_ORG)) {
159 if (HTTPS.equals(s) && (u.getPort() == 443 || u.getPort() == -1)
160 && path != null && path.startsWith(GERRIT_CONTEXT_ROOT)) {
161 return true;
162 } else if (SSH.equals(s)
163 && u.getPort() == GERRIT_SSHD_DEFAULT_PORT) {
164 return true;
168 if (path != null && (HTTP.equals(s) || HTTPS.equals(s))) {
169 String baseURL = u.setPath("/").toString(); //$NON-NLS-1$
170 baseURL = baseURL.substring(0, baseURL.length() - 1);
171 String tmpPath = ""; //$NON-NLS-1$
172 int slash = 1;
173 while (true) {
174 HttpURLConnection httpConnection = null;
175 try {
176 httpConnection = (HttpURLConnection) new URL(baseURL
177 + tmpPath + GERRIT_CONFIG_SERVER_VERSION_API)
178 .openConnection();
179 NetUtil.setSslVerification(repo, httpConnection);
180 httpConnection.setRequestMethod("GET"); //$NON-NLS-1$
181 httpConnection.setReadTimeout(1000 * timeout);
182 int responseCode = httpConnection.getResponseCode();
183 switch (responseCode) {
184 case HttpURLConnection.HTTP_OK:
185 try (InputStream in = httpConnection.getInputStream()) {
186 String response = readFully(in, "UTF-8"); //$NON-NLS-1$
187 return response
188 .startsWith(GERRIT_XSSI_MAGIC_STRING);
190 case HttpURLConnection.HTTP_NOT_FOUND:
191 if (slash > path.length()) {
192 return false;
194 slash = path.indexOf('/', slash);
195 if (slash == -1) {
196 // try the entire path
197 slash = path.length();
199 tmpPath = path.substring(0, slash);
200 slash++;
201 break;
202 default:
203 return false;
205 } catch (IOException e) {
206 return false;
207 } finally {
208 if (httpConnection != null) {
209 httpConnection.disconnect();
213 } else if (SSH.equals(s)) {
214 if (u.getPort() < 0) {
215 return false;
217 URIish sshUri = u.setPath(""); //$NON-NLS-1$
218 try {
219 String result = SshSupport.runSshCommand(sshUri,
220 credentialsProvider, repo.getFS(),
221 GERRIT_SSHD_VERSION_API, timeout);
222 if (result != null && result.contains("\n")) { //$NON-NLS-1$
223 result = result.substring(0, result.indexOf('\n'));
225 return result != null
226 && GERRIT_SSHD_REPLY.matcher(result).matches();
227 } catch (IOException | CommandFailedException e) {
228 // Something went wrong with the connection or with the command
229 // execution. Maybe the server didn't recognize the command. Do
230 // the safe thing and claim it wasn't a Gerrit. In the worst
231 // case, the user may have to do the Gerrit config setup via
232 // the ConfigureGerritWizard.
233 return false;
236 return false;
239 private String readFully(InputStream inputStream, String encoding)
240 throws IOException {
241 return new String(readFully(inputStream), encoding);
244 private byte[] readFully(InputStream inputStream) throws IOException {
245 ByteArrayOutputStream os = new ByteArrayOutputStream();
246 byte[] buffer = new byte[1024];
247 int length = 0;
248 while ((length = inputStream.read(buffer)) != -1) {
249 os.write(buffer, 0, length);
251 return os.toByteArray();
254 private void configureGerrit(Repository repository)
255 throws URISyntaxException, IOException {
256 StoredConfig config = repository.getConfig();
257 RemoteConfig remoteConfig;
258 remoteConfig = GerritUtil.findRemoteConfig(config, remoteName);
259 if (remoteConfig == null) {
260 return;
262 GerritUtil.configurePushURI(remoteConfig, new URIish(uri));
263 GerritUtil.configurePushRefSpec(remoteConfig, Constants.MASTER);
264 GerritUtil.configureFetchNotes(remoteConfig);
265 GerritUtil.setCreateChangeId(config);
266 remoteConfig.update(config);
267 config.save();