001package org.apache.maven.scm.provider.accurev.cli;
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.ByteArrayInputStream;
023import java.io.File;
024import java.io.InputStream;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.regex.Pattern;
032
033import org.apache.maven.scm.command.blame.BlameLine;
034import org.apache.maven.scm.log.ScmLogger;
035import org.apache.maven.scm.provider.accurev.AccuRev;
036import org.apache.maven.scm.provider.accurev.AccuRevException;
037import org.apache.maven.scm.provider.accurev.AccuRevInfo;
038import org.apache.maven.scm.provider.accurev.AccuRevStat;
039import org.apache.maven.scm.provider.accurev.AccuRevVersion;
040import org.apache.maven.scm.provider.accurev.CategorisedElements;
041import org.apache.maven.scm.provider.accurev.FileDifference;
042import org.apache.maven.scm.provider.accurev.Stream;
043import org.apache.maven.scm.provider.accurev.Transaction;
044import org.apache.maven.scm.provider.accurev.WorkSpace;
045import org.codehaus.plexus.util.Os;
046import org.codehaus.plexus.util.StringUtils;
047import org.codehaus.plexus.util.cli.CommandLineException;
048import org.codehaus.plexus.util.cli.CommandLineUtils;
049import org.codehaus.plexus.util.cli.Commandline;
050import org.codehaus.plexus.util.cli.StreamConsumer;
051
052/**
053 * 
054 */
055public class AccuRevCommandLine
056    implements AccuRev
057{
058
059    private static final String[] EMPTY_STRING_ARRAY = new String[] {};
060
061    private static final File CURRENT_DIR = new File( "." );
062
063    private ScmLogger logger;
064
065    private Commandline cl = new Commandline();
066
067    private StringBuilder commandLines = new StringBuilder();
068
069    private StringBuilder errorOutput = new StringBuilder();
070
071    private StreamConsumer systemErr;
072
073    private String[] hostArgs = EMPTY_STRING_ARRAY;
074
075    private String[] authArgs = EMPTY_STRING_ARRAY;
076
077    private String executable = "accurev";
078
079    private long executableModTime;
080
081    private String clientVersion;
082
083    public AccuRevCommandLine()
084    {
085        super();
086        reset();
087    }
088
089    public AccuRevCommandLine( String host, int port )
090    {
091        this();
092        setServer( host, port );
093    }
094
095    public void setServer( String host, int port )
096    {
097
098        if ( host != null )
099        {
100            hostArgs = new String[] { "-H", host + ":" + port };
101        }
102        else
103        {
104            hostArgs = EMPTY_STRING_ARRAY;
105        }
106
107    }
108
109    public void setExecutable( String accuRevExe )
110    {
111
112        executable = accuRevExe;
113        reset();
114    }
115
116    private boolean executeCommandLine( File basedir, String[] args, Iterable<File> elements, Pattern matchPattern,
117                                        List<File> matchedFiles )
118        throws AccuRevException
119    {
120
121        FileConsumer stdoutConsumer = new FileConsumer( matchedFiles, matchPattern );
122
123        return executeCommandLine( basedir, args, elements, stdoutConsumer );
124    }
125
126    private boolean executeCommandLine( File basedir, String[] args, Iterable<File> elements,
127                                        StreamConsumer stdoutConsumer )
128        throws AccuRevException
129    {
130
131        setWorkingDirectory( basedir );
132        setCommandLineArgs( args );
133
134        if ( elements != null )
135        {
136            for ( File file : elements )
137            {
138                String path = file.getPath();
139                // Hack for Windows "/./". TODO find a nicer way to handle this.
140                if ( "\\.".equals( path ) )
141                {
142                    path = "\\.\\";
143                }
144                cl.createArg().setValue( path );
145            }
146        }
147        return executeCommandLine( null, stdoutConsumer ) == 0;
148    }
149
150    private void setCommandLineArgs( String[] args )
151    {
152
153        cl.clearArgs();
154
155        if ( args.length > 0 )
156        {
157            // First arg is the accurev command
158            cl.createArg().setValue( args[0] );
159
160            // Inject -H <host:port> and -A <token> here
161            cl.addArguments( hostArgs );
162            cl.addArguments( authArgs );
163        }
164
165        for ( int i = 1; i < args.length; i++ )
166        {
167            cl.createArg().setValue( args[i] );
168        }
169
170    }
171
172    private boolean executeCommandLine( String[] args )
173        throws AccuRevException
174    {
175
176        return executeCommandLine( args, null, null ) == 0;
177    }
178
179    private int executeCommandLine( String[] args, InputStream stdin, StreamConsumer stdout )
180        throws AccuRevException
181    {
182
183        setCommandLineArgs( args );
184
185        return executeCommandLine( stdin, stdout );
186
187    }
188
189    private int executeCommandLine( InputStream stdin, StreamConsumer stdout )
190        throws AccuRevException
191    {
192
193        commandLines.append( cl.toString() );
194        commandLines.append( ';' );
195
196        if ( getLogger().isDebugEnabled() )
197        {
198            getLogger().debug( cl.toString() );
199        }
200        try
201        {
202
203            int result = executeCommandLine( cl, stdin, new CommandOutputConsumer( getLogger(), stdout ), systemErr );
204            if ( result != 0 )
205            {
206                getLogger().debug( "Non zero result - " + result );
207            }
208            return result;
209        }
210        catch ( CommandLineException ex )
211        {
212            throw new AccuRevException( "Error executing command " + cl.toString(), ex );
213        }
214
215    }
216
217    /**
218     * Extracted so test class can override
219     * 
220     * @param stdin
221     * @param stdout
222     * @param stderr
223     * @return
224     * @throws CommandLineException
225     */
226    protected int executeCommandLine( Commandline cl, InputStream stdin, CommandOutputConsumer stdout,
227                                      StreamConsumer stderr )
228        throws CommandLineException
229    {
230
231        int result = CommandLineUtils.executeCommandLine( cl, stdin, stdout, stderr );
232        stdout.waitComplete();
233
234        return result;
235    }
236
237    protected Commandline getCommandline()
238    {
239
240        return cl;
241    }
242
243    public void reset()
244    {
245
246        // TODO find out why Commandline allows executable, args etc to be initialised to
247        // null, but not allowing them to be reset to null. This results is weird "clear"
248        // behaviour. It is just safer to start again.
249
250        cl = new Commandline();
251        commandLines = new StringBuilder();
252        errorOutput = new StringBuilder();
253        systemErr = new ErrorConsumer( getLogger(), errorOutput );
254        cl.getShell().setQuotedArgumentsEnabled( true );
255        cl.setExecutable( executable );
256
257        try
258        {
259            cl.addSystemEnvironment();
260        }
261        catch ( Exception e )
262        {
263            if ( getLogger().isDebugEnabled() )
264            {
265                getLogger().debug( "Unable to obtain system environment", e );
266            }
267            else
268            {
269                getLogger().warn( "Unable to obtain system environment" );
270            }
271        }
272    }
273
274    /**
275     * {@inheritDoc}
276     */
277    public boolean mkws( String basisStream, String workspaceName, File basedir )
278        throws AccuRevException
279    {
280
281        setWorkingDirectory( basedir );
282        String[] mkws = { "mkws", "-b", basisStream, "-w", workspaceName, "-l", basedir.getAbsolutePath() };
283
284        return executeCommandLine( mkws );
285    }
286
287    /**
288     * {@inheritDoc}
289     */
290    public List<File> update( File baseDir, String transactionId )
291        throws AccuRevException
292    {
293
294        if ( transactionId == null )
295        {
296            transactionId = "highest";
297        }
298        String[] update = { "update", "-t", transactionId };
299        setWorkingDirectory( baseDir );
300
301        List<File> updatedFiles = new ArrayList<File>();
302        int ret = executeCommandLine( update, null, new FileConsumer( updatedFiles, FileConsumer.UPDATE_PATTERN ) );
303        return ret == 0 ? updatedFiles : null;
304
305    }
306
307    /**
308     * {@inheritDoc}
309     */
310    public List<File> add( File basedir, List<File> elements, String message )
311        throws AccuRevException
312    {
313
314        if ( StringUtils.isBlank( message ) )
315        {
316            message = AccuRev.DEFAULT_ADD_MESSAGE;
317        }
318
319        boolean recursive = false;
320
321        if ( elements == null || elements.isEmpty() )
322        {
323            elements = Collections.singletonList( CURRENT_DIR );
324            recursive = true;
325        }
326        else if ( elements.size() == 1 && elements.toArray()[0].equals( CURRENT_DIR ) )
327        {
328            recursive = true;
329        }
330
331        List<File> addedFiles = new ArrayList<File>();
332        return executeCommandLine( basedir, new String[] { "add", "-c", message, recursive ? "-R" : null }, elements,
333                                   FileConsumer.ADD_PATTERN, addedFiles ) ? addedFiles : null;
334
335    }
336
337    public List<File> defunct( File basedir, List<File> files, String message )
338        throws AccuRevException
339    {
340
341        if ( StringUtils.isBlank( message ) )
342        {
343            message = AccuRev.DEFAULT_REMOVE_MESSAGE;
344        }
345
346        if ( files == null || files.isEmpty() )
347        {
348            files = Collections.singletonList( CURRENT_DIR );
349        }
350
351        ArrayList<File> defunctFiles = new ArrayList<File>();
352        return executeCommandLine( basedir, new String[] { "defunct", "-c", message }, files,
353                                   FileConsumer.DEFUNCT_PATTERN, defunctFiles ) ? defunctFiles : null;
354    }
355
356    public List<File> promote( File basedir, List<File> files, String message )
357        throws AccuRevException
358    {
359
360        if ( StringUtils.isBlank( message ) )
361        {
362            message = AccuRev.DEFAULT_PROMOTE_MESSAGE;
363        }
364        List<File> promotedFiles = new ArrayList<File>();
365        return executeCommandLine( basedir, new String[] { "promote", "-K", "-c", message }, files,
366                                   FileConsumer.PROMOTE_PATTERN, promotedFiles ) ? promotedFiles : null;
367
368    }
369
370    public String getCommandLines()
371    {
372
373        return commandLines.toString();
374    }
375
376    public String getErrorOutput()
377    {
378
379        return errorOutput.toString();
380    }
381
382    public void setLogger( ScmLogger logger )
383    {
384        this.logger = logger;
385    }
386
387    public ScmLogger getLogger()
388    {
389
390        return logger;
391    }
392
393    public boolean mkdepot( String depotName )
394        throws AccuRevException
395    {
396
397        String[] mkdepot = { "mkdepot", "-p", depotName };
398
399        return executeCommandLine( mkdepot );
400
401    }
402
403    public boolean mkstream( String backingStream, String newStreamName )
404        throws AccuRevException
405    {
406        String[] mkstream = { "mkstream", "-b", backingStream, "-s", newStreamName };
407        return executeCommandLine( mkstream );
408
409    }
410
411    public boolean promoteStream( String subStream, String commitMessage, List<File> promotedFiles )
412        throws AccuRevException
413    {
414        String[] promote = { "promote", "-s", subStream, "-d" };
415        return executeCommandLine( promote );
416
417    }
418
419    /**
420     * {@inheritDoc}
421     */
422    public List<File> promoteAll( File baseDir, String commitMessage )
423        throws AccuRevException
424    {
425        setWorkingDirectory( baseDir );
426        String[] promote = { "promote", "-p", "-K", "-c", commitMessage };
427
428        List<File> promotedFiles = new ArrayList<File>();
429        int ret = executeCommandLine( promote, null, new FileConsumer( promotedFiles, FileConsumer.PROMOTE_PATTERN ) );
430        return ret == 0 ? promotedFiles : null;
431    }
432
433    public AccuRevInfo info( File basedir )
434        throws AccuRevException
435    {
436
437        setWorkingDirectory( basedir );
438        String[] info = { "info" };
439        AccuRevInfo result = new AccuRevInfo( basedir );
440
441        executeCommandLine( info, null, new InfoConsumer( result ) );
442        return result;
443    }
444
445    private void setWorkingDirectory( File basedir )
446    {
447
448        // TODO raise bug against plexus. Null is OK for working directory
449        // but once set to not-null cannot be set back to null!
450        // this is a problem if the old workingdir has been deleted
451        // probably safer to use a new commandline
452
453        if ( basedir == null )
454        {
455            cl.setWorkingDirectory( "." );
456        }
457        cl.setWorkingDirectory( basedir );
458    }
459
460    public boolean reactivate( String workSpaceName )
461        throws AccuRevException
462    {
463
464        String[] reactivate = { "reactivate", "wspace", workSpaceName };
465
466        return executeCommandLine( reactivate, null, new CommandOutputConsumer( getLogger(), null ) ) == 0;
467
468    }
469
470    public boolean rmws( String workSpaceName )
471        throws AccuRevException
472    {
473
474        String[] rmws = { "rmws", "-s", workSpaceName };
475
476        return executeCommandLine( rmws );
477
478    }
479
480    public String stat( File element )
481        throws AccuRevException
482    {
483
484        String[] stat = { "stat", "-fx", element.getAbsolutePath() };
485
486        StatConsumer statConsumer = new StatConsumer( getLogger() );
487        executeCommandLine( stat, null, statConsumer );
488        return statConsumer.getStatus();
489
490    }
491
492    public boolean chws( File basedir, String workSpaceName, String newBasisStream )
493        throws AccuRevException
494    {
495
496        setWorkingDirectory( basedir );
497        return executeCommandLine( new String[] { "chws", "-s", workSpaceName, "-b", newBasisStream, "-l", "." } );
498
499    }
500
501    public boolean mksnap( String snapShotName, String basisStream )
502        throws AccuRevException
503    {
504        return executeCommandLine( new String[] { "mksnap", "-s", snapShotName, "-b", basisStream, "-t", "now" } );
505    }
506
507    public List<File> statTag( String streamName )
508        throws AccuRevException
509    {
510
511        List<File> taggedFiles = new ArrayList<File>();
512        String[] stat = new String[] { "stat", "-a", "-ffl", "-s", streamName };
513        return executeCommandLine( null, stat, null, FileConsumer.STAT_PATTERN, taggedFiles ) ? taggedFiles : null;
514    }
515
516    public List<File> stat( File basedir, Collection<File> elements, AccuRevStat statType )
517        throws AccuRevException
518    {
519
520        boolean recursive = false;
521
522        if ( elements == null || elements.isEmpty() )
523        {
524            elements = Collections.singletonList( CURRENT_DIR );
525            recursive = true;
526        }
527        else if ( elements.size() == 1 && elements.toArray()[0].equals( CURRENT_DIR ) )
528        {
529            recursive = true;
530        }
531
532        String[] args = { "stat", "-ffr", statType.getStatArg(), recursive ? "-R" : null };
533
534        List<File> matchingElements = new ArrayList<File>();
535        boolean ret = executeCommandLine( basedir, args, elements, statType.getMatchPattern(), matchingElements );
536        return ret ? matchingElements : null;
537    }
538
539    public List<File> pop( File basedir, Collection<File> elements )
540        throws AccuRevException
541    {
542
543        if ( elements == null || elements.isEmpty() )
544        {
545            elements = Collections.singletonList( CURRENT_DIR );
546        }
547
548        String[] popws = { "pop", "-R" };
549
550        List<File> poppedFiles = new ArrayList<File>();
551        boolean ret = executeCommandLine( basedir, popws, elements, FileConsumer.POPULATE_PATTERN, poppedFiles );
552        return ret ? poppedFiles : null;
553    }
554
555    public List<File> popExternal( File basedir, String versionSpec, String tranSpec, Collection<File> elements )
556        throws AccuRevException
557    {
558
559        if ( elements == null || elements.isEmpty() )
560        {
561            elements = Collections.singletonList( new File( "/./" ) );
562        }
563
564        if ( StringUtils.isBlank( tranSpec ) )
565        {
566            tranSpec = "now";
567        }
568
569        String[] popArgs;
570        if ( AccuRevVersion.isNow( tranSpec ) )
571        {
572            popArgs = new String[] { "pop", "-v", versionSpec, "-L", basedir.getAbsolutePath(), "-R" };
573        }
574        else
575        // this will BARF for pre 4.9.0, but clients are expected to check AccuRevCapability before calling.
576        {
577            popArgs = new String[] { "pop", "-v", versionSpec, "-L", basedir.getAbsolutePath(), "-t", tranSpec, "-R" };
578        }
579
580        List<File> poppedFiles = new ArrayList<File>();
581        boolean ret = executeCommandLine( basedir, popArgs, elements, FileConsumer.POPULATE_PATTERN, poppedFiles );
582        return ret ? poppedFiles : null;
583    }
584
585    public CategorisedElements statBackingStream( File basedir, Collection<File> elements )
586        throws AccuRevException
587    {
588
589        CategorisedElements catElems = new CategorisedElements();
590
591        if ( elements.isEmpty() )
592        {
593            return catElems;
594        }
595        String[] args = { "stat", "-b", "-ffr" };
596
597        boolean ret =
598            executeCommandLine( basedir, args, elements, new StatBackingConsumer( catElems.getMemberElements(),
599                                                                                  catElems.getNonMemberElements() ) );
600        return ret ? catElems : null;
601
602    }
603
604    public List<Transaction> history( String baseStream, String fromTimeSpec, String toTimeSpec, int count,
605                                      boolean depotHistory, boolean transactionsOnly )
606        throws AccuRevException
607    {
608
609        String timeSpec = fromTimeSpec;
610
611        if ( toTimeSpec != null )
612        {
613            timeSpec = timeSpec + "-" + toTimeSpec;
614        }
615
616        if ( count > 0 )
617        {
618            timeSpec = timeSpec + "." + count;
619        }
620
621        String[] hist =
622            { "hist", transactionsOnly ? "-ftx" : "-fx", depotHistory ? "-p" : "-s", baseStream, "-t", timeSpec };
623
624        ArrayList<Transaction> transactions = new ArrayList<Transaction>();
625        HistoryConsumer stdout = new HistoryConsumer( getLogger(), transactions );
626        return executeCommandLine( hist, null, stdout ) == 0 ? transactions : null;
627    }
628
629    public List<FileDifference> diff( String baseStream, String fromTimeSpec, String toTimeSpec )
630        throws AccuRevException
631    {
632        String timeSpec = fromTimeSpec + "-" + toTimeSpec;
633        String[] diff = { "diff", "-fx", "-a", "-i", "-v", baseStream, "-V", baseStream, "-t", timeSpec };
634
635        List<FileDifference> results = new ArrayList<FileDifference>();
636        DiffConsumer stdout = new DiffConsumer( getLogger(), results );
637        return executeCommandLine( diff, null, stdout ) < 2 ? results : null;
638    }
639
640    public boolean login( String user, String password )
641        throws AccuRevException
642    {
643
644        // TODO Raise bug against plexus commandline - can't set workingdir to null
645        // and will get an error if the working directory is deleted.
646        cl.setWorkingDirectory( "." );
647        authArgs = EMPTY_STRING_ARRAY;
648        AuthTokenConsumer stdout = new AuthTokenConsumer();
649
650        boolean result;
651        if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
652        {
653            if ( StringUtils.isBlank( password ) )
654            {
655                // Ensure blank is passed in.
656                password = "\"\"";
657            }
658            String[] login = { "login", "-A", user, password };
659            result = executeCommandLine( login, null, stdout ) == 0;
660        }
661        else
662        {
663            String[] login = { "login", "-A", user };
664            password = StringUtils.clean( password ) + "\n";
665            byte[] bytes = password.getBytes();
666            ByteArrayInputStream stdin = new ByteArrayInputStream( bytes );
667            result = executeCommandLine( login, stdin, stdout ) == 0;
668
669        }
670
671        authArgs = new String[] { "-A", stdout.getAuthToken() };
672        return result;
673    }
674
675    public boolean logout()
676        throws AccuRevException
677    {
678
679        String[] logout = { "logout" };
680        return executeCommandLine( logout );
681
682    }
683
684    public List<BlameLine> annotate( File basedir, File file )
685        throws AccuRevException
686    {
687
688        String[] annotate = { "annotate", "-ftud" };
689        List<BlameLine> lines = new ArrayList<BlameLine>();
690        AnnotateConsumer stdout = new AnnotateConsumer( lines, getLogger() );
691
692        return executeCommandLine( basedir, annotate, Collections.singletonList( file ), stdout ) ? lines : null;
693    }
694
695    public Map<String, WorkSpace> showRefTrees()
696        throws AccuRevException
697    {
698
699        String[] show = { "show", "-fx", "refs" };
700        Map<String, WorkSpace> refTrees = new HashMap<String, WorkSpace>();
701        WorkSpaceConsumer stdout = new WorkSpaceConsumer( getLogger(), refTrees );
702        return executeCommandLine( show, null, stdout ) == 0 ? refTrees : null;
703    }
704
705    public Map<String, WorkSpace> showWorkSpaces()
706        throws AccuRevException
707    {
708
709        String[] show = { "show", "-a", "-fx", "wspaces" };
710        Map<String, WorkSpace> workSpaces = new HashMap<String, WorkSpace>();
711        WorkSpaceConsumer stdout = new WorkSpaceConsumer( getLogger(), workSpaces );
712        return executeCommandLine( show, null, stdout ) == 0 ? workSpaces : null;
713    }
714
715    public Stream showStream( String stream )
716        throws AccuRevException
717    {
718        String[] show = { "show", "-s", stream, "-fx", "streams" };
719        List<Stream> streams = new ArrayList<Stream>();
720        StreamsConsumer stdout = new StreamsConsumer( getLogger(), streams );
721
722        return executeCommandLine( show, null, stdout ) == 0 && streams.size() == 1 ? streams.get( 0 ) : null;
723    }
724
725    public String getExecutable()
726    {
727
728        return executable;
729    }
730
731    public String getClientVersion()
732        throws AccuRevException
733    {
734
735        long lastModified = new File( getExecutable() ).lastModified();
736        if ( clientVersion == null || executableModTime != lastModified )
737        {
738            executableModTime = lastModified;
739
740            ClientVersionConsumer stdout = new ClientVersionConsumer();
741            executeCommandLine( new String[] {}, null, stdout );
742            clientVersion = stdout.getClientVersion();
743        }
744        return clientVersion;
745
746    }
747
748    public boolean syncReplica()
749        throws AccuRevException
750    {
751        return executeCommandLine( new String[] { "replica", "sync" } );
752    }
753
754}