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}