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.ScmException;
31  import org.apache.maven.scm.ScmFile;
32  import org.apache.maven.scm.ScmFileSet;
33  import org.apache.maven.scm.ScmFileStatus;
34  import org.apache.maven.scm.ScmVersion;
35  import org.apache.maven.scm.command.checkin.AbstractCheckInCommand;
36  import org.apache.maven.scm.command.checkin.CheckInScmResult;
37  import org.apache.maven.scm.provider.ScmProviderRepository;
38  import org.apache.maven.scm.provider.git.command.GitCommand;
39  import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils;
40  import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand;
41  import org.apache.maven.scm.provider.git.gitexe.command.branch.GitBranchCommand;
42  import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusCommand;
43  import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer;
44  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
45  import org.apache.maven.scm.provider.git.util.GitUtil;
46  import org.codehaus.plexus.util.FileUtils;
47  import org.codehaus.plexus.util.Os;
48  import org.codehaus.plexus.util.cli.CommandLineUtils;
49  import org.codehaus.plexus.util.cli.Commandline;
50  
51  /**
52   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
53   * @author Olivier Lamy
54   *
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      /** {@inheritDoc} */
65      protected CheckInScmResult executeCheckInCommand(
66              ScmProviderRepository repo, ScmFileSet fileSet, String message, ScmVersion version) throws ScmException {
67          GitScmProviderRepository repository = (GitScmProviderRepository) repo;
68  
69          CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
70          CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
71  
72          int exitCode = -1;
73  
74          File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null);
75          try {
76              FileUtils.fileWrite(messageFile.getAbsolutePath(), "UTF-8", message);
77          } catch (IOException ex) {
78              return new CheckInScmResult(
79                      null,
80                      "Error while making a temporary file for the commit message: " + ex.getMessage(),
81                      null,
82                      false);
83          }
84  
85          try {
86              if (!fileSet.getFileList().isEmpty()) {
87                  // if specific fileSet is given, we have to git-add them first
88                  // otherwise we will use 'git-commit -a' later
89  
90                  Commandline clAdd = null;
91  
92                  // SCM-714: Workaround for the Windows terminal command limit
93                  if (Os.isFamily(Os.FAMILY_WINDOWS)) {
94                      for (File file : fileSet.getFileList()) {
95                          clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), Collections.singletonList(file));
96                          exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr);
97  
98                          if (exitCode != 0) {
99                              break;
100                         }
101                     }
102                 } else {
103                     clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), fileSet.getFileList());
104                     exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr);
105                 }
106 
107                 if (exitCode != 0) {
108                     return new CheckInScmResult(
109                             clAdd.toString(), "The git-add command failed.", stderr.getOutput(), false);
110                 }
111             }
112 
113             // SCM-709: statusCommand uses repositoryRoot instead of workingDirectory, adjust it with
114             // relativeRepositoryPath
115             URI relativeRepositoryPath = GitStatusCommand.getRelativeCWD(logger, fileSet);
116 
117             // git-commit doesn't show single files, but only summary :/
118             // so we must run git-status and consume the output
119             // borrow a few things from the git-status command
120             Commandline clStatus = GitStatusCommand.createCommandLine(repository, fileSet);
121 
122             GitStatusConsumer statusConsumer =
123                     new GitStatusConsumer(fileSet.getBasedir(), relativeRepositoryPath, fileSet);
124             exitCode = GitCommandLineUtils.execute(clStatus, statusConsumer, stderr);
125             if (exitCode != 0) {
126                 // git-status returns non-zero if nothing to do
127                 if (logger.isInfoEnabled()) {
128                     logger.info("nothing added to commit but untracked files present (use \"git add\" to " + "track)");
129                 }
130             }
131 
132             if (statusConsumer.getChangedFiles().isEmpty()) {
133                 return new CheckInScmResult(null, statusConsumer.getChangedFiles());
134             }
135 
136             Commandline clCommit = createCommitCommandLine(repository, fileSet, messageFile, environmentVariables);
137 
138             exitCode = GitCommandLineUtils.execute(clCommit, stdout, stderr);
139             if (exitCode != 0) {
140                 return new CheckInScmResult(
141                         clCommit.toString(), "The git-commit command failed.", stderr.getOutput(), false);
142             }
143 
144             if (repo.isPushChanges()) {
145                 Commandline cl = createPushCommandLine(repository, fileSet, version);
146 
147                 exitCode = GitCommandLineUtils.execute(cl, stdout, stderr);
148                 if (exitCode != 0) {
149                     return new CheckInScmResult(
150                             cl.toString(), "The git-push command failed.", stderr.getOutput(), false);
151                 }
152             }
153 
154             List<ScmFile> checkedInFiles =
155                     new ArrayList<>(statusConsumer.getChangedFiles().size());
156 
157             // rewrite all detected files to now have status 'checked_in'
158             for (ScmFile changedFile : statusConsumer.getChangedFiles()) {
159                 ScmFile scmfile = new ScmFile(changedFile.getPath(), ScmFileStatus.CHECKED_IN);
160 
161                 if (fileSet.getFileList().isEmpty()) {
162                     checkedInFiles.add(scmfile);
163                 } else {
164                     // if a specific fileSet is given, we have to check if the file is really tracked
165                     for (File f : fileSet.getFileList()) {
166                         if (FilenameUtils.separatorsToUnix(f.getPath()).equals(scmfile.getPath())) {
167                             checkedInFiles.add(scmfile);
168                         }
169                     }
170                 }
171             }
172 
173             return new CheckInScmResult(clCommit.toString(), checkedInFiles);
174         } finally {
175             try {
176                 FileUtils.forceDelete(messageFile);
177             } catch (IOException ex) {
178                 // ignore
179             }
180         }
181     }
182 
183     // ----------------------------------------------------------------------
184     //
185     // ----------------------------------------------------------------------
186 
187     public Commandline createPushCommandLine(
188             GitScmProviderRepository repository, ScmFileSet fileSet, ScmVersion version) throws ScmException {
189         Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(
190                 fileSet.getBasedir(), "push", repository, environmentVariables);
191 
192         String branch = GitBranchCommand.getCurrentBranch(repository, fileSet);
193 
194         if (branch == null || branch.length() == 0) {
195             throw new ScmException("Could not detect the current branch. Don't know where I should push to!");
196         }
197 
198         cl.createArg().setValue(repository.getPushUrl());
199 
200         cl.createArg().setValue("refs/heads/" + branch + ":" + "refs/heads/" + branch);
201 
202         return cl;
203     }
204 
205     public static Commandline createCommitCommandLine(
206             GitScmProviderRepository repository, ScmFileSet fileSet, File messageFile) throws ScmException {
207         return createCommitCommandLine(repository, fileSet, messageFile, Collections.emptyMap());
208     }
209 
210     public static Commandline createCommitCommandLine(
211             GitScmProviderRepository repository,
212             ScmFileSet fileSet,
213             File messageFile,
214             Map<String, String> environmentVariables)
215             throws ScmException {
216         Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "commit");
217 
218         cl.createArg().setValue("--verbose");
219 
220         cl.createArg().setValue("-F");
221 
222         cl.createArg().setValue(messageFile.getAbsolutePath());
223 
224         if (fileSet.getFileList().isEmpty()) {
225             // commit all tracked files
226             cl.createArg().setValue("-a");
227         }
228 
229         if (GitUtil.getSettings().isCommitNoVerify()) {
230             cl.createArg().setValue("--no-verify");
231         }
232 
233         return cl;
234     }
235 }