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