mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-01 12:26:02 +03:00
Prune Code
to only what's used by the UI
Also adjust the `Code` documentation to clarify this - otherwise we will have more and more variants and nobody actually cares. The frontend code is adjusted as well, as its `Code` counterpart contained unsused variants which are now removed.
This commit is contained in:
parent
d689f36e7f
commit
20d84247e9
@ -5,11 +5,7 @@ import type { EventCallback, EventName } from '@tauri-apps/api/event';
|
||||
export enum Code {
|
||||
Unknown = 'errors.unknown',
|
||||
Validation = 'errors.validation',
|
||||
Projects = 'errors.projects',
|
||||
ProjectsGitAuth = 'errors.projects.git.auth',
|
||||
ProjectsGitRemote = 'errors.projects.git.remote',
|
||||
ProjectHead = 'errors.projects.head',
|
||||
ProjectConflict = 'errors.projects.conflict'
|
||||
ProjectsGitAuth = 'errors.projects.git.auth'
|
||||
}
|
||||
|
||||
export class UserError extends Error {
|
||||
|
@ -123,49 +123,44 @@ use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
|
||||
/// A unique code that consumers of the API may rely on to identify errors.
|
||||
///
|
||||
/// ### Important
|
||||
///
|
||||
/// **Only add variants if a consumer, like the *frontend*, is actually using them**.
|
||||
/// Remove variants when no longer in use.
|
||||
///
|
||||
/// In practice, it should match its [frontend counterpart](https://github.com/gitbutlerapp/gitbutler/blob/fa973fd8f1ae8807621f47601803d98b8a9cf348/app/src/lib/backend/ipc.ts#L5).
|
||||
#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq)]
|
||||
pub enum Code {
|
||||
/// Much like a catch-all error code. It shouldn't be attached explicitly unless
|
||||
/// a message is provided as well as part of a [`Context`].
|
||||
#[default]
|
||||
Unknown,
|
||||
Validation,
|
||||
Projects,
|
||||
Branches,
|
||||
ProjectGitAuth,
|
||||
ProjectGitRemote,
|
||||
/// The push operation failed, specifically because the remote rejected it.
|
||||
ProjectGitPush,
|
||||
// TODO(ST): try to remove this and replace it with downcasting or thiserror pattern matching
|
||||
ProjectConflict,
|
||||
ProjectHead,
|
||||
Menu,
|
||||
PreCommitHook,
|
||||
CommitMsgHook,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Code {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let code = match self {
|
||||
Code::Menu => "errors.menu",
|
||||
Code::Unknown => "errors.unknown",
|
||||
Code::Validation => "errors.validation",
|
||||
Code::Projects => "errors.projects",
|
||||
Code::Branches => "errors.branches",
|
||||
Code::ProjectGitAuth => "errors.projects.git.auth",
|
||||
Code::ProjectGitRemote => "errors.projects.git.remote",
|
||||
Code::ProjectGitPush => "errors.projects.git.push",
|
||||
Code::ProjectHead => "errors.projects.head",
|
||||
Code::ProjectConflict => "errors.projects.conflict",
|
||||
//TODO: rename js side to be more precise what kind of hook error this is
|
||||
Code::PreCommitHook => "errors.hook",
|
||||
Code::CommitMsgHook => "errors.hooks.commit.msg",
|
||||
};
|
||||
f.write_str(code)
|
||||
}
|
||||
}
|
||||
|
||||
/// A context to wrap around lower errors to allow its classification, along with a message for the user.
|
||||
/// A context for classifying errors.
|
||||
///
|
||||
/// It provides a [`Code`], which may be [unknown](Code::Unknown), and a `message` which explains
|
||||
/// more about the problem at hand.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Context {
|
||||
/// The identifier of the error.
|
||||
/// The classification of the error.
|
||||
pub code: Code,
|
||||
/// A description of what went wrong, if available.
|
||||
pub message: Option<Cow<'static, str>>,
|
||||
@ -188,9 +183,9 @@ impl From<Code> for Context {
|
||||
|
||||
impl Context {
|
||||
/// Create a new instance with `code` and an owned `message`.
|
||||
pub fn new(code: Code, message: impl Into<String>) -> Self {
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Context {
|
||||
code,
|
||||
code: Code::Unknown,
|
||||
message: Some(Cow::Owned(message.into())),
|
||||
}
|
||||
}
|
||||
@ -202,6 +197,12 @@ impl Context {
|
||||
message: Some(Cow::Borrowed(message)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust the `code` of this instance to the given one.
|
||||
pub fn with_code(mut self, code: Code) -> Self {
|
||||
self.code = code;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::{AnyhowContextExt, Code, Context, ErrorWithContext};
|
||||
use crate::{error, keys, project_repository, projects, users};
|
||||
use crate::{keys, project_repository, projects, users};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SshCredential {
|
||||
@ -87,19 +86,6 @@ pub enum HelpError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for HelpError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
HelpError::NoUrlSet => {
|
||||
error::Context::new_static(Code::ProjectGitRemote, "no url set for remote")
|
||||
}
|
||||
HelpError::UrlConvertError(_) => Code::ProjectGitRemote.into(),
|
||||
HelpError::Git(_) => return None,
|
||||
HelpError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Helper {
|
||||
pub fn new(
|
||||
keys: keys::Controller,
|
||||
|
@ -3,6 +3,6 @@ pub mod conflicts;
|
||||
mod repository;
|
||||
|
||||
pub use config::Config;
|
||||
pub use repository::{LogUntil, OpenError, RemoteError, Repository};
|
||||
pub use repository::{LogUntil, RemoteError, Repository};
|
||||
|
||||
pub mod signatures;
|
||||
|
@ -4,7 +4,7 @@ use std::{
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
||||
use super::conflicts;
|
||||
use crate::{
|
||||
@ -24,82 +24,63 @@ pub struct Repository {
|
||||
project: projects::Project,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum OpenError {
|
||||
#[error("repository not found at {0}")]
|
||||
NotFound(path::PathBuf),
|
||||
#[error(transparent)]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for OpenError {
|
||||
fn context(&self) -> Option<crate::error::Context> {
|
||||
match self {
|
||||
OpenError::NotFound(path) => {
|
||||
error::Context::new(Code::Projects, format!("{} not found", path.display())).into()
|
||||
}
|
||||
OpenError::Other(error) => error.custom_context_or_root_cause().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Repository {
|
||||
pub fn open(project: &projects::Project) -> Result<Self, OpenError> {
|
||||
git::Repository::open(&project.path)
|
||||
.map_err(|error| match error {
|
||||
git::Error::NotFound(_) => OpenError::NotFound(project.path.clone()),
|
||||
other => OpenError::Other(other.into()),
|
||||
})
|
||||
.map(|git_repository| {
|
||||
// XXX(qix-): This is a temporary measure to disable GC on the project repository.
|
||||
// XXX(qix-): We do this because the internal repository we use to store the "virtual"
|
||||
// XXX(qix-): refs and information use Git's alternative-objects mechanism to refer
|
||||
// XXX(qix-): to the project repository's objects. However, the project repository
|
||||
// XXX(qix-): has no knowledge of these refs, and will GC them away (usually after
|
||||
// XXX(qix-): about 2 weeks) which will corrupt the internal repository.
|
||||
// XXX(qix-):
|
||||
// XXX(qix-): We will ultimately move away from an internal repository for a variety
|
||||
// XXX(qix-): of reasons, but for now, this is a simple, short-term solution that we
|
||||
// XXX(qix-): can clean up later on. We're aware this isn't ideal.
|
||||
if let Ok(config) = git_repository.config().as_mut() {
|
||||
let should_set = match config.get_bool("gitbutler.didSetPrune") {
|
||||
Ok(None | Some(false)) => true,
|
||||
Ok(Some(true)) => false,
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
pub fn open(project: &projects::Project) -> Result<Self> {
|
||||
let repo = git::Repository::open(&project.path).or_else(|err| match err {
|
||||
git::Error::NotFound(_) => Err(anyhow::Error::from(err)).context(format!(
|
||||
"repository not found at \"{}\"",
|
||||
project.path.display()
|
||||
)),
|
||||
other => Err(other.into()),
|
||||
})?;
|
||||
|
||||
// XXX(qix-): This is a temporary measure to disable GC on the project repository.
|
||||
// XXX(qix-): We do this because the internal repository we use to store the "virtual"
|
||||
// XXX(qix-): refs and information use Git's alternative-objects mechanism to refer
|
||||
// XXX(qix-): to the project repository's objects. However, the project repository
|
||||
// XXX(qix-): has no knowledge of these refs, and will GC them away (usually after
|
||||
// XXX(qix-): about 2 weeks) which will corrupt the internal repository.
|
||||
// XXX(qix-):
|
||||
// XXX(qix-): We will ultimately move away from an internal repository for a variety
|
||||
// XXX(qix-): of reasons, but for now, this is a simple, short-term solution that we
|
||||
// XXX(qix-): can clean up later on. We're aware this isn't ideal.
|
||||
if let Ok(config) = repo.config().as_mut() {
|
||||
let should_set = match config.get_bool("gitbutler.didSetPrune") {
|
||||
Ok(None | Some(false)) => true,
|
||||
Ok(Some(true)) => false,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"failed to get gitbutler.didSetPrune for repository at {}; cannot disable gc: {}",
|
||||
project.path.display(),
|
||||
error
|
||||
err
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if should_set {
|
||||
if let Err(error) = config
|
||||
.set_str("gc.pruneExpire", "never")
|
||||
.and_then(|()| config.set_bool("gitbutler.didSetPrune", true))
|
||||
{
|
||||
tracing::warn!(
|
||||
if should_set {
|
||||
if let Err(error) = config
|
||||
.set_str("gc.pruneExpire", "never")
|
||||
.and_then(|()| config.set_bool("gitbutler.didSetPrune", true))
|
||||
{
|
||||
tracing::warn!(
|
||||
"failed to set gc.auto to false for repository at {}; cannot disable gc: {}",
|
||||
project.path.display(),
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"failed to get config for repository at {}; cannot disable gc",
|
||||
project.path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"failed to get config for repository at {}; cannot disable gc",
|
||||
project.path.display()
|
||||
);
|
||||
}
|
||||
|
||||
git_repository
|
||||
})
|
||||
.map(|git_repository| Self {
|
||||
git_repository,
|
||||
project: project.clone(),
|
||||
})
|
||||
Ok(Self {
|
||||
git_repository: repo,
|
||||
project: project.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_resolving(&self) -> bool {
|
||||
@ -517,9 +498,7 @@ impl Repository {
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(err) = update_refs_error.as_ref() {
|
||||
return Err(RemoteError::Other(
|
||||
anyhow::anyhow!(err.to_string()).context(Code::ProjectGitPush),
|
||||
));
|
||||
return Err(RemoteError::Other(anyhow!(err.to_string())));
|
||||
}
|
||||
return Err(RemoteError::Other(err.into()));
|
||||
}
|
||||
@ -630,17 +609,13 @@ pub enum RemoteError {
|
||||
impl ErrorWithContext for RemoteError {
|
||||
fn context(&self) -> Option<error::Context> {
|
||||
Some(match self {
|
||||
RemoteError::Help(error) => return error.context(),
|
||||
RemoteError::Network => {
|
||||
error::Context::new_static(Code::ProjectGitRemote, "Network error occurred")
|
||||
RemoteError::Help(_) | RemoteError::Network | RemoteError::Git(_) => {
|
||||
error::Context::default()
|
||||
}
|
||||
RemoteError::Auth => error::Context::new_static(
|
||||
Code::ProjectGitAuth,
|
||||
"Project remote authentication error",
|
||||
),
|
||||
RemoteError::Git(_) => {
|
||||
error::Context::new_static(Code::ProjectGitRemote, "Git command failed")
|
||||
}
|
||||
RemoteError::Other(error) => {
|
||||
return error.custom_context_or_root_cause().into();
|
||||
}
|
||||
|
@ -7,11 +7,8 @@ use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::{storage, storage::UpdateRequest, Project, ProjectId};
|
||||
use crate::{error, project_repository};
|
||||
use crate::{
|
||||
error::{AnyhowContextExt, Code, Error, ErrorWithContext},
|
||||
projects::AuthKey,
|
||||
};
|
||||
use crate::project_repository;
|
||||
use crate::{error::Error, projects::AuthKey};
|
||||
|
||||
#[async_trait]
|
||||
pub trait Watchers {
|
||||
@ -160,40 +157,28 @@ impl Controller {
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
pub fn get(&self, id: ProjectId) -> Result<Project, GetError> {
|
||||
let project = self.projects_storage.get(id).map_err(|error| match error {
|
||||
super::storage::Error::NotFound => GetError::NotFound,
|
||||
error => GetError::Other(error.into()),
|
||||
});
|
||||
if let Ok(project) = &project {
|
||||
if !project.gb_dir().exists() {
|
||||
if let Err(error) = std::fs::create_dir_all(project.gb_dir()) {
|
||||
tracing::error!(project_id = %project.id, ?error, "failed to create {:?} on project get", project.gb_dir());
|
||||
}
|
||||
pub fn get(&self, id: ProjectId) -> Result<Project> {
|
||||
let project = self.projects_storage.get(id)?;
|
||||
if !project.gb_dir().exists() {
|
||||
if let Err(error) = std::fs::create_dir_all(project.gb_dir()) {
|
||||
tracing::error!(project_id = %project.id, ?error, "failed to create \"{}\" on project get", project.gb_dir().display());
|
||||
}
|
||||
// Clean up old virtual_branches.toml that was never used
|
||||
if project
|
||||
.path
|
||||
.join(".git")
|
||||
.join("virtual_branches.toml")
|
||||
.exists()
|
||||
{
|
||||
if let Err(error) =
|
||||
std::fs::remove_file(project.path.join(".git").join("virtual_branches.toml"))
|
||||
{
|
||||
tracing::error!(project_id = %project.id, ?error, "failed to remove old virtual_branches.toml");
|
||||
}
|
||||
}
|
||||
// Clean up old virtual_branches.toml that was never used
|
||||
let old_virtual_branches_path = project.path.join(".git").join("virtual_branches.toml");
|
||||
if old_virtual_branches_path.exists() {
|
||||
if let Err(error) = std::fs::remove_file(old_virtual_branches_path) {
|
||||
tracing::error!(project_id = %project.id, ?error, "failed to remove old virtual_branches.toml");
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(qix-): On windows, we have to force to system executable
|
||||
#[cfg(windows)]
|
||||
let project = project.map(|mut p| {
|
||||
p.preferred_key = AuthKey::SystemExecutable;
|
||||
p
|
||||
});
|
||||
{
|
||||
project.preferred_key = AuthKey::SystemExecutable;
|
||||
}
|
||||
|
||||
project
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
pub fn list(&self) -> Result<Vec<Project>> {
|
||||
@ -246,8 +231,7 @@ impl Controller {
|
||||
error => ConfigError::Other(error.into()),
|
||||
})?;
|
||||
|
||||
let repo = project_repository::Repository::open(&project)
|
||||
.map_err(|e| ConfigError::Other(e.into()))?;
|
||||
let repo = project_repository::Repository::open(&project).map_err(ConfigError::Other)?;
|
||||
repo.config()
|
||||
.get_local(key)
|
||||
.map_err(|e| ConfigError::Other(e.into()))
|
||||
@ -264,8 +248,7 @@ impl Controller {
|
||||
error => ConfigError::Other(error.into()),
|
||||
})?;
|
||||
|
||||
let repo = project_repository::Repository::open(&project)
|
||||
.map_err(|e| ConfigError::Other(e.into()))?;
|
||||
let repo = project_repository::Repository::open(&project).map_err(ConfigError::Other)?;
|
||||
repo.config()
|
||||
.set_local(key, value)
|
||||
.map_err(|e| ConfigError::Other(e.into()))?;
|
||||
@ -290,17 +273,6 @@ pub enum GetError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl error::ErrorWithContext for GetError {
|
||||
fn context(&self) -> Option<error::Context> {
|
||||
match self {
|
||||
GetError::NotFound => {
|
||||
error::Context::new_static(Code::Projects, "Project not found").into()
|
||||
}
|
||||
GetError::Other(error) => error.custom_context_or_root_cause().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UpdateError {
|
||||
#[error("project not found")]
|
||||
@ -311,26 +283,6 @@ pub enum UpdateError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for UpdateError {
|
||||
fn context(&self) -> Option<error::Context> {
|
||||
Some(match self {
|
||||
UpdateError::Validation(UpdateValidationError::KeyNotFound(path)) => {
|
||||
error::Context::new(Code::Projects, format!("'{}' not found", path.display()))
|
||||
}
|
||||
UpdateError::Validation(UpdateValidationError::KeyNotFile(path)) => {
|
||||
error::Context::new(
|
||||
Code::Projects,
|
||||
format!("'{}' is not a file", path.display()),
|
||||
)
|
||||
}
|
||||
UpdateError::NotFound => {
|
||||
error::Context::new_static(Code::Projects, "Project not found")
|
||||
}
|
||||
UpdateError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UpdateValidationError {
|
||||
#[error("{0} not found")]
|
||||
@ -343,47 +295,18 @@ pub enum UpdateValidationError {
|
||||
pub enum AddError {
|
||||
#[error("not a directory")]
|
||||
NotADirectory,
|
||||
#[error("not a git repository")]
|
||||
#[error("must be a Git repository")]
|
||||
NotAGitRepository(#[from] Box<gix::open::Error>),
|
||||
#[error("bare repositories are not supported")]
|
||||
#[error("bare repositories are unsupported")]
|
||||
BareUnsupported,
|
||||
#[error("worktrees unsupported")]
|
||||
#[error("can only work in main worktrees")]
|
||||
WorktreeNotSupported,
|
||||
#[error("path not found")]
|
||||
PathNotFound,
|
||||
#[error("project already exists")]
|
||||
AlreadyExists,
|
||||
#[error("submodules not supported")]
|
||||
#[error("repositories with git submodules are not supported")]
|
||||
SubmodulesNotSupported,
|
||||
#[error(transparent)]
|
||||
OpenProjectRepository(#[from] project_repository::OpenError),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for AddError {
|
||||
fn context(&self) -> Option<error::Context> {
|
||||
Some(match self {
|
||||
AddError::NotAGitRepository(_) => {
|
||||
error::Context::new_static(Code::Projects, "Must be a git directory")
|
||||
}
|
||||
AddError::BareUnsupported => {
|
||||
error::Context::new_static(Code::Projects, "Bare repositories are unsupported")
|
||||
}
|
||||
AddError::AlreadyExists => {
|
||||
error::Context::new_static(Code::Projects, "Project already exists")
|
||||
}
|
||||
AddError::OpenProjectRepository(error) => return error.context(),
|
||||
AddError::NotADirectory => error::Context::new(Code::Projects, "Not a directory"),
|
||||
AddError::WorktreeNotSupported => {
|
||||
error::Context::new(Code::Projects, "Can only work in main worktrees")
|
||||
}
|
||||
AddError::PathNotFound => error::Context::new(Code::Projects, "Path not found"),
|
||||
AddError::SubmodulesNotSupported => error::Context::new_static(
|
||||
Code::Projects,
|
||||
"Repositories with git submodules are not supported",
|
||||
),
|
||||
AddError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +455,7 @@ impl ControllerInner {
|
||||
user,
|
||||
run_hooks,
|
||||
)
|
||||
.map_err(Into::into);
|
||||
.map_err(Error::from_err);
|
||||
let _ = snapshot_tree.and_then(|snapshot_tree| {
|
||||
project_repository.project().snapshot_commit_creation(
|
||||
snapshot_tree,
|
||||
@ -475,10 +475,7 @@ impl ControllerInner {
|
||||
) -> Result<bool, Error> {
|
||||
let project = self.projects.get(project_id)?;
|
||||
let project_repository = project_repository::Repository::open(&project)?;
|
||||
Ok(super::is_remote_branch_mergeable(
|
||||
&project_repository,
|
||||
branch_name,
|
||||
)?)
|
||||
super::is_remote_branch_mergeable(&project_repository, branch_name).map_err(Error::from_err)
|
||||
}
|
||||
|
||||
pub fn can_apply_virtual_branch(
|
||||
@ -523,9 +520,8 @@ impl ControllerInner {
|
||||
let _permit = self.semaphore.acquire().await;
|
||||
|
||||
self.with_verify_branch(project_id, |project_repository, user| {
|
||||
let result =
|
||||
super::create_virtual_branch_from_branch(project_repository, branch, user)?;
|
||||
Ok(result)
|
||||
super::create_virtual_branch_from_branch(project_repository, branch, user)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -543,7 +539,7 @@ impl ControllerInner {
|
||||
let project = self.projects.get(project_id)?;
|
||||
let project_repository = project_repository::Repository::open(&project)?;
|
||||
super::list_remote_commit_files(&project_repository.git_repository, commit_oid)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
}
|
||||
|
||||
pub fn set_base_branch(
|
||||
@ -556,8 +552,7 @@ impl ControllerInner {
|
||||
let _ = project_repository
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::SetBaseBranch));
|
||||
let result = super::set_base_branch(&project_repository, target_branch)?;
|
||||
Ok(result)
|
||||
super::set_base_branch(&project_repository, target_branch).map_err(Error::from_err)
|
||||
}
|
||||
|
||||
pub fn set_target_push_remote(
|
||||
@ -567,8 +562,7 @@ impl ControllerInner {
|
||||
) -> Result<(), Error> {
|
||||
let project = self.projects.get(project_id)?;
|
||||
let project_repository = project_repository::Repository::open(&project)?;
|
||||
super::set_target_push_remote(&project_repository, push_remote)?;
|
||||
Ok(())
|
||||
super::set_target_push_remote(&project_repository, push_remote).map_err(Error::from_err)
|
||||
}
|
||||
|
||||
pub async fn integrate_upstream_commits(
|
||||
@ -633,7 +627,7 @@ impl ControllerInner {
|
||||
self.with_verify_branch(project_id, |project_repository, user| {
|
||||
let snapshot_tree = project_repository.project().prepare_snapshot();
|
||||
let result =
|
||||
super::apply_branch(project_repository, branch_id, user).map_err(Into::into);
|
||||
super::apply_branch(project_repository, branch_id, user).map_err(Error::from_err);
|
||||
|
||||
let _ = snapshot_tree.and_then(|snapshot_tree| {
|
||||
project_repository
|
||||
@ -687,7 +681,8 @@ impl ControllerInner {
|
||||
let _ = project_repository
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::AmendCommit));
|
||||
super::amend(project_repository, branch_id, commit_oid, ownership).map_err(Into::into)
|
||||
super::amend(project_repository, branch_id, commit_oid, ownership)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -712,7 +707,7 @@ impl ControllerInner {
|
||||
to_commit_oid,
|
||||
ownership,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -727,7 +722,7 @@ impl ControllerInner {
|
||||
self.with_verify_branch(project_id, |project_repository, _| {
|
||||
let snapshot_tree = project_repository.project().prepare_snapshot();
|
||||
let result: Result<(), Error> =
|
||||
super::undo_commit(project_repository, branch_id, commit_oid).map_err(Into::into);
|
||||
super::undo_commit(project_repository, branch_id, commit_oid).map_err(Error::from_err);
|
||||
let _ = snapshot_tree.and_then(|snapshot_tree| {
|
||||
project_repository.project().snapshot_commit_undo(
|
||||
snapshot_tree,
|
||||
@ -753,7 +748,7 @@ impl ControllerInner {
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::InsertBlankCommit));
|
||||
super::insert_blank_commit(project_repository, branch_id, commit_oid, user, offset)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -771,7 +766,7 @@ impl ControllerInner {
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::ReorderCommit));
|
||||
super::reorder_commit(project_repository, branch_id, commit_oid, offset)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -788,7 +783,7 @@ impl ControllerInner {
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::UndoCommit));
|
||||
super::reset_branch(project_repository, branch_id, target_commit_oid)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -845,7 +840,7 @@ impl ControllerInner {
|
||||
let _ = project_repository
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::CherryPick));
|
||||
super::cherry_pick(project_repository, branch_id, commit_oid).map_err(Into::into)
|
||||
super::cherry_pick(project_repository, branch_id, commit_oid).map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -880,7 +875,7 @@ impl ControllerInner {
|
||||
let _ = project_repository
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::SquashCommit));
|
||||
super::squash(project_repository, branch_id, commit_oid).map_err(Into::into)
|
||||
super::squash(project_repository, branch_id, commit_oid).map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -897,7 +892,7 @@ impl ControllerInner {
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::UpdateCommitMessage));
|
||||
super::update_commit_message(project_repository, branch_id, commit_oid, message)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -977,7 +972,7 @@ impl ControllerInner {
|
||||
.project()
|
||||
.create_snapshot(SnapshotDetails::new(OperationKind::MoveCommit));
|
||||
super::move_commit(project_repository, target_branch_id, commit_oid, user)
|
||||
.map_err(Into::into)
|
||||
.map_err(Error::from_err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -991,7 +986,7 @@ impl ControllerInner {
|
||||
let project = self.projects.get(project_id)?;
|
||||
let project_repository = project_repository::Repository::open(&project)?;
|
||||
let user = self.users.get_user()?;
|
||||
super::integration::verify_branch(&project_repository)?;
|
||||
super::integration::verify_branch(&project_repository).map_err(Error::from_err)?;
|
||||
action(&project_repository, user.as_ref())
|
||||
}
|
||||
|
||||
@ -1005,7 +1000,7 @@ impl ControllerInner {
|
||||
let project = self.projects.get(project_id)?;
|
||||
let project_repository = project_repository::Repository::open(&project)?;
|
||||
let user = self.users.get_user()?;
|
||||
super::integration::verify_branch(&project_repository)?;
|
||||
super::integration::verify_branch(&project_repository).map_err(Error::from_err)?;
|
||||
Ok(tokio::task::spawn_blocking(move || {
|
||||
action(&project_repository, user.as_ref())
|
||||
}))
|
||||
|
@ -25,49 +25,21 @@ pub enum VirtualBranchError {
|
||||
RebaseFailed,
|
||||
#[error("force push not allowed")]
|
||||
ForcePushNotAllowed(ForcePushNotAllowed),
|
||||
#[error("branch has no commits")]
|
||||
#[error("Branch has no commits - there is nothing to amend to")]
|
||||
BranchHasNoCommits,
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for VirtualBranchError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
VirtualBranchError::Conflict(ctx) => ctx.to_context(),
|
||||
VirtualBranchError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
VirtualBranchError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
VirtualBranchError::TargetOwnerhshipNotFound(_) => {
|
||||
error::Context::new_static(Code::Branches, "target ownership not found")
|
||||
}
|
||||
VirtualBranchError::GitObjectNotFound(oid) => {
|
||||
error::Context::new(Code::Branches, format!("git object {oid} not found"))
|
||||
}
|
||||
VirtualBranchError::CommitFailed => {
|
||||
error::Context::new_static(Code::Branches, "commit failed")
|
||||
}
|
||||
VirtualBranchError::RebaseFailed => {
|
||||
error::Context::new_static(Code::Branches, "rebase failed")
|
||||
}
|
||||
VirtualBranchError::BranchHasNoCommits => error::Context::new_static(
|
||||
Code::Branches,
|
||||
"Branch has no commits - there is nothing to amend to",
|
||||
),
|
||||
VirtualBranchError::ForcePushNotAllowed(ctx) => ctx.to_context(),
|
||||
VirtualBranchError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum VerifyError {
|
||||
#[error("head is detached")]
|
||||
#[error("project in detached head state. Please checkout {} to continue", GITBUTLER_INTEGRATION_REFERENCE.branch())]
|
||||
DetachedHead,
|
||||
#[error("head is {0}")]
|
||||
#[error("project is on {0}. Please checkout {} to continue", GITBUTLER_INTEGRATION_REFERENCE.branch())]
|
||||
InvalidHead(String),
|
||||
#[error("head not found")]
|
||||
#[error("Repo HEAD is unavailable")]
|
||||
HeadNotFound,
|
||||
#[error("integration commit not found")]
|
||||
#[error("GibButler's integration commit not found on head.")]
|
||||
NoIntegrationCommit,
|
||||
#[error(transparent)]
|
||||
GitError(#[from] git::Error),
|
||||
@ -75,39 +47,6 @@ pub enum VerifyError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for VerifyError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
VerifyError::DetachedHead => error::Context::new(
|
||||
Code::ProjectHead,
|
||||
format!(
|
||||
"Project in detached head state. Please checkout {0} to continue.",
|
||||
GITBUTLER_INTEGRATION_REFERENCE.branch()
|
||||
),
|
||||
),
|
||||
VerifyError::InvalidHead(head) => error::Context::new(
|
||||
Code::ProjectHead,
|
||||
format!(
|
||||
"Project is on {}. Please checkout {} to continue.",
|
||||
head,
|
||||
GITBUTLER_INTEGRATION_REFERENCE.branch()
|
||||
),
|
||||
),
|
||||
VerifyError::NoIntegrationCommit => error::Context::new_static(
|
||||
Code::ProjectHead,
|
||||
"GibButler's integration commit not found on head.",
|
||||
),
|
||||
VerifyError::HeadNotFound => {
|
||||
error::Context::new_static(Code::Validation, "Repo HEAD is unavailable")
|
||||
}
|
||||
VerifyError::GitError(error) => {
|
||||
error::Context::new(Code::Validation, error.to_string())
|
||||
}
|
||||
VerifyError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ResetBranchError {
|
||||
#[error("commit {0} not in the branch")]
|
||||
@ -122,27 +61,13 @@ pub enum ResetBranchError {
|
||||
Git(#[from] git::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for ResetBranchError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
ResetBranchError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
ResetBranchError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
ResetBranchError::CommitNotFoundInBranch(oid) => {
|
||||
error::Context::new(Code::Branches, format!("commit {} not found", oid))
|
||||
}
|
||||
ResetBranchError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
ResetBranchError::Git(_err) => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ApplyBranchError {
|
||||
#[error("project")]
|
||||
Conflict(ProjectConflict),
|
||||
#[error("branch not found")]
|
||||
BranchNotFound(BranchNotFound),
|
||||
#[error("branch being applied conflicts with other branch: {0}")]
|
||||
#[error("branch {0} is in a conflicting state")]
|
||||
BranchConflicts(BranchId),
|
||||
#[error("default target not set")]
|
||||
DefaultTargetNotSet(DefaultTargetNotSet),
|
||||
@ -152,22 +77,6 @@ pub enum ApplyBranchError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for ApplyBranchError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
ApplyBranchError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
ApplyBranchError::Conflict(ctx) => ctx.to_context(),
|
||||
ApplyBranchError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
ApplyBranchError::BranchConflicts(id) => error::Context::new(
|
||||
Code::Branches,
|
||||
format!("Branch {} is in a conflicting state", id),
|
||||
),
|
||||
ApplyBranchError::GitError(_) => return None,
|
||||
ApplyBranchError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UnapplyOwnershipError {
|
||||
#[error("default target not set")]
|
||||
@ -252,31 +161,14 @@ pub enum CommitError {
|
||||
DefaultTargetNotSet(DefaultTargetNotSet),
|
||||
#[error("will not commit conflicted files")]
|
||||
Conflicted(ProjectConflict),
|
||||
#[error("commit hook rejected")]
|
||||
#[error("commit hook rejected: {0}")]
|
||||
CommitHookRejected(String),
|
||||
#[error("commit msg hook rejected")]
|
||||
#[error("commit-msg hook rejected: {0}")]
|
||||
CommitMsgHookRejected(String),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for CommitError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
CommitError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
CommitError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
CommitError::Conflicted(ctx) => ctx.to_context(),
|
||||
CommitError::CommitHookRejected(error) => {
|
||||
error::Context::new(Code::PreCommitHook, error)
|
||||
}
|
||||
CommitError::CommitMsgHookRejected(error) => {
|
||||
error::Context::new(Code::CommitMsgHook, error)
|
||||
}
|
||||
CommitError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PushError {
|
||||
#[error("default target not set")]
|
||||
@ -304,26 +196,12 @@ impl ErrorWithContext for PushError {
|
||||
pub enum IsRemoteBranchMergableError {
|
||||
#[error("default target not set")]
|
||||
DefaultTargetNotSet(DefaultTargetNotSet),
|
||||
#[error("branch not found")]
|
||||
#[error("Remote branch {0} not found")]
|
||||
BranchNotFound(git::RemoteRefname),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for IsRemoteBranchMergableError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
IsRemoteBranchMergableError::BranchNotFound(name) => {
|
||||
error::Context::new(Code::Branches, format!("Remote branch {} not found", name))
|
||||
}
|
||||
IsRemoteBranchMergableError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
IsRemoteBranchMergableError::Other(error) => {
|
||||
return error.custom_context_or_root_cause().into()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum IsVirtualBranchMergeable {
|
||||
#[error("default target not set")]
|
||||
@ -354,7 +232,7 @@ pub struct ForcePushNotAllowed {
|
||||
impl ForcePushNotAllowed {
|
||||
fn to_context(&self) -> error::Context {
|
||||
error::Context::new_static(
|
||||
Code::Branches,
|
||||
Code::Unknown,
|
||||
"Action will lead to force pushing, which is not allowed for this",
|
||||
)
|
||||
}
|
||||
@ -362,7 +240,7 @@ impl ForcePushNotAllowed {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CherryPickError {
|
||||
#[error("target commit {0} not found ")]
|
||||
#[error("commit {0} not found ")]
|
||||
CommitNotFound(git::Oid),
|
||||
#[error("can not cherry pick not applied branch")]
|
||||
NotApplied,
|
||||
@ -372,21 +250,6 @@ pub enum CherryPickError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for CherryPickError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
CherryPickError::NotApplied => {
|
||||
error::Context::new_static(Code::Branches, "can not cherry pick non applied branch")
|
||||
}
|
||||
CherryPickError::Conflict(ctx) => ctx.to_context(),
|
||||
CherryPickError::CommitNotFound(oid) => {
|
||||
error::Context::new(Code::Branches, format!("commit {oid} not found"))
|
||||
}
|
||||
CherryPickError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SquashError {
|
||||
#[error("force push not allowed")]
|
||||
@ -405,25 +268,6 @@ pub enum SquashError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for SquashError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
SquashError::ForcePushNotAllowed(ctx) => ctx.to_context(),
|
||||
SquashError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
SquashError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
SquashError::Conflict(ctx) => ctx.to_context(),
|
||||
SquashError::CantSquashRootCommit => {
|
||||
error::Context::new_static(Code::Branches, "can not squash root branch commit")
|
||||
}
|
||||
SquashError::CommitNotFound(oid) => error::Context::new(
|
||||
crate::error::Code::Branches,
|
||||
format!("commit {oid} not found"),
|
||||
),
|
||||
SquashError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FetchFromTargetError {
|
||||
#[error("default target not set")]
|
||||
@ -448,7 +292,7 @@ impl ErrorWithContext for FetchFromTargetError {
|
||||
pub enum UpdateCommitMessageError {
|
||||
#[error("force push not allowed")]
|
||||
ForcePushNotAllowed(ForcePushNotAllowed),
|
||||
#[error("empty message")]
|
||||
#[error("Commit message can not be empty")]
|
||||
EmptyMessage,
|
||||
#[error("default target not set")]
|
||||
DefaultTargetNotSet(DefaultTargetNotSet),
|
||||
@ -462,51 +306,16 @@ pub enum UpdateCommitMessageError {
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for UpdateCommitMessageError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
UpdateCommitMessageError::ForcePushNotAllowed(ctx) => ctx.to_context(),
|
||||
UpdateCommitMessageError::EmptyMessage => {
|
||||
error::Context::new_static(Code::Branches, "Commit message can not be empty")
|
||||
}
|
||||
UpdateCommitMessageError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
UpdateCommitMessageError::CommitNotFound(oid) => {
|
||||
error::Context::new(Code::Branches, format!("Commit {} not found", oid))
|
||||
}
|
||||
UpdateCommitMessageError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
UpdateCommitMessageError::Conflict(ctx) => ctx.to_context(),
|
||||
UpdateCommitMessageError::Other(error) => {
|
||||
return error.custom_context_or_root_cause().into()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SetBaseBranchError {
|
||||
#[error("wd is dirty")]
|
||||
#[error("Current HEAD is dirty.")]
|
||||
DirtyWorkingDirectory,
|
||||
#[error("branch {0} not found")]
|
||||
#[error("remote branch '{0}' not found")]
|
||||
BranchNotFound(git::RemoteRefname),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for SetBaseBranchError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
SetBaseBranchError::DirtyWorkingDirectory => {
|
||||
error::Context::new(Code::ProjectConflict, "Current HEAD is dirty.")
|
||||
}
|
||||
SetBaseBranchError::BranchNotFound(name) => error::Context::new(
|
||||
Code::Branches,
|
||||
format!("remote branch '{}' not found", name),
|
||||
),
|
||||
SetBaseBranchError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UpdateBaseBranchError {
|
||||
#[error("project is in conflicting state")]
|
||||
@ -539,65 +348,26 @@ pub enum MoveCommitError {
|
||||
DefaultTargetNotSet(DefaultTargetNotSet),
|
||||
#[error("branch not found")]
|
||||
BranchNotFound(BranchNotFound),
|
||||
#[error("commit not found")]
|
||||
#[error("commit {0} not found")]
|
||||
CommitNotFound(git::Oid),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for MoveCommitError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
MoveCommitError::SourceLocked => error::Context::new_static(
|
||||
Code::Branches,
|
||||
"Source branch contains hunks locked to the target commit",
|
||||
),
|
||||
MoveCommitError::Conflicted(ctx) => ctx.to_context(),
|
||||
MoveCommitError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
MoveCommitError::BranchNotFound(ctx) => ctx.to_context(),
|
||||
MoveCommitError::CommitNotFound(oid) => {
|
||||
error::Context::new(Code::Branches, format!("Commit {} not found", oid))
|
||||
}
|
||||
MoveCommitError::Other(error) => return error.custom_context_or_root_cause().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CreateVirtualBranchFromBranchError {
|
||||
#[error("failed to apply")]
|
||||
ApplyBranch(ApplyBranchError),
|
||||
#[error("can't make branch from default target")]
|
||||
#[error("can not create a branch from default target")]
|
||||
CantMakeBranchFromDefaultTarget,
|
||||
#[error("default target not set")]
|
||||
DefaultTargetNotSet(DefaultTargetNotSet),
|
||||
#[error("{0} not found")]
|
||||
#[error("branch {0} not found")]
|
||||
BranchNotFound(git::Refname),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for CreateVirtualBranchFromBranchError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
Some(match self {
|
||||
CreateVirtualBranchFromBranchError::ApplyBranch(err) => return err.context(),
|
||||
CreateVirtualBranchFromBranchError::CantMakeBranchFromDefaultTarget => {
|
||||
error::Context::new_static(
|
||||
Code::Branches,
|
||||
"Can not create a branch from default target",
|
||||
)
|
||||
}
|
||||
CreateVirtualBranchFromBranchError::DefaultTargetNotSet(ctx) => ctx.to_context(),
|
||||
CreateVirtualBranchFromBranchError::BranchNotFound(name) => {
|
||||
error::Context::new(Code::Branches, format!("Branch {} not found", name))
|
||||
}
|
||||
CreateVirtualBranchFromBranchError::Other(error) => {
|
||||
return error.custom_context_or_root_cause().into()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProjectConflict {
|
||||
pub project_id: ProjectId,
|
||||
@ -605,10 +375,10 @@ pub struct ProjectConflict {
|
||||
|
||||
impl ProjectConflict {
|
||||
fn to_context(&self) -> error::Context {
|
||||
error::Context::new(
|
||||
Code::ProjectConflict,
|
||||
format!("project {} is in a conflicted state", self.project_id),
|
||||
)
|
||||
error::Context::new(format!(
|
||||
"project {} is in a conflicted state",
|
||||
self.project_id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -619,13 +389,10 @@ pub struct DefaultTargetNotSet {
|
||||
|
||||
impl DefaultTargetNotSet {
|
||||
fn to_context(&self) -> error::Context {
|
||||
error::Context::new(
|
||||
Code::ProjectConflict,
|
||||
format!(
|
||||
"project {} does not have a default target set",
|
||||
self.project_id
|
||||
),
|
||||
)
|
||||
error::Context::new(format!(
|
||||
"project {} does not have a default target set",
|
||||
self.project_id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -637,10 +404,7 @@ pub struct BranchNotFound {
|
||||
|
||||
impl BranchNotFound {
|
||||
fn to_context(&self) -> error::Context {
|
||||
error::Context::new(
|
||||
Code::Branches,
|
||||
format!("branch {} not found", self.branch_id),
|
||||
)
|
||||
error::Context::new(format!("branch {} not found", self.branch_id))
|
||||
}
|
||||
}
|
||||
|
||||
@ -666,23 +430,12 @@ impl ErrorWithContext for UpdateBranchError {
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ListRemoteCommitFilesError {
|
||||
#[error("failed to find commit {0}")]
|
||||
#[error("commit {0} not found")]
|
||||
CommitNotFound(git::Oid),
|
||||
#[error("failed to find commit")]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl ErrorWithContext for ListRemoteCommitFilesError {
|
||||
fn context(&self) -> Option<Context> {
|
||||
match self {
|
||||
ListRemoteCommitFilesError::CommitNotFound(oid) => {
|
||||
error::Context::new(Code::Branches, format!("Commit {} not found", oid)).into()
|
||||
}
|
||||
ListRemoteCommitFilesError::Other(error) => error.custom_context_or_root_cause().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ListRemoteBranchesError {
|
||||
#[error("default target not set")]
|
||||
|
@ -1233,7 +1233,7 @@ pub fn integrate_upstream_commits(
|
||||
if has_rebased_commits && !can_use_force {
|
||||
let message = "Aborted because force push is disallowed and commits have been rebased.";
|
||||
return Err(anyhow!("Cannot merge rebased commits without force push")
|
||||
.context(error::Context::new(Code::ProjectConflict, message)));
|
||||
.context(error::Context::new(message)));
|
||||
}
|
||||
|
||||
let integration_result = match can_use_force {
|
||||
@ -1243,7 +1243,7 @@ pub fn integrate_upstream_commits(
|
||||
let message =
|
||||
"Aborted because force push is disallowed and commits have been rebased.";
|
||||
return Err(anyhow!("Cannot merge rebased commits without force push")
|
||||
.context(error::Context::new(Code::ProjectConflict, message)));
|
||||
.context(error::Context::new(message)));
|
||||
}
|
||||
integrate_with_merge(
|
||||
project_repository,
|
||||
|
@ -13,9 +13,9 @@ mod into_anyhow {
|
||||
}
|
||||
}
|
||||
|
||||
let err = into_anyhow(Error(Code::Projects));
|
||||
let err = into_anyhow(Error(Code::Validation));
|
||||
let ctx = err.downcast_ref::<Context>().unwrap();
|
||||
assert_eq!(ctx.code, Code::Projects, "the context is attached");
|
||||
assert_eq!(ctx.code, Code::Validation, "the context is attached");
|
||||
assert_eq!(
|
||||
ctx.message, None,
|
||||
"there is no message when context was created from bare code"
|
||||
@ -38,11 +38,11 @@ mod into_anyhow {
|
||||
}
|
||||
}
|
||||
|
||||
let err = into_anyhow(Outer::from(Inner(Code::Projects)));
|
||||
let err = into_anyhow(Outer::from(Inner(Code::Validation)));
|
||||
let ctx = err.downcast_ref::<Context>().unwrap();
|
||||
assert_eq!(
|
||||
ctx.code,
|
||||
Code::Projects,
|
||||
Code::Validation,
|
||||
"there is no magic here, it's all about manually implementing the nesting :/"
|
||||
);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ mod add {
|
||||
|
||||
let worktree = repo.worktree("feature", &worktree_dir, None).unwrap();
|
||||
let err = controller.add(worktree.path()).unwrap_err();
|
||||
assert_eq!(err.to_string(), "worktrees unsupported");
|
||||
assert_eq!(err.to_string(), "can only work in main worktrees");
|
||||
}
|
||||
|
||||
fn create_initial_commit(repo: &git2::Repository) -> git2::Oid {
|
||||
|
@ -2095,7 +2095,7 @@ fn verify_branch_not_integration() -> Result<()> {
|
||||
assert!(verify_result.is_err());
|
||||
assert_eq!(
|
||||
verify_result.unwrap_err().to_string(),
|
||||
"head is refs/heads/master"
|
||||
"project is on refs/heads/master. Please checkout gitbutler/integration to continue"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
@ -48,6 +48,17 @@ mod frontend {
|
||||
pub fn from_error_with_context(err: impl ErrorWithContext + Send + Sync + 'static) -> Self {
|
||||
Self(into_anyhow(err))
|
||||
}
|
||||
|
||||
/// Convert an error without context to our type.
|
||||
///
|
||||
/// For now, we avoid using a conversion as it would be so general, we'd miss errors with context
|
||||
/// which need [`from_error_with_context`](Self::from_error_with_context) for the context to be
|
||||
/// picked up.
|
||||
pub fn from_error_without_context(
|
||||
err: impl std::error::Error + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
@ -92,30 +103,30 @@ mod frontend {
|
||||
|
||||
#[test]
|
||||
fn find_code() {
|
||||
let err = anyhow!("err msg").context(Code::Projects);
|
||||
let err = anyhow!("err msg").context(Code::Validation);
|
||||
assert_eq!(
|
||||
json(err),
|
||||
"{\"code\":\"errors.projects\",\"message\":\"err msg\"}",
|
||||
"{\"code\":\"errors.validation\",\"message\":\"err msg\"}",
|
||||
"the 'code' is available as string, but the message is taken from the source error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_context() {
|
||||
let err = anyhow!("err msg").context(Context::new_static(Code::Projects, "ctx msg"));
|
||||
let err = anyhow!("err msg").context(Context::new_static(Code::Validation, "ctx msg"));
|
||||
assert_eq!(
|
||||
json(err),
|
||||
"{\"code\":\"errors.projects\",\"message\":\"ctx msg\"}",
|
||||
"{\"code\":\"errors.validation\",\"message\":\"ctx msg\"}",
|
||||
"Contexts often provide their own message, so the error message is ignored"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_context_without_message() {
|
||||
let err = anyhow!("err msg").context(Context::from(Code::Projects));
|
||||
let err = anyhow!("err msg").context(Context::from(Code::Validation));
|
||||
assert_eq!(
|
||||
json(err),
|
||||
"{\"code\":\"errors.projects\",\"message\":\"err msg\"}",
|
||||
"{\"code\":\"errors.validation\",\"message\":\"err msg\"}",
|
||||
"Contexts without a message show the error's message as well"
|
||||
);
|
||||
}
|
||||
@ -124,10 +135,10 @@ mod frontend {
|
||||
fn find_nested_code() {
|
||||
let err = anyhow!("bottom msg")
|
||||
.context("top msg")
|
||||
.context(Code::Projects);
|
||||
.context(Code::Validation);
|
||||
assert_eq!(
|
||||
json(err),
|
||||
"{\"code\":\"errors.projects\",\"message\":\"top msg\"}",
|
||||
"{\"code\":\"errors.validation\",\"message\":\"top msg\"}",
|
||||
"the 'code' gets the message of the error that it provides context to, and it finds it down the chain"
|
||||
);
|
||||
}
|
||||
@ -135,12 +146,12 @@ mod frontend {
|
||||
#[test]
|
||||
fn multiple_codes() {
|
||||
let err = anyhow!("bottom msg")
|
||||
.context(Code::Menu)
|
||||
.context(Code::ProjectGitAuth)
|
||||
.context("top msg")
|
||||
.context(Code::Projects);
|
||||
.context(Code::Validation);
|
||||
assert_eq!(
|
||||
json(err),
|
||||
"{\"code\":\"errors.projects\",\"message\":\"top msg\"}",
|
||||
"{\"code\":\"errors.validation\",\"message\":\"top msg\"}",
|
||||
"it finds the most recent 'code' (and the same would be true for contexts, of course)"
|
||||
);
|
||||
}
|
||||
|
@ -27,9 +27,7 @@ pub async fn menu_item_set_enabled(
|
||||
let menu_item = window
|
||||
.menu_handle()
|
||||
.try_get_item(menu_item_id)
|
||||
.with_context(|| {
|
||||
error::Context::new(Code::Menu, format!("menu item not found: {}", menu_item_id))
|
||||
})?;
|
||||
.with_context(|| error::Context::new(format!("menu item not found: {}", menu_item_id)))?;
|
||||
|
||||
menu_item.set_enabled(enabled).context(Code::Unknown)?;
|
||||
|
||||
|
@ -2,7 +2,6 @@ pub mod commands {
|
||||
use anyhow::Context;
|
||||
use std::path;
|
||||
|
||||
use gitbutler_core::error::Code;
|
||||
use gitbutler_core::projects::{self, controller::Controller, ProjectId};
|
||||
use tauri::Manager;
|
||||
use tracing::instrument;
|
||||
@ -20,7 +19,7 @@ pub mod commands {
|
||||
.state::<Controller>()
|
||||
.update(&project)
|
||||
.await
|
||||
.map_err(Error::from_error_with_context)
|
||||
.map_err(Error::from_error_without_context)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
@ -32,7 +31,7 @@ pub mod commands {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.add(path)
|
||||
.map_err(Error::from_error_with_context)
|
||||
.map_err(Error::from_error_without_context)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
@ -41,10 +40,7 @@ pub mod commands {
|
||||
handle: tauri::AppHandle,
|
||||
id: ProjectId,
|
||||
) -> Result<projects::Project, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.get(id)
|
||||
.map_err(Error::from_error_with_context)
|
||||
Ok(handle.state::<Controller>().get(id)?)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
@ -83,10 +79,10 @@ pub mod commands {
|
||||
id: ProjectId,
|
||||
key: &str,
|
||||
) -> Result<Option<String>, Error> {
|
||||
Ok(handle
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.get_local_config(id, key)
|
||||
.context(Code::Projects)?)
|
||||
.map_err(Error::from_error_without_context)
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
@ -97,9 +93,9 @@ pub mod commands {
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> Result<(), Error> {
|
||||
Ok(handle
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.set_local_config(id, key, value)
|
||||
.context(Code::Projects)?)
|
||||
.map_err(Error::from_error_without_context)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user