@NotNull
UpdateIndexCommand updateIndex();
+
+ @NotNull
+ DiffCommand diff();
}
return new UpdateIndexCommandImpl(createCommandLine());
}
+ @NotNull
+ @Override
+ public DiffCommand diff() {
+ return new DiffCommandImpl(createCommandLine());
+ }
+
@NotNull
public Branches listBranches() throws VcsException {
GitCommandLine cmd = createCommandLine();
package jetbrains.buildServer.buildTriggers.vcs.git.agent;
import com.intellij.openapi.util.Trinity;
+import jetbrains.buildServer.BuildProblemData;
import jetbrains.buildServer.agent.AgentRunningBuild;
import jetbrains.buildServer.agent.BuildDirectoryCleanerCallback;
import jetbrains.buildServer.agent.BuildProgressLogger;
import jetbrains.buildServer.buildTriggers.vcs.git.agent.errors.GitOutdatedIndexException;
import jetbrains.buildServer.log.Loggers;
import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.StringUtil;
import jetbrains.buildServer.vcs.*;
import org.apache.log4j.Logger;
import org.eclipse.jgit.errors.ConfigInvalidException;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.regex.Matcher;
import static com.intellij.openapi.util.text.StringUtil.isEmpty;
logSshOptions(myPluginConfig.getGitVersion());
checkAuthMethodIsSupported();
doUpdate();
+ checkNoDiffWithUpperLimitRevision();
}
private void logSshOptions(@NotNull GitVersion gitVersion) {
}
}
}
+
+
+ private void checkNoDiffWithUpperLimitRevision() {
+ if ("false".equals(myBuild.getSharedConfigParameters().get("teamcity.git.checkDiffWithUpperLimitRevision"))) {
+ return;
+ }
+
+ String upperLimitRevision = getUpperLimitRevision();
+ if (upperLimitRevision == null) {
+ return;
+ }
+
+ String message = "Check no diff with upper limit revision " + upperLimitRevision;
+ myLogger.activityStarted(message, GitBuildProgressLogger.GIT_PROGRESS_ACTIVITY);
+ try {
+ if (!ensureCommitLoaded(upperLimitRevision)) {
+ myLogger.warning("Failed to fetch " + upperLimitRevision + ", will not analyze diff with upper limit revision");
+ return;
+ }
+ List<String> pathsMatchedByRules = getChangedFilesMatchedByRules(upperLimitRevision);
+ if (!pathsMatchedByRules.isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Files matched by checkout rules changed between build revision and upper-limit revision\n");
+ msg.append("Checkout rules: '").append(myRules.getAsString()).append("'\n");
+ msg.append("Build revision: '").append(myRevision).append("'\n");
+ msg.append("Upper limit revision: '").append(upperLimitRevision).append("'\n");
+ msg.append("Files:\n");
+ for (String path : pathsMatchedByRules) {
+ msg.append("\t").append(path).append("\n");
+ }
+ myLogger.error(msg.toString());
+ String type = "UpperLimitRevisionDiff";
+ myLogger.logBuildProblem(BuildProblemData.createBuildProblem(type + myRoot.getId(), type, "Diff with upper limit revision found"));
+ }
+ } finally {
+ myLogger.activityFinished(message, GitBuildProgressLogger.GIT_PROGRESS_ACTIVITY);
+ }
+ }
+
+ private boolean ensureCommitLoaded(@NotNull String commit) {
+ if (hasRevision(myTargetDirectory, commit))
+ return true;
+ try {
+ fetchAllBranches();
+ } catch (VcsException e) {
+ LOG.warn("Error while fetching commit " + commit, e);
+ return false;
+ }
+ return hasRevision(myTargetDirectory, commit);
+ }
+
+ @NotNull
+ private List<String> getChangedFilesMatchedByRules(@NotNull String upperLimitRevision) {
+ List<String> pathsMatchedByRules = new ArrayList<String>();
+ List<String> changedFiles = getChangedFiles(upperLimitRevision);
+ for (String file : changedFiles) {
+ if (myRules.map(file) != null) {
+ pathsMatchedByRules.add(file);
+ }
+ }
+ return pathsMatchedByRules;
+ }
+
+ @NotNull
+ private List<String> getChangedFiles(@NotNull String upperLimitRevision) {
+ try {
+ return myGitFactory.create(myTargetDirectory).diff()
+ .setFormat("--name-only")
+ .setCommit1(myRevision)
+ .setCommit2(upperLimitRevision)
+ .call();
+ } catch (VcsException e) {
+ myLogger.warning("Error while computing changed files between build and upper limit revisions: " + e.toString());
+ return Collections.emptyList();
+ }
+ }
+
+ @Nullable
+ private String getUpperLimitRevision() {
+ String rootExtId = getVcsRootExtId();
+ return rootExtId != null ? myBuild.getSharedConfigParameters().get("teamcity.upperLimitRevision." + rootExtId) : null;
+ }
+
+ @Nullable
+ private String getVcsRootExtId() {
+ // We don't have vcs root extId on the agent, deduce it from vcs.number parameters
+ String revisionParamPrefix = "build.vcs.number.";
+ String vcsRootExtId = null;
+ Map<String, String> params = myBuild.getSharedConfigParameters();
+ for (Map.Entry<String, String> param : params.entrySet()) {
+ if (param.getKey().startsWith(revisionParamPrefix) && myRevision.equals(param.getValue())) {
+ String extId = param.getKey().substring(revisionParamPrefix.length());
+ if (StringUtil.isNotEmpty(extId) && Character.isDigit(extId.charAt(0))) {
+ // We have build.vcs.number.<extId> and build.vcs.number.<root number>, ignore the latter (extId cannot start with digit)
+ continue;
+ }
+ if (vcsRootExtId != null) {
+ LOG.debug("Build has more than one VCS root with same revision " + myRevision + ": " + vcsRootExtId + " and " +
+ extId + ", cannot deduce VCS root extId");
+ return null;
+ } else {
+ vcsRootExtId = extId;
+ }
+ }
+ }
+ return vcsRootExtId;
+ }
}
--- /dev/null
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed 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.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.agent.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public interface DiffCommand {
+
+ @NotNull
+ DiffCommand setCommit1(@NotNull String commit1);
+
+ @NotNull
+ DiffCommand setCommit2(@NotNull String commit2);
+
+ @NotNull
+ DiffCommand setFormat(@NotNull String format);
+
+ @NotNull
+ List<String> call() throws VcsException;
+
+}
--- /dev/null
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed 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.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.agent.command.impl;
+
+import jetbrains.buildServer.ExecResult;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitCommandLine;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.command.DiffCommand;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DiffCommandImpl extends BaseCommandImpl implements DiffCommand {
+
+ private String myCommit1;
+ private String myCommit2;
+ private String myFormat;
+
+ public DiffCommandImpl(@NotNull GitCommandLine cmd) {
+ super(cmd);
+ }
+
+ @NotNull
+ @Override
+ public DiffCommand setCommit1(@NotNull final String commit1) {
+ myCommit1 = commit1;
+ return this;
+ }
+
+ @NotNull
+ @Override
+ public DiffCommand setCommit2(@NotNull final String commit2) {
+ myCommit2 = commit2;
+ return this;
+ }
+
+ @NotNull
+ @Override
+ public DiffCommand setFormat(@NotNull final String format) {
+ myFormat = format;
+ return this;
+ }
+
+ @NotNull
+ @Override
+ public List<String> call() throws VcsException {
+ GitCommandLine cmd = getCmd();
+ cmd.addParameter("diff");
+ if (myFormat != null) {
+ cmd.addParameter(myFormat);
+ }
+ if (myCommit1 != null) {
+ cmd.addParameter(myCommit1);
+ }
+ if (myCommit2 != null) {
+ cmd.addParameter(myCommit2);
+ }
+
+ ExecResult r = CommandUtil.runCommand(cmd);
+ return Arrays.asList(StringUtil.splitByLines(r.getStdout().trim()));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed 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.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git.tests;
+
+import jetbrains.buildServer.BuildProblemData;
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.agent.FlowLogger;
+import jetbrains.buildServer.agent.NullBuildProgressLogger;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.GitAgentVcsSupport;
+import jetbrains.buildServer.buildTriggers.vcs.git.agent.PluginConfigImpl;
+import jetbrains.buildServer.messages.BuildMessage1;
+import jetbrains.buildServer.messages.Status;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.util.*;
+
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.GitVersionProvider.getGitPath;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.VcsRootBuilder.vcsRoot;
+import static jetbrains.buildServer.buildTriggers.vcs.git.tests.builders.AgentRunningBuildBuilder.runningBuild;
+import static jetbrains.buildServer.util.Util.map;
+import static org.assertj.core.api.BDDAssertions.then;
+
+@Test
+public class DiffWithUpperLimitRevisionTest extends BaseRemoteRepositoryTest {
+
+ private GitAgentVcsSupport myVcsSupport;
+ private File myCheckoutDir;
+ private VcsRoot myRoot;
+ private BuildLogger myBuildLogger;
+
+ public DiffWithUpperLimitRevisionTest() {
+ super("repo.git");
+ }
+
+
+ @Override
+ @BeforeMethod
+ public void setUp() throws Exception {
+ super.setUp();
+
+ myCheckoutDir = myTempFiles.createTempDir();
+ myVcsSupport = new AgentSupportBuilder(myTempFiles).build();
+ String pathToGit = getGitPath();
+ myRoot = vcsRoot()
+ .withAgentGitPath(pathToGit)
+ .withFetchUrl(getRemoteRepositoryUrl("repo.git"))
+ .withBranch("master")
+ .build();
+ myBuildLogger = new BuildLogger();
+ }
+
+
+ public void no_error_if_upper_limit_revision_param_is_missing() throws Exception {
+ String version = "465ad9f630e451b9f2b782ffb09804c6a98c4bb9";
+ AgentRunningBuild build = createBuild(version, null);
+ myVcsSupport.updateSources(myRoot, new CheckoutRules("+:dir"), version, myCheckoutDir, build, false);
+ }
+
+
+ public void error_if_diff_found() throws Exception {
+ String version = "ad4528ed5c84092fdbe9e0502163cf8d6e6141e7";
+ AgentRunningBuild build = createBuild(version, "465ad9f630e451b9f2b782ffb09804c6a98c4bb9");
+ myVcsSupport.updateSources(myRoot, new CheckoutRules("+:dir"), version, myCheckoutDir, build, false);
+ then(myBuildLogger.getErrors()).isNotEmpty();
+ then(myBuildLogger.getErrors().iterator().next()).contains("Files matched by checkout rules changed between build revision and upper-limit revision");
+ }
+
+
+ public void can_be_disabled() throws Exception {
+ String version = "ad4528ed5c84092fdbe9e0502163cf8d6e6141e7";
+ AgentRunningBuild build = createBuild(version, "465ad9f630e451b9f2b782ffb09804c6a98c4bb9", "teamcity.git.checkDiffWithUpperLimitRevision", "false");
+ myVcsSupport.updateSources(myRoot, new CheckoutRules("+:dir"), version, myCheckoutDir, build, false);
+ then(myBuildLogger.getErrors()).isEmpty();
+ }
+
+
+ private AgentRunningBuild createBuild(@NotNull String buildRevision, @Nullable String upperLimitRevision, String... additionalParams) {
+ String rootExtId = "RootExtId";
+ Map<String, String> params = new HashMap<>();
+ params.put(PluginConfigImpl.USE_SPARSE_CHECKOUT, "true");
+ params.put("build.vcs.number." + rootExtId, buildRevision);
+ params.put("build.vcs.number.1", buildRevision);
+ if (upperLimitRevision != null) {
+ params.put("teamcity.upperLimitRevision." + rootExtId, upperLimitRevision);
+ }
+ params.putAll(map(additionalParams));
+ return runningBuild().sharedConfigParams(params).withBuildLogger(myBuildLogger).build();
+ }
+
+
+ private class BuildLogger extends NullBuildProgressLogger {
+ private List<String> myErrors = new ArrayList<>();
+
+ @NotNull
+ List<String> getErrors() {
+ return myErrors;
+ }
+
+ @Override
+ public void error(final String message) {
+ myErrors.add(message);
+ }
+ }
+}
private Map<String, String> mySharedConfigParameters = new HashMap<String, String>();
private Map<String, String> mySharedBuildParameters = new HashMap<String, String>();
private List<VcsRootEntry> myRootEntries = null;
+ private BuildProgressLogger myBuildLogger = null;
public static AgentRunningBuildBuilder runningBuild() {
return new AgentRunningBuildBuilder();
}
+ public AgentRunningBuildBuilder withBuildLogger(BuildProgressLogger logger) {
+ myBuildLogger = logger;
+ return this;
+ }
public AgentRunningBuildBuilder sharedConfigParams(String... params) {
mySharedConfigParameters.putAll(map(params));
@NotNull
public BuildProgressLogger getBuildLogger() {
- return new NullBuildProgressLogger();
+ return myBuildLogger != null ? myBuildLogger : new NullBuildProgressLogger();
}
@NotNull
<class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.HttpUrlWithUsernameTest"/>
<class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.RemoteRepositoryConfiguratorTest"/>
<class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.AutoCheckoutTest"/>
+ <class name="jetbrains.buildServer.buildTriggers.vcs.git.tests.DiffWithUpperLimitRevisionTest"/>
</classes>
</test>
</suite>
\ No newline at end of file