1mod ids;
2mod queries;
3mod tables;
4#[cfg(test)]
5pub mod tests;
6
7use crate::{executor::Executor, Error, Result};
8use anyhow::anyhow;
9use collections::{BTreeMap, HashMap, HashSet};
10use dashmap::DashMap;
11use futures::StreamExt;
12use rand::{prelude::StdRng, Rng, SeedableRng};
13use rpc::{
14 proto::{self},
15 ConnectionId, ExtensionMetadata,
16};
17use sea_orm::{
18 entity::prelude::*,
19 sea_query::{Alias, Expr, OnConflict},
20 ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr,
21 FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
22 TransactionTrait,
23};
24use semantic_version::SemanticVersion;
25use serde::{Deserialize, Serialize};
26use std::ops::RangeInclusive;
27use std::{
28 fmt::Write as _,
29 future::Future,
30 marker::PhantomData,
31 ops::{Deref, DerefMut},
32 rc::Rc,
33 sync::Arc,
34 time::Duration,
35};
36use time::PrimitiveDateTime;
37use tokio::sync::{Mutex, OwnedMutexGuard};
38use worktree_repository_statuses::StatusKind;
39use worktree_settings_file::LocalSettingsKind;
40
41#[cfg(test)]
42pub use tests::TestDb;
43
44pub use ids::*;
45pub use queries::billing_customers::{CreateBillingCustomerParams, UpdateBillingCustomerParams};
46pub use queries::billing_preferences::{
47 CreateBillingPreferencesParams, UpdateBillingPreferencesParams,
48};
49pub use queries::billing_subscriptions::{
50 CreateBillingSubscriptionParams, UpdateBillingSubscriptionParams,
51};
52pub use queries::contributors::ContributorSelector;
53pub use queries::processed_stripe_events::CreateProcessedStripeEventParams;
54pub use sea_orm::ConnectOptions;
55pub use tables::user::Model as User;
56pub use tables::*;
57
58/// Database gives you a handle that lets you access the database.
59/// It handles pooling internally.
60pub struct Database {
61 options: ConnectOptions,
62 pool: DatabaseConnection,
63 rooms: DashMap<RoomId, Arc<Mutex<()>>>,
64 projects: DashMap<ProjectId, Arc<Mutex<()>>>,
65 rng: Mutex<StdRng>,
66 executor: Executor,
67 notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
68 notification_kinds_by_name: HashMap<String, NotificationKindId>,
69 #[cfg(test)]
70 runtime: Option<tokio::runtime::Runtime>,
71}
72
73// The `Database` type has so many methods that its impl blocks are split into
74// separate files in the `queries` folder.
75impl Database {
76 /// Connects to the database with the given options
77 pub async fn new(options: ConnectOptions, executor: Executor) -> Result<Self> {
78 sqlx::any::install_default_drivers();
79 Ok(Self {
80 options: options.clone(),
81 pool: sea_orm::Database::connect(options).await?,
82 rooms: DashMap::with_capacity(16384),
83 projects: DashMap::with_capacity(16384),
84 rng: Mutex::new(StdRng::seed_from_u64(0)),
85 notification_kinds_by_id: HashMap::default(),
86 notification_kinds_by_name: HashMap::default(),
87 executor,
88 #[cfg(test)]
89 runtime: None,
90 })
91 }
92
93 pub fn options(&self) -> &ConnectOptions {
94 &self.options
95 }
96
97 #[cfg(test)]
98 pub fn reset(&self) {
99 self.rooms.clear();
100 self.projects.clear();
101 }
102
103 /// Transaction runs things in a transaction. If you want to call other methods
104 /// and pass the transaction around you need to reborrow the transaction at each
105 /// call site with: `&*tx`.
106 pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
107 where
108 F: Send + Fn(TransactionHandle) -> Fut,
109 Fut: Send + Future<Output = Result<T>>,
110 {
111 let body = async {
112 let mut i = 0;
113 loop {
114 let (tx, result) = self.with_transaction(&f).await?;
115 match result {
116 Ok(result) => match tx.commit().await.map_err(Into::into) {
117 Ok(()) => return Ok(result),
118 Err(error) => {
119 if !self.retry_on_serialization_error(&error, i).await {
120 return Err(error);
121 }
122 }
123 },
124 Err(error) => {
125 tx.rollback().await?;
126 if !self.retry_on_serialization_error(&error, i).await {
127 return Err(error);
128 }
129 }
130 }
131 i += 1;
132 }
133 };
134
135 self.run(body).await
136 }
137
138 pub async fn weak_transaction<F, Fut, T>(&self, f: F) -> Result<T>
139 where
140 F: Send + Fn(TransactionHandle) -> Fut,
141 Fut: Send + Future<Output = Result<T>>,
142 {
143 let body = async {
144 let (tx, result) = self.with_weak_transaction(&f).await?;
145 match result {
146 Ok(result) => match tx.commit().await.map_err(Into::into) {
147 Ok(()) => Ok(result),
148 Err(error) => Err(error),
149 },
150 Err(error) => {
151 tx.rollback().await?;
152 Err(error)
153 }
154 }
155 };
156
157 self.run(body).await
158 }
159
160 /// The same as room_transaction, but if you need to only optionally return a Room.
161 async fn optional_room_transaction<F, Fut, T>(
162 &self,
163 f: F,
164 ) -> Result<Option<TransactionGuard<T>>>
165 where
166 F: Send + Fn(TransactionHandle) -> Fut,
167 Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
168 {
169 let body = async {
170 let mut i = 0;
171 loop {
172 let (tx, result) = self.with_transaction(&f).await?;
173 match result {
174 Ok(Some((room_id, data))) => {
175 let lock = self.rooms.entry(room_id).or_default().clone();
176 let _guard = lock.lock_owned().await;
177 match tx.commit().await.map_err(Into::into) {
178 Ok(()) => {
179 return Ok(Some(TransactionGuard {
180 data,
181 _guard,
182 _not_send: PhantomData,
183 }));
184 }
185 Err(error) => {
186 if !self.retry_on_serialization_error(&error, i).await {
187 return Err(error);
188 }
189 }
190 }
191 }
192 Ok(None) => match tx.commit().await.map_err(Into::into) {
193 Ok(()) => return Ok(None),
194 Err(error) => {
195 if !self.retry_on_serialization_error(&error, i).await {
196 return Err(error);
197 }
198 }
199 },
200 Err(error) => {
201 tx.rollback().await?;
202 if !self.retry_on_serialization_error(&error, i).await {
203 return Err(error);
204 }
205 }
206 }
207 i += 1;
208 }
209 };
210
211 self.run(body).await
212 }
213
214 async fn project_transaction<F, Fut, T>(
215 &self,
216 project_id: ProjectId,
217 f: F,
218 ) -> Result<TransactionGuard<T>>
219 where
220 F: Send + Fn(TransactionHandle) -> Fut,
221 Fut: Send + Future<Output = Result<T>>,
222 {
223 let room_id = Database::room_id_for_project(self, project_id).await?;
224 let body = async {
225 let mut i = 0;
226 loop {
227 let lock = if let Some(room_id) = room_id {
228 self.rooms.entry(room_id).or_default().clone()
229 } else {
230 self.projects.entry(project_id).or_default().clone()
231 };
232 let _guard = lock.lock_owned().await;
233 let (tx, result) = self.with_transaction(&f).await?;
234 match result {
235 Ok(data) => match tx.commit().await.map_err(Into::into) {
236 Ok(()) => {
237 return Ok(TransactionGuard {
238 data,
239 _guard,
240 _not_send: PhantomData,
241 });
242 }
243 Err(error) => {
244 if !self.retry_on_serialization_error(&error, i).await {
245 return Err(error);
246 }
247 }
248 },
249 Err(error) => {
250 tx.rollback().await?;
251 if !self.retry_on_serialization_error(&error, i).await {
252 return Err(error);
253 }
254 }
255 }
256 i += 1;
257 }
258 };
259
260 self.run(body).await
261 }
262
263 /// room_transaction runs the block in a transaction. It returns a RoomGuard, that keeps
264 /// the database locked until it is dropped. This ensures that updates sent to clients are
265 /// properly serialized with respect to database changes.
266 async fn room_transaction<F, Fut, T>(
267 &self,
268 room_id: RoomId,
269 f: F,
270 ) -> Result<TransactionGuard<T>>
271 where
272 F: Send + Fn(TransactionHandle) -> Fut,
273 Fut: Send + Future<Output = Result<T>>,
274 {
275 let body = async {
276 let mut i = 0;
277 loop {
278 let lock = self.rooms.entry(room_id).or_default().clone();
279 let _guard = lock.lock_owned().await;
280 let (tx, result) = self.with_transaction(&f).await?;
281 match result {
282 Ok(data) => match tx.commit().await.map_err(Into::into) {
283 Ok(()) => {
284 return Ok(TransactionGuard {
285 data,
286 _guard,
287 _not_send: PhantomData,
288 });
289 }
290 Err(error) => {
291 if !self.retry_on_serialization_error(&error, i).await {
292 return Err(error);
293 }
294 }
295 },
296 Err(error) => {
297 tx.rollback().await?;
298 if !self.retry_on_serialization_error(&error, i).await {
299 return Err(error);
300 }
301 }
302 }
303 i += 1;
304 }
305 };
306
307 self.run(body).await
308 }
309
310 async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
311 where
312 F: Send + Fn(TransactionHandle) -> Fut,
313 Fut: Send + Future<Output = Result<T>>,
314 {
315 let tx = self
316 .pool
317 .begin_with_config(Some(IsolationLevel::Serializable), None)
318 .await?;
319
320 let mut tx = Arc::new(Some(tx));
321 let result = f(TransactionHandle(tx.clone())).await;
322 let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
323 return Err(anyhow!(
324 "couldn't complete transaction because it's still in use"
325 ))?;
326 };
327
328 Ok((tx, result))
329 }
330
331 async fn with_weak_transaction<F, Fut, T>(
332 &self,
333 f: &F,
334 ) -> Result<(DatabaseTransaction, Result<T>)>
335 where
336 F: Send + Fn(TransactionHandle) -> Fut,
337 Fut: Send + Future<Output = Result<T>>,
338 {
339 let tx = self
340 .pool
341 .begin_with_config(Some(IsolationLevel::ReadCommitted), None)
342 .await?;
343
344 let mut tx = Arc::new(Some(tx));
345 let result = f(TransactionHandle(tx.clone())).await;
346 let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
347 return Err(anyhow!(
348 "couldn't complete transaction because it's still in use"
349 ))?;
350 };
351
352 Ok((tx, result))
353 }
354
355 async fn run<F, T>(&self, future: F) -> Result<T>
356 where
357 F: Future<Output = Result<T>>,
358 {
359 #[cfg(test)]
360 {
361 if let Executor::Deterministic(executor) = &self.executor {
362 executor.simulate_random_delay().await;
363 }
364
365 self.runtime.as_ref().unwrap().block_on(future)
366 }
367
368 #[cfg(not(test))]
369 {
370 future.await
371 }
372 }
373
374 async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: usize) -> bool {
375 // If the error is due to a failure to serialize concurrent transactions, then retry
376 // this transaction after a delay. With each subsequent retry, double the delay duration.
377 // Also vary the delay randomly in order to ensure different database connections retry
378 // at different times.
379 const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.];
380 if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() {
381 let base_delay = SLEEPS[prev_attempt_count];
382 let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0);
383 log::warn!(
384 "retrying transaction after serialization error. delay: {} ms.",
385 randomized_delay
386 );
387 self.executor
388 .sleep(Duration::from_millis(randomized_delay as u64))
389 .await;
390 true
391 } else {
392 false
393 }
394 }
395}
396
397fn is_serialization_error(error: &Error) -> bool {
398 const SERIALIZATION_FAILURE_CODE: &str = "40001";
399 match error {
400 Error::Database(
401 DbErr::Exec(sea_orm::RuntimeErr::SqlxError(error))
402 | DbErr::Query(sea_orm::RuntimeErr::SqlxError(error)),
403 ) if error
404 .as_database_error()
405 .and_then(|error| error.code())
406 .as_deref()
407 == Some(SERIALIZATION_FAILURE_CODE) =>
408 {
409 true
410 }
411 _ => false,
412 }
413}
414
415/// A handle to a [`DatabaseTransaction`].
416pub struct TransactionHandle(pub(crate) Arc<Option<DatabaseTransaction>>);
417
418impl Deref for TransactionHandle {
419 type Target = DatabaseTransaction;
420
421 fn deref(&self) -> &Self::Target {
422 self.0.as_ref().as_ref().unwrap()
423 }
424}
425
426/// [`TransactionGuard`] keeps a database transaction alive until it is dropped.
427/// It wraps data that depends on the state of the database and prevents an additional
428/// transaction from starting that would invalidate that data.
429pub struct TransactionGuard<T> {
430 data: T,
431 _guard: OwnedMutexGuard<()>,
432 _not_send: PhantomData<Rc<()>>,
433}
434
435impl<T> Deref for TransactionGuard<T> {
436 type Target = T;
437
438 fn deref(&self) -> &T {
439 &self.data
440 }
441}
442
443impl<T> DerefMut for TransactionGuard<T> {
444 fn deref_mut(&mut self) -> &mut T {
445 &mut self.data
446 }
447}
448
449impl<T> TransactionGuard<T> {
450 /// Returns the inner value of the guard.
451 pub fn into_inner(self) -> T {
452 self.data
453 }
454}
455
456#[derive(Clone, Debug, PartialEq, Eq)]
457pub enum Contact {
458 Accepted { user_id: UserId, busy: bool },
459 Outgoing { user_id: UserId },
460 Incoming { user_id: UserId },
461}
462
463impl Contact {
464 pub fn user_id(&self) -> UserId {
465 match self {
466 Contact::Accepted { user_id, .. } => *user_id,
467 Contact::Outgoing { user_id } => *user_id,
468 Contact::Incoming { user_id, .. } => *user_id,
469 }
470 }
471}
472
473pub type NotificationBatch = Vec<(UserId, proto::Notification)>;
474
475pub struct CreatedChannelMessage {
476 pub message_id: MessageId,
477 pub participant_connection_ids: HashSet<ConnectionId>,
478 pub notifications: NotificationBatch,
479}
480
481pub struct UpdatedChannelMessage {
482 pub message_id: MessageId,
483 pub participant_connection_ids: Vec<ConnectionId>,
484 pub notifications: NotificationBatch,
485 pub reply_to_message_id: Option<MessageId>,
486 pub timestamp: PrimitiveDateTime,
487 pub deleted_mention_notification_ids: Vec<NotificationId>,
488 pub updated_mention_notifications: Vec<rpc::proto::Notification>,
489}
490
491#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
492pub struct Invite {
493 pub email_address: String,
494 pub email_confirmation_code: String,
495}
496
497#[derive(Clone, Debug, Deserialize)]
498pub struct NewSignup {
499 pub email_address: String,
500 pub platform_mac: bool,
501 pub platform_windows: bool,
502 pub platform_linux: bool,
503 pub editor_features: Vec<String>,
504 pub programming_languages: Vec<String>,
505 pub device_id: Option<String>,
506 pub added_to_mailing_list: bool,
507 pub created_at: Option<DateTime>,
508}
509
510#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromQueryResult)]
511pub struct WaitlistSummary {
512 pub count: i64,
513 pub linux_count: i64,
514 pub mac_count: i64,
515 pub windows_count: i64,
516 pub unknown_count: i64,
517}
518
519/// The parameters to create a new user.
520#[derive(Debug, Serialize, Deserialize)]
521pub struct NewUserParams {
522 pub github_login: String,
523 pub github_user_id: i32,
524}
525
526/// The result of creating a new user.
527#[derive(Debug)]
528pub struct NewUserResult {
529 pub user_id: UserId,
530 pub metrics_id: String,
531 pub inviting_user_id: Option<UserId>,
532 pub signup_device_id: Option<String>,
533}
534
535/// The result of updating a channel membership.
536#[derive(Debug)]
537pub struct MembershipUpdated {
538 pub channel_id: ChannelId,
539 pub new_channels: ChannelsForUser,
540 pub removed_channels: Vec<ChannelId>,
541}
542
543/// The result of setting a member's role.
544#[derive(Debug)]
545#[allow(clippy::large_enum_variant)]
546pub enum SetMemberRoleResult {
547 InviteUpdated(Channel),
548 MembershipUpdated(MembershipUpdated),
549}
550
551/// The result of inviting a member to a channel.
552#[derive(Debug)]
553pub struct InviteMemberResult {
554 pub channel: Channel,
555 pub notifications: NotificationBatch,
556}
557
558#[derive(Debug)]
559pub struct RespondToChannelInvite {
560 pub membership_update: Option<MembershipUpdated>,
561 pub notifications: NotificationBatch,
562}
563
564#[derive(Debug)]
565pub struct RemoveChannelMemberResult {
566 pub membership_update: MembershipUpdated,
567 pub notification_id: Option<NotificationId>,
568}
569
570#[derive(Debug, PartialEq, Eq, Hash)]
571pub struct Channel {
572 pub id: ChannelId,
573 pub name: String,
574 pub visibility: ChannelVisibility,
575 /// parent_path is the channel ids from the root to this one (not including this one)
576 pub parent_path: Vec<ChannelId>,
577}
578
579impl Channel {
580 pub fn from_model(value: channel::Model) -> Self {
581 Channel {
582 id: value.id,
583 visibility: value.visibility,
584 name: value.clone().name,
585 parent_path: value.ancestors().collect(),
586 }
587 }
588
589 pub fn to_proto(&self) -> proto::Channel {
590 proto::Channel {
591 id: self.id.to_proto(),
592 name: self.name.clone(),
593 visibility: self.visibility.into(),
594 parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
595 }
596 }
597}
598
599#[derive(Debug, PartialEq, Eq, Hash)]
600pub struct ChannelMember {
601 pub role: ChannelRole,
602 pub user_id: UserId,
603 pub kind: proto::channel_member::Kind,
604}
605
606impl ChannelMember {
607 pub fn to_proto(&self) -> proto::ChannelMember {
608 proto::ChannelMember {
609 role: self.role.into(),
610 user_id: self.user_id.to_proto(),
611 kind: self.kind.into(),
612 }
613 }
614}
615
616#[derive(Debug, PartialEq)]
617pub struct ChannelsForUser {
618 pub channels: Vec<Channel>,
619 pub channel_memberships: Vec<channel_member::Model>,
620 pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
621 pub invited_channels: Vec<Channel>,
622
623 pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
624 pub observed_channel_messages: Vec<proto::ChannelMessageId>,
625 pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
626 pub latest_channel_messages: Vec<proto::ChannelMessageId>,
627}
628
629#[derive(Debug)]
630pub struct RejoinedChannelBuffer {
631 pub buffer: proto::RejoinedChannelBuffer,
632 pub old_connection_id: ConnectionId,
633}
634
635#[derive(Clone)]
636pub struct JoinRoom {
637 pub room: proto::Room,
638 pub channel: Option<channel::Model>,
639}
640
641pub struct RejoinedRoom {
642 pub room: proto::Room,
643 pub rejoined_projects: Vec<RejoinedProject>,
644 pub reshared_projects: Vec<ResharedProject>,
645 pub channel: Option<channel::Model>,
646}
647
648pub struct ResharedProject {
649 pub id: ProjectId,
650 pub old_connection_id: ConnectionId,
651 pub collaborators: Vec<ProjectCollaborator>,
652 pub worktrees: Vec<proto::WorktreeMetadata>,
653}
654
655pub struct RejoinedProject {
656 pub id: ProjectId,
657 pub old_connection_id: ConnectionId,
658 pub collaborators: Vec<ProjectCollaborator>,
659 pub worktrees: Vec<RejoinedWorktree>,
660 pub language_servers: Vec<proto::LanguageServer>,
661}
662
663impl RejoinedProject {
664 pub fn to_proto(&self) -> proto::RejoinedProject {
665 proto::RejoinedProject {
666 id: self.id.to_proto(),
667 worktrees: self
668 .worktrees
669 .iter()
670 .map(|worktree| proto::WorktreeMetadata {
671 id: worktree.id,
672 root_name: worktree.root_name.clone(),
673 visible: worktree.visible,
674 abs_path: worktree.abs_path.clone(),
675 })
676 .collect(),
677 collaborators: self
678 .collaborators
679 .iter()
680 .map(|collaborator| collaborator.to_proto())
681 .collect(),
682 language_servers: self.language_servers.clone(),
683 }
684 }
685}
686
687#[derive(Debug)]
688pub struct RejoinedWorktree {
689 pub id: u64,
690 pub abs_path: String,
691 pub root_name: String,
692 pub visible: bool,
693 pub updated_entries: Vec<proto::Entry>,
694 pub removed_entries: Vec<u64>,
695 pub updated_repositories: Vec<proto::RepositoryEntry>,
696 pub removed_repositories: Vec<u64>,
697 pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
698 pub settings_files: Vec<WorktreeSettingsFile>,
699 pub scan_id: u64,
700 pub completed_scan_id: u64,
701}
702
703pub struct LeftRoom {
704 pub room: proto::Room,
705 pub channel: Option<channel::Model>,
706 pub left_projects: HashMap<ProjectId, LeftProject>,
707 pub canceled_calls_to_user_ids: Vec<UserId>,
708 pub deleted: bool,
709}
710
711pub struct RefreshedRoom {
712 pub room: proto::Room,
713 pub channel: Option<channel::Model>,
714 pub stale_participant_user_ids: Vec<UserId>,
715 pub canceled_calls_to_user_ids: Vec<UserId>,
716}
717
718pub struct RefreshedChannelBuffer {
719 pub connection_ids: Vec<ConnectionId>,
720 pub collaborators: Vec<proto::Collaborator>,
721}
722
723pub struct Project {
724 pub id: ProjectId,
725 pub role: ChannelRole,
726 pub collaborators: Vec<ProjectCollaborator>,
727 pub worktrees: BTreeMap<u64, Worktree>,
728 pub language_servers: Vec<proto::LanguageServer>,
729}
730
731pub struct ProjectCollaborator {
732 pub connection_id: ConnectionId,
733 pub user_id: UserId,
734 pub replica_id: ReplicaId,
735 pub is_host: bool,
736}
737
738impl ProjectCollaborator {
739 pub fn to_proto(&self) -> proto::Collaborator {
740 proto::Collaborator {
741 peer_id: Some(self.connection_id.into()),
742 replica_id: self.replica_id.0 as u32,
743 user_id: self.user_id.to_proto(),
744 is_host: self.is_host,
745 }
746 }
747}
748
749#[derive(Debug)]
750pub struct LeftProject {
751 pub id: ProjectId,
752 pub should_unshare: bool,
753 pub connection_ids: Vec<ConnectionId>,
754}
755
756pub struct Worktree {
757 pub id: u64,
758 pub abs_path: String,
759 pub root_name: String,
760 pub visible: bool,
761 pub entries: Vec<proto::Entry>,
762 pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
763 pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
764 pub settings_files: Vec<WorktreeSettingsFile>,
765 pub scan_id: u64,
766 pub completed_scan_id: u64,
767}
768
769#[derive(Debug)]
770pub struct WorktreeSettingsFile {
771 pub path: String,
772 pub content: String,
773 pub kind: LocalSettingsKind,
774}
775
776pub struct NewExtensionVersion {
777 pub name: String,
778 pub version: semver::Version,
779 pub description: String,
780 pub authors: Vec<String>,
781 pub repository: String,
782 pub schema_version: i32,
783 pub wasm_api_version: Option<String>,
784 pub published_at: PrimitiveDateTime,
785}
786
787pub struct ExtensionVersionConstraints {
788 pub schema_versions: RangeInclusive<i32>,
789 pub wasm_api_versions: RangeInclusive<SemanticVersion>,
790}
791
792impl LocalSettingsKind {
793 pub fn from_proto(proto_kind: proto::LocalSettingsKind) -> Self {
794 match proto_kind {
795 proto::LocalSettingsKind::Settings => Self::Settings,
796 proto::LocalSettingsKind::Tasks => Self::Tasks,
797 proto::LocalSettingsKind::Editorconfig => Self::Editorconfig,
798 }
799 }
800
801 pub fn to_proto(&self) -> proto::LocalSettingsKind {
802 match self {
803 Self::Settings => proto::LocalSettingsKind::Settings,
804 Self::Tasks => proto::LocalSettingsKind::Tasks,
805 Self::Editorconfig => proto::LocalSettingsKind::Editorconfig,
806 }
807 }
808}
809
810fn db_status_to_proto(
811 entry: worktree_repository_statuses::Model,
812) -> anyhow::Result<proto::StatusEntry> {
813 use proto::git_file_status::{Tracked, Unmerged, Variant};
814
815 let (simple_status, variant) =
816 match (entry.status_kind, entry.first_status, entry.second_status) {
817 (StatusKind::Untracked, None, None) => (
818 proto::GitStatus::Added as i32,
819 Variant::Untracked(Default::default()),
820 ),
821 (StatusKind::Ignored, None, None) => (
822 proto::GitStatus::Added as i32,
823 Variant::Ignored(Default::default()),
824 ),
825 (StatusKind::Unmerged, Some(first_head), Some(second_head)) => (
826 proto::GitStatus::Conflict as i32,
827 Variant::Unmerged(Unmerged {
828 first_head,
829 second_head,
830 }),
831 ),
832 (StatusKind::Tracked, Some(index_status), Some(worktree_status)) => {
833 let simple_status = if worktree_status != proto::GitStatus::Unmodified as i32 {
834 worktree_status
835 } else if index_status != proto::GitStatus::Unmodified as i32 {
836 index_status
837 } else {
838 proto::GitStatus::Unmodified as i32
839 };
840 (
841 simple_status,
842 Variant::Tracked(Tracked {
843 index_status,
844 worktree_status,
845 }),
846 )
847 }
848 _ => {
849 return Err(anyhow!(
850 "Unexpected combination of status fields: {entry:?}"
851 ))
852 }
853 };
854 Ok(proto::StatusEntry {
855 repo_path: entry.repo_path,
856 simple_status,
857 status: Some(proto::GitFileStatus {
858 variant: Some(variant),
859 }),
860 })
861}
862
863fn proto_status_to_db(
864 status_entry: proto::StatusEntry,
865) -> (String, StatusKind, Option<i32>, Option<i32>) {
866 use proto::git_file_status::{Tracked, Unmerged, Variant};
867
868 let (status_kind, first_status, second_status) = status_entry
869 .status
870 .clone()
871 .and_then(|status| status.variant)
872 .map_or(
873 (StatusKind::Untracked, None, None),
874 |variant| match variant {
875 Variant::Untracked(_) => (StatusKind::Untracked, None, None),
876 Variant::Ignored(_) => (StatusKind::Ignored, None, None),
877 Variant::Unmerged(Unmerged {
878 first_head,
879 second_head,
880 }) => (StatusKind::Unmerged, Some(first_head), Some(second_head)),
881 Variant::Tracked(Tracked {
882 index_status,
883 worktree_status,
884 }) => (
885 StatusKind::Tracked,
886 Some(index_status),
887 Some(worktree_status),
888 ),
889 },
890 );
891 (
892 status_entry.repo_path,
893 status_kind,
894 first_status,
895 second_status,
896 )
897}