mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 23:02:31 +03:00
Merge pull request #3373 from gitbutlerapp/lib-in-toplevel
extract general library from `app` crate
This commit is contained in:
commit
86b69064c2
27
.github/workflows/push.yaml
vendored
27
.github/workflows/push.yaml
vendored
@ -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' }}
|
||||
|
132
Cargo.lock
generated
132
Cargo.lock
generated
@ -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]]
|
||||
|
71
Cargo.toml
71
Cargo.toml
@ -1,3 +1,72 @@
|
||||
[package]
|
||||
name = "gitbutler"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.57"
|
||||
authors = ["GitButler <gitbutler@gitbutler.com>"]
|
||||
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" ] }
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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<Option<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AskpassBroker {
|
||||
pending_requests: Arc<Mutex<HashMap<Id<AskpassRequest>, AskpassRequest>>>,
|
||||
handle: AppHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
struct PromptEvent<C: Serialize + Clone> {
|
||||
id: Id<AskpassRequest>,
|
||||
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<C: Serialize + Clone>(
|
||||
&self,
|
||||
prompt: String,
|
||||
context: C,
|
||||
) -> Option<String> {
|
||||
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<AskpassRequest>, response: Option<String>) {
|
||||
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(
|
||||
|
@ -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<app::Error> for Error {
|
||||
@ -71,13 +71,13 @@ pub async fn git_test_push(
|
||||
branch_name: &str,
|
||||
) -> Result<(), Error> {
|
||||
let app = handle.state::<app::App>();
|
||||
let helper = handle.state::<crate::git::credentials::Helper>();
|
||||
let helper = handle.state::<gitbutler::git::credentials::Helper>();
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".to_string(),
|
||||
})?;
|
||||
let askpass_broker = handle
|
||||
.state::<crate::askpass::AskpassBroker>()
|
||||
.state::<gitbutler::askpass::AskpassBroker>()
|
||||
.inner()
|
||||
.clone();
|
||||
app.git_test_push(
|
||||
@ -102,13 +102,13 @@ pub async fn git_test_fetch(
|
||||
action: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
let app = handle.state::<app::App>();
|
||||
let helper = handle.state::<crate::git::credentials::Helper>();
|
||||
let helper = handle.state::<gitbutler::git::credentials::Helper>();
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".to_string(),
|
||||
})?;
|
||||
let askpass_broker = handle
|
||||
.state::<crate::askpass::AskpassBroker>()
|
||||
.state::<gitbutler::askpass::AskpassBroker>()
|
||||
.inner()
|
||||
.clone();
|
||||
app.git_test_fetch(
|
||||
|
@ -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<ListError> 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<Vec<&str>>,
|
||||
) -> Result<HashMap<String, Vec<Delta>>, 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::<Controller>()
|
||||
.list_by_session_id(&project_id, &session_id, &paths)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -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<ListError> 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<Vec<&str>>,
|
||||
) -> Result<HashMap<String, Vec<Delta>>, 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::<Controller>()
|
||||
.list_by_session_id(&project_id, &session_id, &paths)
|
||||
.map_err(Into::into)
|
||||
}
|
@ -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<project_repository::OpenError> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use crate::{
|
||||
use gitbutler::{
|
||||
deltas,
|
||||
projects::ProjectId,
|
||||
reader,
|
||||
|
@ -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<Verification, Error> {
|
||||
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<String, Error> {
|
||||
#[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::<AccessTokenContainer>(&rsp_body)
|
||||
.map(|rsp_body| rsp_body.access_token)
|
||||
.context("Failed to parse response body")
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -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<Verification, Error> {
|
||||
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<String, Error> {
|
||||
#[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::<AccessTokenContainer>(&rsp_body)
|
||||
.map(|rsp_body| rsp_body.access_token)
|
||||
.context("Failed to parse response body")
|
||||
.map_err(Into::into)
|
||||
}
|
@ -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<controller::GetOrCreateError> 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<PublicKey, Error> {
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.get_or_create()
|
||||
.map(|key| key.public_key())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
use tauri::Manager;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use super::{controller, PublicKey};
|
||||
|
||||
impl From<controller::GetOrCreateError> 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<PublicKey, Error> {
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.get_or_create()
|
||||
.map(|key| key.public_key())
|
||||
.map_err(Into::into)
|
||||
}
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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<controller::UpdateError> 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<projects::Project, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.update(&project)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::AddError> 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<projects::Project, Error> {
|
||||
handle.state::<Controller>().add(path).map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::GetError> 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<projects::Project, Error> {
|
||||
let id = id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle.state::<Controller>().get(&id).map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::ListError> 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<Vec<projects::Project>, Error> {
|
||||
handle.state::<Controller>().list().map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::DeleteError> 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::<Controller>()
|
||||
.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<Option<String>, Error> {
|
||||
let id = id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.set_local_config(&id, key, value)
|
||||
.map_err(|e| Error::UserError {
|
||||
code: Code::Projects,
|
||||
message: e.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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<controller::UpdateError> 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<projects::Project, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.update(&project)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::AddError> 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<projects::Project, Error> {
|
||||
handle.state::<Controller>().add(path).map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::GetError> 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<projects::Project, Error> {
|
||||
let id = id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle.state::<Controller>().get(&id).map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::ListError> 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<Vec<projects::Project>, Error> {
|
||||
handle.state::<Controller>().list().map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::DeleteError> 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::<Controller>()
|
||||
.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<Option<String>, Error> {
|
||||
let id = id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.set_local_config(&id, key, value)
|
||||
.map_err(|e| Error::UserError {
|
||||
code: Code::Projects,
|
||||
message: e.to_string(),
|
||||
})
|
||||
}
|
@ -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<RateLimiter<NotKeyed, InMemoryState, QuantaClock>> = OnceCell::new();
|
||||
|
@ -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<ListError> 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<u128>,
|
||||
) -> Result<Vec<Session>, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".to_string(),
|
||||
})?;
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.list(&project_id, earliest_timestamp_ms)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1 @@
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::error::{Code, Error};
|
||||
|
||||
use super::{
|
||||
controller::{Controller, ListError},
|
||||
Session,
|
||||
};
|
||||
|
||||
impl From<ListError> 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<u128>,
|
||||
) -> Result<Vec<Session>, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".to_string(),
|
||||
})?;
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.list(&project_id, earliest_timestamp_ms)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
@ -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<GetError> 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<Option<User>, Error> {
|
||||
let app = handle.state::<Controller>();
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
|
||||
match app.get_user()? {
|
||||
Some(user) => Ok(Some(proxy.proxy_user(user).await)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<controller::SetError> 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<User, Error> {
|
||||
let app = handle.state::<Controller>();
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
|
||||
app.set_user(&user)?;
|
||||
|
||||
sentry::configure_scope(Some(&user));
|
||||
|
||||
Ok(proxy.proxy_user(user).await)
|
||||
}
|
||||
|
||||
impl From<controller::DeleteError> 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::<Controller>();
|
||||
|
||||
app.delete_user()?;
|
||||
|
||||
sentry::configure_scope(None);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -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<GetError> 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<Option<User>, Error> {
|
||||
let app = handle.state::<Controller>();
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
|
||||
match app.get_user()? {
|
||||
Some(user) => Ok(Some(proxy.proxy_user(user).await)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<controller::SetError> 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<User, Error> {
|
||||
let app = handle.state::<Controller>();
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
|
||||
app.set_user(&user)?;
|
||||
|
||||
sentry::configure_scope(Some(&user));
|
||||
|
||||
Ok(proxy.proxy_user(user).await)
|
||||
}
|
||||
|
||||
impl From<controller::DeleteError> 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::<Controller>();
|
||||
|
||||
app.delete_user()?;
|
||||
|
||||
sentry::configure_scope(None);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -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<E: Into<Error>>(value: ControllerError<E>) -> 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<BranchOwnershipClaims>,
|
||||
run_hooks: bool,
|
||||
) -> Result<git::Oid, Error> {
|
||||
let oid = handle
|
||||
.state::<Controller>()
|
||||
.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<BranchId>,
|
||||
) -> Result<(), Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<VirtualBranches, Error> {
|
||||
let (branches, uses_diff_context, skipped_files) = handle
|
||||
.state::<Controller>()
|
||||
.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::<projects::Controller>()
|
||||
.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::<assets::Proxy>();
|
||||
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<BranchId, Error> {
|
||||
let branch_id = handle
|
||||
.state::<Controller>()
|
||||
.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<BranchId, Error> {
|
||||
let branch_id = handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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<Option<BaseBranch>, Error> {
|
||||
if let Some(base_branch) = handle
|
||||
.state::<Controller>()
|
||||
.get_base_branch_data(&project_id)
|
||||
.await
|
||||
.map_err(into_error)?
|
||||
{
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
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<BaseBranch, Error> {
|
||||
let branch_name = format!("refs/remotes/{}", branch)
|
||||
.parse()
|
||||
.context("Invalid branch name")?;
|
||||
let base_branch = handle
|
||||
.state::<Controller>()
|
||||
.set_base_branch(&project_id, &branch_name)
|
||||
.await
|
||||
.map_err(into_error)?;
|
||||
let base_branch = handle
|
||||
.state::<assets::Proxy>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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<String>
|
||||
let files = files
|
||||
.split('\n')
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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::<AskpassBroker>();
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<bool, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<bool, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<Vec<RemoteBranchFile>, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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<Option<git::Oid>, Error> {
|
||||
let oid = handle
|
||||
.state::<Controller>()
|
||||
.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<git::Oid, Error> {
|
||||
let oid = handle
|
||||
.state::<Controller>()
|
||||
.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<Vec<RemoteBranch>, Error> {
|
||||
let branches = handle
|
||||
.state::<Controller>()
|
||||
.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<RemoteBranchData, Error> {
|
||||
let branch_data = handle
|
||||
.state::<Controller>()
|
||||
.get_remote_branch_data(&project_id, &refname)
|
||||
.await
|
||||
.map_err(into_error)?;
|
||||
let branch_data = handle
|
||||
.state::<assets::Proxy>()
|
||||
.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::<Controller>()
|
||||
.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<String>,
|
||||
) -> Result<BaseBranch, Error> {
|
||||
let askpass_broker = handle.state::<AskpassBroker>().inner().clone();
|
||||
let base_branch = handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<watcher::Watchers>()
|
||||
.post(watcher::Event::CalculateVirtualBranches(*project_id))
|
||||
.await
|
||||
{
|
||||
tracing::error!(?error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<E: Into<Error>> From<ControllerError<E>> for Error {
|
||||
fn from(value: ControllerError<E>) -> 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<BranchOwnershipClaims>,
|
||||
run_hooks: bool,
|
||||
) -> Result<git::Oid, Error> {
|
||||
let oid = handle
|
||||
.state::<Controller>()
|
||||
.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<BranchId>,
|
||||
) -> Result<(), Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<super::VirtualBranches, Error> {
|
||||
let (branches, uses_diff_context, skipped_files) = handle
|
||||
.state::<Controller>()
|
||||
.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::<projects::Controller>()
|
||||
.update(&projects::UpdateRequest {
|
||||
id: project_id,
|
||||
use_diff_context: Some(true),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
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<BranchId, Error> {
|
||||
let branch_id = handle
|
||||
.state::<Controller>()
|
||||
.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<BranchId, Error> {
|
||||
let branch_id = handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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<Option<super::BaseBranch>, Error> {
|
||||
if let Some(base_branch) = handle
|
||||
.state::<Controller>()
|
||||
.get_base_branch_data(&project_id)
|
||||
.await?
|
||||
{
|
||||
let proxy = handle.state::<assets::Proxy>();
|
||||
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<super::BaseBranch, Error> {
|
||||
let branch_name = format!("refs/remotes/{}", branch)
|
||||
.parse()
|
||||
.context("Invalid branch name")?;
|
||||
let base_branch = handle
|
||||
.state::<Controller>()
|
||||
.set_base_branch(&project_id, &branch_name)
|
||||
.await?;
|
||||
let base_branch = handle
|
||||
.state::<assets::Proxy>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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<String>
|
||||
let files = files
|
||||
.split('\n')
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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::<crate::askpass::AskpassBroker>();
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<bool, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<bool, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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<Vec<RemoteBranchFile>, Error> {
|
||||
handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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<Option<git::Oid>, Error> {
|
||||
let oid = handle
|
||||
.state::<Controller>()
|
||||
.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<git::Oid, Error> {
|
||||
let oid = handle
|
||||
.state::<Controller>()
|
||||
.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<Vec<super::RemoteBranch>, Error> {
|
||||
let branches = handle
|
||||
.state::<Controller>()
|
||||
.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<super::RemoteBranchData, Error> {
|
||||
let branch_data = handle
|
||||
.state::<Controller>()
|
||||
.get_remote_branch_data(&project_id, &refname)
|
||||
.await?;
|
||||
let branch_data = handle
|
||||
.state::<assets::Proxy>()
|
||||
.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::<Controller>()
|
||||
.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<String>,
|
||||
) -> Result<BaseBranch, Error> {
|
||||
let askpass_broker = handle
|
||||
.state::<crate::askpass::AskpassBroker>()
|
||||
.inner()
|
||||
.clone();
|
||||
let base_branch = handle
|
||||
.state::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<Controller>()
|
||||
.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::<watcher::Watchers>()
|
||||
.post(watcher::Event::CalculateVirtualBranches(*project_id))
|
||||
.await
|
||||
{
|
||||
tracing::error!(?error);
|
||||
}
|
||||
}
|
@ -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<WatcherInner>,
|
||||
|
@ -10,7 +10,7 @@ use tokio::{
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::projects::ProjectId;
|
||||
use gitbutler::projects::ProjectId;
|
||||
|
||||
use super::events;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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},
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -10,7 +10,7 @@ use governor::{
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
use gitbutler::{
|
||||
project_repository,
|
||||
projects::{self, ProjectId},
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<Oid>,
|
||||
project_id: &crate::id::Id<projects::Project>,
|
||||
project_id: &gitbutler::id::Id<projects::Project>,
|
||||
user: &Option<users::User>,
|
||||
) -> 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<projects::Project>,
|
||||
project_id: &gitbutler::id::Id<projects::Project>,
|
||||
id: &Oid,
|
||||
) -> Result<(), project_repository::RemoteError> {
|
||||
state
|
||||
@ -211,7 +211,7 @@ struct State {
|
||||
fn push_all_refs(
|
||||
project_repository: &project_repository::Repository,
|
||||
user: &Option<users::User>,
|
||||
project_id: &crate::id::Id<projects::Project>,
|
||||
project_id: &gitbutler::id::Id<projects::Project>,
|
||||
) -> Result<(), project_repository::RemoteError> {
|
||||
let gb_references = collect_refs(project_repository)?;
|
||||
|
||||
|
@ -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<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(&self, path: P) -> Result<path::PathBuf> {
|
||||
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<P: AsRef<path::Path>>(
|
||||
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<T>(
|
||||
it: &mut dyn Iterator<Item = DirEntry>,
|
||||
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<controller::ArchiveError> 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<P: AsRef<path::Path>>(path: P) -> Result<String> {
|
||||
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<path::PathBuf, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.archive(&project_id)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
Ok(format!("{:X}", hasher.finalize()))
|
||||
}
|
||||
impl From<controller::DataArchiveError> 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<P: AsRef<path::Path>>(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<path::PathBuf, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.data_archive(&project_id)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::LogsArchiveError> 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<path::PathBuf, Error> {
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.logs_archive()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -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<controller::ArchiveError> 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<path::PathBuf, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.archive(&project_id)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::DataArchiveError> 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<path::PathBuf, Error> {
|
||||
let project_id = project_id.parse().map_err(|_| Error::UserError {
|
||||
code: Code::Validation,
|
||||
message: "Malformed project id".into(),
|
||||
})?;
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.data_archive(&project_id)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
impl From<controller::LogsArchiveError> 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<path::PathBuf, Error> {
|
||||
handle
|
||||
.state::<controller::Controller>()
|
||||
.logs_archive()
|
||||
.map_err(Into::into)
|
||||
}
|
@ -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<TempDir>,
|
||||
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<PathBuf, &str>) -> (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<PathBuf, &str>) -> 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<TempDir>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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::<Result<Vec<virtual_branches::Branch>, gitbutler_app::reader::Error>>()
|
||||
.collect::<Result<Vec<virtual_branches::Branch>, gitbutler::reader::Error>>()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect::<Vec<virtual_branches::Branch>>();
|
||||
@ -719,7 +719,7 @@ fn should_restore_branches_targets_state_from_head_session() -> Result<()> {
|
||||
|
||||
let branches = virtual_branches::Iterator::new(&session_reader)
|
||||
.unwrap()
|
||||
.collect::<Result<Vec<virtual_branches::Branch>, gitbutler_app::reader::Error>>()
|
||||
.collect::<Result<Vec<virtual_branches::Branch>, gitbutler::reader::Error>>()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect::<Vec<virtual_branches::Branch>>();
|
||||
|
@ -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]
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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)> {
|
||||
|
@ -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<git::Oid> {
|
||||
|
63
src/askpass.rs
Normal file
63
src/askpass.rs
Normal file
@ -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<Option<String>>,
|
||||
}
|
||||
|
||||
#[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<BranchId> },
|
||||
Fetch { action: String },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AskpassBroker {
|
||||
pending_requests: Arc<Mutex<HashMap<Id<AskpassRequest>, AskpassRequest>>>,
|
||||
submit_prompt_event: Arc<dyn Fn(PromptEvent<Context>) + Send + Sync>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct PromptEvent<C: Serialize + Clone> {
|
||||
id: Id<AskpassRequest>,
|
||||
prompt: String,
|
||||
context: C,
|
||||
}
|
||||
|
||||
impl AskpassBroker {
|
||||
pub fn init(submit_prompt: impl Fn(PromptEvent<Context>) + 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<String> {
|
||||
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<AskpassRequest>, response: Option<String>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
15
src/deltas.rs
Normal file
15
src/deltas.rs
Normal file
@ -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;
|
414
src/error.rs
Normal file
414
src/error.rs
Normal file
@ -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<Box<ErrorContext>>,
|
||||
pub vars: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
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::<Backtrace>() {
|
||||
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<E: Into<ErrorContext>> WithContext<ErrorContext> for E {
|
||||
fn add_err_context<K: Into<String>, V: Into<String>>(
|
||||
self,
|
||||
name: K,
|
||||
value: V,
|
||||
) -> ErrorContext {
|
||||
let mut e = self.into();
|
||||
e.context.vars.insert(name.into(), value.into());
|
||||
e
|
||||
}
|
||||
|
||||
fn wrap_err<K: Into<ErrorKind>>(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<T, E> WithContext<Result<T>> for std::result::Result<T, E>
|
||||
where
|
||||
E: Into<ErrorKind>,
|
||||
{
|
||||
#[inline]
|
||||
fn add_err_context<K: Into<String>, V: Into<String>>(
|
||||
self,
|
||||
name: K,
|
||||
value: V,
|
||||
) -> Result<T> {
|
||||
self.map_err(|e| {
|
||||
ErrorContext {
|
||||
error: e.into(),
|
||||
context: Context::default(),
|
||||
}
|
||||
.add_err_context(name, value)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wrap_err<K: Into<ErrorKind>>(self, error: K) -> Result<T> {
|
||||
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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
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<ErrorKind> 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<R> {
|
||||
fn add_err_context<K: Into<String>, V: Into<String>>(self, name: K, value: V) -> R;
|
||||
fn wrap_err<E: Into<ErrorKind>>(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.{}", <std::io::Error as ErrorCode>::code(e)),
|
||||
ErrorKind::Git(e) => format!("git.{}", <git2::Error as ErrorCode>::code(e)),
|
||||
ErrorKind::CommonDirNotAvailable(_) => "no-common-dir".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
match self {
|
||||
ErrorKind::Io(e) => <std::io::Error as ErrorCode>::message(e),
|
||||
ErrorKind::Git(e) => <git2::Error as ErrorCode>::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!(
|
||||
"{}: {}",
|
||||
<Self as ErrorCode>::code(self),
|
||||
<Self as ErrorCode>::message(self)
|
||||
)
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "error-context"))]
|
||||
pub type Error = ErrorKind;
|
||||
#[cfg(feature = "error-context")]
|
||||
pub type Error = ErrorContext;
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
#[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<ErrorKind> for ErrorKind {
|
||||
#[inline]
|
||||
fn add_err_context<K: Into<String>, V: Into<String>>(self, _name: K, _value: V) -> Error {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wrap_err(self, _error: Error) -> Error {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "error-context"))]
|
||||
impl<T, E> WithContext<std::result::Result<T, E>> for std::result::Result<T, E> {
|
||||
#[inline]
|
||||
fn add_err_context<K: Into<String>, V: Into<String>>(
|
||||
self,
|
||||
_name: K,
|
||||
_value: V,
|
||||
) -> std::result::Result<T, E> {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wrap_err(self, _error: Error) -> std::result::Result<T, E> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "error-context")]
|
||||
impl serde::Serialize for ErrorKind {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<anyhow::Error> for Error {
|
||||
fn from(error: anyhow::Error) -> Self {
|
||||
tracing::error!(?error);
|
||||
Error::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<keys::GetOrCreateError> for Error {
|
||||
fn from(error: keys::GetOrCreateError) -> Self {
|
||||
tracing::error!(?error);
|
||||
Error::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<users::GetError> for Error {
|
||||
fn from(error: users::GetError) -> Self {
|
||||
tracing::error!(?error);
|
||||
Error::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl From<projects::controller::GetError> for Error {
|
||||
fn from(error: projects::controller::GetError) -> Self {
|
||||
tracing::error!(?error);
|
||||
Error::Unknown
|
||||
}
|
||||
}
|
||||
}
|
89
src/error/sentry.rs
Normal file
89
src/error/sentry.rs
Normal file
@ -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<E: Into<ErrorContext>> 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<Exception>,
|
||||
vars: &mut BTreeMap<String, Value>,
|
||||
);
|
||||
}
|
||||
|
||||
impl PopulateException for ErrorContext {
|
||||
fn populate_exception(
|
||||
self,
|
||||
exceptions: &mut Vec<Exception>,
|
||||
vars: &mut BTreeMap<String, Value>,
|
||||
) {
|
||||
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<ErrorContext> 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
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user