View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.scm.provider.git.gitexe.command.checkin;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.URI;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.commons.io.FilenameUtils;
30  import org.apache.maven.scm.CommandParameters.SignOption;
31  import org.apache.maven.scm.ScmException;
32  import org.apache.maven.scm.ScmFile;
33  import org.apache.maven.scm.ScmFileSet;
34  import org.apache.maven.scm.ScmFileStatus;
35  import org.apache.maven.scm.ScmVersion;
36  import org.apache.maven.scm.command.checkin.AbstractCheckInCommand;
37  import org.apache.maven.scm.command.checkin.CheckInScmResult;
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.gitexe.command.GitCommandLineUtils;
41  import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand;
42  import org.apache.maven.scm.provider.git.gitexe.command.branch.GitBranchCommand;
43  import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusCommand;
44  import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer;
45  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
46  import org.apache.maven.scm.provider.git.util.GitUtil;
47  import org.codehaus.plexus.util.FileUtils;
48  import org.codehaus.plexus.util.Os;
49  import org.codehaus.plexus.util.cli.CommandLineUtils;
50  import org.codehaus.plexus.util.cli.Commandline;
51  
52  /**
53   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
54   * @author Olivier Lamy
55   */
56  public class GitCheckInCommand extends AbstractCheckInCommand implements GitCommand {
57      private final Map<String, String> environmentVariables;
58  
59      public GitCheckInCommand(Map<String, String> environmentVariables) {
60          super();
61          this.environmentVariables = environmentVariables;
62      }
63  
64      @Override
65      protected CheckInScmResult executeCheckInCommand(
66              ScmProviderRepository repository, ScmFileSet fileSet, String message, ScmVersion scmVersion)
67              throws ScmException {
68          return executeCheckInCommand(repository, fileSet, message, scmVersion, SignOption.DEFAULT);
69      }
70  
71      /**
72       * {@inheritDoc}
73       */
74      protected CheckInScmResult executeCheckInCommand(
75              ScmProviderRepository repo, ScmFileSet fileSet, String message, ScmVersion version, SignOption signOption)
76              throws ScmException {
77          GitScmProviderRepository repository = (GitScmProviderRepository) repo;
78  
79          CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
80          CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
81  
82          int exitCode = -1;
83  
84          File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null);
85          try {
86              FileUtils.fileWrite(messageFile.getAbsolutePath(), "UTF-8", message);
87          } catch (IOException ex) {
88              return new CheckInScmResult(
89                      null,
90                      "Error while making a temporary file for the commit message: " + ex.getMessage(),
91                      null,
92                      false);
93          }
94  
95          try {
96              if (!fileSet.getFileList().isEmpty()) {
97                  // if specific fileSet is given, we have to git-add them first
98                  // otherwise we will use 'git-commit -a' later
99  
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 }