Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,22 @@ public ProcessResult build(String... args) throws Exception {
return runBinary("build", args);
}

/**
* Runs <code>bazel cquery &lt;args&gt;</code> and returns the result. Asserts that the process
* exit code is zero. Does not assert that the error stream is empty.
*
* @param args arguments to pass to cquery command
* @return ProcessResult with process exit code, strings with stdout and error streams contents
* @throws TimeoutException in case of timeout
* @throws IOException in case of the process startup/interaction problems
* @throws InterruptedException if the current thread is interrupted while waiting
* @throws ProcessRunnerException if the process return code is not zero or error stream is not
* empty when it was expected
*/
public ProcessResult cquery(String... args) throws Exception {
return runBinary("cquery", args);
}

/**
* Runs <code>bazel query &lt;args&gt;</code> and returns the result. Asserts that the process
* exit code is zero. Does not assert that the error stream is empty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@
package com.google.devtools.build.lib.blackbox.tests.workspace;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.devtools.build.lib.blackbox.tests.workspace.RepoWithRuleWritingTextGenerator.callRule;
import static com.google.devtools.build.lib.blackbox.tests.workspace.RepoWithRuleWritingTextGenerator.loadRule;
import static java.util.Map.entry;
import static java.util.stream.Collectors.toMap;

import com.google.devtools.build.lib.blackbox.framework.BlackBoxTestContext;
import com.google.devtools.build.lib.blackbox.framework.BuilderRunner;
import com.google.devtools.build.lib.blackbox.framework.PathUtils;
import com.google.devtools.build.lib.blackbox.framework.ProcessResult;
import com.google.devtools.build.lib.blackbox.junit.AbstractBlackBoxTest;
import com.google.devtools.build.lib.util.OS;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;

/**
Expand Down Expand Up @@ -302,6 +310,139 @@ public void testGitRepositoryErrorMessage() throws Exception {
assertThat(result.errString()).contains("fatal: Could not read from remote repository.");
}

/**
* Tests that git_repository ignores a host core.autocrlf (the LF-to-CRLF checkout rewrite that is
* prevalent on Windows), supplied here via $HOME/.gitconfig. Honoring it would tie the checkout
* to the host's git setup (CRLF where autocrlf is set, LF elsewhere), so fetches would not be
* reproducible.
*/
@Test
public void testCheckoutIgnoresHostAutocrlf() throws Exception {
assertCheckoutIgnoresHostGitConfig(
scratch -> {
Path home = Files.createDirectories(scratch.resolve("home"));
Files.writeString(
home.resolve(".gitconfig"),
"""
[core]
\tautocrlf = true
""");
return Map.ofEntries(
entry("HOME", home.toString()), // git also honors HOME (over USERPROFILE) on Windows
entry("GIT_CONFIG_NOSYSTEM", "1")); // neutralize host /etc/gitconfig in this test
});
}

/**
* Tests that git_repository ignores a host core.eol (which sets the working-tree EOL of
* text-marked files on checkout), supplied here via a GIT_CONFIG_SYSTEM file plus a global
* text=auto attribute so the files count as text. Honoring it would tie the checkout to the
* host's git setup, so fetches would not be reproducible.
*/
@Test
public void testCheckoutIgnoresHostEol() throws Exception {
assertCheckoutIgnoresHostGitConfig(
scratch -> {
// core.eol only converts files marked text, so plant an ambient "* text=auto".
Path etc = Files.createDirectories(scratch.resolve("etc"));
Path gitattributes = etc.resolve("gitattributes");
Files.writeString(
gitattributes,
"""
* text=auto
""");
Path gitconfig = etc.resolve("gitconfig");
Files.writeString(
gitconfig,
"""
[core]
\teol = crlf
\tattributesFile = %s
"""
// git config would read backslashes as escapes on Windows.
.formatted(PathUtils.pathForStarlarkFile(gitattributes)));
String nullConfig = OS.getCurrent() == OS.WINDOWS ? "NUL" : "/dev/null";
return Map.ofEntries(
entry("GIT_CONFIG_SYSTEM", gitconfig.toString()),
entry("GIT_CONFIG_GLOBAL", nullConfig)); // neutralize host ~/.gitconfig in this test
});
}

@FunctionalInterface
private interface HostGitConfig {
/**
* Writes a host git config under {@code scratch} and returns the environment that makes git
* read it while neutralizing the other config sources.
*/
Map<String, String> generate(Path scratch) throws Exception;
}

