mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-28 20:15:20 +03:00
Merged origin/master into Update modal styles
This commit is contained in:
commit
cdccb59e33
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -1763,6 +1763,18 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2-hooks"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76beea92a16bbdf05842f0c6d9611bbb7eef764b34dde223359077463b800"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"log",
|
||||
"shellexpand",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitbutler"
|
||||
version = "0.0.0"
|
||||
@ -1778,6 +1790,7 @@ dependencies = [
|
||||
"filetime",
|
||||
"futures",
|
||||
"git2",
|
||||
"git2-hooks",
|
||||
"governor",
|
||||
"itertools 0.12.0",
|
||||
"lazy_static",
|
||||
@ -4596,6 +4609,15 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
|
@ -32,6 +32,7 @@ diffy = "0.3.0"
|
||||
filetime = "0.2.22"
|
||||
futures = "0.3"
|
||||
git2 = { version = "0.18.1", features = ["vendored-openssl", "vendored-libgit2"] }
|
||||
git2-hooks = "0.3"
|
||||
governor = "0.6.0"
|
||||
itertools = "0.12"
|
||||
lazy_static = "1.4.0"
|
||||
|
@ -13,6 +13,7 @@ pub enum Code {
|
||||
ProjectConflict,
|
||||
ProjectHead,
|
||||
Menu,
|
||||
Hook,
|
||||
}
|
||||
|
||||
impl fmt::Display for Code {
|
||||
@ -27,6 +28,7 @@ impl fmt::Display for Code {
|
||||
Code::ProjectGitRemote => write!(f, "errors.projects.git.remote"),
|
||||
Code::ProjectHead => write!(f, "errors.projects.head"),
|
||||
Code::ProjectConflict => write!(f, "errors.projects.conflict"),
|
||||
Code::Hook => write!(f, "errors.hook"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ pub enum Error {
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("network error: {0}")]
|
||||
Network(git2::Error),
|
||||
#[error("hook error: {0}")]
|
||||
Hooks(#[from] git2_hooks::HooksError),
|
||||
#[error(transparent)]
|
||||
Other(git2::Error),
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::{path, str};
|
||||
|
||||
use git2::Submodule;
|
||||
use git2_hooks::HookResult;
|
||||
|
||||
use crate::keys;
|
||||
|
||||
@ -424,6 +425,11 @@ impl Repository {
|
||||
.map(|iter| iter.map(|reference| reference.map(Into::into).map_err(Into::into)))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn run_hook_pre_commit(&self) -> Result<HookResult> {
|
||||
let res = git2_hooks::hooks_pre_commit(&self.0, Some(&["../.husky"]))?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CheckoutTreeBuidler<'a> {
|
||||
|
@ -24,6 +24,7 @@ pub mod sentry;
|
||||
pub mod sessions;
|
||||
pub mod ssh;
|
||||
pub mod storage;
|
||||
pub mod types;
|
||||
pub mod users;
|
||||
pub mod virtual_branches;
|
||||
pub mod watcher;
|
||||
|
1
packages/tauri/src/types.rs
Normal file
1
packages/tauri/src/types.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod default_true;
|
113
packages/tauri/src/types/default_true.rs
Normal file
113
packages/tauri/src/types/default_true.rs
Normal file
@ -0,0 +1,113 @@
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DefaultTrue(bool);
|
||||
|
||||
impl core::fmt::Debug for DefaultTrue {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
<bool as core::fmt::Debug>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for DefaultTrue {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
<bool as core::fmt::Display>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefaultTrue {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
DefaultTrue(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DefaultTrue> for bool {
|
||||
#[inline]
|
||||
fn from(default_true: DefaultTrue) -> Self {
|
||||
default_true.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for DefaultTrue {
|
||||
#[inline]
|
||||
fn from(boolean: bool) -> Self {
|
||||
DefaultTrue(boolean)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for DefaultTrue {
|
||||
#[inline]
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_bool(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for DefaultTrue {
|
||||
#[inline]
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Ok(DefaultTrue(bool::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for DefaultTrue {
|
||||
type Target = bool;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::DerefMut for DefaultTrue {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<bool> for DefaultTrue {
|
||||
#[inline]
|
||||
fn eq(&self, other: &bool) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<DefaultTrue> for bool {
|
||||
#[inline]
|
||||
fn eq(&self, other: &DefaultTrue) -> bool {
|
||||
*self == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Not for DefaultTrue {
|
||||
type Output = bool;
|
||||
|
||||
#[inline]
|
||||
fn not(self) -> Self::Output {
|
||||
!self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::DefaultTrue;
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
fn test_default_true() {
|
||||
let default_true = DefaultTrue::default();
|
||||
assert!(default_true);
|
||||
assert_eq!(default_true, true);
|
||||
assert_eq!(!default_true, false);
|
||||
assert!(!!default_true);
|
||||
|
||||
if !(*default_true) {
|
||||
unreachable!("default_true is false")
|
||||
}
|
||||
|
||||
let mut default_true = DefaultTrue::default();
|
||||
*default_true = false;
|
||||
assert!(!default_true);
|
||||
}
|
||||
}
|
@ -144,6 +144,8 @@ pub enum CommitError {
|
||||
DefaultTargetNotSet(DefaultTargetNotSetError),
|
||||
#[error("will not commit conflicted files")]
|
||||
Conflicted(ProjectConflictError),
|
||||
#[error("commit hook rejected")]
|
||||
CommitHookRejected(String),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
@ -410,6 +412,10 @@ impl From<CommitError> for Error {
|
||||
CommitError::BranchNotFound(error) => error.into(),
|
||||
CommitError::DefaultTargetNotSet(error) => error.into(),
|
||||
CommitError::Conflicted(error) => error.into(),
|
||||
CommitError::CommitHookRejected(error) => Error::UserError {
|
||||
code: crate::error::Code::Hook,
|
||||
message: error,
|
||||
},
|
||||
CommitError::Other(error) => {
|
||||
tracing::error!(?error, "commit error");
|
||||
Error::Unknown
|
||||
|
@ -14,6 +14,7 @@ use pretty_assertions::{assert_eq, assert_ne};
|
||||
use crate::{
|
||||
gb_repository, git, project_repository, reader, sessions,
|
||||
test_utils::{self, empty_bare_repository, Case, Suite},
|
||||
virtual_branches::errors::CommitError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -2740,3 +2741,71 @@ fn test_verify_branch_not_integration() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_commit_hook_rejection() -> Result<()> {
|
||||
let suite = Suite::default();
|
||||
let Case {
|
||||
project,
|
||||
gb_repository,
|
||||
project_repository,
|
||||
..
|
||||
} = suite.new_case_with_files(HashMap::from([
|
||||
(
|
||||
path::PathBuf::from("test.txt"),
|
||||
"line1\nline2\nline3\nline4\n",
|
||||
),
|
||||
(
|
||||
path::PathBuf::from("test2.txt"),
|
||||
"line5\nline6\nline7\nline8\n",
|
||||
),
|
||||
]));
|
||||
|
||||
set_test_target(&gb_repository, &project_repository)?;
|
||||
|
||||
let branch1_id = create_virtual_branch(
|
||||
&gb_repository,
|
||||
&project_repository,
|
||||
&BranchCreateRequest::default(),
|
||||
)
|
||||
.expect("failed to create virtual branch")
|
||||
.id;
|
||||
|
||||
std::fs::write(
|
||||
std::path::Path::new(&project.path).join("test.txt"),
|
||||
"line0\nline1\nline2\nline3\nline4\n",
|
||||
)?;
|
||||
|
||||
let hook = b"#!/bin/sh
|
||||
echo 'rejected'
|
||||
exit 1
|
||||
";
|
||||
|
||||
git2_hooks::create_hook(
|
||||
(&project_repository.git_repository).into(),
|
||||
git2_hooks::HOOK_PRE_COMMIT,
|
||||
hook,
|
||||
);
|
||||
|
||||
let res = commit(
|
||||
&gb_repository,
|
||||
&project_repository,
|
||||
&branch1_id,
|
||||
"test commit",
|
||||
None,
|
||||
Some(suite.keys.get_or_create()?).as_ref(),
|
||||
None,
|
||||
);
|
||||
|
||||
let error = res.unwrap_err();
|
||||
|
||||
assert!(matches!(error, CommitError::CommitHookRejected(_)));
|
||||
|
||||
let CommitError::CommitHookRejected(output) = error else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
assert_eq!(&output, "rejected\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use std::{
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use diffy::{apply_bytes, Patch};
|
||||
use git2_hooks::HookResult;
|
||||
use serde::Serialize;
|
||||
use slug::slugify;
|
||||
|
||||
@ -2009,6 +2010,15 @@ pub fn commit(
|
||||
signing_key: Option<&keys::PrivateKey>,
|
||||
user: Option<&users::User>,
|
||||
) -> Result<git::Oid, errors::CommitError> {
|
||||
let hook_result = project_repository
|
||||
.git_repository
|
||||
.run_hook_pre_commit()
|
||||
.context("failed to run hook")?;
|
||||
|
||||
if let HookResult::RunNotSuccessful { stdout, .. } = hook_result {
|
||||
return Err(errors::CommitError::CommitHookRejected(stdout));
|
||||
}
|
||||
|
||||
let default_target = gb_repository
|
||||
.default_target()
|
||||
.context("failed to get default target")?
|
||||
|
Loading…
Reference in New Issue
Block a user