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.jgit.command.checkin;
20  
21  import java.io.File;
22  import java.net.InetAddress;
23  import java.net.UnknownHostException;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Set;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.maven.scm.ScmException;
30  import org.apache.maven.scm.ScmFile;
31  import org.apache.maven.scm.ScmFileSet;
32  import org.apache.maven.scm.ScmVersion;
33  import org.apache.maven.scm.command.checkin.AbstractCheckInCommand;
34  import org.apache.maven.scm.command.checkin.CheckInScmResult;
35  import org.apache.maven.scm.provider.ScmProviderRepository;
36  import org.apache.maven.scm.provider.git.command.GitCommand;
37  import org.apache.maven.scm.provider.git.jgit.command.JGitUtils;
38  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
39  import org.eclipse.jgit.api.AddCommand;
40  import org.eclipse.jgit.api.CommitCommand;
41  import org.eclipse.jgit.api.Git;
42  import org.eclipse.jgit.api.Status;
43  import org.eclipse.jgit.lib.Constants;
44  import org.eclipse.jgit.lib.UserConfig;
45  import org.eclipse.jgit.revwalk.RevCommit;
46  import org.eclipse.jgit.transport.PushResult;
47  import org.eclipse.jgit.transport.RefSpec;
48  import org.eclipse.jgit.transport.RemoteRefUpdate;
49  
50  /**
51   * This provider uses the following strategy to discover the committer and author name/mail for a commit:
52   * <ol>
53   * <li>"user" section in .gitconfig</li>
54   * <li>"username" passed to maven execution</li>
55   * <li>default git config (system user and hostname for email)</li>
56   * </ol>
57   * the "maven-scm" config can be configured like this: <br>
58   * the default email domain to be used (will be used to create an email from the username passed to maven):<br>
59   * <code>git config --global maven-scm.maildomain mycomp.com</code> <br>
60   * you can also enforce the usage of the username for the author and committer:<br>
61   * <code>git config --global maven-scm.forceUsername true</code> <br>
62   *
63   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
64   * @author Dominik Bartholdi (imod)
65   * @since 1.9
66   */
67  public class JGitCheckInCommand extends AbstractCheckInCommand implements GitCommand {
68  
69      protected static final String GIT_MAVEN_SECTION = "maven-scm";
70  
71      protected static final String GIT_MAILDOMAIN = "maildomain";
72  
73      protected static final String GIT_FORCE = "forceUsername";
74  
75      /**
76       * {@inheritDoc}
77       */
78      protected CheckInScmResult executeCheckInCommand(
79              ScmProviderRepository repo, ScmFileSet fileSet, String message, ScmVersion version) throws ScmException {
80  
81          Git git = null;
82          try {
83              File basedir = fileSet.getBasedir();
84              git = JGitUtils.openRepo(basedir);
85  
86              boolean doCommit = false;
87  
88              if (!fileSet.getFileList().isEmpty()) {
89                  // add files first
90                  doCommit = JGitUtils.addAllFiles(git, fileSet).size() > 0;
91                  if (!doCommit) {
92                      doCommit = git.status().call().hasUncommittedChanges();
93                  }
94              } else {
95                  // add all tracked files which are modified manually
96                  Status status = git.status().call();
97                  Set<String> changeds = git.status().call().getModified();
98                  if (changeds.isEmpty()) {
99                      if (!status.hasUncommittedChanges()) {
100                         // warn there is nothing to add
101                         logger.warn("There are neither files to be added nor any uncommitted changes");
102                         doCommit = false;
103                     } else {
104                         logger.debug("There are uncommitted changes in the git index");
105                         doCommit = true;
106                     }
107                 } else {
108                     // TODO: gitexe only adds if fileSet is not empty
109                     AddCommand add = git.add();
110                     for (String changed : changeds) {
111                         logger.debug("Add manually: {}", changed);
112                         add.addFilepattern(changed);
113                         doCommit = true;
114                     }
115                     add.call();
116                 }
117             }
118 
119             List<ScmFile> checkedInFiles = Collections.emptyList();
120             if (doCommit) {
121                 UserInfo author = getAuthor(repo, git);
122                 UserInfo committer = getCommitter(repo, git);
123 
124                 CommitCommand command = git.commit().setMessage(message).setAuthor(author.name, author.email);
125                 command.setCommitter(committer.name, committer.email);
126                 RevCommit commitRev = command.call();
127 
128                 logger.info("commit done: " + commitRev.getShortMessage());
129                 checkedInFiles = JGitUtils.getFilesInCommit(git.getRepository(), commitRev, fileSet.getBasedir());
130                 if (logger.isDebugEnabled()) {
131                     for (ScmFile scmFile : checkedInFiles) {
132                         logger.debug("in commit: " + scmFile);
133                     }
134                 }
135             }
136 
137             if (repo.isPushChanges()) {
138                 String branch = version != null ? version.getName() : null;
139                 if (StringUtils.isBlank(branch)) {
140                     branch = git.getRepository().getBranch();
141                 }
142                 RefSpec refSpec = new RefSpec(Constants.R_HEADS + branch + ":" + Constants.R_HEADS + branch);
143                 logger.info("push changes to remote... " + refSpec);
144                 Iterable<PushResult> pushResultList = JGitUtils.push(git, (GitScmProviderRepository) repo, refSpec);
145 
146                 for (PushResult pushResult : pushResultList) {
147                     for (RemoteRefUpdate remoteRefUpdate : pushResult.getRemoteUpdates()) {
148                         if (!isSuccessStatus(remoteRefUpdate.getStatus())) {
149                             return new CheckInScmResult(
150                                     "JGit checkin",
151                                     "The git-push command failed, with status: " + remoteRefUpdate.getStatus(),
152                                     remoteRefUpdate.getMessage(),
153                                     false);
154                         }
155                     }
156                 }
157             }
158 
159             return new CheckInScmResult("JGit checkin", checkedInFiles);
160         } catch (Exception e) {
161             throw new ScmException("JGit checkin failure!", e);
162         } finally {
163             JGitUtils.closeRepo(git);
164         }
165     }
166 
167     private boolean isSuccessStatus(RemoteRefUpdate.Status remoteRefUpdateStatus) {
168         return remoteRefUpdateStatus == RemoteRefUpdate.Status.OK
169                 || remoteRefUpdateStatus == RemoteRefUpdate.Status.UP_TO_DATE;
170     }
171 
172     private static final class UserInfo {
173 
174         final String name;
175 
176         final String email;
177 
178         UserInfo(String name, String email) {
179             this.name = name;
180             this.email = email;
181         }
182     }
183 
184     private UserInfo getCommitter(ScmProviderRepository repo, Git git) {
185         boolean forceMvnUser = git.getRepository().getConfig().getBoolean(GIT_MAVEN_SECTION, GIT_FORCE, false);
186 
187         // git config
188         UserConfig user = git.getRepository().getConfig().get(UserConfig.KEY);
189         String committerName = null;
190         if (!forceMvnUser && !user.isCommitterNameImplicit()) {
191             committerName = user.getCommitterName();
192         }
193 
194         // mvn parameter
195         if (StringUtils.isBlank(committerName)) {
196             committerName = repo.getUser();
197         }
198 
199         // git default
200         if (StringUtils.isBlank(committerName)) {
201             committerName = user.getCommitterName();
202         }
203 
204         // git config
205         String committerMail = null;
206         if (!user.isCommitterEmailImplicit()) {
207             committerMail = user.getCommitterEmail();
208         }
209 
210         if (StringUtils.isBlank(committerMail)) {
211             String defaultDomain = git.getRepository().getConfig().getString(GIT_MAVEN_SECTION, null, GIT_MAILDOMAIN);
212             defaultDomain = StringUtils.isNotBlank(defaultDomain) ? defaultDomain : getHostname();
213 
214             // mvn parameter (constructed with username) or git default
215             committerMail = StringUtils.isNotBlank(repo.getUser())
216                     ? repo.getUser() + "@" + defaultDomain
217                     : user.getCommitterEmail();
218         }
219 
220         return new UserInfo(committerName, committerMail);
221     }
222 
223     private UserInfo getAuthor(ScmProviderRepository repo, Git git) {
224         boolean forceMvnUser = git.getRepository().getConfig().getBoolean(GIT_MAVEN_SECTION, GIT_FORCE, false);
225 
226         // git config
227         UserConfig user = git.getRepository().getConfig().get(UserConfig.KEY);
228         String authorName = null;
229         if (!forceMvnUser && !user.isAuthorNameImplicit()) {
230             authorName = user.getAuthorName();
231         }
232 
233         // mvn parameter
234         if (StringUtils.isBlank(authorName)) {
235             authorName = repo.getUser();
236         }
237 
238         // git default
239         if (StringUtils.isBlank(authorName)) {
240             authorName = user.getAuthorName();
241         }
242 
243         // git config
244         String authorMail = null;
245         if (!user.isAuthorEmailImplicit()) {
246             authorMail = user.getAuthorEmail();
247         }
248 
249         if (StringUtils.isBlank(authorMail)) {
250             String defaultDomain = git.getRepository().getConfig().getString(GIT_MAVEN_SECTION, null, GIT_MAILDOMAIN);
251             defaultDomain = StringUtils.isNotBlank(defaultDomain) ? defaultDomain : getHostname();
252 
253             // mvn parameter (constructed with username) or git default
254             authorMail = StringUtils.isNotBlank(repo.getUser())
255                     ? repo.getUser() + "@" + defaultDomain
256                     : user.getAuthorEmail();
257         }
258 
259         return new UserInfo(authorName, authorMail);
260     }
261 
262     private String getHostname() {
263         String hostname;
264         try {
265             InetAddress localhost = java.net.InetAddress.getLocalHost();
266             hostname = localhost.getHostName();
267         } catch (UnknownHostException e) {
268             logger.warn(
269                     "failed to resolve hostname to create mail address, " + "defaulting to 'maven-scm-provider-jgit'");
270             hostname = "maven-scm-provider-jgit";
271         }
272         return hostname;
273     }
274 }