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}