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