Skip to content

Commit

Permalink
Add pre-commit self udpate (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
j178 authored Nov 16, 2024
1 parent 20c6677 commit d70df55
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fs-err = "2.11.0"
fs2 = "0.4.3"
futures = "0.3.31"
home = "0.5.9"
http = "1.1.0"
indicatif = "0.17.8"
indoc = "2.0.5"
itertools = "0.13.0"
Expand Down
30 changes: 30 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ mod hook_impl;
mod install;
mod run;
mod sample_config;
mod self_update;
mod validate;

pub(crate) use clean::clean;
pub(crate) use hook_impl::hook_impl;
pub(crate) use install::{install, uninstall};
pub(crate) use run::run;
pub(crate) use sample_config::sample_config;
pub(crate) use self_update::self_update;
pub(crate) use validate::{validate_configs, validate_manifest};

#[derive(Copy, Clone)]
Expand Down Expand Up @@ -178,6 +180,10 @@ pub(crate) enum Command {
#[command(hide = true)]
HookImpl(HookImplArgs),

/// `pre-commit-rs` self management.
#[command(name = "self")]
Self_(SelfNamespace),

/// Generate shell completion scripts.
#[command(hide = true)]
GenerateShellCompletion(GenerateShellCompletionArgs),
Expand Down Expand Up @@ -303,6 +309,30 @@ pub(crate) struct HookImplArgs {
pub(crate) args: Vec<OsString>,
}

#[derive(Debug, Args)]
pub struct SelfNamespace {
#[command(subcommand)]
pub command: SelfCommand,
}

#[derive(Debug, Subcommand)]
pub enum SelfCommand {
/// Update pre-commit-rs.
Update(SelfUpdateArgs),
}

#[derive(Debug, Args)]
pub struct SelfUpdateArgs {
/// Update to the specified version.
/// If not provided, pre-commit-rs will update to the latest version.
pub target_version: Option<String>,

/// A GitHub token for authentication.
/// A token is not required but can be used to reduce the chance of encountering rate limits.
#[arg(long, env = "GITHUB_TOKEN")]
pub token: Option<String>,
}

#[derive(Debug, Args)]
pub(crate) struct GenerateShellCompletionArgs {
/// The shell to generate the completion script for
Expand Down
175 changes: 175 additions & 0 deletions src/cli/self_update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// MIT License
//
// Copyright (c) 2023 Astral Software Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use std::fmt::Write;

use anyhow::Result;
use axoupdater::{AxoUpdater, AxoupdateError, UpdateRequest};
use owo_colors::OwoColorize;
use tracing::debug;

use crate::cli::ExitStatus;
use crate::printer::Printer;

/// Attempt to update the pre-commit-rs binary.
pub(crate) async fn self_update(
version: Option<String>,
token: Option<String>,
printer: Printer,
) -> Result<ExitStatus> {
let mut updater = AxoUpdater::new_for("pre-commit-rs");
updater.disable_installer_output();

if let Some(ref token) = token {
updater.set_github_token(token);
}

// Load the "install receipt" for the current binary. If the receipt is not found, then
// pre-commit-rs was likely installed via a package manager.
let Ok(updater) = updater.load_receipt() else {
debug!("no receipt found; assuming pre-commit-rs was installed via a package manager");
writeln!(
printer.stderr(),
"{}",
format_args!(
concat!(
"{}{} Self-update is only available for pre-commit-rs binaries installed via the standalone installation scripts.",
"\n",
"\n",
"If you installed pre-commit-rs with pip, brew, or another package manager, update pre-commit-rs with `pip install --upgrade`, `brew upgrade`, or similar."
),
"warning".yellow().bold(),
":".bold()
)
)?;
return Ok(ExitStatus::Error);
};

// Ensure the receipt is for the current binary. If it's not, then the user likely has multiple
// pre-commit-rs binaries installed, and the current binary was _not_ installed via the standalone
// installation scripts.
if !updater.check_receipt_is_for_this_executable()? {
debug!(
"receipt is not for this executable; assuming pre-commit-rs was installed via a package manager"
);
writeln!(
printer.stderr(),
"{}",
format_args!(
concat!(
"{}{} Self-update is only available for pre-commit-rs binaries installed via the standalone installation scripts.",
"\n",
"\n",
"If you installed pre-commit-rs with pip, brew, or another package manager, update pre-commit-rs with `pip install --upgrade`, `brew upgrade`, or similar."
),
"warning".yellow().bold(),
":".bold()
)
)?;
return Ok(ExitStatus::Error);
}

writeln!(
printer.stderr(),
"{}",
format_args!(
"{}{} Checking for updates...",
"info".cyan().bold(),
":".bold()
)
)?;

let update_request = if let Some(version) = version {
UpdateRequest::SpecificTag(version)
} else {
UpdateRequest::Latest
};

updater.configure_version_specifier(update_request);

// Run the updater. This involves a network request, since we need to determine the latest
// available version of pre-commit-rs.
match updater.run().await {
Ok(Some(result)) => {
let version_information = if let Some(old_version) = result.old_version {
format!(
"from {} to {}",
format!("v{old_version}").bold().white(),
format!("v{}", result.new_version).bold().white(),
)
} else {
format!("to {}", format!("v{}", result.new_version).bold().white())
};

writeln!(
printer.stderr(),
"{}",
format_args!(
"{}{} Upgraded pre-commit-rs {}! {}",
"success".green().bold(),
":".bold(),
version_information,
format!(
"https://github.com/j178/pre-commit-rs/releases/tag/{}",
result.new_version_tag
)
.cyan()
)
)?;
}
Ok(None) => {
writeln!(
printer.stderr(),
"{}",
format_args!(
"{}{} You're on the latest version of pre-commit-rs ({})",
"success".green().bold(),
":".bold(),
format!("v{}", env!("CARGO_PKG_VERSION")).bold().white()
)
)?;
}
Err(err) => {
return if let AxoupdateError::Reqwest(err) = err {
if err.status() == Some(http::StatusCode::FORBIDDEN) && token.is_none() {
writeln!(
printer.stderr(),
"{}",
format_args!(
"{}{} GitHub API rate limit exceeded. Please provide a GitHub token via the {} option.",
"error".red().bold(),
":".bold(),
"`--token`".green().bold()
)
)?;
Ok(ExitStatus::Error)
} else {
Err(err.into())
}
} else {
Err(err.into())
};
}
}

Ok(ExitStatus::Success)
}
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tracing::{debug, error};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::EnvFilter;

use crate::cli::{Cli, Command, ExitStatus};
use crate::cli::{Cli, Command, ExitStatus, SelfCommand, SelfNamespace, SelfUpdateArgs};
use crate::git::get_root;
use crate::printer::Printer;

Expand Down Expand Up @@ -218,6 +218,13 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
Ok(cli::validate_manifest(args.manifests))
}
Command::SampleConfig => Ok(cli::sample_config()),
Command::Self_(SelfNamespace {
command:
SelfCommand::Update(SelfUpdateArgs {
target_version,
token,
}),
}) => cli::self_update(target_version, token, printer).await,
Command::GenerateShellCompletion(args) => {
show_settings!(args);

Expand Down

0 comments on commit d70df55

Please sign in to comment.