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.checkin; 020 021import java.io.File; 022import java.io.IOException; 023import java.net.InetAddress; 024import java.net.UnknownHostException; 025import java.util.Collections; 026import java.util.EnumSet; 027import java.util.List; 028import java.util.Optional; 029import java.util.Set; 030import java.util.function.BiFunction; 031 032import org.apache.commons.lang3.StringUtils; 033import org.apache.maven.scm.CommandParameter; 034import org.apache.maven.scm.CommandParameters; 035import org.apache.maven.scm.ScmException; 036import org.apache.maven.scm.ScmFile; 037import org.apache.maven.scm.ScmFileSet; 038import org.apache.maven.scm.ScmVersion; 039import org.apache.maven.scm.command.checkin.AbstractCheckInCommand; 040import org.apache.maven.scm.command.checkin.CheckInScmResult; 041import org.apache.maven.scm.provider.ScmProviderRepository; 042import org.apache.maven.scm.provider.git.command.GitCommand; 043import org.apache.maven.scm.provider.git.jgit.command.CustomizableSshSessionFactoryCommand; 044import org.apache.maven.scm.provider.git.jgit.command.JGitTransportConfigCallback; 045import org.apache.maven.scm.provider.git.jgit.command.JGitUtils; 046import org.apache.maven.scm.provider.git.jgit.command.PushException; 047import org.apache.maven.scm.provider.git.jgit.command.ScmProviderAwareSshdSessionFactory; 048import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; 049import org.apache.maven.scm.provider.git.util.GitUtil; 050import org.eclipse.jgit.api.AddCommand; 051import org.eclipse.jgit.api.CommitCommand; 052import org.eclipse.jgit.api.Git; 053import org.eclipse.jgit.api.Status; 054import org.eclipse.jgit.api.TransportConfigCallback; 055import org.eclipse.jgit.api.errors.GitAPIException; 056import org.eclipse.jgit.lib.Constants; 057import org.eclipse.jgit.lib.UserConfig; 058import org.eclipse.jgit.revwalk.RevCommit; 059import org.eclipse.jgit.transport.RefSpec; 060import org.eclipse.jgit.transport.RemoteRefUpdate; 061import org.slf4j.Logger; 062 063/** 064 * This provider uses the following strategy to discover the committer and author name/mail for a commit: 065 * <ol> 066 * <li>"user" section in .gitconfig</li> 067 * <li>"username" passed to maven execution</li> 068 * <li>default git config (system user and hostname for email)</li> 069 * </ol> 070 * the "maven-scm" config can be configured like this: <br> 071 * the default email domain to be used (will be used to create an email from the username passed to maven):<br> 072 * <code>git config --global maven-scm.maildomain mycomp.com</code> <br> 073 * you can also enforce the usage of the username for the author and committer:<br> 074 * <code>git config --global maven-scm.forceUsername true</code> <br> 075 * 076 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a> 077 * @author Dominik Bartholdi (imod) 078 * @since 1.9 079 */ 080public class JGitCheckInCommand extends AbstractCheckInCommand 081 implements GitCommand, CustomizableSshSessionFactoryCommand { 082 083 protected static final String GIT_MAVEN_SECTION = "maven-scm"; 084 085 protected static final String GIT_MAILDOMAIN = "maildomain"; 086 087 protected static final String GIT_FORCE = "forceUsername"; 088 089 private BiFunction<GitScmProviderRepository, Logger, ScmProviderAwareSshdSessionFactory> sshSessionFactorySupplier; 090 091 public JGitCheckInCommand() { 092 sshSessionFactorySupplier = ScmProviderAwareSshdSessionFactory::new; 093 } 094 095 @Override 096 public void setSshSessionFactorySupplier( 097 BiFunction<GitScmProviderRepository, Logger, ScmProviderAwareSshdSessionFactory> 098 sshSessionFactorySupplier) { 099 this.sshSessionFactorySupplier = sshSessionFactorySupplier; 100 } 101 102 @Override 103 public CheckInScmResult executeCommand(ScmProviderRepository repo, ScmFileSet fileSet, CommandParameters parameters) 104 throws ScmException { 105 String message = parameters.getString(CommandParameter.MESSAGE); 106 107 ScmVersion version = parameters.getScmVersion(CommandParameter.SCM_VERSION, null); 108 109 Git git = null; 110 try { 111 File basedir = fileSet.getBasedir(); 112 git = JGitUtils.openRepo(basedir); 113 114 boolean doCommit = false; 115 116 if (!fileSet.getFileList().isEmpty()) { 117 // add files first 118 doCommit = JGitUtils.addAllFiles(git, fileSet).size() > 0; 119 if (!doCommit) { 120 Status status = git.status().call(); 121 doCommit = status.getAdded().size() > 0 122 || status.getChanged().size() > 0 123 || status.getRemoved().size() > 0; 124 } 125 } else { 126 // add all tracked files which are modified manually 127 Status status = git.status().call(); 128 Set<String> changeds = git.status().call().getModified(); 129 if (changeds.isEmpty()) { 130 if (!status.hasUncommittedChanges()) { 131 // warn there is nothing to add 132 logger.warn("There are neither files to be added nor any uncommitted changes"); 133 doCommit = false; 134 } else { 135 logger.debug("There are uncommitted changes in the git index"); 136 doCommit = true; 137 } 138 } else { 139 // TODO: gitexe only adds if fileSet is not empty 140 AddCommand add = git.add(); 141 for (String changed : changeds) { 142 logger.debug("Add manually: {}", changed); 143 add.addFilepattern(changed); 144 doCommit = true; 145 } 146 add.call(); 147 } 148 } 149 150 List<ScmFile> checkedInFiles = Collections.emptyList(); 151 if (doCommit) { 152 UserInfo author = getAuthor(repo, git); 153 UserInfo committer = getCommitter(repo, git); 154 155 CommitCommand command = git.commit() 156 .setMessage(message) 157 .setAuthor(author.name, author.email) 158 .setCommitter(committer.name, committer.email); 159 if (GitUtil.getSettings().isCommitNoVerify()) { 160 command.setNoVerify(true); 161 } 162 RevCommit commitRev = command.call(); 163 164 logger.debug("commit done: " + commitRev.getShortMessage()); 165 checkedInFiles = JGitUtils.getFilesInCommit(git.getRepository(), commitRev, fileSet.getBasedir()); 166 if (logger.isDebugEnabled()) { 167 for (ScmFile scmFile : checkedInFiles) { 168 logger.debug("in commit: " + scmFile); 169 } 170 } 171 } else { 172 logger.info("nothing to commit"); 173 } 174 175 if (repo.isPushChanges()) { 176 String branch = version != null ? version.getName() : null; 177 if (StringUtils.isBlank(branch)) { 178 branch = git.getRepository().getBranch(); 179 } 180 RefSpec refSpec = new RefSpec(Constants.R_HEADS + branch + ":" + Constants.R_HEADS + branch); 181 logger.info("push changes to remote... " + refSpec); 182 TransportConfigCallback transportConfigCallback = new JGitTransportConfigCallback( 183 sshSessionFactorySupplier.apply((GitScmProviderRepository) repo, logger)); 184 185 JGitUtils.push( 186 git, 187 (GitScmProviderRepository) repo, 188 refSpec, 189 EnumSet.of(RemoteRefUpdate.Status.OK, RemoteRefUpdate.Status.UP_TO_DATE), 190 Optional.of(transportConfigCallback)); 191 } 192 193 return new CheckInScmResult("JGit checkin", checkedInFiles); 194 } catch (PushException e) { 195 logger.debug("Failed to push commits", e); 196 return new CheckInScmResult("JGit checkin", "Failed to push changes: " + e.getMessage(), "", false); 197 } catch (IOException | GitAPIException e) { 198 throw new ScmException("JGit checkin failure!", e); 199 } finally { 200 JGitUtils.closeRepo(git); 201 } 202 } 203 204 /** 205 * {@inheritDoc} 206 */ 207 protected CheckInScmResult executeCheckInCommand( 208 ScmProviderRepository repo, ScmFileSet fileSet, String message, ScmVersion version) throws ScmException { 209 210 CommandParameters parameters = new CommandParameters(); 211 parameters.setString(CommandParameter.MESSAGE, message); 212 parameters.setScmVersion(CommandParameter.SCM_VERSION, version); 213 return executeCommand(repo, fileSet, parameters); 214 } 215 216 private static final class UserInfo { 217 218 final String name; 219 220 final String email; 221 222 UserInfo(String name, String email) { 223 this.name = name; 224 this.email = email; 225 } 226 } 227 228 private UserInfo getCommitter(ScmProviderRepository repo, Git git) { 229 boolean forceMvnUser = git.getRepository().getConfig().getBoolean(GIT_MAVEN_SECTION, GIT_FORCE, false); 230 231 // git config 232 UserConfig user = git.getRepository().getConfig().get(UserConfig.KEY); 233 String committerName = null; 234 if (!forceMvnUser && !user.isCommitterNameImplicit()) { 235 committerName = user.getCommitterName(); 236 } 237 238 // mvn parameter 239 if (StringUtils.isBlank(committerName)) { 240 committerName = repo.getUser(); 241 } 242 243 // git default 244 if (StringUtils.isBlank(committerName)) { 245 committerName = user.getCommitterName(); 246 } 247 248 // git config 249 String committerMail = null; 250 if (!user.isCommitterEmailImplicit()) { 251 committerMail = user.getCommitterEmail(); 252 } 253 254 if (StringUtils.isBlank(committerMail)) { 255 String defaultDomain = git.getRepository().getConfig().getString(GIT_MAVEN_SECTION, null, GIT_MAILDOMAIN); 256 defaultDomain = StringUtils.isNotBlank(defaultDomain) ? defaultDomain : getHostname(); 257 258 // mvn parameter (constructed with username) or git default 259 committerMail = StringUtils.isNotBlank(repo.getUser()) 260 ? repo.getUser() + "@" + defaultDomain 261 : user.getCommitterEmail(); 262 } 263 264 return new UserInfo(committerName, committerMail); 265 } 266 267 private UserInfo getAuthor(ScmProviderRepository repo, Git git) { 268 boolean forceMvnUser = git.getRepository().getConfig().getBoolean(GIT_MAVEN_SECTION, GIT_FORCE, false); 269 270 // git config 271 UserConfig user = git.getRepository().getConfig().get(UserConfig.KEY); 272 String authorName = null; 273 if (!forceMvnUser && !user.isAuthorNameImplicit()) { 274 authorName = user.getAuthorName(); 275 } 276 277 // mvn parameter 278 if (StringUtils.isBlank(authorName)) { 279 authorName = repo.getUser(); 280 } 281 282 // git default 283 if (StringUtils.isBlank(authorName)) { 284 authorName = user.getAuthorName(); 285 } 286 287 // git config 288 String authorMail = null; 289 if (!user.isAuthorEmailImplicit()) { 290 authorMail = user.getAuthorEmail(); 291 } 292 293 if (StringUtils.isBlank(authorMail)) { 294 String defaultDomain = git.getRepository().getConfig().getString(GIT_MAVEN_SECTION, null, GIT_MAILDOMAIN); 295 defaultDomain = StringUtils.isNotBlank(defaultDomain) ? defaultDomain : getHostname(); 296 297 // mvn parameter (constructed with username) or git default 298 authorMail = StringUtils.isNotBlank(repo.getUser()) 299 ? repo.getUser() + "@" + defaultDomain 300 : user.getAuthorEmail(); 301 } 302 303 return new UserInfo(authorName, authorMail); 304 } 305 306 private String getHostname() { 307 String hostname; 308 try { 309 InetAddress localhost = java.net.InetAddress.getLocalHost(); 310 hostname = localhost.getHostName(); 311 } catch (UnknownHostException e) { 312 logger.warn( 313 "failed to resolve hostname to create mail address, " + "defaulting to 'maven-scm-provider-jgit'"); 314 hostname = "maven-scm-provider-jgit"; 315 } 316 return hostname; 317 } 318}