Skip to content

Commit

Permalink
fix: don't read .gitignore from above the git root (#458)
Browse files Browse the repository at this point in the history
Also, only read .gitignore from git directories.

Release 24.11.2

Fixes #450
  • Loading branch information
sourcefrog authored Nov 24, 2024
2 parents e3ddd9e + bb48673 commit 005fde4
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 46 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-mutants"
version = "24.11.1"
version = "24.11.2"
edition = "2021"
authors = ["Martin Pool"]
license = "MIT"
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# cargo-mutants changelog

## 24.11.2

- Changed: `.gitignore` (and other git ignore files) are only consulted when copying the tree if it is contained within a directory with a `.git` directory.

- Fixed: `.gitignore` files above the git root directory are no longer read. In particular this fixes the problem where `.gitignore *` in the home directory would prevent copying any source trees.

## 24.11.1

- Changed: The arguments of calls to functions or methods named `with_capacity` are not mutated by default. This can be turned off with `--skip-calls-defaults=false` on the command line or `skip_calls_defaults = false` in `.cargo/mutants.toml`.
Expand Down
6 changes: 5 additions & 1 deletion book/src/build-dirs.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ Files or directories matching these patterns are not copied:

From 23.11.2, by default, cargo-mutants will not copy files that are excluded by gitignore patterns, to make copying faster in large trees.

This behavior can be turned off with `--gitignore=false`.
gitignore filtering is only used within trees containing a `.git` directory.

