001package org.apache.maven.scm.provider.svn.svnexe.command;
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.io.FileOutputStream;
024import java.io.IOException;
025import java.io.PrintStream;
026import java.util.List;
027
028import org.apache.maven.scm.log.ScmLogger;
029import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
030import org.apache.maven.scm.provider.svn.util.SvnUtil;
031import org.codehaus.plexus.util.Os;
032import org.codehaus.plexus.util.StringUtils;
033import org.codehaus.plexus.util.cli.CommandLineException;
034import org.codehaus.plexus.util.cli.CommandLineUtils;
035import org.codehaus.plexus.util.cli.Commandline;
036import org.codehaus.plexus.util.cli.StreamConsumer;
037
038/**
039 * Command line construction utility.
040 *
041 * @author Brett Porter
042 * @author Olivier Lamy
043 *
044 */
045public final class SvnCommandLineUtils
046{
047    private SvnCommandLineUtils()
048    {
049    }
050
051    public static void addTarget( Commandline cl, List<File> files )
052        throws IOException
053    {
054        if ( files == null || files.isEmpty() )
055        {
056            return;
057        }
058
059        StringBuilder sb = new StringBuilder();
060        String ls = System.getProperty( "line.separator" );
061        for ( File f : files )
062        {
063            sb.append( f.getPath().replace( '\\', '/' ) );
064            sb.append( ls );
065        }
066
067        File targets = File.createTempFile( "maven-scm-", "-targets" );
068        PrintStream out = new PrintStream( new FileOutputStream( targets ) );
069        out.print( sb.toString() );
070        out.flush();
071        out.close();
072
073        cl.createArg().setValue( "--targets" );
074        cl.createArg().setValue( targets.getAbsolutePath() );
075
076        targets.deleteOnExit();
077    }
078
079    public static Commandline getBaseSvnCommandLine( File workingDirectory, SvnScmProviderRepository repository )
080    {
081        Commandline cl = new Commandline();
082
083        cl.setExecutable( "svn" );
084        try
085        {
086            cl.addSystemEnvironment();
087            cl.addEnvironment( "LC_MESSAGES", "C" );
088        }
089        catch ( Exception e )
090        {
091            //Do nothing
092        }
093
094        if ( workingDirectory != null )
095        {
096            cl.setWorkingDirectory( workingDirectory.getAbsolutePath() );
097        }
098
099        if ( !StringUtils.isEmpty( System.getProperty( "maven.scm.svn.config_directory" ) ) )
100        {
101            cl.createArg().setValue( "--config-dir" );
102            cl.createArg().setValue( System.getProperty( "maven.scm.svn.config_directory" ) );
103        }
104        else if ( !StringUtils.isEmpty( SvnUtil.getSettings().getConfigDirectory() ) )
105        {
106            cl.createArg().setValue( "--config-dir" );
107            cl.createArg().setValue( SvnUtil.getSettings().getConfigDirectory() );
108        }
109
110        boolean hasAuthInfo = false;
111        if ( repository != null && !StringUtils.isEmpty( repository.getUser() ) )
112        {
113            hasAuthInfo = true;
114            cl.createArg().setValue( "--username" );
115            cl.createArg().setValue( repository.getUser() );
116        }
117
118        if ( repository != null && !StringUtils.isEmpty( repository.getPassword() ) )
119        {
120            hasAuthInfo = true;
121            cl.createArg().setValue( "--password" );
122            cl.createArg().setValue( repository.getPassword() );
123        }
124
125        // [by Lenik] don't overwrite existing auth cache by default.
126        if ( hasAuthInfo && !SvnUtil.getSettings().isUseAuthCache() )
127        {
128            cl.createArg().setValue( "--no-auth-cache" );
129        }
130
131        if ( SvnUtil.getSettings().isUseNonInteractive() )
132        {
133            cl.createArg().setValue( "--non-interactive" );
134        }
135
136        if ( SvnUtil.getSettings().isTrustServerCert() )
137        {
138            cl.createArg().setValue( "--trust-server-cert" );
139        }
140
141        return cl;
142    }
143
144    public static int execute( Commandline cl, StreamConsumer consumer, CommandLineUtils.StringStreamConsumer stderr,
145                               ScmLogger logger )
146        throws CommandLineException
147    {
148        // SCM-482: force English resource bundle
149        cl.addEnvironment( "LC_MESSAGES", "en" );
150
151        int exitCode = CommandLineUtils.executeCommandLine( cl, consumer, stderr );
152
153        exitCode = checkIfCleanUpIsNeeded( exitCode, cl, consumer, stderr, logger );
154
155        return exitCode;
156    }
157
158    public static int execute( Commandline cl, CommandLineUtils.StringStreamConsumer stdout,
159                               CommandLineUtils.StringStreamConsumer stderr, ScmLogger logger )
160        throws CommandLineException
161    {
162        int exitCode = CommandLineUtils.executeCommandLine( cl, stdout, stderr );
163
164        exitCode = checkIfCleanUpIsNeeded( exitCode, cl, stdout, stderr, logger );
165
166        return exitCode;
167    }
168
169    private static int checkIfCleanUpIsNeeded( int exitCode, Commandline cl, StreamConsumer consumer,
170                                               CommandLineUtils.StringStreamConsumer stderr, ScmLogger logger )
171        throws CommandLineException
172    {
173        if ( exitCode != 0 && stderr.getOutput() != null && stderr.getOutput().indexOf( "'svn cleanup'" ) > 0
174            && stderr.getOutput().indexOf( "'svn help cleanup'" ) > 0 )
175        {
176            if ( logger.isInfoEnabled() )
177            {
178                logger.info( "Svn command failed due to some locks in working copy. We try to run a 'svn cleanup'." );
179            }
180
181            if ( executeCleanUp( cl.getWorkingDirectory(), consumer, stderr, logger ) == 0 )
182            {
183                exitCode = CommandLineUtils.executeCommandLine( cl, consumer, stderr );
184            }
185        }
186        return exitCode;
187    }
188
189    public static int executeCleanUp( File workinDirectory, StreamConsumer stdout, StreamConsumer stderr )
190        throws CommandLineException
191    {
192        return executeCleanUp( workinDirectory, stdout, stderr, null );
193    }
194
195    public static int executeCleanUp( File workinDirectory, StreamConsumer stdout, StreamConsumer stderr,
196                                      ScmLogger logger )
197        throws CommandLineException
198    {
199        Commandline cl = new Commandline();
200
201        cl.setExecutable( "svn" );
202
203        cl.setWorkingDirectory( workinDirectory.getAbsolutePath() );
204
205        if ( logger != null )
206        {
207            if ( logger.isInfoEnabled() )
208            {
209                logger.info( "Executing: " + SvnCommandLineUtils.cryptPassword( cl ) );
210
211                if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
212                {
213                    logger.info( "Working directory: " + cl.getWorkingDirectory().getAbsolutePath() );
214                }
215            }
216        }
217
218        return CommandLineUtils.executeCommandLine( cl, stdout, stderr );
219    }
220
221    public static String cryptPassword( Commandline cl )
222    {
223        String clString = cl.toString();
224
225        final String passwordOpt = "--password";
226        String quoteChar;
227        String escapedQuoteChar;
228        String cryptedPassword;
229
230        int pos = clString.indexOf( passwordOpt );
231
232        if ( pos > 0 )
233        {
234           if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
235           {
236                quoteChar = "\"";
237                escapedQuoteChar = "\"\"";
238                cryptedPassword = "*****";
239           }
240           else
241           {
242               quoteChar = "'";
243               escapedQuoteChar = "'\"'\"'";
244               cryptedPassword = "'*****'";
245           }
246
247           // Move pointer after password option
248           pos += passwordOpt.length();
249
250           // Skip quote after password option
251           if ( clString.substring( pos,  pos + 1 ).equals( quoteChar ) )
252           {
253               pos++;
254           }
255
256           // Skip space after password option
257           pos++;
258
259            String beforePassword = clString.substring( 0, pos );
260            String afterPassword = clString.substring( pos );
261
262            if ( afterPassword.startsWith( quoteChar ) )
263            {
264                pos = 1;
265                while ( afterPassword.indexOf( escapedQuoteChar, pos ) != -1 )
266                {
267                    pos = afterPassword.indexOf( escapedQuoteChar, pos ) + escapedQuoteChar.length();
268                }
269                afterPassword = afterPassword.substring ( afterPassword.indexOf( quoteChar, pos )
270                                                          + quoteChar.length() );
271            }
272            else
273            {
274                // We assume that the password arg ist not the last one on the arg list
275                afterPassword = afterPassword.substring( afterPassword.indexOf( ' ' ) );
276            }
277
278            clString = beforePassword + cryptedPassword + afterPassword;
279
280        }
281
282        return clString;
283    }
284}