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