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                git = Git.open( fileSet.getBasedir() );
124            }
125            
126            if ( fileSet.getBasedir().exists() && new File( fileSet.getBasedir(), ".git" ).exists()
127                && result.getBranches().size() > 0 )
128            {
129                // git repo exists, so we must git-pull the changes
130                CredentialsProvider credentials = JGitUtils.prepareSession( getLogger(), git, repository );
131
132                if ( version != null && StringUtils.isNotEmpty( version.getName() ) && ( version instanceof ScmTag ) )
133                {
134                    // A tag will not be pulled but we only fetch all the commits from the upstream repo
135                    // This is done because checking out a tag might not happen on the current branch
136                    // but create a 'detached HEAD'.
137                    // In fact, a tag in git may be in multiple branches. This occurs if
138                    // you create a branch after the tag has been created
139                    getLogger().debug( "fetch..." );
140                    git.fetch().setCredentialsProvider( credentials ).setProgressMonitor( monitor ).call();
141                }
142                else
143                {
144                    getLogger().debug( "pull..." );
145                    git.pull().setCredentialsProvider( credentials ).setProgressMonitor( monitor ).call();
146                }
147            }
148
149            Set<String> localBranchNames = JGitBranchCommand.getShortLocalBranchNames( git );
150            if ( version instanceof ScmTag )
151            {
152                getLogger().info( "checkout tag [" + branch + "] at " + fileSet.getBasedir() );
153                git.checkout().setName( branch ).call();
154            }
155            else if ( localBranchNames.contains( branch ) )
156            {
157                getLogger().info( "checkout [" + branch + "] at " + fileSet.getBasedir() );
158                git.checkout().setName( branch ).call();
159            }
160            else
161            {
162                getLogger().info( "checkout remote branch [" + branch + "] at " + fileSet.getBasedir() );
163                git.checkout().setName( branch ).setCreateBranch( true ).setStartPoint( Constants.DEFAULT_REMOTE_NAME
164                                                                                            + "/" + branch ).call();
165            }
166
167            RevWalk revWalk = new RevWalk( git.getRepository() );
168            RevCommit commit = revWalk.parseCommit( git.getRepository().resolve( Constants.HEAD ) );
169            revWalk.release();
170
171            final TreeWalk walk = new TreeWalk( git.getRepository() );
172            walk.reset(); // drop the first empty tree, which we do not need here
173            walk.setRecursive( true );
174            walk.addTree( commit.getTree() );
175
176            List<ScmFile> listedFiles = new ArrayList<ScmFile>();
177            while ( walk.next() )
178            {
179                listedFiles.add( new ScmFile( walk.getPathString(), ScmFileStatus.CHECKED_OUT ) );
180            }
181            walk.release();
182
183            getLogger().debug( "current branch: " + git.getRepository().getBranch() );
184
185            return new CheckOutScmResult( "checkout via JGit", listedFiles );
186        }
187        catch ( Exception e )
188        {
189            throw new ScmException( "JGit checkout failure!", e );
190        }
191        finally
192        {
193            JGitUtils.closeRepo( git );
194        }
195    }
196
197}