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