Merge branch 'master' into refactor-project-setup-page

This commit is contained in:
Mattias Granlund 2023-11-30 16:48:48 +00:00 committed by GitHub
commit eb7f2674a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 452 additions and 80 deletions

View File

@ -4,7 +4,7 @@ use anyhow::Result;
use walkdir::WalkDir;
// Returns an ordered list of relative paths for files inside a directory recursively.
pub fn list_files<P: AsRef<Path>>(dir_path: P) -> Result<Vec<PathBuf>> {
pub fn list_files<P: AsRef<Path>>(dir_path: P, ignore_prefixes: &[P]) -> Result<Vec<PathBuf>> {
let mut files = vec![];
let dir_path = dir_path.as_ref();
if !dir_path.exists() {
@ -16,6 +16,12 @@ pub fn list_files<P: AsRef<Path>>(dir_path: P) -> Result<Vec<PathBuf>> {
let path = entry.path();
let path = path.strip_prefix(dir_path)?;
let path = path.to_path_buf();
if ignore_prefixes
.iter()
.any(|prefix| path.starts_with(prefix.as_ref()))
{
continue;
}
files.push(path);
}
}

View File

@ -1,5 +1,5 @@
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
fs::File,
io::{BufReader, Read},
os::unix::prelude::{MetadataExt, OsStrExt},
@ -11,16 +11,17 @@ use filetime::FileTime;
use sha2::{Digest, Sha256};
use crate::{
fs, git, lock,
deltas, fs, git, lock,
paths::DataDir,
project_repository,
projects::{self, ProjectId},
reader::{self, Reader},
sessions,
sessions::SessionId,
users,
virtual_branches::{self, target},
};
use crate::{project_repository, reader, sessions};
pub struct Repository {
git_repository: git::Repository,
project: projects::Project,
@ -429,6 +430,7 @@ impl Repository {
sessions::Writer::new(self).write(session)?;
let mut tree_builder = self.git_repository.treebuilder(None);
tree_builder.upsert(
"session",
build_session_tree(self).context("failed to build session tree")?,
@ -462,7 +464,7 @@ impl Repository {
.context("failed to remove session directory")?;
let session = sessions::Session {
hash: Some(commit_oid.to_string()),
hash: Some(commit_oid),
..session.clone()
};
@ -532,9 +534,81 @@ impl Repository {
}
}
fn build_wd_tree(
gb_repository: &Repository,
project_repository: &project_repository::Repository,
) -> Result<git::Oid> {
match gb_repository
.git_repository
.find_reference(&"refs/heads/current".parse().unwrap())
{
Result::Ok(reference) => build_wd_tree_from_reference(gb_repository, &reference)
.context("failed to build wd index"),
Err(git::Error::NotFound(_)) => build_wd_tree_from_repo(gb_repository, project_repository)
.context("failed to build wd index"),
Err(e) => Err(e.into()),
}
}
fn build_wd_tree_from_reference(
gb_repository: &Repository,
reference: &git::Reference,
) -> Result<git::Oid> {
// start off with the last tree as a base
let tree = reference.peel_to_tree()?;
let wd_tree_entry = tree.get_name("wd").unwrap();
let wd_tree = gb_repository.git_repository.find_tree(wd_tree_entry.id())?;
let mut index = git::Index::try_from(&wd_tree)?;
// write updated files on top of the last tree
for file_path in fs::list_files(gb_repository.session_wd_path(), &[]).with_context(|| {
format!(
"failed to session working directory files list files in {}",
gb_repository.session_wd_path().display()
)
})? {
add_wd_path(
&mut index,
&gb_repository.session_wd_path(),
&file_path,
gb_repository,
)
.with_context(|| {
format!(
"failed to add session working directory path {}",
file_path.display()
)
})?;
}
let session_reader = reader::DirReader::open(gb_repository.root());
let deltas = deltas::Reader::new(&session_reader)
.read(None)
.context("failed to read deltas")?;
let wd_files = session_reader.list_files(path::Path::new("session/wd"))?;
let wd_files = wd_files.iter().collect::<HashSet<_>>();
// if a file has delta, but doesn't exist in wd, it was deleted
let deleted_files = deltas
.keys()
.filter(|key| !wd_files.contains(key))
.collect::<Vec<_>>();
for deleted_file in deleted_files {
index
.remove_path(deleted_file)
.context("failed to remove path")?;
}
let wd_tree_oid = index
.write_tree_to(&gb_repository.git_repository)
.context("failed to write wd tree")?;
Ok(wd_tree_oid)
}
// build wd index from the working directory files new session wd files
// this is important because we want to make sure session files are in sync with session deltas
fn build_wd_tree(
fn build_wd_tree_from_repo(
gb_repository: &Repository,
project_repository: &project_repository::Repository,
) -> Result<git::Oid> {
@ -544,16 +618,15 @@ fn build_wd_tree(
// first, add session/wd files. session/wd are written at the same time as deltas, so it's important to add them first
// to make sure they are in sync with the deltas
for file_path in fs::list_files(gb_repository.session_wd_path()).with_context(|| {
for file_path in fs::list_files(gb_repository.session_wd_path(), &[]).with_context(|| {
format!(
"failed to session working directory files list files in {}",
gb_repository.session_wd_path().display()
)
})? {
let file_path = std::path::Path::new(&file_path);
if project_repository
.git_repository
.is_path_ignored(file_path)
.is_path_ignored(&file_path)
.unwrap_or(true)
{
continue;
@ -562,7 +635,7 @@ fn build_wd_tree(
add_wd_path(
&mut index,
&gb_repository.session_wd_path(),
file_path,
&file_path,
gb_repository,
)
.with_context(|| {
@ -575,21 +648,21 @@ fn build_wd_tree(
}
// finally, add files from the working directory if they aren't already in the index
for file_path in fs::list_files(project_repository.root()).with_context(|| {
format!(
"failed to working directory list files in {}",
project_repository.root().display()
)
})? {
for file_path in fs::list_files(project_repository.root(), &[path::Path::new(".git")])
.with_context(|| {
format!(
"failed to working directory list files in {}",
project_repository.root().display()
)
})?
{
if added.contains_key(&file_path.to_string_lossy().to_string()) {
continue;
}
let file_path = std::path::Path::new(&file_path);
if project_repository
.git_repository
.is_path_ignored(file_path)
.is_path_ignored(&file_path)
.unwrap_or(true)
{
continue;
@ -598,7 +671,7 @@ fn build_wd_tree(
add_wd_path(
&mut index,
project_repository.root(),
file_path,
&file_path,
gb_repository,
)
.with_context(|| {
@ -720,7 +793,9 @@ fn build_branches_tree(gb_repository: &Repository) -> Result<git::Oid> {
let mut index = git::Index::new()?;
let branches_dir = gb_repository.root().join("branches");
for file_path in fs::list_files(&branches_dir).context("failed to find branches directory")? {
for file_path in
fs::list_files(&branches_dir, &[]).context("failed to find branches directory")?
{
let file_path = std::path::Path::new(&file_path);
add_file_to_index(
gb_repository,
@ -742,18 +817,17 @@ fn build_session_tree(gb_repository: &Repository) -> Result<git::Oid> {
let mut index = git::Index::new()?;
// add all files in the working directory to the in-memory index, skipping for matching entries in the repo index
for file_path in
fs::list_files(gb_repository.session_path()).context("failed to list session files")?
for file_path in fs::list_files(
gb_repository.session_path(),
&[path::Path::new("wd").to_path_buf()],
)
.context("failed to list session files")?
{
let file_path = std::path::Path::new(&file_path);
if file_path.starts_with("wd/") {
continue;
}
add_file_to_index(
gb_repository,
&mut index,
file_path,
&gb_repository.session_path().join(file_path),
&file_path,
&gb_repository.session_path().join(&file_path),
)
.with_context(|| format!("failed to add session file: {}", file_path.display()))?;
}
@ -809,7 +883,7 @@ fn write_gb_commit(
let comitter = git::Signature::now("gitbutler", "gitbutler@localhost")?;
let author = match user {
None => comitter.clone(),
Some(user) => git::Signature::now(user.name.as_str(), user.email.as_str())?,
Some(user) => git::Signature::try_from(user)?,
};
let current_refname: git::Refname = "refs/heads/current".parse().unwrap();

View File

@ -34,9 +34,19 @@ impl TryFrom<&users::User> for Signature<'_> {
type Error = super::Error;
fn try_from(value: &users::User) -> Result<Self, Self::Error> {
git2::Signature::now(&value.name, &value.email)
.map(Into::into)
.map_err(Into::into)
if let Some(name) = &value.name {
git2::Signature::now(name, &value.email)
.map(Into::into)
.map_err(Into::into)
} else if let Some(name) = &value.given_name {
git2::Signature::now(name, &value.email)
.map(Into::into)
.map_err(Into::into)
} else {
git2::Signature::now(&value.email, &value.email)
.map(Into::into)
.map_err(Into::into)
}
}
}

View File

@ -53,12 +53,10 @@ impl Reader for DirReader {
}
fn list_files(&self, dir_path: &path::Path) -> Result<Vec<path::PathBuf>> {
fs::list_files(self.root.join(dir_path)).map(|files| {
files
.into_iter()
.filter(|f| !f.starts_with(".git"))
.collect::<Vec<_>>()
})
fs::list_files(
self.root.join(dir_path),
&[path::Path::new(".git").to_path_buf()],
)
}
}

View File

@ -40,10 +40,18 @@ pub fn init(package_info: &PackageInfo) -> ClientInitGuard {
/// Sets the current user in the Sentry scope.
/// There is only one scope in the application, so this will overwrite any previous user.
pub fn configure_scope(user: Option<&users::User>) {
let name = match user {
Some(user) => match &user.name {
Some(name) => Some(name.clone()),
None => user.given_name.as_ref().cloned(),
},
None => None,
};
sentry::configure_scope(|scope| {
scope.set_user(user.map(|user| sentry::User {
id: Some(user.id.to_string()),
username: Some(user.name.clone()),
username: name,
email: Some(user.email.clone()),
..Default::default()
}));

View File

@ -32,7 +32,7 @@ impl Database {
stmt.execute(rusqlite::named_params! {
":id": session.id,
":project_id": project_id,
":hash": session.hash,
":hash": session.hash.map(|hash| hash.to_string()),
":branch": session.meta.branch,
":commit": session.meta.commit,
":start_timestamp_ms": session.meta.start_timestamp_ms.to_string(),
@ -127,7 +127,11 @@ impl Database {
fn parse_row(row: &rusqlite::Row) -> Result<session::Session> {
Ok(session::Session {
id: row.get(0).context("Failed to get id")?,
hash: row.get(2).context("Failed to get hash")?,
hash: row
.get::<usize, Option<String>>(2)
.context("Failed to get hash")?
.map(|hash| hash.parse().context("Failed to parse hash"))
.transpose()?,
meta: session::Meta {
branch: row.get(3).context("Failed to get branch")?,
commit: row.get(4).context("Failed to get commit")?,
@ -212,7 +216,7 @@ mod tests {
};
let session2 = session::Session {
id: SessionId::generate(),
hash: Some("hash2".to_string()),
hash: Some("08f23df1b9c2dec3d0c826a3ae745f9b821a1a26".parse().unwrap()),
meta: session::Meta {
branch: Some("branch2".to_string()),
commit: Some("commit2".to_string()),
@ -253,7 +257,7 @@ mod tests {
};
let session_updated = session::Session {
id: session.id,
hash: Some("hash2".to_string()),
hash: Some("08f23df1b9c2dec3d0c826a3ae745f9b821a1a26".parse().unwrap()),
meta: session::Meta {
branch: Some("branch2".to_string()),
commit: Some("commit2".to_string()),

View File

@ -3,7 +3,7 @@ use std::{collections::HashMap, path};
use anyhow::{anyhow, Context, Result};
use crate::{
gb_repository, git,
gb_repository,
reader::{self, CommitReader, Reader},
};
@ -62,13 +62,9 @@ impl<'reader> SessionReader<'reader> {
));
};
let oid: git::Oid = session_hash
.parse()
.context(format!("failed to parse commit hash {}", session_hash))?;
let commit = repository
.git_repository()
.find_commit(oid)
.find_commit(*session_hash)
.context("failed to get commit")?;
let commit_reader =
reader::CommitReader::from_commit(repository.git_repository(), &commit)?;

View File

@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use serde::Serialize;
use thiserror::Error;
use crate::{id::Id, reader};
use crate::{git, id::Id, reader};
#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
@ -26,7 +26,7 @@ pub type SessionId = Id<Session>;
pub struct Session {
pub id: SessionId,
// if hash is not set, the session is not saved aka current
pub hash: Option<String>,
pub hash: Option<git::Oid>,
pub meta: Meta,
}
@ -107,7 +107,7 @@ impl<'reader> TryFrom<reader::CommitReader<'reader>> for Session {
type Error = SessionError;
fn try_from(reader: reader::CommitReader<'reader>) -> Result<Self, Self::Error> {
let commit_oid = reader.get_commit_oid().to_string();
let commit_oid = reader.get_commit_oid();
let session = Session::try_from(&reader as &dyn reader::Reader)?;
Ok(Session {
hash: Some(commit_oid),

View File

@ -13,7 +13,7 @@ fn test_should_not_write_session_with_hash() {
let session = sessions::Session {
id: SessionId::generate(),
hash: Some("hash".to_string()),
hash: Some("08f23df1b9c2dec3d0c826a3ae745f9b821a1a26".parse().unwrap()),
meta: sessions::Meta {
start_timestamp_ms: 0,
last_timestamp_ms: 1,

View File

@ -35,7 +35,7 @@ impl Default for Suite {
impl Suite {
pub fn sign_in(&self) -> users::User {
let user = users::User {
name: "test".to_string(),
name: Some("test".to_string()),
email: "test@email.com".to_string(),
access_token: "token".to_string(),
..Default::default()

View File

@ -5,7 +5,7 @@ use crate::git;
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct User {
pub id: u64,
pub name: String,
pub name: Option<String>,
pub given_name: Option<String>,
pub family_name: Option<String>,
pub email: String,
@ -23,6 +23,12 @@ impl TryFrom<User> for git::Signature<'_> {
type Error = git::Error;
fn try_from(value: User) -> Result<Self, Self::Error> {
git::Signature::now(&value.name, &value.email)
if let Some(name) = value.name {
git::Signature::now(&name, &value.email)
} else if let Some(name) = value.given_name {
git::Signature::now(&name, &value.email)
} else {
git::Signature::now(&value.email, &value.email)
}
}
}

View File

@ -66,7 +66,6 @@ pub fn list_remote_branches(
.context("failed to convert branches")?
.into_iter()
.flatten()
.filter(|branch| branch.name.branch() != Some(default_target.branch.branch()))
.collect::<Vec<_>>();
Ok(remote_branches)

View File

@ -191,7 +191,9 @@ mod test {
use once_cell::sync::Lazy;
use crate::{
deltas, sessions,
deltas,
reader::Reader,
sessions,
test_utils::{self, Case, Suite},
virtual_branches::{self, branch},
};
@ -923,4 +925,262 @@ mod test {
Ok(())
}
mod flush_wd {
use super::*;
#[test]
fn should_add_new_files_to_session_wd() {
let suite = Suite::default();
let Case {
gb_repository,
project,
project_repository,
..
} = suite.new_case();
let listener = Handler::from(&suite.local_app_data);
// write a file into session
std::fs::write(project.path.join("test.txt"), "hello world!").unwrap();
listener.handle("test.txt", &project.id).unwrap();
let flushed_session = gb_repository
.flush(&project_repository, None)
.unwrap()
.unwrap();
{
// after flush it should be flushed into the commit
let session_commit = gb_repository
.git_repository()
.find_commit(flushed_session.hash.unwrap())
.unwrap();
let commit_reader = reader::CommitReader::from_commit(
gb_repository.git_repository(),
&session_commit,
)
.unwrap();
assert_eq!(
commit_reader.list_files(path::Path::new("wd")).unwrap(),
vec![path::Path::new("test.txt")]
);
assert_eq!(
commit_reader.read(path::Path::new("wd/test.txt")).unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
}
// write another file into session
std::fs::create_dir_all(project.path.join("one/two")).unwrap();
std::fs::write(project.path.join("one/two/test2.txt"), "hello world!").unwrap();
listener.handle("one/two/test2.txt", &project.id).unwrap();
let flushed_session = gb_repository
.flush(&project_repository, None)
.unwrap()
.unwrap();
{
// after flush it should be flushed into the commit next to the previous one
let session_commit = gb_repository
.git_repository()
.find_commit(flushed_session.hash.unwrap())
.unwrap();
let commit_reader = reader::CommitReader::from_commit(
gb_repository.git_repository(),
&session_commit,
)
.unwrap();
assert_eq!(
commit_reader.list_files(path::Path::new("wd")).unwrap(),
vec![
path::Path::new("one/two/test2.txt"),
path::Path::new("test.txt"),
]
);
assert_eq!(
commit_reader.read(path::Path::new("wd/test.txt")).unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
assert_eq!(
commit_reader
.read(path::Path::new("wd/one/two/test2.txt"))
.unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
}
}
#[test]
fn should_remove_deleted_files_from_session_wd() {
let suite = Suite::default();
let Case {
gb_repository,
project,
project_repository,
..
} = suite.new_case();
let listener = Handler::from(&suite.local_app_data);
// write a file into session
std::fs::write(project.path.join("test.txt"), "hello world!").unwrap();
listener.handle("test.txt", &project.id).unwrap();
std::fs::create_dir_all(project.path.join("one/two")).unwrap();
std::fs::write(project.path.join("one/two/test2.txt"), "hello world!").unwrap();
listener.handle("one/two/test2.txt", &project.id).unwrap();
let flushed_session = gb_repository
.flush(&project_repository, None)
.unwrap()
.unwrap();
{
// after flush it should be flushed into the commit
let session_commit = gb_repository
.git_repository()
.find_commit(flushed_session.hash.unwrap())
.unwrap();
let commit_reader = reader::CommitReader::from_commit(
gb_repository.git_repository(),
&session_commit,
)
.unwrap();
assert_eq!(
commit_reader.list_files(path::Path::new("wd")).unwrap(),
vec![
path::Path::new("one/two/test2.txt"),
path::Path::new("test.txt"),
]
);
assert_eq!(
commit_reader.read(path::Path::new("wd/test.txt")).unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
assert_eq!(
commit_reader
.read(path::Path::new("wd/one/two/test2.txt"))
.unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
}
// rm the files
std::fs::remove_file(project.path.join("test.txt")).unwrap();
listener.handle("test.txt", &project.id).unwrap();
std::fs::remove_file(project.path.join("one/two/test2.txt")).unwrap();
listener.handle("one/two/test2.txt", &project.id).unwrap();
let flushed_session = gb_repository
.flush(&project_repository, None)
.unwrap()
.unwrap();
{
// after flush it should be removed from the commit
let session_commit = gb_repository
.git_repository()
.find_commit(flushed_session.hash.unwrap())
.unwrap();
let commit_reader = reader::CommitReader::from_commit(
gb_repository.git_repository(),
&session_commit,
)
.unwrap();
assert!(commit_reader
.list_files(path::Path::new("wd"))
.unwrap()
.is_empty());
}
}
#[test]
fn should_update_updated_files_in_session_wd() {
let suite = Suite::default();
let Case {
gb_repository,
project,
project_repository,
..
} = suite.new_case();
let listener = Handler::from(&suite.local_app_data);
// write a file into session
std::fs::write(project.path.join("test.txt"), "hello world!").unwrap();
listener.handle("test.txt", &project.id).unwrap();
std::fs::create_dir_all(project.path.join("one/two")).unwrap();
std::fs::write(project.path.join("one/two/test2.txt"), "hello world!").unwrap();
listener.handle("one/two/test2.txt", &project.id).unwrap();
let flushed_session = gb_repository
.flush(&project_repository, None)
.unwrap()
.unwrap();
{
// after flush it should be flushed into the commit
let session_commit = gb_repository
.git_repository()
.find_commit(flushed_session.hash.unwrap())
.unwrap();
let commit_reader = reader::CommitReader::from_commit(
gb_repository.git_repository(),
&session_commit,
)
.unwrap();
assert_eq!(
commit_reader.list_files(path::Path::new("wd")).unwrap(),
vec![
path::Path::new("one/two/test2.txt"),
path::Path::new("test.txt"),
]
);
assert_eq!(
commit_reader.read(path::Path::new("wd/test.txt")).unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
assert_eq!(
commit_reader
.read(path::Path::new("wd/one/two/test2.txt"))
.unwrap(),
reader::Content::UTF8("hello world!".to_string())
);
}
// update the file
std::fs::write(project.path.join("test.txt"), "hello world!2").unwrap();
listener.handle("test.txt", &project.id).unwrap();
std::fs::write(project.path.join("one/two/test2.txt"), "hello world!2").unwrap();
listener.handle("one/two/test2.txt", &project.id).unwrap();
let flushed_session = gb_repository
.flush(&project_repository, None)
.unwrap()
.unwrap();
{
// after flush it should be updated in the commit
let session_commit = gb_repository
.git_repository()
.find_commit(flushed_session.hash.unwrap())
.unwrap();
let commit_reader = reader::CommitReader::from_commit(
gb_repository.git_repository(),
&session_commit,
)
.unwrap();
assert_eq!(
commit_reader.list_files(path::Path::new("wd")).unwrap(),
vec![
path::Path::new("one/two/test2.txt"),
path::Path::new("test.txt"),
]
);
assert_eq!(
commit_reader.read(path::Path::new("wd/test.txt")).unwrap(),
reader::Content::UTF8("hello world!2".to_string())
);
assert_eq!(
commit_reader
.read(path::Path::new("wd/one/two/test2.txt"))
.unwrap(),
reader::Content::UTF8("hello world!2".to_string())
);
}
}
}
}

View File

@ -1,7 +1,11 @@
use anyhow::{Context, Result};
use tauri::{AppHandle, Manager};
use crate::{assets, events as app_events, projects::ProjectId, virtual_branches};
use crate::{
assets, events as app_events,
projects::ProjectId,
virtual_branches::{self, controller::ControllerError},
};
use super::events;
@ -26,16 +30,19 @@ impl TryFrom<&AppHandle> for Handler {
impl Handler {
pub async fn handle(&self, project_id: &ProjectId) -> Result<Vec<events::Event>> {
let branches = self
match self
.vbranch_controller
.list_virtual_branches(project_id)
.await
.context("failed to list virtual branches")?;
let branches = self.assets_proxy.proxy_virtual_branches(branches).await;
Ok(vec![events::Event::Emit(
app_events::Event::virtual_branches(project_id, &branches),
)])
{
Ok(branches) => Ok(vec![events::Event::Emit(
app_events::Event::virtual_branches(
project_id,
&self.assets_proxy.proxy_virtual_branches(branches).await,
),
)]),
Err(ControllerError::VerifyError(_)) => Ok(vec![]),
Err(error) => Err(error).context("failed to list virtual branches"),
}
}
}

View File

@ -179,7 +179,8 @@ mod test {
fn create_new_session_via_new_file(project: &projects::Project, suite: &Suite) {
fs::write(project.path.join("test.txt"), "test").unwrap();
let file_change_listener = handlers::session_handler::Handler::from(&suite.local_app_data);
let file_change_listener =
handlers::calculate_deltas_handler::Handler::from(&suite.local_app_data);
file_change_listener
.handle("test.txt", &project.id)
.unwrap();

View File

@ -1,4 +1,6 @@
mod analytics_handler;
mod calculate_deltas_handler;
mod caltulate_virtual_branches_handler;
mod fetch_gitbutler_data;
mod fetch_project_data;
mod flush_session;
@ -7,9 +9,7 @@ mod index_handler;
mod project_file_change;
mod push_gitbutler_data;
mod push_project_to_gitbutler;
mod session_handler;
mod tick_handler;
mod vbranch_handler;
use std::time;
@ -33,8 +33,8 @@ pub struct Handler {
analytics_handler: analytics_handler::Handler,
index_handler: index_handler::Handler,
push_project_to_gitbutler: push_project_to_gitbutler::Handler,
virtual_branch_handler: vbranch_handler::Handler,
session_processing_handler: session_handler::Handler,
calculate_vbranches_handler: caltulate_virtual_branches_handler::Handler,
calculate_deltas_handler: calculate_deltas_handler::Handler,
events_sender: app_events::Sender,
}
@ -55,8 +55,10 @@ impl TryFrom<&AppHandle> for Handler {
fetch_gitbutler_handler: fetch_gitbutler_data::Handler::try_from(value)?,
analytics_handler: analytics_handler::Handler::from(value),
push_project_to_gitbutler: push_project_to_gitbutler::Handler::try_from(value)?,
virtual_branch_handler: vbranch_handler::Handler::try_from(value)?,
session_processing_handler: session_handler::Handler::try_from(value)?,
calculate_vbranches_handler: caltulate_virtual_branches_handler::Handler::try_from(
value,
)?,
calculate_deltas_handler: calculate_deltas_handler::Handler::try_from(value)?,
})
}
}
@ -138,13 +140,13 @@ impl Handler {
}
events::Event::CalculateVirtualBranches(project_id) => self
.virtual_branch_handler
.calculate_vbranches_handler
.handle(project_id)
.await
.context("failed to handle virtual branch event"),
events::Event::CalculateDeltas(project_id, path) => self
.session_processing_handler
.calculate_deltas_handler
.handle(path, project_id)
.context(format!(
"failed to handle session processing event: {:?}",

View File

@ -29,7 +29,7 @@ export type LoginToken = {
export type User = {
id: number;
name: string;
name: string | undefined;
given_name: string | undefined;
family_name: string | undefined;
email: string;

View File

@ -52,7 +52,7 @@
// We account for the NewBranchDropZone by subtracting 2
for (let i = 0; i < children.length - 2; i++) {
const pos = children[i].getBoundingClientRect();
if (e.clientX > pos.left + pos.width) {
if (e.clientX > pos.left + dragged.offsetWidth / 2) {
dropPosition = i + 1; // Note that this is declared in the <script>
} else {
break;
@ -91,6 +91,7 @@
// We rely on elements with id `drag-handle` to initiate this drag
e.preventDefault();
e.stopPropagation();
return;
}
clone = cloneNode(e.target);
document.body.appendChild(clone);