001package org.apache.maven.scm.provider.git.jgit.command.checkin;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.scm.ScmException;
023import org.apache.maven.scm.ScmFile;
024import org.apache.maven.scm.ScmFileSet;
025import org.apache.maven.scm.ScmVersion;
026import org.apache.maven.scm.command.checkin.AbstractCheckInCommand;
027import org.apache.maven.scm.command.checkin.CheckInScmResult;
028import org.apache.maven.scm.provider.ScmProviderRepository;
029import org.apache.maven.scm.provider.git.command.GitCommand;
030import org.apache.maven.scm.provider.git.jgit.command.JGitUtils;
031import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
032import org.codehaus.plexus.util.StringUtils;
033import org.eclipse.jgit.api.AddCommand;
034import org.eclipse.jgit.api.CommitCommand;
035import org.eclipse.jgit.api.Git;
036import org.eclipse.jgit.lib.Constants;
037import org.eclipse.jgit.lib.UserConfig;
038import org.eclipse.jgit.revwalk.RevCommit;
039import org.eclipse.jgit.transport.RefSpec;
040
041import java.io.File;
042import java.net.InetAddress;
043import java.net.UnknownHostException;
044import java.util.Collections;
045import java.util.List;
046import java.util.Set;
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
066    extends AbstractCheckInCommand
067    implements GitCommand
068{
069
070    protected static final String GIT_MAVEN_SECTION = "maven-scm";
071
072    protected static final String GIT_MAILDOMAIN = "maildomain";
073
074    protected static final String GIT_FORCE = "forceUsername";
075
076    /**
077     * {@inheritDoc}
078     */
079    protected CheckInScmResult executeCheckInCommand( ScmProviderRepository repo, ScmFileSet fileSet, String message,
080                                                      ScmVersion version )
081        throws ScmException
082    {
083
084        Git git = null;
085        try
086        {
087            File basedir = fileSet.getBasedir();
088            git = JGitUtils.openRepo( basedir );
089
090            boolean doCommit = false;
091
092            if ( !fileSet.getFileList().isEmpty() )
093            {
094                doCommit = JGitUtils.addAllFiles( git, fileSet ).size() > 0;
095            }
096            else
097            {
098                // add all tracked files which are modified manually
099                Set<String> changeds = git.status().call().getModified();
100                if ( changeds.isEmpty() )
101                {
102                    // warn there is nothing to add
103                    getLogger().warn( "there are no files to be added" );
104                    doCommit = false;
105                }
106                else
107                {
108                    AddCommand add = git.add();
109                    for ( String changed : changeds )
110                    {
111                        getLogger().debug( "add manualy: " + 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            {
122                UserInfo author = getAuthor( repo, git );
123                UserInfo committer = getCommitter( repo, git );
124
125                CommitCommand command = git.commit().setMessage( message ).setAuthor( author.name, author.email );
126                command.setCommitter( committer.name, committer.email );
127                RevCommit commitRev = command.call();
128
129                getLogger().info( "commit done: " + commitRev.getShortMessage() );
130                checkedInFiles = JGitUtils.getFilesInCommit( git.getRepository(), commitRev );
131                if ( getLogger().isDebugEnabled() )
132                {
133                    for ( ScmFile scmFile : checkedInFiles )
134                    {
135                        getLogger().debug( "in commit: " + scmFile );
136                    }
137                }
138            }
139
140            if ( repo.isPushChanges() )
141            {
142                String branch = version != null ? version.getName() : null;
143                if ( StringUtils.isBlank( branch ) )
144                {
145                    branch = git.getRepository().getBranch();
146                }
147                RefSpec refSpec = new RefSpec( Constants.R_HEADS + branch + ":" + Constants.R_HEADS + branch );
148                getLogger().info( "push changes to remote... " + refSpec.toString() );
149                JGitUtils.push( getLogger(), git, (GitScmProviderRepository) repo, refSpec );
150            }
151
152            return new CheckInScmResult( "JGit checkin", checkedInFiles );
153        }
154        catch ( Exception e )
155        {
156            throw new ScmException( "JGit checkin failure!", e );
157        }
158        finally
159        {
160            JGitUtils.closeRepo( git );
161        }
162    }
163
164    private static final class UserInfo
165    {
166        final String name;
167
168        final String email;
169
170        UserInfo( String name, String email )
171        {
172            this.name = name;
173            this.email = email;
174        }
175    }
176
177    private UserInfo getCommitter( ScmProviderRepository repo, Git git )
178    {
179        boolean forceMvnUser = git.getRepository().getConfig().getBoolean( GIT_MAVEN_SECTION, GIT_FORCE, false );
180
181        // git config
182        UserConfig user = git.getRepository().getConfig().get( UserConfig.KEY );
183        String committerName = null;
184        if ( !forceMvnUser && !user.isCommitterNameImplicit() )
185        {
186            committerName = user.getCommitterName();
187        }
188
189        // mvn parameter
190        if ( StringUtils.isBlank( committerName ) )
191        {
192            committerName = repo.getUser();
193        }
194
195        // git default
196        if ( StringUtils.isBlank( committerName ) )
197        {
198            committerName = user.getCommitterName();
199        }
200
201        // git config
202        String committerMail = null;
203        if ( !user.isCommitterEmailImplicit() )
204        {
205            committerMail = user.getCommitterEmail();
206        }
207
208        if ( StringUtils.isBlank( committerMail ) )
209        {
210            String defaultDomain = git.getRepository().getConfig().getString( GIT_MAVEN_SECTION, null, GIT_MAILDOMAIN );
211            defaultDomain = StringUtils.isNotBlank( defaultDomain ) ? defaultDomain : getHostname();
212
213            // mvn parameter (constructed with username) or git default
214            committerMail =
215                StringUtils.isNotBlank( repo.getUser() ) ? repo.getUser() + "@" + defaultDomain
216                                : user.getCommitterEmail();
217        }
218
219        return new UserInfo( committerName, committerMail );
220    }
221
222    private UserInfo getAuthor( ScmProviderRepository repo, Git git )
223    {
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        {
231            authorName = user.getAuthorName();
232        }
233
234        // mvn parameter
235        if ( StringUtils.isBlank( authorName ) )
236        {
237            authorName = repo.getUser();
238        }
239
240        // git default
241        if ( StringUtils.isBlank( authorName ) )
242        {
243            authorName = user.getAuthorName();
244        }
245
246        // git config
247        String authorMail = null;
248        if ( !user.isAuthorEmailImplicit() )
249        {
250            authorMail = user.getAuthorEmail();
251        }
252
253        if ( StringUtils.isBlank( authorMail ) )
254        {
255            String defaultDomain = git.getRepository().getConfig().getString( GIT_MAVEN_SECTION, null, GIT_MAILDOMAIN );
256            defaultDomain = StringUtils.isNotBlank( defaultDomain ) ? defaultDomain : getHostname();
257
258            // mvn parameter (constructed with username) or git default
259            authorMail =
260                StringUtils.isNotBlank( repo.getUser() ) ? repo.getUser() + "@" + defaultDomain : user.getAuthorEmail();
261        }
262
263        return new UserInfo( authorName, authorMail );
264    }
265
266    private String getHostname()
267    {
268        String hostname;
269        try
270        {
271            InetAddress localhost = java.net.InetAddress.getLocalHost();
272            hostname = localhost.getHostName();
273        }
274        catch ( UnknownHostException e )
275        {
276            getLogger().warn( "failed to resolve hostname to create mail address, "
277                                  + "defaulting to 'maven-scm-provider-jgit'" );
278            hostname = "maven-scm-provider-jgit";
279        }
280        return hostname;
281    }
282
283}