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