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 cl, List<File> files) {
56          if (files == null || files.isEmpty()) {
57              return;
58          }
59          final File workingDirectory = cl.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                  cl.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       * @param workingDirectory
88       * @param command
89       * @return TODO
90       */
91      public static Commandline getBaseGitCommandLine(File workingDirectory, String command) {
92          return getBaseGitCommandLine(workingDirectory, command, null, null);
93      }
94  
95      /**
96       * Use this for commands requiring environment variables (i.e. remote commands).
97       * @param workingDirectory
98       * @param command
99       * @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 }