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}