Merged origin/master into Update modal styles

This commit is contained in:
Pavel Laptev 2023-12-12 17:32:17 +01:00 committed by GitButler
commit cdccb59e33
11 changed files with 233 additions and 0 deletions

22
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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"),
}
}
}

View File

@ -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),
}

View File

@ -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> {

View File

@ -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;

View File

@ -0,0 +1 @@
pub mod default_true;

View 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);
}
}

View File

@ -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

View File

@ -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(())
}

View File

@ -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")?