Skip to content

Commit

Permalink
[JENKINS-24686] Fix behavior for job names with blanks
Browse files Browse the repository at this point in the history
The basic problem here is SCM-772.[1] maven-scm cannot parse the output
from git status if it contains quoted/escaped file names.

There's at least two pull requests, one attached to SCM-772 and one at
[2] aimed at fixing this; both are erroneous and don't look like they'd
go in anytime soon. (The first one would at least replace \r by \f, and
the second one only strips quotes but doesn't de-escape.)

So we fix this by providing our own commands in our own gitexe
provider and making sure that our implementation of these commands can
deal with quoted/escaped filenames.

Enabled the two test cases for job names with blanks.

Also, since we're rewriting part of maven-scm here anyway, I've also
included a minimal and proper fix for SCM-695.[3]

[1] https://issues.apache.org/jira/browse/SCM-772
[2] apache/maven-scm#26
[3] https://issues.apache.org/jira/browse/SCM-695
  • Loading branch information
tomaswolf authored and rodrigc committed Dec 12, 2015
1 parent d92a541 commit 2f59c9d
Show file tree
Hide file tree
Showing 8 changed files with 616 additions and 124 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe;

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.maven.scm.ScmFile;
import org.apache.maven.scm.ScmFileStatus;
import org.apache.maven.scm.log.ScmLogger;
import org.codehaus.plexus.util.cli.StreamConsumer;

import com.google.common.base.Strings;

/**
* Copied from org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer (maven-scm 1.9.1)
* and fixed to account for https://issues.apache.org/jira/browse/SCM-772 .
*/
public class FixedGitStatusConsumer
implements StreamConsumer
{

/**
* The pattern used to match added file lines
*/
private static final Pattern ADDED_PATTERN = Pattern.compile( "^A[ M]* (.*)$" );

/**
* The pattern used to match modified file lines
*/
private static final Pattern MODIFIED_PATTERN = Pattern.compile( "^ *M[ M]* (.*)$" );

/**
* The pattern used to match deleted file lines
*/
private static final Pattern DELETED_PATTERN = Pattern.compile( "^ *D * (.*)$" );

/**
* The pattern used to match renamed file lines
*/
private static final Pattern RENAMED_PATTERN = Pattern.compile( "^R (.*) -> (.*)$" );

private ScmLogger logger;

private File workingDirectory;

/**
* Entries are relative to working directory, not to the repositoryroot
*/
private List<ScmFile> changedFiles = new ArrayList<ScmFile>();

private String relativeRepositoryPath;

private final File repositoryRoot;

// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------

public FixedGitStatusConsumer (ScmLogger logger, File workingDirectory, File repositoryRoot) {
this.logger = logger;
this.workingDirectory = workingDirectory;
if (repositoryRoot != null) {
String absoluteRepositoryRoot = repositoryRoot.getAbsolutePath(); // Make sure all separators are File.separator
// The revparse runs with fileset.getBasedir(). That of course must be under the repo root.
String basePath = workingDirectory.getAbsolutePath();
if (!absoluteRepositoryRoot.endsWith(File.separator)) {
absoluteRepositoryRoot += File.separator;
}
if (basePath.startsWith(absoluteRepositoryRoot)) {
String pathInsideRepo = basePath.substring(absoluteRepositoryRoot.length());
if (!Strings.isNullOrEmpty(pathInsideRepo)) {
relativeRepositoryPath = pathInsideRepo;
}
}
}
this.repositoryRoot = repositoryRoot;
// Either the workingDirectory == repositoryRoot: we have no relativeRepositoryPath set
// Or the working directory was a subdirectory (in the workspace!) of repositoryRoot, then
// relativeRepositoryPath contains now the relative path to the working directory.
//
// It would appear that git status --porcelain always returns paths relative to the repository
// root.
}

// ----------------------------------------------------------------------
// StreamConsumer Implementation
// ----------------------------------------------------------------------

/**
* {@inheritDoc}
*/
public void consumeLine( String line )
{
if ( logger.isDebugEnabled() )
{
logger.debug( line );
}
if ( StringUtils.isEmpty( line ) )
{
return;
}

ScmFileStatus status = null;

List<String> files = new ArrayList<String>();

Matcher matcher;
if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() )
{
status = ScmFileStatus.ADDED;
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
}
else if ( ( matcher = MODIFIED_PATTERN.matcher( line ) ).find() )
{
status = ScmFileStatus.MODIFIED;
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
}
else if ( ( matcher = DELETED_PATTERN.matcher( line ) ) .find() )
{
status = ScmFileStatus.DELETED;
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
}
else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() )
{
status = ScmFileStatus.RENAMED;
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
files.add(ScmSyncGitUtils.dequote(matcher.group(2)));
}
else
{
logger.warn( "Ignoring unrecognized line: " + line );
return;
}

// If the file isn't a file; don't add it.
if (files.isEmpty() || status == null) {
return;
}
File checkDir = repositoryRoot;
if (workingDirectory != null && relativeRepositoryPath != null) {
// Make all paths relative to this directory.
List<String> relativeNames = new ArrayList<String>();
for (String repoRelativeName : files) {
relativeNames.add(ScmSyncGitUtils.relativizePath(relativeRepositoryPath, new File(repoRelativeName).getPath()));
}
files = relativeNames;
checkDir = workingDirectory;
}
// Now check them all against the checkDir. This check has been taken over from the base implementation
// in maven-scm's GitStatusConsumer, but I'm not really sure this makes sense. Who said the workspace
// had to be equal to the git index (staging area) here?
if (status == ScmFileStatus.RENAMED) {
String oldFilePath = files.get( 0 );
String newFilePath = files.get( 1 );
if (new File(checkDir, oldFilePath).isFile()) {
logger.debug("file '" + oldFilePath + "' still exists after rename");
return;
}
if (!new File(checkDir, newFilePath).isFile()) {
logger.debug("file '" + newFilePath + "' does not exist after rename");
return;
}
} else if (status == ScmFileStatus.DELETED) {
if (new File(checkDir, files.get(0)).isFile()) {
return;
}
} else {
if (!new File(checkDir, files.get(0)).isFile()) {
return;
}
}

for (String file : files) {
changedFiles.add(new ScmFile(file.replaceAll(File.separator, "/"), status));
}
}

