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.URLEncoder;
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
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                 try
282                 {
283                     urlSb.append( URLEncoder.encode( userName, "UTF-8" ) );
284                 }
285                 catch ( UnsupportedEncodingException e )
286                 {
287                     // Quite impossible...
288                     // Otherwise throw a RTE, since this method is also used by toString()
289                     e.printStackTrace();
290                 }
291 
292                 if ( password != null && password.length() > 0 )
293                 {
294                     urlSb.append( ':' );
295                     try
296                     {
297                         urlSb.append( URLEncoder.encode( password, "UTF-8" ) );
298                     }
299                     catch ( UnsupportedEncodingException e )
300                     {
301                         // Quite impossible...
302                         // Otherwise throw a RTE, since this method is also used by toString()
303                         e.printStackTrace();
304                     }
305                 }
306 
307                 urlSb.append( '@' );
308             }
309         }
310 
311         // add host and port information
312         urlSb.append( repoUrl.getHost() );
313         if ( repoUrl.getPort() != null && repoUrl.getPort().length() > 0 )
314         {
315             urlSb.append( ':' ).append( repoUrl.getPort() );
316         }
317 
318         // finaly we add the path to the repo on the host
319         urlSb.append( repoUrl.getPath() );
320 
321         return urlSb.toString();
322     }
323 
324     /**
325      * Parse the protocol from the given url and fill it into the given RepositoryUrl.
326      *
327      * @param repoUrl
328      * @param url
329      * @return the given url with the protocol parts removed
330      */
331     private String parseProtocol( RepositoryUrl repoUrl, String url )
332         throws ScmException
333     {
334         // extract the protocol
335         if ( url.startsWith( PROTOCOL_FILE + PROTOCOL_SEPARATOR ) )
336         {
337             repoUrl.setProtocol( PROTOCOL_FILE );
338         }
339         else if ( url.startsWith( PROTOCOL_HTTPS + PROTOCOL_SEPARATOR ) )
340         {
341             repoUrl.setProtocol( PROTOCOL_HTTPS );
342         }
343         else if ( url.startsWith( PROTOCOL_HTTP + PROTOCOL_SEPARATOR ) )
344         {
345             repoUrl.setProtocol( PROTOCOL_HTTP );
346         }
347         else if ( url.startsWith( PROTOCOL_SSH + PROTOCOL_SEPARATOR ) )
348         {
349             repoUrl.setProtocol( PROTOCOL_SSH );
350         }
351         else if ( url.startsWith( PROTOCOL_GIT + PROTOCOL_SEPARATOR ) )
352         {
353             repoUrl.setProtocol( PROTOCOL_GIT );
354         }
355         else if ( url.startsWith( PROTOCOL_RSYNC + PROTOCOL_SEPARATOR ) )
356         {
357             repoUrl.setProtocol( PROTOCOL_RSYNC );
358         }
359         else
360         {
361             // when no protocol is specified git will pick either ssh:// or git://
362             // depending on whether we work locally or over the network
363             repoUrl.setProtocol( PROTOCOL_NONE );
364             return url;
365         }
366 
367         url = url.substring( repoUrl.getProtocol().length() + 3 );
368 
369         return url;
370     }
371 
372     /**
373      * Parse the user information from the given url and fill
374      * user name and password into the given RepositoryUrl.
375      *
376      * @param repoUrl
377      * @param url
378      * @return the given url with the user parts removed
379      */
380     private String parseUserInfo( RepositoryUrl repoUrl, String url )
381         throws ScmException
382     {
383         // extract user information
384         int indexAt = url.indexOf( '@' );
385         if ( indexAt >= 0 )
386         {
387             String userInfo = url.substring( 0, indexAt );
388             int indexPwdSep = userInfo.indexOf( ':' );
389             if ( indexPwdSep < 0 )
390             {
391                 repoUrl.setUserName( userInfo );
392             }
393             else
394             {
395                 repoUrl.setUserName( userInfo.substring( 0, indexPwdSep ) );
396                 repoUrl.setPassword( userInfo.substring( indexPwdSep + 1 ) );
397             }
398 
399             url = url.substring( indexAt + 1 );
400         }
401         return url;
402     }
403 
404     /**
405      * Parse server and port from the given url and fill it into the
406      * given RepositoryUrl.
407      *
408      * @param repoUrl
409      * @param url
410      * @return the given url with the server parts removed
411      * @throws ScmException
412      */
413     private String parseHostAndPort( RepositoryUrl repoUrl, String url )
414         throws ScmException
415     {
416 
417         repoUrl.setPort( "" );
418         repoUrl.setHost( "" );
419 
420         if ( PROTOCOL_FILE.equals( repoUrl.getProtocol() ) )
421         {
422             // a file:// URL doesn't need any further parsing as it cannot contain a port, etc
423             return url;
424         }
425         else
426         {
427 
428             Matcher hostAndPortMatcher = HOST_AND_PORT_EXTRACTOR.matcher( url );
429             if ( hostAndPortMatcher.matches() )
430             {
431                 if ( hostAndPortMatcher.groupCount() > 1 && hostAndPortMatcher.group( 1 ) != null )
432                 {
433                     repoUrl.setHost( hostAndPortMatcher.group( 1 ) );
434                 }
435                 if ( hostAndPortMatcher.groupCount() > 2 && hostAndPortMatcher.group( 2 ) != null )
436                 {
437                     repoUrl.setPort( hostAndPortMatcher.group( 2 ) );
438                 }
439 
440                 StringBuilder computedUrl = new StringBuilder();
441                 if ( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() - 1 ) != null )
442                 {
443                     computedUrl.append( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() - 1 ) );
444                 }
445                 if ( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() ) != null )
446                 {
447                     computedUrl.append( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() ) );
448                 }
449                 return computedUrl.toString();
450             }
451             else
452             {
453                 // Pattern doesn't match, let's return the original url
454                 return url;
455             }
456         }
457     }
458 
459 
460     /**
461      * {@inheritDoc}
462      */
463     public String getRelativePath( ScmProviderRepository ancestor )
464     {
465         if ( ancestor instanceof GitScmProviderRepository )
466         {
467             GitScmProviderRepository gitAncestor = (GitScmProviderRepository) ancestor;
468 
469             //X TODO review!
470             String url = getFetchUrl();
471             String path = url.replaceFirst( gitAncestor.getFetchUrl() + "/", "" );
472 
473             if ( !path.equals( url ) )
474             {
475                 return path;
476             }
477         }
478         return null;
479     }
480 
481     /**
482      * {@inheritDoc}
483      */
484     public String toString()
485     {
486         // yes we really like to check if those are the exact same instance!
487         if ( fetchInfo == pushInfo )
488         {
489             return getUrl( fetchInfo );
490         }
491         return URL_DELIMITER_FETCH + getUrl( fetchInfo ) + URL_DELIMITER_PUSH + getUrl( pushInfo );
492     }
493 
494 }