Skip to content

Commit

Permalink
Check config file staged (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
j178 authored Oct 31, 2024
1 parent 6a42bcd commit 3c1933b
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 23 deletions.
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ exclude: |
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer

- repo: https://github.com/crate-ci/typos
rev: v1.26.0
hooks:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
A reimplementation of the [pre-commit](https://pre-commit.com/) tool in Rust, providing a faster and dependency-free alternative.
It aims to be a drop-in replacement for the original tool while also providing some more advanced features.

> [!WARNING]
> [!WARNING]
> This project is still in very early development, only a few of the original pre-commit features are implemented.
## Features
Expand Down Expand Up @@ -39,8 +39,8 @@ Please refer to the [official documentation](https://pre-commit.com/) for more i

## Acknowledgements

This project is heavily inspired by the original [pre-commit](https://pre-commit.com/) tool, and it wouldn't be possible without the hard work
This project is heavily inspired by the original [pre-commit](https://pre-commit.com/) tool, and it wouldn't be possible without the hard work
of the maintainers and contributors of that project.

And a special thanks to the [Astral](https://github.com/astral-sh) team for their remarkable projects, particularly [uv](https://github.com/astral-sh/uv),
And a special thanks to the [Astral](https://github.com/astral-sh) team for their remarkable projects, particularly [uv](https://github.com/astral-sh/uv),
from which I've learned a lot on how to write efficient and idiomatic Rust code.
28 changes: 25 additions & 3 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use unicode_width::UnicodeWidthStr;

use crate::cli::ExitStatus;
use crate::config::Stage;
use crate::fs::normalize_path;
use crate::git::{get_all_files, get_changed_files, get_diff, get_staged_files};
use crate::fs::{normalize_path, Simplified};
use crate::git::{get_all_files, get_changed_files, get_diff, get_staged_files, GIT};
use crate::hook::{Hook, Project};
use crate::identify::tags_from_path;
use crate::printer::Printer;
Expand All @@ -37,8 +37,18 @@ pub(crate) async fn run(
verbose: bool,
printer: Printer,
) -> Result<ExitStatus> {
let config_file = Project::find_config_file(config)?;
if config_not_staged(&config_file).await? {
writeln!(
printer.stderr(),
"Your pre-commit configuration is unstaged.\n`git add {}` to fix this.",
&config_file.user_display()
)?;
return Ok(ExitStatus::Failure);
}

let mut project = Project::new(config_file)?;
let store = Store::from_settings()?.init()?;
let mut project = Project::current(config)?;

// TODO: check .pre-commit-config.yaml status and git status
// TODO: fill env vars
Expand Down Expand Up @@ -123,6 +133,18 @@ pub(crate) async fn run(
Ok(ExitStatus::Success)
}

async fn config_not_staged(config: &Path) -> Result<bool> {
let output = Command::new(GIT.as_ref()?)
.arg("diff")
.arg("--quiet") // Implies --exit-code
.arg("--no-ext-diff")
.arg(config)
.status()
.await?;

Ok(!output.success())
}

fn get_skips() -> Vec<String> {
match std::env::var_os("SKIP") {
Some(s) if !s.is_empty() => s
Expand Down
21 changes: 16 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use anyhow::Result;
use serde::{Deserialize, Deserializer, Serialize};
use url::Url;

use crate::fs::Simplified;

pub const CONFIG_FILE: &str = ".pre-commit-config.yaml";
pub const MANIFEST_FILE: &str = ".pre-commit-hooks.yaml";

Expand Down Expand Up @@ -505,6 +507,9 @@ pub struct ManifestWire {

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Config file not found: {0}")]
NotFound(String),

#[error(transparent)]
Io(#[from] std::io::Error),

Expand All @@ -517,18 +522,24 @@ pub enum Error {

/// Read the configuration file from the given path.
pub fn read_config(path: &Path) -> Result<ConfigWire, Error> {
let content = fs_err::read_to_string(path)?;
let config =
serde_yaml::from_str(&content).map_err(|e| Error::Yaml(path.display().to_string(), e))?;
let content = match fs_err::read_to_string(path) {
Ok(content) => content,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Err(Error::NotFound(path.user_display().to_string()));
}
Err(e) => return Err(e.into()),
};
let config = serde_yaml::from_str(&content)
.map_err(|e| Error::Yaml(path.user_display().to_string(), e))?;
Ok(config)
}

// TODO: check id duplication?
/// Read the manifest file from the given path.
pub fn read_manifest(path: &Path) -> Result<ManifestWire, Error> {
let content = fs_err::read_to_string(path)?;
let manifest =
serde_yaml::from_str(&content).map_err(|e| Error::Yaml(path.display().to_string(), e))?;
let manifest = serde_yaml::from_str(&content)
.map_err(|e| Error::Yaml(path.user_display().to_string(), e))?;
Ok(manifest)
}

Expand Down
43 changes: 43 additions & 0 deletions src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,46 @@ pub fn relative_to(

Ok(up.join(stripped))
}

pub trait Simplified {
/// Simplify a [`Path`].
///
/// On Windows, this will strip the `\\?\` prefix from paths. On other platforms, it's a no-op.
fn simplified(&self) -> &Path;

/// Render a [`Path`] for display.
///
/// On Windows, this will strip the `\\?\` prefix from paths. On other platforms, it's
/// equivalent to [`std::path::Display`].
fn simplified_display(&self) -> impl Display;

/// Render a [`Path`] for user-facing display.
///
/// Like [`simplified_display`], but relativizes the path against the current working directory.
fn user_display(&self) -> impl Display;
}

impl<T: AsRef<Path>> Simplified for T {
fn simplified(&self) -> &Path {
dunce::simplified(self.as_ref())
}

fn simplified_display(&self) -> impl Display {
dunce::simplified(self.as_ref()).display()
}

fn user_display(&self) -> impl Display {
let path = dunce::simplified(self.as_ref());

// If current working directory is root, display the path as-is.
if CWD.ancestors().nth(1).is_none() {
return path.display();
}

// Attempt to strip the current working directory, then the canonicalized current working
// directory, in case they differ.
let path = path.strip_prefix(CWD.simplified()).unwrap_or(path);

path.display()
}
}
2 changes: 1 addition & 1 deletion src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub enum Error {
GitNotFound(#[from] which::Error),
}

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

static GIT_ENV: LazyLock<Vec<(String, String)>> = LazyLock::new(|| {
let keep = &[
Expand Down
25 changes: 14 additions & 11 deletions src/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::config::{
self, read_config, read_manifest, ConfigLocalHook, ConfigRemoteHook, ConfigRepo, ConfigWire,
ManifestHook, Stage, CONFIG_FILE, MANIFEST_FILE,
};
use crate::fs::CWD;
use crate::fs::{Simplified, CWD};
use crate::languages::{Language, DEFAULT_VERSION};
use crate::printer::Printer;
use crate::store::Store;
Expand All @@ -26,7 +26,7 @@ pub enum Error {
#[error("Failed to parse URL: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error(transparent)]
ReadConfig(#[from] config::Error),
Config(#[from] config::Error),
#[error("Hook {hook} in not present in repository {repo}")]
HookNotFound { hook: String, repo: String },
#[error(transparent)]
Expand Down Expand Up @@ -106,33 +106,36 @@ impl Display for Repo {
}

pub struct Project {
root: PathBuf,
config_path: PathBuf,
config: ConfigWire,
repos: Vec<Rc<Repo>>,
}

impl Project {
pub fn find_config_file(config: Option<PathBuf>) -> Result<PathBuf, Error> {
let file = config.unwrap_or_else(|| CWD.join(CONFIG_FILE));
if file.try_exists()? {
return Ok(file);
}
let file = file.user_display().to_string();
Err(Error::Config(config::Error::NotFound(file)))
}

/// Load a project configuration from a directory.
pub fn from_directory(root: PathBuf, config: Option<PathBuf>) -> Result<Self, Error> {
let config_path = config.unwrap_or_else(|| root.join(CONFIG_FILE));
pub fn new(config_path: PathBuf) -> Result<Self, Error> {
debug!(
"Loading project configuration from {}",
config_path.display()
);
let config = read_config(&config_path)?;
let size = config.repos.len();
Ok(Self {
root,
config,
config_path,
repos: Vec::with_capacity(size),
})
}

/// Load project configuration from the current directory.
pub fn current(config: Option<PathBuf>) -> Result<Self, Error> {
Self::from_directory(CWD.clone(), config)
}

pub fn config(&self) -> &ConfigWire {
&self.config
}
Expand Down

0 comments on commit 3c1933b

Please sign in to comment.