Merge pull request #1364 from gitbutlerapp/Add-id-module

Add id module
This commit is contained in:
Nikita Galaiko 2023-10-13 13:14:01 +02:00 committed by GitHub
commit 04b9f7bd8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 938 additions and 526 deletions

View File

@ -20,7 +20,7 @@ impl super::RunCommand for Branches {
.context("failed to list branches")?;
for branch in branches {
println!("{}", branch.id.red());
println!("{}", branch.id.to_string().red());
println!("{}", branch.name.red());
for file in branch.files {
println!(" {}", file.path.display().to_string().blue());

View File

@ -47,8 +47,11 @@ impl super::RunCommand for Commit {
None => return Ok(()),
};
let commit_branch = ids[selection].clone();
println!("Committing virtual branch {}", commit_branch.red());
let commit_branch = ids[selection];
println!(
"Committing virtual branch {}",
commit_branch.to_string().red()
);
// get the commit message
let message: String = Input::with_theme(&ColorfulTheme::default())

View File

@ -26,7 +26,7 @@ impl super::RunCommand for Info {
// find the project in project storage that matches the cwd
println!("{}", "project:".to_string().red());
println!(" id: {}", app.project().id.blue());
println!(" id: {}", app.project().id.to_string().blue());
println!(" title: {}", app.project().title.blue());
println!(
" description: {}",
@ -77,7 +77,7 @@ impl super::RunCommand for Info {
.unwrap();
//list the sessions
for session in &sessions {
println!(" id: {}", session.id.blue());
println!(" id: {}", session.id.to_string().blue());
}
// gitbutler repo stuff

View File

@ -22,7 +22,7 @@ impl super::RunCommand for Status {
println!(" branch: {}", branch.name.blue());
println!(" head: {}", branch.head.to_string().green());
println!(" tree: {}", branch.tree.to_string().green());
println!(" id: {}", branch.id.green());
println!(" id: {}", branch.id.to_string().green());
println!("applied: {}", branch.applied.to_string().green());
println!(" files:");
for file in files {

View File

@ -2,7 +2,7 @@ use std::{str, sync::Arc};
use tauri::AppHandle;
use crate::users::User;
use crate::{projects::ProjectId, users::User};
mod posthog;
@ -13,13 +13,13 @@ pub struct Config<'c> {
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
HeadChange {
project_id: String,
project_id: ProjectId,
reference_name: String,
},
}
impl Event {
pub fn project_id(&self) -> &str {
pub fn project_id(&self) -> &ProjectId {
match self {
Event::HeadChange { project_id, .. } => project_id,
}

View File

@ -9,7 +9,10 @@ use crate::{
keys,
paths::DataDir,
project_repository::{self, conflicts},
projects, reader, search, sessions, users,
projects::{self, ProjectId},
reader, search,
sessions::{self, SessionId},
users,
virtual_branches::{self, target},
watcher,
};
@ -73,19 +76,19 @@ impl App {
.with_context(|| "failed to list projects")?
{
if let Err(error) = self.init_project(&project).await {
tracing::error!(project.id, ?error, "failed to init project");
tracing::error!(%project.id, ?error, "failed to init project");
}
}
Ok(())
}
pub fn get_project(&self, id: &str) -> Result<projects::Project, Error> {
pub fn get_project(&self, id: &ProjectId) -> Result<projects::Project, Error> {
self.projects.get(id).map_err(Error::GetProject)
}
pub fn list_sessions(
&self,
project_id: &str,
project_id: &ProjectId,
earliest_timestamp_ms: Option<u128>,
) -> Result<Vec<sessions::Session>> {
self.sessions_database
@ -94,8 +97,8 @@ impl App {
pub fn list_session_files(
&self,
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
paths: &Option<Vec<path::PathBuf>>,
) -> Result<HashMap<path::PathBuf, reader::Content>, Error> {
let session = self
@ -120,7 +123,7 @@ impl App {
.map_err(Error::Other)
}
pub fn mark_resolved(&self, project_id: &str, path: &str) -> Result<(), Error> {
pub fn mark_resolved(&self, project_id: &ProjectId, path: &str) -> Result<(), Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
// mark file as resolved
@ -128,7 +131,7 @@ impl App {
Ok(())
}
pub fn fetch_from_target(&self, project_id: &str) -> Result<(), Error> {
pub fn fetch_from_target(&self, project_id: &ProjectId) -> Result<(), Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
let user = self.users.get_user().context("failed to get user")?;
@ -197,7 +200,7 @@ impl App {
pub fn list_bookmarks(
&self,
project_id: &str,
project_id: &ProjectId,
range: Option<ops::Range<u128>>,
) -> Result<Vec<bookmarks::Bookmark>, Error> {
self.bookmarks_database
@ -207,8 +210,8 @@ impl App {
pub fn list_session_deltas(
&self,
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
paths: &Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<deltas::Delta>>, Error> {
self.deltas_database
@ -218,7 +221,7 @@ impl App {
pub fn git_wd_diff(
&self,
project_id: &str,
project_id: &ProjectId,
context_lines: u32,
) -> Result<HashMap<path::PathBuf, String>, Error> {
let project = self.projects.get(project_id)?;
@ -255,7 +258,7 @@ impl App {
pub fn git_remote_branches(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<git::RemoteBranchName>, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -266,7 +269,7 @@ impl App {
pub fn git_remote_branches_data(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<virtual_branches::RemoteBranch>, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -281,7 +284,7 @@ impl App {
.map_err(Error::Other)
}
pub fn git_head(&self, project_id: &str) -> Result<String, Error> {
pub fn git_head(&self, project_id: &ProjectId) -> Result<String, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
let head = project_repository
@ -311,7 +314,7 @@ impl App {
}
}
pub fn git_gb_push(&self, project_id: &str) -> Result<(), Error> {
pub fn git_gb_push(&self, project_id: &ProjectId) -> Result<(), Error> {
let user = self.users.get_user().context("failed to get user")?;
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;

View File

@ -3,7 +3,7 @@ use std::ops;
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::database;
use crate::{database, projects::ProjectId};
use super::Bookmark;
@ -27,7 +27,7 @@ impl From<&AppHandle> for Database {
impl Database {
fn get_by_project_id_timestamp_ms(
&self,
project_id: &str,
project_id: &ProjectId,
timestamp_ms: &u128,
) -> Result<Option<Bookmark>> {
self.database.transaction(|tx| {
@ -98,7 +98,7 @@ impl Database {
fn list_by_project_id_range(
&self,
project_id: &str,
project_id: &ProjectId,
range: ops::Range<u128>,
) -> Result<Vec<Bookmark>> {
self.database.transaction(|tx| {
@ -119,7 +119,7 @@ impl Database {
})
}
fn list_by_project_id_all(&self, project_id: &str) -> Result<Vec<Bookmark>> {
fn list_by_project_id_all(&self, project_id: &ProjectId) -> Result<Vec<Bookmark>> {
self.database.transaction(|tx| {
let mut stmt = list_by_project_id_stmt(tx)
.context("Failed to prepare list_by_project_id statement")?;
@ -136,7 +136,7 @@ impl Database {
pub fn list_by_project_id(
&self,
project_id: &str,
project_id: &ProjectId,
range: Option<ops::Range<u128>>,
) -> Result<Vec<Bookmark>> {
if let Some(range) = range {
@ -250,7 +250,7 @@ mod tests {
let database = Database::from(db);
let bookmark = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 123,
created_timestamp_ms: 0,
updated_timestamp_ms: 0,
@ -274,7 +274,7 @@ mod tests {
let database = Database::from(db);
let bookmark_one = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 123,
created_timestamp_ms: 0,
updated_timestamp_ms: 0,
@ -284,7 +284,7 @@ mod tests {
database.upsert(&bookmark_one)?;
let bookmark_two = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 456,
created_timestamp_ms: 0,
updated_timestamp_ms: 1,
@ -309,7 +309,7 @@ mod tests {
let database = Database::from(db);
let bookmark = Bookmark {
project_id: "project_id".to_string(),
project_id: ProjectId::generate(),
timestamp_ms: 123,
created_timestamp_ms: 0,
updated_timestamp_ms: 0,

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Bookmark {
pub project_id: String,
pub project_id: ProjectId,
pub timestamp_ms: u128,
pub created_timestamp_ms: u128,
pub updated_timestamp_ms: u128,
@ -18,3 +18,5 @@ pub struct Bookmark {
pub use database::Database;
pub use reader::BookmarksReader as Reader;
pub use writer::BookmarksWriter as Writer;
use crate::projects::ProjectId;

View File

@ -24,7 +24,7 @@ impl<'writer> BookmarksWriter<'writer> {
)?;
tracing::debug!(
project_id = self.repository.get_project_id(),
project_id = %self.repository.get_project_id(),
timestamp_ms = bookmark.timestamp_ms,
"wrote bookmark",
);

View File

@ -5,7 +5,11 @@ use tauri::Manager;
use tracing::instrument;
use crate::{
app, assets, bookmarks, deltas, error::Error, git, reader, search, sessions, virtual_branches,
app, assets, bookmarks, deltas,
error::{Code, Error},
git, reader, search,
sessions::{self, SessionId},
virtual_branches,
};
impl From<app::Error> for Error {
@ -50,7 +54,11 @@ pub async fn list_sessions(
earliest_timestamp_ms: Option<u128>,
) -> Result<Vec<sessions::Session>, Error> {
let app = handle.state::<app::App>();
let sessions = app.list_sessions(project_id, earliest_timestamp_ms)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let sessions = app.list_sessions(&project_id, earliest_timestamp_ms)?;
Ok(sessions)
}
@ -63,7 +71,15 @@ pub async fn list_session_files(
paths: Option<Vec<path::PathBuf>>,
) -> Result<HashMap<path::PathBuf, reader::Content>, Error> {
let app = handle.state::<app::App>();
let files = app.list_session_files(project_id, session_id, &paths)?;
let session_id: SessionId = session_id.parse().map_err(|_| Error::UserError {
message: "Malformed session id".to_string(),
code: Code::Sessions,
})?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let files = app.list_session_files(&project_id, &session_id, &paths)?;
Ok(files)
}
@ -76,7 +92,15 @@ pub async fn list_deltas(
paths: Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<deltas::Delta>>, Error> {
let app = handle.state::<app::App>();
let deltas = app.list_session_deltas(project_id, session_id, &paths)?;
let session_id = session_id.parse().map_err(|_| Error::UserError {
message: "Malformed session id".to_string(),
code: Code::Sessions,
})?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let deltas = app.list_session_deltas(&project_id, &session_id, &paths)?;
Ok(deltas)
}
@ -88,7 +112,11 @@ pub async fn git_wd_diff(
context_lines: u32,
) -> Result<HashMap<path::PathBuf, String>, Error> {
let app = handle.state::<app::App>();
let diff = app.git_wd_diff(project_id, context_lines)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let diff = app.git_wd_diff(&project_id, context_lines)?;
Ok(diff)
}
@ -99,7 +127,11 @@ pub async fn git_remote_branches(
project_id: &str,
) -> Result<Vec<git::RemoteBranchName>, Error> {
let app = handle.state::<app::App>();
let branches = app.git_remote_branches(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branches = app.git_remote_branches(&project_id)?;
Ok(branches)
}
@ -110,7 +142,11 @@ pub async fn git_remote_branches_data(
project_id: &str,
) -> Result<Vec<virtual_branches::RemoteBranch>, Error> {
let app = handle.state::<app::App>();
let branches = app.git_remote_branches_data(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branches = app.git_remote_branches_data(&project_id)?;
let branches = handle
.state::<assets::Proxy>()
.proxy_remote_branches(&branches)
@ -122,7 +158,11 @@ pub async fn git_remote_branches_data(
#[instrument(skip(handle))]
pub async fn git_head(handle: tauri::AppHandle, project_id: &str) -> Result<String, Error> {
let app = handle.state::<app::App>();
let head = app.git_head(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let head = app.git_head(&project_id)?;
Ok(head)
}
@ -148,6 +188,10 @@ pub async fn upsert_bookmark(
.elapsed()
.context("failed to get time")?
.as_millis();
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let bookmark = bookmarks::Bookmark {
project_id,
timestamp_ms: timestamp_ms
@ -170,7 +214,11 @@ pub async fn list_bookmarks(
range: Option<ops::Range<u128>>,
) -> Result<Vec<bookmarks::Bookmark>, Error> {
let app = handle.state::<app::App>();
let bookmarks = app.list_bookmarks(project_id, range)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let bookmarks = app.list_bookmarks(&project_id, range)?;
Ok(bookmarks)
}
@ -178,7 +226,11 @@ pub async fn list_bookmarks(
#[instrument(skip(handle))]
pub async fn fetch_from_target(handle: tauri::AppHandle, project_id: &str) -> Result<(), Error> {
let app = handle.state::<app::App>();
app.fetch_from_target(project_id)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
app.fetch_from_target(&project_id)?;
Ok(())
}
@ -190,7 +242,11 @@ pub async fn mark_resolved(
path: &str,
) -> Result<(), Error> {
let app = handle.state::<app::App>();
app.mark_resolved(project_id, path)?;
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
app.mark_resolved(&project_id, path)?;
Ok(())
}

View File

@ -3,7 +3,7 @@ use std::{collections::HashMap, path};
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::database;
use crate::{database, projects::ProjectId, sessions::SessionId};
use super::{delta, operations};
@ -27,8 +27,8 @@ impl From<&AppHandle> for Database {
impl Database {
pub fn insert(
&self,
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path: &path::Path,
deltas: &Vec<delta::Delta>,
) -> Result<()> {
@ -55,8 +55,8 @@ impl Database {
pub fn list_by_project_id_session_id(
&self,
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path_filter: &Option<Vec<&str>>,
) -> Result<HashMap<String, Vec<delta::Delta>>> {
self.database
@ -141,8 +141,8 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let session_id = "session_id";
let project_id = ProjectId::generate();
let session_id = SessionId::generate();
let file_path = path::PathBuf::from("file_path");
let delta1 = delta::Delta {
timestamp_ms: 0,
@ -150,10 +150,10 @@ mod tests {
};
let deltas = vec![delta1.clone()];
database.insert(project_id, session_id, &file_path, &deltas)?;
database.insert(&project_id, &session_id, &file_path, &deltas)?;
assert_eq!(
database.list_by_project_id_session_id(project_id, session_id, &None)?,
database.list_by_project_id_session_id(&project_id, &session_id, &None)?,
vec![(file_path.display().to_string(), vec![delta1])]
.into_iter()
.collect()
@ -167,8 +167,8 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let session_id = "session_id";
let project_id = ProjectId::generate();
let session_id = SessionId::generate();
let file_path = path::PathBuf::from("file_path");
let delta1 = delta::Delta {
timestamp_ms: 0,
@ -182,11 +182,11 @@ mod tests {
))],
};
database.insert(project_id, session_id, &file_path, &vec![delta1])?;
database.insert(project_id, session_id, &file_path, &vec![delta2.clone()])?;
database.insert(&project_id, &session_id, &file_path, &vec![delta1])?;
database.insert(&project_id, &session_id, &file_path, &vec![delta2.clone()])?;
assert_eq!(
database.list_by_project_id_session_id(project_id, session_id, &None)?,
database.list_by_project_id_session_id(&project_id, &session_id, &None)?,
vec![(file_path.display().to_string(), vec![delta2])]
.into_iter()
.collect()
@ -200,8 +200,8 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let session_id = "session_id";
let project_id = ProjectId::generate();
let session_id = SessionId::generate();
let file_path1 = path::PathBuf::from("file_path1");
let file_path2 = path::PathBuf::from("file_path2");
let delta1 = delta::Delta {
@ -216,12 +216,12 @@ mod tests {
))],
};
database.insert(project_id, session_id, &file_path1, &vec![delta1.clone()])?;
database.insert(project_id, session_id, &file_path2, &vec![delta1.clone()])?;
database.insert(project_id, session_id, &file_path2, &vec![delta2.clone()])?;
database.insert(&project_id, &session_id, &file_path1, &vec![delta1.clone()])?;
database.insert(&project_id, &session_id, &file_path2, &vec![delta1.clone()])?;
database.insert(&project_id, &session_id, &file_path2, &vec![delta2.clone()])?;
assert_eq!(
database.list_by_project_id_session_id(project_id, session_id, &None)?,
database.list_by_project_id_session_id(&project_id, &session_id, &None)?,
vec![
(file_path1.display().to_string(), vec![delta1.clone()]),
(file_path2.display().to_string(), vec![delta1, delta2])

View File

@ -30,7 +30,7 @@ impl<'writer> DeltasWriter<'writer> {
.write_string(&format!("session/deltas/{}", path.display()), &raw_deltas)?;
tracing::debug!(
project_id = self.repository.get_project_id(),
project_id = %self.repository.get_project_id(),
path = %path.display(),
"wrote deltas"
);
@ -48,7 +48,7 @@ impl<'writer> DeltasWriter<'writer> {
.write_string(&format!("session/wd/{}", path.display()), contents)?;
tracing::debug!(
project_id = self.repository.get_project_id(),
project_id = %self.repository.get_project_id(),
path = %path.display(),
"wrote session wd file"
);

View File

@ -5,6 +5,8 @@ use serde::{ser::SerializeMap, Serialize};
#[derive(Debug)]
pub enum Code {
Unknown,
Sessions,
Branches,
Projects,
ProjectGitAuth,
ProjectGitRemote,
@ -16,6 +18,8 @@ impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Code::Unknown => write!(f, "errors.unknown"),
Code::Branches => write!(f, "errors.branches"),
Code::Sessions => write!(f, "errors.sessions"),
Code::Projects => write!(f, "errors.projects"),
Code::ProjectGitAuth => write!(f, "errors.projects.git.auth"),
Code::ProjectGitRemote => write!(f, "errors.projects.git.remote"),

View File

@ -1,7 +1,12 @@
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{bookmarks, deltas, reader, sessions};
use crate::{
bookmarks, deltas,
projects::ProjectId,
reader,
sessions::{self, SessionId},
};
#[derive(Clone)]
pub struct Sender {
@ -30,7 +35,7 @@ impl Sender {
pub struct Event {
name: String,
payload: serde_json::Value,
project_id: String,
project_id: ProjectId,
}
impl Event {
@ -38,45 +43,45 @@ impl Event {
&self.name
}
pub fn project_id(&self) -> &str {
pub fn project_id(&self) -> &ProjectId {
&self.project_id
}
pub fn git_index(project_id: &str) -> Self {
pub fn git_index(project_id: &ProjectId) -> Self {
Event {
name: format!("project://{}/git/index", project_id),
payload: serde_json::json!({}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn git_fetch(project_id: &str) -> Self {
pub fn git_fetch(project_id: &ProjectId) -> Self {
Event {
name: format!("project://{}/git/fetch", project_id),
payload: serde_json::json!({}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn git_head(project_id: &str, head: &str) -> Self {
pub fn git_head(project_id: &ProjectId, head: &str) -> Self {
Event {
name: format!("project://{}/git/head", project_id),
payload: serde_json::json!({ "head": head }),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn git_activity(project_id: &str) -> Self {
pub fn git_activity(project_id: &ProjectId) -> Self {
Event {
name: format!("project://{}/git/activity", project_id),
payload: serde_json::json!({}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn file(
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path: &str,
contents: Option<&reader::Content>,
) -> Self {
@ -86,29 +91,29 @@ impl Event {
"filePath": file_path,
"contents": contents,
}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn session(project_id: &str, session: &sessions::Session) -> Self {
pub fn session(project_id: &ProjectId, session: &sessions::Session) -> Self {
Event {
name: format!("project://{}/sessions", project_id),
payload: serde_json::to_value(session).unwrap(),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn bookmark(project_id: &str, bookmark: &bookmarks::Bookmark) -> Self {
pub fn bookmark(project_id: &ProjectId, bookmark: &bookmarks::Bookmark) -> Self {
Event {
name: format!("project://{}/bookmarks", project_id),
payload: serde_json::to_value(bookmark).unwrap(),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
pub fn deltas(
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
deltas: &Vec<deltas::Delta>,
relative_file_path: &std::path::Path,
) -> Self {
@ -118,7 +123,7 @@ impl Event {
"deltas": deltas,
"filePath": relative_file_path,
}),
project_id: project_id.to_string(),
project_id: *project_id,
}
}
}

View File

@ -9,12 +9,13 @@ use std::{
use anyhow::{anyhow, Context, Ok, Result};
use filetime::FileTime;
use sha2::{Digest, Sha256};
use uuid::Uuid;
use crate::{
fs, git, lock,
paths::DataDir,
projects, users,
projects::{self, ProjectId},
sessions::SessionId,
users,
virtual_branches::{self, target},
};
@ -56,7 +57,7 @@ impl Repository {
let projects_dir = root.to_path_buf().join("projects");
let path = projects_dir.join(&project.id);
let path = projects_dir.join(project.id.to_string());
let lock_path = projects_dir.join(format!("{}.lock", project.id));
if path.exists() {
let git_repository = git::Repository::open(path.clone())
@ -95,7 +96,7 @@ impl Repository {
.migrate(project)
.context("failed to migrate")?
{
tracing::info!(project_id = gb_repository.project.id, "repository migrated");
tracing::info!(project_id = %gb_repository.project.id, "repository migrated");
return Result::Ok(gb_repository);
}
@ -111,7 +112,7 @@ impl Repository {
}
}
pub fn get_project_id(&self) -> &str {
pub fn get_project_id(&self) -> &ProjectId {
&self.project.id
}
@ -150,7 +151,7 @@ impl Repository {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.push_update_reference(move |refname, message| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
refname,
message,
"pulling reference"
@ -159,7 +160,7 @@ impl Repository {
});
callbacks.push_transfer_progress(move |one, two, three| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
"transferred {}/{}/{} objects",
one,
two,
@ -181,7 +182,7 @@ impl Repository {
))?;
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
remote = %remote.url()?.unwrap(),
"gb repo fetched",
);
@ -199,7 +200,7 @@ impl Repository {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.push_update_reference(move |refname, message| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
refname,
message,
"pushing reference"
@ -208,7 +209,7 @@ impl Repository {
});
callbacks.push_transfer_progress(move |one, two, three| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
"transferred {}/{}/{} objects",
one,
two,
@ -232,7 +233,7 @@ impl Repository {
remote.url()?.unwrap()
))?;
tracing::info!(project_id = self.project.id, remote = %remote.url()?.unwrap(), "gb repository pushed");
tracing::info!(project_id = %self.project.id, remote = %remote.url()?.unwrap(), "gb repository pushed");
Ok(())
}
@ -328,7 +329,7 @@ impl Repository {
};
let session = sessions::Session {
id: Uuid::new_v4().to_string(),
id: SessionId::generate(),
hash: None,
meta,
};
@ -339,8 +340,8 @@ impl Repository {
.context("failed to write session")?;
tracing::info!(
project_id = self.project.id,
session_id = session.id,
project_id = %self.project.id,
session_id = %session.id,
"created new session"
);
@ -461,8 +462,8 @@ impl Repository {
write_gb_commit(tree_id, self, user).context("failed to write gb commit")?;
tracing::info!(
project_id = self.project.id,
session_id = session.id,
project_id = %self.project.id,
session_id = %session.id,
%commit_oid,
"flushed session"
);
@ -543,7 +544,7 @@ impl Repository {
match reference {
Err(git::Error::NotFound(_)) => {
tracing::debug!(
project_id = project.id,
project_id = %project.id,
refname,
"reference not found, no migration"
);
@ -596,10 +597,9 @@ impl Repository {
}
}
fn flush_gitbutler_file(&self, session_id: &str) -> Result<()> {
fn flush_gitbutler_file(&self, session_id: &SessionId) -> Result<()> {
let gb_path = self.git_repository.path();
let project_id = self.project.id.as_str();
let project_id = self.project.id.to_string();
let gb_file_content = serde_json::json!({
"sessionId": session_id,
"repositoryId": project_id,
@ -651,7 +651,7 @@ fn build_wd_tree(
Result::Ok(reader::Content::UTF8(content)) => content,
Result::Ok(reader::Content::Large) => {
tracing::error!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %abs_path.display(),
"large file in session working directory"
);
@ -659,7 +659,7 @@ fn build_wd_tree(
}
Result::Ok(reader::Content::Binary) => {
tracing::error!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %abs_path.display(),
"binary file in session working directory"
);
@ -667,7 +667,7 @@ fn build_wd_tree(
}
Err(error) => {
tracing::error!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %abs_path.display(),
?error,
"failed to read file"
@ -836,7 +836,7 @@ fn add_wd_path(
// TODO: size limit should be configurable
let blob = if metadata.len() > 100_000_000 {
tracing::warn!(
project_id = gb_repository.project.id,
project_id = %gb_repository.project.id,
path = %file_path.display(),
"file too big"
);

View File

@ -5,7 +5,10 @@ use pretty_assertions::assert_eq;
use tempfile::tempdir;
use crate::{
deltas, projects, reader, sessions,
deltas,
projects::{self, ProjectId},
reader,
sessions::{self, SessionId},
test_utils::{Case, Suite},
};
@ -109,13 +112,11 @@ fn test_list_deltas_from_current_session() -> Result<()> {
assert_eq!(deltas.len(), 1);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0]
.operations
.len(),
deltas[&path::PathBuf::from("test.txt")][0].operations.len(),
1
);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0].operations[0],
deltas[&path::PathBuf::from("test.txt")][0].operations[0],
deltas::Operation::Insert((0, "Hello World".to_string()))
);
@ -146,13 +147,11 @@ fn test_list_deltas_from_flushed_session() -> Result<()> {
assert_eq!(deltas.len(), 1);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0]
.operations
.len(),
deltas[&path::PathBuf::from("test.txt")][0].operations.len(),
1
);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap()[0].operations[0],
deltas[&path::PathBuf::from("test.txt")][0].operations[0],
deltas::Operation::Insert((0, "Hello World".to_string()))
);
@ -172,8 +171,8 @@ fn test_list_files_from_current_session() -> Result<()> {
assert_eq!(files.len(), 1);
assert_eq!(
files.get(&path::PathBuf::from("test.txt")).unwrap(),
&reader::Content::UTF8("Hello World".to_string())
files[&path::PathBuf::from("test.txt")],
reader::Content::UTF8("Hello World".to_string())
);
Ok(())
@ -197,8 +196,8 @@ fn test_list_files_from_flushed_session() -> Result<()> {
assert_eq!(files.len(), 1);
assert_eq!(
files.get(&path::PathBuf::from("test.txt")).unwrap(),
&reader::Content::UTF8("Hello World".to_string())
files[&path::PathBuf::from("test.txt")],
reader::Content::UTF8("Hello World".to_string())
);
Ok(())
@ -213,8 +212,8 @@ fn test_remote_syncronization() -> Result<()> {
description: None,
repository_id: "123".to_string(),
git_url: cloud.path().to_str().unwrap().to_string(),
created_at: 0.to_string(),
updated_at: 0.to_string(),
created_at: 0_i32.to_string(),
updated_at: 0_i32.to_string(),
sync: true,
};
@ -227,7 +226,7 @@ fn test_remote_syncronization() -> Result<()> {
"Hello World",
)]));
suite.projects.update(&projects::UpdateRequest {
id: case_one.project.id.clone(),
id: case_one.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -250,7 +249,7 @@ fn test_remote_syncronization() -> Result<()> {
// create second local project, fetch it and make sure session is there
let case_two = suite.new_case();
suite.projects.update(&projects::UpdateRequest {
id: case_two.project.id.clone(),
id: case_two.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -262,7 +261,7 @@ fn test_remote_syncronization() -> Result<()> {
let sessions_two = case_two
.gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect::<Vec<_>>();
assert_eq!(sessions_two.len(), 1);
assert_eq!(sessions_two[0].id, session_one.id);
@ -274,12 +273,12 @@ fn test_remote_syncronization() -> Result<()> {
assert_eq!(deltas.len(), 1);
assert_eq!(files.len(), 1);
assert_eq!(
files.get(&path::PathBuf::from("test.txt")).unwrap(),
&reader::Content::UTF8("Hello World".to_string())
files[&path::PathBuf::from("test.txt")],
reader::Content::UTF8("Hello World".to_string())
);
assert_eq!(
deltas.get(&path::PathBuf::from("test.txt")).unwrap(),
&vec![deltas::Delta {
deltas[&path::PathBuf::from("test.txt")],
vec![deltas::Delta {
operations: vec![deltas::Operation::Insert((0, "Hello World".to_string()))],
timestamp_ms: 0,
}]
@ -297,8 +296,8 @@ fn test_remote_sync_order() -> Result<()> {
description: None,
repository_id: "123".to_string(),
git_url: cloud.path().to_str().unwrap().to_string(),
created_at: 0.to_string(),
updated_at: 0.to_string(),
created_at: 0_i32.to_string(),
updated_at: 0_i32.to_string(),
sync: true,
};
@ -306,7 +305,7 @@ fn test_remote_sync_order() -> Result<()> {
let case_one = suite.new_case();
suite.projects.update(&projects::UpdateRequest {
id: case_one.project.id.clone(),
id: case_one.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -314,7 +313,7 @@ fn test_remote_sync_order() -> Result<()> {
let case_two = suite.new_case();
suite.projects.update(&projects::UpdateRequest {
id: case_two.project.id.clone(),
id: case_two.project.id,
api: Some(api_project.clone()),
..Default::default()
})?;
@ -364,14 +363,14 @@ fn test_remote_sync_order() -> Result<()> {
let sessions_one = case_one
.gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect::<Vec<_>>();
case_two.gb_repository.fetch(Some(&user))?;
let sessions_two = case_two
.gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect::<Vec<_>>();
// make sure the sessions are the same on both repos
@ -401,12 +400,11 @@ fn test_gitbutler_file() -> Result<()> {
let file_content: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&gitbutler_file_path)?)?;
let sid: SessionId = file_content["sessionId"].as_str().unwrap().parse()?;
assert_eq!(sid, session.id);
assert_eq!(file_content["sessionId"], session.id);
assert_eq!(
file_content["repositoryId"],
project_repository.project().id
);
let pid: ProjectId = file_content["repositoryId"].as_str().unwrap().parse()?;
assert_eq!(pid, project_repository.project().id);
Ok(())
}

110
packages/tauri/src/id.rs Normal file
View File

@ -0,0 +1,110 @@
use std::{fmt, hash::Hash, marker::PhantomData, str};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;
pub struct Id<T>(Uuid, PhantomData<T>);
impl<T> Hash for Id<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T> PartialOrd for Id<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Id<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<T> Id<T> {
pub fn generate() -> Self {
Id(Uuid::new_v4(), PhantomData)
}
}
impl<T> Default for Id<T> {
fn default() -> Self {
Self::generate()
}
}
impl<T> rusqlite::types::FromSql for Id<T> {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
Uuid::parse_str(value.as_str()?)
.map(Into::into)
.map_err(|error| rusqlite::types::FromSqlError::Other(Box::new(error)))
}
}
impl<T> rusqlite::ToSql for Id<T> {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(self.0.to_string()))
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<T> Eq for Id<T> {}
impl<T> From<Uuid> for Id<T> {
fn from(value: Uuid) -> Self {
Self(value, PhantomData)
}
}
impl<'de, T> Deserialize<'de> for Id<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Uuid::deserialize(deserializer).map(Into::into)
}
}
impl<T> Serialize for Id<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> fmt::Display for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T> Copy for Id<T> {}
impl<T> str::FromStr for Id<T> {
type Err = uuid::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Uuid::parse_str(s).map(Into::into)
}
}

View File

@ -98,6 +98,7 @@ pub mod fs;
pub mod gb_repository;
pub mod git;
pub mod github;
pub mod id;
pub mod keys;
pub mod lock;
pub mod logs;

View File

@ -257,7 +257,7 @@ impl Repository {
) {
Ok(()) => {
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
remote = %branch.remote(),
%head,
branch = branch.branch(),
@ -266,7 +266,7 @@ impl Repository {
return Ok(());
}
Err(git::Error::AuthenticationFailed(error)) => {
tracing::error!(project_id = self.project.id, ?error, "git push failed",);
tracing::error!(project_id = %self.project.id, ?error, "git push failed",);
continue;
}
Err(error) => return Err(RemoteError::Other(error.into())),
@ -285,7 +285,7 @@ impl Repository {
remote_callbacks.push_update_reference(|refname, message| {
if let Some(msg) = message {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
refname,
msg,
"push update reference",
@ -295,7 +295,7 @@ impl Repository {
});
remote_callbacks.push_negotiation(|proposals| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
proposals = proposals
.iter()
.map(|p| format!(
@ -311,7 +311,7 @@ impl Repository {
});
remote_callbacks.push_transfer_progress(|one, two, three| {
tracing::debug!(
project_id = self.project.id,
project_id = %self.project.id,
"push transfer progress: {}/{}/{}",
one,
two,
@ -327,11 +327,11 @@ impl Repository {
match remote.fetch(&[refspec], Some(&mut fetch_opts)) {
Ok(()) => {
tracing::info!(project_id = self.project.id, %refspec, "git fetched");
tracing::info!(project_id = %self.project.id, %refspec, "git fetched");
return Ok(());
}
Err(git::Error::AuthenticationFailed(error)) => {
tracing::error!(project_id = self.project.id, ?error, "fetch failed");
tracing::error!(project_id = %self.project.id, ?error, "fetch failed");
continue;
}
Err(error) => return Err(RemoteError::Other(error.into())),
@ -373,7 +373,7 @@ impl Repository {
)?;
tracing::info!(
project_id = self.project.id,
project_id = %self.project.id,
%commit_oid,
message,
"created commit"

View File

@ -91,7 +91,11 @@ impl From<controller::GetError> for Error {
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn get_project(handle: tauri::AppHandle, id: &str) -> Result<projects::Project, Error> {
handle.state::<Controller>().get(id).map_err(Into::into)
let id = id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle.state::<Controller>().get(&id).map_err(Into::into)
}
impl From<controller::ListError> for Error {
@ -125,5 +129,9 @@ impl From<controller::DeleteError> for Error {
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> {
handle.state::<Controller>().delete(id).map_err(Into::into)
let id = id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle.state::<Controller>().delete(&id).map_err(Into::into)
}

View File

@ -6,7 +6,7 @@ use tauri::{AppHandle, Manager};
use crate::{paths::DataDir, watcher};
use super::{storage, storage::UpdateRequest, Project};
use super::{storage, storage::UpdateRequest, Project, ProjectId};
#[derive(Clone)]
pub struct Controller {
@ -65,7 +65,7 @@ impl Controller {
.map_or_else(|| id.clone(), |p| p.to_str().unwrap().to_string());
let project = Project {
id: uuid::Uuid::new_v4().to_string(),
id: ProjectId::generate(),
title,
path: path.to_path_buf(),
api: None,
@ -96,11 +96,11 @@ impl Controller {
if let Some(api) = &project.api {
if api.sync {
if let Err(error) = block_on(watchers.post(watcher::Event::FetchGitbutlerData(
project.id.clone(),
project.id,
time::SystemTime::now(),
))) {
tracing::error!(
project_id = &project.id,
project_id = %project.id,
?error,
"failed to post fetch project event"
);
@ -108,10 +108,10 @@ impl Controller {
}
if let Err(error) =
block_on(watchers.post(watcher::Event::PushGitbutlerData(project.id.clone())))
block_on(watchers.post(watcher::Event::PushGitbutlerData(project.id)))
{
tracing::error!(
project_id = &project.id,
project_id = %project.id,
?error,
"failed to post push project event"
);
@ -122,7 +122,7 @@ impl Controller {
Ok(updated)
}
pub fn get(&self, id: &str) -> Result<Project, GetError> {
pub fn get(&self, id: &ProjectId) -> Result<Project, GetError> {
self.projects_storage.get(id).map_err(|error| match error {
super::storage::Error::NotFound => GetError::NotFound,
error => GetError::Other(error.into()),
@ -135,7 +135,7 @@ impl Controller {
.map_err(|error| ListError::Other(error.into()))
}
pub fn delete(&self, id: &str) -> Result<(), DeleteError> {
pub fn delete(&self, id: &ProjectId) -> Result<(), DeleteError> {
let project = match self.projects_storage.get(id) {
Ok(project) => Ok(project),
Err(super::storage::Error::NotFound) => return Ok(()),
@ -145,7 +145,7 @@ impl Controller {
if let Some(watchers) = &self.watchers {
if let Err(error) = block_on(watchers.stop(id)) {
tracing::error!(
project_id = id,
project_id = %id,
?error,
"failed to stop watcher for project",
);
@ -160,9 +160,9 @@ impl Controller {
self.local_data_dir
.to_path_buf()
.join("projects")
.join(&project.id),
.join(project.id.to_string()),
) {
tracing::error!(project_id = id, ?error, "failed to remove project data",);
tracing::error!(project_id = %id, ?error, "failed to remove project data",);
}
Ok(())

View File

@ -4,5 +4,5 @@ mod project;
mod storage;
pub use controller::*;
pub use project::{ApiProject, AuthKey, FetchResult, Project};
pub use project::{ApiProject, AuthKey, FetchResult, Project, ProjectId};
pub use storage::UpdateRequest;

View File

@ -3,6 +3,8 @@ use std::{path, time};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::id::Id;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AuthKey {
@ -78,9 +80,11 @@ impl FetchResult {
}
}
pub type ProjectId = Id<Project>;
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Project {
pub id: String,
pub id: ProjectId,
pub title: String,
pub description: Option<String>,
pub path: path::PathBuf,

View File

@ -1,7 +1,11 @@
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Manager};
use crate::{paths::DataDir, projects::project, storage};
use crate::{
paths::DataDir,
projects::{project, ProjectId},
storage,
};
const PROJECTS_FILE: &str = "projects.json";
@ -32,7 +36,7 @@ impl From<&AppHandle> for Storage {
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct UpdateRequest {
pub id: String,
pub id: ProjectId,
pub title: Option<String>,
pub description: Option<String>,
pub api: Option<project::ApiProject>,
@ -74,9 +78,9 @@ impl Storage {
}
}
pub fn get(&self, id: &str) -> Result<project::Project, Error> {
pub fn get(&self, id: &ProjectId) -> Result<project::Project, Error> {
let projects = self.list()?;
match projects.into_iter().find(|p| p.id == id) {
match projects.into_iter().find(|p| p.id == *id) {
Some(project) => Ok(project),
None => Err(Error::NotFound),
}
@ -125,9 +129,9 @@ impl Storage {
.clone())
}
pub fn purge(&self, id: &str) -> Result<(), Error> {
pub fn purge(&self, id: &ProjectId) -> Result<(), Error> {
let mut projects = self.list()?;
if let Some(index) = projects.iter().position(|p| p.id == id) {
if let Some(index) = projects.iter().position(|p| p.id == *id) {
projects.remove(index);
self.storage
.write(PROJECTS_FILE, &serde_json::to_string_pretty(&projects)?)?;

View File

@ -3,14 +3,16 @@ use tantivy::{
Document,
};
use crate::{projects::ProjectId, sessions::SessionId};
#[derive(Debug, Default)]
pub struct IndexDocument {
pub version: u64,
pub timestamp_ms: Option<u64>,
pub index: Option<u64>,
pub id: String,
pub project_id: Option<String>,
pub session_id: Option<String>,
pub project_id: Option<ProjectId>,
pub session_id: Option<SessionId>,
pub file_path: Option<String>,
pub diff: Option<String>,
pub note: Option<String>,
@ -67,10 +69,10 @@ impl IndexDocument {
.to_string();
let project_id = doc
.get_first(schema.get_field("project_id").unwrap())
.map(|v| v.as_text().unwrap().to_string());
.map(|v| v.as_text().unwrap().parse().unwrap());
let session_id = doc
.get_first(schema.get_field("session_id").unwrap())
.map(|v| v.as_text().unwrap().to_string());
.map(|v| v.as_text().unwrap().parse().unwrap());
let file_path = doc
.get_first(schema.get_field("file_path").unwrap())
.map(|v| v.as_text().unwrap().to_string());

View File

@ -2,7 +2,7 @@ use std::path;
use anyhow::Result;
use crate::{paths::DataDir, storage};
use crate::{paths::DataDir, projects::ProjectId, sessions::SessionId, storage};
use super::index;
@ -29,12 +29,12 @@ impl Storage {
Ok(())
}
pub fn get(&self, project_id: &str, session_hash: &str) -> Result<Option<u64>> {
pub fn get(&self, project_id: &ProjectId, session_id: &SessionId) -> Result<Option<u64>> {
let filepath = path::Path::new("indexes")
.join(format!("v{}", index::VERSION))
.join("meta")
.join(project_id)
.join(session_hash);
.join(project_id.to_string())
.join(session_id.to_string());
let meta = match self.storage.read(filepath.to_str().unwrap())? {
None => None,
Some(meta) => meta.parse::<u64>().ok(),
@ -42,12 +42,12 @@ impl Storage {
Ok(meta)
}
pub fn set(&self, project_id: &str, session_hash: &str, version: u64) -> Result<()> {
pub fn set(&self, project_id: &ProjectId, session_id: &SessionId, version: u64) -> Result<()> {
let filepath = path::Path::new("indexes")
.join(format!("v{}", index::VERSION))
.join("meta")
.join(project_id)
.join(session_hash);
.join(project_id.to_string())
.join(session_id.to_string());
self.storage.write(filepath, &version.to_string())?;
Ok(())
}

View File

@ -1,4 +1,5 @@
use std::{
cmp::Ordering,
fs, path,
sync::{Arc, RwLock},
time, vec,
@ -7,14 +8,19 @@ use std::{
use anyhow::{Context, Result};
use serde::Serialize;
use similar::{ChangeTag, TextDiff};
use std::cmp::Ordering;
use tantivy::query::TermQuery;
use tantivy::{collector, directory::MmapDirectory, IndexWriter};
use tantivy::{query::QueryParser, Term};
use tantivy::{schema::IndexRecordOption, tokenizer};
use tauri::AppHandle;
use crate::{bookmarks, deltas, gb_repository, paths::DataDir, reader, sessions};
use crate::{
bookmarks, deltas, gb_repository,
paths::DataDir,
projects::ProjectId,
reader,
sessions::{self, SessionId},
};
use super::{index, meta};
@ -241,7 +247,7 @@ impl SearcherInner {
self.reader.reload()?;
tracing::debug!(
project_id = bookmark.project_id,
project_id = %bookmark.project_id,
timestamp_ms = bookmark.timestamp_ms,
"bookmark added to search",
);
@ -279,8 +285,8 @@ impl SearcherInner {
.set(repository.get_project_id(), &session.id, index::VERSION)?;
tracing::debug!(
project_id = repository.get_project_id(),
session_id = session.id,
project_id = %repository.get_project_id(),
session_id = %session.id,
"session added to search",
);
@ -288,7 +294,7 @@ impl SearcherInner {
}
}
fn build_id(project_id: &str, timestamp_ms: &u128) -> String {
fn build_id(project_id: &ProjectId, timestamp_ms: &u128) -> String {
format!("{}-{}-{}", index::VERSION, project_id, timestamp_ms)
}
@ -297,8 +303,8 @@ const WRITE_BUFFER_SIZE: usize = 10_000_000; // 10MB
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchResult {
pub project_id: String,
pub session_id: String,
pub project_id: ProjectId,
pub session_id: SessionId,
pub file_path: String,
pub index: u64,
}
@ -389,8 +395,8 @@ fn index_delta(
index: &tantivy::Index,
writer: &mut IndexWriter,
reader: &tantivy::IndexReader,
session_id: &str,
project_id: &str,
session_id: &SessionId,
project_id: &ProjectId,
file_text: &mut Vec<char>,
file_path: &path::Path,
i: usize,
@ -439,9 +445,9 @@ fn index_delta(
.join(" ");
doc.index = Some(i.try_into()?);
doc.session_id = Some(session_id.to_string());
doc.session_id = Some(*session_id);
doc.file_path = Some(file_path.display().to_string());
doc.project_id = Some(project_id.to_string());
doc.project_id = Some(*project_id);
doc.timestamp_ms = Some(delta.timestamp_ms.try_into()?);
doc.diff = Some(changes);

View File

@ -4,6 +4,7 @@ use anyhow::Result;
use crate::{
bookmarks, deltas,
projects::ProjectId,
test_utils::{Case, Suite},
};
@ -66,7 +67,7 @@ fn search_by_bookmark_note() -> Result<()> {
Path::new("test.txt"),
&vec![deltas::Delta {
operations: vec![deltas::Operation::Insert((0, "Hello".to_string()))],
timestamp_ms: 123456,
timestamp_ms: 123_456,
}],
)?;
let session = gb_repository.flush(&project_repository, None)?.unwrap();
@ -75,8 +76,8 @@ fn search_by_bookmark_note() -> Result<()> {
// first we index bookmark
searcher.index_bookmark(&bookmarks::Bookmark {
project_id: gb_repository.get_project_id().to_string(),
timestamp_ms: 123456,
project_id: *gb_repository.get_project_id(),
timestamp_ms: 123_456,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
note: "bookmark note".to_string(),
@ -114,8 +115,8 @@ fn search_by_bookmark_note() -> Result<()> {
// then update the note
searcher.index_bookmark(&bookmarks::Bookmark {
project_id: gb_repository.get_project_id().to_string(),
timestamp_ms: 123456,
project_id: *gb_repository.get_project_id(),
timestamp_ms: 123_456,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
note: "updated bookmark note".to_string(),
@ -218,7 +219,7 @@ fn search_by_diff() -> Result<()> {
})?;
assert_eq!(result.total, 1);
assert_eq!(result.page[0].session_id, session.id);
assert_eq!(result.page[0].project_id, gb_repository.get_project_id());
assert_eq!(result.page[0].project_id, *gb_repository.get_project_id());
assert_eq!(result.page[0].file_path, "test.txt");
assert_eq!(result.page[0].index, 1);
@ -229,11 +230,12 @@ fn search_by_diff() -> Result<()> {
fn should_index_bookmark_once() -> Result<()> {
let suite = Suite::default();
let searcher = super::Searcher::try_from(&suite.local_app_data).unwrap();
let project_id = ProjectId::generate();
// should not index deleted non-existing bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -245,7 +247,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should index new non deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -257,7 +259,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should not index existing non deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -269,7 +271,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should index existing deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -281,7 +283,7 @@ fn should_index_bookmark_once() -> Result<()> {
// should not index existing deleted bookmark
assert!(searcher
.index_bookmark(&bookmarks::Bookmark {
project_id: "test".to_string(),
project_id,
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -361,7 +363,7 @@ fn search_bookmark_by_phrase() -> Result<()> {
searcher.index_session(&gb_repository, &session)?;
searcher.index_bookmark(&bookmarks::Bookmark {
project_id: gb_repository.get_project_id().to_string(),
project_id: *gb_repository.get_project_id(),
timestamp_ms: 0,
created_timestamp_ms: 0,
updated_timestamp_ms: time::UNIX_EPOCH.elapsed()?.as_millis(),
@ -428,7 +430,7 @@ fn search_by_filename() -> Result<()> {
.page;
assert_eq!(found_result.len(), 2);
assert_eq!(found_result[0].session_id, session.id);
assert_eq!(found_result[0].project_id, gb_repository.get_project_id());
assert_eq!(found_result[0].project_id, *gb_repository.get_project_id());
assert_eq!(found_result[0].file_path, "test.txt");
let not_found_result = searcher.search(&super::Query {

View File

@ -1,9 +1,9 @@
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::database;
use crate::{database, projects::ProjectId};
use super::session;
use super::session::{self, SessionId};
#[derive(Clone)]
pub struct Database {
@ -25,7 +25,7 @@ impl From<&AppHandle> for Database {
}
impl Database {
pub fn insert(&self, project_id: &str, sessions: &[&session::Session]) -> Result<()> {
pub fn insert(&self, project_id: &ProjectId, sessions: &[&session::Session]) -> Result<()> {
self.database.transaction(|tx| -> Result<()> {
let mut stmt = insert_stmt(tx).context("Failed to prepare insert statement")?;
for session in sessions {
@ -48,7 +48,7 @@ impl Database {
pub fn list_by_project_id(
&self,
project_id: &str,
project_id: &ProjectId,
earliest_timestamp_ms: Option<u128>,
) -> Result<Vec<session::Session>> {
self.database.transaction(|tx| {
@ -81,8 +81,8 @@ impl Database {
pub fn get_by_project_id_id(
&self,
project_id: &str,
id: &str,
project_id: &ProjectId,
id: &SessionId,
) -> Result<Option<session::Session>> {
self.database.transaction(|tx| {
let mut stmt = get_by_project_id_id_stmt(tx)
@ -104,7 +104,7 @@ impl Database {
})
}
pub fn get_by_id(&self, id: &str) -> Result<Option<session::Session>> {
pub fn get_by_id(&self, id: &SessionId) -> Result<Option<session::Session>> {
self.database.transaction(|tx| {
let mut stmt = get_by_id_stmt(tx).context("Failed to prepare get_by_id statement")?;
let mut rows = stmt
@ -199,9 +199,9 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let project_id = ProjectId::generate();
let session1 = session::Session {
id: "id1".to_string(),
id: SessionId::generate(),
hash: None,
meta: session::Meta {
branch: None,
@ -211,7 +211,7 @@ mod tests {
},
};
let session2 = session::Session {
id: "id2".to_string(),
id: SessionId::generate(),
hash: Some("hash2".to_string()),
meta: session::Meta {
branch: Some("branch2".to_string()),
@ -222,15 +222,15 @@ mod tests {
};
let sessions = vec![&session1, &session2];
database.insert(project_id, &sessions)?;
database.insert(&project_id, &sessions)?;
assert_eq!(
database.list_by_project_id(project_id, None)?,
database.list_by_project_id(&project_id, None)?,
vec![session2.clone(), session1.clone()]
);
assert_eq!(database.get_by_id("id1")?.unwrap(), session1);
assert_eq!(database.get_by_id("id2")?.unwrap(), session2);
assert_eq!(database.get_by_id("id3")?, None);
assert_eq!(database.get_by_id(&session1.id)?.unwrap(), session1);
assert_eq!(database.get_by_id(&session2.id)?.unwrap(), session2);
assert_eq!(database.get_by_id(&SessionId::generate())?, None);
Ok(())
}
@ -240,9 +240,9 @@ mod tests {
let db = test_utils::test_database();
let database = Database::from(db);
let project_id = "project_id";
let session1 = session::Session {
id: "id1".to_string(),
let project_id = ProjectId::generate();
let session = session::Session {
id: SessionId::generate(),
hash: None,
meta: session::Meta {
branch: None,
@ -252,7 +252,7 @@ mod tests {
},
};
let session_updated = session::Session {
id: "id1".to_string(),
id: session.id,
hash: Some("hash2".to_string()),
meta: session::Meta {
branch: Some("branch2".to_string()),
@ -261,14 +261,14 @@ mod tests {
last_timestamp_ms: 4,
},
};
database.insert(project_id, &[&session1])?;
database.insert(project_id, &[&session_updated])?;
database.insert(&project_id, &[&session])?;
database.insert(&project_id, &[&session_updated])?;
assert_eq!(
database.list_by_project_id(project_id, None)?,
database.list_by_project_id(&project_id, None)?,
vec![session_updated.clone()]
);
assert_eq!(database.get_by_id("id1")?.unwrap(), session_updated);
assert_eq!(database.get_by_id(&session.id)?.unwrap(), session_updated);
Ok(())
}

View File

@ -10,5 +10,5 @@ mod tests;
pub use database::Database;
pub use iterator::SessionsIterator;
pub use reader::SessionReader as Reader;
pub use session::{Meta, Session, SessionError};
pub use session::{Meta, Session, SessionError, SessionId};
pub use writer::SessionWriter as Writer;

View File

@ -41,7 +41,7 @@ impl<'reader> SessionReader<'reader> {
if let Ok(reader::Content::UTF8(current_session_id)) =
wd_reader.read(&repository.session_path().join("meta").join("id"))
{
if current_session_id == session.id {
if current_session_id == session.id.to_string() {
let head_commit = repository.git_repository.head()?.peel_to_commit()?;
return Ok(SessionReader {
reader: Box::new(wd_reader),

View File

@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use serde::Serialize;
use thiserror::Error;
use crate::reader;
use crate::{id::Id, reader};
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
@ -19,10 +19,12 @@ pub struct Meta {
pub commit: Option<String>,
}
pub type SessionId = Id<Session>;
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Session {
pub id: String,
pub id: SessionId,
// if hash is not set, the session is not saved aka current
pub hash: Option<String>,
pub meta: Meta,
@ -51,6 +53,11 @@ impl TryFrom<&dyn reader::Reader> for Session {
.try_into()
.context("failed to parse session id")
.map_err(SessionError::Err)?;
let id: SessionId = id
.parse()
.context("failed to parse session id")
.map_err(SessionError::Err)?;
let start_timestamp_ms = reader
.read(path::Path::new("session/meta/start"))
.context("failed to read session start timestamp")

View File

@ -1,18 +1,18 @@
use anyhow::Result;
use crate::{
sessions,
sessions::{self, session::SessionId},
test_utils::{Case, Suite},
};
use super::Writer;
#[test]
fn test_should_not_write_session_with_hash() -> Result<()> {
fn test_should_not_write_session_with_hash() {
let Case { gb_repository, .. } = Suite::default().new_case();
let session = sessions::Session {
id: "session_id".to_string(),
id: SessionId::generate(),
hash: Some("hash".to_string()),
meta: sessions::Meta {
start_timestamp_ms: 0,
@ -23,8 +23,6 @@ fn test_should_not_write_session_with_hash() -> Result<()> {
};
assert!(Writer::new(&gb_repository).write(&session).is_err());
Ok(())
}
#[test]
@ -32,7 +30,7 @@ fn test_should_write_full_session() -> Result<()> {
let Case { gb_repository, .. } = Suite::default().new_case();
let session = sessions::Session {
id: "session_id".to_string(),
id: SessionId::generate(),
hash: None,
meta: sessions::Meta {
start_timestamp_ms: 0,
@ -46,7 +44,7 @@ fn test_should_write_full_session() -> Result<()> {
assert_eq!(
std::fs::read_to_string(gb_repository.session_path().join("meta/id"))?,
"session_id"
session.id.to_string()
);
assert_eq!(
std::fs::read_to_string(gb_repository.session_path().join("meta/commit"))?,
@ -73,7 +71,7 @@ fn test_should_write_partial_session() -> Result<()> {
let Case { gb_repository, .. } = Suite::default().new_case();
let session = sessions::Session {
id: "session_id".to_string(),
id: SessionId::generate(),
hash: None,
meta: sessions::Meta {
start_timestamp_ms: 0,
@ -87,7 +85,7 @@ fn test_should_write_partial_session() -> Result<()> {
assert_eq!(
std::fs::read_to_string(gb_repository.session_path().join("meta/id"))?,
"session_id"
session.id.to_string()
);
assert!(!gb_repository.session_path().join("meta/commit").exists());
assert!(!gb_repository.session_path().join("meta/branch").exists());

View File

@ -36,7 +36,9 @@ impl<'writer> SessionWriter<'writer> {
None
};
if current_session_id.is_some() && current_session_id.as_ref() != Some(&session.id) {
if current_session_id.is_some()
&& current_session_id.as_ref() != Some(&session.id.to_string())
{
return Err(anyhow!(
"{}: can not open writer for {} because a writer for {} is still open",
self.repository.get_project_id(),
@ -57,7 +59,9 @@ impl<'writer> SessionWriter<'writer> {
)
.context("failed to write last timestamp")?;
if current_session_id.is_some() && current_session_id.as_ref() == Some(&session.id) {
if current_session_id.is_some()
&& current_session_id.as_ref() == Some(&session.id.to_string())
{
return Ok(());
}
@ -69,7 +73,7 @@ impl<'writer> SessionWriter<'writer> {
.join("id")
.to_str()
.unwrap(),
session.id.as_str(),
&session.id.to_string(),
)
.context("failed to write id")?;

View File

@ -2,7 +2,6 @@ use std::time;
use anyhow::{bail, Context, Result};
use serde::Serialize;
use uuid::Uuid;
use crate::{
gb_repository,
@ -11,7 +10,7 @@ use crate::{
reader, sessions, users,
};
use super::{branch, delete_branch, iterator, target, RemoteCommit};
use super::{branch, delete_branch, iterator, target, BranchId, RemoteCommit};
#[derive(Debug, Serialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
@ -540,9 +539,8 @@ pub fn create_virtual_branch_from_branch(
}
};
let branch_id = Uuid::new_v4().to_string();
let mut branch = branch::Branch {
id: branch_id.clone(),
id: BranchId::generate(),
name: upstream.branch().to_string(),
notes: String::new(),
applied: applied.unwrap_or(false),

View File

@ -16,7 +16,9 @@ use serde::{Deserialize, Serialize};
use anyhow::Result;
use crate::git;
use crate::{git, id::Id};
pub type BranchId = Id<Branch>;
// this is the struct for the virtual branch data that is stored in our data
// store. it is more or less equivalent to a git branch reference, but it is not
@ -24,7 +26,7 @@ use crate::git;
// session storage under the branches/ directory.
#[derive(Debug, PartialEq, Clone)]
pub struct Branch {
pub id: String,
pub id: BranchId,
pub name: String,
pub notes: String,
pub applied: bool,
@ -43,7 +45,7 @@ pub struct Branch {
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct BranchUpdateRequest {
pub id: String,
pub id: BranchId,
pub name: Option<String>,
pub notes: Option<String>,
pub ownership: Option<Ownership>,
@ -63,6 +65,12 @@ impl TryFrom<&dyn crate::reader::Reader> for Branch {
fn try_from(reader: &dyn crate::reader::Reader) -> Result<Self, Self::Error> {
let id: String = reader.read(&path::PathBuf::from("id"))?.try_into()?;
let id: BranchId = id.parse().map_err(|e| {
crate::reader::Error::Io(std::io::Error::new(
std::io::ErrorKind::Other,
format!("id: {}", e),
))
})?;
let name: String = reader.read(&path::PathBuf::from("meta/name"))?.try_into()?;
let notes: String = match reader.read(&path::PathBuf::from("meta/notes")) {

View File

@ -2,7 +2,7 @@ use std::path;
use crate::reader::{self, Reader, SubReader};
use super::Branch;
use super::{Branch, BranchId};
pub struct BranchReader<'reader> {
reader: &'reader dyn reader::Reader,
@ -17,7 +17,7 @@ impl<'reader> BranchReader<'reader> {
self.reader
}
pub fn read(&self, id: &str) -> Result<Branch, reader::Error> {
pub fn read(&self, id: &BranchId) -> Result<Branch, reader::Error> {
if !self
.reader
.exists(&path::PathBuf::from(format!("branches/{}", id)))
@ -52,7 +52,7 @@ mod tests {
TEST_INDEX.fetch_add(1, Ordering::Relaxed);
Branch {
id: format!("branch_{}", TEST_INDEX.load(Ordering::Relaxed)),
id: BranchId::generate(),
name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)),
notes: "".to_string(),
applied: true,
@ -103,7 +103,7 @@ mod tests {
let session_reader = sessions::Reader::open(&gb_repository, &session)?;
let reader = BranchReader::new(&session_reader);
let result = reader.read("not found");
let result = reader.read(&BranchId::generate());
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "file not found");

View File

@ -34,7 +34,10 @@ impl<'writer> BranchWriter<'writer> {
let _lock = self.repository.lock();
self.writer
.write_string(&format!("branches/{}/id", branch.id), &branch.id)
.write_string(
&format!("branches/{}/id", branch.id),
&branch.id.to_string(),
)
.context("Failed to write branch id")?;
self.writer
@ -121,6 +124,8 @@ mod tests {
virtual_branches::branch,
};
use self::branch::BranchId;
use super::*;
static TEST_INDEX: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));
@ -129,7 +134,7 @@ mod tests {
TEST_INDEX.fetch_add(1, Ordering::Relaxed);
Branch {
id: format!("branch_{}", TEST_INDEX.load(Ordering::Relaxed)),
id: BranchId::generate(),
name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)),
notes: "".to_string(),
applied: true,
@ -175,7 +180,10 @@ mod tests {
let writer = BranchWriter::new(&gb_repository);
writer.write(&branch)?;
let root = gb_repository.root().join("branches").join(&branch.id);
let root = gb_repository
.root()
.join("branches")
.join(branch.id.to_string());
assert_eq!(
fs::read_to_string(root.join("meta").join("name").to_str().unwrap())
@ -259,7 +267,10 @@ mod tests {
writer.write(&updated_branch)?;
let root = gb_repository.root().join("branches").join(&branch.id);
let root = gb_repository
.root()
.join("branches")
.join(branch.id.to_string());
assert_eq!(
fs::read_to_string(root.join("meta").join("name").to_str().unwrap())

View File

@ -2,10 +2,14 @@ use anyhow::Context;
use tauri::{AppHandle, Manager};
use tracing::instrument;
use crate::{assets, error::Error, git};
use crate::{
assets,
error::{Code, Error},
git,
};
use super::{
branch::Ownership,
branch::{BranchId, Ownership},
controller::{self, Controller},
RemoteBranchFile,
};
@ -38,9 +42,17 @@ pub async fn commit_virtual_branch(
message: &str,
ownership: Option<Ownership>,
) -> Result<git::Oid, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.create_commit(project_id, branch, message, ownership.as_ref())
.create_commit(&project_id, &branch_id, message, ownership.as_ref())
.await
.map_err(Into::into)
}
@ -51,9 +63,13 @@ pub async fn list_virtual_branches(
handle: AppHandle,
project_id: &str,
) -> Result<Vec<super::VirtualBranch>, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.list_virtual_branches(project_id)
.list_virtual_branches(&project_id)
.await
.map_err(Into::into)
}
@ -64,10 +80,14 @@ pub async fn create_virtual_branch(
handle: AppHandle,
project_id: &str,
branch: super::branch::BranchCreateRequest,
) -> Result<String, Error> {
) -> Result<BranchId, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.create_virtual_branch(project_id, &branch)
.create_virtual_branch(&project_id, &branch)
.await
.map_err(Into::into)
}
@ -78,10 +98,14 @@ pub async fn create_virtual_branch_from_branch(
handle: AppHandle,
project_id: &str,
branch: git::BranchName,
) -> Result<String, Error> {
) -> Result<BranchId, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.create_virtual_branch_from_branch(project_id, &branch)
.create_virtual_branch_from_branch(&project_id, &branch)
.await
.map_err(Into::into)
}
@ -93,9 +117,17 @@ pub async fn merge_virtual_branch_upstream(
project_id: &str,
branch: &str,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.merge_virtual_branch_upstream(project_id, branch)
.merge_virtual_branch_upstream(&project_id, &branch_id)
.await
.map_err(Into::into)
}
@ -106,9 +138,13 @@ pub async fn get_base_branch_data(
handle: AppHandle,
project_id: &str,
) -> Result<Option<super::BaseBranch>, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
if let Some(base_branch) = handle
.state::<Controller>()
.get_base_branch_data(project_id)?
.get_base_branch_data(&project_id)?
{
let proxy = handle.state::<assets::Proxy>();
let base_branch = proxy.proxy_base_branch(&base_branch).await;
@ -125,12 +161,16 @@ pub async fn set_base_branch(
project_id: &str,
branch: &str,
) -> Result<super::BaseBranch, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_name = format!("refs/remotes/{}", branch)
.parse()
.context("Invalid branch name")?;
let base_branch = handle
.state::<Controller>()
.set_base_branch(project_id, &branch_name)?;
.set_base_branch(&project_id, &branch_name)?;
let base_branch = handle
.state::<assets::Proxy>()
.proxy_base_branch(&base_branch)
@ -141,9 +181,13 @@ pub async fn set_base_branch(
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn update_base_branch(handle: AppHandle, project_id: &str) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle
.state::<Controller>()
.update_base_branch(project_id)
.update_base_branch(&project_id)
.await
.map_err(Into::into)
}
@ -155,9 +199,13 @@ pub async fn update_virtual_branch(
project_id: &str,
branch: super::branch::BranchUpdateRequest,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.update_virtual_branch(project_id, branch)
.update_virtual_branch(&project_id, branch)
.await
.map_err(Into::into)
}
@ -169,9 +217,17 @@ pub async fn delete_virtual_branch(
project_id: &str,
branch_id: &str,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch_id.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.delete_virtual_branch(project_id, branch_id)
.delete_virtual_branch(&project_id, &branch_id)
.await
.map_err(Into::into)
}
@ -179,9 +235,17 @@ pub async fn delete_virtual_branch(
#[tauri::command(async)]
#[instrument(skip(handle))]
pub async fn apply_branch(handle: AppHandle, project_id: &str, branch: &str) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.apply_virtual_branch(project_id, branch)
.apply_virtual_branch(&project_id, &branch_id)
.await
.map_err(Into::into)
}
@ -193,9 +257,17 @@ pub async fn unapply_branch(
project_id: &str,
branch: &str,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.unapply_virtual_branch(project_id, branch)
.unapply_virtual_branch(&project_id, &branch_id)
.await
.map_err(Into::into)
}
@ -207,9 +279,13 @@ pub async fn unapply_ownership(
project_id: &str,
ownership: Ownership,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.unapply_ownership(project_id, &ownership)
.unapply_ownership(&project_id, &ownership)
.await
.map_err(Into::into)
}
@ -222,9 +298,17 @@ pub async fn push_virtual_branch(
branch_id: &str,
with_force: bool,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch_id.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.push_virtual_branch(project_id, branch_id, with_force)
.push_virtual_branch(&project_id, &branch_id, with_force)
.await
.map_err(Into::into)
}
@ -236,9 +320,17 @@ pub async fn can_apply_virtual_branch(
project_id: &str,
branch_id: &str,
) -> Result<bool, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch_id.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.can_apply_virtual_branch(project_id, branch_id)
.can_apply_virtual_branch(&project_id, &branch_id)
.map_err(Into::into)
}
@ -249,9 +341,13 @@ pub async fn can_apply_remote_branch(
project_id: &str,
branch: git::BranchName,
) -> Result<bool, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.can_apply_remote_branch(project_id, &branch)
.can_apply_remote_branch(&project_id, &branch)
.map_err(Into::into)
}
@ -262,9 +358,13 @@ pub async fn list_remote_commit_files(
project_id: &str,
commit_oid: git::Oid,
) -> Result<Vec<RemoteBranchFile>, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
handle
.state::<Controller>()
.list_remote_commit_files(project_id, commit_oid)
.list_remote_commit_files(&project_id, commit_oid)
.map_err(Into::into)
}
@ -276,9 +376,17 @@ pub async fn reset_virtual_branch(
branch_id: &str,
target_commit_oid: git::Oid,
) -> Result<(), Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".to_string(),
})?;
let branch_id = branch_id.parse().map_err(|_| Error::UserError {
code: Code::Branches,
message: "Malformed branch id".to_string(),
})?;
handle
.state::<Controller>()
.reset_virtual_branch(project_id, branch_id, target_commit_oid)
.reset_virtual_branch(&project_id, &branch_id, target_commit_oid)
.await
.map_err(Into::into)
}

View File

@ -8,10 +8,14 @@ use crate::{
gb_repository, git, keys,
paths::DataDir,
project_repository::{self, conflicts},
projects, users,
projects::{self, ProjectId},
users,
};
use super::{branch::Ownership, RemoteBranchFile};
use super::{
branch::{BranchId, Ownership},
RemoteBranchFile,
};
pub struct Controller {
local_data_dir: DataDir,
@ -84,8 +88,8 @@ impl Controller {
pub async fn create_commit(
&self,
project_id: &str,
branch: &str,
project_id: &ProjectId,
branch_id: &BranchId,
message: &str,
ownership: Option<&Ownership>,
) -> Result<git::Oid, Error> {
@ -107,7 +111,7 @@ impl Controller {
super::commit(
gb_repository,
project_repository,
branch,
branch_id,
message,
ownership,
signing_key.as_ref(),
@ -124,7 +128,7 @@ impl Controller {
pub fn can_apply_remote_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_name: &git::BranchName,
) -> Result<bool, Error> {
let project = self.projects.get(project_id)?;
@ -142,8 +146,8 @@ impl Controller {
pub fn can_apply_virtual_branch(
&self,
project_id: &str,
branch_id: &str,
project_id: &ProjectId,
branch_id: &BranchId,
) -> Result<bool, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -160,7 +164,7 @@ impl Controller {
pub async fn list_virtual_branches(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<super::VirtualBranch>, Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
@ -173,9 +177,9 @@ impl Controller {
pub async fn create_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
create: &super::branch::BranchCreateRequest,
) -> Result<String, Error> {
) -> Result<BranchId, Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
if conflicts::is_resolving(project_repository) {
@ -192,10 +196,10 @@ impl Controller {
pub async fn create_virtual_branch_from_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch: &git::BranchName,
) -> Result<String, Error> {
self.with_lock::<Result<String, Error>>(project_id, || {
) -> Result<BranchId, Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, user| {
let branch = super::create_virtual_branch_from_branch(
gb_repository,
@ -237,7 +241,7 @@ impl Controller {
pub fn get_base_branch_data(
&self,
project_id: &str,
project_id: &ProjectId,
) -> Result<Option<super::BaseBranch>, Error> {
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)?;
@ -255,7 +259,7 @@ impl Controller {
pub fn list_remote_commit_files(
&self,
project_id: &str,
project_id: &ProjectId,
commit_oid: git::Oid,
) -> Result<Vec<RemoteBranchFile>, Error> {
let project = self.projects.get(project_id)?;
@ -270,7 +274,7 @@ impl Controller {
pub fn set_base_branch(
&self,
project_id: &str,
project_id: &ProjectId,
target_branch: &git::RemoteBranchName,
) -> Result<super::BaseBranch, Error> {
let project = self.projects.get(project_id)?;
@ -298,8 +302,8 @@ impl Controller {
pub async fn merge_virtual_branch_upstream(
&self,
project_id: &str,
branch: &str,
project_id: &ProjectId,
branch_id: &BranchId,
) -> Result<(), Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, user| {
@ -325,7 +329,7 @@ impl Controller {
super::merge_virtual_branch_upstream(
gb_repository,
project_repository,
branch,
branch_id,
signing_key.as_ref(),
user,
)
@ -335,7 +339,7 @@ impl Controller {
.await
}
pub async fn update_base_branch(&self, project_id: &str) -> Result<(), Error> {
pub async fn update_base_branch(&self, project_id: &ProjectId) -> Result<(), Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, user| {
super::update_base_branch(gb_repository, project_repository, user)
@ -347,7 +351,7 @@ impl Controller {
pub async fn update_virtual_branch(
&self,
project_id: &str,
project_id: &ProjectId,
branch_update: super::branch::BranchUpdateRequest,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -361,8 +365,8 @@ impl Controller {
pub async fn delete_virtual_branch(
&self,
project_id: &str,
branch_id: &str,
project_id: &ProjectId,
branch_id: &BranchId,
) -> Result<(), Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
@ -375,8 +379,8 @@ impl Controller {
pub async fn apply_virtual_branch(
&self,
project_id: &str,
branch_id: &str,
project_id: &ProjectId,
branch_id: &BranchId,
) -> Result<(), Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, user| {
@ -408,7 +412,7 @@ impl Controller {
pub async fn unapply_ownership(
&self,
project_id: &str,
project_id: &ProjectId,
ownership: &Ownership,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -422,8 +426,8 @@ impl Controller {
pub async fn reset_virtual_branch(
&self,
project_id: &str,
branch_id: &str,
project_id: &ProjectId,
branch_id: &BranchId,
target_commit_oid: git::Oid,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -442,8 +446,8 @@ impl Controller {
pub async fn unapply_virtual_branch(
&self,
project_id: &str,
branch_id: &str,
project_id: &ProjectId,
branch_id: &BranchId,
) -> Result<(), Error> {
self.with_lock(project_id, || {
self.with_verify_branch(project_id, |gb_repository, project_repository, _| {
@ -456,8 +460,8 @@ impl Controller {
pub async fn push_virtual_branch(
&self,
project_id: &str,
branch_id: &str,
project_id: &ProjectId,
branch_id: &BranchId,
with_force: bool,
) -> Result<(), Error> {
self.with_lock(project_id, || {
@ -497,7 +501,7 @@ impl Controller {
fn with_verify_branch<T>(
&self,
project_id: &str,
project_id: &ProjectId,
action: impl FnOnce(
&gb_repository::Repository,
&project_repository::Repository,
@ -518,7 +522,7 @@ impl Controller {
action(&gb_repository, &project_repository, user.as_ref())
}
async fn with_lock<T>(&self, project_id: &str, action: impl FnOnce() -> T) -> T {
async fn with_lock<T>(&self, project_id: &ProjectId, action: impl FnOnce() -> T) -> T {
let mut semaphores = self.semaphores.lock().await;
let semaphore = semaphores
.entry(project_id.to_string())

View File

@ -4,11 +4,11 @@ use anyhow::Result;
use crate::reader;
use super::branch;
use super::branch::{self, BranchId};
pub struct BranchIterator<'iterator> {
branch_reader: branch::Reader<'iterator>,
ids: Vec<String>,
ids: Vec<BranchId>,
}
impl<'iterator> BranchIterator<'iterator> {
@ -28,7 +28,11 @@ impl<'iterator> BranchIterator<'iterator> {
.filter(|file_path| file_path != "selected")
.filter(|file_path| file_path != "target");
let unique_ids: HashSet<String> = ids_itarator.collect();
let mut ids: Vec<String> = unique_ids.into_iter().collect();
let mut ids: Vec<BranchId> = unique_ids
.into_iter()
.map(|id| id.parse())
.filter_map(Result::ok)
.collect();
ids.sort();
Ok(Self {
branch_reader: branch::Reader::new(reader),
@ -72,9 +76,9 @@ mod tests {
TEST_INDEX.fetch_add(1, Ordering::Relaxed);
branch::Branch {
id: format!("branch_{}", TEST_INDEX.load(Ordering::Relaxed)),
id: BranchId::generate(),
name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)),
notes: "".to_string(),
notes: String::new(),
applied: true,
upstream: Some(
format!(
@ -157,10 +161,12 @@ mod tests {
let session = gb_repository.get_current_session()?.unwrap();
let session_reader = sessions::Reader::open(&gb_repository, &session)?;
let mut iter = BranchIterator::new(&session_reader)?;
assert_eq!(iter.next().unwrap().unwrap(), branch_1);
assert_eq!(iter.next().unwrap().unwrap(), branch_2);
assert_eq!(iter.next().unwrap().unwrap(), branch_3);
let iter =
BranchIterator::new(&session_reader)?.collect::<Result<Vec<_>, reader::Error>>()?;
assert_eq!(iter.len(), 3);
assert!(iter.contains(&branch_1));
assert!(iter.contains(&branch_2));
assert!(iter.contains(&branch_3));
Ok(())
}

View File

@ -1,5 +1,5 @@
pub mod branch;
pub use branch::Branch;
pub use branch::{Branch, BranchId};
pub mod target;
mod files;

View File

@ -1,6 +1,9 @@
use std::path;
use crate::reader::{self, SubReader};
use crate::{
reader::{self, SubReader},
virtual_branches::BranchId,
};
use super::Target;
@ -22,7 +25,7 @@ impl<'reader> TargetReader<'reader> {
Target::try_from(reader)
}
pub fn read(&self, id: &str) -> Result<Target, reader::Error> {
pub fn read(&self, id: &BranchId) -> Result<Target, reader::Error> {
if !self
.reader
.exists(&path::PathBuf::from(format!("branches/{}/target", id)))
@ -58,7 +61,7 @@ mod tests {
TEST_INDEX.fetch_add(1, Ordering::Relaxed);
branch::Branch {
id: format!("branch_{}", TEST_INDEX.load(Ordering::Relaxed)),
id: BranchId::generate(),
name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)),
notes: "".to_string(),
applied: true,
@ -103,7 +106,7 @@ mod tests {
let session_reader = sessions::Reader::open(&gb_repository, &session)?;
let reader = TargetReader::new(&session_reader);
let result = reader.read("not found");
let result = reader.read(&BranchId::generate());
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "file not found");

View File

@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use crate::{
gb_repository,
virtual_branches::BranchId,
writer::{self, Writer},
};
@ -43,7 +44,7 @@ impl<'writer> TargetWriter<'writer> {
Ok(())
}
pub fn write(&self, id: &str, target: &Target) -> Result<()> {
pub fn write(&self, id: &BranchId, target: &Target) -> Result<()> {
self.repository
.mark_active_session()
.context("Failed to get or create current session")?;
@ -101,7 +102,7 @@ mod tests {
TEST_INDEX.fetch_add(1, Ordering::Relaxed);
branch::Branch {
id: format!("branch_{}", TEST_INDEX.load(Ordering::Relaxed)),
id: BranchId::generate(),
name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)),
notes: format!("branch_notes_{}", TEST_INDEX.load(Ordering::Relaxed)),
applied: true,
@ -155,7 +156,10 @@ mod tests {
let target_writer = TargetWriter::new(&gb_repository);
target_writer.write(&branch.id, &target)?;
let root = gb_repository.root().join("branches").join(&branch.id);
let root = gb_repository
.root()
.join("branches")
.join(branch.id.to_string());
assert_eq!(
fs::read_to_string(root.join("meta").join("name").to_str().unwrap())
@ -247,7 +251,10 @@ mod tests {
target_writer.write(&branch.id, &updated_target)?;
let root = gb_repository.root().join("branches").join(&branch.id);
let root = gb_repository
.root()
.join("branches")
.join(branch.id.to_string());
assert_eq!(
fs::read_to_string(root.join("target").join("branch_name").to_str().unwrap())

View File

@ -8,8 +8,6 @@ use anyhow::{anyhow, bail, Context, Result};
use diffy::{apply_bytes, Patch};
use serde::Serialize;
use uuid::Uuid;
use crate::{
dedup::{dedup, dedup_fmt},
gb_repository,
@ -20,7 +18,7 @@ use crate::{
};
use super::{
branch::{self, Branch, BranchCreateRequest, FileOwnership, Hunk, Ownership},
branch::{self, Branch, BranchCreateRequest, BranchId, FileOwnership, Hunk, Ownership},
target, Iterator,
};
@ -37,7 +35,7 @@ type AppliedStatuses = Vec<(branch::Branch, Vec<VirtualBranchFile>)>;
#[serde(rename_all = "camelCase")]
#[allow(clippy::struct_excessive_bools)]
pub struct VirtualBranch {
pub id: String,
pub id: BranchId,
pub name: String,
pub notes: String,
pub active: bool,
@ -154,7 +152,7 @@ pub fn get_default_target(
pub fn apply_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
signing_key: Option<&keys::PrivateKey>,
user: Option<&users::User>,
) -> Result<()> {
@ -413,7 +411,7 @@ pub fn unapply_ownership(
pub fn unapply_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
) -> Result<()> {
if conflicts::is_resolving(project_repository) {
bail!("cannot unapply, project is in a conflicted state");
@ -440,7 +438,7 @@ pub fn unapply_branch(
// then check that out into the working directory
let final_tree = applied_statuses
.into_iter()
.filter(|(branch, _)| branch.id != branch_id)
.filter(|(branch, _)| &branch.id != branch_id)
.fold(
target_commit.tree().context("failed to get target tree"),
|final_tree, status| {
@ -468,7 +466,7 @@ pub fn unapply_branch(
pub fn flush_vbranch_as_tree(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
applied: bool,
) -> Result<Option<(target::Target, AppliedStatuses)>> {
let session = &gb_repository
@ -514,7 +512,7 @@ pub fn flush_vbranch_as_tree(
let status = applied_statuses
.iter()
.find(|(s, _)| s.id == branch_id)
.find(|(s, _)| s.id == *branch_id)
.context("failed to find status for branch");
if let Ok((_, files)) = status {
@ -663,7 +661,7 @@ pub fn list_virtual_branches(
let requires_force = is_requires_force(project_repository, branch)?;
let branch = VirtualBranch {
id: branch.id.clone(),
id: branch.id,
name: branch.name.clone(),
notes: branch.notes.clone(),
active: branch.applied,
@ -958,7 +956,7 @@ pub fn create_virtual_branch(
);
let mut branch = Branch {
id: Uuid::new_v4().to_string(),
id: BranchId::generate(),
name,
notes: String::new(),
applied: true,
@ -988,7 +986,7 @@ pub fn create_virtual_branch(
pub fn merge_virtual_branch_upstream(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
signing_key: Option<&keys::PrivateKey>,
user: Option<&users::User>,
) -> Result<()> {
@ -1044,7 +1042,7 @@ pub fn merge_virtual_branch_upstream(
.context("failed to read virtual branches")?
.into_iter()
.filter(|b| b.applied)
.filter(|b| b.id != branch_id)
.filter(|b| b.id != *branch_id)
.collect::<Vec<_>>();
// unapply all other branches
@ -1222,7 +1220,7 @@ pub fn update_branch(
pub fn delete_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
) -> Result<branch::Branch> {
let current_session = gb_repository
.get_or_create_current_session()
@ -1512,9 +1510,9 @@ fn get_applied_status(
// - update shifted hunks
// - remove non existent hunks
let mut hunks_by_branch_id: HashMap<String, Vec<VirtualBranchHunk>> = virtual_branches
let mut hunks_by_branch_id: HashMap<BranchId, Vec<VirtualBranchHunk>> = virtual_branches
.iter()
.map(|branch| (branch.id.clone(), vec![]))
.map(|branch| (branch.id, vec![]))
.collect();
for branch in &mut virtual_branches {
@ -1556,14 +1554,13 @@ fn get_applied_status(
.unwrap_or(current_hunk.modified_at);
// push hunk to the end of the list, preserving the order
hunks_by_branch_id
.entry(branch.id.clone())
.or_default()
.push(VirtualBranchHunk {
hunks_by_branch_id.entry(branch.id).or_default().push(
VirtualBranchHunk {
id: ch.with_timestamp(timestamp).to_string(),
modified_at: timestamp,
..current_hunk.clone()
});
},
);
// remove the hunk from the current hunks because each hunk can
// only be owned once
@ -1573,16 +1570,13 @@ fn get_applied_status(
} else if owned_hunk.intersects(&ch) {
// if it's an intersection, push the hunk to the beginning,
// indicating the the hunk has been updated
hunks_by_branch_id
.entry(branch.id.clone())
.or_default()
.insert(
0,
VirtualBranchHunk {
id: ch.to_string(),
..current_hunk.clone()
},
);
hunks_by_branch_id.entry(branch.id).or_default().insert(
0,
VirtualBranchHunk {
id: ch.to_string(),
..current_hunk.clone()
},
);
// track updated hunks to bubble them up later
updated.push(FileOwnership {
@ -1636,7 +1630,7 @@ fn get_applied_status(
.unwrap(),
);
hunks_by_branch_id
.entry(virtual_branches[0].id.clone())
.entry(virtual_branches[0].id)
.or_default()
.push(hunk.clone());
}
@ -1701,7 +1695,7 @@ fn virtual_hunks_to_virtual_files(
pub fn reset_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
target_commit_oid: git::Oid,
) -> Result<()> {
let current_session = gb_repository.get_or_create_current_session()?;
@ -1905,7 +1899,7 @@ fn _print_tree(repo: &git2::Repository, tree: &git2::Tree) -> Result<()> {
pub fn commit(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
message: &str,
ownership: Option<&branch::Ownership>,
signing_key: Option<&keys::PrivateKey>,
@ -1922,7 +1916,7 @@ pub fn commit(
let (branch, files) = statuses
.iter()
.find(|(branch, _)| branch.id == branch_id)
.find(|(branch, _)| branch.id == *branch_id)
.ok_or_else(|| anyhow!("branch {} not found", branch_id))?;
let files = calculate_non_commited_files(project_repository, branch, &default_target, files)?;
@ -2054,7 +2048,7 @@ pub enum PushError {
pub fn push(
project_repository: &project_repository::Repository,
gb_repository: &gb_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
with_force: bool,
key: &Key,
) -> Result<(), PushError> {
@ -2241,7 +2235,7 @@ pub fn is_remote_branch_mergeable(
pub fn is_virtual_branch_mergeable(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
branch_id: &str,
branch_id: &BranchId,
) -> Result<bool> {
let current_session = gb_repository
.get_or_create_current_session()

View File

@ -11,7 +11,7 @@ use tokio::{
task,
};
use crate::{git, watcher::events};
use crate::{git, projects::ProjectId, watcher::events};
#[derive(Debug, Clone)]
pub struct Dispatcher {
@ -29,7 +29,7 @@ impl Dispatcher {
self.watcher.lock().unwrap().take();
}
pub fn run(self, project_id: &str, path: &path::Path) -> Result<Receiver<events::Event>> {
pub fn run(self, project_id: &ProjectId, path: &path::Path) -> Result<Receiver<events::Event>> {
let repo = git::Repository::open(path)
.with_context(|| format!("failed to open project repository: {}", path.display()))?;
@ -64,27 +64,26 @@ impl Dispatcher {
.with_context(|| format!("failed to watch project path: {}", path.display()))?;
self.watcher.lock().unwrap().replace(watcher);
tracing::debug!(project_id, "file watcher started");
tracing::debug!(%project_id, "file watcher started");
let (tx, rx) = channel(1);
let project_id = project_id.to_string();
task::Builder::new()
.name(&format!("{} file watcher", project_id))
.spawn({
let path = path.to_path_buf();
let project_id = project_id.clone();
let project_id = *project_id;
async move {
while let Some(file_path) = notify_rx.recv().await {
match file_path.strip_prefix(&path) {
Ok(relative_file_path) => {
let event = if relative_file_path.starts_with(".git") {
tracing::info!(
project_id,
%project_id,
file_path = %relative_file_path.display(),
"git file change",
);
events::Event::GitFileChange(
project_id.clone(),
project_id,
relative_file_path
.strip_prefix(".git")
.unwrap()
@ -92,29 +91,29 @@ impl Dispatcher {
)
} else {
tracing::info!(
project_id,
%project_id,
file_path = %relative_file_path.display(),
"project file change",
);
events::Event::ProjectFileChange(
project_id.clone(),
project_id,
relative_file_path.to_path_buf(),
)
};
if let Err(error) = tx.send(event).await {
tracing::error!(
project_id,
%project_id,
?error,
"failed to send file change event",
);
}
}
Err(error) => {
tracing::error!(project_id, ?error, "failed to strip prefix");
tracing::error!(%project_id, ?error, "failed to strip prefix");
}
}
}
tracing::debug!(project_id, "file watcher stopped");
tracing::debug!(%project_id, "file watcher stopped");
}
})?;

View File

@ -11,6 +11,8 @@ use tokio::{
};
use tokio_util::sync::CancellationToken;
use crate::projects::ProjectId;
use super::events;
#[derive(Clone)]
@ -36,7 +38,7 @@ impl Dispatcher {
pub fn run<P: AsRef<path::Path>>(
self,
project_id: &str,
project_id: &ProjectId,
path: P,
) -> Result<Receiver<events::Event>> {
let path = path.as_ref();
@ -55,7 +57,7 @@ impl Dispatcher {
))?;
let (tx, rx) = channel(1);
let project_id = project_id.to_owned();
let project_id = *project_id;
task::Builder::new()
.name(&format!("{} dispatcher", project_id))
.spawn(async move {
@ -66,17 +68,17 @@ impl Dispatcher {
}
Some(event) = tick_rx.recv() => {
if let Err(error) = tx.send(event).await {
tracing::error!(project_id, ?error,"failed to send tick");
tracing::error!(%project_id, ?error,"failed to send tick");
}
}
Some(event) = file_change_rx.recv() => {
if let Err(error) = tx.send(event).await {
tracing::error!( project_id, ?error,"failed to send file change");
tracing::error!(%project_id, ?error,"failed to send file change");
}
}
}
}
tracing::debug!(project_id, "dispatcher stopped");
tracing::debug!(%project_id, "dispatcher stopped");
})?;
Ok(rx)

View File

@ -7,7 +7,7 @@ use tokio::{
};
use tokio_util::sync::CancellationToken;
use crate::watcher::events;
use crate::{projects::ProjectId, watcher::events};
#[derive(Debug, Clone)]
pub struct Dispatcher {
@ -27,7 +27,7 @@ impl Dispatcher {
pub fn run(
self,
project_id: &str,
project_id: &ProjectId,
interval: time::Duration,
) -> Result<Receiver<events::Event>> {
let (tx, rx) = channel(1);
@ -36,25 +36,22 @@ impl Dispatcher {
task::Builder::new()
.name(&format!("{} ticker", project_id))
.spawn({
let project_id = project_id.to_string();
let project_id = *project_id;
async move {
tracing::debug!(project_id, "ticker started");
tracing::debug!(%project_id, "ticker started");
loop {
ticker.tick().await;
if self.cancellation_token.is_cancelled() {
break;
}
if let Err(error) = tx
.send(events::Event::Tick(
project_id.clone(),
time::SystemTime::now(),
))
.send(events::Event::Tick(project_id, time::SystemTime::now()))
.await
{
tracing::error!(project_id, ?error, "failed to send tick");
tracing::error!(%project_id, ?error, "failed to send tick");
}
}
tracing::debug!(project_id, "ticker stopped");
tracing::debug!(%project_id, "ticker stopped");
}
})?;
@ -64,14 +61,17 @@ impl Dispatcher {
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use super::*;
#[tokio::test]
async fn test_ticker() {
let dispatcher = Dispatcher::new();
let dispatcher2 = dispatcher.clone();
let mut rx = dispatcher2.run("test", Duration::from_millis(10)).unwrap();
let mut rx = dispatcher2
.run(&ProjectId::generate(), Duration::from_millis(10))
.unwrap();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;

View File

@ -1,33 +1,38 @@
use std::{fmt::Display, path, time};
use crate::{analytics, bookmarks, deltas, events, reader, sessions};
use crate::{
analytics, bookmarks, deltas, events,
projects::ProjectId,
reader,
sessions::{self, SessionId},
};
#[derive(Debug, PartialEq, Clone)]
pub enum Event {
Tick(String, time::SystemTime),
Flush(String, sessions::Session),
Tick(ProjectId, time::SystemTime),
Flush(ProjectId, sessions::Session),
FetchGitbutlerData(String, time::SystemTime),
PushGitbutlerData(String),
FetchProjectData(String, time::SystemTime),
FetchGitbutlerData(ProjectId, time::SystemTime),
PushGitbutlerData(ProjectId),
FetchProjectData(ProjectId, time::SystemTime),
GitFileChange(String, path::PathBuf),
GitFileChange(ProjectId, path::PathBuf),
ProjectFileChange(String, path::PathBuf),
ProjectFileChange(ProjectId, path::PathBuf),
Session(String, sessions::Session),
SessionFile((String, String, path::PathBuf, Option<reader::Content>)),
SessionDelta((String, String, path::PathBuf, deltas::Delta)),
Session(ProjectId, sessions::Session),
SessionFile((ProjectId, SessionId, path::PathBuf, Option<reader::Content>)),
SessionDelta((ProjectId, SessionId, path::PathBuf, deltas::Delta)),
Bookmark(bookmarks::Bookmark),
IndexAll(String),
IndexAll(ProjectId),
Emit(events::Event),
Analytics(analytics::Event),
}
impl Event {
pub fn project_id(&self) -> &str {
pub fn project_id(&self) -> &ProjectId {
match self {
Event::Analytics(event) => event.project_id(),
Event::Emit(event) => event.project_id(),

View File

@ -6,8 +6,8 @@ use std::{
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::paths::DataDir;
use crate::{gb_repository, project_repository, projects, users};
use crate::{paths::DataDir, projects::ProjectId};
use super::events;
@ -28,7 +28,11 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
self.inner.handle(project_id, now)
}
}
@ -59,7 +63,11 @@ impl TryFrom<&AppHandle> for HandlerInner {
}
impl HandlerInner {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
let _lock = match self.mutex.try_lock() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => return Err(anyhow::anyhow!("mutex poisoned")),
@ -81,7 +89,7 @@ impl HandlerInner {
// mark fetching
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
gitbutler_data_last_fetched: Some(projects::FetchResult::Fetching {
timestamp_ms: now.duration_since(time::UNIX_EPOCH)?.as_millis(),
}),
@ -104,7 +112,7 @@ impl HandlerInner {
.collect::<Vec<_>>();
let fetch_result = if let Err(error) = gb_repo.fetch(user.as_ref()) {
tracing::error!(project_id, ?error, "failed to fetch gitbutler data");
tracing::error!(%project_id, ?error, "failed to fetch gitbutler data");
projects::FetchResult::Error {
attempt: project
.gitbutler_data_last_fetched
@ -125,7 +133,7 @@ impl HandlerInner {
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
gitbutler_data_last_fetched: Some(fetch_result),
..Default::default()
})
@ -144,7 +152,7 @@ impl HandlerInner {
let events = new_sessions
.into_iter()
.cloned()
.map(|session| events::Event::Session(project_id.to_string(), session))
.map(|session| events::Event::Session(*project_id, session))
.collect::<Vec<_>>();
Ok(events)

View File

@ -6,7 +6,13 @@ use std::{
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{gb_repository, keys, paths::DataDir, project_repository, projects, users};
use crate::{
gb_repository, keys,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
users,
};
use super::events;
@ -27,7 +33,11 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
self.inner.handle(project_id, now)
}
}
@ -59,7 +69,11 @@ impl TryFrom<&AppHandle> for HandlerInner {
}
impl HandlerInner {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
let _lock = match self.mutex.try_lock() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => return Err(anyhow::anyhow!("mutex poisoned")),
@ -71,7 +85,7 @@ impl HandlerInner {
// mark fetching
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
project_data_last_fetched: Some(projects::FetchResult::Fetching {
timestamp_ms: now.duration_since(time::UNIX_EPOCH)?.as_millis(),
}),
@ -109,7 +123,7 @@ impl HandlerInner {
let fetch_result =
if let Err(error) = project_repository.fetch(default_target.branch.remote(), &key) {
tracing::error!(project_id, ?error, "failed to fetch project data");
tracing::error!(%project_id, ?error, "failed to fetch project data");
projects::FetchResult::Error {
attempt: project
.project_data_last_fetched
@ -130,7 +144,7 @@ impl HandlerInner {
self.projects
.update(&projects::UpdateRequest {
id: project_id.to_string(),
id: *project_id,
project_data_last_fetched: Some(fetch_result),
..Default::default()
})

View File

@ -2,7 +2,11 @@ use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{
gb_repository, paths::DataDir, project_repository, projects, sessions, users, virtual_branches,
gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
sessions, users,
};
use super::events;
@ -29,7 +33,7 @@ impl TryFrom<&AppHandle> for Handler {
impl Handler {
pub fn handle(
&self,
project_id: &str,
project_id: &ProjectId,
session: &sessions::Session,
) -> Result<Vec<events::Event>> {
let project = self
@ -47,22 +51,23 @@ impl Handler {
)
.context("failed to open repository")?;
if let Some(branch_id) = &session.meta.branch {
virtual_branches::flush_vbranch_as_tree(
&gb_repo,
&project_repository,
branch_id,
true,
)?;
}
// TODO: fixme
// if let Some(branch_id) = &session.meta.branch {
// virtual_branches::flush_vbranch_as_tree(
// &gb_repo,
// &project_repository,
// branch_id,
// true,
// )?;
// }
let session = gb_repo
.flush_session(&project_repository, session, user.as_ref())
.context("failed to flush session")?;
Ok(vec![
events::Event::Session(project_id.to_string(), session),
events::Event::PushGitbutlerData(project_id.to_string()),
events::Event::Session(*project_id, session),
events::Event::PushGitbutlerData(*project_id),
])
}
}

View File

@ -2,7 +2,10 @@ use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{
analytics, events as app_events, gb_repository, paths::DataDir, project_repository, projects,
analytics, events as app_events, gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
users,
};
@ -31,7 +34,7 @@ impl Handler {
pub fn handle<P: AsRef<std::path::Path>>(
&self,
path: P,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<events::Event>> {
let project = self
.projects
@ -61,17 +64,14 @@ impl Handler {
if file_path.exists() {
if let Err(e) = std::fs::remove_file(&file_path) {
tracing::error!(project_id, path = %file_path.display(), "GB_FLUSH file delete error: {}", e);
tracing::error!(%project_id, path = %file_path.display(), "GB_FLUSH file delete error: {}", e);
}
if let Some(current_session) = gb_repo
.get_current_session()
.context("failed to get current session")?
{
return Ok(vec![events::Event::Flush(
project.id.clone(),
current_session,
)]);
return Ok(vec![events::Event::Flush(project.id, current_session)]);
}
}
@ -85,7 +85,7 @@ impl Handler {
if let Some(head) = head_ref.name() {
Ok(vec![
events::Event::Analytics(analytics::Event::HeadChange {
project_id: project.id.clone(),
project_id: project.id,
reference_name: head_ref_name.to_string(),
}),
events::Event::Emit(app_events::Event::git_head(&project.id, head)),

View File

@ -4,8 +4,13 @@ use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{
bookmarks, deltas, events as app_events, gb_repository, paths::DataDir, project_repository,
projects, search, sessions, users,
bookmarks, deltas, events as app_events, gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
search,
sessions::{self, SessionId},
users,
};
use super::events;
@ -40,8 +45,8 @@ impl TryFrom<&AppHandle> for Handler {
impl Handler {
pub fn index_deltas(
&self,
project_id: &str,
session_id: &str,
project_id: &ProjectId,
session_id: &SessionId,
file_path: &path::Path,
deltas: &Vec<deltas::Delta>,
) -> Result<Vec<events::Event>> {
@ -53,7 +58,7 @@ impl Handler {
pub fn index_bookmark(
&self,
project_id: &str,
project_id: &ProjectId,
bookmark: &bookmarks::Bookmark,
) -> Result<Vec<events::Event>> {
let updated = self.bookmarks_database.upsert(bookmark)?;
@ -67,7 +72,7 @@ impl Handler {
}
}
pub fn reindex(&self, project_id: &str) -> Result<Vec<events::Event>> {
pub fn reindex(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
let user = self.users.get_user()?;
let project = self.projects.get(project_id)?;
let project_repository = project_repository::Repository::try_from(&project)
@ -89,7 +94,7 @@ impl Handler {
pub fn index_session(
&self,
project_id: &str,
project_id: &ProjectId,
session: &sessions::Session,
) -> Result<Vec<events::Event>> {
let user = self.users.get_user()?;

View File

@ -6,7 +6,8 @@ use tauri::AppHandle;
use crate::{
deltas, gb_repository,
paths::DataDir,
project_repository, projects,
project_repository,
projects::{self, ProjectId},
reader::{self, Reader},
sessions, users,
};
@ -79,7 +80,7 @@ impl Handler {
pub fn handle<P: AsRef<std::path::Path>>(
&self,
path: P,
project_id: &str,
project_id: &ProjectId,
) -> Result<Vec<events::Event>> {
let project = self
.projects
@ -146,7 +147,7 @@ impl Handler {
.context("failed to calculate new deltas")?;
if new_delta.is_none() {
tracing::debug!(project_id, path = %path.display(), "no new deltas, ignoring");
tracing::debug!(%project_id, path = %path.display(), "no new deltas, ignoring");
return Ok(vec![]);
}
let new_delta = new_delta.as_ref().unwrap();
@ -166,15 +167,15 @@ impl Handler {
Ok(vec![
events::Event::SessionFile((
project_id.to_string(),
current_session.id.clone(),
*project_id,
current_session.id,
path.to_path_buf(),
latest_file_content,
)),
events::Event::Session(project_id.to_string(), current_session.clone()),
events::Event::Session(*project_id, current_session.clone()),
events::Event::SessionDelta((
project_id.to_string(),
current_session.id.clone(),
*project_id,
current_session.id,
path.to_path_buf(),
new_delta.clone(),
)),
@ -197,6 +198,8 @@ mod test {
virtual_branches::{self, branch},
};
use self::branch::BranchId;
use super::*;
static TEST_TARGET_INDEX: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));
@ -226,7 +229,7 @@ mod test {
TEST_INDEX.fetch_add(1, Ordering::Relaxed);
virtual_branches::branch::Branch {
id: format!("branch_{}", TEST_INDEX.load(Ordering::Relaxed)),
id: BranchId::generate(),
name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)),
notes: format!("branch_notes_{}", TEST_INDEX.load(Ordering::Relaxed)),
applied: true,
@ -518,7 +521,7 @@ mod test {
// get all the created sessions
let mut sessions: Vec<sessions::Session> = gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect();
assert_eq!(sessions.len(), size);
// verify sessions order is correct
@ -533,23 +536,23 @@ mod test {
sessions.reverse();
// try to reconstruct file state from operations for every session slice
for i in 0..=sessions.len() - 1 {
for i in 0..sessions.len() {
let sessions_slice = &mut sessions[i..];
// collect all operations from sessions in the reverse order
let mut operations: Vec<deltas::Operation> = vec![];
sessions_slice.iter().for_each(|session| {
for session in &mut *sessions_slice {
let session_reader = sessions::Reader::open(&gb_repository, session).unwrap();
let deltas_reader = deltas::Reader::new(&session_reader);
let deltas_by_filepath = deltas_reader.read(&None).unwrap();
for deltas in deltas_by_filepath.values() {
deltas.iter().for_each(|delta| {
for delta in deltas {
delta.operations.iter().for_each(|operation| {
operations.push(operation.clone());
});
});
}
}
});
}
let reader =
sessions::Reader::open(&gb_repository, sessions_slice.first().unwrap()).unwrap();
@ -604,7 +607,7 @@ mod test {
// get all the created sessions
let mut sessions: Vec<sessions::Session> = gb_repository
.get_sessions_iterator()?
.map(|s| s.unwrap())
.map(Result::unwrap)
.collect();
assert_eq!(sessions.len(), size);
// verify sessions order is correct
@ -619,23 +622,23 @@ mod test {
sessions.reverse();
// try to reconstruct file state from operations for every session slice
for i in 0..=sessions.len() - 1 {
for i in 0..sessions.len() {
let sessions_slice = &mut sessions[i..];
// collect all operations from sessions in the reverse order
let mut operations: Vec<deltas::Operation> = vec![];
sessions_slice.iter().for_each(|session| {
for session in &mut *sessions_slice {
let session_reader = sessions::Reader::open(&gb_repository, session).unwrap();
let deltas_reader = deltas::Reader::new(&session_reader);
let deltas_by_filepath = deltas_reader.read(&None).unwrap();
for deltas in deltas_by_filepath.values() {
deltas.iter().for_each(|delta| {
for delta in deltas {
delta.operations.iter().for_each(|operation| {
operations.push(operation.clone());
});
});
}
}
});
}
let reader =
sessions::Reader::open(&gb_repository, sessions_slice.first().unwrap()).unwrap();
@ -672,9 +675,9 @@ mod test {
} = suite.new_case();
let listener = Handler::from(&suite.local_app_data);
let size = 10;
let size = 10_i32;
let relative_file_path = std::path::Path::new("one/two/test.txt");
for i in 1..=size {
for i in 1_i32..=size {
std::fs::create_dir_all(std::path::Path::new(&project.path).join("one/two"))?;
// create a session with a single file change and flush it
std::fs::write(
@ -692,11 +695,11 @@ mod test {
let deltas_reader = deltas::Reader::new(&session_reader);
let deltas_by_filepath = deltas_reader.read(&None).unwrap();
for deltas in deltas_by_filepath.values() {
deltas.iter().for_each(|delta| {
for delta in deltas {
delta.operations.iter().for_each(|operation| {
operations.push(operation.clone());
});
});
}
}
let reader = sessions::Reader::open(&gb_repository, &session).unwrap();
@ -760,8 +763,9 @@ mod test {
.into_iter()
.collect::<Vec<virtual_branches::Branch>>();
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].id, vbranch0.id);
assert_eq!(branches[1].id, vbranch1.id);
let branch_ids = branches.iter().map(|b| b.id).collect::<Vec<_>>();
assert!(branch_ids.contains(&vbranch0.id));
assert!(branch_ids.contains(&vbranch1.id));
let target_reader = virtual_branches::target::Reader::new(&session_reader);
assert_eq!(target_reader.read_default().unwrap(), default_target);
@ -818,8 +822,9 @@ mod test {
.into_iter()
.collect::<Vec<virtual_branches::Branch>>();
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].id, vbranch0.id);
assert_eq!(branches[1].id, vbranch1.id);
let branch_ids = branches.iter().map(|b| b.id).collect::<Vec<_>>();
assert!(branch_ids.contains(&vbranch0.id));
assert!(branch_ids.contains(&vbranch1.id));
let target_reader = virtual_branches::target::Reader::new(&session_reader);
assert_eq!(target_reader.read_default().unwrap(), default_target);

View File

@ -4,6 +4,7 @@ use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::paths::DataDir;
use crate::projects::ProjectId;
use crate::{gb_repository, project_repository, projects, users};
use super::events;
@ -24,7 +25,7 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str) -> Result<Vec<events::Event>> {
pub fn handle(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
self.inner.handle(project_id)
}
}
@ -66,7 +67,7 @@ impl HandlerInner {
}
}
pub fn handle(&self, project_id: &str) -> Result<Vec<events::Event>> {
pub fn handle(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
let _lock = match self.mutex.try_lock() {
Ok(lock) => lock,
Err(TryLockError::Poisoned(_)) => return Err(anyhow::anyhow!("mutex poisoned")),

View File

@ -3,7 +3,13 @@ use std::time;
use anyhow::{Context, Result};
use tauri::AppHandle;
use crate::{gb_repository, paths::DataDir, project_repository, projects, sessions, users};
use crate::{
gb_repository,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
sessions, users,
};
use super::events;
@ -27,7 +33,11 @@ impl TryFrom<&AppHandle> for Handler {
}
impl Handler {
pub fn handle(&self, project_id: &str, now: &time::SystemTime) -> Result<Vec<events::Event>> {
pub fn handle(
&self,
project_id: &ProjectId,
now: &time::SystemTime,
) -> Result<Vec<events::Event>> {
let user = self.users.get_user()?;
let project = self.projects.get(project_id)?;
@ -51,10 +61,7 @@ impl Handler {
.map_or(Ok(true), |f| f.should_fetch(now))
.context("failed to check if gitbutler data should be fetched")?
{
events.push(events::Event::FetchGitbutlerData(
project_id.to_string(),
*now,
));
events.push(events::Event::FetchGitbutlerData(*project_id, *now));
}
if project
@ -63,10 +70,7 @@ impl Handler {
.map_or(Ok(true), |f| f.should_fetch(now))
.context("failed to check if project data should be fetched")?
{
events.push(events::Event::FetchProjectData(
project_id.to_string(),
*now,
));
events.push(events::Event::FetchProjectData(*project_id, *now));
}
if let Some(current_session) = gb_repo
@ -74,10 +78,7 @@ impl Handler {
.context("failed to get current session")?
{
if should_flush(now, &current_session)? {
events.push(events::Event::Flush(
project_id.to_string(),
current_session,
));
events.push(events::Event::Flush(*project_id, current_session));
}
}
@ -107,6 +108,8 @@ fn is_session_active(now: &time::SystemTime, session: &sessions::Session) -> Res
#[cfg(test)]
mod tests {
use crate::sessions::SessionId;
use super::*;
const ONE_MILLISECOND: time::Duration = time::Duration::from_millis(1);
@ -133,7 +136,7 @@ mod tests {
.into_iter()
.for_each(|(start, last, expected)| {
let session = sessions::Session {
id: "session-id".to_string(),
id: SessionId::generate(),
hash: None,
meta: sessions::Meta {
start_timestamp_ms: start.duration_since(time::UNIX_EPOCH).unwrap().as_millis(),

View File

@ -17,12 +17,12 @@ use tokio::{
};
use tokio_util::sync::CancellationToken;
use crate::projects;
use crate::projects::{self, ProjectId};
#[derive(Clone)]
pub struct Watchers {
app_handle: AppHandle,
watchers: Arc<Mutex<HashMap<String, Watcher>>>,
watchers: Arc<Mutex<HashMap<ProjectId, Watcher>>>,
}
impl TryFrom<&AppHandle> for Watchers {
@ -41,22 +41,22 @@ impl Watchers {
let watcher = Watcher::try_from(&self.app_handle)?;
let c_watcher = watcher.clone();
let project_id = project.id.clone();
let project_id = project.id;
let project_path = project.path.clone();
task::Builder::new()
.name(&format!("{} watcher", project_id))
.spawn(async move {
if let Err(error) = c_watcher.run(&project_path, &project_id).await {
tracing::error!(?error, project_id, "watcher error");
tracing::error!(?error, %project_id, "watcher error");
}
tracing::debug!(project_id, "watcher stopped");
tracing::debug!(%project_id, "watcher stopped");
})?;
self.watchers
.lock()
.await
.insert(project.id.clone(), watcher.clone());
.insert(project.id, watcher.clone());
Ok(())
}
@ -73,7 +73,7 @@ impl Watchers {
}
}
pub async fn stop(&self, project_id: &str) -> Result<()> {
pub async fn stop(&self, project_id: &ProjectId) -> Result<()> {
if let Some((_, watcher)) = self.watchers.lock().await.remove_entry(project_id) {
watcher.stop();
};
@ -105,7 +105,7 @@ impl Watcher {
self.inner.post(event).await
}
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &str) -> Result<()> {
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &ProjectId) -> Result<()> {
self.inner.run(path, project_id).await
}
}
@ -149,7 +149,7 @@ impl WatcherInner {
}
}
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &str) -> Result<()> {
pub async fn run<P: AsRef<path::Path>>(&self, path: P, project_id: &ProjectId) -> Result<()> {
let (proxy_tx, mut proxy_rx) = unbounded_channel();
self.proxy_tx.lock().await.replace(proxy_tx.clone());
@ -159,7 +159,7 @@ impl WatcherInner {
.context("failed to run dispatcher")?;
proxy_tx
.send(Event::IndexAll(project_id.to_string()))
.send(Event::IndexAll(*project_id))
.context("failed to send event")?;
let handle_event = |event: &Event| -> Result<()> {

View File

@ -3,7 +3,7 @@ use std::path;
use tauri::{AppHandle, Manager};
use tracing::instrument;
use crate::error::Error;
use crate::error::{Code, Error};
use super::controller;
@ -25,9 +25,13 @@ pub async fn get_project_archive_path(
handle: AppHandle,
project_id: &str,
) -> Result<path::PathBuf, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle
.state::<controller::Controller>()
.archive(project_id)
.archive(&project_id)
.map_err(Into::into)
}
@ -49,9 +53,13 @@ pub async fn get_project_data_archive_path(
handle: AppHandle,
project_id: &str,
) -> Result<path::PathBuf, Error> {
let project_id = project_id.parse().map_err(|_| Error::UserError {
code: Code::Projects,
message: "Malformed project id".into(),
})?;
handle
.state::<controller::Controller>()
.data_archive(project_id)
.data_archive(&project_id)
.map_err(Into::into)
}

View File

@ -4,7 +4,7 @@ use tauri::AppHandle;
use crate::{
paths::{DataDir, LogsDir},
projects,
projects::{self, ProjectId},
};
use super::Zipper;
@ -32,19 +32,19 @@ impl TryFrom<&AppHandle> for Controller {
}
impl Controller {
pub fn archive(&self, project_id: &str) -> Result<path::PathBuf, ArchiveError> {
pub fn archive(&self, project_id: &ProjectId) -> Result<path::PathBuf, ArchiveError> {
let project = self.projects_controller.get(project_id)?;
self.zipper.zip(project.path).map_err(Into::into)
}
pub fn data_archive(&self, project_id: &str) -> Result<path::PathBuf, DataArchiveError> {
pub fn data_archive(&self, project_id: &ProjectId) -> Result<path::PathBuf, DataArchiveError> {
let project = self.projects_controller.get(project_id)?;
self.zipper
.zip(
self.local_data_dir
.to_path_buf()
.join("projects")
.join(project.id),
.join(project.id.to_string()),
)
.map_err(Into::into)
}

View File

@ -18,7 +18,6 @@ mod add {
let project = controller.add(path).unwrap();
assert_eq!(project.path, path);
assert_eq!(project.title, path.iter().last().unwrap().to_str().unwrap());
assert_eq!(project.id.len(), 36);
}
mod error {

View File

@ -1,7 +1,9 @@
use std::{fs, str::FromStr};
use gitbutler::{
git, keys, projects, users,
git, keys,
projects::{self, ProjectId},
users,
virtual_branches::{Controller, ControllerError},
};
@ -9,7 +11,7 @@ use crate::{common::TestProject, paths};
struct Test {
repository: TestProject,
project_id: String,
project_id: ProjectId,
controller: Controller,
}