mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-29 12:33:49 +03:00
display errors when adding project
This commit is contained in:
parent
a6a3df025e
commit
1ff419a8c8
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@ -1016,6 +1016,7 @@ dependencies = [
|
|||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"uuid 1.3.0",
|
"uuid 1.3.0",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
@ -34,6 +34,7 @@ tempfile = "3.3.0"
|
|||||||
reqwest = "0.11.14"
|
reqwest = "0.11.14"
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
@ -11,7 +11,7 @@ mod watchers;
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use deltas::Delta;
|
use deltas::Delta;
|
||||||
use log;
|
use log;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{ser::SerializeMap, Serialize};
|
||||||
use std::{collections::HashMap, sync::mpsc, thread};
|
use std::{collections::HashMap, sync::mpsc, thread};
|
||||||
use storage::Storage;
|
use storage::Storage;
|
||||||
use tauri::{generate_context, Manager};
|
use tauri::{generate_context, Manager};
|
||||||
@ -19,10 +19,40 @@ use tauri_plugin_log::{
|
|||||||
fern::colors::{Color, ColoredLevelConfig},
|
fern::colors::{Color, ColoredLevelConfig},
|
||||||
LogTarget,
|
LogTarget,
|
||||||
};
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Error)]
|
||||||
pub struct Error {
|
pub enum Error {
|
||||||
pub message: String,
|
#[error("{0}")]
|
||||||
|
ProjectError(projects::CreateError),
|
||||||
|
#[error("Project already exists")]
|
||||||
|
ProjectAlreadyExists,
|
||||||
|
#[error("Something went wrong")]
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Error {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let mut map = serializer.serialize_map(Some(1))?;
|
||||||
|
map.serialize_entry("message", &self.to_string())?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<projects::CreateError> for Error {
|
||||||
|
fn from(e: projects::CreateError) -> Self {
|
||||||
|
Error::ProjectError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<anyhow::Error> for Error {
|
||||||
|
fn from(e: anyhow::Error) -> Self {
|
||||||
|
log::error!("{:#}", e);
|
||||||
|
Error::Unknown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IS_DEV: bool = cfg!(debug_assertions);
|
const IS_DEV: bool = cfg!(debug_assertions);
|
||||||
@ -85,19 +115,11 @@ fn list_sessions(
|
|||||||
let users_storage = users::Storage::new(storage);
|
let users_storage = users::Storage::new(storage);
|
||||||
|
|
||||||
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)
|
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)
|
||||||
.map_err(|e| {
|
.with_context(|| format!("Failed to open repository for project {}", project_id))?;
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to open project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let sessions = repo.sessions().map_err(|e| {
|
let sessions = repo
|
||||||
log::error!("{:#}", e);
|
.sessions()
|
||||||
Error {
|
.with_context(|| format!("Failed to list sessions for project {}", project_id))?;
|
||||||
message: "Failed to list sessions".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(sessions)
|
Ok(sessions)
|
||||||
}
|
}
|
||||||
@ -108,12 +130,10 @@ fn get_user(handle: tauri::AppHandle) -> Result<Option<users::User>, Error> {
|
|||||||
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
||||||
let users_storage = users::Storage::new(storage);
|
let users_storage = users::Storage::new(storage);
|
||||||
|
|
||||||
match users_storage.get().map_err(|e| {
|
match users_storage
|
||||||
log::error!("{:#}", e);
|
.get()
|
||||||
Error {
|
.with_context(|| "Failed to get user".to_string())?
|
||||||
message: "Failed to get user".to_string(),
|
{
|
||||||
}
|
|
||||||
})? {
|
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
let local_picture = match proxy_image(handle, &user.picture) {
|
let local_picture = match proxy_image(handle, &user.picture) {
|
||||||
Ok(picture) => picture,
|
Ok(picture) => picture,
|
||||||
@ -140,12 +160,9 @@ fn set_user(handle: tauri::AppHandle, user: users::User) -> Result<(), Error> {
|
|||||||
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
||||||
let users_storage = users::Storage::new(storage);
|
let users_storage = users::Storage::new(storage);
|
||||||
|
|
||||||
users_storage.set(&user).map_err(|e| {
|
users_storage
|
||||||
log::error!("{:#}", e);
|
.set(&user)
|
||||||
Error {
|
.with_context(|| "Failed to set user".to_string())?;
|
||||||
message: "Failed to save user".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
sentry::configure_scope(|scope| scope.set_user(Some(user.clone().into())));
|
sentry::configure_scope(|scope| scope.set_user(Some(user.clone().into())));
|
||||||
|
|
||||||
@ -158,12 +175,9 @@ fn delete_user(handle: tauri::AppHandle) -> Result<(), Error> {
|
|||||||
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
||||||
let users_storage = users::Storage::new(storage);
|
let users_storage = users::Storage::new(storage);
|
||||||
|
|
||||||
users_storage.delete().map_err(|e| {
|
users_storage
|
||||||
log::error!("{:#}", e);
|
.delete()
|
||||||
Error {
|
.with_context(|| "Failed to delete user".to_string())?;
|
||||||
message: "Failed to delete user".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
sentry::configure_scope(|scope| scope.set_user(None));
|
sentry::configure_scope(|scope| scope.set_user(None));
|
||||||
|
|
||||||
@ -179,12 +193,11 @@ fn update_project(
|
|||||||
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
||||||
let projects_storage = projects::Storage::new(storage);
|
let projects_storage = projects::Storage::new(storage);
|
||||||
|
|
||||||
projects_storage.update_project(&project).map_err(|e| {
|
let project = projects_storage
|
||||||
log::error!("{:#}", e);
|
.update_project(&project)
|
||||||
Error {
|
.with_context(|| format!("Failed to update project {}", project.id))?;
|
||||||
message: "Failed to update project".to_string(),
|
|
||||||
}
|
Ok(project)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -200,63 +213,32 @@ fn add_project(handle: tauri::AppHandle, path: &str) -> Result<projects::Project
|
|||||||
users_storage.clone(),
|
users_storage.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for project in projects_storage.list_projects().map_err(|e| {
|
for project in projects_storage
|
||||||
log::error!("{:#}", e);
|
.list_projects()
|
||||||
Error {
|
.with_context(|| "Failed to list projects".to_string())?
|
||||||
message: "Failed to list projects".to_string(),
|
{
|
||||||
}
|
|
||||||
})? {
|
|
||||||
if project.path == path {
|
if project.path == path {
|
||||||
if !project.deleted {
|
if !project.deleted {
|
||||||
return Err(Error {
|
return Err(Error::ProjectAlreadyExists);
|
||||||
message: "Project already exists".to_string(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
projects_storage
|
projects_storage.update_project(&projects::UpdateRequest {
|
||||||
.update_project(&projects::UpdateRequest {
|
|
||||||
id: project.id.clone(),
|
id: project.id.clone(),
|
||||||
deleted: Some(false),
|
deleted: Some(false),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to undelete project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
return Ok(project);
|
return Ok(project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let project = projects::Project::from_path(path.to_string());
|
let project = projects::Project::from_path(path.to_string())?;
|
||||||
if project.is_ok() {
|
projects_storage.add_project(&project)?;
|
||||||
let project = project.unwrap();
|
|
||||||
projects_storage.add_project(&project).map_err(|e| {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to add project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) =
|
|
||||||
mpsc::channel();
|
|
||||||
|
|
||||||
watchers.watch(tx, &project).map_err(|e| {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to watch project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
|
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) = mpsc::channel();
|
||||||
|
watchers.watch(tx, &project)?;
|
||||||
watch_events(handle, rx);
|
watch_events(handle, rx);
|
||||||
|
|
||||||
return Ok(project);
|
Ok(project)
|
||||||
} else {
|
|
||||||
return Err(Error {
|
|
||||||
message: "Failed to add project".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -265,12 +247,9 @@ fn list_projects(handle: tauri::AppHandle) -> Result<Vec<projects::Project>, Err
|
|||||||
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
let storage = storage::Storage::from_path_resolver(&path_resolver);
|
||||||
let projects_storage = projects::Storage::new(storage);
|
let projects_storage = projects::Storage::new(storage);
|
||||||
|
|
||||||
projects_storage.list_projects().map_err(|e| {
|
let projects = projects_storage.list_projects()?;
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
Ok(projects)
|
||||||
message: "Failed to list projects".to_string(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -286,37 +265,19 @@ fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> {
|
|||||||
users_storage.clone(),
|
users_storage.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
match projects_storage.get_project(id) {
|
match projects_storage.get_project(id)? {
|
||||||
Ok(Some(project)) => {
|
Some(project) => {
|
||||||
watchers.unwatch(project).map_err(|e| {
|
watchers.unwatch(project)?;
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to unwatch project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
projects_storage
|
projects_storage.update_project(&projects::UpdateRequest {
|
||||||
.update_project(&projects::UpdateRequest {
|
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
deleted: Some(true),
|
deleted: Some(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to delete project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(None) => Ok(()),
|
None => Ok(()),
|
||||||
Err(e) => {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Err(Error {
|
|
||||||
message: "Failed to get project".to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,20 +293,9 @@ fn list_session_files(
|
|||||||
let projects_storage = projects::Storage::new(storage.clone());
|
let projects_storage = projects::Storage::new(storage.clone());
|
||||||
let users_storage = users::Storage::new(storage);
|
let users_storage = users::Storage::new(storage);
|
||||||
|
|
||||||
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)
|
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)?;
|
||||||
.map_err(|e| {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to open project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let files = repo.files(session_id, paths).map_err(|e| {
|
let files = repo.files(session_id, paths)?;
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to list files".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
@ -361,20 +311,9 @@ fn list_deltas(
|
|||||||
let projects_storage = projects::Storage::new(storage.clone());
|
let projects_storage = projects::Storage::new(storage.clone());
|
||||||
let users_storage = users::Storage::new(storage);
|
let users_storage = users::Storage::new(storage);
|
||||||
|
|
||||||
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)
|
let repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)?;
|
||||||
.map_err(|e| {
|
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to open project".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let deltas = repo.deltas(session_id).map_err(|e| {
|
let deltas = repo.deltas(session_id)?;
|
||||||
log::error!("{:#}", e);
|
|
||||||
Error {
|
|
||||||
message: "Failed to list deltas".to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(deltas)
|
Ok(deltas)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
mod project;
|
mod project;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
pub use project::Project;
|
pub use project::{CreateError, Project};
|
||||||
pub use storage::Storage;
|
pub use storage::{Storage, UpdateRequest};
|
||||||
pub use storage::UpdateRequest;
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
use anyhow::Result;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct ApiProject {
|
pub struct ApiProject {
|
||||||
@ -31,6 +31,16 @@ impl AsRef<Project> for Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum CreateError {
|
||||||
|
#[error("{0} does not exist")]
|
||||||
|
PathNotFound(String),
|
||||||
|
#[error("{0} is not a directory")]
|
||||||
|
NotADirectory(String),
|
||||||
|
#[error("{0} is not a git repository")]
|
||||||
|
NotAGitRepository(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
pub fn refname(&self) -> String {
|
pub fn refname(&self) -> String {
|
||||||
format!("refs/gitbutler-{}/current", self.id)
|
format!("refs/gitbutler-{}/current", self.id)
|
||||||
@ -47,34 +57,40 @@ impl Project {
|
|||||||
self.session_path().join("deltas")
|
self.session_path().join("deltas")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_path(path: String) -> Result<Self> {
|
pub fn from_path(fpath: String) -> Result<Self, CreateError> {
|
||||||
// make sure path exists
|
// make sure path exists
|
||||||
let path = std::path::Path::new(&path);
|
let path = std::path::Path::new(&fpath);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(anyhow!("path {} does not exist", path.display()));
|
return Err(CreateError::PathNotFound(fpath).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure path is a directory
|
// make sure path is a directory
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
return Err(anyhow!("path {} is not a directory", path.display()));
|
return Err(CreateError::NotADirectory(fpath).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure it's a git repository
|
// make sure it's a git repository
|
||||||
if !path.join(".git").exists() {
|
if !path.join(".git").exists() {
|
||||||
return Err(anyhow!("path {} is not a git repository", path.display()));
|
return Err(CreateError::NotAGitRepository(fpath).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let id = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
// title is the base name of the file
|
// title is the base name of the file
|
||||||
path.into_iter()
|
let title = path
|
||||||
|
.into_iter()
|
||||||
.last()
|
.last()
|
||||||
.map(|p| p.to_str().unwrap().to_string())
|
.map(|p| p.to_str().unwrap().to_string())
|
||||||
.map(|title| Self {
|
.unwrap_or_else(|| id.clone());
|
||||||
|
|
||||||
|
let project = Project {
|
||||||
id: uuid::Uuid::new_v4().to_string(),
|
id: uuid::Uuid::new_v4().to_string(),
|
||||||
deleted: false,
|
deleted: false,
|
||||||
title,
|
title,
|
||||||
path: path.to_str().unwrap().to_string(),
|
path: path.to_str().unwrap().to_string(),
|
||||||
api: None,
|
api: None,
|
||||||
})
|
};
|
||||||
.ok_or_else(|| anyhow!("failed to get title from path"))
|
|
||||||
|
Ok(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { open } from '@tauri-apps/api/dialog';
|
import { open } from '@tauri-apps/api/dialog';
|
||||||
import type { LayoutData } from './$types';
|
import type { LayoutData } from './$types';
|
||||||
|
import { toasts } from '$lib';
|
||||||
|
|
||||||
export let data: LayoutData;
|
export let data: LayoutData;
|
||||||
|
|
||||||
const { projects } = data;
|
const { projects } = data;
|
||||||
@ -14,10 +16,11 @@
|
|||||||
if (Array.isArray(selectedPath) && selectedPath.length !== 1) return;
|
if (Array.isArray(selectedPath) && selectedPath.length !== 1) return;
|
||||||
const projectPath = Array.isArray(selectedPath) ? selectedPath[0] : selectedPath;
|
const projectPath = Array.isArray(selectedPath) ? selectedPath[0] : selectedPath;
|
||||||
|
|
||||||
const projectExists = $projects.some((p) => p.path === projectPath);
|
try {
|
||||||
if (projectExists) return;
|
|
||||||
|
|
||||||
await projects.add({ path: projectPath });
|
await projects.add({ path: projectPath });
|
||||||
|
} catch (e: any) {
|
||||||
|
toasts.error(e.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user