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.gitexe.command.checkin;
020
021import java.io.File;
022import java.io.IOException;
023import java.net.URI;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.io.FilenameUtils;
030import org.apache.maven.scm.ScmException;
031import org.apache.maven.scm.ScmFile;
032import org.apache.maven.scm.ScmFileSet;
033import org.apache.maven.scm.ScmFileStatus;
034import org.apache.maven.scm.ScmVersion;
035import org.apache.maven.scm.command.checkin.AbstractCheckInCommand;
036import org.apache.maven.scm.command.checkin.CheckInScmResult;
037import org.apache.maven.scm.provider.ScmProviderRepository;
038import org.apache.maven.scm.provider.git.command.GitCommand;
039import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils;
040import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand;
041import org.apache.maven.scm.provider.git.gitexe.command.branch.GitBranchCommand;
042import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusCommand;
043import org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer;
044import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
045import org.apache.maven.scm.provider.git.util.GitUtil;
046import org.codehaus.plexus.util.FileUtils;
047import org.codehaus.plexus.util.Os;
048import org.codehaus.plexus.util.cli.CommandLineUtils;
049import org.codehaus.plexus.util.cli.Commandline;
050
051/**
052 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
053 * @author Olivier Lamy
054 *
055 */
056public class GitCheckInCommand extends AbstractCheckInCommand implements GitCommand {
057    private final Map<String, String> environmentVariables;
058
059    public GitCheckInCommand(Map<String, String> environmentVariables) {
060        super();
061        this.environmentVariables = environmentVariables;
062    }
063
064    /** {@inheritDoc} */
065    protected CheckInScmResult executeCheckInCommand(
066            ScmProviderRepository repo, ScmFileSet fileSet, String message, ScmVersion version) throws ScmException {
067        GitScmProviderRepository repository = (GitScmProviderRepository) repo;
068
069        CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
070        CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
071
072        int exitCode = -1;
073
074        File messageFile = FileUtils.createTempFile("maven-scm-", ".commit", null);
075        try {
076            FileUtils.fileWrite(messageFile.getAbsolutePath(), "UTF-8", message);
077        } catch (IOException ex) {
078            return new CheckInScmResult(
079                    null,
080                    "Error while making a temporary file for the commit message: " + ex.getMessage(),
081                    null,
082                    false);
083        }
084
085        try {
086            if (!fileSet.getFileList().isEmpty()) {
087                // if specific fileSet is given, we have to git-add them first
088                // otherwise we will use 'git-commit -a' later
089
090                Commandline clAdd = null;
091
092                // SCM-714: Workaround for the Windows terminal command limit
093                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
094                    for (File file : fileSet.getFileList()) {
095                        clAdd = GitAddCommand.createCommandLine(fileSet.getBasedir(), Collections.singletonList(file));
096                        exitCode = GitCommandLineUtils.execute(clAdd, stdout, stderr);
097
098                        if (exitCode != 0) {
099                            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}