1use git::repository::DiffType;
2use gpui::{App, Entity, Task};
3use serde::{Deserialize, Serialize};
4use worktree::Worktree;
5
6use crate::{
7 Project,
8 git_store::{GitStore, RepositoryState},
9};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct TelemetrySnapshot {
13 pub worktree_snapshots: Vec<TelemetryWorktreeSnapshot>,
14}
15
16impl TelemetrySnapshot {
17 pub fn new(project: &Entity<Project>, cx: &mut App) -> Task<TelemetrySnapshot> {
18 let git_store = project.read(cx).git_store().clone();
19 let worktree_snapshots: Vec<_> = project
20 .read(cx)
21 .visible_worktrees(cx)
22 .map(|worktree| TelemetryWorktreeSnapshot::new(worktree, git_store.clone(), cx))
23 .collect();
24
25 cx.spawn(async move |_| {
26 let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
27
28 Self { worktree_snapshots }
29 })
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34pub struct TelemetryWorktreeSnapshot {
35 pub worktree_path: String,
36 pub git_state: Option<GitState>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
40pub struct GitState {
41 pub remote_url: Option<String>,
42 pub head_sha: Option<String>,
43 pub current_branch: Option<String>,
44 pub diff: Option<String>,
45}
46
47impl TelemetryWorktreeSnapshot {
48 fn new(
49 worktree: Entity<Worktree>,
50 git_store: Entity<GitStore>,
51 cx: &App,
52 ) -> Task<TelemetryWorktreeSnapshot> {
53 cx.spawn(async move |cx| {
54 // Get worktree path and snapshot
55 let worktree_info = cx.update(|app_cx| {
56 let worktree = worktree.read(app_cx);
57 let path = worktree.abs_path().to_string_lossy().into_owned();
58 let snapshot = worktree.snapshot();
59 (path, snapshot)
60 });
61
62 let Ok((worktree_path, _snapshot)) = worktree_info else {
63 return TelemetryWorktreeSnapshot {
64 worktree_path: String::new(),
65 git_state: None,
66 };
67 };
68
69 let git_state = git_store
70 .update(cx, |git_store, cx| {
71 git_store
72 .repositories()
73 .values()
74 .find(|repo| {
75 repo.read(cx)
76 .abs_path_to_repo_path(&worktree.read(cx).abs_path())
77 .is_some()
78 })
79 .cloned()
80 })
81 .ok()
82 .flatten()
83 .map(|repo| {
84 repo.update(cx, |repo, _| {
85 let current_branch =
86 repo.branch.as_ref().map(|branch| branch.name().to_owned());
87 repo.send_job(None, |state, _| async move {
88 let RepositoryState::Local { backend, .. } = state else {
89 return GitState {
90 remote_url: None,
91 head_sha: None,
92 current_branch,
93 diff: None,
94 };
95 };
96
97 let remote_url = backend.remote_url("origin");
98 let head_sha = backend.head_sha().await;
99 let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
100
101 GitState {
102 remote_url,
103 head_sha,
104 current_branch,
105 diff,
106 }
107 })
108 })
109 });
110
111 let git_state = match git_state {
112 Some(git_state) => match git_state.ok() {
113 Some(git_state) => git_state.await.ok(),
114 None => None,
115 },
116 None => None,
117 };
118
119 TelemetryWorktreeSnapshot {
120 worktree_path,
121 git_state,
122 }
123 })
124 }
125}