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