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;
020
021import java.io.File;
022import java.io.IOException;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.commons.io.FilenameUtils;
028import org.apache.commons.lang3.StringUtils;
029import org.apache.maven.scm.ScmException;
030import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
031import org.apache.maven.scm.provider.git.util.GitUtil;
032import org.apache.maven.scm.providers.gitlib.settings.Settings;
033import org.codehaus.plexus.util.cli.CommandLineException;
034import org.codehaus.plexus.util.cli.CommandLineUtils;
035import org.codehaus.plexus.util.cli.Commandline;
036import org.codehaus.plexus.util.cli.StreamConsumer;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * Command line construction utility.
042 *
043 * @author Brett Porter
044 * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
045 *
046 */
047public final class GitCommandLineUtils {
048    private static final Logger LOGGER = LoggerFactory.getLogger(GitCommandLineUtils.class);
049
050    // https://git-scm.com/docs/git#Documentation/git.txt-codeGITSSHCOMMANDcode, requires git 2.3.0 or newer
051    public static final String VARIABLE_GIT_SSH_COMMAND = "GIT_SSH_COMMAND";
052
053    private GitCommandLineUtils() {}
054
055    public static void addTarget(Commandline cl, List<File> files) {
056        if (files == null || files.isEmpty()) {
057            return;
058        }
059        final File workingDirectory = cl.getWorkingDirectory();
060        try {
061            final String canonicalWorkingDirectory = workingDirectory.getCanonicalPath();
062            for (File file : files) {
063                String relativeFile = file.getPath();
064
065                final String canonicalFile = file.getCanonicalPath();
066                if (canonicalFile.startsWith(canonicalWorkingDirectory)) {
067                    // so we can omit the starting characters
068                    relativeFile = canonicalFile.substring(canonicalWorkingDirectory.length());
069
070                    if (relativeFile.startsWith(File.separator)) {
071                        relativeFile = relativeFile.substring(File.separator.length());
072                    }
073                }
074
075                // no setFile() since this screws up the working directory!
076                cl.createArg().setValue(FilenameUtils.separatorsToUnix(relativeFile));
077            }
078        } catch (IOException ex) {
079            throw new IllegalArgumentException(
080                    "Could not get canonical paths for workingDirectory = " + workingDirectory + " or files=" + files,
081                    ex);
082        }
083    }
084
085    /**
086     * Use this only for commands not requiring environment variables (i.e. local commands).
087     * @param workingDirectory
088     * @param command
089     * @return TODO
090     */
091    public static Commandline getBaseGitCommandLine(File workingDirectory, String command) {
092        return getBaseGitCommandLine(workingDirectory, command, null, null);
093    }
094
095    /**
096     * Use this for commands requiring environment variables (i.e. remote commands).
097     * @param workingDirectory
098     * @param command
099     * @param environment
100     * @return TODO
101     */
102    public static Commandline getBaseGitCommandLine(
103            File workingDirectory,
104            String command,
105            GitScmProviderRepository repository,
106            Map<String, String> environment) {
107        Commandline cl = getAnonymousBaseGitCommandLine(workingDirectory, command);
108        if (repository != null) {
109            prepareEnvVariablesForRepository(repository, environment).forEach(cl::addEnvironment);
110        } else if (environment != null) {
111            environment.forEach(cl::addEnvironment);
112        }
113        return cl;
114    }
115
116    /**
117     * Creates a {@link Commandline} for which the toString() do not display
118     * password.
119     *
120     * @param workingDirectory
121     * @param command
122     * @return CommandLine with anonymous output.
123     */
124    private static Commandline getAnonymousBaseGitCommandLine(File workingDirectory, String command) {
125        if (command == null || command.length() == 0) {
126            return null;
127        }
128
129        Commandline cl = new AnonymousCommandLine();
130
131        composeCommand(workingDirectory, command, cl);
132
133        return cl;
134    }
135
136    private static void composeCommand(File workingDirectory, String command, Commandline cl) {
137        Settings settings = GitUtil.getSettings();
138
139        cl.setExecutable(settings.getGitCommand());
140
141        cl.createArg().setValue(command);
142
143        if (workingDirectory != null) {
144            cl.setWorkingDirectory(workingDirectory.getAbsolutePath());
145        }
146    }
147
148    public static int execute(Commandline cl, StreamConsumer consumer, CommandLineUtils.StringStreamConsumer stderr)
149            throws ScmException {
150        if (LOGGER.isInfoEnabled()) {
151            LOGGER.info("Executing: " + cl);
152            LOGGER.info("Working directory: " + cl.getWorkingDirectory().getAbsolutePath());
153        }
154
155        int exitCode;
156        try {
157            exitCode = CommandLineUtils.executeCommandLine(cl, consumer, stderr);
158        } catch (CommandLineException ex) {
159            throw new ScmException("Error while executing command.", ex);
160        }
161
162        return exitCode;
163    }
164
165    public static int execute(
166            Commandline cl, CommandLineUtils.StringStreamConsumer stdout, CommandLineUtils.StringStreamConsumer stderr)
167            throws ScmException {
168        if (LOGGER.isInfoEnabled()) {
169            LOGGER.info("Executing: " + cl);
170            LOGGER.info("Working directory: " + cl.getWorkingDirectory().getAbsolutePath());
171        }
172
173        int exitCode;
174        try {
175            exitCode = CommandLineUtils.executeCommandLine(cl, stdout, stderr);
176        } catch (CommandLineException ex) {
177            throw new ScmException("Error while executing command.", ex);
178        }
179
180        return exitCode;
181    }
182
183    static Map<String, String> prepareEnvVariablesForRepository(
184            GitScmProviderRepository repository, Map<String, String> environmentVariables) {
185        Map<String, String> effectiveEnvironmentVariables = new HashMap<>();
186        if (environmentVariables != null) {
187            effectiveEnvironmentVariables.putAll(environmentVariables);
188        }
189        if (StringUtils.isNotBlank(repository.getPrivateKey())) {
190            if (effectiveEnvironmentVariables.putIfAbsent(
191                            VARIABLE_GIT_SSH_COMMAND,
192                            "ssh -o IdentitiesOnly=yes -i "
193                                    + FilenameUtils.separatorsToUnix(repository.getPrivateKey()))
194                    != null) {
195                LOGGER.warn(
196                        "Ignore GitScmProviderRepository.privateKey as environment variable {} is already set",
197                        VARIABLE_GIT_SSH_COMMAND);
198            }
199        }
200        if (StringUtils.isNotBlank(repository.getPassphrase())) {
201            LOGGER.warn("GitScmProviderRepository.passphrase currently not supported by provider 'git'");
202        }
203        return effectiveEnvironmentVariables;
204    }
205}