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.gitexe.command;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.commons.io.FilenameUtils;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.maven.scm.ScmException;
30  import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
31  import org.apache.maven.scm.provider.git.util.GitUtil;
32  import org.apache.maven.scm.providers.gitlib.settings.Settings;
33  import org.codehaus.plexus.util.cli.CommandLineException;
34  import org.codehaus.plexus.util.cli.CommandLineUtils;
35  import org.codehaus.plexus.util.cli.Commandline;
36  import org.codehaus.plexus.util.cli.StreamConsumer;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * Command line construction utility.
42   *
43   * @author Brett Porter
44   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
45   *
46   */
47  public final class GitCommandLineUtils {
48      private static final Logger LOGGER = LoggerFactory.getLogger(GitCommandLineUtils.class);
49  
50      // https://git-scm.com/docs/git#Documentation/git.txt-codeGITSSHCOMMANDcode, requires git 2.3.0 or newer
51      public static final String VARIABLE_GIT_SSH_COMMAND = "GIT_SSH_COMMAND";
52  
53      private GitCommandLineUtils() {}
54  
55      public static void addTarget(Commandline commandLine, List<File> files) {
56          if (files == null || files.isEmpty()) {
57              return;
58          }
59          final File workingDirectory = commandLine.getWorkingDirectory();
60          try {
61              final String canonicalWorkingDirectory = workingDirectory.getCanonicalPath();
62              for (File file : files) {
63                  String relativeFile = file.getPath();
64  
65                  final String canonicalFile = file.getCanonicalPath();
66                  if (canonicalFile.startsWith(canonicalWorkingDirectory)) {
67                      // so we can omit the starting characters
68                      relativeFile = canonicalFile.substring(canonicalWorkingDirectory.length());
69  
70                      if (relativeFile.startsWith(File.separator)) {
71                          relativeFile = relativeFile.substring(File.separator.length());
72                      }
73                  }
74  
75                  // no setFile() since this screws up the working directory!
76                  commandLine.createArg().setValue(FilenameUtils.separatorsToUnix(relativeFile));
77              }
78          } catch (IOException ex) {
79              throw new IllegalArgumentException(
80                      "Could not get canonical paths for workingDirectory = " + workingDirectory + " or files=" + files,
81                      ex);
82          }
83      }
84  
85      /**
86       * Use this only for commands not requiring environment variables (i.e. local commands).
87       */
88      public static Commandline getBaseGitCommandLine(File workingDirectory, String command) {
89          return getBaseGitCommandLine(workingDirectory, command, null, null);
90      }
91  
92      /**
93       * Use this for commands requiring environment variables (i.e. remote commands).
94       */
95      public static Commandline getBaseGitCommandLine(
96              File workingDirectory,
97              String command,
98              GitScmProviderRepository repository,
99              Map<String, String> environment) {
100         Commandline commandLine = getAnonymousBaseGitCommandLine(workingDirectory, command);
101         if (repository != null) {
102             prepareEnvVariablesForRepository(repository, environment).forEach(commandLine::addEnvironment);
103         } else if (environment != null) {
104             environment.forEach(commandLine::addEnvironment);
105         }
106         return commandLine;
107     }
108 
109     /**
110      * Creates a {@link Commandline} for which toString() does not display
111      * the password.
112      *
113      * @param workingDirectory
114      * @param command
115      * @return CommandLine with anonymous output
116      */
117     private static Commandline getAnonymousBaseGitCommandLine(File workingDirectory, String command) {
118         if (command == null || command.isEmpty()) {
119             return null;
120         }
121 
122         Commandline commandLine = new AnonymousCommandLine();
123 
124         composeCommand(workingDirectory, command, commandLine);
125 
126         return commandLine;
127     }
128 
129     private static void composeCommand(File workingDirectory, String command, Commandline commandLine) {
130         Settings settings = GitUtil.getSettings();
131 
132         commandLine.setExecutable(settings.getGitCommand());
133 
134         commandLine.createArg().setValue(command);
135 
136         if (workingDirectory != null) {
137             commandLine.setWorkingDirectory(workingDirectory.getAbsolutePath());
138         }
139     }
140 
141     public static int execute(
142             Commandline commandline, StreamConsumer consumer, CommandLineUtils.StringStreamConsumer stderr)
143             throws ScmException {
144         if (LOGGER.isInfoEnabled()) {
145             LOGGER.info("Executing: " + commandline);
146             LOGGER.info(
147                     "Working directory: " + commandline.getWorkingDirectory().getAbsolutePath());
148         }
149 
150         int exitCode;
151         try {
152             exitCode = CommandLineUtils.executeCommandLine(commandline, consumer, stderr);
153         } catch (CommandLineException ex) {
154             throw new ScmException("Error while executing command.", ex);
155         }
156 
157         return exitCode;
158     }
159 
160     public static int execute(
161             Commandline commandLine,
162             CommandLineUtils.StringStreamConsumer stdout,
163             CommandLineUtils.StringStreamConsumer stderr)
164             throws ScmException {
165         if (LOGGER.isInfoEnabled()) {
166             LOGGER.info("Executing: " + commandLine);
167             LOGGER.info(
168                     "Working directory: " + commandLine.getWorkingDirectory().getAbsolutePath());
169         }
170 
171         int exitCode;
172         try {
173             exitCode = CommandLineUtils.executeCommandLine(commandLine, stdout, stderr);
174         } catch (CommandLineException ex) {
175             throw new ScmException("Error while executing command.", ex);
176         }
177 
178         return exitCode;
179     }
180 
181     static Map<String, String> prepareEnvVariablesForRepository(
182             GitScmProviderRepository repository, Map<String, String> environmentVariables) {
183         Map<String, String> effectiveEnvironmentVariables = new HashMap<>();
184         if (environmentVariables != null) {
185             effectiveEnvironmentVariables.putAll(environmentVariables);
186         }
187         if (StringUtils.isNotBlank(repository.getPrivateKey())) {
188             if (effectiveEnvironmentVariables.putIfAbsent(
189                             VARIABLE_GIT_SSH_COMMAND,
190                             "ssh -o IdentitiesOnly=yes -i "
191                                     + FilenameUtils.separatorsToUnix(repository.getPrivateKey()))
192                     != null) {
193                 LOGGER.warn(
194                         "Ignore GitScmProviderRepository.privateKey as environment variable {} is already set",
195                         VARIABLE_GIT_SSH_COMMAND);
196             }
197         }
198         if (StringUtils.isNotBlank(repository.getPassphrase())) {
199             LOGGER.warn("GitScmProviderRepository.passphrase currently not supported by provider 'git'");
200         }
201         return effectiveEnvironmentVariables;
202     }
203 }