blob: 6553c051246bb948ca9cdaa69f2ad390ff0879d9 [file] [log] [blame]
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 );
}
}