001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.scm.provider.svn; 020 021import java.io.File; 022import java.util.ArrayList; 023import java.util.List; 024 025import org.apache.commons.lang3.StringUtils; 026import org.apache.maven.scm.CommandParameters; 027import org.apache.maven.scm.ScmException; 028import org.apache.maven.scm.ScmFileSet; 029import org.apache.maven.scm.ScmResult; 030import org.apache.maven.scm.command.add.AddScmResult; 031import org.apache.maven.scm.command.blame.BlameScmResult; 032import org.apache.maven.scm.command.branch.BranchScmResult; 033import org.apache.maven.scm.command.changelog.ChangeLogScmResult; 034import org.apache.maven.scm.command.checkin.CheckInScmResult; 035import org.apache.maven.scm.command.checkout.CheckOutScmResult; 036import org.apache.maven.scm.command.diff.DiffScmResult; 037import org.apache.maven.scm.command.export.ExportScmResult; 038import org.apache.maven.scm.command.info.InfoItem; 039import org.apache.maven.scm.command.info.InfoScmResult; 040import org.apache.maven.scm.command.list.ListScmResult; 041import org.apache.maven.scm.command.mkdir.MkdirScmResult; 042import org.apache.maven.scm.command.remove.RemoveScmResult; 043import org.apache.maven.scm.command.status.StatusScmResult; 044import org.apache.maven.scm.command.tag.TagScmResult; 045import org.apache.maven.scm.command.untag.UntagScmResult; 046import org.apache.maven.scm.command.update.UpdateScmResult; 047import org.apache.maven.scm.provider.AbstractScmProvider; 048import org.apache.maven.scm.provider.ScmProviderRepository; 049import org.apache.maven.scm.provider.svn.command.SvnCommand; 050import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository; 051import org.apache.maven.scm.provider.svn.util.SvnUtil; 052import org.apache.maven.scm.repository.ScmRepository; 053import org.apache.maven.scm.repository.ScmRepositoryException; 054import org.apache.maven.scm.repository.UnknownRepositoryStructure; 055 056/** 057 * SCM Provider for Subversion. 058 * 059 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> 060 */ 061public abstract class AbstractSvnScmProvider extends AbstractScmProvider { 062 // ---------------------------------------------------------------------- 063 // 064 // ---------------------------------------------------------------------- 065 066 private static class ScmUrlParserResult { 067 private final List<String> messages = new ArrayList<>(); 068 069 private ScmProviderRepository repository; 070 } 071 072 public static final String CURRENT_WORKING_DIRECTORY = "scmCheckWorkingDirectoryUrl.currentWorkingDirectory"; 073 074 // ---------------------------------------------------------------------- 075 // ScmProvider Implementation 076 // ---------------------------------------------------------------------- 077 078 /** 079 * {@inheritDoc} 080 */ 081 public String getScmSpecificFilename() { 082 return ".svn"; 083 } 084 085 /** 086 * {@inheritDoc} 087 */ 088 public ScmProviderRepository makeProviderScmRepository(String scmSpecificUrl, char delimiter) 089 throws ScmRepositoryException { 090 ScmUrlParserResult result = parseScmUrl(scmSpecificUrl); 091 092 if (checkCurrentWorkingDirectoryUrl()) { 093 logger.debug("Checking svn info 'URL:' field matches current sources directory"); 094 try { 095 String workingDir = System.getProperty(CURRENT_WORKING_DIRECTORY); 096 InfoScmResult info = 097 info(result.repository, new ScmFileSet(new File(workingDir)), new CommandParameters()); 098 099 String url = findUrlInfoItem(info); 100 String comparison = "'" + url + "' vs. '" + scmSpecificUrl + "'"; 101 logger.debug("Comparing : " + comparison); 102 if (url != null && !url.equals(scmSpecificUrl)) { 103 result.messages.add("Scm url does not match the value returned by svn info (" + comparison + ")"); 104 } 105 } catch (ScmException e) { 106 throw new ScmRepositoryException("An error occurred while trying to svn info", e); 107 } 108 } 109 if (result.messages.size() > 0) { 110 throw new ScmRepositoryException("The scm url is invalid.", result.messages); 111 } 112 113 return result.repository; 114 } 115 116 private boolean checkCurrentWorkingDirectoryUrl() { 117 return StringUtils.isNotEmpty(System.getProperty(CURRENT_WORKING_DIRECTORY)); 118 } 119 120 private String findUrlInfoItem(InfoScmResult infoScmResult) { 121 for (InfoItem infoItem : infoScmResult.getInfoItems()) { 122 if (infoItem.getURL() != null) { 123 logger.debug("URL found: " + infoItem.getURL()); 124 return infoItem.getURL(); 125 } 126 } 127 logger.debug("URL not found (command output=" + infoScmResult.getCommandOutput() + ")"); 128 return null; 129 } 130 131 /** 132 * {@inheritDoc} 133 */ 134 public ScmProviderRepository makeProviderScmRepository(File path) 135 throws ScmRepositoryException, UnknownRepositoryStructure { 136 if (path == null) { 137 throw new NullPointerException("Path argument is null"); 138 } 139 140 if (!path.isDirectory()) { 141 throw new ScmRepositoryException(path.getAbsolutePath() + " isn't a valid directory."); 142 } 143 144 if (!new File(path, ".svn").exists()) { 145 throw new ScmRepositoryException(path.getAbsolutePath() + " isn't a svn checkout directory."); 146 } 147 148 try { 149 return makeProviderScmRepository(getRepositoryURL(path), ':'); 150 } catch (ScmException e) { 151 // XXX We should allow throwing of SCMException. 152 throw new ScmRepositoryException("Error executing info command", e); 153 } 154 } 155 156 protected abstract String getRepositoryURL(File path) throws ScmException; 157 158 /** 159 * {@inheritDoc} 160 */ 161 public List<String> validateScmUrl(String scmSpecificUrl, char delimiter) { 162 List<String> messages = new ArrayList<>(); 163 try { 164 makeProviderScmRepository(scmSpecificUrl, delimiter); 165 } catch (ScmRepositoryException e) { 166 messages = e.getValidationMessages(); 167 } 168 return messages; 169 } 170 171 /** 172 * {@inheritDoc} 173 */ 174 public String getScmType() { 175 return "svn"; 176 } 177 178 // ---------------------------------------------------------------------- 179 // 180 // ---------------------------------------------------------------------- 181 182 private ScmUrlParserResult parseScmUrl(String scmSpecificUrl) { 183 ScmUrlParserResult result = new ScmUrlParserResult(); 184 185 // ---------------------------------------------------------------------- 186 // Do some sanity checking of the SVN url 187 // ---------------------------------------------------------------------- 188 189 if (scmSpecificUrl.startsWith("file")) { 190 if (!scmSpecificUrl.startsWith("file://")) { 191 result.messages.add("A svn 'file' url must be on the form 'file://[hostname]/'."); 192 193 return result; 194 } 195 } else if (scmSpecificUrl.startsWith("https")) { 196 if (!scmSpecificUrl.startsWith("https://")) { 197 result.messages.add("A svn 'http' url must be on the form 'https://'."); 198 199 return result; 200 } 201 } else if (scmSpecificUrl.startsWith("http")) { 202 if (!scmSpecificUrl.startsWith("http://")) { 203 result.messages.add("A svn 'http' url must be on the form 'http://'."); 204 205 return result; 206 } 207 } 208 // Support of tunnels: svn+xxx with xxx defined in subversion conf file 209 else if (scmSpecificUrl.startsWith("svn+")) { 210 if (scmSpecificUrl.indexOf("://") < 0) { 211 result.messages.add("A svn 'svn+xxx' url must be on the form 'svn+xxx://'."); 212 213 return result; 214 } else { 215 String tunnel = scmSpecificUrl.substring("svn+".length(), scmSpecificUrl.indexOf("://")); 216 217 // ssh is always an allowed tunnel 218 if (!"ssh".equals(tunnel)) { 219 SvnConfigFileReader reader = new SvnConfigFileReader(); 220 if (SvnUtil.getSettings().getConfigDirectory() != null) { 221 reader.setConfigDirectory(new File(SvnUtil.getSettings().getConfigDirectory())); 222 } 223 224 if (StringUtils.isEmpty(reader.getProperty("tunnels", tunnel))) { 225 result.messages.add( 226 "The tunnel '" + tunnel + "' isn't defined in your subversion configuration file."); 227 228 return result; 229 } 230 } 231 } 232 } else if (scmSpecificUrl.startsWith("svn")) { 233 if (!scmSpecificUrl.startsWith("svn://")) { 234 result.messages.add("A svn 'svn' url must be on the form 'svn://'."); 235 236 return result; 237 } 238 } else { 239 result.messages.add(scmSpecificUrl + " url isn't a valid svn URL."); 240 241 return result; 242 } 243 244 result.repository = new SvnScmProviderRepository(scmSpecificUrl); 245 246 return result; 247 } 248 249 protected abstract SvnCommand getAddCommand(); 250 251 /** 252 * {@inheritDoc} 253 */ 254 public AddScmResult add(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 255 throws ScmException { 256 return (AddScmResult) executeCommand(getAddCommand(), repository, fileSet, parameters); 257 } 258 259 protected abstract SvnCommand getBranchCommand(); 260 261 /** 262 * {@inheritDoc} 263 */ 264 protected BranchScmResult branch(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 265 throws ScmException { 266 return (BranchScmResult) executeCommand(getBranchCommand(), repository, fileSet, parameters); 267 } 268 269 protected abstract SvnCommand getChangeLogCommand(); 270 271 /** 272 * {@inheritDoc} 273 */ 274 public ChangeLogScmResult changelog( 275 ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException { 276 return (ChangeLogScmResult) executeCommand(getChangeLogCommand(), repository, fileSet, parameters); 277 } 278 279 protected abstract SvnCommand getCheckInCommand(); 280 281 /** 282 * {@inheritDoc} 283 */ 284 public CheckInScmResult checkin(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 285 throws ScmException { 286 return (CheckInScmResult) executeCommand(getCheckInCommand(), repository, fileSet, parameters); 287 } 288 289 protected abstract SvnCommand getCheckOutCommand(); 290 291 /** 292 * {@inheritDoc} 293 */ 294 public CheckOutScmResult checkout( 295 ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException { 296 return (CheckOutScmResult) executeCommand(getCheckOutCommand(), repository, fileSet, parameters); 297 } 298 299 protected abstract SvnCommand getDiffCommand(); 300 301 /** 302 * {@inheritDoc} 303 */ 304 public DiffScmResult diff(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 305 throws ScmException { 306 return (DiffScmResult) executeCommand(getDiffCommand(), repository, fileSet, parameters); 307 } 308 309 protected abstract SvnCommand getExportCommand(); 310 311 /** 312 * {@inheritDoc} 313 */ 314 protected ExportScmResult export(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 315 throws ScmException { 316 return (ExportScmResult) executeCommand(getExportCommand(), repository, fileSet, parameters); 317 } 318 319 protected abstract SvnCommand getRemoveCommand(); 320 321 /** 322 * {@inheritDoc} 323 */ 324 public RemoveScmResult remove(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 325 throws ScmException { 326 return (RemoveScmResult) executeCommand(getRemoveCommand(), repository, fileSet, parameters); 327 } 328 329 protected abstract SvnCommand getStatusCommand(); 330 331 /** 332 * {@inheritDoc} 333 */ 334 public StatusScmResult status(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 335 throws ScmException { 336 return (StatusScmResult) executeCommand(getStatusCommand(), repository, fileSet, parameters); 337 } 338 339 protected abstract SvnCommand getTagCommand(); 340 341 /** 342 * {@inheritDoc} 343 */ 344 public TagScmResult tag(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 345 throws ScmException { 346 return (TagScmResult) executeCommand(getTagCommand(), repository, fileSet, parameters); 347 } 348 349 protected abstract SvnCommand getUntagCommand(); 350 351 /** 352 * {@inheritDoc} 353 */ 354 @Override 355 public UntagScmResult untag(ScmRepository repository, ScmFileSet fileSet, CommandParameters parameters) 356 throws ScmException { 357 return (UntagScmResult) 358 executeCommand(getUntagCommand(), repository.getProviderRepository(), fileSet, parameters); 359 } 360 361 protected abstract SvnCommand getUpdateCommand(); 362 363 /** 364 * {@inheritDoc} 365 */ 366 public UpdateScmResult update(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 367 throws ScmException { 368 return (UpdateScmResult) executeCommand(getUpdateCommand(), repository, fileSet, parameters); 369 } 370 371 protected ScmResult executeCommand( 372 SvnCommand command, ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 373 throws ScmException { 374 return command.execute(repository, fileSet, parameters); 375 } 376 377 protected abstract SvnCommand getListCommand(); 378 379 /** 380 * {@inheritDoc} 381 */ 382 public ListScmResult list(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 383 throws ScmException { 384 SvnCommand cmd = getListCommand(); 385 386 return (ListScmResult) executeCommand(cmd, repository, fileSet, parameters); 387 } 388 389 protected abstract SvnCommand getInfoCommand(); 390 391 public InfoScmResult info(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 392 throws ScmException { 393 SvnCommand cmd = getInfoCommand(); 394 395 return (InfoScmResult) executeCommand(cmd, repository, fileSet, parameters); 396 } 397 398 /** 399 * {@inheritDoc} 400 */ 401 protected BlameScmResult blame(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 402 throws ScmException { 403 SvnCommand cmd = getBlameCommand(); 404 405 return (BlameScmResult) executeCommand(cmd, repository, fileSet, parameters); 406 } 407 408 protected abstract SvnCommand getBlameCommand(); 409 410 /** 411 * {@inheritDoc} 412 */ 413 public MkdirScmResult mkdir(ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) 414 throws ScmException { 415 SvnCommand cmd = getMkdirCommand(); 416 417 return (MkdirScmResult) executeCommand(cmd, repository, fileSet, parameters); 418 } 419 420 protected abstract SvnCommand getMkdirCommand(); 421 422 /** 423 * @param repository 424 * @param parameters 425 * @return true if remote url exists 426 * @throws ScmException 427 * @since 1.8 428 */ 429 public abstract boolean remoteUrlExist(ScmProviderRepository repository, CommandParameters parameters) 430 throws ScmException; 431}