Skip to content
Merged
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
20 changes: 20 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,3 +584,23 @@ pub(crate) async fn get_parent_commit(commit: &str) -> Result<Option<String>, Er
Ok(None)
}
}

/// Return a list of absolute paths of all git submodules in the repository.
pub(crate) fn list_submodules(git_root: &Path) -> Result<Vec<PathBuf>, Error> {
let git = GIT.as_ref().map_err(|&e| Error::GitNotFound(e))?;
let output = std::process::Command::new(git)
.current_dir(git_root)
.arg("config")
.arg("--file")
.arg(".gitmodules")
.arg("--get-regexp")
.arg(r"^submodule\..*\.path$")
.output()?;

Ok(String::from_utf8_lossy(&output.stdout)
.trim_ascii()
.lines()
.filter_map(|line| line.split_whitespace().nth(1))
.map(|submodule| git_root.join(submodule))
.collect())
}
3 changes: 0 additions & 3 deletions src/languages/python/pep723.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,6 @@ pub(crate) async fn extract_pep723_metadata(hook: &mut Hook) -> Result<()> {
let repo_path = hook.repo_path().unwrap_or(hook.work_dir());

let split = hook.entry.split()?;
if split.is_empty() {
return Ok(());
}
let file = repo_path.join(&split[0]);

let Some(script) = Pep723Script::read(&file).await? else {
Expand Down
15 changes: 13 additions & 2 deletions src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use prek_consts::{ALT_CONFIG_FILE, CONFIG_FILE};
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{debug, error, instrument};
use tracing::{debug, error, instrument, trace};

use crate::cli::run::Selectors;
use crate::config::{self, Config, ManifestHook, read_config};
Expand Down Expand Up @@ -92,7 +92,7 @@ impl PartialEq for Project {
impl Eq for Project {}

impl Hash for Project {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.config_path.hash(state);
}
}
Expand Down Expand Up @@ -650,6 +650,9 @@ impl Workspace {
) -> Result<Vec<Arc<Project>>, Error> {
let projects = Mutex::new(Ok(Vec::new()));

let git_root = GIT_ROOT.as_ref().map_err(|e| Error::Git(e.into()))?;
let submodules = git::list_submodules(git_root).unwrap_or_default();

ignore::WalkBuilder::new(root)
.follow_links(false)
.add_custom_ignore_filename(".prekignore")
Expand All @@ -665,6 +668,14 @@ impl Workspace {
if !file_type.is_dir() {
return WalkState::Continue;
}
// Skip git submodules
if submodules.iter().any(|submodule| submodule == entry.path()) {
trace!(
path = %entry.path().user_display(),
"Skipping git submodule"
);
return WalkState::Skip;
}

match Project::from_directory(entry.path()) {
Ok(mut project) => {
Expand Down
16 changes: 16 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ impl TestContext {
let temp_dir = ChildPath::new(root.path()).child("temp");
fs_err::create_dir_all(&temp_dir).expect("Failed to create test working directory");

Self::from_root(root, temp_dir)
}

pub fn new_at(path: PathBuf) -> Self {
let bucket = Self::test_bucket_dir();
fs_err::create_dir_all(&bucket).expect("Failed to create test bucket");

let root = tempfile::TempDir::new_in(bucket).expect("Failed to create test root directory");

let temp_dir = ChildPath::new(path);
fs_err::create_dir_all(&temp_dir).expect("Failed to create test working directory");

Self::from_root(root, temp_dir)
}

fn from_root(root: tempfile::TempDir, temp_dir: ChildPath) -> Self {
let home_dir = ChildPath::new(root.path()).child("home");
fs_err::create_dir_all(&home_dir).expect("Failed to create test home directory");

Expand Down
96 changes: 96 additions & 0 deletions tests/workspace.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
mod common;

use std::process::Command;

use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::fixture::{FileWriteStr, PathChild};
use indoc::indoc;
use prek_consts::env_vars::EnvVars;
Expand Down Expand Up @@ -909,3 +912,96 @@ fn reference_files_across_projects() -> Result<()> {

Ok(())
}

#[test]
fn submodule_discovery() -> Result<()> {
let context = TestContext::new();
let cwd = context.work_dir();
context.init_project();

let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};

context.setup_workspace(&["project2"], config)?;

// Create a submodule
let submodule_path = cwd.child("submodule");
let submodule_context = TestContext::new_at(submodule_path.to_path_buf());

submodule_context.init_project();
submodule_context.configure_git_author();
submodule_context.write_pre_commit_config(config);
submodule_context.git_add(".");
submodule_context.git_commit("Initial commit");

// Add submodule to the main project
Command::new("git")
.args(["submodule", "add", "./submodule"])
.current_dir(cwd)
.assert()
.success();
context.git_add(".");

// 1. Test that workspace discovery does not recurse into git submodules
cmd_snapshot!(context.filters(), context.run().arg("--all-files"), @r"
success: true
exit_code: 0
----- stdout -----
Running hooks for `project2`:
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']

Running hooks for `.`:
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', '.gitmodules', 'project2/.pre-commit-config.yaml']

----- stderr -----
");

// 2. Test that current directory is in the submodule with a .pre-commit-config
cmd_snapshot!(context.filters(), context.run().current_dir(&submodule_path).arg("--all-files"), @r"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/submodule
['.pre-commit-config.yaml']

----- stderr -----
");

// 3. Test that current directory is in the submodule without .pre-commit-config
// Remove the config file in the submodule
std::fs::remove_file(submodule_path.join(".pre-commit-config.yaml"))?;
submodule_context.git_add(".");
submodule_context.git_commit("Remove config");

cmd_snapshot!(context.filters(), context.run().current_dir(&submodule_path), @r"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
error: No `.pre-commit-config.yaml` found in the current directory or parent directories.

hint: If you just added one, rerun your command with the `--refresh` flag to rescan the workspace.
");

Ok(())
}
Loading