001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.scm.provider.git.jgit.command.checkout; 020 021import java.io.File; 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Set; 026import java.util.function.BiFunction; 027 028import org.apache.commons.lang3.StringUtils; 029import org.apache.maven.scm.ScmException; 030import org.apache.maven.scm.ScmFile; 031import org.apache.maven.scm.ScmFileSet; 032import org.apache.maven.scm.ScmFileStatus; 033import org.apache.maven.scm.ScmTag; 034import org.apache.maven.scm.ScmVersion; 035import org.apache.maven.scm.command.checkout.AbstractCheckOutCommand; 036import org.apache.maven.scm.command.checkout.CheckOutScmResult; 037import org.apache.maven.scm.command.remoteinfo.RemoteInfoScmResult; 038import org.apache.maven.scm.provider.ScmProviderRepository; 039import org.apache.maven.scm.provider.git.command.GitCommand; 040import org.apache.maven.scm.provider.git.jgit.command.CustomizableSshSessionFactoryCommand; 041import org.apache.maven.scm.provider.git.jgit.command.JGitTransportConfigCallback; 042import org.apache.maven.scm.provider.git.jgit.command.JGitUtils; 043import org.apache.maven.scm.provider.git.jgit.command.ScmProviderAwareSshdSessionFactory; 044import org.apache.maven.scm.provider.git.jgit.command.branch.JGitBranchCommand; 045import org.apache.maven.scm.provider.git.jgit.command.remoteinfo.JGitRemoteInfoCommand; 046import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; 047import org.eclipse.jgit.api.CloneCommand; 048import org.eclipse.jgit.api.FetchCommand; 049import org.eclipse.jgit.api.Git; 050import org.eclipse.jgit.api.PullCommand; 051import org.eclipse.jgit.api.TransportConfigCallback; 052import org.eclipse.jgit.api.errors.GitAPIException; 053import org.eclipse.jgit.lib.Constants; 054import org.eclipse.jgit.lib.ProgressMonitor; 055import org.eclipse.jgit.revwalk.RevCommit; 056import org.eclipse.jgit.revwalk.RevWalk; 057import org.eclipse.jgit.storage.file.WindowCacheConfig; 058import org.eclipse.jgit.transport.CredentialsProvider; 059import org.eclipse.jgit.treewalk.TreeWalk; 060import org.slf4j.Logger; 061 062/** 063 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a> 064 * @author Dominik Bartholdi (imod) 065 * @since 1.9 066 */ 067public class JGitCheckOutCommand extends AbstractCheckOutCommand 068 implements GitCommand, CustomizableSshSessionFactoryCommand { 069 private BiFunction<GitScmProviderRepository, Logger, ScmProviderAwareSshdSessionFactory> sshSessionFactorySupplier; 070 071 public JGitCheckOutCommand() { 072 sshSessionFactorySupplier = ScmProviderAwareSshdSessionFactory::new; 073 } 074 075 @Override 076 public void setSshSessionFactorySupplier( 077 BiFunction<GitScmProviderRepository, Logger, ScmProviderAwareSshdSessionFactory> 078 sshSessionFactorySupplier) { 079 this.sshSessionFactorySupplier = sshSessionFactorySupplier; 080 } 081 082 /** 083 * For git, the given repository is a remote one. We have to clone it first if the working directory does not 084 * contain a git repo yet, otherwise we have to git-pull it. 085 * <p> 086 * {@inheritDoc} 087 */ 088 protected CheckOutScmResult executeCheckOutCommand( 089 ScmProviderRepository repo, ScmFileSet fileSet, ScmVersion version, boolean recursive, boolean shallow) 090 throws ScmException { 091 GitScmProviderRepository repository = (GitScmProviderRepository) repo; 092 093 if (GitScmProviderRepository.PROTOCOL_FILE.equals( 094 repository.getFetchInfo().getProtocol()) 095 && repository 096 .getFetchInfo() 097 .getPath() 098 .indexOf(fileSet.getBasedir().getPath()) 099 >= 0) { 100 throw new ScmException("remote repository must not be the working directory"); 101 } 102 103 Git git = null; 104 try { 105 106 ProgressMonitor monitor = JGitUtils.getMonitor(); 107 108 String branch = version != null ? version.getName() : null; 109 110 if (StringUtils.isBlank(branch)) { 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 if (fileSet.getBasedir().exists()) { 121 // git refuses to clone otherwise 122 fileSet.getBasedir().delete(); 123 } 124 125 // FIXME only if windauze 126 WindowCacheConfig cfg = new WindowCacheConfig(); 127 cfg.setPackedGitMMAP(false); 128 cfg.install(); 129 130 // no git repo seems to exist, let's clone the original repo 131 CredentialsProvider credentials = JGitUtils.getCredentials((GitScmProviderRepository) repo); 132 logger.info("cloning [" + branch + "] to " + fileSet.getBasedir()); 133 CloneCommand command = Git.cloneRepository().setURI(repository.getFetchUrl()); 134 135 command.setCredentialsProvider(credentials).setBranch(branch).setDirectory(fileSet.getBasedir()); 136 137 command.setTransportConfigCallback(transportConfigCallback); 138 139 command.setProgressMonitor(monitor); 140 git = command.call(); 141 } 142 143 JGitRemoteInfoCommand remoteInfoCommand = new JGitRemoteInfoCommand(); 144 remoteInfoCommand.setSshSessionFactorySupplier(sshSessionFactorySupplier); 145 RemoteInfoScmResult result = remoteInfoCommand.executeRemoteInfoCommand(repository, fileSet, null); 146 147 if (git == null) { 148 // deliberately not using JGitUtils.openRepo(), the caller told us exactly where to checkout 149 git = Git.open(fileSet.getBasedir()); 150 } 151 152 if (fileSet.getBasedir().exists() 153 && new File(fileSet.getBasedir(), ".git").exists() 154 && result.getBranches().size() > 0) { 155 // git repo exists, so we must git-pull the changes 156 CredentialsProvider credentials = JGitUtils.prepareSession(git, repository); 157 158 if (version != null && StringUtils.isNotEmpty(version.getName()) && (version instanceof ScmTag)) { 159 // A tag will not be pulled but we only fetch all the commits from the upstream repo 160 // This is done because checking out a tag might not happen on the current branch 161 // but create a 'detached HEAD'. 162 // In fact, a tag in git may be in multiple branches. This occurs if 163 // you create a branch after the tag has been created 164 logger.debug("fetch..."); 165 FetchCommand command = 166 git.fetch().setCredentialsProvider(credentials).setProgressMonitor(monitor); 167 command.setTransportConfigCallback(transportConfigCallback); 168 command.call(); 169 170 } else { 171 logger.debug("pull..."); 172 PullCommand command = 173 git.pull().setCredentialsProvider(credentials).setProgressMonitor(monitor); 174 command.setTransportConfigCallback(transportConfigCallback); 175 command.call(); 176 } 177 } 178 179 Set<String> localBranchNames = JGitBranchCommand.getShortLocalBranchNames(git); 180 if (version instanceof ScmTag) { 181 logger.info("checkout tag [" + branch + "] at " + fileSet.getBasedir()); 182 git.checkout().setName(branch).call(); 183 } else if (localBranchNames.contains(branch)) { 184 logger.info("checkout [" + branch + "] at " + fileSet.getBasedir()); 185 git.checkout().setName(branch).call(); 186 } else { 187 logger.info("checkout remote branch [" + branch + "] at " + fileSet.getBasedir()); 188 git.checkout() 189 .setName(branch) 190 .setCreateBranch(true) 191 .setStartPoint(Constants.DEFAULT_REMOTE_NAME + "/" + branch) 192 .call(); 193 } 194 195 RevWalk revWalk = new RevWalk(git.getRepository()); 196 RevCommit commit = revWalk.parseCommit(git.getRepository().resolve(Constants.HEAD)); 197 revWalk.close(); 198 199 try (TreeWalk walk = new TreeWalk(git.getRepository())) { 200 walk.reset(); // drop the first empty tree, which we do not need here 201 walk.setRecursive(true); 202 walk.addTree(commit.getTree()); 203 204 List<ScmFile> listedFiles = new ArrayList<>(); 205 while (walk.next()) { 206 listedFiles.add(new ScmFile(walk.getPathString(), ScmFileStatus.CHECKED_OUT)); 207 } 208 logger.debug("current branch: " + git.getRepository().getBranch()); 209 210 return new CheckOutScmResult("checkout via JGit", listedFiles); 211 } 212 } catch (RuntimeException | IOException | GitAPIException e) { 213 throw new ScmException("JGit checkout failure!", e); 214 } finally { 215 JGitUtils.closeRepo(git); 216 } 217 } 218}