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