diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 00b4fe01e..fd7f6136f 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -14,6 +14,7 @@ jobs: outputs: node: ${{ steps.filter.outputs.node }} rust: ${{ steps.filter.outputs.rust }} + gitbutler: ${{ steps.filter.outputs.gitbutler }} gitbutler-app: ${{ steps.filter.outputs.gitbutler-app }} gitbutler-changeset: ${{ steps.filter.outputs.gitbutler-changeset }} gitbutler-git: ${{ steps.filter.outputs.gitbutler-git }} @@ -36,6 +37,10 @@ jobs: - 'gitbutler-!(ui)/**' gitbutler-app: - *any-rust + gitbutler: + - *rust + - 'src/**' + - 'tests/**' gitbutler-changeset: - *rust - 'gitbutler-changeset/**' @@ -97,6 +102,28 @@ jobs: env: RUSTDOCFLAGS: -Dwarnings + check-gitbutler: + needs: [changes, rust-init] + if: ${{ needs.changes.outputs.gitbutler == 'true' }} + runs-on: ubuntu-latest + container: + image: ghcr.io/gitbutlerapp/ci-base-image:latest + strategy: + matrix: + action: + - test + - check + - check-tests + features: + - '' + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/check-crate + with: + crate: gitbutler + features: ${{ toJson(matrix.features) }} + action: ${{ matrix.action }} + check-gitbutler-app: needs: [changes, rust-init] if: ${{ needs.changes.outputs.gitbutler-app == 'true' }} diff --git a/Cargo.lock b/Cargo.lock index 14a1bd556..70c61d166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,7 +276,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -625,7 +625,7 @@ checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" dependencies = [ "glib-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.4" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" +checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" dependencies = [ "smallvec", "target-lexicon", @@ -1451,9 +1451,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" [[package]] name = "field-offset" @@ -1670,9 +1670,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" @@ -1740,7 +1740,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -1757,7 +1757,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -1771,7 +1771,7 @@ dependencies = [ "gobject-sys", "libc", "pkg-config", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -1783,7 +1783,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", "x11", ] @@ -1875,7 +1875,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", "winapi 0.3.9", ] @@ -1907,17 +1907,14 @@ dependencies = [ ] [[package]] -name = "gitbutler-app" +name = "gitbutler" version = "0.0.0" dependencies = [ "anyhow", "async-trait", - "backoff", "backtrace", "bstr 1.9.1", - "byteorder", "chrono", - "console-subscriber", "diffy", "filetime", "fslock", @@ -1925,15 +1922,10 @@ dependencies = [ "git2", "git2-hooks", "gitbutler-git", - "governor", "itertools 0.12.1", "lazy_static", "log", "md5", - "nonzero_ext", - "notify", - "notify-debouncer-full", - "num_cpus", "once_cell", "pretty_assertions", "r2d2", @@ -1944,16 +1936,52 @@ dependencies = [ "reqwest 0.12.2", "resolve-path", "rusqlite", - "sentry", - "sentry-tracing", "serde", "serde_json", - "sha1", "sha2", "similar", "slug", "ssh-key", "ssh2", + "tempfile", + "thiserror", + "tokio", + "toml 0.8.12", + "tracing", + "url", + "urlencoding", + "uuid", + "walkdir", + "zip", +] + +[[package]] +name = "gitbutler-app" +version = "0.0.0" +dependencies = [ + "anyhow", + "async-trait", + "backoff", + "backtrace", + "chrono", + "console-subscriber", + "futures", + "git2", + "gitbutler", + "governor", + "itertools 0.12.1", + "log", + "nonzero_ext", + "notify", + "notify-debouncer-full", + "once_cell", + "pretty_assertions", + "reqwest 0.12.2", + "sentry", + "sentry-tracing", + "serde", + "serde_json", + "slug", "tauri", "tauri-build", "tauri-plugin-context-menu", @@ -1965,15 +1993,9 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "toml 0.8.12", "tracing", "tracing-appender", "tracing-subscriber", - "url", - "urlencoding", - "uuid", - "walkdir", - "zip", ] [[package]] @@ -2041,7 +2063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" dependencies = [ "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -2071,7 +2093,7 @@ checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" dependencies = [ "glib-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -2143,7 +2165,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -2237,11 +2259,11 @@ dependencies = [ [[package]] name = "hdrhistogram" -version = "7.5.2" +version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64 0.13.1", + "base64 0.21.3", "byteorder", "flate2", "nom", @@ -3005,9 +3027,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md5" @@ -3395,9 +3417,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags 2.4.0", "cfg-if", @@ -3540,7 +3562,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] @@ -3997,9 +4019,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", "prost-derive", @@ -4007,9 +4029,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools 0.11.0", @@ -4020,9 +4042,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ "prost", ] @@ -5426,14 +5448,14 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.1.1" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" +checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" dependencies = [ - "cfg-expr 0.15.4", + "cfg-expr 0.15.7", "heck 0.4.1", "pkg-config", - "toml 0.7.6", + "toml 0.8.12", "version-compare 0.1.1", ] @@ -5955,9 +5977,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -6537,7 +6559,7 @@ dependencies = [ "pango-sys", "pkg-config", "soup2-sys", - "system-deps 6.1.1", + "system-deps 6.2.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 66f33920a..cdbb099e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,72 @@ +[package] +name = "gitbutler" +version = "0.0.0" +edition = "2021" +rust-version = "1.57" +authors = ["GitButler "] +publish = false + +[lib] +doctest = false + +[dev-dependencies] +once_cell = "1.19" +pretty_assertions = "1.4" + +[dependencies] +toml = "0.8.12" +anyhow = "1.0.81" +async-trait = "0.1.79" +backtrace = { version = "0.3.71", optional = true } +bstr = "1.9.1" +chrono = { version = "0.4.37", features = ["serde"] } +diffy = "0.3.0" +filetime = "0.2.23" +fslock = "0.2.1" +futures = "0.3" +git2.workspace = true +git2-hooks = "0.3" +itertools = "0.12" +lazy_static = "1.4.0" +md5 = "0.7.0" +r2d2 = "0.8.10" +r2d2_sqlite = "0.22.0" +rand = "0.8.5" +refinery = { version = "0.8", features = [ "rusqlite" ] } +regex = "1.10" +reqwest = { version = "0.12.2", features = ["json"] } +resolve-path = "0.1.0" +rusqlite.workspace = true +serde.workspace = true +serde_json = { version = "1.0", features = [ "std", "arbitrary_precision" ] } +sha2 = "0.10.8" +similar = { version = "2.4.0", features = ["unicode"] } +slug = "0.1.5" +ssh-key = { version = "0.6.5", features = [ "alloc", "ed25519" ] } +ssh2 = { version = "0.9.4", features = ["vendored-openssl"] } +log = "^0.4" +thiserror.workspace = true +tokio = { workspace = true, features = [ "full", "sync" ] } +tracing = "0.1.40" +url = { version = "2.5", features = ["serde"] } +urlencoding = "2.1.3" +uuid.workspace = true +walkdir = "2.5.0" +zip = "0.6.5" +tempfile = "3.10" +gitbutler-git = { path = "gitbutler-git" } + +[features] +# by default Tauri runs in production mode +# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is a URL +default = ["error-context"] +error-context = ["dep:backtrace"] + +[lints.clippy] +all = "deny" +perf = "deny" +correctness = "deny" + [workspace] members = [ "gitbutler-app", @@ -8,7 +77,7 @@ resolver = "2" [workspace.dependencies] git2 = { version = "0.18.3", features = ["vendored-openssl", "vendored-libgit2"] } -uuid = "1.8.0" +uuid = { version = "1.8.0", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.58" rusqlite = { version = "0.29.0", features = [ "bundled", "blob" ] } diff --git a/gitbutler-app/Cargo.toml b/gitbutler-app/Cargo.toml index 0269f44e7..292fa3ad0 100644 --- a/gitbutler-app/Cargo.toml +++ b/gitbutler-app/Cargo.toml @@ -18,52 +18,31 @@ test = false tauri-build = { version = "1.5", features = [] } [dev-dependencies] -once_cell = "1.19" +#once_cell = "1.19" pretty_assertions = "1.4" +tempfile = "3.10" [dependencies] -toml = "0.8.12" anyhow = "1.0.81" async-trait = "0.1.79" backoff = "0.4.0" backtrace = { version = "0.3.71", optional = true } -bstr = "1.9.1" -byteorder = "1.5.0" chrono = { version = "0.4.37", features = ["serde"] } console-subscriber = "0.2.0" -diffy = "0.3.0" -filetime = "0.2.23" -fslock = "0.2.1" futures = "0.3" git2.workspace = true -git2-hooks = "0.3" governor = "0.6.3" itertools = "0.12" -lazy_static = "1.4.0" -md5 = "0.7.0" nonzero_ext = "0.3.0" notify = { version = "6.0.1" } notify-debouncer-full = "0.3.1" -num_cpus = "1.16.0" once_cell = "1.19" -r2d2 = "0.8.10" -r2d2_sqlite = "0.22.0" -rand = "0.8.5" -refinery = { version = "0.8", features = [ "rusqlite" ] } -regex = "1.10" reqwest = { version = "0.12.2", features = ["json"] } -resolve-path = "0.1.0" -rusqlite.workspace = true sentry = { version = "0.32", optional = true, features = ["backtrace", "contexts", "panic", "transport", "anyhow", "debug-images", "reqwest", "native-tls" ] } sentry-tracing = "0.32.0" serde.workspace = true serde_json = { version = "1.0", features = [ "std", "arbitrary_precision" ] } -sha1 = "0.10.6" -sha2 = "0.10.8" -similar = { version = "2.4.0", features = ["unicode"] } slug = "0.1.5" -ssh-key = { version = "0.6.5", features = [ "alloc", "ed25519" ] } -ssh2 = { version = "0.9.4", features = ["vendored-openssl"] } tauri = { version = "1.6.1", features = [ "http-all", "os-all", "dialog-open", "fs-read-file", "path-all", "process-relaunch", "protocol-asset", "shell-open", "window-maximize", "window-start-dragging", "window-unmaximize"] } tauri-plugin-context-menu = { git = "https://github.com/c2r0b/tauri-plugin-context-menu", branch = "main" } tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } @@ -77,13 +56,7 @@ tokio-util = "0.7.10" tracing = "0.1.40" tracing-appender = "0.2.3" tracing-subscriber = "0.3.17" -url = "2.5" -urlencoding = "2.1.3" -uuid.workspace = true -walkdir = "2.5.0" -zip = "0.6.5" -tempfile = "3.10" -gitbutler-git = { path = "../gitbutler-git" } +gitbutler = { path = "../" } [lints.clippy] all = "deny" diff --git a/gitbutler-app/src/analytics.rs b/gitbutler-app/src/analytics.rs index 4d515280b..8a248e997 100644 --- a/gitbutler-app/src/analytics.rs +++ b/gitbutler-app/src/analytics.rs @@ -2,7 +2,7 @@ use std::{fmt, str, sync::Arc}; use tauri::AppHandle; -use crate::{projects::ProjectId, users::User}; +use gitbutler::{projects::ProjectId, users::User}; mod posthog; diff --git a/gitbutler-app/src/app.rs b/gitbutler-app/src/app.rs index 40a67896a..cc18c97cb 100644 --- a/gitbutler-app/src/app.rs +++ b/gitbutler-app/src/app.rs @@ -2,7 +2,8 @@ use std::{collections::HashMap, path}; use anyhow::{Context, Result}; -use crate::{ +use crate::watcher; +use gitbutler::{ askpass::AskpassBroker, gb_repository, git, project_repository::{self, conflicts}, @@ -11,7 +12,6 @@ use crate::{ sessions::{self, SessionId}, users, virtual_branches::BranchId, - watcher, }; #[derive(Clone)] diff --git a/gitbutler-app/src/askpass.rs b/gitbutler-app/src/askpass.rs index 4efcd5ad8..5a54913fa 100644 --- a/gitbutler-app/src/askpass.rs +++ b/gitbutler-app/src/askpass.rs @@ -1,70 +1,10 @@ -use std::{collections::HashMap, sync::Arc}; - -use serde::Serialize; -use tauri::{AppHandle, Manager}; -use tokio::sync::{oneshot, Mutex}; - -use crate::id::Id; - -pub struct AskpassRequest { - sender: oneshot::Sender>, -} - -#[derive(Clone)] -pub struct AskpassBroker { - pending_requests: Arc, AskpassRequest>>>, - handle: AppHandle, -} - -#[derive(Debug, Clone, serde::Serialize)] -struct PromptEvent { - id: Id, - prompt: String, - context: C, -} - -impl AskpassBroker { - pub fn init(handle: AppHandle) -> Self { - Self { - pending_requests: Arc::new(Mutex::new(HashMap::new())), - handle, - } - } - - pub async fn submit_prompt( - &self, - prompt: String, - context: C, - ) -> Option { - let (sender, receiver) = oneshot::channel(); - let id = Id::generate(); - let request = AskpassRequest { sender }; - self.pending_requests.lock().await.insert(id, request); - self.handle - .emit_all( - "git_prompt", - PromptEvent { - id, - prompt, - context, - }, - ) - .expect("failed to emit askpass event"); - receiver.await.unwrap() - } - - pub async fn handle_response(&self, id: Id, response: Option) { - let mut pending_requests = self.pending_requests.lock().await; - if let Some(request) = pending_requests.remove(&id) { - let _ = request.sender.send(response); - } else { - log::warn!("received response for unknown askpass request: {}", id); - } - } -} - pub mod commands { - use super::{AppHandle, AskpassBroker, AskpassRequest, Id, Manager}; + use gitbutler::{ + askpass::{AskpassBroker, AskpassRequest}, + id::Id, + }; + use tauri::{AppHandle, Manager}; + #[tauri::command(async)] #[tracing::instrument(skip(handle, response))] pub async fn submit_prompt_response( diff --git a/gitbutler-app/src/commands.rs b/gitbutler-app/src/commands.rs index 11aa9777f..a7124e111 100644 --- a/gitbutler-app/src/commands.rs +++ b/gitbutler-app/src/commands.rs @@ -4,12 +4,12 @@ use anyhow::Context; use tauri::Manager; use tracing::instrument; -use crate::{ - app, +use crate::{app, watcher}; +use gitbutler::{ error::{Code, Error}, gb_repository, git, project_repository, projects, reader, sessions::SessionId, - users, watcher, + users, }; impl From for Error { @@ -71,13 +71,13 @@ pub async fn git_test_push( branch_name: &str, ) -> Result<(), Error> { let app = handle.state::(); - let helper = handle.state::(); + let helper = handle.state::(); let project_id = project_id.parse().map_err(|_| Error::UserError { code: Code::Validation, message: "Malformed project id".to_string(), })?; let askpass_broker = handle - .state::() + .state::() .inner() .clone(); app.git_test_push( @@ -102,13 +102,13 @@ pub async fn git_test_fetch( action: Option, ) -> Result<(), Error> { let app = handle.state::(); - let helper = handle.state::(); + let helper = handle.state::(); let project_id = project_id.parse().map_err(|_| Error::UserError { code: Code::Validation, message: "Malformed project id".to_string(), })?; let askpass_broker = handle - .state::() + .state::() .inner() .clone(); app.git_test_fetch( diff --git a/gitbutler-app/src/deltas.rs b/gitbutler-app/src/deltas.rs index f4359556a..6c5bef0e5 100644 --- a/gitbutler-app/src/deltas.rs +++ b/gitbutler-app/src/deltas.rs @@ -1,16 +1,43 @@ -mod controller; -mod delta; -mod document; -mod reader; -mod writer; +pub mod commands { + use std::collections::HashMap; -pub mod commands; -pub mod database; -pub mod operations; + use tauri::{AppHandle, Manager}; + use tracing::instrument; -pub use controller::Controller; -pub use database::Database; -pub use delta::Delta; -pub use document::Document; -pub use reader::DeltasReader as Reader; -pub use writer::DeltasWriter as Writer; + use crate::error::{Code, Error}; + + use gitbutler::deltas::{controller::ListError, Controller, Delta}; + + impl From for Error { + fn from(value: ListError) -> Self { + match value { + ListError::Other(error) => { + tracing::error!(?error); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn list_deltas( + handle: AppHandle, + project_id: &str, + session_id: &str, + paths: Option>, + ) -> Result>, Error> { + let session_id = session_id.parse().map_err(|_| Error::UserError { + message: "Malformed session id".to_string(), + code: Code::Validation, + })?; + let project_id = project_id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".to_string(), + })?; + handle + .state::() + .list_by_session_id(&project_id, &session_id, &paths) + .map_err(Into::into) + } +} diff --git a/gitbutler-app/src/deltas/commands.rs b/gitbutler-app/src/deltas/commands.rs deleted file mode 100644 index 479369aae..000000000 --- a/gitbutler-app/src/deltas/commands.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::collections::HashMap; - -use tauri::{AppHandle, Manager}; -use tracing::instrument; - -use crate::error::{Code, Error}; - -use super::{controller::ListError, Controller, Delta}; - -impl From for Error { - fn from(value: ListError) -> Self { - match value { - ListError::Other(error) => { - tracing::error!(?error); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn list_deltas( - handle: AppHandle, - project_id: &str, - session_id: &str, - paths: Option>, -) -> Result>, Error> { - let session_id = session_id.parse().map_err(|_| Error::UserError { - message: "Malformed session id".to_string(), - code: Code::Validation, - })?; - let project_id = project_id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".to_string(), - })?; - handle - .state::() - .list_by_session_id(&project_id, &session_id, &paths) - .map_err(Into::into) -} diff --git a/gitbutler-app/src/error.rs b/gitbutler-app/src/error.rs index 19546ac09..ea7baa8bf 100644 --- a/gitbutler-app/src/error.rs +++ b/gitbutler-app/src/error.rs @@ -1,9 +1,9 @@ #[cfg(feature = "sentry")] mod sentry; -pub use legacy::*; +pub(crate) use legacy::*; -pub mod gb { +pub(crate) mod gb { #[cfg(feature = "error-context")] pub use error_context::*; @@ -319,6 +319,7 @@ pub mod gb { mod legacy { use core::fmt; + use gitbutler::project_repository; use serde::{ser::SerializeMap, Serialize}; #[derive(Debug)] @@ -389,4 +390,19 @@ mod legacy { Error::Unknown } } + + impl From for Error { + fn from(value: project_repository::OpenError) -> Self { + match value { + project_repository::OpenError::NotFound(path) => Error::UserError { + code: Code::Projects, + message: format!("{} not found", path.display()), + }, + project_repository::OpenError::Other(error) => { + tracing::error!(?error); + Error::Unknown + } + } + } + } } diff --git a/gitbutler-app/src/events.rs b/gitbutler-app/src/events.rs index 67ae38621..1f2d06175 100644 --- a/gitbutler-app/src/events.rs +++ b/gitbutler-app/src/events.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; -use crate::{ +use gitbutler::{ deltas, projects::ProjectId, reader, diff --git a/gitbutler-app/src/github.rs b/gitbutler-app/src/github.rs index 82b6da3c0..1a1041780 100644 --- a/gitbutler-app/src/github.rs +++ b/gitbutler-app/src/github.rs @@ -1 +1,82 @@ -pub mod commands; +pub mod commands { + use std::collections::HashMap; + + use anyhow::{Context, Result}; + use serde::{Deserialize, Serialize}; + use tracing::instrument; + + use crate::error::Error; + + const GITHUB_CLIENT_ID: &str = "cd51880daa675d9e6452"; + + #[derive(Debug, Deserialize, Serialize, Clone, Default)] + pub struct Verification { + pub user_code: String, + pub device_code: String, + } + + #[tauri::command(async)] + #[instrument] + pub async fn init_device_oauth() -> Result { + let mut req_body = HashMap::new(); + req_body.insert("client_id", GITHUB_CLIENT_ID); + req_body.insert("scope", "repo"); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ); + + let client = reqwest::Client::new(); + let res = client + .post("https://github.com/login/device/code") + .headers(headers) + .json(&req_body) + .send() + .await + .context("Failed to send request")?; + + let rsp_body = res.text().await.context("Failed to get response body")?; + + serde_json::from_str(&rsp_body) + .context("Failed to parse response body") + .map_err(Into::into) + } + + #[tauri::command(async)] + #[instrument] + pub async fn check_auth_status(device_code: &str) -> Result { + #[derive(Debug, Deserialize, Serialize, Clone, Default)] + struct AccessTokenContainer { + access_token: String, + } + + let mut req_body = HashMap::new(); + req_body.insert("client_id", GITHUB_CLIENT_ID); + req_body.insert("device_code", device_code); + req_body.insert("grant_type", "urn:ietf:params:oauth:grant-type:device_code"); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ); + + let client = reqwest::Client::new(); + let res = client + .post("https://github.com/login/oauth/access_token") + .headers(headers) + .json(&req_body) + .send() + .await + .context("Failed to send request")?; + + let rsp_body = res.text().await.context("Failed to get response body")?; + + serde_json::from_str::(&rsp_body) + .map(|rsp_body| rsp_body.access_token) + .context("Failed to parse response body") + .map_err(Into::into) + } +} diff --git a/gitbutler-app/src/github/commands.rs b/gitbutler-app/src/github/commands.rs deleted file mode 100644 index 18406bcaa..000000000 --- a/gitbutler-app/src/github/commands.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::collections::HashMap; - -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use tracing::instrument; - -use crate::error::Error; - -const GITHUB_CLIENT_ID: &str = "cd51880daa675d9e6452"; - -#[derive(Debug, Deserialize, Serialize, Clone, Default)] -pub struct Verification { - pub user_code: String, - pub device_code: String, -} - -#[tauri::command(async)] -#[instrument] -pub async fn init_device_oauth() -> Result { - let mut req_body = HashMap::new(); - req_body.insert("client_id", GITHUB_CLIENT_ID); - req_body.insert("scope", "repo"); - - let mut headers = reqwest::header::HeaderMap::new(); - headers.insert( - reqwest::header::ACCEPT, - reqwest::header::HeaderValue::from_static("application/json"), - ); - - let client = reqwest::Client::new(); - let res = client - .post("https://github.com/login/device/code") - .headers(headers) - .json(&req_body) - .send() - .await - .context("Failed to send request")?; - - let rsp_body = res.text().await.context("Failed to get response body")?; - - serde_json::from_str(&rsp_body) - .context("Failed to parse response body") - .map_err(Into::into) -} - -#[tauri::command(async)] -#[instrument] -pub async fn check_auth_status(device_code: &str) -> Result { - #[derive(Debug, Deserialize, Serialize, Clone, Default)] - struct AccessTokenContainer { - access_token: String, - } - - let mut req_body = HashMap::new(); - req_body.insert("client_id", GITHUB_CLIENT_ID); - req_body.insert("device_code", device_code); - req_body.insert("grant_type", "urn:ietf:params:oauth:grant-type:device_code"); - - let mut headers = reqwest::header::HeaderMap::new(); - headers.insert( - reqwest::header::ACCEPT, - reqwest::header::HeaderValue::from_static("application/json"), - ); - - let client = reqwest::Client::new(); - let res = client - .post("https://github.com/login/oauth/access_token") - .headers(headers) - .json(&req_body) - .send() - .await - .context("Failed to send request")?; - - let rsp_body = res.text().await.context("Failed to get response body")?; - - serde_json::from_str::(&rsp_body) - .map(|rsp_body| rsp_body.access_token) - .context("Failed to parse response body") - .map_err(Into::into) -} diff --git a/gitbutler-app/src/keys.rs b/gitbutler-app/src/keys.rs index d0d35e52b..c30ab5cb4 100644 --- a/gitbutler-app/src/keys.rs +++ b/gitbutler-app/src/keys.rs @@ -1,7 +1,29 @@ -pub mod commands; -mod controller; -mod key; -pub mod storage; +pub mod commands { + use tauri::Manager; + use tracing::instrument; -pub use controller::*; -pub use key::{PrivateKey, PublicKey, SignError}; + use crate::error::Error; + + use gitbutler::keys::{controller, PublicKey}; + + impl From for Error { + fn from(value: controller::GetOrCreateError) -> Self { + match value { + controller::GetOrCreateError::Other(error) => { + tracing::error!(?error, "failed to get or create key"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_public_key(handle: tauri::AppHandle) -> Result { + handle + .state::() + .get_or_create() + .map(|key| key.public_key()) + .map_err(Into::into) + } +} diff --git a/gitbutler-app/src/keys/commands.rs b/gitbutler-app/src/keys/commands.rs deleted file mode 100644 index 4fe620aa3..000000000 --- a/gitbutler-app/src/keys/commands.rs +++ /dev/null @@ -1,27 +0,0 @@ -use tauri::Manager; -use tracing::instrument; - -use crate::error::Error; - -use super::{controller, PublicKey}; - -impl From for Error { - fn from(value: controller::GetOrCreateError) -> Self { - match value { - controller::GetOrCreateError::Other(error) => { - tracing::error!(?error, "failed to get or create key"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_public_key(handle: tauri::AppHandle) -> Result { - handle - .state::() - .get_or_create() - .map(|key| key.public_key()) - .map_err(Into::into) -} diff --git a/gitbutler-app/src/lib.rs b/gitbutler-app/src/lib.rs index cb5bf5ba3..77c35a4ca 100644 --- a/gitbutler-app/src/lib.rs +++ b/gitbutler-app/src/lib.rs @@ -15,36 +15,20 @@ pub mod analytics; pub mod app; -pub mod askpass; -pub mod assets; pub mod commands; -pub mod database; -pub mod dedup; -pub mod deltas; -pub mod error; pub mod events; -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; pub mod menu; -pub mod path; -pub mod project_repository; +pub mod watcher; + +pub mod askpass; +pub mod deltas; +pub mod error; +pub mod github; +pub mod keys; pub mod projects; -pub mod reader; pub mod sentry; pub mod sessions; -pub mod ssh; -pub mod storage; -pub mod types; pub mod users; pub mod virtual_branches; -pub mod watcher; -#[cfg(target_os = "windows")] -pub mod windows; -pub mod writer; pub mod zip; diff --git a/gitbutler-app/src/main.rs b/gitbutler-app/src/main.rs index 8432c49c1..7f86ff9ef 100644 --- a/gitbutler-app/src/main.rs +++ b/gitbutler-app/src/main.rs @@ -13,14 +13,17 @@ clippy::too_many_lines )] +use gitbutler::assets; +use gitbutler::database; +use gitbutler::git; +use gitbutler::storage; +#[cfg(target_os = "windows")] +use gitbutler::windows; use gitbutler_app::analytics; use gitbutler_app::app; use gitbutler_app::askpass; -use gitbutler_app::assets; use gitbutler_app::commands; -use gitbutler_app::database; use gitbutler_app::deltas; -use gitbutler_app::git; use gitbutler_app::github; use gitbutler_app::keys; use gitbutler_app::logs; @@ -28,12 +31,9 @@ use gitbutler_app::menu; use gitbutler_app::projects; use gitbutler_app::sentry; use gitbutler_app::sessions; -use gitbutler_app::storage; use gitbutler_app::users; use gitbutler_app::virtual_branches; use gitbutler_app::watcher; -#[cfg(target_os = "windows")] -use gitbutler_app::windows; use gitbutler_app::zip; use std::path::PathBuf; @@ -101,7 +101,12 @@ fn main() { tracing::info!(version = %app_handle.package_info().version, name = %app_handle.package_info().name, "starting app"); - let askpass_broker = askpass::AskpassBroker::init(app_handle.clone()); + let askpass_broker = gitbutler::askpass::AskpassBroker::init({ + let handle = app_handle.clone(); + move |event| { + handle.emit_all("git_prompt", event).expect("tauri event emission doesn't fail in practice") + } + }); app_handle.manage(askpass_broker); let storage_controller = storage::Storage::new(&app_data_dir); @@ -110,16 +115,16 @@ fn main() { let watcher_controller = watcher::Watchers::new(app_handle.clone()); app_handle.manage(watcher_controller.clone()); - let projects_storage_controller = projects::storage::Storage::new(storage_controller.clone()); + let projects_storage_controller = gitbutler::projects::storage::Storage::new(storage_controller.clone()); app_handle.manage(projects_storage_controller.clone()); - let users_storage_controller = users::storage::Storage::new(storage_controller.clone()); + let users_storage_controller = gitbutler::users::storage::Storage::new(storage_controller.clone()); app_handle.manage(users_storage_controller.clone()); - let users_controller = users::Controller::new(users_storage_controller.clone()); + let users_controller = gitbutler::users::Controller::new(users_storage_controller.clone()); app_handle.manage(users_controller.clone()); - let projects_controller = projects::Controller::new( + let projects_controller = gitbutler::projects::Controller::new( app_data_dir.clone(), projects_storage_controller.clone(), users_controller.clone(), @@ -132,21 +137,21 @@ fn main() { let database_controller = database::Database::open_in_directory(&app_data_dir).expect("failed to open database"); app_handle.manage(database_controller.clone()); - let zipper = zip::Zipper::new(&app_cache_dir); + let zipper = gitbutler::zip::Zipper::new(&app_cache_dir); app_handle.manage(zipper.clone()); - app_handle.manage(zip::Controller::new(app_data_dir.clone(), app_log_dir.clone(), zipper.clone(), projects_controller.clone())); + app_handle.manage(gitbutler::zip::Controller::new(app_data_dir.clone(), app_log_dir.clone(), zipper.clone(), projects_controller.clone())); - let deltas_database_controller = deltas::database::Database::new(database_controller.clone()); + let deltas_database_controller = gitbutler::deltas::database::Database::new(database_controller.clone()); app_handle.manage(deltas_database_controller.clone()); - let deltas_controller = deltas::Controller::new(deltas_database_controller.clone()); + let deltas_controller = gitbutler::deltas::Controller::new(deltas_database_controller.clone()); app_handle.manage(deltas_controller); - let keys_storage_controller = keys::storage::Storage::new(storage_controller.clone()); + let keys_storage_controller = gitbutler::keys::storage::Storage::new(storage_controller.clone()); app_handle.manage(keys_storage_controller.clone()); - let keys_controller = keys::Controller::new(keys_storage_controller.clone()); + let keys_controller = gitbutler::keys::Controller::new(keys_storage_controller.clone()); app_handle.manage(keys_controller.clone()); let git_credentials_controller = git::credentials::Helper::new( @@ -156,7 +161,7 @@ fn main() { ); app_handle.manage(git_credentials_controller.clone()); - app_handle.manage(virtual_branches::controller::Controller::new( + app_handle.manage(gitbutler::virtual_branches::controller::Controller::new( app_data_dir.clone(), projects_controller.clone(), users_controller.clone(), @@ -196,10 +201,10 @@ fn main() { }; } - let sessions_database_controller = sessions::database::Database::new(database_controller.clone()); + let sessions_database_controller = gitbutler::sessions::database::Database::new(database_controller.clone()); app_handle.manage(sessions_database_controller.clone()); - app_handle.manage(sessions::Controller::new( + app_handle.manage(gitbutler::sessions::Controller::new( app_data_dir.clone(), sessions_database_controller.clone(), projects_controller.clone(), diff --git a/gitbutler-app/src/projects.rs b/gitbutler-app/src/projects.rs index 8189caa70..fd741244d 100644 --- a/gitbutler-app/src/projects.rs +++ b/gitbutler-app/src/projects.rs @@ -1,10 +1,206 @@ -pub mod commands; -mod controller; -mod project; -pub mod storage; +pub mod commands { + use std::path; -pub use controller::*; -pub use project::{AuthKey, CodePushState, FetchResult, Project, ProjectId}; -pub use storage::UpdateRequest; + use tauri::Manager; + use tracing::instrument; -pub use project::ApiProject; + use crate::error::{Code, Error}; + + use gitbutler::projects::{ + self, + controller::{self, Controller}, + }; + + impl From for Error { + fn from(value: controller::UpdateError) -> Self { + match value { + controller::UpdateError::Validation( + controller::UpdateValidationError::KeyNotFound(path), + ) => Error::UserError { + code: Code::Projects, + message: format!("'{}' not found", path.display()), + }, + controller::UpdateError::Validation( + controller::UpdateValidationError::KeyNotFile(path), + ) => Error::UserError { + code: Code::Projects, + message: format!("'{}' is not a file", path.display()), + }, + controller::UpdateError::NotFound => Error::UserError { + code: Code::Projects, + message: "Project not found".into(), + }, + controller::UpdateError::Other(error) => { + tracing::error!(?error, "failed to update project"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn update_project( + handle: tauri::AppHandle, + project: projects::UpdateRequest, + ) -> Result { + handle + .state::() + .update(&project) + .await + .map_err(Into::into) + } + + impl From for Error { + fn from(value: controller::AddError) -> Self { + match value { + controller::AddError::NotAGitRepository => Error::UserError { + code: Code::Projects, + message: "Must be a git directory".to_string(), + }, + controller::AddError::AlreadyExists => Error::UserError { + code: Code::Projects, + message: "Project already exists".to_string(), + }, + controller::AddError::OpenProjectRepository(error) => error.into(), + controller::AddError::NotADirectory => Error::UserError { + code: Code::Projects, + message: "Not a directory".to_string(), + }, + controller::AddError::PathNotFound => Error::UserError { + code: Code::Projects, + message: "Path not found".to_string(), + }, + controller::AddError::SubmodulesNotSupported => Error::UserError { + code: Code::Projects, + message: "Repositories with git submodules are not supported".to_string(), + }, + controller::AddError::User(error) => error.into(), + controller::AddError::Other(error) => { + tracing::error!(?error, "failed to add project"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn add_project( + handle: tauri::AppHandle, + path: &path::Path, + ) -> Result { + handle.state::().add(path).map_err(Into::into) + } + + impl From for Error { + fn from(value: controller::GetError) -> Self { + match value { + controller::GetError::NotFound => Error::UserError { + code: Code::Projects, + message: "Project not found".into(), + }, + controller::GetError::Other(error) => { + tracing::error!(?error, "failed to get project"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_project( + handle: tauri::AppHandle, + id: &str, + ) -> Result { + let id = id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".into(), + })?; + handle.state::().get(&id).map_err(Into::into) + } + + impl From for Error { + fn from(value: controller::ListError) -> Self { + match value { + controller::ListError::Other(error) => { + tracing::error!(?error, "failed to list projects"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn list_projects(handle: tauri::AppHandle) -> Result, Error> { + handle.state::().list().map_err(Into::into) + } + + impl From for Error { + fn from(value: controller::DeleteError) -> Self { + match value { + controller::DeleteError::Other(error) => { + tracing::error!(?error, "failed to delete project"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> { + let id = id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".into(), + })?; + handle + .state::() + .delete(&id) + .await + .map_err(Into::into) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn git_get_local_config( + handle: tauri::AppHandle, + id: &str, + key: &str, + ) -> Result, Error> { + let id = id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".into(), + })?; + handle + .state::() + .get_local_config(&id, key) + .map_err(|e| Error::UserError { + code: Code::Projects, + message: e.to_string(), + }) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn git_set_local_config( + handle: tauri::AppHandle, + id: &str, + key: &str, + value: &str, + ) -> Result<(), Error> { + let id = id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".into(), + })?; + handle + .state::() + .set_local_config(&id, key, value) + .map_err(|e| Error::UserError { + code: Code::Projects, + message: e.to_string(), + }) + } +} diff --git a/gitbutler-app/src/projects/commands.rs b/gitbutler-app/src/projects/commands.rs deleted file mode 100644 index c4f9a7629..000000000 --- a/gitbutler-app/src/projects/commands.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::path; - -use tauri::Manager; -use tracing::instrument; - -use crate::{ - error::{Code, Error}, - projects, -}; - -use super::controller::{self, Controller}; - -impl From for Error { - fn from(value: controller::UpdateError) -> Self { - match value { - controller::UpdateError::Validation( - controller::UpdateValidationError::KeyNotFound(path), - ) => Error::UserError { - code: Code::Projects, - message: format!("'{}' not found", path.display()), - }, - controller::UpdateError::Validation(controller::UpdateValidationError::KeyNotFile( - path, - )) => Error::UserError { - code: Code::Projects, - message: format!("'{}' is not a file", path.display()), - }, - controller::UpdateError::NotFound => Error::UserError { - code: Code::Projects, - message: "Project not found".into(), - }, - controller::UpdateError::Other(error) => { - tracing::error!(?error, "failed to update project"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn update_project( - handle: tauri::AppHandle, - project: projects::UpdateRequest, -) -> Result { - handle - .state::() - .update(&project) - .await - .map_err(Into::into) -} - -impl From for Error { - fn from(value: controller::AddError) -> Self { - match value { - controller::AddError::NotAGitRepository => Error::UserError { - code: Code::Projects, - message: "Must be a git directory".to_string(), - }, - controller::AddError::AlreadyExists => Error::UserError { - code: Code::Projects, - message: "Project already exists".to_string(), - }, - controller::AddError::OpenProjectRepository(error) => error.into(), - controller::AddError::NotADirectory => Error::UserError { - code: Code::Projects, - message: "Not a directory".to_string(), - }, - controller::AddError::PathNotFound => Error::UserError { - code: Code::Projects, - message: "Path not found".to_string(), - }, - controller::AddError::SubmodulesNotSupported => Error::UserError { - code: Code::Projects, - message: "Repositories with git submodules are not supported".to_string(), - }, - controller::AddError::User(error) => error.into(), - controller::AddError::Other(error) => { - tracing::error!(?error, "failed to add project"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn add_project( - handle: tauri::AppHandle, - path: &path::Path, -) -> Result { - handle.state::().add(path).map_err(Into::into) -} - -impl From for Error { - fn from(value: controller::GetError) -> Self { - match value { - controller::GetError::NotFound => Error::UserError { - code: Code::Projects, - message: "Project not found".into(), - }, - controller::GetError::Other(error) => { - tracing::error!(?error, "failed to get project"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_project(handle: tauri::AppHandle, id: &str) -> Result { - let id = id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".into(), - })?; - handle.state::().get(&id).map_err(Into::into) -} - -impl From for Error { - fn from(value: controller::ListError) -> Self { - match value { - controller::ListError::Other(error) => { - tracing::error!(?error, "failed to list projects"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn list_projects(handle: tauri::AppHandle) -> Result, Error> { - handle.state::().list().map_err(Into::into) -} - -impl From for Error { - fn from(value: controller::DeleteError) -> Self { - match value { - controller::DeleteError::Other(error) => { - tracing::error!(?error, "failed to delete project"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn delete_project(handle: tauri::AppHandle, id: &str) -> Result<(), Error> { - let id = id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".into(), - })?; - handle - .state::() - .delete(&id) - .await - .map_err(Into::into) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn git_get_local_config( - handle: tauri::AppHandle, - id: &str, - key: &str, -) -> Result, Error> { - let id = id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".into(), - })?; - handle - .state::() - .get_local_config(&id, key) - .map_err(|e| Error::UserError { - code: Code::Projects, - message: e.to_string(), - }) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn git_set_local_config( - handle: tauri::AppHandle, - id: &str, - key: &str, - value: &str, -) -> Result<(), Error> { - let id = id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".into(), - })?; - handle - .state::() - .set_local_config(&id, key, value) - .map_err(|e| Error::UserError { - code: Code::Projects, - message: e.to_string(), - }) -} diff --git a/gitbutler-app/src/sentry.rs b/gitbutler-app/src/sentry.rs index 2b2f9d9ad..9ca06ed08 100644 --- a/gitbutler-app/src/sentry.rs +++ b/gitbutler-app/src/sentry.rs @@ -12,7 +12,7 @@ use sentry_tracing::SentryLayer; use tracing::Subscriber; use tracing_subscriber::registry::LookupSpan; -use crate::users; +use gitbutler::users; static SENTRY_QUOTA: Quota = Quota::per_second(nonzero!(1_u32)); // 1 per second at most. static SENTRY_LIMIT: OnceCell> = OnceCell::new(); diff --git a/gitbutler-app/src/sessions.rs b/gitbutler-app/src/sessions.rs index c904b9115..09a450f79 100644 --- a/gitbutler-app/src/sessions.rs +++ b/gitbutler-app/src/sessions.rs @@ -1,15 +1,42 @@ -mod controller; -mod iterator; -mod reader; -pub mod session; -mod writer; +pub mod commands { + use tauri::{AppHandle, Manager}; + use tracing::instrument; -pub mod commands; -pub mod database; + use crate::error::{Code, Error}; -pub use controller::Controller; -pub use database::Database; -pub use iterator::SessionsIterator; -pub use reader::SessionReader as Reader; -pub use session::{Meta, Session, SessionError, SessionId}; -pub use writer::SessionWriter as Writer; + use gitbutler::sessions::{ + Session, + {controller::ListError, Controller}, + }; + + impl From for Error { + fn from(value: ListError) -> Self { + match value { + ListError::UsersError(error) => Error::from(error), + ListError::ProjectsError(error) => Error::from(error), + ListError::ProjectRepositoryError(error) => Error::from(error), + ListError::Other(error) => { + tracing::error!(?error); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn list_sessions( + handle: AppHandle, + project_id: &str, + earliest_timestamp_ms: Option, + ) -> Result, Error> { + let project_id = project_id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".to_string(), + })?; + handle + .state::() + .list(&project_id, earliest_timestamp_ms) + .map_err(Into::into) + } +} diff --git a/gitbutler-app/src/sessions/commands.rs b/gitbutler-app/src/sessions/commands.rs index 57e3a6a95..8b1378917 100644 --- a/gitbutler-app/src/sessions/commands.rs +++ b/gitbutler-app/src/sessions/commands.rs @@ -1,40 +1 @@ -use tauri::{AppHandle, Manager}; -use tracing::instrument; -use crate::error::{Code, Error}; - -use super::{ - controller::{Controller, ListError}, - Session, -}; - -impl From for Error { - fn from(value: ListError) -> Self { - match value { - ListError::UsersError(error) => Error::from(error), - ListError::ProjectsError(error) => Error::from(error), - ListError::ProjectRepositoryError(error) => Error::from(error), - ListError::Other(error) => { - tracing::error!(?error); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn list_sessions( - handle: AppHandle, - project_id: &str, - earliest_timestamp_ms: Option, -) -> Result, Error> { - let project_id = project_id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".to_string(), - })?; - handle - .state::() - .list(&project_id, earliest_timestamp_ms) - .map_err(Into::into) -} diff --git a/gitbutler-app/src/users.rs b/gitbutler-app/src/users.rs index 67c452d2f..27dd350fb 100644 --- a/gitbutler-app/src/users.rs +++ b/gitbutler-app/src/users.rs @@ -1,7 +1,82 @@ -pub mod commands; -pub mod controller; -pub mod storage; -mod user; +pub mod commands { + use tauri::{AppHandle, Manager}; + use tracing::instrument; -pub use controller::*; -pub use user::User; + use crate::{error::Error, sentry}; + + use gitbutler::{ + assets, + users::controller::{self, Controller, GetError}, + users::User, + }; + + impl From for Error { + fn from(value: GetError) -> Self { + match value { + GetError::Other(error) => { + tracing::error!(?error, "failed to get user"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_user(handle: AppHandle) -> Result, Error> { + let app = handle.state::(); + let proxy = handle.state::(); + + match app.get_user()? { + Some(user) => Ok(Some(proxy.proxy_user(user).await)), + None => Ok(None), + } + } + + impl From for Error { + fn from(value: controller::SetError) -> Self { + match value { + controller::SetError::Other(error) => { + tracing::error!(?error, "failed to set user"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn set_user(handle: AppHandle, user: User) -> Result { + let app = handle.state::(); + let proxy = handle.state::(); + + app.set_user(&user)?; + + sentry::configure_scope(Some(&user)); + + Ok(proxy.proxy_user(user).await) + } + + impl From for Error { + fn from(value: controller::DeleteError) -> Self { + match value { + controller::DeleteError::Other(error) => { + tracing::error!(?error, "failed to delete user"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn delete_user(handle: AppHandle) -> Result<(), Error> { + let app = handle.state::(); + + app.delete_user()?; + + sentry::configure_scope(None); + + Ok(()) + } +} diff --git a/gitbutler-app/src/users/commands.rs b/gitbutler-app/src/users/commands.rs deleted file mode 100644 index 70cef0153..000000000 --- a/gitbutler-app/src/users/commands.rs +++ /dev/null @@ -1,79 +0,0 @@ -use tauri::{AppHandle, Manager}; -use tracing::instrument; - -use crate::{assets, error::Error, sentry}; - -use super::{ - controller::{self, Controller, GetError}, - User, -}; - -impl From for Error { - fn from(value: GetError) -> Self { - match value { - GetError::Other(error) => { - tracing::error!(?error, "failed to get user"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_user(handle: AppHandle) -> Result, Error> { - let app = handle.state::(); - let proxy = handle.state::(); - - match app.get_user()? { - Some(user) => Ok(Some(proxy.proxy_user(user).await)), - None => Ok(None), - } -} - -impl From for Error { - fn from(value: controller::SetError) -> Self { - match value { - controller::SetError::Other(error) => { - tracing::error!(?error, "failed to set user"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn set_user(handle: AppHandle, user: User) -> Result { - let app = handle.state::(); - let proxy = handle.state::(); - - app.set_user(&user)?; - - sentry::configure_scope(Some(&user)); - - Ok(proxy.proxy_user(user).await) -} - -impl From for Error { - fn from(value: controller::DeleteError) -> Self { - match value { - controller::DeleteError::Other(error) => { - tracing::error!(?error, "failed to delete user"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn delete_user(handle: AppHandle) -> Result<(), Error> { - let app = handle.state::(); - - app.delete_user()?; - - sentry::configure_scope(None); - - Ok(()) -} diff --git a/gitbutler-app/src/virtual_branches.rs b/gitbutler-app/src/virtual_branches.rs index ae76e2ce8..3eca0e14c 100644 --- a/gitbutler-app/src/virtual_branches.rs +++ b/gitbutler-app/src/virtual_branches.rs @@ -1,31 +1,540 @@ -pub mod branch; -pub use branch::{Branch, BranchId}; -pub mod context; -pub mod target; +pub mod commands { + use anyhow::Context; + use tauri::{AppHandle, Manager}; + use tracing::instrument; -pub mod errors; + use gitbutler::error::{Code, Error}; -mod files; -pub use files::*; + use crate::watcher; + use gitbutler::askpass::AskpassBroker; + use gitbutler::virtual_branches::{RemoteBranch, RemoteBranchData}; + use gitbutler::{ + assets, git, projects, + projects::ProjectId, + virtual_branches::branch::{self, BranchId, BranchOwnershipClaims}, + virtual_branches::controller::{Controller, ControllerError}, + virtual_branches::BaseBranch, + virtual_branches::{RemoteBranchFile, VirtualBranches}, + }; -pub mod integration; -pub use integration::GITBUTLER_INTEGRATION_REFERENCE; + fn into_error>(value: ControllerError) -> Error { + match value { + ControllerError::User(error) => error, + ControllerError::Action(error) => error.into(), + ControllerError::VerifyError(error) => error.into(), + ControllerError::Other(error) => { + tracing::error!(?error, "failed to verify branch"); + Error::Unknown + } + } + } -mod base; -pub use base::*; + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn commit_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch: BranchId, + message: &str, + ownership: Option, + run_hooks: bool, + ) -> Result { + let oid = handle + .state::() + .create_commit(&project_id, &branch, message, ownership.as_ref(), run_hooks) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(oid) + } -pub mod controller; -pub use controller::Controller; + /// This is a test command. It retrieves the virtual branches state from the gitbutler repository (legacy state) and persists it into a flat TOML file + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn save_vbranches_state( + handle: AppHandle, + project_id: ProjectId, + branch_ids: Vec, + ) -> Result<(), Error> { + handle + .state::() + .save_vbranches_state(&project_id, branch_ids) + .await?; + return Ok(()); + } -pub mod commands; + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn list_virtual_branches( + handle: AppHandle, + project_id: ProjectId, + ) -> Result { + let (branches, uses_diff_context, skipped_files) = handle + .state::() + .list_virtual_branches(&project_id) + .await + .map_err(into_error)?; -mod iterator; -pub use iterator::BranchIterator as Iterator; + // Migration: If use_diff_context is not already set and if there are no vbranches, set use_diff_context to true + let has_active_branches = branches.iter().any(|branch| branch.active); + if !uses_diff_context && !has_active_branches { + let _ = handle + .state::() + .update(&projects::UpdateRequest { + id: project_id, + use_diff_context: Some(true), + ..Default::default() + }) + .await; + } -mod r#virtual; -pub use r#virtual::*; + let proxy = handle.state::(); + let branches = proxy.proxy_virtual_branches(branches).await; + Ok(VirtualBranches { + branches, + skipped_files, + }) + } -mod remote; -pub use remote::*; + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn create_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch: branch::BranchCreateRequest, + ) -> Result { + let branch_id = handle + .state::() + .create_virtual_branch(&project_id, &branch) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(branch_id) + } -mod state; + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn create_virtual_branch_from_branch( + handle: AppHandle, + project_id: ProjectId, + branch: git::Refname, + ) -> Result { + let branch_id = handle + .state::() + .create_virtual_branch_from_branch(&project_id, &branch) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(branch_id) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn merge_virtual_branch_upstream( + handle: AppHandle, + project_id: ProjectId, + branch: BranchId, + ) -> Result<(), Error> { + handle + .state::() + .merge_virtual_branch_upstream(&project_id, &branch) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_base_branch_data( + handle: AppHandle, + project_id: ProjectId, + ) -> Result, Error> { + if let Some(base_branch) = handle + .state::() + .get_base_branch_data(&project_id) + .await + .map_err(into_error)? + { + let proxy = handle.state::(); + let base_branch = proxy.proxy_base_branch(base_branch).await; + Ok(Some(base_branch)) + } else { + Ok(None) + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn set_base_branch( + handle: AppHandle, + project_id: ProjectId, + branch: &str, + ) -> Result { + let branch_name = format!("refs/remotes/{}", branch) + .parse() + .context("Invalid branch name")?; + let base_branch = handle + .state::() + .set_base_branch(&project_id, &branch_name) + .await + .map_err(into_error)?; + let base_branch = handle + .state::() + .proxy_base_branch(base_branch) + .await; + emit_vbranches(&handle, &project_id).await; + Ok(base_branch) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn update_base_branch(handle: AppHandle, project_id: ProjectId) -> Result<(), Error> { + handle + .state::() + .update_base_branch(&project_id) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn update_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch: branch::BranchUpdateRequest, + ) -> Result<(), Error> { + handle + .state::() + .update_virtual_branch(&project_id, branch) + .await + .map_err(into_error)?; + + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn delete_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch_id: BranchId, + ) -> Result<(), Error> { + handle + .state::() + .delete_virtual_branch(&project_id, &branch_id) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn apply_branch( + handle: AppHandle, + project_id: ProjectId, + branch: BranchId, + ) -> Result<(), Error> { + handle + .state::() + .apply_virtual_branch(&project_id, &branch) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn unapply_branch( + handle: AppHandle, + project_id: ProjectId, + branch: BranchId, + ) -> Result<(), Error> { + handle + .state::() + .unapply_virtual_branch(&project_id, &branch) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn unapply_ownership( + handle: AppHandle, + project_id: ProjectId, + ownership: BranchOwnershipClaims, + ) -> Result<(), Error> { + handle + .state::() + .unapply_ownership(&project_id, &ownership) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn reset_files( + handle: AppHandle, + project_id: ProjectId, + files: &str, + ) -> Result<(), Error> { + // convert files to Vec + let files = files + .split('\n') + .map(std::string::ToString::to_string) + .collect::>(); + handle + .state::() + .reset_files(&project_id, &files) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn push_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch_id: BranchId, + with_force: bool, + ) -> Result<(), Error> { + let askpass_broker = handle.state::(); + handle + .state::() + .push_virtual_branch( + &project_id, + &branch_id, + with_force, + Some((askpass_broker.inner().clone(), Some(branch_id))), + ) + .await + .map_err(|e| Error::UserError { + code: Code::Unknown, + message: e.to_string(), + })?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn can_apply_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch_id: BranchId, + ) -> Result { + handle + .state::() + .can_apply_virtual_branch(&project_id, &branch_id) + .await + .map_err(Into::into) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn can_apply_remote_branch( + handle: AppHandle, + project_id: ProjectId, + branch: git::RemoteRefname, + ) -> Result { + handle + .state::() + .can_apply_remote_branch(&project_id, &branch) + .await + .map_err(into_error) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn list_remote_commit_files( + handle: AppHandle, + project_id: ProjectId, + commit_oid: git::Oid, + ) -> Result, Error> { + handle + .state::() + .list_remote_commit_files(&project_id, commit_oid) + .await + .map_err(Into::into) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn reset_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch_id: BranchId, + target_commit_oid: git::Oid, + ) -> Result<(), Error> { + handle + .state::() + .reset_virtual_branch(&project_id, &branch_id, target_commit_oid) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn cherry_pick_onto_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch_id: BranchId, + target_commit_oid: git::Oid, + ) -> Result, Error> { + let oid = handle + .state::() + .cherry_pick(&project_id, &branch_id, target_commit_oid) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(oid) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn amend_virtual_branch( + handle: AppHandle, + project_id: ProjectId, + branch_id: BranchId, + ownership: BranchOwnershipClaims, + ) -> Result { + let oid = handle + .state::() + .amend(&project_id, &branch_id, &ownership) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(oid) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn list_remote_branches( + handle: tauri::AppHandle, + project_id: ProjectId, + ) -> Result, Error> { + let branches = handle + .state::() + .list_remote_branches(&project_id) + .await + .map_err(into_error)?; + Ok(branches) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_remote_branch_data( + handle: tauri::AppHandle, + project_id: ProjectId, + refname: git::Refname, + ) -> Result { + let branch_data = handle + .state::() + .get_remote_branch_data(&project_id, &refname) + .await + .map_err(into_error)?; + let branch_data = handle + .state::() + .proxy_remote_branch_data(branch_data) + .await; + Ok(branch_data) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn squash_branch_commit( + handle: tauri::AppHandle, + project_id: ProjectId, + branch_id: BranchId, + target_commit_oid: git::Oid, + ) -> Result<(), Error> { + handle + .state::() + .squash(&project_id, &branch_id, target_commit_oid) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn fetch_from_target( + handle: tauri::AppHandle, + project_id: ProjectId, + action: Option, + ) -> Result { + let askpass_broker = handle.state::().inner().clone(); + let base_branch = handle + .state::() + .fetch_from_target( + &project_id, + Some(( + askpass_broker, + action.unwrap_or_else(|| "unknown".to_string()), + )), + ) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(base_branch) + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn move_commit( + handle: tauri::AppHandle, + project_id: ProjectId, + commit_oid: git::Oid, + target_branch_id: BranchId, + ) -> Result<(), Error> { + handle + .state::() + .move_commit(&project_id, &target_branch_id, commit_oid) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + // XXX(qix-): Is this command used? + #[allow(dead_code)] + pub async fn update_commit_message( + handle: tauri::AppHandle, + project_id: ProjectId, + branch_id: BranchId, + commit_oid: git::Oid, + message: &str, + ) -> Result<(), Error> { + handle + .state::() + .update_commit_message(&project_id, &branch_id, commit_oid, message) + .await + .map_err(into_error)?; + emit_vbranches(&handle, &project_id).await; + Ok(()) + } + + async fn emit_vbranches(handle: &AppHandle, project_id: &projects::ProjectId) { + if let Err(error) = handle + .state::() + .post(watcher::Event::CalculateVirtualBranches(*project_id)) + .await + { + tracing::error!(?error); + } + } +} diff --git a/gitbutler-app/src/virtual_branches/commands.rs b/gitbutler-app/src/virtual_branches/commands.rs deleted file mode 100644 index 203ad5f99..000000000 --- a/gitbutler-app/src/virtual_branches/commands.rs +++ /dev/null @@ -1,518 +0,0 @@ -use crate::{projects::ProjectId, watcher}; -use anyhow::Context; -use tauri::{AppHandle, Manager}; -use tracing::instrument; - -use crate::{ - assets, - error::{Code, Error}, - git, projects, -}; - -use super::{ - branch::{BranchId, BranchOwnershipClaims}, - controller::{Controller, ControllerError}, - BaseBranch, RemoteBranchFile, -}; - -impl> From> for Error { - fn from(value: ControllerError) -> Self { - match value { - ControllerError::User(error) => error, - ControllerError::Action(error) => error.into(), - ControllerError::VerifyError(error) => error.into(), - ControllerError::Other(error) => { - tracing::error!(?error, "failed to verify branch"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn commit_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch: BranchId, - message: &str, - ownership: Option, - run_hooks: bool, -) -> Result { - let oid = handle - .state::() - .create_commit(&project_id, &branch, message, ownership.as_ref(), run_hooks) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(oid) -} - -/// This is a test command. It retrieves the virtual branches state from the gitbutler repository (legacy state) and persists it into a flat TOML file -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn save_vbranches_state( - handle: AppHandle, - project_id: ProjectId, - branch_ids: Vec, -) -> Result<(), Error> { - handle - .state::() - .save_vbranches_state(&project_id, branch_ids) - .await?; - return Ok(()); -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn list_virtual_branches( - handle: AppHandle, - project_id: ProjectId, -) -> Result { - let (branches, uses_diff_context, skipped_files) = handle - .state::() - .list_virtual_branches(&project_id) - .await?; - - // Migration: If use_diff_context is not already set and if there are no vbranches, set use_diff_context to true - let has_active_branches = branches.iter().any(|branch| branch.active); - if !uses_diff_context && !has_active_branches { - let _ = handle - .state::() - .update(&projects::UpdateRequest { - id: project_id, - use_diff_context: Some(true), - ..Default::default() - }) - .await; - } - - let proxy = handle.state::(); - let branches = proxy.proxy_virtual_branches(branches).await; - Ok(super::VirtualBranches { - branches, - skipped_files, - }) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn create_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch: super::branch::BranchCreateRequest, -) -> Result { - let branch_id = handle - .state::() - .create_virtual_branch(&project_id, &branch) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(branch_id) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn create_virtual_branch_from_branch( - handle: AppHandle, - project_id: ProjectId, - branch: git::Refname, -) -> Result { - let branch_id = handle - .state::() - .create_virtual_branch_from_branch(&project_id, &branch) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(branch_id) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn merge_virtual_branch_upstream( - handle: AppHandle, - project_id: ProjectId, - branch: BranchId, -) -> Result<(), Error> { - handle - .state::() - .merge_virtual_branch_upstream(&project_id, &branch) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_base_branch_data( - handle: AppHandle, - project_id: ProjectId, -) -> Result, Error> { - if let Some(base_branch) = handle - .state::() - .get_base_branch_data(&project_id) - .await? - { - let proxy = handle.state::(); - let base_branch = proxy.proxy_base_branch(base_branch).await; - Ok(Some(base_branch)) - } else { - Ok(None) - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn set_base_branch( - handle: AppHandle, - project_id: ProjectId, - branch: &str, -) -> Result { - let branch_name = format!("refs/remotes/{}", branch) - .parse() - .context("Invalid branch name")?; - let base_branch = handle - .state::() - .set_base_branch(&project_id, &branch_name) - .await?; - let base_branch = handle - .state::() - .proxy_base_branch(base_branch) - .await; - emit_vbranches(&handle, &project_id).await; - Ok(base_branch) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn update_base_branch(handle: AppHandle, project_id: ProjectId) -> Result<(), Error> { - handle - .state::() - .update_base_branch(&project_id) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn update_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch: super::branch::BranchUpdateRequest, -) -> Result<(), Error> { - handle - .state::() - .update_virtual_branch(&project_id, branch) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn delete_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch_id: BranchId, -) -> Result<(), Error> { - handle - .state::() - .delete_virtual_branch(&project_id, &branch_id) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn apply_branch( - handle: AppHandle, - project_id: ProjectId, - branch: BranchId, -) -> Result<(), Error> { - handle - .state::() - .apply_virtual_branch(&project_id, &branch) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn unapply_branch( - handle: AppHandle, - project_id: ProjectId, - branch: BranchId, -) -> Result<(), Error> { - handle - .state::() - .unapply_virtual_branch(&project_id, &branch) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn unapply_ownership( - handle: AppHandle, - project_id: ProjectId, - ownership: BranchOwnershipClaims, -) -> Result<(), Error> { - handle - .state::() - .unapply_ownership(&project_id, &ownership) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn reset_files( - handle: AppHandle, - project_id: ProjectId, - files: &str, -) -> Result<(), Error> { - // convert files to Vec - let files = files - .split('\n') - .map(std::string::ToString::to_string) - .collect::>(); - handle - .state::() - .reset_files(&project_id, &files) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn push_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch_id: BranchId, - with_force: bool, -) -> Result<(), Error> { - let askpass_broker = handle.state::(); - handle - .state::() - .push_virtual_branch( - &project_id, - &branch_id, - with_force, - Some((askpass_broker.inner().clone(), Some(branch_id))), - ) - .await - .map_err(|e| Error::UserError { - code: Code::Unknown, - message: e.to_string(), - })?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn can_apply_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch_id: BranchId, -) -> Result { - handle - .state::() - .can_apply_virtual_branch(&project_id, &branch_id) - .await - .map_err(Into::into) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn can_apply_remote_branch( - handle: AppHandle, - project_id: ProjectId, - branch: git::RemoteRefname, -) -> Result { - handle - .state::() - .can_apply_remote_branch(&project_id, &branch) - .await - .map_err(Into::into) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn list_remote_commit_files( - handle: AppHandle, - project_id: ProjectId, - commit_oid: git::Oid, -) -> Result, Error> { - handle - .state::() - .list_remote_commit_files(&project_id, commit_oid) - .await - .map_err(Into::into) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn reset_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch_id: BranchId, - target_commit_oid: git::Oid, -) -> Result<(), Error> { - handle - .state::() - .reset_virtual_branch(&project_id, &branch_id, target_commit_oid) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn cherry_pick_onto_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch_id: BranchId, - target_commit_oid: git::Oid, -) -> Result, Error> { - let oid = handle - .state::() - .cherry_pick(&project_id, &branch_id, target_commit_oid) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(oid) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn amend_virtual_branch( - handle: AppHandle, - project_id: ProjectId, - branch_id: BranchId, - ownership: BranchOwnershipClaims, -) -> Result { - let oid = handle - .state::() - .amend(&project_id, &branch_id, &ownership) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(oid) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn list_remote_branches( - handle: tauri::AppHandle, - project_id: ProjectId, -) -> Result, Error> { - let branches = handle - .state::() - .list_remote_branches(&project_id) - .await?; - Ok(branches) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_remote_branch_data( - handle: tauri::AppHandle, - project_id: ProjectId, - refname: git::Refname, -) -> Result { - let branch_data = handle - .state::() - .get_remote_branch_data(&project_id, &refname) - .await?; - let branch_data = handle - .state::() - .proxy_remote_branch_data(branch_data) - .await; - Ok(branch_data) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn squash_branch_commit( - handle: tauri::AppHandle, - project_id: ProjectId, - branch_id: BranchId, - target_commit_oid: git::Oid, -) -> Result<(), Error> { - handle - .state::() - .squash(&project_id, &branch_id, target_commit_oid) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn fetch_from_target( - handle: tauri::AppHandle, - project_id: ProjectId, - action: Option, -) -> Result { - let askpass_broker = handle - .state::() - .inner() - .clone(); - let base_branch = handle - .state::() - .fetch_from_target( - &project_id, - Some(( - askpass_broker, - action.unwrap_or_else(|| "unknown".to_string()), - )), - ) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(base_branch) -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn move_commit( - handle: tauri::AppHandle, - project_id: ProjectId, - commit_oid: git::Oid, - target_branch_id: BranchId, -) -> Result<(), Error> { - handle - .state::() - .move_commit(&project_id, &target_branch_id, commit_oid) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -// XXX(qix-): Is this command used? -#[allow(dead_code)] -pub async fn update_commit_message( - handle: tauri::AppHandle, - project_id: ProjectId, - branch_id: BranchId, - commit_oid: git::Oid, - message: &str, -) -> Result<(), Error> { - handle - .state::() - .update_commit_message(&project_id, &branch_id, commit_oid, message) - .await?; - emit_vbranches(&handle, &project_id).await; - Ok(()) -} - -async fn emit_vbranches(handle: &AppHandle, project_id: &projects::ProjectId) { - if let Err(error) = handle - .state::() - .post(watcher::Event::CalculateVirtualBranches(*project_id)) - .await - { - tracing::error!(?error); - } -} diff --git a/gitbutler-app/src/watcher.rs b/gitbutler-app/src/watcher.rs index ac7c41b35..05e96e163 100644 --- a/gitbutler-app/src/watcher.rs +++ b/gitbutler-app/src/watcher.rs @@ -17,7 +17,7 @@ use tokio::{ }; use tokio_util::sync::CancellationToken; -use crate::projects::{self, ProjectId}; +use gitbutler::projects::{self, Project, ProjectId}; #[derive(Clone)] pub struct Watchers { @@ -80,6 +80,25 @@ impl Watchers { } } +#[async_trait::async_trait] +impl gitbutler::projects::Watchers for Watchers { + fn watch(&self, project: &Project) -> Result<()> { + Watchers::watch(self, project) + } + + async fn stop(&self, id: ProjectId) -> Result<()> { + Watchers::stop(self, &id).await + } + + async fn fetch(&self, id: ProjectId) -> Result<()> { + self.post(Event::FetchGitbutlerData(id)).await + } + + async fn push(&self, id: ProjectId) -> Result<()> { + self.post(Event::PushGitbutlerData(id)).await + } +} + #[derive(Clone)] struct Watcher { inner: Arc, diff --git a/gitbutler-app/src/watcher/dispatchers.rs b/gitbutler-app/src/watcher/dispatchers.rs index 20530e1c7..2c2033683 100644 --- a/gitbutler-app/src/watcher/dispatchers.rs +++ b/gitbutler-app/src/watcher/dispatchers.rs @@ -10,7 +10,7 @@ use tokio::{ }; use tokio_util::sync::CancellationToken; -use crate::projects::ProjectId; +use gitbutler::projects::ProjectId; use super::events; diff --git a/gitbutler-app/src/watcher/dispatchers/file_change.rs b/gitbutler-app/src/watcher/dispatchers/file_change.rs index 47b33d0c7..2cbae1d99 100644 --- a/gitbutler-app/src/watcher/dispatchers/file_change.rs +++ b/gitbutler-app/src/watcher/dispatchers/file_change.rs @@ -13,7 +13,8 @@ use tokio::{ task, }; -use crate::{git, projects::ProjectId, watcher::events}; +use crate::watcher::events; +use gitbutler::{git, projects::ProjectId}; #[derive(Debug, Clone)] pub struct Dispatcher { diff --git a/gitbutler-app/src/watcher/events.rs b/gitbutler-app/src/watcher/events.rs index 514d41fed..ec861c87e 100644 --- a/gitbutler-app/src/watcher/events.rs +++ b/gitbutler-app/src/watcher/events.rs @@ -1,12 +1,14 @@ use std::{fmt::Display, path}; -use crate::{ - analytics, deltas, events, +use gitbutler::{ + deltas, projects::ProjectId, reader, sessions::{self, SessionId}, }; +use crate::{analytics, events}; + #[derive(Debug, PartialEq, Clone)] pub enum Event { Flush(ProjectId, sessions::Session), diff --git a/gitbutler-app/src/watcher/handlers/analytics_handler.rs b/gitbutler-app/src/watcher/handlers/analytics_handler.rs index 4361080e3..22057118c 100644 --- a/gitbutler-app/src/watcher/handlers/analytics_handler.rs +++ b/gitbutler-app/src/watcher/handlers/analytics_handler.rs @@ -1,7 +1,8 @@ use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; -use crate::{analytics, users}; +use crate::analytics; +use gitbutler::users; use super::events; diff --git a/gitbutler-app/src/watcher/handlers/calculate_deltas_handler.rs b/gitbutler-app/src/watcher/handlers/calculate_deltas_handler.rs index 907fe0357..454c651d1 100644 --- a/gitbutler-app/src/watcher/handlers/calculate_deltas_handler.rs +++ b/gitbutler-app/src/watcher/handlers/calculate_deltas_handler.rs @@ -3,7 +3,7 @@ use std::{path, vec}; use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; -use crate::{ +use gitbutler::{ deltas, gb_repository, project_repository, projects::{self, ProjectId}, reader, sessions, users, diff --git a/gitbutler-app/src/watcher/handlers/caltulate_virtual_branches_handler.rs b/gitbutler-app/src/watcher/handlers/caltulate_virtual_branches_handler.rs index 3635c78d3..4b0b6d691 100644 --- a/gitbutler-app/src/watcher/handlers/caltulate_virtual_branches_handler.rs +++ b/gitbutler-app/src/watcher/handlers/caltulate_virtual_branches_handler.rs @@ -9,8 +9,9 @@ use governor::{ use tauri::{AppHandle, Manager}; use tokio::sync::Mutex; -use crate::{ - assets, events as app_events, +use crate::events as app_events; +use gitbutler::{ + assets, projects::ProjectId, virtual_branches::{self, controller::ControllerError, VirtualBranches}, }; diff --git a/gitbutler-app/src/watcher/handlers/fetch_gitbutler_data.rs b/gitbutler-app/src/watcher/handlers/fetch_gitbutler_data.rs index 7d8f7e1a3..44950abeb 100644 --- a/gitbutler-app/src/watcher/handlers/fetch_gitbutler_data.rs +++ b/gitbutler-app/src/watcher/handlers/fetch_gitbutler_data.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; use tokio::sync::Mutex; -use crate::{gb_repository, project_repository, projects, projects::ProjectId, users}; +use gitbutler::{gb_repository, project_repository, projects, projects::ProjectId, users}; use super::events; diff --git a/gitbutler-app/src/watcher/handlers/filter_ignored_files.rs b/gitbutler-app/src/watcher/handlers/filter_ignored_files.rs index 79ae05f59..4ddb69375 100644 --- a/gitbutler-app/src/watcher/handlers/filter_ignored_files.rs +++ b/gitbutler-app/src/watcher/handlers/filter_ignored_files.rs @@ -10,7 +10,7 @@ use governor::{ use tauri::{AppHandle, Manager}; use tokio::sync::Mutex; -use crate::{ +use gitbutler::{ project_repository, projects::{self, ProjectId}, }; diff --git a/gitbutler-app/src/watcher/handlers/flush_session.rs b/gitbutler-app/src/watcher/handlers/flush_session.rs index ded656a70..b3405d901 100644 --- a/gitbutler-app/src/watcher/handlers/flush_session.rs +++ b/gitbutler-app/src/watcher/handlers/flush_session.rs @@ -4,7 +4,9 @@ use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; use tokio::sync::Mutex; -use crate::{gb_repository, project_repository, projects, projects::ProjectId, sessions, users}; +use gitbutler::{ + gb_repository, project_repository, projects, projects::ProjectId, sessions, users, +}; use super::events; diff --git a/gitbutler-app/src/watcher/handlers/git_file_change.rs b/gitbutler-app/src/watcher/handlers/git_file_change.rs index 6e38ed208..13ab970d8 100644 --- a/gitbutler-app/src/watcher/handlers/git_file_change.rs +++ b/gitbutler-app/src/watcher/handlers/git_file_change.rs @@ -3,8 +3,9 @@ use std::path; use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; -use crate::{ - analytics, events as app_events, gb_repository, git, project_repository, +use crate::{analytics, events as app_events}; +use gitbutler::{ + gb_repository, git, project_repository, projects::{self, ProjectId}, users, }; diff --git a/gitbutler-app/src/watcher/handlers/index_handler.rs b/gitbutler-app/src/watcher/handlers/index_handler.rs index 54d855a86..7b5a1655b 100644 --- a/gitbutler-app/src/watcher/handlers/index_handler.rs +++ b/gitbutler-app/src/watcher/handlers/index_handler.rs @@ -3,8 +3,9 @@ use std::path; use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; -use crate::{ - deltas, events as app_events, gb_repository, project_repository, +use crate::events as app_events; +use gitbutler::{ + deltas, gb_repository, project_repository, projects::{self, ProjectId}, sessions::{self, SessionId}, users, diff --git a/gitbutler-app/src/watcher/handlers/push_gitbutler_data.rs b/gitbutler-app/src/watcher/handlers/push_gitbutler_data.rs index 58fed9b4c..3d3b58961 100644 --- a/gitbutler-app/src/watcher/handlers/push_gitbutler_data.rs +++ b/gitbutler-app/src/watcher/handlers/push_gitbutler_data.rs @@ -4,9 +4,9 @@ use std::sync::{Arc, Mutex, TryLockError}; use anyhow::{Context, Result}; use tauri::{AppHandle, Manager}; -use crate::gb_repository::RemoteError; -use crate::projects::ProjectId; -use crate::{gb_repository, project_repository, projects, users}; +use gitbutler::gb_repository::RemoteError; +use gitbutler::projects::ProjectId; +use gitbutler::{gb_repository, project_repository, projects, users}; use super::events; diff --git a/gitbutler-app/src/watcher/handlers/push_project_to_gitbutler.rs b/gitbutler-app/src/watcher/handlers/push_project_to_gitbutler.rs index 9e561c4cd..2a0406bc2 100644 --- a/gitbutler-app/src/watcher/handlers/push_project_to_gitbutler.rs +++ b/gitbutler-app/src/watcher/handlers/push_project_to_gitbutler.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use tauri::{AppHandle, Manager}; use tokio::sync::Mutex; -use crate::{ +use gitbutler::{ gb_repository, git::{self, Oid, Repository}, project_repository, @@ -129,9 +129,9 @@ impl Handler { async fn push_target( state: &State, project_repository: &project_repository::Repository, - default_target: &crate::virtual_branches::target::Target, + default_target: &gitbutler::virtual_branches::target::Target, gb_code_last_commit: Option, - project_id: &crate::id::Id, + project_id: &gitbutler::id::Id, user: &Option, ) -> Result<(), project_repository::RemoteError> { let ids = batch_rev_walk( @@ -181,7 +181,7 @@ impl Handler { async fn update_project( state: &State, - project_id: &crate::id::Id, + project_id: &gitbutler::id::Id, id: &Oid, ) -> Result<(), project_repository::RemoteError> { state @@ -211,7 +211,7 @@ struct State { fn push_all_refs( project_repository: &project_repository::Repository, user: &Option, - project_id: &crate::id::Id, + project_id: &gitbutler::id::Id, ) -> Result<(), project_repository::RemoteError> { let gb_references = collect_refs(project_repository)?; diff --git a/gitbutler-app/src/zip.rs b/gitbutler-app/src/zip.rs index 9750c112f..8f2e654ae 100644 --- a/gitbutler-app/src/zip.rs +++ b/gitbutler-app/src/zip.rs @@ -1,165 +1,87 @@ -pub mod commands; -mod controller; -pub use controller::Controller; +pub mod commands { + #![allow(clippy::used_underscore_binding)] + use std::path; -use std::{ - fs, - io::{self, Read, Write}, - path, time, -}; + use tauri::{AppHandle, Manager}; + use tracing::instrument; -use anyhow::{Context, Result}; -use sha2::{Digest, Sha256}; -use walkdir::{DirEntry, WalkDir}; -use zip::{result::ZipError, write, CompressionMethod, ZipWriter}; + use crate::error::{Code, Error}; -#[derive(Clone)] -pub struct Zipper { - cache: path::PathBuf, -} + use gitbutler::zip::controller; -impl Zipper { - pub fn new>(cache_dir: P) -> Self { - let cache = cache_dir.as_ref().to_path_buf().join("archives"); - Self { cache } - } - - // takes a path to create zip of, returns path of a created archive. - pub fn zip>(&self, path: P) -> Result { - let path = path.as_ref(); - if !path.exists() { - return Err(anyhow::anyhow!("{} does not exist", path.display())); - } - if !path.is_dir() { - return Err(anyhow::anyhow!("{} is not a directory", path.display())); - } - let path_hash = calculate_path_hash(path)?; - fs::create_dir_all(&self.cache).context("failed to create cache dir")?; - let archive_path = self.cache.join(format!("{}.zip", path_hash)); - if !archive_path.exists() { - doit(path, &archive_path, CompressionMethod::Bzip2)?; - } - Ok(archive_path) - } -} - -fn doit>( - src_dir: P, - dst_file: P, - method: zip::CompressionMethod, -) -> zip::result::ZipResult<()> { - let src = src_dir.as_ref(); - let dst = dst_file.as_ref(); - if !src.is_dir() { - return Err(ZipError::FileNotFound); - } - - let file = fs::File::create(dst).unwrap(); - - let walkdir = WalkDir::new(src); - let it = walkdir.into_iter(); - - zip_dir(&mut it.filter_map(Result::ok), src, file, method)?; - - Ok(()) -} - -fn zip_dir( - it: &mut dyn Iterator, - prefix: &path::Path, - writer: T, - method: zip::CompressionMethod, -) -> zip::result::ZipResult<()> -where - T: io::Write + io::Seek, -{ - let mut zip = ZipWriter::new(writer); - let options = write::FileOptions::default() - .compression_method(method) - .unix_permissions(0o755); - - let mut buffer = Vec::new(); - for entry in it { - let path = entry.path(); - let name = path.strip_prefix(prefix).unwrap(); - - // Write file or directory explicitly - // Some unzip tools unzip files with directory paths correctly, some do not! - if path.is_file() { - #[allow(deprecated)] - zip.start_file_from_path(name, options)?; - let mut f = fs::File::open(path)?; - - f.read_to_end(&mut buffer)?; - zip.write_all(&buffer)?; - buffer.clear(); - } else if !name.as_os_str().is_empty() { - // Only if not root! Avoids path spec / warning - // and mapname conversion failed error on unzip - #[allow(deprecated)] - zip.add_directory_from_path(name, options)?; + impl From for Error { + fn from(error: controller::ArchiveError) -> Self { + match error { + controller::ArchiveError::GetProject(error) => error.into(), + controller::ArchiveError::Other(error) => { + tracing::error!(?error, "failed to archive project"); + Error::Unknown + } + } } } - zip.finish()?; - Result::Ok(()) -} -// returns hash of a path by calculating metadata hash of all files in it. -fn calculate_path_hash>(path: P) -> Result { - let path = path.as_ref(); - let mut hasher = Sha256::new(); - - if path.is_dir() { - let entries = fs::read_dir(path)?; - let mut entry_paths: Vec<_> = entries - .filter_map(|entry| entry.ok().map(|e| e.path())) - .collect(); - entry_paths.sort(); - - for entry_path in entry_paths { - file_hash(&mut hasher, &entry_path).with_context(|| { - format!( - "failed to calculate hash of file {}", - entry_path.to_str().unwrap() - ) - })?; - } - } else if path.is_file() { - file_hash(&mut hasher, path).with_context(|| { - format!( - "failed to calculate hash of file {}", - path.to_str().unwrap() - ) + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_project_archive_path( + handle: AppHandle, + project_id: &str, + ) -> Result { + let project_id = project_id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".into(), })?; + handle + .state::() + .archive(&project_id) + .map_err(Into::into) } - Ok(format!("{:X}", hasher.finalize())) -} + impl From for Error { + fn from(value: controller::DataArchiveError) -> Self { + match value { + controller::DataArchiveError::GetProject(error) => error.into(), + controller::DataArchiveError::Other(error) => { + tracing::error!(?error, "failed to archive project data"); + Error::Unknown + } + } + } + } -fn file_hash>(digest: &mut Sha256, path: P) -> Result<()> { - let path = path.as_ref(); - let metadata = fs::metadata(path).context("failed to get metadata")?; - digest.update(path.to_str().unwrap().as_bytes()); - digest.update(metadata.len().to_string().as_bytes()); - digest.update( - metadata - .modified() - .unwrap_or(time::UNIX_EPOCH) - .duration_since(time::UNIX_EPOCH) - .unwrap() - .as_secs() - .to_string() - .as_bytes(), - ); - digest.update( - metadata - .created() - .unwrap_or(time::UNIX_EPOCH) - .duration_since(time::UNIX_EPOCH) - .unwrap() - .as_secs() - .to_string() - .as_bytes(), - ); - Ok(()) + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_project_data_archive_path( + handle: AppHandle, + project_id: &str, + ) -> Result { + let project_id = project_id.parse().map_err(|_| Error::UserError { + code: Code::Validation, + message: "Malformed project id".into(), + })?; + handle + .state::() + .data_archive(&project_id) + .map_err(Into::into) + } + + impl From for Error { + fn from(error: controller::LogsArchiveError) -> Self { + match error { + controller::LogsArchiveError::Other(error) => { + tracing::error!(?error, "failed to archive logs"); + Error::Unknown + } + } + } + } + + #[tauri::command(async)] + #[instrument(skip(handle))] + pub async fn get_logs_archive_path(handle: AppHandle) -> Result { + handle + .state::() + .logs_archive() + .map_err(Into::into) + } } diff --git a/gitbutler-app/src/zip/commands.rs b/gitbutler-app/src/zip/commands.rs deleted file mode 100644 index aabbe62c2..000000000 --- a/gitbutler-app/src/zip/commands.rs +++ /dev/null @@ -1,85 +0,0 @@ -#![allow(clippy::used_underscore_binding)] -use std::path; - -use tauri::{AppHandle, Manager}; -use tracing::instrument; - -use crate::error::{Code, Error}; - -use super::controller; - -impl From for Error { - fn from(error: controller::ArchiveError) -> Self { - match error { - controller::ArchiveError::GetProject(error) => error.into(), - controller::ArchiveError::Other(error) => { - tracing::error!(?error, "failed to archive project"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_project_archive_path( - handle: AppHandle, - project_id: &str, -) -> Result { - let project_id = project_id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".into(), - })?; - handle - .state::() - .archive(&project_id) - .map_err(Into::into) -} - -impl From for Error { - fn from(value: controller::DataArchiveError) -> Self { - match value { - controller::DataArchiveError::GetProject(error) => error.into(), - controller::DataArchiveError::Other(error) => { - tracing::error!(?error, "failed to archive project data"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_project_data_archive_path( - handle: AppHandle, - project_id: &str, -) -> Result { - let project_id = project_id.parse().map_err(|_| Error::UserError { - code: Code::Validation, - message: "Malformed project id".into(), - })?; - handle - .state::() - .data_archive(&project_id) - .map_err(Into::into) -} - -impl From for Error { - fn from(error: controller::LogsArchiveError) -> Self { - match error { - controller::LogsArchiveError::Other(error) => { - tracing::error!(?error, "failed to archive logs"); - Error::Unknown - } - } - } -} - -#[tauri::command(async)] -#[instrument(skip(handle))] -pub async fn get_logs_archive_path(handle: AppHandle) -> Result { - handle - .state::() - .logs_archive() - .map_err(Into::into) -} diff --git a/gitbutler-app/tests/app.rs b/gitbutler-app/tests/app.rs index 2369cbe1a..1ddd035f9 100644 --- a/gitbutler-app/tests/app.rs +++ b/gitbutler-app/tests/app.rs @@ -1,261 +1,4 @@ -const VAR_NO_CLEANUP: &str = "GITBUTLER_TESTS_NO_CLEANUP"; - -pub(crate) mod common; -mod suite { - mod gb_repository; - mod projects; - mod virtual_branches; -} - -mod database; -mod deltas; -mod gb_repository; -mod git; -mod keys; -mod lock; -mod reader; -mod sessions; -mod types; -pub mod virtual_branches; +// TODO(ST): move test code into crate and use that, but wait for `crates/` +#[path = "../../tests/shared/mod.rs"] +pub mod shared; mod watcher; -mod zip; - -use std::path::{Path, PathBuf}; -use std::{collections::HashMap, fs}; - -use tempfile::{tempdir, TempDir}; - -pub struct Suite { - pub local_app_data: Option, - pub storage: gitbutler_app::storage::Storage, - pub users: gitbutler_app::users::Controller, - pub projects: gitbutler_app::projects::Controller, - pub keys: gitbutler_app::keys::Controller, -} - -impl Drop for Suite { - fn drop(&mut self) { - if std::env::var_os(VAR_NO_CLEANUP).is_some() { - let _ = self.local_app_data.take().unwrap().into_path(); - } - } -} - -impl Default for Suite { - fn default() -> Self { - let local_app_data = temp_dir(); - let storage = gitbutler_app::storage::Storage::new(&local_app_data); - let users = gitbutler_app::users::Controller::from_path(&local_app_data); - let projects = gitbutler_app::projects::Controller::from_path(&local_app_data); - let keys = gitbutler_app::keys::Controller::from_path(&local_app_data); - Self { - storage, - local_app_data: Some(local_app_data), - users, - projects, - keys, - } - } -} - -impl Suite { - pub fn local_app_data(&self) -> &Path { - self.local_app_data.as_ref().unwrap().path() - } - pub fn sign_in(&self) -> gitbutler_app::users::User { - let user = gitbutler_app::users::User { - name: Some("test".to_string()), - email: "test@email.com".to_string(), - access_token: "token".to_string(), - ..Default::default() - }; - self.users.set_user(&user).expect("failed to add user"); - user - } - - fn project(&self, fs: HashMap) -> (gitbutler_app::projects::Project, TempDir) { - let (repository, tmp) = test_repository(); - for (path, contents) in fs { - if let Some(parent) = path.parent() { - fs::create_dir_all(repository.path().parent().unwrap().join(parent)) - .expect("failed to create dir"); - } - fs::write( - repository.path().parent().unwrap().join(&path), - contents.as_bytes(), - ) - .expect("failed to write file"); - } - commit_all(&repository); - - ( - self.projects - .add(repository.path().parent().unwrap()) - .expect("failed to add project"), - tmp, - ) - } - - pub fn new_case_with_files(&self, fs: HashMap) -> Case { - let (project, project_tmp) = self.project(fs); - Case::new(self, project, project_tmp) - } - - pub fn new_case(&self) -> Case { - self.new_case_with_files(HashMap::new()) - } -} - -pub struct Case<'a> { - suite: &'a Suite, - pub project: gitbutler_app::projects::Project, - pub project_repository: gitbutler_app::project_repository::Repository, - pub gb_repository: gitbutler_app::gb_repository::Repository, - pub credentials: gitbutler_app::git::credentials::Helper, - /// The directory containing the `project_repository` - project_tmp: Option, -} - -impl Drop for Case<'_> { - fn drop(&mut self) { - if let Some(tmp) = self - .project_tmp - .take() - .filter(|_| std::env::var_os(VAR_NO_CLEANUP).is_some()) - { - let _ = tmp.into_path(); - } - } -} - -impl<'a> Case<'a> { - fn new( - suite: &'a Suite, - project: gitbutler_app::projects::Project, - project_tmp: TempDir, - ) -> Case<'a> { - let project_repository = gitbutler_app::project_repository::Repository::open(&project) - .expect("failed to create project repository"); - let gb_repository = gitbutler_app::gb_repository::Repository::open( - suite.local_app_data(), - &project_repository, - None, - ) - .expect("failed to open gb repository"); - let credentials = - gitbutler_app::git::credentials::Helper::from_path(suite.local_app_data()); - Case { - suite, - project, - gb_repository, - project_repository, - project_tmp: Some(project_tmp), - credentials, - } - } - - pub fn refresh(mut self) -> Self { - let project = self - .suite - .projects - .get(&self.project.id) - .expect("failed to get project"); - let project_repository = gitbutler_app::project_repository::Repository::open(&project) - .expect("failed to create project repository"); - let user = self.suite.users.get_user().expect("failed to get user"); - let credentials = - gitbutler_app::git::credentials::Helper::from_path(self.suite.local_app_data()); - Self { - suite: self.suite, - gb_repository: gitbutler_app::gb_repository::Repository::open( - self.suite.local_app_data(), - &project_repository, - user.as_ref(), - ) - .expect("failed to open gb repository"), - credentials, - project_repository, - project, - project_tmp: self.project_tmp.take(), - } - } -} - -pub fn test_database() -> (gitbutler_app::database::Database, TempDir) { - let tmp = temp_dir(); - let db = gitbutler_app::database::Database::open_in_directory(&tmp).unwrap(); - (db, tmp) -} - -pub fn temp_dir() -> TempDir { - tempdir().unwrap() -} - -pub fn empty_bare_repository() -> (gitbutler_app::git::Repository, TempDir) { - let tmp = temp_dir(); - ( - gitbutler_app::git::Repository::init_opts(&tmp, &init_opts_bare()) - .expect("failed to init repository"), - tmp, - ) -} - -pub fn test_repository() -> (gitbutler_app::git::Repository, TempDir) { - let tmp = temp_dir(); - let repository = gitbutler_app::git::Repository::init_opts(&tmp, &init_opts()) - .expect("failed to init repository"); - let mut index = repository.index().expect("failed to get index"); - let oid = index.write_tree().expect("failed to write tree"); - let signature = gitbutler_app::git::Signature::now("test", "test@email.com").unwrap(); - repository - .commit( - Some(&"refs/heads/master".parse().unwrap()), - &signature, - &signature, - "Initial commit", - &repository.find_tree(oid).expect("failed to find tree"), - &[], - ) - .expect("failed to commit"); - (repository, tmp) -} - -pub fn commit_all(repository: &gitbutler_app::git::Repository) -> gitbutler_app::git::Oid { - let mut index = repository.index().expect("failed to get index"); - index - .add_all(["."], git2::IndexAddOption::DEFAULT, None) - .expect("failed to add all"); - index.write().expect("failed to write index"); - let oid = index.write_tree().expect("failed to write tree"); - let signature = gitbutler_app::git::Signature::now("test", "test@email.com").unwrap(); - let head = repository.head().expect("failed to get head"); - let commit_oid = repository - .commit( - Some(&head.name().unwrap()), - &signature, - &signature, - "some commit", - &repository.find_tree(oid).expect("failed to find tree"), - &[&repository - .find_commit( - repository - .refname_to_id("HEAD") - .expect("failed to get head"), - ) - .expect("failed to find commit")], - ) - .expect("failed to commit"); - commit_oid -} - -fn init_opts() -> git2::RepositoryInitOptions { - let mut opts = git2::RepositoryInitOptions::new(); - opts.initial_head("master"); - opts -} - -pub fn init_opts_bare() -> git2::RepositoryInitOptions { - let mut opts = init_opts(); - opts.bare(true); - opts -} diff --git a/gitbutler-app/tests/watcher/handler/calculate_delta_handler.rs b/gitbutler-app/tests/watcher/handler/calculate_delta_handler.rs index 443026fb3..af3cf34e9 100644 --- a/gitbutler-app/tests/watcher/handler/calculate_delta_handler.rs +++ b/gitbutler-app/tests/watcher/handler/calculate_delta_handler.rs @@ -7,13 +7,13 @@ use std::{ use once_cell::sync::Lazy; -use crate::{commit_all, Case, Suite}; -use gitbutler_app::watcher::handlers::calculate_deltas_handler::Handler; -use gitbutler_app::{ +use crate::shared::{commit_all, Case, Suite}; +use gitbutler::{ deltas::{self, operations::Operation}, reader, sessions, virtual_branches::{self, branch}, }; +use gitbutler_app::watcher::handlers::calculate_deltas_handler::Handler; use self::branch::BranchId; @@ -663,7 +663,7 @@ fn should_persist_branches_targets_state_between_sessions() -> Result<()> { let branches = virtual_branches::Iterator::new(&session_reader) .unwrap() - .collect::, gitbutler_app::reader::Error>>() + .collect::, gitbutler::reader::Error>>() .unwrap() .into_iter() .collect::>(); @@ -719,7 +719,7 @@ fn should_restore_branches_targets_state_from_head_session() -> Result<()> { let branches = virtual_branches::Iterator::new(&session_reader) .unwrap() - .collect::, gitbutler_app::reader::Error>>() + .collect::, gitbutler::reader::Error>>() .unwrap() .into_iter() .collect::>(); diff --git a/gitbutler-app/tests/watcher/handler/fetch_gitbutler_data.rs b/gitbutler-app/tests/watcher/handler/fetch_gitbutler_data.rs index 93dc2a02c..d147f4b13 100644 --- a/gitbutler-app/tests/watcher/handler/fetch_gitbutler_data.rs +++ b/gitbutler-app/tests/watcher/handler/fetch_gitbutler_data.rs @@ -1,10 +1,10 @@ use std::time::SystemTime; -use gitbutler_app::projects; +use gitbutler::projects; use pretty_assertions::assert_eq; +use crate::shared::{Case, Suite}; use crate::watcher::handler::test_remote_repository; -use crate::{Case, Suite}; use gitbutler_app::watcher::handlers::fetch_gitbutler_data::Handler; #[tokio::test] diff --git a/gitbutler-app/tests/watcher/handler/git_file_change.rs b/gitbutler-app/tests/watcher/handler/git_file_change.rs index b090d71bd..3c9d0ce3b 100644 --- a/gitbutler-app/tests/watcher/handler/git_file_change.rs +++ b/gitbutler-app/tests/watcher/handler/git_file_change.rs @@ -1,10 +1,10 @@ use anyhow::Result; use std::fs; -use gitbutler_app::projects; +use gitbutler::projects; use pretty_assertions::assert_eq; -use crate::{Case, Suite}; +use crate::shared::{Case, Suite}; use gitbutler_app::watcher::handlers::git_file_change::Handler; use gitbutler_app::watcher::{handlers, Event}; diff --git a/gitbutler-app/tests/watcher/handler/mod.rs b/gitbutler-app/tests/watcher/handler/mod.rs index 658ec3efd..0ef1b56df 100644 --- a/gitbutler-app/tests/watcher/handler/mod.rs +++ b/gitbutler-app/tests/watcher/handler/mod.rs @@ -1,4 +1,4 @@ -use crate::init_opts_bare; +use crate::shared::init_opts_bare; use tempfile::TempDir; fn test_remote_repository() -> anyhow::Result<(git2::Repository, TempDir)> { diff --git a/gitbutler-app/tests/watcher/handler/push_project_to_gitbutler.rs b/gitbutler-app/tests/watcher/handler/push_project_to_gitbutler.rs index e1b943de1..33280fa9b 100644 --- a/gitbutler-app/tests/watcher/handler/push_project_to_gitbutler.rs +++ b/gitbutler-app/tests/watcher/handler/push_project_to_gitbutler.rs @@ -1,12 +1,12 @@ use anyhow::Result; -use gitbutler_app::{git, projects}; +use gitbutler::{git, projects}; use std::collections::HashMap; use std::path::PathBuf; -use crate::virtual_branches::set_test_target; +use crate::shared::virtual_branches::set_test_target; +use crate::shared::{Case, Suite}; use crate::watcher::handler::test_remote_repository; -use crate::{Case, Suite}; -use gitbutler_app::project_repository::LogUntil; +use gitbutler::project_repository::LogUntil; use gitbutler_app::watcher::handlers::push_project_to_gitbutler::Handler; fn log_walk(repo: &git2::Repository, head: git::Oid) -> Vec { diff --git a/src/askpass.rs b/src/askpass.rs new file mode 100644 index 000000000..33625a6bd --- /dev/null +++ b/src/askpass.rs @@ -0,0 +1,63 @@ +use std::{collections::HashMap, sync::Arc}; + +use serde::Serialize; +use tokio::sync::{oneshot, Mutex}; + +use crate::id::Id; +use crate::virtual_branches::BranchId; + +pub struct AskpassRequest { + sender: oneshot::Sender>, +} + +#[derive(Debug, Clone, serde::Serialize)] +// This is needed to end up with a struct with either `branch_id` or `action` +#[serde(untagged)] +pub enum Context { + Push { branch_id: Option }, + Fetch { action: String }, +} + +#[derive(Clone)] +pub struct AskpassBroker { + pending_requests: Arc, AskpassRequest>>>, + submit_prompt_event: Arc) + Send + Sync>, +} + +#[derive(Debug, Clone, serde::Serialize)] +pub struct PromptEvent { + id: Id, + prompt: String, + context: C, +} + +impl AskpassBroker { + pub fn init(submit_prompt: impl Fn(PromptEvent) + Send + Sync + 'static) -> Self { + Self { + pending_requests: Arc::new(Mutex::new(HashMap::new())), + submit_prompt_event: Arc::new(submit_prompt), + } + } + + pub async fn submit_prompt(&self, prompt: String, context: Context) -> Option { + let (sender, receiver) = oneshot::channel(); + let id = Id::generate(); + let request = AskpassRequest { sender }; + self.pending_requests.lock().await.insert(id, request); + (self.submit_prompt_event)(PromptEvent { + id, + prompt, + context, + }); + receiver.await.unwrap() + } + + pub async fn handle_response(&self, id: Id, response: Option) { + let mut pending_requests = self.pending_requests.lock().await; + if let Some(request) = pending_requests.remove(&id) { + let _ = request.sender.send(response); + } else { + log::warn!("received response for unknown askpass request: {}", id); + } + } +} diff --git a/gitbutler-app/src/assets.rs b/src/assets.rs similarity index 100% rename from gitbutler-app/src/assets.rs rename to src/assets.rs diff --git a/gitbutler-app/src/database.rs b/src/database.rs similarity index 100% rename from gitbutler-app/src/database.rs rename to src/database.rs diff --git a/gitbutler-app/src/database/migrations/V0__deltas.sql b/src/database/migrations/V0__deltas.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V0__deltas.sql rename to src/database/migrations/V0__deltas.sql diff --git a/gitbutler-app/src/database/migrations/V1__sessions.sql b/src/database/migrations/V1__sessions.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V1__sessions.sql rename to src/database/migrations/V1__sessions.sql diff --git a/gitbutler-app/src/database/migrations/V2__files.sql b/src/database/migrations/V2__files.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V2__files.sql rename to src/database/migrations/V2__files.sql diff --git a/gitbutler-app/src/database/migrations/V3__bookmarks.sql b/src/database/migrations/V3__bookmarks.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V3__bookmarks.sql rename to src/database/migrations/V3__bookmarks.sql diff --git a/gitbutler-app/src/database/migrations/V4__bookmarks_update.sql b/src/database/migrations/V4__bookmarks_update.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V4__bookmarks_update.sql rename to src/database/migrations/V4__bookmarks_update.sql diff --git a/gitbutler-app/src/database/migrations/V5__bookmarks_update.sql b/src/database/migrations/V5__bookmarks_update.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V5__bookmarks_update.sql rename to src/database/migrations/V5__bookmarks_update.sql diff --git a/gitbutler-app/src/database/migrations/V6__sessions_project_id_id_idx.sql b/src/database/migrations/V6__sessions_project_id_id_idx.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V6__sessions_project_id_id_idx.sql rename to src/database/migrations/V6__sessions_project_id_id_idx.sql diff --git a/gitbutler-app/src/database/migrations/V7__drop_files.sql b/src/database/migrations/V7__drop_files.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V7__drop_files.sql rename to src/database/migrations/V7__drop_files.sql diff --git a/gitbutler-app/src/database/migrations/V8__drop_bookmarks.sql b/src/database/migrations/V8__drop_bookmarks.sql similarity index 100% rename from gitbutler-app/src/database/migrations/V8__drop_bookmarks.sql rename to src/database/migrations/V8__drop_bookmarks.sql diff --git a/gitbutler-app/src/dedup.rs b/src/dedup.rs similarity index 100% rename from gitbutler-app/src/dedup.rs rename to src/dedup.rs diff --git a/src/deltas.rs b/src/deltas.rs new file mode 100644 index 000000000..8bb23303d --- /dev/null +++ b/src/deltas.rs @@ -0,0 +1,15 @@ +pub mod controller; +mod delta; +mod document; +mod reader; +mod writer; + +pub mod database; +pub mod operations; + +pub use controller::Controller; +pub use database::Database; +pub use delta::Delta; +pub use document::Document; +pub use reader::DeltasReader as Reader; +pub use writer::DeltasWriter as Writer; diff --git a/gitbutler-app/src/deltas/controller.rs b/src/deltas/controller.rs similarity index 100% rename from gitbutler-app/src/deltas/controller.rs rename to src/deltas/controller.rs diff --git a/gitbutler-app/src/deltas/database.rs b/src/deltas/database.rs similarity index 100% rename from gitbutler-app/src/deltas/database.rs rename to src/deltas/database.rs diff --git a/gitbutler-app/src/deltas/delta.rs b/src/deltas/delta.rs similarity index 100% rename from gitbutler-app/src/deltas/delta.rs rename to src/deltas/delta.rs diff --git a/gitbutler-app/src/deltas/document.rs b/src/deltas/document.rs similarity index 100% rename from gitbutler-app/src/deltas/document.rs rename to src/deltas/document.rs diff --git a/gitbutler-app/src/deltas/operations.rs b/src/deltas/operations.rs similarity index 100% rename from gitbutler-app/src/deltas/operations.rs rename to src/deltas/operations.rs diff --git a/gitbutler-app/src/deltas/reader.rs b/src/deltas/reader.rs similarity index 100% rename from gitbutler-app/src/deltas/reader.rs rename to src/deltas/reader.rs diff --git a/gitbutler-app/src/deltas/writer.rs b/src/deltas/writer.rs similarity index 100% rename from gitbutler-app/src/deltas/writer.rs rename to src/deltas/writer.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 000000000..7fd068dd7 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,414 @@ +#[cfg(feature = "sentry")] +mod sentry; + +pub use legacy::*; + +pub mod gb { + #[cfg(feature = "error-context")] + pub use error_context::*; + + #[cfg(feature = "error-context")] + mod error_context { + use super::{ErrorKind, Result, WithContext}; + use backtrace::Backtrace; + use std::collections::BTreeMap; + + #[derive(Debug)] + pub struct Context { + pub backtrace: Backtrace, + pub caused_by: Option>, + pub vars: BTreeMap, + } + + impl Default for Context { + fn default() -> Self { + Self { + backtrace: Backtrace::new_unresolved(), + caused_by: None, + vars: BTreeMap::default(), + } + } + } + + #[derive(Debug)] + pub struct ErrorContext { + error: ErrorKind, + context: Context, + } + + impl core::fmt::Display for ErrorContext { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.error.fmt(f) + } + } + + impl std::error::Error for ErrorContext { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.context + .caused_by + .as_ref() + .map(|e| e as &dyn std::error::Error) + } + + fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { + if request.would_be_satisfied_by_ref_of::() { + request.provide_ref(&self.context.backtrace); + } + } + } + + impl ErrorContext { + #[inline] + pub fn error(&self) -> &ErrorKind { + &self.error + } + + #[inline] + pub fn context(&self) -> &Context { + &self.context + } + + pub fn into_owned(self) -> (ErrorKind, Context) { + (self.error, self.context) + } + } + + impl> WithContext for E { + fn add_err_context, V: Into>( + self, + name: K, + value: V, + ) -> ErrorContext { + let mut e = self.into(); + e.context.vars.insert(name.into(), value.into()); + e + } + + fn wrap_err>(self, error: K) -> ErrorContext { + let mut new_err = ErrorContext { + error: error.into(), + context: Context::default(), + }; + + new_err.context.caused_by = Some(Box::new(self.into())); + new_err + } + } + + impl WithContext> for std::result::Result + where + E: Into, + { + #[inline] + fn add_err_context, V: Into>( + self, + name: K, + value: V, + ) -> Result { + self.map_err(|e| { + ErrorContext { + error: e.into(), + context: Context::default(), + } + .add_err_context(name, value) + }) + } + + #[inline] + fn wrap_err>(self, error: K) -> Result { + self.map_err(|e| { + ErrorContext { + error: e.into(), + context: Context::default(), + } + .wrap_err(error) + }) + } + } + + #[cfg(feature = "error-context")] + impl serde::Serialize for ErrorContext { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeSeq; + let mut seq = serializer.serialize_seq(None)?; + let mut current = Some(self); + while let Some(err) = current { + seq.serialize_element(&err.error)?; + current = err.context.caused_by.as_deref(); + } + seq.end() + } + } + + impl From for ErrorContext { + fn from(error: ErrorKind) -> Self { + Self { + error, + context: Context::default(), + } + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn error_context() { + fn low_level_io() -> std::result::Result<(), std::io::Error> { + Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!")) + } + + fn app_level_io() -> Result<()> { + low_level_io().add_err_context("foo", "bar")?; + unreachable!(); + } + + use std::error::Error; + + let r = app_level_io(); + assert!(r.is_err()); + let e = r.unwrap_err(); + assert_eq!(e.context().vars.get("foo"), Some(&"bar".to_string())); + assert!(e.source().is_none()); + assert!(e.to_string().starts_with("io.other-error:")); + } + } + } + + pub trait WithContext { + fn add_err_context, V: Into>(self, name: K, value: V) -> R; + fn wrap_err>(self, error: E) -> R; + } + + #[cfg(not(feature = "error-context"))] + pub struct Context; + + pub trait ErrorCode { + fn code(&self) -> String; + fn message(&self) -> String; + } + + #[derive(Debug, thiserror::Error)] + pub enum ErrorKind { + Io(#[from] ::std::io::Error), + Git(#[from] ::git2::Error), + CommonDirNotAvailable(String), + } + + impl ErrorCode for std::io::Error { + fn code(&self) -> String { + slug::slugify(self.kind().to_string()) + } + + fn message(&self) -> String { + self.to_string() + } + } + + impl ErrorCode for git2::Error { + fn code(&self) -> String { + slug::slugify(format!("{:?}", self.class())) + } + + fn message(&self) -> String { + self.to_string() + } + } + + impl ErrorCode for ErrorKind { + fn code(&self) -> String { + match self { + ErrorKind::Io(e) => format!("io.{}", ::code(e)), + ErrorKind::Git(e) => format!("git.{}", ::code(e)), + ErrorKind::CommonDirNotAvailable(_) => "no-common-dir".to_string(), + } + } + + fn message(&self) -> String { + match self { + ErrorKind::Io(e) => ::message(e), + ErrorKind::Git(e) => ::message(e), + ErrorKind::CommonDirNotAvailable(s) => format!("{s} is not available"), + } + } + } + + impl core::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + format!( + "{}: {}", + ::code(self), + ::message(self) + ) + .fmt(f) + } + } + + #[cfg(not(feature = "error-context"))] + pub type Error = ErrorKind; + #[cfg(feature = "error-context")] + pub type Error = ErrorContext; + + pub type Result = ::std::result::Result; + + #[cfg(not(feature = "error-context"))] + impl ErrorKind { + #[inline] + pub fn error(&self) -> &Error { + self + } + + #[inline] + pub fn context(&self) -> Option<&Context> { + None + } + } + + #[cfg(not(feature = "error-context"))] + impl WithContext for ErrorKind { + #[inline] + fn add_err_context, V: Into>(self, _name: K, _value: V) -> Error { + self + } + + #[inline] + fn wrap_err(self, _error: Error) -> Error { + self + } + } + + #[cfg(not(feature = "error-context"))] + impl WithContext> for std::result::Result { + #[inline] + fn add_err_context, V: Into>( + self, + _name: K, + _value: V, + ) -> std::result::Result { + self + } + + #[inline] + fn wrap_err(self, _error: Error) -> std::result::Result { + self + } + } + + #[cfg(feature = "error-context")] + impl serde::Serialize for ErrorKind { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeTuple; + let mut seq = serializer.serialize_tuple(2)?; + seq.serialize_element(&self.code())?; + seq.serialize_element(&self.message())?; + seq.end() + } + } +} + +//#[deprecated( +// note = "the types in the error::legacy::* module are deprecated; use error::gb::Error and error::gb::Result instead" +//)] +mod legacy { + use core::fmt; + + use crate::{keys, projects, users}; + use serde::{ser::SerializeMap, Serialize}; + + #[derive(Debug)] + pub enum Code { + Unknown, + Validation, + Projects, + Branches, + ProjectGitAuth, + ProjectGitRemote, + ProjectConflict, + ProjectHead, + Menu, + PreCommitHook, + CommitMsgHook, + } + + impl fmt::Display for Code { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Code::Menu => write!(f, "errors.menu"), + Code::Unknown => write!(f, "errors.unknown"), + Code::Validation => write!(f, "errors.validation"), + Code::Projects => write!(f, "errors.projects"), + Code::Branches => write!(f, "errors.branches"), + Code::ProjectGitAuth => write!(f, "errors.projects.git.auth"), + Code::ProjectGitRemote => write!(f, "errors.projects.git.remote"), + Code::ProjectHead => write!(f, "errors.projects.head"), + Code::ProjectConflict => write!(f, "errors.projects.conflict"), + //TODO: rename js side to be more precise what kind of hook error this is + Code::PreCommitHook => write!(f, "errors.hook"), + Code::CommitMsgHook => write!(f, "errors.hooks.commit.msg"), + } + } + } + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error("[{code}]: {message}")] + UserError { code: Code, message: String }, + #[error("[errors.unknown]: Something went wrong")] + Unknown, + } + + impl Serialize for Error { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let (code, message) = match self { + Error::UserError { code, message } => (code.to_string(), message.to_string()), + Error::Unknown => ( + Code::Unknown.to_string(), + "Something went wrong".to_string(), + ), + }; + + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("code", &code)?; + map.serialize_entry("message", &message)?; + map.end() + } + } + + impl From for Error { + fn from(error: anyhow::Error) -> Self { + tracing::error!(?error); + Error::Unknown + } + } + + impl From for Error { + fn from(error: keys::GetOrCreateError) -> Self { + tracing::error!(?error); + Error::Unknown + } + } + + impl From for Error { + fn from(error: users::GetError) -> Self { + tracing::error!(?error); + Error::Unknown + } + } + + impl From for Error { + fn from(error: projects::controller::GetError) -> Self { + tracing::error!(?error); + Error::Unknown + } + } +} diff --git a/src/error/sentry.rs b/src/error/sentry.rs new file mode 100644 index 000000000..b321454be --- /dev/null +++ b/src/error/sentry.rs @@ -0,0 +1,89 @@ +use crate::error::gb::{ErrorCode, ErrorContext}; +use sentry::{ + protocol::{value::Map, Event, Exception, Value}, + types::Uuid, +}; +use std::collections::BTreeMap; + +pub trait SentrySender { + fn send_to_sentry(self) -> Uuid; +} + +impl> SentrySender for E { + fn send_to_sentry(self) -> Uuid { + let sentry_event = self.into().into(); + sentry::capture_event(sentry_event) + } +} + +trait PopulateException { + fn populate_exception( + self, + exceptions: &mut Vec, + vars: &mut BTreeMap, + ); +} + +impl PopulateException for ErrorContext { + fn populate_exception( + self, + exceptions: &mut Vec, + vars: &mut BTreeMap, + ) { + let (error, mut context) = self.into_owned(); + + let mut exc = Exception { + ty: error.code(), + value: Some(error.message()), + ..Exception::default() + }; + + if let Some(cause) = context.caused_by { + cause.populate_exception(exceptions, vars); + } + + // We don't resolve at capture time because it can DRASTICALLY + // slow down the application (can take up to 0.5s to resolve + // a *single* frame). We do it here, only when a Sentry event + // is being created. + context.backtrace.resolve(); + exc.stacktrace = + sentry::integrations::backtrace::backtrace_to_stacktrace(&context.backtrace); + + ::backtrace::clear_symbol_cache(); + + vars.insert( + error.code(), + Value::Object( + context + .vars + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + ), + ); + exceptions.push(exc); + } +} + +impl From for Event<'_> { + fn from(error_context: ErrorContext) -> Self { + let mut sentry_event = Event { + message: Some(format!( + "{}: {}", + error_context.error().code(), + error_context.error().message() + )), + ..Event::default() + }; + + let mut vars = BTreeMap::new(); + error_context.populate_exception(&mut sentry_event.exception.values, &mut vars); + + sentry_event + .extra + .insert("context_vars".into(), Value::Object(Map::from_iter(vars))); + + sentry_event + } +} diff --git a/gitbutler-app/src/fs.rs b/src/fs.rs similarity index 100% rename from gitbutler-app/src/fs.rs rename to src/fs.rs diff --git a/gitbutler-app/src/gb_repository.rs b/src/gb_repository.rs similarity index 100% rename from gitbutler-app/src/gb_repository.rs rename to src/gb_repository.rs diff --git a/gitbutler-app/src/gb_repository/repository.rs b/src/gb_repository/repository.rs similarity index 100% rename from gitbutler-app/src/gb_repository/repository.rs rename to src/gb_repository/repository.rs diff --git a/gitbutler-app/src/git.rs b/src/git.rs similarity index 100% rename from gitbutler-app/src/git.rs rename to src/git.rs diff --git a/gitbutler-app/src/git/blob.rs b/src/git/blob.rs similarity index 100% rename from gitbutler-app/src/git/blob.rs rename to src/git/blob.rs diff --git a/gitbutler-app/src/git/branch.rs b/src/git/branch.rs similarity index 100% rename from gitbutler-app/src/git/branch.rs rename to src/git/branch.rs diff --git a/gitbutler-app/src/git/commit.rs b/src/git/commit.rs similarity index 100% rename from gitbutler-app/src/git/commit.rs rename to src/git/commit.rs diff --git a/gitbutler-app/src/git/config.rs b/src/git/config.rs similarity index 100% rename from gitbutler-app/src/git/config.rs rename to src/git/config.rs diff --git a/gitbutler-app/src/git/credentials.rs b/src/git/credentials.rs similarity index 100% rename from gitbutler-app/src/git/credentials.rs rename to src/git/credentials.rs diff --git a/gitbutler-app/src/git/diff.rs b/src/git/diff.rs similarity index 100% rename from gitbutler-app/src/git/diff.rs rename to src/git/diff.rs diff --git a/gitbutler-app/src/git/error.rs b/src/git/error.rs similarity index 100% rename from gitbutler-app/src/git/error.rs rename to src/git/error.rs diff --git a/gitbutler-app/src/git/index.rs b/src/git/index.rs similarity index 100% rename from gitbutler-app/src/git/index.rs rename to src/git/index.rs diff --git a/gitbutler-app/src/git/oid.rs b/src/git/oid.rs similarity index 100% rename from gitbutler-app/src/git/oid.rs rename to src/git/oid.rs diff --git a/gitbutler-app/src/git/reference.rs b/src/git/reference.rs similarity index 100% rename from gitbutler-app/src/git/reference.rs rename to src/git/reference.rs diff --git a/gitbutler-app/src/git/reference/refname.rs b/src/git/reference/refname.rs similarity index 100% rename from gitbutler-app/src/git/reference/refname.rs rename to src/git/reference/refname.rs diff --git a/gitbutler-app/src/git/reference/refname/error.rs b/src/git/reference/refname/error.rs similarity index 100% rename from gitbutler-app/src/git/reference/refname/error.rs rename to src/git/reference/refname/error.rs diff --git a/gitbutler-app/src/git/reference/refname/local.rs b/src/git/reference/refname/local.rs similarity index 100% rename from gitbutler-app/src/git/reference/refname/local.rs rename to src/git/reference/refname/local.rs diff --git a/gitbutler-app/src/git/reference/refname/remote.rs b/src/git/reference/refname/remote.rs similarity index 100% rename from gitbutler-app/src/git/reference/refname/remote.rs rename to src/git/reference/refname/remote.rs diff --git a/gitbutler-app/src/git/reference/refname/virtual.rs b/src/git/reference/refname/virtual.rs similarity index 100% rename from gitbutler-app/src/git/reference/refname/virtual.rs rename to src/git/reference/refname/virtual.rs diff --git a/gitbutler-app/src/git/remote.rs b/src/git/remote.rs similarity index 100% rename from gitbutler-app/src/git/remote.rs rename to src/git/remote.rs diff --git a/gitbutler-app/src/git/repository.rs b/src/git/repository.rs similarity index 100% rename from gitbutler-app/src/git/repository.rs rename to src/git/repository.rs diff --git a/gitbutler-app/src/git/show.rs b/src/git/show.rs similarity index 100% rename from gitbutler-app/src/git/show.rs rename to src/git/show.rs diff --git a/gitbutler-app/src/git/signature.rs b/src/git/signature.rs similarity index 100% rename from gitbutler-app/src/git/signature.rs rename to src/git/signature.rs diff --git a/gitbutler-app/src/git/tree.rs b/src/git/tree.rs similarity index 100% rename from gitbutler-app/src/git/tree.rs rename to src/git/tree.rs diff --git a/gitbutler-app/src/git/url.rs b/src/git/url.rs similarity index 100% rename from gitbutler-app/src/git/url.rs rename to src/git/url.rs diff --git a/gitbutler-app/src/git/url/convert.rs b/src/git/url/convert.rs similarity index 100% rename from gitbutler-app/src/git/url/convert.rs rename to src/git/url/convert.rs diff --git a/gitbutler-app/src/git/url/parse.rs b/src/git/url/parse.rs similarity index 100% rename from gitbutler-app/src/git/url/parse.rs rename to src/git/url/parse.rs diff --git a/gitbutler-app/src/git/url/scheme.rs b/src/git/url/scheme.rs similarity index 100% rename from gitbutler-app/src/git/url/scheme.rs rename to src/git/url/scheme.rs diff --git a/gitbutler-app/src/id.rs b/src/id.rs similarity index 100% rename from gitbutler-app/src/id.rs rename to src/id.rs diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 000000000..852f758bc --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,6 @@ +pub mod controller; +mod key; +pub mod storage; + +pub use controller::*; +pub use key::{PrivateKey, PublicKey, SignError}; diff --git a/gitbutler-app/src/keys/controller.rs b/src/keys/controller.rs similarity index 100% rename from gitbutler-app/src/keys/controller.rs rename to src/keys/controller.rs diff --git a/gitbutler-app/src/keys/key.rs b/src/keys/key.rs similarity index 100% rename from gitbutler-app/src/keys/key.rs rename to src/keys/key.rs diff --git a/gitbutler-app/src/keys/storage.rs b/src/keys/storage.rs similarity index 100% rename from gitbutler-app/src/keys/storage.rs rename to src/keys/storage.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..675c84f92 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,41 @@ +#![feature(error_generic_member_access)] +#![cfg_attr(windows, feature(windows_by_handle))] +#![cfg_attr( + all(windows, not(test), not(debug_assertions)), + windows_subsystem = "windows" +)] +// FIXME(qix-): Stuff we want to fix but don't have a lot of time for. +// FIXME(qix-): PRs welcome! +#![allow( + clippy::used_underscore_binding, + clippy::module_name_repetitions, + clippy::struct_field_names, + clippy::too_many_lines +)] + +pub mod askpass; +pub mod assets; +pub mod database; +pub mod dedup; +pub mod deltas; +pub mod error; +pub mod fs; +pub mod gb_repository; +pub mod git; +pub mod id; +pub mod keys; +pub mod lock; +pub mod path; +pub mod project_repository; +pub mod projects; +pub mod reader; +pub mod sessions; +pub mod ssh; +pub mod storage; +pub mod types; +pub mod users; +pub mod virtual_branches; +#[cfg(target_os = "windows")] +pub mod windows; +pub mod writer; +pub mod zip; diff --git a/gitbutler-app/src/lock.rs b/src/lock.rs similarity index 100% rename from gitbutler-app/src/lock.rs rename to src/lock.rs diff --git a/gitbutler-app/src/path.rs b/src/path.rs similarity index 100% rename from gitbutler-app/src/path.rs rename to src/path.rs diff --git a/gitbutler-app/src/project_repository.rs b/src/project_repository.rs similarity index 100% rename from gitbutler-app/src/project_repository.rs rename to src/project_repository.rs diff --git a/gitbutler-app/src/project_repository/config.rs b/src/project_repository/config.rs similarity index 100% rename from gitbutler-app/src/project_repository/config.rs rename to src/project_repository/config.rs diff --git a/gitbutler-app/src/project_repository/conflicts.rs b/src/project_repository/conflicts.rs similarity index 100% rename from gitbutler-app/src/project_repository/conflicts.rs rename to src/project_repository/conflicts.rs diff --git a/gitbutler-app/src/project_repository/repository.rs b/src/project_repository/repository.rs similarity index 99% rename from gitbutler-app/src/project_repository/repository.rs rename to src/project_repository/repository.rs index d51f097ac..064572483 100644 --- a/gitbutler-app/src/project_repository/repository.rs +++ b/src/project_repository/repository.rs @@ -7,6 +7,7 @@ use std::{ use anyhow::{Context, Result}; use crate::{ + askpass, askpass::AskpassBroker, git::{self, credentials::HelpError, Url}, keys, @@ -673,7 +674,7 @@ async fn handle_git_prompt_push( if let Some((askpass_broker, branch_id)) = askpass { tracing::info!("received prompt for branch push {branch_id:?}: {prompt:?}"); askpass_broker - .submit_prompt(prompt, AskpassPromptPushContext { branch_id }) + .submit_prompt(prompt, askpass::Context::Push { branch_id }) .await } else { tracing::warn!("received askpass push prompt but no broker was supplied; returning None"); @@ -688,7 +689,7 @@ async fn handle_git_prompt_fetch( if let Some((askpass_broker, action)) = askpass { tracing::info!("received prompt for fetch with action {action:?}: {prompt:?}"); askpass_broker - .submit_prompt(prompt, AskpassPromptFetchContext { action }) + .submit_prompt(prompt, askpass::Context::Fetch { action }) .await } else { tracing::warn!("received askpass fetch prompt but no broker was supplied; returning None"); diff --git a/gitbutler-app/src/project_repository/signatures.rs b/src/project_repository/signatures.rs similarity index 100% rename from gitbutler-app/src/project_repository/signatures.rs rename to src/project_repository/signatures.rs diff --git a/src/projects.rs b/src/projects.rs new file mode 100644 index 000000000..8a139a6b7 --- /dev/null +++ b/src/projects.rs @@ -0,0 +1,9 @@ +pub mod controller; +mod project; +pub mod storage; + +pub use controller::*; +pub use project::{AuthKey, CodePushState, FetchResult, Project, ProjectId}; +pub use storage::UpdateRequest; + +pub use project::ApiProject; diff --git a/gitbutler-app/src/projects/controller.rs b/src/projects/controller.rs similarity index 92% rename from gitbutler-app/src/projects/controller.rs rename to src/projects/controller.rs index 6f2c30baf..a3ccb02a1 100644 --- a/gitbutler-app/src/projects/controller.rs +++ b/src/projects/controller.rs @@ -1,14 +1,24 @@ use super::{storage, storage::UpdateRequest, Project, ProjectId}; -use crate::{gb_repository, project_repository, users, watcher}; +use crate::{gb_repository, project_repository, users}; use anyhow::Context; +use async_trait::async_trait; use std::path::{Path, PathBuf}; +use std::sync::Arc; + +#[async_trait] +pub trait Watchers { + fn watch(&self, project: &Project) -> anyhow::Result<()>; + async fn stop(&self, id: ProjectId) -> anyhow::Result<()>; + async fn fetch(&self, id: ProjectId) -> anyhow::Result<()>; + async fn push(&self, id: ProjectId) -> anyhow::Result<()>; +} #[derive(Clone)] pub struct Controller { local_data_dir: PathBuf, projects_storage: storage::Storage, users: users::Controller, - watchers: Option, + watchers: Option>, } impl Controller { @@ -16,13 +26,13 @@ impl Controller { local_data_dir: PathBuf, projects_storage: storage::Storage, users: users::Controller, - watchers: Option, + watchers: Option, ) -> Self { Self { local_data_dir, projects_storage, users, - watchers, + watchers: watchers.map(|w| Arc::new(w) as Arc<_>), } } @@ -130,10 +140,7 @@ impl Controller { if let Some(watchers) = &self.watchers { if let Some(api) = &project.api { if api.sync { - if let Err(error) = watchers - .post(watcher::Event::FetchGitbutlerData(project.id)) - .await - { + if let Err(error) = watchers.fetch(project.id).await { tracing::error!( project_id = %project.id, ?error, @@ -142,10 +149,7 @@ impl Controller { } } - if let Err(error) = watchers - .post(watcher::Event::PushGitbutlerData(project.id)) - .await - { + if let Err(error) = watchers.push(project.id).await { tracing::error!( project_id = %project.id, ?error, @@ -200,7 +204,7 @@ impl Controller { }?; if let Some(watchers) = &self.watchers { - if let Err(error) = watchers.stop(id).await { + if let Err(error) = watchers.stop(*id).await { tracing::error!( project_id = %id, ?error, diff --git a/gitbutler-app/src/projects/project.rs b/src/projects/project.rs similarity index 100% rename from gitbutler-app/src/projects/project.rs rename to src/projects/project.rs diff --git a/gitbutler-app/src/projects/storage.rs b/src/projects/storage.rs similarity index 100% rename from gitbutler-app/src/projects/storage.rs rename to src/projects/storage.rs diff --git a/gitbutler-app/src/reader.rs b/src/reader.rs similarity index 100% rename from gitbutler-app/src/reader.rs rename to src/reader.rs diff --git a/src/sessions.rs b/src/sessions.rs new file mode 100644 index 000000000..458810ee5 --- /dev/null +++ b/src/sessions.rs @@ -0,0 +1,14 @@ +pub mod controller; +mod iterator; +mod reader; +pub mod session; +mod writer; + +pub mod database; + +pub use controller::Controller; +pub use database::Database; +pub use iterator::SessionsIterator; +pub use reader::SessionReader as Reader; +pub use session::{Meta, Session, SessionError, SessionId}; +pub use writer::SessionWriter as Writer; diff --git a/gitbutler-app/src/sessions/controller.rs b/src/sessions/controller.rs similarity index 100% rename from gitbutler-app/src/sessions/controller.rs rename to src/sessions/controller.rs diff --git a/gitbutler-app/src/sessions/database.rs b/src/sessions/database.rs similarity index 100% rename from gitbutler-app/src/sessions/database.rs rename to src/sessions/database.rs diff --git a/gitbutler-app/src/sessions/iterator.rs b/src/sessions/iterator.rs similarity index 100% rename from gitbutler-app/src/sessions/iterator.rs rename to src/sessions/iterator.rs diff --git a/gitbutler-app/src/sessions/reader.rs b/src/sessions/reader.rs similarity index 100% rename from gitbutler-app/src/sessions/reader.rs rename to src/sessions/reader.rs diff --git a/gitbutler-app/src/sessions/session.rs b/src/sessions/session.rs similarity index 100% rename from gitbutler-app/src/sessions/session.rs rename to src/sessions/session.rs diff --git a/gitbutler-app/src/sessions/writer.rs b/src/sessions/writer.rs similarity index 100% rename from gitbutler-app/src/sessions/writer.rs rename to src/sessions/writer.rs diff --git a/gitbutler-app/src/ssh.rs b/src/ssh.rs similarity index 100% rename from gitbutler-app/src/ssh.rs rename to src/ssh.rs diff --git a/gitbutler-app/src/storage.rs b/src/storage.rs similarity index 100% rename from gitbutler-app/src/storage.rs rename to src/storage.rs diff --git a/gitbutler-app/src/types.rs b/src/types.rs similarity index 100% rename from gitbutler-app/src/types.rs rename to src/types.rs diff --git a/gitbutler-app/src/types/default_true.rs b/src/types/default_true.rs similarity index 100% rename from gitbutler-app/src/types/default_true.rs rename to src/types/default_true.rs diff --git a/src/users.rs b/src/users.rs new file mode 100644 index 000000000..7c26c7204 --- /dev/null +++ b/src/users.rs @@ -0,0 +1,6 @@ +pub mod controller; +pub mod storage; +mod user; + +pub use controller::*; +pub use user::User; diff --git a/gitbutler-app/src/users/controller.rs b/src/users/controller.rs similarity index 100% rename from gitbutler-app/src/users/controller.rs rename to src/users/controller.rs diff --git a/gitbutler-app/src/users/storage.rs b/src/users/storage.rs similarity index 100% rename from gitbutler-app/src/users/storage.rs rename to src/users/storage.rs diff --git a/gitbutler-app/src/users/user.rs b/src/users/user.rs similarity index 100% rename from gitbutler-app/src/users/user.rs rename to src/users/user.rs diff --git a/src/virtual_branches.rs b/src/virtual_branches.rs new file mode 100644 index 000000000..cb85550a8 --- /dev/null +++ b/src/virtual_branches.rs @@ -0,0 +1,29 @@ +pub mod branch; +pub use branch::{Branch, BranchId}; +pub mod context; +pub mod target; + +pub mod errors; + +mod files; +pub use files::*; + +pub mod integration; +pub use integration::GITBUTLER_INTEGRATION_REFERENCE; + +mod base; +pub use base::*; + +pub mod controller; +pub use controller::Controller; + +mod iterator; +pub use iterator::BranchIterator as Iterator; + +mod r#virtual; +pub use r#virtual::*; + +mod remote; +pub use remote::*; + +mod state; diff --git a/gitbutler-app/src/virtual_branches/base.rs b/src/virtual_branches/base.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/base.rs rename to src/virtual_branches/base.rs diff --git a/gitbutler-app/src/virtual_branches/branch.rs b/src/virtual_branches/branch.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/branch.rs rename to src/virtual_branches/branch.rs diff --git a/gitbutler-app/src/virtual_branches/branch/file_ownership.rs b/src/virtual_branches/branch/file_ownership.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/branch/file_ownership.rs rename to src/virtual_branches/branch/file_ownership.rs diff --git a/gitbutler-app/src/virtual_branches/branch/hunk.rs b/src/virtual_branches/branch/hunk.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/branch/hunk.rs rename to src/virtual_branches/branch/hunk.rs diff --git a/gitbutler-app/src/virtual_branches/branch/ownership.rs b/src/virtual_branches/branch/ownership.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/branch/ownership.rs rename to src/virtual_branches/branch/ownership.rs diff --git a/gitbutler-app/src/virtual_branches/branch/reader.rs b/src/virtual_branches/branch/reader.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/branch/reader.rs rename to src/virtual_branches/branch/reader.rs diff --git a/gitbutler-app/src/virtual_branches/branch/writer.rs b/src/virtual_branches/branch/writer.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/branch/writer.rs rename to src/virtual_branches/branch/writer.rs diff --git a/gitbutler-app/src/virtual_branches/context.rs b/src/virtual_branches/context.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/context.rs rename to src/virtual_branches/context.rs diff --git a/gitbutler-app/src/virtual_branches/controller.rs b/src/virtual_branches/controller.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/controller.rs rename to src/virtual_branches/controller.rs diff --git a/gitbutler-app/src/virtual_branches/errors.rs b/src/virtual_branches/errors.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/errors.rs rename to src/virtual_branches/errors.rs diff --git a/gitbutler-app/src/virtual_branches/files.rs b/src/virtual_branches/files.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/files.rs rename to src/virtual_branches/files.rs diff --git a/gitbutler-app/src/virtual_branches/integration.rs b/src/virtual_branches/integration.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/integration.rs rename to src/virtual_branches/integration.rs diff --git a/gitbutler-app/src/virtual_branches/iterator.rs b/src/virtual_branches/iterator.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/iterator.rs rename to src/virtual_branches/iterator.rs diff --git a/gitbutler-app/src/virtual_branches/remote.rs b/src/virtual_branches/remote.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/remote.rs rename to src/virtual_branches/remote.rs diff --git a/gitbutler-app/src/virtual_branches/state.rs b/src/virtual_branches/state.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/state.rs rename to src/virtual_branches/state.rs diff --git a/gitbutler-app/src/virtual_branches/target.rs b/src/virtual_branches/target.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/target.rs rename to src/virtual_branches/target.rs diff --git a/gitbutler-app/src/virtual_branches/target/reader.rs b/src/virtual_branches/target/reader.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/target/reader.rs rename to src/virtual_branches/target/reader.rs diff --git a/gitbutler-app/src/virtual_branches/target/writer.rs b/src/virtual_branches/target/writer.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/target/writer.rs rename to src/virtual_branches/target/writer.rs diff --git a/gitbutler-app/src/virtual_branches/virtual.rs b/src/virtual_branches/virtual.rs similarity index 100% rename from gitbutler-app/src/virtual_branches/virtual.rs rename to src/virtual_branches/virtual.rs diff --git a/gitbutler-app/src/windows.rs b/src/windows.rs similarity index 100% rename from gitbutler-app/src/windows.rs rename to src/windows.rs diff --git a/gitbutler-app/src/writer.rs b/src/writer.rs similarity index 100% rename from gitbutler-app/src/writer.rs rename to src/writer.rs diff --git a/src/zip.rs b/src/zip.rs new file mode 100644 index 000000000..f16d4be2e --- /dev/null +++ b/src/zip.rs @@ -0,0 +1,164 @@ +pub mod controller; +pub use controller::Controller; + +use std::{ + fs, + io::{self, Read, Write}, + path, time, +}; + +use anyhow::{Context, Result}; +use sha2::{Digest, Sha256}; +use walkdir::{DirEntry, WalkDir}; +use zip::{result::ZipError, write, CompressionMethod, ZipWriter}; + +#[derive(Clone)] +pub struct Zipper { + cache: path::PathBuf, +} + +impl Zipper { + pub fn new>(cache_dir: P) -> Self { + let cache = cache_dir.as_ref().to_path_buf().join("archives"); + Self { cache } + } + + // takes a path to create zip of, returns path of a created archive. + pub fn zip>(&self, path: P) -> Result { + let path = path.as_ref(); + if !path.exists() { + return Err(anyhow::anyhow!("{} does not exist", path.display())); + } + if !path.is_dir() { + return Err(anyhow::anyhow!("{} is not a directory", path.display())); + } + let path_hash = calculate_path_hash(path)?; + fs::create_dir_all(&self.cache).context("failed to create cache dir")?; + let archive_path = self.cache.join(format!("{}.zip", path_hash)); + if !archive_path.exists() { + doit(path, &archive_path, CompressionMethod::Bzip2)?; + } + Ok(archive_path) + } +} + +fn doit>( + src_dir: P, + dst_file: P, + method: zip::CompressionMethod, +) -> zip::result::ZipResult<()> { + let src = src_dir.as_ref(); + let dst = dst_file.as_ref(); + if !src.is_dir() { + return Err(ZipError::FileNotFound); + } + + let file = fs::File::create(dst).unwrap(); + + let walkdir = WalkDir::new(src); + let it = walkdir.into_iter(); + + zip_dir(&mut it.filter_map(Result::ok), src, file, method)?; + + Ok(()) +} + +fn zip_dir( + it: &mut dyn Iterator, + prefix: &path::Path, + writer: T, + method: zip::CompressionMethod, +) -> zip::result::ZipResult<()> +where + T: io::Write + io::Seek, +{ + let mut zip = ZipWriter::new(writer); + let options = write::FileOptions::default() + .compression_method(method) + .unix_permissions(0o755); + + let mut buffer = Vec::new(); + for entry in it { + let path = entry.path(); + let name = path.strip_prefix(prefix).unwrap(); + + // Write file or directory explicitly + // Some unzip tools unzip files with directory paths correctly, some do not! + if path.is_file() { + #[allow(deprecated)] + zip.start_file_from_path(name, options)?; + let mut f = fs::File::open(path)?; + + f.read_to_end(&mut buffer)?; + zip.write_all(&buffer)?; + buffer.clear(); + } else if !name.as_os_str().is_empty() { + // Only if not root! Avoids path spec / warning + // and mapname conversion failed error on unzip + #[allow(deprecated)] + zip.add_directory_from_path(name, options)?; + } + } + zip.finish()?; + Result::Ok(()) +} + +// returns hash of a path by calculating metadata hash of all files in it. +fn calculate_path_hash>(path: P) -> Result { + let path = path.as_ref(); + let mut hasher = Sha256::new(); + + if path.is_dir() { + let entries = fs::read_dir(path)?; + let mut entry_paths: Vec<_> = entries + .filter_map(|entry| entry.ok().map(|e| e.path())) + .collect(); + entry_paths.sort(); + + for entry_path in entry_paths { + file_hash(&mut hasher, &entry_path).with_context(|| { + format!( + "failed to calculate hash of file {}", + entry_path.to_str().unwrap() + ) + })?; + } + } else if path.is_file() { + file_hash(&mut hasher, path).with_context(|| { + format!( + "failed to calculate hash of file {}", + path.to_str().unwrap() + ) + })?; + } + + Ok(format!("{:X}", hasher.finalize())) +} + +fn file_hash>(digest: &mut Sha256, path: P) -> Result<()> { + let path = path.as_ref(); + let metadata = fs::metadata(path).context("failed to get metadata")?; + digest.update(path.to_str().unwrap().as_bytes()); + digest.update(metadata.len().to_string().as_bytes()); + digest.update( + metadata + .modified() + .unwrap_or(time::UNIX_EPOCH) + .duration_since(time::UNIX_EPOCH) + .unwrap() + .as_secs() + .to_string() + .as_bytes(), + ); + digest.update( + metadata + .created() + .unwrap_or(time::UNIX_EPOCH) + .duration_since(time::UNIX_EPOCH) + .unwrap() + .as_secs() + .to_string() + .as_bytes(), + ); + Ok(()) +} diff --git a/gitbutler-app/src/zip/controller.rs b/src/zip/controller.rs similarity index 100% rename from gitbutler-app/src/zip/controller.rs rename to src/zip/controller.rs diff --git a/tests/app.rs b/tests/app.rs new file mode 100644 index 000000000..6e4079a5d --- /dev/null +++ b/tests/app.rs @@ -0,0 +1,19 @@ +pub mod shared; + +mod suite { + mod gb_repository; + mod projects; + mod virtual_branches; +} + +mod database; +mod deltas; +mod gb_repository; +mod git; +mod keys; +mod lock; +mod reader; +mod sessions; +mod types; +pub mod virtual_branches; +mod zip; diff --git a/gitbutler-app/tests/database/mod.rs b/tests/database/mod.rs similarity index 90% rename from gitbutler-app/tests/database/mod.rs rename to tests/database/mod.rs index a75e8260e..37223235f 100644 --- a/gitbutler-app/tests/database/mod.rs +++ b/tests/database/mod.rs @@ -1,5 +1,5 @@ -use crate::temp_dir; -use gitbutler_app::database::Database; +use crate::shared::temp_dir; +use gitbutler::database::Database; #[test] fn smoke() { diff --git a/gitbutler-app/tests/deltas/document.rs b/tests/deltas/document.rs similarity index 98% rename from gitbutler-app/tests/deltas/document.rs rename to tests/deltas/document.rs index 3c35d18a4..6dae00aba 100644 --- a/gitbutler-app/tests/deltas/document.rs +++ b/tests/deltas/document.rs @@ -1,6 +1,6 @@ -use gitbutler_app::deltas::operations::Operation; -use gitbutler_app::deltas::{Delta, Document}; -use gitbutler_app::reader; +use gitbutler::deltas::operations::Operation; +use gitbutler::deltas::{Delta, Document}; +use gitbutler::reader; #[test] fn new() { diff --git a/gitbutler-app/tests/deltas/mod.rs b/tests/deltas/mod.rs similarity index 90% rename from gitbutler-app/tests/deltas/mod.rs rename to tests/deltas/mod.rs index 7241f78bc..df87ed792 100644 --- a/gitbutler-app/tests/deltas/mod.rs +++ b/tests/deltas/mod.rs @@ -1,8 +1,8 @@ mod database { - use crate::test_database; - use gitbutler_app::deltas::{operations, Database, Delta}; - use gitbutler_app::projects::ProjectId; - use gitbutler_app::sessions::SessionId; + use crate::shared::test_database; + use gitbutler::deltas::{operations, Database, Delta}; + use gitbutler::projects::ProjectId; + use gitbutler::sessions::SessionId; use std::path; #[test] @@ -107,11 +107,11 @@ mod document; mod operations; mod writer { - use gitbutler_app::deltas::operations::Operation; - use gitbutler_app::{deltas, sessions}; + use gitbutler::deltas::operations::Operation; + use gitbutler::{deltas, sessions}; use std::vec; - use crate::{Case, Suite}; + use crate::shared::{Case, Suite}; #[test] fn write_no_vbranches() -> anyhow::Result<()> { @@ -122,15 +122,15 @@ mod writer { let session = gb_repository.get_or_create_current_session()?; let session_reader = sessions::Reader::open(gb_repository, &session)?; - let deltas_reader = gitbutler_app::deltas::Reader::new(&session_reader); + let deltas_reader = gitbutler::deltas::Reader::new(&session_reader); let path = "test.txt"; let deltas = vec![ - gitbutler_app::deltas::Delta { + gitbutler::deltas::Delta { operations: vec![Operation::Insert((0, "hello".to_string()))], timestamp_ms: 0, }, - gitbutler_app::deltas::Delta { + gitbutler::deltas::Delta { operations: vec![Operation::Insert((5, " world".to_string()))], timestamp_ms: 0, }, diff --git a/gitbutler-app/tests/deltas/operations.rs b/tests/deltas/operations.rs similarity index 95% rename from gitbutler-app/tests/deltas/operations.rs rename to tests/deltas/operations.rs index ed080a986..b273d4096 100644 --- a/gitbutler-app/tests/deltas/operations.rs +++ b/tests/deltas/operations.rs @@ -1,4 +1,4 @@ -use gitbutler_app::deltas::operations::{get_delta_operations, Operation}; +use gitbutler::deltas::operations::{get_delta_operations, Operation}; #[test] fn get_delta_operations_insert_end() { diff --git a/gitbutler-app/tests/gb_repository/mod.rs b/tests/gb_repository/mod.rs similarity index 99% rename from gitbutler-app/tests/gb_repository/mod.rs rename to tests/gb_repository/mod.rs index 822702a28..9505fb05b 100644 --- a/gitbutler-app/tests/gb_repository/mod.rs +++ b/tests/gb_repository/mod.rs @@ -4,10 +4,10 @@ use anyhow::Result; use pretty_assertions::assert_eq; use tempfile::TempDir; -use crate::init_opts_bare; -use crate::{Case, Suite}; +use crate::shared::init_opts_bare; +use crate::shared::{Case, Suite}; -use gitbutler_app::{ +use gitbutler::{ deltas::{self, operations::Operation}, projects::{self, ApiProject, ProjectId}, reader, @@ -17,7 +17,7 @@ use gitbutler_app::{ mod repository { use std::path::PathBuf; - use crate::{Case, Suite}; + use crate::shared::{Case, Suite}; use anyhow::Result; use pretty_assertions::assert_eq; diff --git a/gitbutler-app/tests/git/config.rs b/tests/git/config.rs similarity index 95% rename from gitbutler-app/tests/git/config.rs rename to tests/git/config.rs index 730401d70..6f94b90a6 100644 --- a/gitbutler-app/tests/git/config.rs +++ b/tests/git/config.rs @@ -1,4 +1,4 @@ -use crate::test_repository; +use crate::shared::test_repository; #[test] pub fn set_str() { diff --git a/gitbutler-app/tests/git/credentials.rs b/tests/git/credentials.rs similarity index 98% rename from gitbutler-app/tests/git/credentials.rs rename to tests/git/credentials.rs index f524a05d9..00fa1d86c 100644 --- a/gitbutler-app/tests/git/credentials.rs +++ b/tests/git/credentials.rs @@ -1,8 +1,8 @@ -use gitbutler_app::git::credentials::{Credential, Helper, HttpsCredential, SshCredential}; -use gitbutler_app::{keys, project_repository, projects, users}; +use gitbutler::git::credentials::{Credential, Helper, HttpsCredential, SshCredential}; +use gitbutler::{keys, project_repository, projects, users}; use std::path::PathBuf; -use crate::{temp_dir, test_repository}; +use crate::shared::{temp_dir, test_repository}; #[derive(Default)] struct TestCase<'a> { diff --git a/gitbutler-app/tests/git/diff.rs b/tests/git/diff.rs similarity index 99% rename from gitbutler-app/tests/git/diff.rs rename to tests/git/diff.rs index 1997df884..5494c2328 100644 --- a/gitbutler-app/tests/git/diff.rs +++ b/tests/git/diff.rs @@ -4,9 +4,9 @@ use anyhow::Result; use pretty_assertions::assert_eq; use tempfile::TempDir; -use crate::init_opts_bare; -use crate::{Case, Suite}; -use gitbutler_app::{ +use crate::shared::init_opts_bare; +use crate::shared::{Case, Suite}; +use gitbutler::{ deltas::{self, operations::Operation}, projects::{self, ApiProject, ProjectId}, reader, diff --git a/gitbutler-app/tests/git/mod.rs b/tests/git/mod.rs similarity index 100% rename from gitbutler-app/tests/git/mod.rs rename to tests/git/mod.rs diff --git a/gitbutler-app/tests/keys/mod.rs b/tests/keys/mod.rs similarity index 91% rename from gitbutler-app/tests/keys/mod.rs rename to tests/keys/mod.rs index 1655d4221..5856691b7 100644 --- a/gitbutler-app/tests/keys/mod.rs +++ b/tests/keys/mod.rs @@ -1,15 +1,15 @@ -use gitbutler_app::keys::{PrivateKey, PublicKey}; +use gitbutler::keys::{PrivateKey, PublicKey}; mod controller { #[cfg(not(target_os = "windows"))] mod not_windows { - use gitbutler_app::keys::storage::Storage; - use gitbutler_app::keys::Controller; + use gitbutler::keys::storage::Storage; + use gitbutler::keys::Controller; use std::fs; #[cfg(target_family = "unix")] use std::os::unix::prelude::*; - use crate::Suite; + use crate::shared::Suite; #[test] fn get_or_create() { diff --git a/gitbutler-app/tests/lock/mod.rs b/tests/lock/mod.rs similarity index 97% rename from gitbutler-app/tests/lock/mod.rs rename to tests/lock/mod.rs index 269e5e6e7..40a4442b1 100644 --- a/gitbutler-app/tests/lock/mod.rs +++ b/tests/lock/mod.rs @@ -1,6 +1,6 @@ -use gitbutler_app::lock::Dir; +use gitbutler::lock::Dir; -use crate::temp_dir; +use crate::shared::temp_dir; #[tokio::test] async fn lock_same_instance() { diff --git a/gitbutler-app/tests/reader/mod.rs b/tests/reader/mod.rs similarity index 97% rename from gitbutler-app/tests/reader/mod.rs rename to tests/reader/mod.rs index 3c60815cd..549c68903 100644 --- a/gitbutler-app/tests/reader/mod.rs +++ b/tests/reader/mod.rs @@ -1,8 +1,8 @@ -use gitbutler_app::reader::{CommitReader, Content, Reader}; +use gitbutler::reader::{CommitReader, Content, Reader}; use std::fs; use std::path::Path; -use crate::{commit_all, temp_dir, test_repository}; +use crate::shared::{commit_all, temp_dir, test_repository}; use anyhow::Result; #[test] diff --git a/gitbutler-app/tests/sessions/database.rs b/tests/sessions/database.rs similarity index 94% rename from gitbutler-app/tests/sessions/database.rs rename to tests/sessions/database.rs index 67abb17d8..185e02f29 100644 --- a/gitbutler-app/tests/sessions/database.rs +++ b/tests/sessions/database.rs @@ -1,6 +1,6 @@ -use crate::test_database; -use gitbutler_app::projects::ProjectId; -use gitbutler_app::sessions::{session, Database, Session, SessionId}; +use crate::shared::test_database; +use gitbutler::projects::ProjectId; +use gitbutler::sessions::{session, Database, Session, SessionId}; #[test] fn insert_query() -> anyhow::Result<()> { diff --git a/gitbutler-app/tests/sessions/mod.rs b/tests/sessions/mod.rs similarity index 96% rename from gitbutler-app/tests/sessions/mod.rs rename to tests/sessions/mod.rs index f6f6287d0..bc9d79efb 100644 --- a/gitbutler-app/tests/sessions/mod.rs +++ b/tests/sessions/mod.rs @@ -2,8 +2,8 @@ mod database; use anyhow::Result; -use crate::{Case, Suite}; -use gitbutler_app::sessions::{self, session::SessionId}; +use crate::shared::{Case, Suite}; +use gitbutler::sessions::{self, session::SessionId}; #[test] fn should_not_write_session_with_hash() { diff --git a/tests/shared/mod.rs b/tests/shared/mod.rs new file mode 100644 index 000000000..5d1f78815 --- /dev/null +++ b/tests/shared/mod.rs @@ -0,0 +1,61 @@ +pub const VAR_NO_CLEANUP: &str = "GITBUTLER_TESTS_NO_CLEANUP"; + +mod test_project; +pub use test_project::TestProject; + +mod suite; +pub use suite::*; + +pub mod paths { + use super::temp_dir; + use tempfile::TempDir; + + pub fn data_dir() -> TempDir { + temp_dir() + } +} + +pub mod virtual_branches { + use crate::shared::empty_bare_repository; + use gitbutler::{gb_repository, project_repository, virtual_branches}; + + pub fn set_test_target( + gb_repo: &gb_repository::Repository, + project_repository: &project_repository::Repository, + ) -> anyhow::Result<()> { + let (remote_repo, _tmp) = empty_bare_repository(); + let mut remote = project_repository + .git_repository + .remote( + "origin", + &remote_repo.path().to_str().unwrap().parse().unwrap(), + ) + .expect("failed to add remote"); + remote.push(&["refs/heads/master:refs/heads/master"], None)?; + + virtual_branches::target::Writer::new(gb_repo, project_repository.project().gb_dir())? + .write_default(&virtual_branches::target::Target { + branch: "refs/remotes/origin/master".parse().unwrap(), + remote_url: remote_repo.path().to_str().unwrap().parse().unwrap(), + sha: remote_repo.head().unwrap().target().unwrap(), + }) + .expect("failed to write target"); + + virtual_branches::integration::update_gitbutler_integration(gb_repo, project_repository) + .expect("failed to update integration"); + + Ok(()) + } +} + +pub fn init_opts() -> git2::RepositoryInitOptions { + let mut opts = git2::RepositoryInitOptions::new(); + opts.initial_head("master"); + opts +} + +pub fn init_opts_bare() -> git2::RepositoryInitOptions { + let mut opts = init_opts(); + opts.bare(true); + opts +} diff --git a/tests/shared/suite.rs b/tests/shared/suite.rs new file mode 100644 index 000000000..eee4e9b60 --- /dev/null +++ b/tests/shared/suite.rs @@ -0,0 +1,227 @@ +use std::path::{Path, PathBuf}; +use std::{collections::HashMap, fs}; + +use crate::shared::{init_opts, init_opts_bare, VAR_NO_CLEANUP}; +use tempfile::{tempdir, TempDir}; + +pub struct Suite { + pub local_app_data: Option, + pub storage: gitbutler::storage::Storage, + pub users: gitbutler::users::Controller, + pub projects: gitbutler::projects::Controller, + pub keys: gitbutler::keys::Controller, +} + +impl Drop for Suite { + fn drop(&mut self) { + if std::env::var_os(VAR_NO_CLEANUP).is_some() { + let _ = self.local_app_data.take().unwrap().into_path(); + } + } +} + +impl Default for Suite { + fn default() -> Self { + let local_app_data = temp_dir(); + let storage = gitbutler::storage::Storage::new(&local_app_data); + let users = gitbutler::users::Controller::from_path(&local_app_data); + let projects = gitbutler::projects::Controller::from_path(&local_app_data); + let keys = gitbutler::keys::Controller::from_path(&local_app_data); + Self { + storage, + local_app_data: Some(local_app_data), + users, + projects, + keys, + } + } +} + +impl Suite { + pub fn local_app_data(&self) -> &Path { + self.local_app_data.as_ref().unwrap().path() + } + pub fn sign_in(&self) -> gitbutler::users::User { + let user = gitbutler::users::User { + name: Some("test".to_string()), + email: "test@email.com".to_string(), + access_token: "token".to_string(), + ..Default::default() + }; + self.users.set_user(&user).expect("failed to add user"); + user + } + + fn project(&self, fs: HashMap) -> (gitbutler::projects::Project, TempDir) { + let (repository, tmp) = test_repository(); + for (path, contents) in fs { + if let Some(parent) = path.parent() { + fs::create_dir_all(repository.path().parent().unwrap().join(parent)) + .expect("failed to create dir"); + } + fs::write( + repository.path().parent().unwrap().join(&path), + contents.as_bytes(), + ) + .expect("failed to write file"); + } + commit_all(&repository); + + ( + self.projects + .add(repository.path().parent().unwrap()) + .expect("failed to add project"), + tmp, + ) + } + + pub fn new_case_with_files(&self, fs: HashMap) -> Case { + let (project, project_tmp) = self.project(fs); + Case::new(self, project, project_tmp) + } + + pub fn new_case(&self) -> Case { + self.new_case_with_files(HashMap::new()) + } +} + +pub struct Case<'a> { + suite: &'a Suite, + pub project: gitbutler::projects::Project, + pub project_repository: gitbutler::project_repository::Repository, + pub gb_repository: gitbutler::gb_repository::Repository, + pub credentials: gitbutler::git::credentials::Helper, + /// The directory containing the `project_repository` + project_tmp: Option, +} + +impl Drop for Case<'_> { + fn drop(&mut self) { + if let Some(tmp) = self + .project_tmp + .take() + .filter(|_| std::env::var_os(VAR_NO_CLEANUP).is_some()) + { + let _ = tmp.into_path(); + } + } +} + +impl<'a> Case<'a> { + fn new( + suite: &'a Suite, + project: gitbutler::projects::Project, + project_tmp: TempDir, + ) -> Case<'a> { + let project_repository = gitbutler::project_repository::Repository::open(&project) + .expect("failed to create project repository"); + let gb_repository = gitbutler::gb_repository::Repository::open( + suite.local_app_data(), + &project_repository, + None, + ) + .expect("failed to open gb repository"); + let credentials = gitbutler::git::credentials::Helper::from_path(suite.local_app_data()); + Case { + suite, + project, + gb_repository, + project_repository, + project_tmp: Some(project_tmp), + credentials, + } + } + + pub fn refresh(mut self) -> Self { + let project = self + .suite + .projects + .get(&self.project.id) + .expect("failed to get project"); + let project_repository = gitbutler::project_repository::Repository::open(&project) + .expect("failed to create project repository"); + let user = self.suite.users.get_user().expect("failed to get user"); + let credentials = + gitbutler::git::credentials::Helper::from_path(self.suite.local_app_data()); + Self { + suite: self.suite, + gb_repository: gitbutler::gb_repository::Repository::open( + self.suite.local_app_data(), + &project_repository, + user.as_ref(), + ) + .expect("failed to open gb repository"), + credentials, + project_repository, + project, + project_tmp: self.project_tmp.take(), + } + } +} + +pub fn test_database() -> (gitbutler::database::Database, TempDir) { + let tmp = temp_dir(); + let db = gitbutler::database::Database::open_in_directory(&tmp).unwrap(); + (db, tmp) +} + +pub fn temp_dir() -> TempDir { + tempdir().unwrap() +} + +pub fn empty_bare_repository() -> (gitbutler::git::Repository, TempDir) { + let tmp = temp_dir(); + ( + gitbutler::git::Repository::init_opts(&tmp, &init_opts_bare()) + .expect("failed to init repository"), + tmp, + ) +} + +pub fn test_repository() -> (gitbutler::git::Repository, TempDir) { + let tmp = temp_dir(); + let repository = gitbutler::git::Repository::init_opts(&tmp, &init_opts()) + .expect("failed to init repository"); + let mut index = repository.index().expect("failed to get index"); + let oid = index.write_tree().expect("failed to write tree"); + let signature = gitbutler::git::Signature::now("test", "test@email.com").unwrap(); + repository + .commit( + Some(&"refs/heads/master".parse().unwrap()), + &signature, + &signature, + "Initial commit", + &repository.find_tree(oid).expect("failed to find tree"), + &[], + ) + .expect("failed to commit"); + (repository, tmp) +} + +pub fn commit_all(repository: &gitbutler::git::Repository) -> gitbutler::git::Oid { + let mut index = repository.index().expect("failed to get index"); + index + .add_all(["."], git2::IndexAddOption::DEFAULT, None) + .expect("failed to add all"); + index.write().expect("failed to write index"); + let oid = index.write_tree().expect("failed to write tree"); + let signature = gitbutler::git::Signature::now("test", "test@email.com").unwrap(); + let head = repository.head().expect("failed to get head"); + let commit_oid = repository + .commit( + Some(&head.name().unwrap()), + &signature, + &signature, + "some commit", + &repository.find_tree(oid).expect("failed to find tree"), + &[&repository + .find_commit( + repository + .refname_to_id("HEAD") + .expect("failed to get head"), + ) + .expect("failed to find commit")], + ) + .expect("failed to commit"); + commit_oid +} diff --git a/gitbutler-app/tests/common/mod.rs b/tests/shared/test_project.rs similarity index 98% rename from gitbutler-app/tests/common/mod.rs rename to tests/shared/test_project.rs index c236e06f7..9d7fe3cb1 100644 --- a/gitbutler-app/tests/common/mod.rs +++ b/tests/shared/test_project.rs @@ -1,6 +1,7 @@ #![allow(unused)] -use crate::{init_opts, VAR_NO_CLEANUP}; -use gitbutler_app::git; + +use crate::shared::{init_opts, VAR_NO_CLEANUP}; +use gitbutler::git; use std::{path, str::from_utf8}; use tempfile::TempDir; @@ -343,13 +344,3 @@ impl TestProject { submodule.add_finalize().unwrap(); } } - -pub mod paths { - use super::temp_dir; - use std::path; - use tempfile::TempDir; - - pub fn data_dir() -> TempDir { - temp_dir() - } -} diff --git a/gitbutler-app/tests/suite/gb_repository.rs b/tests/suite/gb_repository.rs similarity index 97% rename from gitbutler-app/tests/suite/gb_repository.rs rename to tests/suite/gb_repository.rs index a1942bff6..03af30c03 100644 --- a/gitbutler-app/tests/suite/gb_repository.rs +++ b/tests/suite/gb_repository.rs @@ -1,5 +1,5 @@ -use crate::common::{paths, TestProject}; -use gitbutler_app::{gb_repository, git, project_repository, projects}; +use crate::shared::{paths, TestProject}; +use gitbutler::{gb_repository, git, project_repository, projects}; use std::path; mod init { diff --git a/gitbutler-app/tests/suite/projects.rs b/tests/suite/projects.rs similarity index 88% rename from gitbutler-app/tests/suite/projects.rs rename to tests/suite/projects.rs index eb4459dd8..422d9a195 100644 --- a/gitbutler-app/tests/suite/projects.rs +++ b/tests/suite/projects.rs @@ -1,7 +1,7 @@ -use gitbutler_app::projects::Controller; +use gitbutler::projects::Controller; use tempfile::TempDir; -use crate::common::{self, paths}; +use crate::shared::{self, paths}; pub fn new() -> (Controller, TempDir) { let data_dir = paths::data_dir(); @@ -15,7 +15,7 @@ mod add { #[test] fn success() { let (controller, _tmp) = new(); - let repository = common::TestProject::default(); + let repository = shared::TestProject::default(); let path = repository.path(); let project = controller.add(path).unwrap(); assert_eq!(project.path, path); @@ -23,7 +23,7 @@ mod add { } mod error { - use gitbutler_app::projects::AddError; + use gitbutler::projects::AddError; use super::*; @@ -62,7 +62,7 @@ mod add { #[test] fn twice() { let (controller, _tmp) = new(); - let repository = common::TestProject::default(); + let repository = shared::TestProject::default(); let path = repository.path(); controller.add(path).unwrap(); assert!(matches!(controller.add(path), Err(AddError::AlreadyExists))); diff --git a/gitbutler-app/tests/suite/virtual_branches/amend.rs b/tests/suite/virtual_branches/amend.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/amend.rs rename to tests/suite/virtual_branches/amend.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/apply_virtual_branch.rs b/tests/suite/virtual_branches/apply_virtual_branch.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/apply_virtual_branch.rs rename to tests/suite/virtual_branches/apply_virtual_branch.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/cherry_pick.rs b/tests/suite/virtual_branches/cherry_pick.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/cherry_pick.rs rename to tests/suite/virtual_branches/cherry_pick.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/create_commit.rs b/tests/suite/virtual_branches/create_commit.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/create_commit.rs rename to tests/suite/virtual_branches/create_commit.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/create_virtual_branch_from_branch.rs b/tests/suite/virtual_branches/create_virtual_branch_from_branch.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/create_virtual_branch_from_branch.rs rename to tests/suite/virtual_branches/create_virtual_branch_from_branch.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/delete_virtual_branch.rs b/tests/suite/virtual_branches/delete_virtual_branch.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/delete_virtual_branch.rs rename to tests/suite/virtual_branches/delete_virtual_branch.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/fetch_from_target.rs b/tests/suite/virtual_branches/fetch_from_target.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/fetch_from_target.rs rename to tests/suite/virtual_branches/fetch_from_target.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/init.rs b/tests/suite/virtual_branches/init.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/init.rs rename to tests/suite/virtual_branches/init.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/mod.rs b/tests/suite/virtual_branches/mod.rs similarity index 98% rename from gitbutler-app/tests/suite/virtual_branches/mod.rs rename to tests/suite/virtual_branches/mod.rs index 5534c34c3..f08b80286 100644 --- a/gitbutler-app/tests/suite/virtual_branches/mod.rs +++ b/tests/suite/virtual_branches/mod.rs @@ -1,9 +1,9 @@ use std::{fs, path, str::FromStr}; use tempfile::TempDir; -use crate::common::{paths, TestProject}; -use crate::VAR_NO_CLEANUP; -use gitbutler_app::{ +use crate::shared::VAR_NO_CLEANUP; +use crate::shared::{paths, TestProject}; +use gitbutler::{ git, keys, projects::{self, ProjectId}, users, diff --git a/gitbutler-app/tests/suite/virtual_branches/move_commit_to_vbranch.rs b/tests/suite/virtual_branches/move_commit_to_vbranch.rs similarity index 98% rename from gitbutler-app/tests/suite/virtual_branches/move_commit_to_vbranch.rs rename to tests/suite/virtual_branches/move_commit_to_vbranch.rs index 34848382b..ad4a0400e 100644 --- a/gitbutler-app/tests/suite/virtual_branches/move_commit_to_vbranch.rs +++ b/tests/suite/virtual_branches/move_commit_to_vbranch.rs @@ -1,7 +1,7 @@ use crate::suite::virtual_branches::Test; -use gitbutler_app::git; -use gitbutler_app::virtual_branches::controller::ControllerError; -use gitbutler_app::virtual_branches::{branch, errors, BranchId}; +use gitbutler::git; +use gitbutler::virtual_branches::controller::ControllerError; +use gitbutler::virtual_branches::{branch, errors, BranchId}; use std::str::FromStr; #[tokio::test] diff --git a/gitbutler-app/tests/suite/virtual_branches/references.rs b/tests/suite/virtual_branches/references.rs similarity index 98% rename from gitbutler-app/tests/suite/virtual_branches/references.rs rename to tests/suite/virtual_branches/references.rs index 91682ee34..3f4d4ff40 100644 --- a/gitbutler-app/tests/suite/virtual_branches/references.rs +++ b/tests/suite/virtual_branches/references.rs @@ -52,7 +52,7 @@ mod create_virtual_branch { let branch1_id = controller .create_virtual_branch( project_id, - &gitbutler_app::virtual_branches::branch::BranchCreateRequest { + &gitbutler::virtual_branches::branch::BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, @@ -63,7 +63,7 @@ mod create_virtual_branch { let branch2_id = controller .create_virtual_branch( project_id, - &gitbutler_app::virtual_branches::branch::BranchCreateRequest { + &gitbutler::virtual_branches::branch::BranchCreateRequest { name: Some("name".to_string()), ..Default::default() }, diff --git a/gitbutler-app/tests/suite/virtual_branches/reset_virtual_branch.rs b/tests/suite/virtual_branches/reset_virtual_branch.rs similarity index 98% rename from gitbutler-app/tests/suite/virtual_branches/reset_virtual_branch.rs rename to tests/suite/virtual_branches/reset_virtual_branch.rs index 7a67efb1a..bbae5a12f 100644 --- a/gitbutler-app/tests/suite/virtual_branches/reset_virtual_branch.rs +++ b/tests/suite/virtual_branches/reset_virtual_branch.rs @@ -1,7 +1,5 @@ use crate::suite::virtual_branches::Test; -use gitbutler_app::virtual_branches::{ - branch, controller::ControllerError, errors::ResetBranchError, -}; +use gitbutler::virtual_branches::{branch, controller::ControllerError, errors::ResetBranchError}; use std::fs; #[tokio::test] diff --git a/gitbutler-app/tests/suite/virtual_branches/selected_for_changes.rs b/tests/suite/virtual_branches/selected_for_changes.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/selected_for_changes.rs rename to tests/suite/virtual_branches/selected_for_changes.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/set_base_branch.rs b/tests/suite/virtual_branches/set_base_branch.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/set_base_branch.rs rename to tests/suite/virtual_branches/set_base_branch.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/squash.rs b/tests/suite/virtual_branches/squash.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/squash.rs rename to tests/suite/virtual_branches/squash.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/unapply.rs b/tests/suite/virtual_branches/unapply.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/unapply.rs rename to tests/suite/virtual_branches/unapply.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/unapply_ownership.rs b/tests/suite/virtual_branches/unapply_ownership.rs similarity index 92% rename from gitbutler-app/tests/suite/virtual_branches/unapply_ownership.rs rename to tests/suite/virtual_branches/unapply_ownership.rs index 2e3f285d9..d283bcac2 100644 --- a/gitbutler-app/tests/suite/virtual_branches/unapply_ownership.rs +++ b/tests/suite/virtual_branches/unapply_ownership.rs @@ -1,6 +1,6 @@ use crate::suite::virtual_branches::Test; -use gitbutler_app::virtual_branches::branch; -use gitbutler_app::virtual_branches::branch::BranchOwnershipClaims; +use gitbutler::virtual_branches::branch; +use gitbutler::virtual_branches::branch::BranchOwnershipClaims; use std::fs; #[tokio::test] diff --git a/gitbutler-app/tests/suite/virtual_branches/update_base_branch.rs b/tests/suite/virtual_branches/update_base_branch.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/update_base_branch.rs rename to tests/suite/virtual_branches/update_base_branch.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/update_commit_message.rs b/tests/suite/virtual_branches/update_commit_message.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/update_commit_message.rs rename to tests/suite/virtual_branches/update_commit_message.rs diff --git a/gitbutler-app/tests/suite/virtual_branches/upstream.rs b/tests/suite/virtual_branches/upstream.rs similarity index 100% rename from gitbutler-app/tests/suite/virtual_branches/upstream.rs rename to tests/suite/virtual_branches/upstream.rs diff --git a/gitbutler-app/tests/types/mod.rs b/tests/types/mod.rs similarity index 89% rename from gitbutler-app/tests/types/mod.rs rename to tests/types/mod.rs index 98b97dd38..2eb45eede 100644 --- a/gitbutler-app/tests/types/mod.rs +++ b/tests/types/mod.rs @@ -1,4 +1,4 @@ -use gitbutler_app::types::default_true::DefaultTrue; +use gitbutler::types::default_true::DefaultTrue; #[test] #[allow(clippy::bool_assert_comparison)] diff --git a/gitbutler-app/tests/virtual_branches/branch/context.rs b/tests/virtual_branches/branch/context.rs similarity index 99% rename from gitbutler-app/tests/virtual_branches/branch/context.rs rename to tests/virtual_branches/branch/context.rs index 215d6fbc6..05601f81e 100644 --- a/gitbutler-app/tests/virtual_branches/branch/context.rs +++ b/tests/virtual_branches/branch/context.rs @@ -1,5 +1,5 @@ -use gitbutler_app::git::diff; -use gitbutler_app::virtual_branches::context::hunk_with_context; +use gitbutler::git::diff; +use gitbutler::virtual_branches::context::hunk_with_context; #[test] fn replace_line_mid_file() { diff --git a/gitbutler-app/tests/virtual_branches/branch/file_ownership.rs b/tests/virtual_branches/branch/file_ownership.rs similarity index 98% rename from gitbutler-app/tests/virtual_branches/branch/file_ownership.rs rename to tests/virtual_branches/branch/file_ownership.rs index 7d7ff0c36..ab30481be 100644 --- a/gitbutler-app/tests/virtual_branches/branch/file_ownership.rs +++ b/tests/virtual_branches/branch/file_ownership.rs @@ -1,4 +1,4 @@ -use gitbutler_app::virtual_branches::branch::OwnershipClaim; +use gitbutler::virtual_branches::branch::OwnershipClaim; #[test] fn parse_ownership() { diff --git a/gitbutler-app/tests/virtual_branches/branch/hunk.rs b/tests/virtual_branches/branch/hunk.rs similarity index 97% rename from gitbutler-app/tests/virtual_branches/branch/hunk.rs rename to tests/virtual_branches/branch/hunk.rs index d91d7313a..5703a9407 100644 --- a/gitbutler-app/tests/virtual_branches/branch/hunk.rs +++ b/tests/virtual_branches/branch/hunk.rs @@ -1,4 +1,4 @@ -use gitbutler_app::virtual_branches::branch::Hunk; +use gitbutler::virtual_branches::branch::Hunk; #[test] fn to_from_string() { diff --git a/gitbutler-app/tests/virtual_branches/branch/mod.rs b/tests/virtual_branches/branch/mod.rs similarity index 64% rename from gitbutler-app/tests/virtual_branches/branch/mod.rs rename to tests/virtual_branches/branch/mod.rs index 93303001b..5264e4f0e 100644 --- a/gitbutler-app/tests/virtual_branches/branch/mod.rs +++ b/tests/virtual_branches/branch/mod.rs @@ -1,4 +1,4 @@ -use gitbutler_app::virtual_branches::Branch; +use gitbutler::virtual_branches::Branch; mod context; mod file_ownership; diff --git a/gitbutler-app/tests/virtual_branches/branch/ownership.rs b/tests/virtual_branches/branch/ownership.rs similarity index 98% rename from gitbutler-app/tests/virtual_branches/branch/ownership.rs rename to tests/virtual_branches/branch/ownership.rs index f4889e48c..54d68efd3 100644 --- a/gitbutler-app/tests/virtual_branches/branch/ownership.rs +++ b/tests/virtual_branches/branch/ownership.rs @@ -1,7 +1,7 @@ -use gitbutler_app::virtual_branches::branch::{ +use gitbutler::virtual_branches::branch::{ reconcile_claims, BranchOwnershipClaims, Hunk, OwnershipClaim, }; -use gitbutler_app::virtual_branches::Branch; +use gitbutler::virtual_branches::Branch; use std::{path::PathBuf, vec}; diff --git a/gitbutler-app/tests/virtual_branches/branch/reader.rs b/tests/virtual_branches/branch/reader.rs similarity index 88% rename from gitbutler-app/tests/virtual_branches/branch/reader.rs rename to tests/virtual_branches/branch/reader.rs index f8c4a04d2..61cdf3d94 100644 --- a/gitbutler-app/tests/virtual_branches/branch/reader.rs +++ b/tests/virtual_branches/branch/reader.rs @@ -3,9 +3,9 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use anyhow::Result; use once_cell::sync::Lazy; -use crate::{Case, Suite}; -use gitbutler_app::virtual_branches::branch::BranchOwnershipClaims; -use gitbutler_app::virtual_branches::{branch, Branch, BranchId}; +use crate::shared::{Case, Suite}; +use gitbutler::virtual_branches::branch::BranchOwnershipClaims; +use gitbutler::virtual_branches::{branch, Branch, BranchId}; static TEST_INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); @@ -63,7 +63,7 @@ fn read_not_found() -> Result<()> { let Case { gb_repository, .. } = &suite.new_case(); let session = gb_repository.get_or_create_current_session()?; - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let reader = branch::Reader::new(&session_reader); let result = reader.read(&BranchId::generate()); @@ -88,7 +88,7 @@ fn read_override() -> Result<()> { writer.write(&mut branch)?; let session = gb_repository.get_current_session()?.unwrap(); - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let reader = branch::Reader::new(&session_reader); diff --git a/gitbutler-app/tests/virtual_branches/branch/writer.rs b/tests/virtual_branches/branch/writer.rs similarity index 94% rename from gitbutler-app/tests/virtual_branches/branch/writer.rs rename to tests/virtual_branches/branch/writer.rs index 34e1f9b1b..1e65e18c5 100644 --- a/gitbutler-app/tests/virtual_branches/branch/writer.rs +++ b/tests/virtual_branches/branch/writer.rs @@ -4,10 +4,10 @@ use std::{ }; use anyhow::Context; -use gitbutler_app::virtual_branches::branch; +use gitbutler::virtual_branches::branch; use once_cell::sync::Lazy; -use crate::{Case, Suite}; +use crate::shared::{Case, Suite}; use self::branch::BranchId; @@ -46,8 +46,8 @@ fn new_test_branch() -> Branch { ) .parse() .unwrap(), - ownership: gitbutler_app::virtual_branches::branch::BranchOwnershipClaims { - claims: vec![gitbutler_app::virtual_branches::branch::OwnershipClaim { + ownership: gitbutler::virtual_branches::branch::BranchOwnershipClaims { + claims: vec![gitbutler::virtual_branches::branch::OwnershipClaim { file_path: format!("file/{}:1-2", TEST_INDEX.load(Ordering::Relaxed)).into(), hunks: vec![], }], @@ -162,9 +162,7 @@ fn should_update() -> anyhow::Result<()> { upstream: Some("refs/remotes/origin/upstream_updated".parse().unwrap()), created_timestamp_ms: 2, updated_timestamp_ms: 3, - ownership: gitbutler_app::virtual_branches::branch::BranchOwnershipClaims { - claims: vec![], - }, + ownership: gitbutler::virtual_branches::branch::BranchOwnershipClaims { claims: vec![] }, ..branch.clone() }; diff --git a/gitbutler-app/tests/virtual_branches/iterator.rs b/tests/virtual_branches/iterator.rs similarity index 86% rename from gitbutler-app/tests/virtual_branches/iterator.rs rename to tests/virtual_branches/iterator.rs index fbe0809df..22dee020b 100644 --- a/gitbutler-app/tests/virtual_branches/iterator.rs +++ b/tests/virtual_branches/iterator.rs @@ -1,10 +1,10 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use anyhow::Result; -use gitbutler_app::virtual_branches; +use gitbutler::virtual_branches; use once_cell::sync::Lazy; -use crate::{Case, Suite}; +use crate::shared::{Case, Suite}; static TEST_INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); @@ -72,7 +72,7 @@ fn empty_iterator() -> Result<()> { let Case { gb_repository, .. } = &suite.new_case(); let session = gb_repository.get_or_create_current_session()?; - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let iter = virtual_branches::Iterator::new(&session_reader)?; @@ -91,11 +91,11 @@ fn iterate_all() -> Result<()> { } = &suite.new_case(); let target_writer = - gitbutler_app::virtual_branches::target::Writer::new(gb_repository, project.gb_dir())?; + gitbutler::virtual_branches::target::Writer::new(gb_repository, project.gb_dir())?; target_writer.write_default(&new_test_target())?; let branch_writer = - gitbutler_app::virtual_branches::branch::Writer::new(gb_repository, project.gb_dir())?; + gitbutler::virtual_branches::branch::Writer::new(gb_repository, project.gb_dir())?; let mut branch_1 = new_test_branch(); branch_writer.write(&mut branch_1)?; let mut branch_2 = new_test_branch(); @@ -104,10 +104,10 @@ fn iterate_all() -> Result<()> { branch_writer.write(&mut branch_3)?; let session = gb_repository.get_current_session()?.unwrap(); - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let iter = virtual_branches::Iterator::new(&session_reader)? - .collect::, gitbutler_app::reader::Error>>()?; + .collect::, gitbutler::reader::Error>>()?; assert_eq!(iter.len(), 3); assert!(iter.contains(&branch_1)); assert!(iter.contains(&branch_2)); diff --git a/gitbutler-app/tests/virtual_branches/mod.rs b/tests/virtual_branches/mod.rs similarity index 98% rename from gitbutler-app/tests/virtual_branches/mod.rs rename to tests/virtual_branches/mod.rs index b928e3c63..ead1d0506 100644 --- a/gitbutler-app/tests/virtual_branches/mod.rs +++ b/tests/virtual_branches/mod.rs @@ -13,48 +13,18 @@ use std::{ os::unix::{fs::symlink, prelude::*}, }; -use crate::{commit_all, empty_bare_repository, Case, Suite}; -use gitbutler_app::{ - gb_repository, git, project_repository, reader, sessions, virtual_branches, - virtual_branches::errors::CommitError, -}; +use crate::shared::{commit_all, Case, Suite}; +use gitbutler::{git, reader, sessions, virtual_branches, virtual_branches::errors::CommitError}; -use gitbutler_app::virtual_branches::branch::{BranchCreateRequest, BranchOwnershipClaims}; -use gitbutler_app::virtual_branches::integration::verify_branch; -use gitbutler_app::virtual_branches::{ +use crate::shared::virtual_branches::set_test_target; +use gitbutler::virtual_branches::branch::{BranchCreateRequest, BranchOwnershipClaims}; +use gitbutler::virtual_branches::integration::verify_branch; +use gitbutler::virtual_branches::{ apply_branch, commit, create_virtual_branch, is_remote_branch_mergeable, is_virtual_branch_mergeable, list_remote_branches, merge_virtual_branch_upstream, unapply_ownership, update_branch, }; -pub fn set_test_target( - gb_repo: &gb_repository::Repository, - project_repository: &project_repository::Repository, -) -> Result<()> { - let (remote_repo, _tmp) = empty_bare_repository(); - let mut remote = project_repository - .git_repository - .remote( - "origin", - &remote_repo.path().to_str().unwrap().parse().unwrap(), - ) - .expect("failed to add remote"); - remote.push(&["refs/heads/master:refs/heads/master"], None)?; - - virtual_branches::target::Writer::new(gb_repo, project_repository.project().gb_dir())? - .write_default(&virtual_branches::target::Target { - branch: "refs/remotes/origin/master".parse().unwrap(), - remote_url: remote_repo.path().to_str().unwrap().parse().unwrap(), - sha: remote_repo.head().unwrap().target().unwrap(), - }) - .expect("failed to write target"); - - virtual_branches::integration::update_gitbutler_integration(gb_repo, project_repository) - .expect("failed to update integration"); - - Ok(()) -} - #[test] fn commit_on_branch_then_change_file_then_get_status() -> Result<()> { let suite = Suite::default(); diff --git a/gitbutler-app/tests/virtual_branches/target/mod.rs b/tests/virtual_branches/target/mod.rs similarity index 100% rename from gitbutler-app/tests/virtual_branches/target/mod.rs rename to tests/virtual_branches/target/mod.rs diff --git a/gitbutler-app/tests/virtual_branches/target/reader.rs b/tests/virtual_branches/target/reader.rs similarity index 82% rename from gitbutler-app/tests/virtual_branches/target/reader.rs rename to tests/virtual_branches/target/reader.rs index 874e0c8e0..b3a8586c9 100644 --- a/gitbutler-app/tests/virtual_branches/target/reader.rs +++ b/tests/virtual_branches/target/reader.rs @@ -1,18 +1,18 @@ -use gitbutler_app::virtual_branches::target::Target; -use gitbutler_app::virtual_branches::{target, BranchId}; +use gitbutler::virtual_branches::target::Target; +use gitbutler::virtual_branches::{target, BranchId}; use std::sync::atomic::{AtomicUsize, Ordering}; use anyhow::Result; use once_cell::sync::Lazy; -use crate::{Case, Suite}; +use crate::shared::{Case, Suite}; static TEST_INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); -fn test_branch() -> gitbutler_app::virtual_branches::branch::Branch { +fn test_branch() -> gitbutler::virtual_branches::branch::Branch { TEST_INDEX.fetch_add(1, Ordering::Relaxed); - gitbutler_app::virtual_branches::branch::Branch { + gitbutler::virtual_branches::branch::Branch { id: BranchId::generate(), name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)), notes: String::new(), @@ -40,8 +40,8 @@ fn test_branch() -> gitbutler_app::virtual_branches::branch::Branch { ) .parse() .unwrap(), - ownership: gitbutler_app::virtual_branches::branch::BranchOwnershipClaims { - claims: vec![gitbutler_app::virtual_branches::branch::OwnershipClaim { + ownership: gitbutler::virtual_branches::branch::BranchOwnershipClaims { + claims: vec![gitbutler::virtual_branches::branch::OwnershipClaim { file_path: format!("file/{}", TEST_INDEX.load(Ordering::Relaxed)).into(), hunks: vec![], }], @@ -57,7 +57,7 @@ fn read_not_found() -> Result<()> { let Case { gb_repository, .. } = &suite.new_case(); let session = gb_repository.get_or_create_current_session()?; - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let reader = target::Reader::new(&session_reader); let result = reader.read(&BranchId::generate()); @@ -72,7 +72,7 @@ fn read_deprecated_format() -> Result<()> { let suite = Suite::default(); let Case { gb_repository, .. } = &suite.new_case(); - let writer = gitbutler_app::writer::DirWriter::open(gb_repository.root())?; + let writer = gitbutler::writer::DirWriter::open(gb_repository.root())?; writer .write_string("branches/target/name", "origin/master") .unwrap(); @@ -90,7 +90,7 @@ fn read_deprecated_format() -> Result<()> { .unwrap(); let session = gb_repository.get_or_create_current_session()?; - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let reader = target::Reader::new(&session_reader); let read = reader.read_default().unwrap(); @@ -131,11 +131,11 @@ fn read_override_target() -> Result<()> { }; let branch_writer = - gitbutler_app::virtual_branches::branch::Writer::new(gb_repository, project.gb_dir())?; + gitbutler::virtual_branches::branch::Writer::new(gb_repository, project.gb_dir())?; branch_writer.write(&mut branch)?; let session = gb_repository.get_current_session()?.unwrap(); - let session_reader = gitbutler_app::sessions::Reader::open(gb_repository, &session)?; + let session_reader = gitbutler::sessions::Reader::open(gb_repository, &session)?; let target_writer = target::Writer::new(gb_repository, project.gb_dir())?; let reader = target::Reader::new(&session_reader); diff --git a/gitbutler-app/tests/virtual_branches/target/writer.rs b/tests/virtual_branches/target/writer.rs similarity index 97% rename from gitbutler-app/tests/virtual_branches/target/writer.rs rename to tests/virtual_branches/target/writer.rs index 9ccc2bb3d..764db9e4d 100644 --- a/gitbutler-app/tests/virtual_branches/target/writer.rs +++ b/tests/virtual_branches/target/writer.rs @@ -6,9 +6,9 @@ use std::{ use once_cell::sync::Lazy; -use crate::{Case, Suite}; -use gitbutler_app::virtual_branches::target::Target; -use gitbutler_app::virtual_branches::{branch, target, BranchId}; +use crate::shared::{Case, Suite}; +use gitbutler::virtual_branches::target::Target; +use gitbutler::virtual_branches::{branch, target, BranchId}; static TEST_INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); diff --git a/gitbutler-app/tests/zip/mod.rs b/tests/zip/mod.rs similarity index 97% rename from gitbutler-app/tests/zip/mod.rs rename to tests/zip/mod.rs index f35ac2f84..52778c7a3 100644 --- a/gitbutler-app/tests/zip/mod.rs +++ b/tests/zip/mod.rs @@ -1,4 +1,4 @@ -use gitbutler_app::zip::Zipper; +use gitbutler::zip::Zipper; use walkdir::WalkDir; use std::fs::File;