| package org.apache.maven.scm.provider.git.repository; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.scm.ScmException; |
| import org.apache.maven.scm.provider.ScmProviderRepository; |
| import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> |
| * @author <a href="mailto:struberg@apache.org">Mark Struberg</a> |
| * |
| */ |
| public class GitScmProviderRepository |
| extends ScmProviderRepositoryWithHost |
| { |
| |
| /** |
| * sequence used to delimit the fetch URL |
| */ |
| public static final String URL_DELIMITER_FETCH = "[fetch=]"; |
| |
| /** |
| * sequence used to delimit the push URL |
| */ |
| public static final String URL_DELIMITER_PUSH = "[push=]"; |
| |
| /** |
| * this trails every protocol |
| */ |
| public static final String PROTOCOL_SEPARATOR = "://"; |
| |
| /** |
| * use local file as transport |
| */ |
| public static final String PROTOCOL_FILE = "file"; |
| |
| /** |
| * use gits internal protocol |
| */ |
| public static final String PROTOCOL_GIT = "git"; |
| |
| /** |
| * use secure shell protocol |
| */ |
| public static final String PROTOCOL_SSH = "ssh"; |
| |
| /** |
| * use the standard port 80 http protocol |
| */ |
| public static final String PROTOCOL_HTTP = "http"; |
| |
| /** |
| * use the standard port 443 https protocol |
| */ |
| public static final String PROTOCOL_HTTPS = "https"; |
| |
| /** |
| * use rsync for retrieving the data |
| * TODO implement! |
| */ |
| public static final String PROTOCOL_RSYNC = "rsync"; |
| |
| private static final Pattern HOST_AND_PORT_EXTRACTOR = |
| Pattern.compile( "([^:/\\\\~]*)(?::(\\d*))?(?:([:/\\\\~])(.*))?" ); |
| |
| /** |
| * No special protocol specified. Git will either use git:// |
| * or ssh:// depending on whether we work locally or over the network |
| */ |
| public static final String PROTOCOL_NONE = ""; |
| |
| /** |
| * this may either 'git' or 'jgit' depending on the underlying implementation being used |
| */ |
| private String provider; |
| |
| /** |
| * the URL used to fetch from the upstream repository |
| */ |
| private RepositoryUrl fetchInfo; |
| |
| /** |
| * the URL used to push to the upstream repository |
| */ |
| private RepositoryUrl pushInfo; |
| |
| public GitScmProviderRepository( String url ) |
| throws ScmException |
| { |
| if ( url == null ) |
| { |
| throw new ScmException( "url must not be null" ); |
| } |
| |
| if ( url.startsWith( URL_DELIMITER_FETCH ) ) |
| { |
| String fetch = url.substring( URL_DELIMITER_FETCH.length() ); |
| |
| int indexPushDelimiter = fetch.indexOf( URL_DELIMITER_PUSH ); |
| if ( indexPushDelimiter >= 0 ) |
| { |
| String push = fetch.substring( indexPushDelimiter + URL_DELIMITER_PUSH.length() ); |
| pushInfo = parseUrl( push ); |
| |
| fetch = fetch.substring( 0, indexPushDelimiter ); |
| } |
| |
| fetchInfo = parseUrl( fetch ); |
| |
| if ( pushInfo == null ) |
| { |
| pushInfo = fetchInfo; |
| } |
| } |
| else if ( url.startsWith( URL_DELIMITER_PUSH ) ) |
| { |
| String push = url.substring( URL_DELIMITER_PUSH.length() ); |
| |
| int indexFetchDelimiter = push.indexOf( URL_DELIMITER_FETCH ); |
| if ( indexFetchDelimiter >= 0 ) |
| { |
| String fetch = push.substring( indexFetchDelimiter + URL_DELIMITER_FETCH.length() ); |
| fetchInfo = parseUrl( fetch ); |
| |
| push = push.substring( 0, indexFetchDelimiter ); |
| } |
| |
| pushInfo = parseUrl( push ); |
| |
| if ( fetchInfo == null ) |
| { |
| fetchInfo = pushInfo; |
| } |
| } |
| else |
| { |
| fetchInfo = pushInfo = parseUrl( url ); |
| } |
| |
| // set the default values for backward compatibility from the push url |
| // because it's more likely that the push URL contains 'better' credentials |
| setUser( pushInfo.getUserName() ); |
| setPassword( pushInfo.getPassword() ); |
| setHost( pushInfo.getHost() ); |
| if ( pushInfo.getPort() != null && pushInfo.getPort().length() > 0 ) |
| { |
| setPort( Integer.parseInt( pushInfo.getPort() ) ); |
| } |
| } |
| |
| public GitScmProviderRepository( String url, String user, String password ) |
| throws ScmException |
| { |
| this( url ); |
| |
| setUser( user ); |
| |
| setPassword( password ); |
| } |
| |
| /** |
| * @return either 'git' or 'jgit' depending on the underlying implementation being used |
| */ |
| public String getProvider() |
| { |
| return provider; |
| } |
| |
| public RepositoryUrl getFetchInfo() |
| { |
| return fetchInfo; |
| } |
| |
| public RepositoryUrl getPushInfo() |
| { |
| return pushInfo; |
| } |
| |
| |
| /** |
| * @return the URL used to fetch from the upstream repository |
| */ |
| public String getFetchUrl() |
| { |
| return getUrl( fetchInfo ); |
| } |
| |
| /** |
| * @return the URL used to push to the upstream repository |
| */ |
| public String getPushUrl() |
| { |
| return getUrl( pushInfo ); |
| } |
| |
| |
| /** |
| * Parse the given url string and store all the extracted |
| * information in a {@code RepositoryUrl} |
| * |
| * @param url to parse |
| * @return filled with the information from the given URL |
| * @throws ScmException |
| */ |
| private RepositoryUrl parseUrl( String url ) |
| throws ScmException |
| { |
| RepositoryUrl repoUrl = new RepositoryUrl(); |
| |
| url = parseProtocol( repoUrl, url ); |
| url = parseUserInfo( repoUrl, url ); |
| url = parseHostAndPort( repoUrl, url ); |
| // the rest of the url must be the path to the repository on the server |
| repoUrl.setPath( url ); |
| return repoUrl; |
| } |
| |
| |
| /** |
| * @param repoUrl |
| * @return |
| */ |
| private String getUrl( RepositoryUrl repoUrl ) |
| { |
| StringBuilder urlSb = new StringBuilder( repoUrl.getProtocol() ); |
| boolean urlSupportsUserInformation = false; |
| |
| if ( PROTOCOL_SSH.equals( repoUrl.getProtocol() ) || PROTOCOL_RSYNC.equals( repoUrl.getProtocol() ) |
| || PROTOCOL_GIT.equals( repoUrl.getProtocol() ) || PROTOCOL_HTTP.equals( repoUrl.getProtocol() ) |
| || PROTOCOL_HTTPS.equals( repoUrl.getProtocol() ) || PROTOCOL_NONE.equals( repoUrl.getProtocol() ) ) |
| { |
| urlSupportsUserInformation = true; |
| } |
| |
| if ( repoUrl.getProtocol() != null && repoUrl.getProtocol().length() > 0 ) |
| { |
| urlSb.append( "://" ); |
| } |
| |
| // add user information if given and allowed for the protocol |
| if ( urlSupportsUserInformation ) |
| { |
| String userName = repoUrl.getUserName(); |
| // if specified on the commandline or other configuration, we take this. |
| if ( getUser() != null && getUser().length() > 0 ) |
| { |
| userName = getUser(); |
| } |
| |
| String password = repoUrl.getPassword(); |
| if ( getPassword() != null && getPassword().length() > 0 ) |
| { |
| password = getPassword(); |
| } |
| |
| if ( userName != null && userName.length() > 0 ) |
| { |
| String userInfo = userName; |
| if ( password != null && password.length() > 0 ) |
| { |
| userInfo += ":" + password; |
| } |
| |
| try |
| { |
| URI uri = new URI( null, userInfo, "localhost", -1, null, null, null ); |
| urlSb.append( uri.getRawUserInfo() ); |
| } |
| catch ( URISyntaxException e ) |
| { |
| // Quite impossible... |
| // Otherwise throw a RTE since this method is also used by toString() |
| e.printStackTrace(); |
| } |
| |
| urlSb.append( '@' ); |
| } |
| } |
| |
| // add host and port information |
| urlSb.append( repoUrl.getHost() ); |
| if ( repoUrl.getPort() != null && repoUrl.getPort().length() > 0 ) |
| { |
| urlSb.append( ':' ).append( repoUrl.getPort() ); |
| } |
| |
| // finaly we add the path to the repo on the host |
| urlSb.append( repoUrl.getPath() ); |
| |
| return urlSb.toString(); |
| } |
| |
| /** |
| * Parse the protocol from the given url and fill it into the given RepositoryUrl. |
| * |
| * @param repoUrl |
| * @param url |
| * @return the given url with the protocol parts removed |
| */ |
| private String parseProtocol( RepositoryUrl repoUrl, String url ) |
| throws ScmException |
| { |
| // extract the protocol |
| if ( url.startsWith( PROTOCOL_FILE + PROTOCOL_SEPARATOR ) ) |
| { |
| repoUrl.setProtocol( PROTOCOL_FILE ); |
| } |
| else if ( url.startsWith( PROTOCOL_HTTPS + PROTOCOL_SEPARATOR ) ) |
| { |
| repoUrl.setProtocol( PROTOCOL_HTTPS ); |
| } |
| else if ( url.startsWith( PROTOCOL_HTTP + PROTOCOL_SEPARATOR ) ) |
| { |
| repoUrl.setProtocol( PROTOCOL_HTTP ); |
| } |
| else if ( url.startsWith( PROTOCOL_SSH + PROTOCOL_SEPARATOR ) ) |
| { |
| repoUrl.setProtocol( PROTOCOL_SSH ); |
| } |
| else if ( url.startsWith( PROTOCOL_GIT + PROTOCOL_SEPARATOR ) ) |
| { |
| repoUrl.setProtocol( PROTOCOL_GIT ); |
| } |
| else if ( url.startsWith( PROTOCOL_RSYNC + PROTOCOL_SEPARATOR ) ) |
| { |
| repoUrl.setProtocol( PROTOCOL_RSYNC ); |
| } |
| else |
| { |
| // when no protocol is specified git will pick either ssh:// or git:// |
| // depending on whether we work locally or over the network |
| repoUrl.setProtocol( PROTOCOL_NONE ); |
| return url; |
| } |
| |
| url = url.substring( repoUrl.getProtocol().length() + 3 ); |
| |
| return url; |
| } |
| |
| /** |
| * Parse the user information from the given url and fill |
| * user name and password into the given RepositoryUrl. |
| * |
| * @param repoUrl |
| * @param url |
| * @return the given url with the user parts removed |
| */ |
| private String parseUserInfo( RepositoryUrl repoUrl, String url ) |
| throws ScmException |
| { |
| if ( PROTOCOL_FILE.equals( repoUrl.getProtocol() ) ) |
| { |
| // a file:// URL may contain userinfo according to RFC 8089, but our implementation is broken |
| return url; |
| } |
| // extract user information, broken see SCM-907 |
| int indexAt = url.lastIndexOf( '@' ); |
| if ( indexAt >= 0 ) |
| { |
| String userInfo = url.substring( 0, indexAt ); |
| int indexPwdSep = userInfo.indexOf( ':' ); |
| if ( indexPwdSep < 0 ) |
| { |
| repoUrl.setUserName( userInfo ); |
| } |
| else |
| { |
| repoUrl.setUserName( userInfo.substring( 0, indexPwdSep ) ); |
| repoUrl.setPassword( userInfo.substring( indexPwdSep + 1 ) ); |
| } |
| |
| url = url.substring( indexAt + 1 ); |
| } |
| return url; |
| } |
| |
| /** |
| * Parse server and port from the given url and fill it into the |
| * given RepositoryUrl. |
| * |
| * @param repoUrl |
| * @param url |
| * @return the given url with the server parts removed |
| * @throws ScmException |
| */ |
| private String parseHostAndPort( RepositoryUrl repoUrl, String url ) |
| throws ScmException |
| { |
| |
| repoUrl.setPort( "" ); |
| repoUrl.setHost( "" ); |
| |
| if ( PROTOCOL_FILE.equals( repoUrl.getProtocol() ) ) |
| { |
| // a file:// URL doesn't need any further parsing as it cannot contain a port, etc |
| return url; |
| } |
| else |
| { |
| |
| Matcher hostAndPortMatcher = HOST_AND_PORT_EXTRACTOR.matcher( url ); |
| if ( hostAndPortMatcher.matches() ) |
| { |
| if ( hostAndPortMatcher.groupCount() > 1 && hostAndPortMatcher.group( 1 ) != null ) |
| { |
| repoUrl.setHost( hostAndPortMatcher.group( 1 ) ); |
| } |
| if ( hostAndPortMatcher.groupCount() > 2 && hostAndPortMatcher.group( 2 ) != null ) |
| { |
| repoUrl.setPort( hostAndPortMatcher.group( 2 ) ); |
| } |
| |
| StringBuilder computedUrl = new StringBuilder(); |
| if ( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() - 1 ) != null ) |
| { |
| computedUrl.append( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() - 1 ) ); |
| } |
| if ( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() ) != null ) |
| { |
| computedUrl.append( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() ) ); |
| } |
| return computedUrl.toString(); |
| } |
| else |
| { |
| // Pattern doesn't match, let's return the original url |
| return url; |
| } |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getRelativePath( ScmProviderRepository ancestor ) |
| { |
| if ( ancestor instanceof GitScmProviderRepository ) |
| { |
| GitScmProviderRepository gitAncestor = (GitScmProviderRepository) ancestor; |
| |
| //X TODO review! |
| String url = getFetchUrl(); |
| String path = url.replaceFirst( gitAncestor.getFetchUrl() + "/", "" ); |
| |
| if ( !path.equals( url ) ) |
| { |
| return path; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String toString() |
| { |
| // yes we really like to check if those are the exact same instance! |
| if ( fetchInfo == pushInfo ) |
| { |
| return getUrl( fetchInfo ); |
| } |
| return URL_DELIMITER_FETCH + getUrl( fetchInfo ) + URL_DELIMITER_PUSH + getUrl( pushInfo ); |
| } |
| |
| } |