001package org.apache.maven.scm.provider.git.jgit.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.ScmException;
023import org.apache.maven.scm.ScmFile;
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.jgit.command.JGitUtils;
034import org.apache.maven.scm.provider.git.jgit.command.branch.JGitBranchCommand;
035import org.apache.maven.scm.provider.git.jgit.command.remoteinfo.JGitRemoteInfoCommand;
036import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
037import org.codehaus.plexus.util.StringUtils;
038import org.eclipse.jgit.api.CloneCommand;
039import org.eclipse.jgit.api.Git;
040import org.eclipse.jgit.lib.Constants;
041import org.eclipse.jgit.lib.ProgressMonitor;
042import org.eclipse.jgit.revwalk.RevCommit;
043import org.eclipse.jgit.revwalk.RevWalk;
044import org.eclipse.jgit.storage.file.WindowCacheConfig;
045import org.eclipse.jgit.transport.CredentialsProvider;
046import org.eclipse.jgit.treewalk.TreeWalk;
047
048import java.io.File;
049import java.util.ArrayList;
050import java.util.List;
051import java.util.Set;
052
053/**
054 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
055 * @author Dominik Bartholdi (imod)
056 * @since 1.9
057 */
058public class JGitCheckOutCommand
059    extends AbstractCheckOutCommand
060    implements GitCommand
061{
062    /**
063     * For git, the given repository is a remote one. We have to clone it first if the working directory does not
064     * contain a git repo yet, otherwise we have to git-pull it.
065     * <p/>
066     * {@inheritDoc}
067     */
068    protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repo, ScmFileSet fileSet,
069                                                        ScmVersion version, boolean recursive )
070        throws ScmException
071    {
072        GitScmProviderRepository repository = (GitScmProviderRepository) repo;
073
074        if ( GitScmProviderRepository.PROTOCOL_FILE.equals( repository.getFetchInfo().getProtocol() )
075            && repository.getFetchInfo().getPath().indexOf( fileSet.getBasedir().getPath() ) >= 0 )
076        {
077            throw new ScmException( "remote repository must not be the working directory" );
078        }
079
080        Git git = null;
081        try
082        {
083
084            ProgressMonitor monitor = JGitUtils.getMonitor( getLogger() );
085
086            String branch = version != null ? version.getName() : null;
087
088            if ( StringUtils.isBlank( branch ) )
089            {
090                branch = Constants.MASTER;
091            }
092
093            getLogger().debug( "try checkout of branch: " + branch );
094
095            if ( !fileSet.getBasedir().exists() || !( new File( fileSet.getBasedir(), ".git" ).exists() ) )
096            {
097                if ( fileSet.getBasedir().exists() )
098                {
099                    // git refuses to clone otherwise
100                    fileSet.getBasedir().delete();
101                }
102
103                // FIXME only if windauze
104                WindowCacheConfig cfg = new WindowCacheConfig();
105                cfg.setPackedGitMMAP( false );
106                cfg.install();
107
108                // no git repo seems to exist, let's clone the original repo
109                CredentialsProvider credentials = JGitUtils.getCredentials( (GitScmProviderRepository) repo );
110                getLogger().info( "cloning [" + branch + "] to " + fileSet.getBasedir() );
111                CloneCommand command = Git.cloneRepository().setURI( repository.getFetchUrl() );
112                command.setCredentialsProvider( credentials ).setBranch( branch ).setDirectory( fileSet.getBasedir() );
113                command.setProgressMonitor( monitor );
114                git = command.call();
115            }
116
117            JGitRemoteInfoCommand remoteInfoCommand = new JGitRemoteInfoCommand();
118            remoteInfoCommand.setLogger( getLogger() );
119            RemoteInfoScmResult result = remoteInfoCommand.executeRemoteInfoCommand( repository, fileSet, null );
120
121            if ( git == null )
122            {
123                // deliberately not using JGitUtils.openRepo(), the caller told us exactly where to checkout
124                git = Git.open( fileSet.getBasedir() );
125            }
126            
127            if ( fileSet.getBasedir().exists() && new File( fileSet.getBasedir(), ".git" ).exists()
128                && result.getBranches().size() > 0 )
129            {
130                // git repo exists, so we must git-pull the changes
131                CredentialsProvider credentials = JGitUtils.prepareSession( getLogger(), git, repository );
132
133                if ( version != null && StringUtils.isNotEmpty( version.getName() ) && ( version instanceof ScmTag ) )
134                {
135                    // A tag will not be pulled but we only fetch all the commits from the upstream repo
136                    // This is done because checking out a tag might not happen on the current branch
137                    // but create a 'detached HEAD'.
138                    // In fact, a tag in git may be in multiple branches. This occurs if
139                    // you create a branch after the tag has been created
140                    getLogger().debug( "fetch..." );
141                    git.fetch().setCredentialsProvider( credentials ).setProgressMonitor( monitor ).call();
142                }
143                else
144                {
145                    getLogger().debug( "pull..." );
146                    git.pull().setCredentialsProvider( credentials ).setProgressMonitor( monitor ).call();
147                }
148            }
149
150            Set<String> localBranchNames = JGitBranchCommand.getShortLocalBranchNames( git );
151            if ( version instanceof ScmTag )
152            {
153                getLogger().info( "checkout tag [" + branch + "] at " + fileSet.getBasedir() );
154                git.checkout().setName( branch ).call();
155            }
156            else if ( localBranchNames.contains( branch ) )
157            {
158                getLogger().info( "checkout [" + branch + "] at " + fileSet.getBasedir() );
159                git.checkout().setName( branch ).call();
160            }
161            else
162            {
163                getLogger().info( "checkout remote branch [" + branch + "] at " + fileSet.getBasedir() );
164                git.checkout().setName( branch ).setCreateBranch( true ).setStartPoint( Constants.DEFAULT_REMOTE_NAME
165                                                                                            + "/" + branch ).call();
166            }
167
168            RevWalk revWalk = new RevWalk( git.getRepository() );
169            RevCommit commit = revWalk.parseCommit( git.getRepository().resolve( Constants.HEAD ) );
170            revWalk.release();
171
172            final TreeWalk walk = new TreeWalk( git.getRepository() );
173            walk.reset(); // drop the first empty tree, which we do not need here
174            walk.setRecursive( true );
175            walk.addTree( commit.getTree() );
176
177            List<ScmFile> listedFiles = new ArrayList<ScmFile>();
178            while ( walk.next() )
179            {
180                listedFiles.add( new ScmFile( walk.getPathString(), ScmFileStatus.CHECKED_OUT ) );
181            }
182            walk.release();
183
184            getLogger().debug( "current branch: " + git.getRepository().getBranch() );
185
186            return new CheckOutScmResult( "checkout via JGit", listedFiles );
187        }
188        catch ( Exception e )
189        {
190            throw new ScmException( "JGit checkout failure!", e );
191        }
192        finally
193        {
194            JGitUtils.closeRepo( git );
195        }
196    }
197
198}