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.net.URI; 027import java.net.URISyntaxException; 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 TODO 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() ) || PROTOCOL_RSYNC.equals( repoUrl.getProtocol() ) 252 || PROTOCOL_GIT.equals( repoUrl.getProtocol() ) || PROTOCOL_HTTP.equals( repoUrl.getProtocol() ) 253 || PROTOCOL_HTTPS.equals( repoUrl.getProtocol() ) || PROTOCOL_NONE.equals( repoUrl.getProtocol() ) ) 254 { 255 urlSupportsUserInformation = true; 256 } 257 258 if ( repoUrl.getProtocol() != null && repoUrl.getProtocol().length() > 0 ) 259 { 260 urlSb.append( "://" ); 261 } 262 263 // add user information if given and allowed for the protocol 264 if ( urlSupportsUserInformation ) 265 { 266 String userName = repoUrl.getUserName(); 267 // if specified on the commandline or other configuration, we take this. 268 if ( getUser() != null && getUser().length() > 0 ) 269 { 270 userName = getUser(); 271 } 272 273 String password = repoUrl.getPassword(); 274 if ( getPassword() != null && getPassword().length() > 0 ) 275 { 276 password = getPassword(); 277 } 278 279 if ( userName != null && userName.length() > 0 ) 280 { 281 String userInfo = userName; 282 if ( password != null && password.length() > 0 ) 283 { 284 userInfo += ":" + password; 285 } 286 287 try 288 { 289 URI uri = new URI( null, userInfo, "localhost", -1, null, null, null ); 290 urlSb.append( uri.getRawUserInfo() ); 291 } 292 catch ( URISyntaxException e ) 293 { 294 // Quite impossible... 295 // Otherwise throw a RTE since this method is also used by toString() 296 e.printStackTrace(); 297 } 298 299 urlSb.append( '@' ); 300 } 301 } 302 303 // add host and port information 304 urlSb.append( repoUrl.getHost() ); 305 if ( repoUrl.getPort() != null && repoUrl.getPort().length() > 0 ) 306 { 307 urlSb.append( ':' ).append( repoUrl.getPort() ); 308 } 309 310 // finaly we add the path to the repo on the host 311 urlSb.append( repoUrl.getPath() ); 312 313 return urlSb.toString(); 314 } 315 316 /** 317 * Parse the protocol from the given url and fill it into the given RepositoryUrl. 318 * 319 * @param repoUrl 320 * @param url 321 * @return the given url with the protocol parts removed 322 */ 323 private String parseProtocol( RepositoryUrl repoUrl, String url ) 324 throws ScmException 325 { 326 // extract the protocol 327 if ( url.startsWith( PROTOCOL_FILE + PROTOCOL_SEPARATOR ) ) 328 { 329 repoUrl.setProtocol( PROTOCOL_FILE ); 330 } 331 else if ( url.startsWith( PROTOCOL_HTTPS + PROTOCOL_SEPARATOR ) ) 332 { 333 repoUrl.setProtocol( PROTOCOL_HTTPS ); 334 } 335 else if ( url.startsWith( PROTOCOL_HTTP + PROTOCOL_SEPARATOR ) ) 336 { 337 repoUrl.setProtocol( PROTOCOL_HTTP ); 338 } 339 else if ( url.startsWith( PROTOCOL_SSH + PROTOCOL_SEPARATOR ) ) 340 { 341 repoUrl.setProtocol( PROTOCOL_SSH ); 342 } 343 else if ( url.startsWith( PROTOCOL_GIT + PROTOCOL_SEPARATOR ) ) 344 { 345 repoUrl.setProtocol( PROTOCOL_GIT ); 346 } 347 else if ( url.startsWith( PROTOCOL_RSYNC + PROTOCOL_SEPARATOR ) ) 348 { 349 repoUrl.setProtocol( PROTOCOL_RSYNC ); 350 } 351 else 352 { 353 // when no protocol is specified git will pick either ssh:// or git:// 354 // depending on whether we work locally or over the network 355 repoUrl.setProtocol( PROTOCOL_NONE ); 356 return url; 357 } 358 359 url = url.substring( repoUrl.getProtocol().length() + 3 ); 360 361 return url; 362 } 363 364 /** 365 * Parse the user information from the given url and fill 366 * user name and password into the given RepositoryUrl. 367 * 368 * @param repoUrl 369 * @param url 370 * @return the given url with the user parts removed 371 */ 372 private String parseUserInfo( RepositoryUrl repoUrl, String url ) 373 throws ScmException 374 { 375 if ( PROTOCOL_FILE.equals( repoUrl.getProtocol() ) ) 376 { 377 // a file:// URL may contain userinfo according to RFC 8089, but our implementation is broken 378 return url; 379 } 380 // extract user information, broken see SCM-907 381 int indexAt = url.lastIndexOf( '@' ); 382 if ( indexAt >= 0 ) 383 { 384 String userInfo = url.substring( 0, indexAt ); 385 int indexPwdSep = userInfo.indexOf( ':' ); 386 if ( indexPwdSep < 0 ) 387 { 388 repoUrl.setUserName( userInfo ); 389 } 390 else 391 { 392 repoUrl.setUserName( userInfo.substring( 0, indexPwdSep ) ); 393 repoUrl.setPassword( userInfo.substring( indexPwdSep + 1 ) ); 394 } 395 396 url = url.substring( indexAt + 1 ); 397 } 398 return url; 399 } 400 401 /** 402 * Parse server and port from the given url and fill it into the 403 * given RepositoryUrl. 404 * 405 * @param repoUrl 406 * @param url 407 * @return the given url with the server parts removed 408 * @throws ScmException 409 */ 410 private String parseHostAndPort( RepositoryUrl repoUrl, String url ) 411 throws ScmException 412 { 413 414 repoUrl.setPort( "" ); 415 repoUrl.setHost( "" ); 416 417 if ( PROTOCOL_FILE.equals( repoUrl.getProtocol() ) ) 418 { 419 // a file:// URL doesn't need any further parsing as it cannot contain a port, etc 420 return url; 421 } 422 else 423 { 424 425 Matcher hostAndPortMatcher = HOST_AND_PORT_EXTRACTOR.matcher( url ); 426 if ( hostAndPortMatcher.matches() ) 427 { 428 if ( hostAndPortMatcher.groupCount() > 1 && hostAndPortMatcher.group( 1 ) != null ) 429 { 430 repoUrl.setHost( hostAndPortMatcher.group( 1 ) ); 431 } 432 if ( hostAndPortMatcher.groupCount() > 2 && hostAndPortMatcher.group( 2 ) != null ) 433 { 434 repoUrl.setPort( hostAndPortMatcher.group( 2 ) ); 435 } 436 437 StringBuilder computedUrl = new StringBuilder(); 438 if ( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() - 1 ) != null ) 439 { 440 computedUrl.append( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() - 1 ) ); 441 } 442 if ( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() ) != null ) 443 { 444 computedUrl.append( hostAndPortMatcher.group( hostAndPortMatcher.groupCount() ) ); 445 } 446 return computedUrl.toString(); 447 } 448 else 449 { 450 // Pattern doesn't match, let's return the original url 451 return url; 452 } 453 } 454 } 455 456 457 /** 458 * {@inheritDoc} 459 */ 460 public String getRelativePath( ScmProviderRepository ancestor ) 461 { 462 if ( ancestor instanceof GitScmProviderRepository ) 463 { 464 GitScmProviderRepository gitAncestor = (GitScmProviderRepository) ancestor; 465 466 //X TODO review! 467 String url = getFetchUrl(); 468 String path = url.replaceFirst( gitAncestor.getFetchUrl() + "/", "" ); 469 470 if ( !path.equals( url ) ) 471 { 472 return path; 473 } 474 } 475 return null; 476 } 477 478 /** 479 * {@inheritDoc} 480 */ 481 public String toString() 482 { 483 // yes we really like to check if those are the exact same instance! 484 if ( fetchInfo == pushInfo ) 485 { 486 return getUrl( fetchInfo ); 487 } 488 return URL_DELIMITER_FETCH + getUrl( fetchInfo ) + URL_DELIMITER_PUSH + getUrl( pushInfo ); 489 } 490 491}