mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-28 12:05:22 +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-window-state",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"urlencoding",
|
||||
"uuid 1.3.0",
|
||||
"walkdir",
|
||||
|
@ -34,6 +34,7 @@ tempfile = "3.3.0"
|
||||
reqwest = "0.11.14"
|
||||
md5 = "0.7.0"
|
||||
urlencoding = "2.1.2"
|
||||
thiserror = "1.0.38"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
@ -11,7 +11,7 @@ mod watchers;
|
||||
use anyhow::{Context, Result};
|
||||
use deltas::Delta;
|
||||
use log;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{ser::SerializeMap, Serialize};
|
||||
use std::{collections::HashMap, sync::mpsc, thread};
|
||||
use storage::Storage;
|
||||
use tauri::{generate_context, Manager};
|
||||
@ -19,10 +19,40 @@ use tauri_plugin_log::{
|
||||
fern::colors::{Color, ColoredLevelConfig},
|
||||
LogTarget,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Error {
|
||||
pub message: String,
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[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);
|
||||
@ -85,19 +115,11 @@ fn list_sessions(
|
||||
let users_storage = users::Storage::new(storage);
|
||||
|
||||
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(),
|
||||
}
|
||||
})?;
|
||||
.with_context(|| format!("Failed to open repository for project {}", project_id))?;
|
||||
|
||||
let sessions = repo.sessions().map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to list sessions".to_string(),
|
||||
}
|
||||
})?;
|
||||
let sessions = repo
|
||||
.sessions()
|
||||
.with_context(|| format!("Failed to list sessions for project {}", project_id))?;
|
||||
|
||||
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 users_storage = users::Storage::new(storage);
|
||||
|
||||
match users_storage.get().map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to get user".to_string(),
|
||||
}
|
||||
})? {
|
||||
match users_storage
|
||||
.get()
|
||||
.with_context(|| "Failed to get user".to_string())?
|
||||
{
|
||||
Some(user) => {
|
||||
let local_picture = match proxy_image(handle, &user.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 users_storage = users::Storage::new(storage);
|
||||
|
||||
users_storage.set(&user).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to save user".to_string(),
|
||||
}
|
||||
})?;
|
||||
users_storage
|
||||
.set(&user)
|
||||
.with_context(|| "Failed to set user".to_string())?;
|
||||
|
||||
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 users_storage = users::Storage::new(storage);
|
||||
|
||||
users_storage.delete().map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to delete user".to_string(),
|
||||
}
|
||||
})?;
|
||||
users_storage
|
||||
.delete()
|
||||
.with_context(|| "Failed to delete user".to_string())?;
|
||||
|
||||
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 projects_storage = projects::Storage::new(storage);
|
||||
|
||||
projects_storage.update_project(&project).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to update project".to_string(),
|
||||
}
|
||||
})
|
||||
let project = projects_storage
|
||||
.update_project(&project)
|
||||
.with_context(|| format!("Failed to update project {}", project.id))?;
|
||||
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -200,63 +213,32 @@ fn add_project(handle: tauri::AppHandle, path: &str) -> Result<projects::Project
|
||||
users_storage.clone(),
|
||||
);
|
||||
|
||||
for project in projects_storage.list_projects().map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to list projects".to_string(),
|
||||
}
|
||||
})? {
|
||||
for project in projects_storage
|
||||
.list_projects()
|
||||
.with_context(|| "Failed to list projects".to_string())?
|
||||
{
|
||||
if project.path == path {
|
||||
if !project.deleted {
|
||||
return Err(Error {
|
||||
message: "Project already exists".to_string(),
|
||||
});
|
||||
return Err(Error::ProjectAlreadyExists);
|
||||
} else {
|
||||
projects_storage
|
||||
.update_project(&projects::UpdateRequest {
|
||||
id: project.id.clone(),
|
||||
deleted: Some(false),
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to undelete project".to_string(),
|
||||
}
|
||||
})?;
|
||||
projects_storage.update_project(&projects::UpdateRequest {
|
||||
id: project.id.clone(),
|
||||
deleted: Some(false),
|
||||
..Default::default()
|
||||
})?;
|
||||
return Ok(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let project = projects::Project::from_path(path.to_string());
|
||||
if project.is_ok() {
|
||||
let project = project.unwrap();
|
||||
projects_storage.add_project(&project).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to add project".to_string(),
|
||||
}
|
||||
})?;
|
||||
let project = projects::Project::from_path(path.to_string())?;
|
||||
projects_storage.add_project(&project)?;
|
||||
|
||||
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) =
|
||||
mpsc::channel();
|
||||
let (tx, rx): (mpsc::Sender<events::Event>, mpsc::Receiver<events::Event>) = mpsc::channel();
|
||||
watchers.watch(tx, &project)?;
|
||||
watch_events(handle, rx);
|
||||
|
||||
watchers.watch(tx, &project).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to watch project".to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
watch_events(handle, rx);
|
||||
|
||||
return Ok(project);
|
||||
} else {
|
||||
return Err(Error {
|
||||
message: "Failed to add project".to_string(),
|
||||
});
|
||||
}
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
#[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 projects_storage = projects::Storage::new(storage);
|
||||
|
||||
projects_storage.list_projects().map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to list projects".to_string(),
|
||||
}
|
||||
})
|
||||
let projects = projects_storage.list_projects()?;
|
||||
|
||||
Ok(projects)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -286,37 +265,19 @@ fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> {
|
||||
users_storage.clone(),
|
||||
);
|
||||
|
||||
match projects_storage.get_project(id) {
|
||||
Ok(Some(project)) => {
|
||||
watchers.unwatch(project).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to unwatch project".to_string(),
|
||||
}
|
||||
})?;
|
||||
match projects_storage.get_project(id)? {
|
||||
Some(project) => {
|
||||
watchers.unwatch(project)?;
|
||||
|
||||
projects_storage
|
||||
.update_project(&projects::UpdateRequest {
|
||||
id: id.to_string(),
|
||||
deleted: Some(true),
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to delete project".to_string(),
|
||||
}
|
||||
})?;
|
||||
projects_storage.update_project(&projects::UpdateRequest {
|
||||
id: id.to_string(),
|
||||
deleted: Some(true),
|
||||
..Default::default()
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(None) => Ok(()),
|
||||
Err(e) => {
|
||||
log::error!("{:#}", e);
|
||||
Err(Error {
|
||||
message: "Failed to get project".to_string(),
|
||||
})
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,20 +293,9 @@ fn list_session_files(
|
||||
let projects_storage = projects::Storage::new(storage.clone());
|
||||
let users_storage = users::Storage::new(storage);
|
||||
|
||||
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 repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)?;
|
||||
|
||||
let files = repo.files(session_id, paths).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to list files".to_string(),
|
||||
}
|
||||
})?;
|
||||
let files = repo.files(session_id, paths)?;
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
@ -361,20 +311,9 @@ fn list_deltas(
|
||||
let projects_storage = projects::Storage::new(storage.clone());
|
||||
let users_storage = users::Storage::new(storage);
|
||||
|
||||
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 repo = repositories::Repository::open(&projects_storage, &users_storage, project_id)?;
|
||||
|
||||
let deltas = repo.deltas(session_id).map_err(|e| {
|
||||
log::error!("{:#}", e);
|
||||
Error {
|
||||
message: "Failed to list deltas".to_string(),
|
||||
}
|
||||
})?;
|
||||
let deltas = repo.deltas(session_id)?;
|
||||
|
||||
Ok(deltas)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
mod project;
|
||||
mod storage;
|
||||
|
||||
pub use project::Project;
|
||||
pub use storage::Storage;
|
||||
pub use storage::UpdateRequest;
|
||||
pub use project::{CreateError, Project};
|
||||
pub use storage::{Storage, UpdateRequest};
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
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 {
|
||||
pub fn refname(&self) -> String {
|
||||
format!("refs/gitbutler-{}/current", self.id)
|
||||
@ -47,34 +57,40 @@ impl Project {
|
||||
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
|
||||
let path = std::path::Path::new(&path);
|
||||
let path = std::path::Path::new(&fpath);
|
||||
if !path.exists() {
|
||||
return Err(anyhow!("path {} does not exist", path.display()));
|
||||
return Err(CreateError::PathNotFound(fpath).into());
|
||||
}
|
||||
|
||||
// make sure path is a directory
|
||||
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
|
||||
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
|
||||
path.into_iter()
|
||||
let title = path
|
||||
.into_iter()
|
||||
.last()
|
||||
.map(|p| p.to_str().unwrap().to_string())
|
||||
.map(|title| Self {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
deleted: false,
|
||||
title,
|
||||
path: path.to_str().unwrap().to_string(),
|
||||
api: None,
|
||||
})
|
||||
.ok_or_else(|| anyhow!("failed to get title from path"))
|
||||
.unwrap_or_else(|| id.clone());
|
||||
|
||||
let project = Project {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
deleted: false,
|
||||
title,
|
||||
path: path.to_str().unwrap().to_string(),
|
||||
api: None,
|
||||
};
|
||||
|
||||
Ok(project)
|
||||
}
|
||||
}
|
||||
|
@ -3,20 +3,20 @@ import { derived, writable } from 'svelte/store';
|
||||
import type { Project as ApiProject } from '$lib/api';
|
||||
|
||||
export type Project = {
|
||||
id: string;
|
||||
title: string;
|
||||
path: string;
|
||||
api: ApiProject & { sync: boolean };
|
||||
id: string;
|
||||
title: string;
|
||||
path: string;
|
||||
api: ApiProject & { sync: boolean };
|
||||
};
|
||||
|
||||
const list = () => invoke<Project[]>('list_projects');
|
||||
|
||||
const update = (params: {
|
||||
project: {
|
||||
id: string;
|
||||
title?: string;
|
||||
api?: ApiProject & { sync: boolean };
|
||||
};
|
||||
project: {
|
||||
id: string;
|
||||
title?: string;
|
||||
api?: ApiProject & { sync: boolean };
|
||||
};
|
||||
}) => invoke<Project>('update_project', params);
|
||||
|
||||
const add = (params: { path: string }) => invoke<Project>('add_project', params);
|
||||
@ -24,35 +24,35 @@ const add = (params: { path: string }) => invoke<Project>('add_project', params)
|
||||
const del = (params: { id: string }) => invoke('delete_project', params);
|
||||
|
||||
export default async () => {
|
||||
const init = await list();
|
||||
const store = writable<Project[]>(init);
|
||||
const init = await list();
|
||||
const store = writable<Project[]>(init);
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
get: (id: string) => {
|
||||
const project = derived(store, (store) => store.find((p) => p.id === id));
|
||||
return {
|
||||
subscribe: project.subscribe,
|
||||
update: (params: { title?: string; api?: Project['api'] }) =>
|
||||
update({
|
||||
project: {
|
||||
id,
|
||||
...params
|
||||
}
|
||||
}).then((project) => {
|
||||
store.update((projects) => projects.map((p) => (p.id === project.id ? project : p)));
|
||||
return project;
|
||||
})
|
||||
};
|
||||
},
|
||||
add: (params: { path: string }) =>
|
||||
add(params).then((project) => {
|
||||
store.update((projects) => [...projects, project]);
|
||||
return project;
|
||||
}),
|
||||
delete: (params: { id: string }) =>
|
||||
del(params).then(() => {
|
||||
store.update((projects) => projects.filter((p) => p.id !== params.id));
|
||||
})
|
||||
};
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
get: (id: string) => {
|
||||
const project = derived(store, (store) => store.find((p) => p.id === id));
|
||||
return {
|
||||
subscribe: project.subscribe,
|
||||
update: (params: { title?: string; api?: Project['api'] }) =>
|
||||
update({
|
||||
project: {
|
||||
id,
|
||||
...params
|
||||
}
|
||||
}).then((project) => {
|
||||
store.update((projects) => projects.map((p) => (p.id === project.id ? project : p)));
|
||||
return project;
|
||||
})
|
||||
};
|
||||
},
|
||||
add: (params: { path: string }) =>
|
||||
add(params).then((project) => {
|
||||
store.update((projects) => [...projects, project]);
|
||||
return project;
|
||||
}),
|
||||
delete: (params: { id: string }) =>
|
||||
del(params).then(() => {
|
||||
store.update((projects) => projects.filter((p) => p.id !== params.id));
|
||||
})
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import type { LayoutData } from './$types';
|
||||
import { toasts } from '$lib';
|
||||
|
||||
export let data: LayoutData;
|
||||
|
||||
const { projects } = data;
|
||||
@ -14,10 +16,11 @@
|
||||
if (Array.isArray(selectedPath) && selectedPath.length !== 1) return;
|
||||
const projectPath = Array.isArray(selectedPath) ? selectedPath[0] : selectedPath;
|
||||
|
||||
const projectExists = $projects.some((p) => p.path === projectPath);
|
||||
if (projectExists) return;
|
||||
|
||||
await projects.add({ path: projectPath });
|
||||
try {
|
||||
await projects.add({ path: projectPath });
|
||||
} catch (e: any) {
|
||||
toasts.error(e.message);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user