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.gitexe.command.checkin; 020 021import java.io.File; 022import java.io.IOException; 023import java.net.URI; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.commons.io.FilenameUtils; 030import org.apache.maven.scm.CommandParameters.SignOption; 031import org.apache.maven.scm.ScmException; 032import org.apache.maven.scm.ScmFile; 033import org.apache.maven.scm.ScmFileSet; 034import org.apache.maven.scm.ScmFileStatus; 035import org.apache.maven.scm.ScmVersion; 036import org.apache.maven.scm.command.checkin.AbstractCheckInCommand; 037import org.apache.maven.scm.command.checkin.CheckInScmResult; 038import org.apache.maven.scm.provider.ScmProviderRepository; 039import org.apache.maven.scm.provider.git.command.GitCommand; 040import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils; 041import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand; 042import org.apache.maven.scm.provider.git.gitexe.command.branch.GitBranchCommand; 043import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusCommand; 044import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer; 045import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; 046import org.apache.maven.scm.provider.git.util.GitUtil; 047import org.codehaus.plexus.util.FileUtils; 048import org.codehaus.plexus.util.Os; 049import org.codehaus.plexus.util.cli.CommandLineUtils; 050import org.codehaus.plexus.util.cli.Commandline; 051 052/** 053 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a> 054 * @author Olivier Lamy 055 */ 056public class GitCheckInCommand extends AbstractCheckInCommand implements GitCommand { 057 private final Map<String, String> environmentVariables; 058 059 public GitCheckInCommand(Map<String, String> environmentVariables) { 060 super(); 061 this.environmentVariables = environmentVariables; 062 } 063 064 @Override 065 protected CheckInScmResult executeCheckInCommand( 066 ScmProviderRepository repository, ScmFileSet fileSet, String message, ScmVersion scmVersion) 067 throws ScmException { 068 return executeCheckInCommand(repository, fileSet, message, scmVersion, SignOption.DEFAULT); 069 } 070 071 /** 072 * {@inheritDoc} 073 */ 074 protected CheckInScmResult executeCheckInCommand( 075 ScmProviderRepository repo, ScmFileSet fileSet, String message, ScmVersion version, SignOption signOption) 076 throws ScmException { 077 GitScmProviderRepository repository = (GitScmProviderRepository) repo; 078 079 CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer(); 080 CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer(); 081 082 int exitCode = -1; 083 084 File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null); 085 try { 086 FileUtils.fileWrite(messageFile.getAbsolutePath(), "UTF-8", message); 087 } catch (IOException ex) { 088 return new CheckInScmResult( 089 null, 090 "Error while making a temporary file for the commit message: " + ex.getMessage(), 091 null, 092 false); 093 } 094 095 try { 096 if (!fileSet.getFileList().isEmpty()) { 097 // if specific fileSet is given, we have to git-add them first 098 // otherwise we will use 'git-commit -a' later 099 100 Commandline clAdd = null; 101 102 // SCM-714: Workaround for the Windows terminal command limit 103 if (Os.isFamily(Os.FAMILY_WINDOWS)) { 104 for (File file : fileSet.getFileList()) { 105 clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), Collections.singletonList(file)); 106 exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr); 107 108 if (exitCode != 0) { 109 break; 110 } 111 } 112 } else { 113 clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), fileSet.getFileList()); 114 exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr); 115 } 116 117 if (exitCode != 0) { 118 return new CheckInScmResult( 119 clAdd.toString(), "The git-add command failed.", stderr.getOutput(), false); 120 } 121 } 122 123 // SCM-709: statusCommand uses repositoryRoot instead of workingDirectory, adjust it with 124 // relativeRepositoryPath 125 URI relativeRepositoryPath = GitStatusCommand.getRelativeCWD(logger, fileSet); 126 127 // git-commit doesn't show single files, but only summary :/ 128 // so we must run git-status and consume the output 129 // borrow a few things from the git-status command 130 Commandline clStatus = GitStatusCommand.createCommandLine(repository, fileSet); 131 132 GitStatusConsumer statusConsumer = 133 new GitStatusConsumer(fileSet.getBasedir(), relativeRepositoryPath, fileSet); 134 exitCode = GitCommandLineUtils.execute(clStatus, statusConsumer, stderr); 135 if (exitCode != 0) { 136 // git-status returns non-zero if nothing to do 137 if (logger.isInfoEnabled()) { 138 logger.info("nothing added to commit but untracked files present (use \"git add\" to " + "track)"); 139 } 140 } 141 142 if (statusConsumer.getChangedFiles().isEmpty()) { 143 return new CheckInScmResult(null, statusConsumer.getChangedFiles()); 144 } 145 146 Commandline clCommit = 147 createCommitCommandLine(repository, fileSet, messageFile, environmentVariables, signOption); 148 149 exitCode = GitCommandLineUtils.execute(clCommit, stdout, stderr); 150 if (exitCode != 0) { 151 return new CheckInScmResult( 152 clCommit.toString(), "The git-commit command failed.", stderr.getOutput(), false); 153 } 154 155 if (repo.isPushChanges()) { 156 Commandline cl = createPushCommandLine(repository, fileSet, version); 157 158 exitCode = GitCommandLineUtils.execute(cl, stdout, stderr); 159 if (exitCode != 0) { 160 return new CheckInScmResult( 161 cl.toString(), "The git-push command failed.", stderr.getOutput(), false); 162 } 163 } 164 165 List<ScmFile> checkedInFiles = 166 new ArrayList<>(statusConsumer.getChangedFiles().size()); 167 168 // rewrite all detected files to now have status 'checked_in' 169 for (ScmFile changedFile : statusConsumer.getChangedFiles()) { 170 ScmFile scmfile = new ScmFile(changedFile.getPath(), ScmFileStatus.CHECKED_IN); 171 172 if (fileSet.getFileList().isEmpty()) { 173 checkedInFiles.add(scmfile); 174 } else { 175 // if a specific fileSet is given, we have to check if the file is really tracked 176 for (File f : fileSet.getFileList()) { 177 if (FilenameUtils.separatorsToUnix(f.getPath()).equals(scmfile.getPath())) { 178 checkedInFiles.add(scmfile); 179 } 180 } 181 } 182 } 183 184 return new CheckInScmResult(clCommit.toString(), checkedInFiles); 185 } finally { 186 try { 187 FileUtils.forceDelete(messageFile); 188 } catch (IOException ex) { 189 // ignore 190 } 191 } 192 } 193 194 // ---------------------------------------------------------------------- 195 // 196 // ---------------------------------------------------------------------- 197 198 public Commandline createPushCommandLine( 199 GitScmProviderRepository repository, ScmFileSet fileSet, ScmVersion version) throws ScmException { 200 Commandline cl = GitCommandLineUtils.getBaseGitCommandLine( 201 fileSet.getBasedir(), "push", repository, environmentVariables); 202 203 String branch = GitBranchCommand.getCurrentBranch(repository, fileSet); 204 205 if (branch == null || branch.length() == 0) { 206 throw new ScmException("Could not detect the current branch. Don't know where I should push to!"); 207 } 208 209 cl.createArg().setValue(repository.getPushUrl()); 210 211 cl.createArg().setValue("refs/heads/" + branch + ":" + "refs/heads/" + branch); 212 213 return cl; 214 } 215 216 @Deprecated 217 public static Commandline createCommitCommandLine( 218 GitScmProviderRepository repository, ScmFileSet fileSet, File messageFile) throws ScmException { 219 return createCommitCommandLine(repository, fileSet, messageFile, Collections.emptyMap()); 220 } 221 222 @Deprecated 223 public static Commandline createCommitCommandLine( 224 GitScmProviderRepository repository, 225 ScmFileSet fileSet, 226 File messageFile, 227 Map<String, String> environmentVariables) 228 throws ScmException { 229 return createCommitCommandLine(repository, fileSet, messageFile, environmentVariables, SignOption.DEFAULT); 230 } 231 232 public static Commandline createCommitCommandLine( 233 GitScmProviderRepository repository, 234 ScmFileSet fileSet, 235 File messageFile, 236 Map<String, String> environmentVariables, 237 SignOption signOption) 238 throws ScmException { 239 Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "commit"); 240 241 cl.createArg().setValue("--verbose"); 242 243 cl.createArg().setValue("-F"); 244 245 cl.createArg().setValue(messageFile.getAbsolutePath()); 246 247 if (fileSet.getFileList().isEmpty()) { 248 // commit all tracked files 249 cl.createArg().setValue("-a"); 250 } 251 252 if (GitUtil.getSettings().isCommitNoVerify()) { 253 cl.createArg().setValue("--no-verify"); 254 } 255 256 if (signOption != null) { 257 switch (signOption) { 258 case FORCE_SIGN: 259 cl.createArg().setValue("--gpg-sign"); 260 break; 261 case FORCE_NO_SIGN: 262 cl.createArg().setValue("--no-gpg-sign"); 263 break; 264 default: 265 // do nothing, this is the default 266 break; 267 } 268 } 269 return cl; 270 } 271}