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