/**
* Commits files with assorted line endings, fetches them via git_repository under an ambient host
* git config (written per {@code hostGitConfig}), and asserts each checks out byte-for-byte.
*/
private void assertCheckoutIgnoresHostGitConfig(HostGitConfig hostGitConfig) throws Exception {
Path repo = context().getTmpDir().resolve("eol_repo");
GitRepositoryHelper gitRepository = initGitRepository(context(), repo);

// Write the files directly as context().write may use OS-specific newlines.
Files.writeString(repo.resolve("lf.txt"), "stays\nLF\n");
Files.writeString(repo.resolve("crlf.txt"), "stays\r\nCRLF\r\n");
Files.writeString(repo.resolve(".gitattributes"), "lf_to_crlf.txt eol=crlf\n");
Files.writeString(repo.resolve("lf_to_crlf.txt"), "commit LF\ncheckout CRLF\n");

gitRepository.config("core.autocrlf", "false"); // ensure newlines are committed verbatim
gitRepository.addAll();
gitRepository.commit("Add files with assorted line endings");
gitRepository.tag("first");
context()
.write(
"MODULE.bazel",
"git_repository = use_repo_rule(\"@bazel_tools//tools/build_defs/repo:git.bzl\","
+ " \"git_repository\")",
"git_repository(",
" name='ext',",
String.format(" remote='%s',", PathUtils.pathToFileURI(repo.resolve(".git"))),
" tag='first',",
" build_file_content='filegroup(name=\"data\", srcs=glob([\"*.txt\"]))',",
")");

Path scratch = Files.createDirectories(context().getTmpDir().resolve("git_env"));
Map<String, String> repoEnv = new HashMap<>(hostGitConfig.generate(scratch));
if (OS.getCurrent() == OS.WINDOWS) {
// git needs these to run on Windows.
repoEnv.put("SYSTEMROOT", System.getenv("SYSTEMROOT"));
repoEnv.put("SYSTEMDRIVE", System.getenv("SYSTEMDRIVE"));
}

// Make sure no ambient GIT_CONFIG_*, XDG_CONFIG_HOME, etc. leaks into the test.
List<String> flags = new ArrayList<>(List.of("--experimental_strict_repo_env"));
repoEnv.forEach((name, value) -> flags.add("--repo_env=" + name + "=" + value));

BuilderRunner bazel =
WorkspaceTestUtils.bazel(context()).withFlags(flags.toArray(new String[0]));
bazel.build("@ext//:data");
Path execRoot = context().resolveExecRootPath(bazel, "");
Map<String, Path> checkout =
bazel
.cquery("@ext//:data", "--output=files")
.outString()
.trim()
.lines()
.map(execPath -> PathUtils.resolve(execRoot, execPath))
.collect(toMap(path -> path.getFileName().toString(), path -> path));

assertWithMessage("host git config must not add CR")
.that(Files.readString(checkout.get("lf.txt")))
.isEqualTo("stays\nLF\n");
assertWithMessage("host git config must not strip CR")
.that(Files.readString(checkout.get("crlf.txt")))
.isEqualTo("stays\r\nCRLF\r\n");
assertWithMessage("git_repository must not shadow in-tree eol=crlf")
.that(Files.readString(checkout.get("lf_to_crlf.txt")))
.isEqualTo("commit LF\r\ncheckout CRLF\r\n");
}

private static String setupGitRepository(BlackBoxTestContext context, Path repo)
throws Exception {
GitRepositoryHelper gitRepository = initGitRepository(context, repo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ void commit(String commitMessage) throws Exception {
runGit("commit", "-m", commitMessage);
}

/**
* Sets a repository-local git config value.
*
* @param key config key
* @param value config value
* @throws Exception related to the invocation of the external git process (like IOException or
* TimeoutException) or ProcessRunnerException if the process returned not expected return
* code.
*/
void config(String key, String value) throws Exception {
runGit("config", key, value);
}

/**
* Tags the HEAD commit.
*
Expand Down
4 changes: 3 additions & 1 deletion tools/build_defs/repo/git_worker.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ _GIT_LOCAL_ENV_VARS = {
def _execute(ctx, git_repo, args):
# "core.fsmonitor=false" disables git from spawning a file system monitor which can cause hangs when cloning a lot.
# See https://github.com/bazelbuild/bazel/issues/21438
start = ["git", "-c", "core.fsmonitor=false"]
# "core.autocrlf=false" & "core.eol=lf" bypass host config to keep checkouts verbatim; .gitattributes still applies.
# See https://github.com/bazelbuild/bazel/issues/30026
start = ["git", "-c", "core.fsmonitor=false", "-c", "core.autocrlf=false", "-c", "core.eol=lf"]
return ctx.execute(
start + args,
environment = ctx.os.environ | _GIT_LOCAL_ENV_VARS,
Expand Down