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, LocalRepositoryState, 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(LocalRepositoryState { backend, .. }) =
89 state
90 else {
91 return GitState {
92 remote_url: None,
93 head_sha: None,
94 current_branch,
95 diff: None,
96 };
97 };
98
99 let remote_url = backend.remote_url("origin");
100 let head_sha = backend.head_sha().await;
101 let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
102
103 GitState {
104 remote_url,
105 head_sha,
106 current_branch,
107 diff,
108 }
109 })
110 })
111 });
112
113 let git_state = match git_state {
114 Some(git_state) => match git_state.ok() {
115 Some(git_state) => git_state.await.ok(),
116 None => None,
117 },
118 None => None,
119 };
120
121 TelemetryWorktreeSnapshot {
122 worktree_path,
123 git_state,
124 }
125 })
126 }
127}