001    package org.apache.maven.scm.provider.git.repository;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import org.apache.maven.scm.ScmException;
023    import org.apache.maven.scm.provider.ScmProviderRepository;
024    import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
025    
026    import java.util.regex.Matcher;
027    import java.util.regex.Pattern;
028    
029    /**
030     * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
031     * @author <a href="mailto:struberg@apache.org">Mark Struberg</a>
032     * @version $Id: GitScmProviderRepository.java 1306864 2012-03-29 13:43:18Z olamy $
033     */
034    public class GitScmProviderRepository
035        extends ScmProviderRepositoryWithHost
036    {
037    
038        /**
039         * sequence used to delimit the fetch URL
040         */
041        public static final String URL_DELIMITER_FETCH = "[fetch=]";
042    
043        /**
044         * sequence used to delimit the push URL
045         */
046        public static final String URL_DELIMITER_PUSH = "[push=]";
047    
048        /**
049         * this trails every protocol
050         */
051        public static final String PROTOCOL_SEPARATOR = "://";
052    
053        /**
054         * use local file as transport
055         */
056        public static final String PROTOCOL_FILE = "file";
057    
058        /**
059         * use gits internal protocol
060         */
061        public static final String PROTOCOL_GIT = "git";
062    
063        /**
064         * use secure shell protocol
065         */
066        public static final String PROTOCOL_SSH = "ssh";
067    
068        /**
069         * use the standard port 80 http protocol
070         */
071        public static final String PROTOCOL_HTTP = "http";
072    
073        /**
074         * use the standard port 443 https protocol
075         */
076        public static final String PROTOCOL_HTTPS = "https";
077    
078        /**
079         * use rsync for retrieving the data
080         * TODO implement!
081         */
082        public static final String PROTOCOL_RSYNC = "rsync";
083    
084        private static final Pattern HOST_AND_PORT_EXTRACTOR =
085            Pattern.compile( "([^:/\\\\~]*)(?::(\\d*))?(?:([:/\\\\~])(.*))?" );
086    
087        /**
088         * No special protocol specified. Git will either use git://
089         * or ssh:// depending on whether we work locally or over the network
090         */
091        public static final String PROTOCOL_NONE = "";
092    
093        /**
094         * this may either 'git' or 'jgit' depending on the underlying implementation being used
095         */
096        private String provider;
097    
098        /**
099         * 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    }