Skip to content
Draft
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
4 changes: 2 additions & 2 deletions crates/prek/src/cli/hook_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ use prek_consts::env_vars::EnvVars;
use crate::cli::{self, ExitStatus, RunArgs};
use crate::config::HookType;
use crate::fs::CWD;
use crate::git::GIT_ROOT;
use crate::languages::resolve_command;
use crate::printer::Printer;
use crate::process::Cmd;
use crate::repo;
use crate::store::Store;
use crate::workspace;
use crate::workspace::Project;
Expand Down Expand Up @@ -92,7 +92,7 @@ pub(crate) async fn hook_impl(
};
}
Ok(project) => {
if project.path() != GIT_ROOT.as_ref()? {
if project.path() != repo::root()? {
writeln!(
printer.stdout(),
"Running in workspace: `{}`",
Expand Down
13 changes: 8 additions & 5 deletions crates/prek/src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ use crate::cli::run::{SelectorSource, Selectors};
use crate::cli::{ExitStatus, HookType};
use crate::config::load_config;
use crate::fs::{CWD, Simplified};
use crate::git::{GIT_ROOT, git_cmd};
use crate::git::git_cmd;
use crate::printer::Printer;
use crate::repo;
use crate::store::Store;
use crate::workspace::{Error as WorkspaceError, Project, Workspace};
use crate::{git, warn_user};
Expand Down Expand Up @@ -64,14 +65,16 @@ pub(crate) async fn install(
let hooks_path = if let Some(dir) = git_dir {
dir.join("hooks")
} else {
git::get_git_common_dir().await?.join("hooks")
repo::hooks_dir().await?
};
fs_err::create_dir_all(&hooks_path)?;

let selectors = if let Some(project) = &project {
Some(Selectors::load(&includes, &skips, project.path())?)
} else if !includes.is_empty() || !skips.is_empty() {
anyhow::bail!("Cannot use `--include` or `--skip` outside of a git repository");
anyhow::bail!(
"Cannot use `--include` or `--skip` outside of a Git or Jujutsu (jj) repository"
);
} else {
None
};
Expand Down Expand Up @@ -264,7 +267,7 @@ fn install_hook_script(

write!(hint, " with specified config `{}`", config.display().cyan())?;
} else if let Some(project) = project {
let git_root = GIT_ROOT.as_ref()?;
let git_root = repo::root()?;
let project_path = project.path();
let relative_path = project_path.strip_prefix(git_root).unwrap_or(project_path);
if !relative_path.as_os_str().is_empty() {
Expand Down Expand Up @@ -387,7 +390,7 @@ pub(crate) async fn uninstall(
printer: Printer,
) -> Result<ExitStatus> {
let project = Project::discover(config.as_deref(), &CWD).ok();
let hooks_path = git::get_git_common_dir().await?.join("hooks");
let hooks_path = repo::hooks_dir().await?;

for hook_type in get_hook_types(hook_types, project.as_ref(), config.as_deref()) {
let hook_path = hooks_path.join(hook_type.as_ref());
Expand Down
18 changes: 10 additions & 8 deletions crates/prek/src/cli/run/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::config::{FilePattern, Stage};
use crate::git::GIT_ROOT;
use crate::hook::Hook;
use crate::workspace::Project;
use crate::{fs, git, warn_user};
use crate::{fs, repo, warn_user};

/// Filter filenames by include/exclude patterns.
pub(crate) struct FilenameFilter<'a> {
Expand Down Expand Up @@ -317,7 +317,7 @@ async fn collect_files_from_args(
}

if let (Some(from_ref), Some(to_ref)) = (from_ref, to_ref) {
let files = git::get_changed_files(&from_ref, &to_ref, workspace_root).await?;
let files = repo::changed_files_between(&from_ref, &to_ref, workspace_root).await?;
debug!(
"Files changed between {} and {}: {}",
from_ref,
Expand Down Expand Up @@ -364,7 +364,7 @@ async fn collect_files_from_args(

for dir in directories {
let dir = adjust_relative_path(&dir, git_root)?;
let dir_files = git::ls_files(git_root, &dir).await?;
let dir_files = repo::ls_files(git_root, &dir).await?;
for file in dir_files {
let file = fs::normalize_path(file);
exists.insert(file);
Expand All @@ -376,19 +376,21 @@ async fn collect_files_from_args(
}

if all_files {
let files = git::ls_files(git_root, workspace_root).await?;
let files = repo::ls_files(git_root, workspace_root).await?;
debug!("All files in the workspace: {}", files.len());
return Ok(files);
}

if git::is_in_merge_conflict().await? {
let files = git::get_conflicted_files(workspace_root).await?;
if let Some(files) = repo::conflicted_files(workspace_root).await? {
debug!("Conflicted files: {}", files.len());
return Ok(files);
}

let files = git::get_staged_files(workspace_root).await?;
debug!("Staged files: {}", files.len());
let files = repo::default_files(workspace_root).await?;
debug!(
"Default files selected from repository backend: {}",
files.len()
);

Ok(files)
}
Expand Down
8 changes: 6 additions & 2 deletions crates/prek/src/cli/run/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::fs::CWD;
use crate::git::GIT_ROOT;
use crate::hook::{Hook, InstallInfo, InstalledHook, Repo};
use crate::printer::Printer;
use crate::repo;
use crate::run::{CONCURRENCY, USE_COLOR};
use crate::store::Store;
use crate::workspace::{Project, Workspace};
Expand Down Expand Up @@ -66,10 +67,13 @@ pub(crate) async fn run(
return Ok(ExitStatus::Success);
}

// Ensure we are in a git repository.
// Ensure we are in a supported repository.
LazyLock::force(&GIT_ROOT).as_ref()?;

let should_stash = !all_files && files.is_empty() && directories.is_empty();
let should_stash = !all_files
&& files.is_empty()
&& directories.is_empty()
&& repo::should_stash_by_default_run();

// Check if we have unresolved merge conflict files and fail fast.
if should_stash && git::has_unmerged_paths().await? {
Expand Down
18 changes: 13 additions & 5 deletions crates/prek/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,23 @@ pub(crate) enum Error {

#[error(transparent)]
UTF8(#[from] Utf8Error),

#[error("{0}")]
Message(String),
}

pub(crate) static GIT: LazyLock<Result<PathBuf, which::Error>> =
LazyLock::new(|| which::which("git"));

pub(crate) static GIT_ROOT: LazyLock<Result<PathBuf, Error>> = LazyLock::new(|| {
get_root().inspect(|root| {
debug!("Git root: {}", root.display());
})
});
pub(crate) static GIT_ROOT: LazyLock<Result<PathBuf, Error>> =
LazyLock::new(|| match crate::repo::REPO_CONTEXT.as_ref() {
Ok(repo) => {
let root = repo.root().to_path_buf();
debug!("Repository root: {}", root.display());
Ok(root)
}
Err(err) => Err(Error::Message(err.to_string())),
});

/// Remove some `GIT_` environment variables exposed by `git`.
///
Expand Down Expand Up @@ -72,6 +79,7 @@ pub(crate) static GIT_ENV_TO_REMOVE: LazyLock<Vec<(String, String)>> = LazyLock:
pub(crate) fn git_cmd(summary: &str) -> Result<Cmd, Error> {
let mut cmd = Cmd::new(GIT.as_ref().map_err(|&e| Error::GitNotFound(e))?, summary);
cmd.arg("-c").arg("core.useBuiltinFSMonitor=false");
crate::repo::apply_git_env(&mut cmd);

Ok(cmd)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use anyhow::Result;
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;

use crate::git;
use crate::hook::Hook;
use crate::repo;

pub(crate) async fn check_case_conflict(
hook: &Hook,
Expand All @@ -15,14 +15,14 @@ pub(crate) async fn check_case_conflict(
let work_dir = hook.work_dir();

// Get all files in the repo.
let repo_files = git::ls_files(work_dir, Path::new(".")).await?;
let repo_files = repo::ls_files(work_dir, Path::new(".")).await?;
let mut repo_files_with_dirs: FxHashSet<&Path> = FxHashSet::default();
for path in &repo_files {
insert_path_and_parents(&mut repo_files_with_dirs, path);
}

// Get relevant files (filenames + added files) and include their parent directories.
let added = git::get_added_files(work_dir).await?;
let added = repo::added_files(work_dir).await?;
let mut relevant_files_with_dirs: FxHashSet<&Path> = FxHashSet::default();
for filename in filenames {
insert_path_and_parents(&mut relevant_files_with_dirs, filename);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,18 @@ use std::path::Path;

use futures::StreamExt;
use owo_colors::OwoColorize;
use rustc_hash::FxHashSet;
use tokio::io::AsyncReadExt;

use crate::git;
use crate::hook::Hook;
use crate::hooks::run_concurrent_file_checks;
use crate::repo;
use crate::run::CONCURRENCY;

pub(crate) async fn check_executables_have_shebangs(
hook: &Hook,
filenames: &[&Path],
) -> Result<(i32, Vec<u8>), anyhow::Error> {
let stdout = git::git_cmd("get file file mode")?
.arg("config")
.arg("core.fileMode")
.check(true)
.output()
.await?
.stdout;

let tracks_executable_bit = std::str::from_utf8(&stdout)?.trim() != "false";
let tracks_executable_bit = repo::tracks_executable_bit().await?;
let file_base = hook.project().relative_path();

let (code, output) = if tracks_executable_bit {
Expand Down Expand Up @@ -77,56 +68,17 @@ async fn git_check_shebangs(
file_base: &Path,
filenames: &[&Path],
) -> Result<(i32, Vec<u8>), anyhow::Error> {
let filenames: FxHashSet<_> = filenames.iter().collect();

let output = git::git_cmd("git ls-files")?
.arg("ls-files")
// Show staged contents' mode bits, object name and stage number in the output.
.arg("--stage")
.arg("-z")
.arg("--")
.arg(if file_base.as_os_str().is_empty() {
Path::new(".")
} else {
file_base
})
.check(true)
.output()
.await?;

let entries = output.stdout.split(|&b| b == b'\0').filter_map(|entry| {
let entry = str::from_utf8(entry).ok()?;
if entry.is_empty() {
return None;
}

let mut parts = entry.split('\t');
let metadata = parts.next()?;
let file_name = parts.next()?;
let file_name = Path::new(file_name);
if !filenames.contains(&file_name) {
return None;
}

let mode_str = metadata.split_whitespace().next()?;
let mode_bits = u32::from_str_radix(mode_str, 8).ok()?;
let is_executable = (mode_bits & 0o111) != 0;
Some((file_name, is_executable))
});

let mut tasks = futures::stream::iter(entries)
.map(async |(file_name, is_executable)| {
if is_executable {
let has_shebang = file_has_shebang(file_name).await?;
if has_shebang {
anyhow::Ok((0, Vec::new()))
} else {
let stripped = file_name.strip_prefix(file_base).unwrap_or(file_name);
let msg = print_shebang_warning(stripped);
Ok((1, msg.into_bytes()))
}
let executable_files = repo::executable_files(file_base, filenames).await?;

let mut tasks = futures::stream::iter(executable_files)
.map(async |file_name| {
let full_path = file_base.join(&file_name);
let has_shebang = file_has_shebang(&full_path).await?;
if has_shebang {
anyhow::Ok((0, Vec::new()))
} else {
Ok((0, Vec::new()))
let msg = print_shebang_warning(&file_name);
Ok((1, msg.into_bytes()))
}
})
.buffered(*CONCURRENCY);
Expand Down
Loading
Loading