View Javadoc
1   package org.apache.maven.scm.provider.git.jgit.command.checkout;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  
23  import java.io.File;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.function.BiFunction;
28  
29  import org.apache.maven.scm.ScmException;
30  import org.apache.maven.scm.ScmFile;
31  import org.apache.maven.scm.ScmFileSet;
32  import org.apache.maven.scm.ScmFileStatus;
33  import org.apache.maven.scm.ScmTag;
34  import org.apache.maven.scm.ScmVersion;
35  import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand;
36  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
37  import org.apache.maven.scm.command.remoteinfo.RemoteInfoScmResult;
38  import org.apache.maven.scm.provider.ScmProviderRepository;
39  import org.apache.maven.scm.provider.git.command.GitCommand;
40  import org.apache.maven.scm.provider.git.jgit.command.JGitTransportConfigCallback;
41  import org.apache.maven.scm.provider.git.jgit.command.JGitUtils;
42  import org.apache.maven.scm.provider.git.jgit.command.ScmProviderAwareSshdSessionFactory;
43  import org.apache.maven.scm.provider.git.jgit.command.branch.JGitBranchCommand;
44  import org.apache.maven.scm.provider.git.jgit.command.remoteinfo.JGitRemoteInfoCommand;
45  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
46  import org.codehaus.plexus.util.StringUtils;
47  import org.eclipse.jgit.api.CloneCommand;
48  import org.eclipse.jgit.api.FetchCommand;
49  import org.eclipse.jgit.api.Git;
50  import org.eclipse.jgit.api.PullCommand;
51  import org.eclipse.jgit.api.TransportConfigCallback;
52  import org.eclipse.jgit.lib.Constants;
53  import org.eclipse.jgit.lib.ProgressMonitor;
54  import org.eclipse.jgit.revwalk.RevCommit;
55  import org.eclipse.jgit.revwalk.RevWalk;
56  import org.eclipse.jgit.storage.file.WindowCacheConfig;
57  import org.eclipse.jgit.transport.CredentialsProvider;
58  import org.eclipse.jgit.treewalk.TreeWalk;
59  import org.slf4j.Logger;
60  
61  /**
62   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
63   * @author Dominik Bartholdi (imod)
64   * @since 1.9
65   */
66  public class JGitCheckOutCommand
67      extends AbstractCheckOutCommand
68      implements GitCommand
69  {
70      private BiFunction<GitScmProviderRepository, Logger, ScmProviderAwareSshdSessionFactory> sshSessionFactorySupplier;
71  
72      public JGitCheckOutCommand()
73      {
74          sshSessionFactorySupplier = ScmProviderAwareSshdSessionFactory::new;
75      }
76  
77      public void setSshSessionFactorySupplier(
78          BiFunction<GitScmProviderRepository, Logger, ScmProviderAwareSshdSessionFactory> sshSessionFactorySupplier )
79      {
80          this.sshSessionFactorySupplier = sshSessionFactorySupplier;
81      }
82  
83      /**
84       * For git, the given repository is a remote one. We have to clone it first if the working directory does not
85       * contain a git repo yet, otherwise we have to git-pull it.
86       * <p>
87       * {@inheritDoc}
88       */
89      protected CheckOutScmResult executeCheckOutCommand( ScmProviderRepository repo, ScmFileSet fileSet,
90                                                         ScmVersion version, boolean recursive, boolean shallow )
91          throws ScmException
92      {
93          GitScmProviderRepository repository = (GitScmProviderRepository) repo;
94  
95          if ( GitScmProviderRepository.PROTOCOL_FILE.equals( repository.getFetchInfo().getProtocol() )
96              && repository.getFetchInfo().getPath().indexOf( fileSet.getBasedir().getPath() ) >= 0 )
97          {
98              throw new ScmException( "remote repository must not be the working directory" );
99          }
100 
101         Git git = null;
102         try
103         {
104 
105             ProgressMonitor monitor = JGitUtils.getMonitor();
106 
107             String branch = version != null ? version.getName() : null;
108 
109             if ( StringUtils.isBlank( branch ) )
110             {
111                 branch = Constants.MASTER;
112             }
113 
114             TransportConfigCallback transportConfigCallback = new JGitTransportConfigCallback(
115                 sshSessionFactorySupplier.apply( (GitScmProviderRepository) repo, logger ) );
116 
117             logger.debug( "try checkout of branch: " + branch );
118 
119             if ( !fileSet.getBasedir().exists() || !( new File( fileSet.getBasedir(), ".git" ).exists() ) )
120             {
121                 if ( fileSet.getBasedir().exists() )
122                 {
123                     // git refuses to clone otherwise
124                     fileSet.getBasedir().delete();
125                 }
126 
127                 // FIXME only if windauze
128                 WindowCacheConfig cfg = new WindowCacheConfig();
129                 cfg.setPackedGitMMAP( false );
130                 cfg.install();
131 
132                 // no git repo seems to exist, let's clone the original repo
133                 CredentialsProvider credentials = JGitUtils.getCredentials( (GitScmProviderRepository) repo );
134                 logger.info( "cloning [" + branch + "] to " + fileSet.getBasedir() );
135                 CloneCommand command = Git.cloneRepository().setURI( repository.getFetchUrl() );
136 
137                 command.setCredentialsProvider( credentials ).setBranch( branch ).setDirectory( fileSet.getBasedir() );
138 
139                 command.setTransportConfigCallback( transportConfigCallback );
140 
141                 command.setProgressMonitor( monitor );
142                 git = command.call();
143             }
144 
145             JGitRemoteInfoCommand remoteInfoCommand = new JGitRemoteInfoCommand();
146             remoteInfoCommand.setSshSessionFactorySupplier( sshSessionFactorySupplier );
147             RemoteInfoScmResult result = remoteInfoCommand.executeRemoteInfoCommand( repository, fileSet, null );
148 
149             if ( git == null )
150             {
151                 // deliberately not using JGitUtils.openRepo(), the caller told us exactly where to checkout
152                 git = Git.open( fileSet.getBasedir() );
153             }
154 
155             if ( fileSet.getBasedir().exists() && new File( fileSet.getBasedir(), ".git" ).exists()
156                 && result.getBranches().size() > 0 )
157             {
158                 // git repo exists, so we must git-pull the changes
159                 CredentialsProvider credentials = JGitUtils.prepareSession( git, repository );
160 
161                 if ( version != null && StringUtils.isNotEmpty( version.getName() ) && ( version instanceof ScmTag ) )
162                 {
163                     // A tag will not be pulled but we only fetch all the commits from the upstream repo
164                     // This is done because checking out a tag might not happen on the current branch
165                     // but create a 'detached HEAD'.
166                     // In fact, a tag in git may be in multiple branches. This occurs if
167                     // you create a branch after the tag has been created
168                     logger.debug( "fetch..." );
169                     FetchCommand command = git.fetch().setCredentialsProvider( credentials )
170                             .setProgressMonitor( monitor );
171                     command.setTransportConfigCallback( transportConfigCallback );
172                     command.call();
173 
174                 }
175                 else
176                 {
177                     logger.debug( "pull..." );
178                     PullCommand command = git.pull().setCredentialsProvider( credentials )
179                             .setProgressMonitor( monitor );
180                     command.setTransportConfigCallback( transportConfigCallback );
181                     command.call();
182                 }
183             }
184 
185             Set<String> localBranchNames = JGitBranchCommand.getShortLocalBranchNames( git );
186             if ( version instanceof ScmTag )
187             {
188                 logger.info( "checkout tag [" + branch + "] at " + fileSet.getBasedir() );
189                 git.checkout().setName( branch ).call();
190             }
191             else if ( localBranchNames.contains( branch ) )
192             {
193                 logger.info( "checkout [" + branch + "] at " + fileSet.getBasedir() );
194                 git.checkout().setName( branch ).call();
195             }
196             else
197             {
198                 logger.info( "checkout remote branch [" + branch + "] at " + fileSet.getBasedir() );
199                 git.checkout().setName( branch ).setCreateBranch( true ).setStartPoint( Constants.DEFAULT_REMOTE_NAME
200                                                                                             + "/" + branch ).call();
201             }
202 
203             RevWalk revWalk = new RevWalk( git.getRepository() );
204             RevCommit commit = revWalk.parseCommit( git.getRepository().resolve( Constants.HEAD ) );
205             revWalk.close();
206 
207             final TreeWalk walk = new TreeWalk( git.getRepository() );
208             walk.reset(); // drop the first empty tree, which we do not need here
209             walk.setRecursive( true );
210             walk.addTree( commit.getTree() );
211 
212             List<ScmFile> listedFiles = new ArrayList<>();
213             while ( walk.next() )
214             {
215                 listedFiles.add( new ScmFile( walk.getPathString(), ScmFileStatus.CHECKED_OUT ) );
216             }
217             walk.close();
218 
219             logger.debug( "current branch: " + git.getRepository().getBranch() );
220 
221             return new CheckOutScmResult( "checkout via JGit", listedFiles );
222         }
223         catch ( Exception e )
224         {
225             throw new ScmException( "JGit checkout failure!", e );
226         }
227         finally
228         {
229             JGitUtils.closeRepo( git );
230         }
231     }
232 
233 }