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 */ 046public final class GitCommandLineUtils { 047 private static final Logger LOGGER = LoggerFactory.getLogger(GitCommandLineUtils.class); 048 049 // https://git-scm.com/docs/git#Documentation/git.txt-codeGITSSHCOMMANDcode, requires git 2.3.0 or newer 050 public static final String VARIABLE_GIT_SSH_COMMAND = "GIT_SSH_COMMAND"; 051 052 private GitCommandLineUtils() {} 053 054 public static void addTarget(Commandline commandLine, List<File> files) { 055 if (files == null || files.isEmpty()) { 056 return; 057 } 058 final File workingDirectory = commandLine.getWorkingDirectory(); 059 try { 060 final String canonicalWorkingDirectory = workingDirectory.getCanonicalPath(); 061 for (File file : files) { 062 String relativeFile = file.getPath(); 063 064 final String canonicalFile = file.getCanonicalPath(); 065 if (canonicalFile.startsWith(canonicalWorkingDirectory)) { 066 // so we can omit the starting characters 067 relativeFile = canonicalFile.substring(canonicalWorkingDirectory.length()); 068 069 if (relativeFile.startsWith(File.separator)) { 070 relativeFile = relativeFile.substring(File.separator.length()); 071 } 072 } 073 074 // no setFile() since this screws up the working directory! 075 commandLine.createArg().setValue(FilenameUtils.separatorsToUnix(relativeFile)); 076 } 077 } catch (IOException ex) { 078 throw new IllegalArgumentException( 079 "Could not get canonical paths for workingDirectory = " + workingDirectory + " or files=" + files, 080 ex); 081 } 082 } 083 084 /** 085 * Use this only for commands not requiring environment variables (i.e. local commands). 086 */ 087 public static Commandline getBaseGitCommandLine(File workingDirectory, String command) { 088 return getBaseGitCommandLine(workingDirectory, command, null, null); 089 } 090 091 /** 092 * Use this for commands requiring environment variables (i.e. remote commands). 093 */ 094 public static Commandline getBaseGitCommandLine( 095 File workingDirectory, 096 String command, 097 GitScmProviderRepository repository, 098 Map<String, String> environment) { 099 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}