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