public List<ScmFile> getChangedFiles()
{
return changedFiles;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.FilenameUtils;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFile;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.command.add.AddScmResult;
import org.apache.maven.scm.command.status.StatusScmResult;
import org.apache.maven.scm.provider.ScmProviderRepository;
import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils;
import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand;
import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;

public class ScmSyncGitAddCommand extends GitAddCommand {

protected AddScmResult executeAddCommand(ScmProviderRepository repo, ScmFileSet fileSet, String message, boolean binary) throws ScmException {
GitScmProviderRepository repository = (GitScmProviderRepository) repo;

if (fileSet.getFileList().isEmpty()) {
throw new ScmException("You must provide at least one file/directory to add");
}

AddScmResult result = executeAddFileSet(fileSet);

if (result != null) {
return result;
}

ScmSyncGitStatusCommand statusCommand = new ScmSyncGitStatusCommand();
statusCommand.setLogger(getLogger());
StatusScmResult status = statusCommand.executeStatusCommand(repository, fileSet);
getLogger().warn("add - status - " + status.isSuccess());
for (ScmFile s : status.getChangedFiles()) {
getLogger().warn("added " + s.getPath());
}
List<ScmFile> changedFiles = new ArrayList<ScmFile>();

if (fileSet.getFileList().isEmpty()) {
changedFiles = status.getChangedFiles();
} else {
for (ScmFile scmfile : status.getChangedFiles()) {
// if a specific fileSet is given, we have to check if the file is really tracked
for (File f : fileSet.getFileList()) {
if (FilenameUtils.separatorsToUnix(f.getPath()).equals(scmfile.getPath())) {
changedFiles.add(scmfile);
}
}
}
}
Commandline cl = createCommandLine(fileSet.getBasedir(), fileSet.getFileList());
return new AddScmResult(cl.toString(), changedFiles);
}

public static Commandline createCommandLine(File workingDirectory, List<File> files) throws ScmException {
Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "add");

// use this separator to make clear that the following parameters are files and not revision info.
cl.createArg().setValue("--");

ScmSyncGitUtils.addTarget(cl, files);

return cl;
}

protected AddScmResult executeAddFileSet(ScmFileSet fileSet) throws ScmException {
File workingDirectory = fileSet.getBasedir();
List<File> files = fileSet.getFileList();

// command line can be too long for windows so add files individually (see SCM-697)
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
for (File file : files) {
AddScmResult result = executeAddFiles(workingDirectory, Collections.singletonList(file));
if (result != null) {
return result;
}
}
} else {
AddScmResult result = executeAddFiles(workingDirectory, files);
if (result != null) {
return result;
}
}

return null;
}

private AddScmResult executeAddFiles(File workingDirectory, List<File> files) throws ScmException {
Commandline cl = createCommandLine(workingDirectory, files);

CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();

int exitCode = -1;
try {
exitCode = GitCommandLineUtils.execute(cl, stdout, stderr, getLogger());
} catch (Throwable t) {
getLogger().error("Failed:", t);
}
if (exitCode != 0) {
String msg = stderr.getOutput();
getLogger().info("Add failed:" + msg);
return new AddScmResult(cl.toString(), "The git-add command failed.", msg, false);
}

return null;
}

}
Loading

0 comments on commit 2f59c9d

Please sign in to comment.