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