The filter, based on the [`ignore` crate](https://docs.rs/ignore/), also respects global git ignore configuration in the home directory, as well as `.gitignore` files within the tree.

This behavior can be turned off with `--gitignore=false`, causing ignored files to be copied.
86 changes: 51 additions & 35 deletions src/copy_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

//! Copy a source tree, with some exclusions, to a new temporary directory.
use std::fs::FileType;

use anyhow::Context;
use camino::{Utf8Path, Utf8PathBuf};
use ignore::WalkBuilder;
use path_slash::PathExt;
use tempfile::TempDir;
use tracing::{debug, warn};

use crate::check_interrupted;
use crate::Console;
use crate::Result;
use crate::{check_interrupted, Console, Result};

#[cfg(unix)]
mod unix;
#[cfg(unix)]
use unix::copy_symlink;

#[cfg(windows)]
mod windows;
#[cfg(windows)]
use windows::copy_symlink;

/// Filenames excluded from being copied with the source.
static SOURCE_EXCLUDE: &[&str] = &[
Expand Down Expand Up @@ -51,16 +57,17 @@ pub fn copy_tree(
.try_into()
.context("Convert path to UTF-8")?;
console.start_copy(dest);
for entry in WalkBuilder::new(from_path)
let mut walk_builder = WalkBuilder::new(from_path);
walk_builder
.standard_filters(gitignore)
.hidden(false)
.ignore(false)
.require_git(false)
.hidden(false) // copy hidden files
.ignore(false) // don't use .ignore
.require_git(true) // stop at git root; only read gitignore files inside git trees
.filter_entry(|entry| {
!SOURCE_EXCLUDE.contains(&entry.file_name().to_string_lossy().as_ref())
})
.build()
{
});
debug!(?walk_builder);
for entry in walk_builder.build() {
check_interrupted()?;
let entry = entry?;
let relative_path = entry
Expand Down Expand Up @@ -106,29 +113,38 @@ pub fn copy_tree(
Ok(temp_dir)
}

#[cfg(unix)]
fn copy_symlink(_ft: FileType, src_path: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
let link_target = std::fs::read_link(src_path)
.with_context(|| format!("Failed to read link {src_path:?}"))?;
std::os::unix::fs::symlink(link_target, dest_path)
.with_context(|| format!("Failed to create symlink {dest_path:?}",))?;
Ok(())
}
#[cfg(test)]
mod test {
use std::fs::{create_dir, write};

#[cfg(windows)]
#[mutants::skip] // Mutant tests run on Linux
fn copy_symlink(ft: FileType, src_path: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
use std::os::windows::fs::FileTypeExt;
let link_target =
std::fs::read_link(src_path).with_context(|| format!("read link {src_path:?}"))?;
if ft.is_symlink_dir() {
std::os::windows::fs::symlink_dir(link_target, dest_path)
.with_context(|| format!("create symlink {dest_path:?}"))?;
} else if ft.is_symlink_file() {
std::os::windows::fs::symlink_file(link_target, dest_path)
.with_context(|| format!("create symlink {dest_path:?}"))?;
} else {
anyhow::bail!("Unknown symlink type: {:?}", ft);
use camino::Utf8PathBuf;
use tempfile::TempDir;

use crate::console::Console;
use crate::Result;

use super::copy_tree;

/// Test for regression of <https://github.com/sourcefrog/cargo-mutants/issues/450>
#[test]
fn copy_tree_with_parent_ignoring_star() -> Result<()> {
let tmp_dir = TempDir::new().unwrap();
let tmp = tmp_dir.path();
write(tmp.join(".gitignore"), "*\n")?;

let a = Utf8PathBuf::try_from(tmp.join("a")).unwrap();
create_dir(&a)?;
write(a.join("Cargo.toml"), "[package]\nname = a")?;
let src = a.join("src");
create_dir(&src)?;
write(src.join("main.rs"), "fn main() {}")?;

let dest_tmpdir = copy_tree(&a, "a", true, &Console::new())?;
let dest = dest_tmpdir.path();
assert!(dest.join("Cargo.toml").is_file());
assert!(dest.join("src").is_dir());
assert!(dest.join("src/main.rs").is_file());

Ok(())
}
Ok(())
}
14 changes: 14 additions & 0 deletions src/copy_tree/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::fs::FileType;

use anyhow::Context;
use camino::Utf8Path;

use crate::Result;

pub(super) fn copy_symlink(_ft: FileType, src_path: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
let link_target = std::fs::read_link(src_path)
.with_context(|| format!("Failed to read link {src_path:?}"))?;
std::os::unix::fs::symlink(link_target, dest_path)
.with_context(|| format!("Failed to create symlink {dest_path:?}",))?;
Ok(())
}
22 changes: 22 additions & 0 deletions src/copy_tree/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::fs::FileType;
use std::os::windows::fs::FileTypeExt;

use anyhow::Context;
use camino::Utf8Path;

use crate::Result;
#[mutants::skip] // Mutant tests run on Linux
pub(super) fn copy_symlink(ft: FileType, src_path: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
let link_target =
std::fs::read_link(src_path).with_context(|| format!("read link {src_path:?}"))?;
if ft.is_symlink_dir() {
std::os::windows::fs::symlink_dir(link_target, dest_path)
.with_context(|| format!("create symlink {dest_path:?}"))?;
} else if ft.is_symlink_file() {
std::os::windows::fs::symlink_file(link_target, dest_path)
.with_context(|| format!("create symlink {dest_path:?}"))?;
} else {
anyhow::bail!("Unknown symlink type: {:?}", ft);
}
Ok(())
}
16 changes: 10 additions & 6 deletions src/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright 2022-2023 Martin Pool.
// Copyright 2022-2024 Martin Pool.

//! Manipulate Cargo manifest and config files.
//!
//! In particular, when the tree is copied we have to fix up relative paths, so
//! that they still work from the new location of the scratch directory.
use std::fs;
use std::fs::{read_to_string, write};

use anyhow::Context;
use camino::Utf8Path;
Expand All @@ -19,11 +19,15 @@ use crate::Result;
/// `manifest_source_dir` is the directory originally containing the manifest, from
/// which the absolute paths are calculated.
pub fn fix_manifest(manifest_scratch_path: &Utf8Path, source_dir: &Utf8Path) -> Result<()> {
let toml_str = fs::read_to_string(manifest_scratch_path).context("read manifest")?;
let toml_str = read_to_string(manifest_scratch_path).with_context(|| {
format!("failed to read manifest from build directory: {manifest_scratch_path}")
})?;
if let Some(changed_toml) = fix_manifest_toml(&toml_str, source_dir)? {
let toml_str =
toml::to_string_pretty(&changed_toml).context("serialize changed manifest")?;
fs::write(manifest_scratch_path, toml_str.as_bytes()).context("write manifest")?;
write(manifest_scratch_path, toml_str.as_bytes()).with_context(|| {
format!("Failed to write fixed manifest to {manifest_scratch_path}")
})?;
}
Ok(())
}
Expand Down Expand Up @@ -101,9 +105,9 @@ fn fix_dependency_table(dependencies: &mut toml::Value, manifest_source_dir: &Ut
pub fn fix_cargo_config(build_path: &Utf8Path, source_path: &Utf8Path) -> Result<()> {
let config_path = build_path.join(".cargo/config.toml");
if config_path.exists() {
let toml_str = fs::read_to_string(&config_path).context("read .cargo/config.toml")?;
let toml_str = read_to_string(&config_path).context("read .cargo/config.toml")?;
if let Some(changed_toml) = fix_cargo_config_toml(&toml_str, source_path)? {
fs::write(build_path.join(&config_path), changed_toml.as_bytes())
write(build_path.join(&config_path), changed_toml.as_bytes())
.context("write .cargo/config.toml")?;
}
}
Expand Down
7 changes: 5 additions & 2 deletions tests/build_dir.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2023 Martin Pool
// Copyright 2023-2024 Martin Pool

use std::fs::write;
use std::fs::{create_dir, write};

mod util;
use util::{copy_of_testdata, run};
Expand All @@ -10,6 +10,9 @@ fn gitignore_respected_in_copy_by_default() {
// Make a tree with a (dumb) gitignore that excludes the source file; when you copy it
// to a build directory, the source file should not be there and so the check will fail.
let tmp = copy_of_testdata("factorial");
// There must be something that looks like a `.git` dir, otherwise we don't read
// `.gitignore` files.
create_dir(tmp.path().join(".git")).unwrap();
write(tmp.path().join(".gitignore"), b"src\n").unwrap();
run()
.args(["mutants", "--check", "-d"])
Expand Down

0 comments on commit 005fde4

Please sign in to comment.