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