001package org.apache.maven.scm.provider.git.gitexe.command.checkout;
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 org.apache.maven.scm.ScmBranch;
023import org.apache.maven.scm.ScmException;
024import org.apache.maven.scm.ScmFileSet;
025import org.apache.maven.scm.ScmFileStatus;
026import org.apache.maven.scm.ScmTag;
027import org.apache.maven.scm.ScmVersion;
028import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
029import org.apache.maven.scm.command.checkout.CheckOutScmResult;
030import org.apache.maven.scm.command.remoteinfo.RemoteInfoScmResult;
031import org.apache.maven.scm.provider.ScmProviderRepository;
032import org.apache.maven.scm.provider.git.command.GitCommand;
033import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils;
034import org.apache.maven.scm.provider.git.gitexe.command.list.GitListCommand;
035import org.apache.maven.scm.provider.git.gitexe.command.list.GitListConsumer;
036import org.apache.maven.scm.provider.git.gitexe.command.remoteinfo.GitRemoteInfoCommand;
037import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
038import org.codehaus.plexus.util.StringUtils;
039import org.codehaus.plexus.util.cli.CommandLineUtils;
040import org.codehaus.plexus.util.cli.Commandline;
041
042import java.io.File;
043
044/**
045 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
046 *
047 */
048public class GitCheckOutCommand
049    extends AbstractCheckOutCommand
050    implements GitCommand
051{
052    /**
053     * For git, the given repository is a remote one.
054     * We have to clone it first if the working directory does not contain a git repo yet,
055     * otherwise we have to git-pull it.
056     * <p/>
057     * TODO We currently assume a '.git' directory, so this does not work for --bare repos
058     * {@inheritDoc}
059     */
060    protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repo, ScmFileSet fileSet,
061                                                        ScmVersion version, boolean recursive )
062        throws ScmException
063    {
064        GitScmProviderRepository repository = (GitScmProviderRepository) repo;
065
066        if ( GitScmProviderRepository.PROTOCOL_FILE.equals( repository.getFetchInfo().getProtocol() )
067            && repository.getFetchInfo().getPath().indexOf( fileSet.getBasedir().getPath() ) >= 0 )
068        {
069            throw new ScmException( "remote repository must not be the working directory" );
070        }
071
072        int exitCode;
073
074        CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
075        CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
076
077        String lastCommandLine = "git-nothing-to-do";
078
079        if ( !fileSet.getBasedir().exists() || !( new File( fileSet.getBasedir(), ".git" ).exists() ) )
080        {
081            if ( fileSet.getBasedir().exists() )
082            {
083                // git refuses to clone otherwise
084                fileSet.getBasedir().delete();
085            }
086
087            // no git repo seems to exist, let's clone the original repo
088            Commandline clClone = createCloneCommand( repository, fileSet.getBasedir(), version );
089
090            exitCode = GitCommandLineUtils.execute( clClone, stdout, stderr, getLogger() );
091            if ( exitCode != 0 )
092            {
093                return new CheckOutScmResult( clClone.toString(), "The git-clone command failed.", stderr.getOutput(),
094                                              false );
095            }
096            lastCommandLine = clClone.toString();
097        }
098
099        GitRemoteInfoCommand gitRemoteInfoCommand = new GitRemoteInfoCommand();
100        gitRemoteInfoCommand.setLogger( getLogger() );
101        RemoteInfoScmResult result = gitRemoteInfoCommand.executeRemoteInfoCommand( repository, null, null );
102
103        if ( fileSet.getBasedir().exists() && new File( fileSet.getBasedir(), ".git" ).exists()
104            && result.getBranches().size() > 0 )
105        {
106            // git repo exists, so we must git-pull the changes
107            Commandline clPull = createPullCommand( repository, fileSet.getBasedir(), version );
108
109            exitCode = GitCommandLineUtils.execute( clPull, stdout, stderr, getLogger() );
110            if ( exitCode != 0 )
111            {
112                return new CheckOutScmResult( clPull.toString(), "The git-pull command failed.", stderr.getOutput(),
113                                              false );
114            }
115            lastCommandLine = clPull.toString();
116
117            // and now lets do the git-checkout itself
118            Commandline clCheckout = createCommandLine( repository, fileSet.getBasedir(), version );
119
120            exitCode = GitCommandLineUtils.execute( clCheckout, stdout, stderr, getLogger() );
121            if ( exitCode != 0 )
122            {
123                return new CheckOutScmResult( clCheckout.toString(), "The git-checkout command failed.",
124                                              stderr.getOutput(), false );
125            }
126            lastCommandLine = clCheckout.toString();
127        }
128
129        // and now search for the files
130        GitListConsumer listConsumer =
131            new GitListConsumer( getLogger(), fileSet.getBasedir(), ScmFileStatus.CHECKED_IN );
132
133        Commandline clList = GitListCommand.createCommandLine( repository, fileSet.getBasedir() );
134
135        exitCode = GitCommandLineUtils.execute( clList, listConsumer, stderr, getLogger() );
136        if ( exitCode != 0 )
137        {
138            return new CheckOutScmResult( clList.toString(), "The git-ls-files command failed.", stderr.getOutput(),
139                                          false );
140        }
141
142        return new CheckOutScmResult( lastCommandLine, listConsumer.getListedFiles() );
143    }
144
145    // ----------------------------------------------------------------------
146    //
147    // ----------------------------------------------------------------------
148
149    public static Commandline createCommandLine( GitScmProviderRepository repository, File workingDirectory,
150                                                 ScmVersion version )
151    {
152        Commandline cl = GitCommandLineUtils.getBaseGitCommandLine( workingDirectory, "checkout" );
153
154        if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
155        {
156            cl.createArg().setValue( version.getName() );
157        }
158
159        return cl;
160    }
161
162    /**
163     * create a git-clone repository command
164     */
165    private Commandline createCloneCommand( GitScmProviderRepository repository, File workingDirectory,
166                                            ScmVersion version )
167    {
168        Commandline cl = GitCommandLineUtils.getBaseGitCommandLine( workingDirectory.getParentFile(), "clone" );
169
170        if ( version != null && ( version instanceof ScmBranch ) )
171        {
172
173            cl.createArg().setValue( "--branch" );
174
175            cl.createArg().setValue( version.getName() );
176        }
177
178        cl.createArg().setValue( repository.getFetchUrl() );
179
180        cl.createArg().setFile( workingDirectory );
181
182        return cl;
183    }
184
185    /**
186     * create a git-pull repository command
187     */
188    private Commandline createPullCommand( GitScmProviderRepository repository, File workingDirectory,
189                                           ScmVersion version )
190    {
191        Commandline cl;
192
193        if ( version != null && StringUtils.isNotEmpty( version.getName() ) )
194        {
195            if ( version instanceof ScmTag )
196            {
197                // A tag will not be pulled but we only fetch all the commits from the upstream repo
198                // This is done because checking out a tag might not happen on the current branch
199                // but create a 'detached HEAD'.
200                // In fact, a tag in git may be in multiple branches. This occurs if 
201                // you create a branch after the tag has been created 
202                cl = GitCommandLineUtils.getBaseGitCommandLine( workingDirectory, "fetch" );
203
204                cl.createArg().setValue( repository.getFetchUrl() );
205            }
206            else
207            {
208                cl = GitCommandLineUtils.getBaseGitCommandLine( workingDirectory, "pull" );
209
210                cl.createArg().setValue( repository.getFetchUrl() );
211
212                cl.createArg().setValue( version.getName() + ":" + version.getName() );
213            }
214        }
215        else
216        {
217            cl = GitCommandLineUtils.getBaseGitCommandLine( workingDirectory, "pull" );
218
219            cl.createArg().setValue( repository.getFetchUrl() );
220            cl.createArg().setValue( "master" );
221        }
222        return cl;
223    }
224}