View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.scm.provider.git.repository;
20  
21  import java.net.URI;
22  import java.net.URISyntaxException;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import org.apache.maven.scm.ScmException;
27  import org.apache.maven.scm.provider.ScmProviderRepository;
28  import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
29  
30  /**
31   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
32   * @author <a href="mailto:struberg@apache.org">Mark Struberg</a>
33   *
34   */
35  public class GitScmProviderRepository extends ScmProviderRepositoryWithHost {
36  
37      /**
38       * sequence used to delimit the fetch URL
39       */
40      public static final String URL_DELIMITER_FETCH = "[fetch=]";
41  
42      /**
43       * sequence used to delimit the push URL
44       */
45      public static final String URL_DELIMITER_PUSH = "[push=]";
46  
47      /**
48       * this trails every protocol
49       */
50      public static final String PROTOCOL_SEPARATOR = "://";
51  
52      /**
53       * use local file as transport
54       */
55      public static final String PROTOCOL_FILE = "file";
56  
57      /**
58       * use gits internal protocol
59       */
60      public static final String PROTOCOL_GIT = "git";
61  
62      /**
63       * use secure shell protocol
64       */
65      public static final String PROTOCOL_SSH = "ssh";
66  
67      /**
68       * use the standard port 80 http protocol
69       */
70      public static final String PROTOCOL_HTTP = "http";
71  
72      /**
73       * use the standard port 443 https protocol
74       */
75      public static final String PROTOCOL_HTTPS = "https";
76  
77      /**
78       * use rsync for retrieving the data
79       * TODO implement!
80       */
81      public static final String PROTOCOL_RSYNC = "rsync";
82  
83      private static final Pattern HOST_AND_PORT_EXTRACTOR =
84              Pattern.compile("([^:/\\\\~]*)(?::(\\d*))?(?:([:/\\\\~])(.*))?");
85  
86      /**
87       * No special protocol specified. Git will either use git://
88       * or ssh:// depending on whether we work locally or over the network
89       */
90      public static final String PROTOCOL_NONE = "";
91  
92      /**
93       * this may either 'git' or 'jgit' depending on the underlying implementation being used
94       */
95      private String provider;
96  
97      /**
98       * the URL used to fetch from the upstream repository
99       */
100     private RepositoryUrl fetchInfo;
101 
102     /**
103      * the URL used to push to the upstream repository
104      */
105     private RepositoryUrl pushInfo;
106 
107     public GitScmProviderRepository(String url) throws ScmException {
108         if (url == null) {
109             throw new ScmException("url must not be null");
110         }
111 
112         if (url.startsWith(URL_DELIMITER_FETCH)) {
113             String fetch = url.substring(URL_DELIMITER_FETCH.length());
114 
115             int indexPushDelimiter = fetch.indexOf(URL_DELIMITER_PUSH);
116             if (indexPushDelimiter >= 0) {
117                 String push = fetch.substring(indexPushDelimiter + URL_DELIMITER_PUSH.length());
118                 pushInfo = parseUrl(push);
119 
120                 fetch = fetch.substring(0, indexPushDelimiter);
121             }
122 
123             fetchInfo = parseUrl(fetch);
124 
125             if (pushInfo == null) {
126                 pushInfo = fetchInfo;
127             }
128         } else if (url.startsWith(URL_DELIMITER_PUSH)) {
129             String push = url.substring(URL_DELIMITER_PUSH.length());
130 
131             int indexFetchDelimiter = push.indexOf(URL_DELIMITER_FETCH);
132             if (indexFetchDelimiter >= 0) {
133                 String fetch = push.substring(indexFetchDelimiter + URL_DELIMITER_FETCH.length());
134                 fetchInfo = parseUrl(fetch);
135 
136                 push = push.substring(0, indexFetchDelimiter);
137             }
138 
139             pushInfo = parseUrl(push);
140 
141             if (fetchInfo == null) {
142                 fetchInfo = pushInfo;
143             }
144         } else {
145             fetchInfo = pushInfo = parseUrl(url);
146         }
147 
148         // set the default values for backward compatibility from the push url
149         // because it's more likely that the push URL contains 'better' credentials
150         setUser(pushInfo.getUserName());
151         setPassword(pushInfo.getPassword());
152         setHost(pushInfo.getHost());
153         if (pushInfo.getPort() != null && pushInfo.getPort().length() > 0) {
154             setPort(Integer.parseInt(pushInfo.getPort()));
155         }
156     }
157 
158     public GitScmProviderRepository(String url, String user, String password) throws ScmException {
159         this(url);
160 
161         setUser(user);
162 
163         setPassword(password);
164     }
165 
166     /**
167      * @return either 'git' or 'jgit' depending on the underlying implementation being used
168      */
169     public String getProvider() {
170         return provider;
171     }
172 
173     public RepositoryUrl getFetchInfo() {
174         return fetchInfo;
175     }
176 
177     public RepositoryUrl getPushInfo() {
178         return pushInfo;
179     }
180 
181     /**
182      * @return the URL used to fetch from the upstream repository
183      */
184     public String getFetchUrl() {
185         return getUrl(fetchInfo);
186     }
187 
188     /**
189      * @return the URL used to push to the upstream repository
190      */
191     public String getPushUrl() {
192         return getUrl(pushInfo);
193     }
194 
195     /**
196      * Parse the given url string and store all the extracted
197      * information in a {@code RepositoryUrl}
198      *
199      * @param url to parse
200      * @return filled with the information from the given URL
201      * @throws ScmException
202      */
203     private RepositoryUrl parseUrl(String url) throws ScmException {
204         RepositoryUrl repoUrl = new RepositoryUrl();
205 
206         url = parseProtocol(repoUrl, url);
207         url = parseUserInfo(repoUrl, url);
208         url = parseHostAndPort(repoUrl, url);
209         // the rest of the url must be the path to the repository on the server
210         repoUrl.setPath(url);
211         return repoUrl;
212     }
213 
214     /**
215      * @param repoUrl
216      * @return TODO
217      */
218     private String getUrl(RepositoryUrl repoUrl) {
219         StringBuilder urlSb = new StringBuilder(repoUrl.getProtocol());
220         boolean urlSupportsUserInformation = false;
221 
222         if (PROTOCOL_SSH.equals(repoUrl.getProtocol())
223                 || PROTOCOL_RSYNC.equals(repoUrl.getProtocol())
224                 || PROTOCOL_GIT.equals(repoUrl.getProtocol())
225                 || PROTOCOL_HTTP.equals(repoUrl.getProtocol())
226                 || PROTOCOL_HTTPS.equals(repoUrl.getProtocol())
227                 || PROTOCOL_NONE.equals(repoUrl.getProtocol())) {
228             urlSupportsUserInformation = true;
229         }
230 
231         if (repoUrl.getProtocol() != null && repoUrl.getProtocol().length() > 0) {
232             urlSb.append("://");
233         }
234 
235         // add user information if given and allowed for the protocol
236         if (urlSupportsUserInformation) {
237             String userName = repoUrl.getUserName();
238             // if specified on the commandline or other configuration, we take this.
239             if (getUser() != null && getUser().length() > 0) {
240                 userName = getUser();
241             }
242 
243             String password = repoUrl.getPassword();
244             if (getPassword() != null && getPassword().length() > 0) {
245                 password = getPassword();
246             }
247 
248             if (userName != null && userName.length() > 0) {
249                 String userInfo = userName;
250                 if (password != null && password.length() > 0) {
251                     userInfo += ":" + password;
252                 }
253 
254                 try {
255                     URI uri = new URI(null, userInfo, "localhost", -1, null, null, null);
256                     urlSb.append(uri.getRawUserInfo());
257                 } catch (URISyntaxException e) {
258                     // Quite impossible...
259                     // Otherwise throw a RTE since this method is also used by toString()
260                     e.printStackTrace();
261                 }
262 
263                 urlSb.append('@');
264             }
265         }
266 
267         // add host and port information
268         urlSb.append(repoUrl.getHost());
269         if (repoUrl.getPort() != null && repoUrl.getPort().length() > 0) {
270             urlSb.append(':').append(repoUrl.getPort());
271         }
272 
273         // finaly we add the path to the repo on the host
274         urlSb.append(repoUrl.getPath());
275 
276         return urlSb.toString();
277     }
278 
279     /**
280      * Parse the protocol from the given url and fill it into the given RepositoryUrl.
281      *
282      * @param repoUrl
283      * @param url
284      * @return the given url with the protocol parts removed
285      */
286     private String parseProtocol(RepositoryUrl repoUrl, String url) throws ScmException {
287         // extract the protocol
288         if (url.startsWith(PROTOCOL_FILE + PROTOCOL_SEPARATOR)) {
289             repoUrl.setProtocol(PROTOCOL_FILE);
290         } else if (url.startsWith(PROTOCOL_HTTPS + PROTOCOL_SEPARATOR)) {
291             repoUrl.setProtocol(PROTOCOL_HTTPS);
292         } else if (url.startsWith(PROTOCOL_HTTP + PROTOCOL_SEPARATOR)) {
293             repoUrl.setProtocol(PROTOCOL_HTTP);
294         } else if (url.startsWith(PROTOCOL_SSH + PROTOCOL_SEPARATOR)) {
295             repoUrl.setProtocol(PROTOCOL_SSH);
296         } else if (url.startsWith(PROTOCOL_GIT + PROTOCOL_SEPARATOR)) {
297             repoUrl.setProtocol(PROTOCOL_GIT);
298         } else if (url.startsWith(PROTOCOL_RSYNC + PROTOCOL_SEPARATOR)) {
299             repoUrl.setProtocol(PROTOCOL_RSYNC);
300         } else {
301             // when no protocol is specified git will pick either ssh:// or git://
302             // depending on whether we work locally or over the network
303             repoUrl.setProtocol(PROTOCOL_NONE);
304             return url;
305         }
306 
307         url = url.substring(repoUrl.getProtocol().length() + 3);
308 
309         return url;
310     }
311 
312     /**
313      * Parse the user information from the given url and fill
314      * user name and password into the given RepositoryUrl.
315      *
316      * @param repoUrl
317      * @param url
318      * @return the given url with the user parts removed
319      */
320     private String parseUserInfo(RepositoryUrl repoUrl, String url) throws ScmException {
321         if (PROTOCOL_FILE.equals(repoUrl.getProtocol())) {
322             // a file:// URL may contain userinfo according to RFC 8089, but our implementation is broken
323             return url;
324         }
325         // extract user information, broken see SCM-907
326         int indexAt = url.lastIndexOf('@');
327         if (indexAt >= 0) {
328             String userInfo = url.substring(0, indexAt);
329             int indexPwdSep = userInfo.indexOf(':');
330             if (indexPwdSep < 0) {
331                 repoUrl.setUserName(userInfo);
332             } else {
333                 repoUrl.setUserName(userInfo.substring(0, indexPwdSep));
334                 repoUrl.setPassword(userInfo.substring(indexPwdSep + 1));
335             }
336 
337             url = url.substring(indexAt + 1);
338         }
339         return url;
340     }
341 
342     /**
343      * Parse server and port from the given url and fill it into the
344      * given RepositoryUrl.
345      *
346      * @param repoUrl
347      * @param url
348      * @return the given url with the server parts removed
349      * @throws ScmException
350      */
351     private String parseHostAndPort(RepositoryUrl repoUrl, String url) throws ScmException {
352 
353         repoUrl.setPort("");
354         repoUrl.setHost("");
355 
356         if (PROTOCOL_FILE.equals(repoUrl.getProtocol())) {
357             // a file:// URL doesn't need any further parsing as it cannot contain a port, etc
358             return url;
359         } else {
360 
361             Matcher hostAndPortMatcher = HOST_AND_PORT_EXTRACTOR.matcher(url);
362             if (hostAndPortMatcher.matches()) {
363                 if (hostAndPortMatcher.groupCount() > 1 && hostAndPortMatcher.group(1) != null) {
364                     repoUrl.setHost(hostAndPortMatcher.group(1));
365                 }
366                 if (hostAndPortMatcher.groupCount() > 2 && hostAndPortMatcher.group(2) != null) {
367                     repoUrl.setPort(hostAndPortMatcher.group(2));
368                 }
369 
370                 StringBuilder computedUrl = new StringBuilder();
371                 if (hostAndPortMatcher.group(hostAndPortMatcher.groupCount() - 1) != null) {
372                     computedUrl.append(hostAndPortMatcher.group(hostAndPortMatcher.groupCount() - 1));
373                 }
374                 if (hostAndPortMatcher.group(hostAndPortMatcher.groupCount()) != null) {
375                     computedUrl.append(hostAndPortMatcher.group(hostAndPortMatcher.groupCount()));
376                 }
377                 return computedUrl.toString();
378             } else {
379                 // Pattern doesn't match, let's return the original url
380                 return url;
381             }
382         }
383     }
384 
385     /**
386      * {@inheritDoc}
387      */
388     public String getRelativePath(ScmProviderRepository ancestor) {
389         if (ancestor instanceof GitScmProviderRepository) {
390             GitScmProviderRepository gitAncestor = (GitScmProviderRepository) ancestor;
391 
392             // X TODO review!
393             String url = getFetchUrl();
394             String path = url.replaceFirst(gitAncestor.getFetchUrl() + "/", "");
395 
396             if (!path.equals(url)) {
397                 return path;
398             }
399         }
400         return null;
401     }
402 
403     /**
404      * {@inheritDoc}
405      */
406     public String toString() {
407         // yes we really like to check if those are the exact same instance!
408         if (fetchInfo == pushInfo) {
409             return getUrl(fetchInfo);
410         }
411         return URL_DELIMITER_FETCH + getUrl(fetchInfo) + URL_DELIMITER_PUSH + getUrl(pushInfo);
412     }
413 }