Skip to content

Commit cb2f319

Browse files
committed
JENKINS-24686: fix behavior for job names with blanks
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
1 parent f32a1ed commit cb2f319

File tree

8 files changed

+616
-124
lines changed

8 files changed

+616
-124
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.io.File;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.regex.Matcher;
26+
import java.util.regex.Pattern;
27+
28+
import org.apache.commons.lang.StringUtils;
29+
import org.apache.maven.scm.ScmFile;
30+
import org.apache.maven.scm.ScmFileStatus;
31+
import org.apache.maven.scm.log.ScmLogger;
32+
import org.codehaus.plexus.util.cli.StreamConsumer;
33+
34+
import com.google.common.base.Strings;
35+
36+
/**
37+
* Copied from org.apache.maven.scm.provider.git.gitexe.command.status.GitStatusConsumer (maven-scm 1.9.1)
38+
* and fixed to account for https://issues.apache.org/jira/browse/SCM-772 .
39+
*/
40+
public class FixedGitStatusConsumer
41+
implements StreamConsumer
42+
{
43+
44+
/**
45+
* The pattern used to match added file lines
46+
*/
47+
private static final Pattern ADDED_PATTERN = Pattern.compile( "^A[ M]* (.*)$" );
48+
49+
/**
50+
* The pattern used to match modified file lines
51+
*/
52+
private static final Pattern MODIFIED_PATTERN = Pattern.compile( "^ *M[ M]* (.*)$" );
53+
54+
/**
55+
* The pattern used to match deleted file lines
56+
*/
57+
private static final Pattern DELETED_PATTERN = Pattern.compile( "^ *D * (.*)$" );
58+
59+
/**
60+
* The pattern used to match renamed file lines
61+
*/
62+
private static final Pattern RENAMED_PATTERN = Pattern.compile( "^R (.*) -> (.*)$" );
63+
64+
private ScmLogger logger;
65+
66+
private File workingDirectory;
67+
68+
/**
69+
* Entries are relative to working directory, not to the repositoryroot
70+
*/
71+
private List<ScmFile> changedFiles = new ArrayList<ScmFile>();
72+
73+
private String relativeRepositoryPath;
74+
75+
private final File repositoryRoot;
76+
77+
// ----------------------------------------------------------------------
78+
//
79+
// ----------------------------------------------------------------------
80+
81+
public FixedGitStatusConsumer (ScmLogger logger, File workingDirectory, File repositoryRoot) {
82+
this.logger = logger;
83+
this.workingDirectory = workingDirectory;
84+
if (repositoryRoot != null) {
85+
String absoluteRepositoryRoot = repositoryRoot.getAbsolutePath(); // Make sure all separators are File.separator
86+
// The revparse runs with fileset.getBasedir(). That of course must be under the repo root.
87+
String basePath = workingDirectory.getAbsolutePath();
88+
if (!absoluteRepositoryRoot.endsWith(File.separator)) {
89+
absoluteRepositoryRoot += File.separator;
90+
}
91+
if (basePath.startsWith(absoluteRepositoryRoot)) {
92+
String pathInsideRepo = basePath.substring(absoluteRepositoryRoot.length());
93+
if (!Strings.isNullOrEmpty(pathInsideRepo)) {
94+
relativeRepositoryPath = pathInsideRepo;
95+
}
96+
}
97+
}
98+
this.repositoryRoot = repositoryRoot;
99+
// Either the workingDirectory == repositoryRoot: we have no relativeRepositoryPath set
100+
// Or the working directory was a subdirectory (in the workspace!) of repositoryRoot, then
101+
// relativeRepositoryPath contains now the relative path to the working directory.
102+
//
103+
// It would appear that git status --porcelain always returns paths relative to the repository
104+
// root.
105+
}
106+
107+
// ----------------------------------------------------------------------
108+
// StreamConsumer Implementation
109+
// ----------------------------------------------------------------------
110+
111+
/**
112+
* {@inheritDoc}
113+
*/
114+
public void consumeLine( String line )
115+
{
116+
if ( logger.isDebugEnabled() )
117+
{
118+
logger.debug( line );
119+
}
120+
if ( StringUtils.isEmpty( line ) )
121+
{
122+
return;
123+
}
124+
125+
ScmFileStatus status = null;
126+
127+
List<String> files = new ArrayList<String>();
128+
129+
Matcher matcher;
130+
if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() )
131+
{
132+
status = ScmFileStatus.ADDED;
133+
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
134+
}
135+
else if ( ( matcher = MODIFIED_PATTERN.matcher( line ) ).find() )
136+
{
137+
status = ScmFileStatus.MODIFIED;
138+
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
139+
}
140+
else if ( ( matcher = DELETED_PATTERN.matcher( line ) ) .find() )
141+
{
142+
status = ScmFileStatus.DELETED;
143+
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
144+
}
145+
else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() )
146+
{
147+
status = ScmFileStatus.RENAMED;
148+
files.add(ScmSyncGitUtils.dequote(matcher.group(1)));
149+
files.add(ScmSyncGitUtils.dequote(matcher.group(2)));
150+
}
151+
else
152+
{
153+
logger.warn( "Ignoring unrecognized line: " + line );
154+
return;
155+
}
156+
157+
// If the file isn't a file; don't add it.
158+
if (files.isEmpty() || status == null) {
159+
return;
160+
}
161+
File checkDir = repositoryRoot;
162+
if (workingDirectory != null && relativeRepositoryPath != null) {
163+
// Make all paths relative to this directory.
164+
List<String> relativeNames = new ArrayList<String>();
165+
for (String repoRelativeName : files) {
166+
relativeNames.add(ScmSyncGitUtils.relativizePath(relativeRepositoryPath, new File(repoRelativeName).getPath()));
167+
}
168+
files = relativeNames;
169+
checkDir = workingDirectory;
170+
}
171+
// Now check them all against the checkDir. This check has been taken over from the base implementation
172+
// in maven-scm's GitStatusConsumer, but I'm not really sure this makes sense. Who said the workspace
173+
// had to be equal to the git index (staging area) here?
174+
if (status == ScmFileStatus.RENAMED) {
175+
String oldFilePath = files.get( 0 );
176+
String newFilePath = files.get( 1 );
177+
if (new File(checkDir, oldFilePath).isFile()) {
178+
logger.debug("file '" + oldFilePath + "' still exists after rename");
179+
return;
180+
}
181+
if (!new File(checkDir, newFilePath).isFile()) {
182+
logger.debug("file '" + newFilePath + "' does not exist after rename");
183+
return;
184+
}
185+
} else if (status == ScmFileStatus.DELETED) {
186+
if (new File(checkDir, files.get(0)).isFile()) {
187+
return;
188+
}
189+
} else {
190+
if (!new File(checkDir, files.get(0)).isFile()) {
191+
return;
192+
}
193+
}
194+
195+
for (String file : files) {
196+
changedFiles.add(new ScmFile(file.replaceAll(File.separator, "/"), status));
197+
}
198+
}
199+
200+
public List<ScmFile> getChangedFiles()
201+
{
202+
return changedFiles;
203+
}
204+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package hudson.plugins.scm_sync_configuration.scms.customproviders.git.gitexe;
2+
3+
import java.io.File;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.List;
7+
8+
import org.apache.commons.io.FilenameUtils;
9+
import org.apache.maven.scm.ScmException;
10+
import org.apache.maven.scm.ScmFile;
11+
import org.apache.maven.scm.ScmFileSet;
12+
import org.apache.maven.scm.command.add.AddScmResult;
13+
import org.apache.maven.scm.command.status.StatusScmResult;
14+
import org.apache.maven.scm.provider.ScmProviderRepository;
15+
import org.apache.maven.scm.provider.git.gitexe.command.GitCommandLineUtils;
16+
import org.apache.maven.scm.provider.git.gitexe.command.add.GitAddCommand;
17+
import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
18+
import org.codehaus.plexus.util.Os;
19+
import org.codehaus.plexus.util.cli.CommandLineUtils;
20+
import org.codehaus.plexus.util.cli.Commandline;
21+
22+
public class ScmSyncGitAddCommand extends GitAddCommand {
23+
24+
protected AddScmResult executeAddCommand(ScmProviderRepository repo, ScmFileSet fileSet, String message, boolean binary) throws ScmException {
25+
GitScmProviderRepository repository = (GitScmProviderRepository) repo;
26+
27+
if (fileSet.getFileList().isEmpty()) {
28+
throw new ScmException("You must provide at least one file/directory to add");
29+
}
30+
31+
AddScmResult result = executeAddFileSet(fileSet);
32+
33+
if (result != null) {
34+
return result;
35+
}
36+
37+
ScmSyncGitStatusCommand statusCommand = new ScmSyncGitStatusCommand();
38+
statusCommand.setLogger(getLogger());
39+
StatusScmResult status = statusCommand.executeStatusCommand(repository, fileSet);
40+
getLogger().warn("add - status - " + status.isSuccess());
41+
for (ScmFile s : status.getChangedFiles()) {
42+
getLogger().warn("added " + s.getPath());
43+
}
44+
List<ScmFile> changedFiles = new ArrayList<ScmFile>();
45+
46+
if (fileSet.getFileList().isEmpty()) {
47+
changedFiles = status.getChangedFiles();
48+
} else {
49+
for (ScmFile scmfile : status.getChangedFiles()) {
50+
// if a specific fileSet is given, we have to check if the file is really tracked
51+
for (File f : fileSet.getFileList()) {
52+
if (FilenameUtils.separatorsToUnix(f.getPath()).equals(scmfile.getPath())) {
53+
changedFiles.add(scmfile);
54+
}
55+
}
56+
}
57+
}
58+
Commandline cl = createCommandLine(fileSet.getBasedir(), fileSet.getFileList());
59+
return new AddScmResult(cl.toString(), changedFiles);
60+
}
61+
62+
public static Commandline createCommandLine(File workingDirectory, List<File> files) throws ScmException {
63+
Commandline cl = GitCommandLineUtils.getBaseGitCommandLine(workingDirectory, "add");
64+
65+
// use this separator to make clear that the following parameters are files and not revision info.
66+
cl.createArg().setValue("--");
67+
68+
ScmSyncGitUtils.addTarget(cl, files);
69+
70+
return cl;
71+
}
72+
73+
protected AddScmResult executeAddFileSet(ScmFileSet fileSet) throws ScmException {
74+
File workingDirectory = fileSet.getBasedir();
75+
List<File> files = fileSet.getFileList();
76+
77+
// command line can be too long for windows so add files individually (see SCM-697)
78+
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
79+
for (File file : files) {
80+
AddScmResult result = executeAddFiles(workingDirectory, Collections.singletonList(file));
81+
if (result != null) {
82+
return result;
83+
}
84+
}
85+
} else {
86+
AddScmResult result = executeAddFiles(workingDirectory, files);
87+
if (result != null) {
88+
return result;
89+
}
90+
}
91+
92+
return null;
93+
}
94+
95+
private AddScmResult executeAddFiles(File workingDirectory, List<File> files) throws ScmException {
96+
Commandline cl = createCommandLine(workingDirectory, files);
97+
98+
CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
99+
CommandLineUtils.StringStreamConsumer stdout = new CommandLineUtils.StringStreamConsumer();
100+
101+
int exitCode = -1;
102+
try {
103+
exitCode = GitCommandLineUtils.execute(cl, stdout, stderr, getLogger());
104+
} catch (Throwable t) {
105+
getLogger().error("Failed:", t);
106+
}
107+
if (exitCode != 0) {
108+
String msg = stderr.getOutput();
109+
getLogger().info("Add failed:" + msg);
110+
return new AddScmResult(cl.toString(), "The git-add command failed.", msg, false);
111+
}
112+
113+
return null;
114+
}
115+
116+
}

0 commit comments

Comments
 (0)