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