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