001package 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 022import org.apache.maven.scm.ScmException; 023import org.apache.maven.scm.provider.ScmProviderRepository; 024import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost; 025 026import java.io.UnsupportedEncodingException; 027import java.net.URLEncoder; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031/** 032 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> 033 * @author <a href="mailto:struberg@apache.org">Mark Struberg</a> 034 * 035 */ 036public class GitScmProviderRepository 037 extends ScmProviderRepositoryWithHost 038{ 039 040 /** 041 * sequence used to delimit the fetch URL 042 */ 043 public static final String URL_DELIMITER_FETCH = "[fetch=]"; 044 045 /** 046 * sequence used to delimit the push URL 047 */ 048 public static final String URL_DELIMITER_PUSH = "[push=]"; 049 050 /** 051 * this trails every protocol 052 */ 053 public static final String PROTOCOL_SEPARATOR = "://"; 054 055 /** 056 * use local file as transport 057 */ 058 public static final String PROTOCOL_FILE = "file"; 059 060 /** 061 * use gits internal protocol 062 */ 063 public static final String PROTOCOL_GIT = "git"; 064 065 /** 066 * use secure shell protocol 067 */ 068 public static final String PROTOCOL_SSH = "ssh"; 069 070 /** 071 * use the standard port 80 http protocol 072 */ 073 public static final String PROTOCOL_HTTP = "http"; 074 075 /** 076 * use the standard port 443 https protocol 077 */ 078 public static final String PROTOCOL_HTTPS = "https"; 079 080 /** 081 * use rsync for retrieving the data 082 * TODO implement! 083 */ 084 public static final String PROTOCOL_RSYNC = "rsync"; 085 086 private static final Pattern HOST_AND_PORT_EXTRACTOR = 087 Pattern.compile( "([^:/\\\\~]*)(?::(\\d*))?(?:([:/\\\\~])(.*))?" ); 088 089 /** 090 * No special protocol specified. Git will either use git:// 091 * or ssh:// depending on whether we work locally or over the network 092 */ 093 public static final String PROTOCOL_NONE = ""; 094 095 /** 096 * this may either 'git' or 'jgit' depending on the underlying implementation being used 097 */ 098 private String provider; 099 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}