diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 481587926a..391ea4e3b0 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -117,6 +117,7 @@ CREATE TABLE "project_repositories" ( "is_deleted" BOOL NOT NULL, "current_merge_conflicts" VARCHAR, "branch_summary" VARCHAR, + "head_commit_details" VARCHAR, PRIMARY KEY (project_id, id) ); diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 194bf7a10b..8d77025c31 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -348,9 +348,10 @@ impl Database { .unwrap(), )), - // Old clients do not use abs path or entry ids. + // Old clients do not use abs path, entry ids or head_commit_details. abs_path: ActiveValue::set(String::new()), entry_ids: ActiveValue::set("[]".into()), + head_commit_details: ActiveValue::set(None), } }), ) @@ -490,6 +491,12 @@ impl Database { .as_ref() .map(|summary| serde_json::to_string(summary).unwrap()), ), + head_commit_details: ActiveValue::Set( + update + .head_commit_details + .as_ref() + .map(|details| serde_json::to_string(details).unwrap()), + ), current_merge_conflicts: ActiveValue::Set(Some( serde_json::to_string(&update.current_merge_conflicts).unwrap(), )), @@ -505,6 +512,7 @@ impl Database { project_repository::Column::EntryIds, project_repository::Column::AbsPath, project_repository::Column::CurrentMergeConflicts, + project_repository::Column::HeadCommitDetails, ]) .to_owned(), ) @@ -928,6 +936,13 @@ impl Database { .transpose()? .unwrap_or_default(); + let head_commit_details = db_repository_entry + .head_commit_details + .as_ref() + .map(|head_commit_details| serde_json::from_str(&head_commit_details)) + .transpose()? + .unwrap_or_default(); + let entry_ids = serde_json::from_str(&db_repository_entry.entry_ids) .context("failed to deserialize repository's entry ids")?; @@ -954,6 +969,7 @@ impl Database { removed_statuses: Vec::new(), current_merge_conflicts, branch_summary, + head_commit_details, scan_id: db_repository_entry.scan_id as u64, is_last_update: true, }); diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 85852ccd36..71a0636a52 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -755,6 +755,13 @@ impl Database { .transpose()? .unwrap_or_default(); + let head_commit_details = db_repository + .head_commit_details + .as_ref() + .map(|head_commit_details| serde_json::from_str(&head_commit_details)) + .transpose()? + .unwrap_or_default(); + let entry_ids = serde_json::from_str(&db_repository.entry_ids) .context("failed to deserialize repository's entry ids")?; @@ -778,6 +785,7 @@ impl Database { removed_statuses, current_merge_conflicts, branch_summary, + head_commit_details, project_id: project_id.to_proto(), id: db_repository.id as u64, abs_path: db_repository.abs_path, diff --git a/crates/collab/src/db/tables/project_repository.rs b/crates/collab/src/db/tables/project_repository.rs index 36fb4a54c6..665e87cd1f 100644 --- a/crates/collab/src/db/tables/project_repository.rs +++ b/crates/collab/src/db/tables/project_repository.rs @@ -18,6 +18,8 @@ pub struct Model { pub current_merge_conflicts: Option, // A JSON object representing the current Branch values pub branch_summary: Option, + // A JSON object representing the current Head commit values + pub head_commit_details: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index a2a776a29c..396250e689 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -2938,6 +2938,7 @@ impl GitPanel { let expand_tooltip_focus_handle = editor_focus_handle.clone(); let branch = active_repository.read(cx).branch.clone(); + let head_commit = active_repository.read(cx).head_commit.clone(); let footer_size = px(32.); let gap = px(9.0); @@ -2966,6 +2967,7 @@ impl GitPanel { .child(PanelRepoFooter::new( display_name, branch, + head_commit, Some(git_panel.clone()), )) .child( @@ -4291,6 +4293,8 @@ impl Render for GitPanelMessageTooltip { pub struct PanelRepoFooter { active_repository: SharedString, branch: Option, + head_commit: Option, + // Getting a GitPanel in previews will be difficult. // // For now just take an option here, and we won't bind handlers to buttons in previews. @@ -4301,11 +4305,13 @@ impl PanelRepoFooter { pub fn new( active_repository: SharedString, branch: Option, + head_commit: Option, git_panel: Option>, ) -> Self { Self { active_repository, branch, + head_commit, git_panel, } } @@ -4314,6 +4320,7 @@ impl PanelRepoFooter { Self { active_repository, branch, + head_commit: None, git_panel: None, } } @@ -4339,11 +4346,26 @@ impl RenderOnce for PanelRepoFooter { const MAX_BRANCH_LEN: usize = 16; const MAX_REPO_LEN: usize = 16; const LABEL_CHARACTER_BUDGET: usize = MAX_BRANCH_LEN + MAX_REPO_LEN; + const MAX_SHORT_SHA_LEN: usize = 8; - let branch = self.branch.clone(); - let branch_name = branch + let branch_name = self + .branch .as_ref() - .map_or(" (no branch)".into(), |branch| branch.name.clone()); + .map(|branch| branch.name.clone()) + .or_else(|| { + self.head_commit.as_ref().map(|commit| { + SharedString::from( + commit + .sha + .chars() + .take(MAX_SHORT_SHA_LEN) + .collect::(), + ) + }) + }) + .unwrap_or_else(|| SharedString::from(" (no branch)")); + let show_separator = self.branch.is_some() || self.head_commit.is_some(); + let active_repo_name = self.active_repository.clone(); let branch_actual_len = branch_name.len(); @@ -4449,7 +4471,7 @@ impl RenderOnce for PanelRepoFooter { ), ) .child(repo_selector) - .when_some(branch.clone(), |this, _| { + .when(show_separator, |this| { this.child( div() .text_color(cx.theme().colors().text_muted) diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 024b347d19..1165373d1e 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -231,6 +231,7 @@ pub struct RepositorySnapshot { pub statuses_by_path: SumTree, pub work_directory_abs_path: Arc, pub branch: Option, + pub head_commit: Option, pub merge_conflicts: TreeSet, pub merge_head_shas: Vec, pub scan_id: u64, @@ -2426,6 +2427,7 @@ impl RepositorySnapshot { statuses_by_path: Default::default(), work_directory_abs_path, branch: None, + head_commit: None, merge_conflicts: Default::default(), merge_head_shas: Default::default(), scan_id: 0, @@ -2435,6 +2437,7 @@ impl RepositorySnapshot { fn initial_update(&self, project_id: u64) -> proto::UpdateRepository { proto::UpdateRepository { branch_summary: self.branch.as_ref().map(branch_to_proto), + head_commit_details: self.head_commit.as_ref().map(commit_details_to_proto), updated_statuses: self .statuses_by_path .iter() @@ -2499,6 +2502,7 @@ impl RepositorySnapshot { proto::UpdateRepository { branch_summary: self.branch.as_ref().map(branch_to_proto), + head_commit_details: self.head_commit.as_ref().map(commit_details_to_proto), updated_statuses, removed_statuses, current_merge_conflicts: self @@ -3748,6 +3752,11 @@ impl Repository { .map(|path| RepoPath(Path::new(&path).into())), ); self.snapshot.branch = update.branch_summary.as_ref().map(proto_to_branch); + self.snapshot.head_commit = update + .head_commit_details + .as_ref() + .map(proto_to_commit_details); + self.snapshot.merge_conflicts = conflicted_paths; let edits = update @@ -4322,6 +4331,26 @@ fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch { } } +fn commit_details_to_proto(commit: &CommitDetails) -> proto::GitCommitDetails { + proto::GitCommitDetails { + sha: commit.sha.to_string(), + message: commit.message.to_string(), + commit_timestamp: commit.commit_timestamp, + author_email: commit.author_email.to_string(), + author_name: commit.author_name.to_string(), + } +} + +fn proto_to_commit_details(proto: &proto::GitCommitDetails) -> CommitDetails { + CommitDetails { + sha: proto.sha.clone().into(), + message: proto.message.clone().into(), + commit_timestamp: proto.commit_timestamp, + author_email: proto.author_email.clone().into(), + author_name: proto.author_name.clone().into(), + } +} + async fn compute_snapshot( id: RepositoryId, work_directory_abs_path: Arc, @@ -4377,6 +4406,12 @@ async fn compute_snapshot( events.push(RepositoryEvent::MergeHeadsChanged); } + // Useful when branch is None in detached head state + let head_commit = match backend.head_sha() { + Some(head_sha) => backend.show(head_sha).await.ok(), + None => None, + }; + let snapshot = RepositorySnapshot { id, merge_message, @@ -4384,6 +4419,7 @@ async fn compute_snapshot( work_directory_abs_path, scan_id: prev_snapshot.scan_id + 1, branch, + head_commit, merge_conflicts, merge_head_shas, }; diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index 0d94bcb469..b2437d9c89 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -120,6 +120,7 @@ message UpdateRepository { repeated string current_merge_conflicts = 8; uint64 scan_id = 9; bool is_last_update = 10; + optional GitCommitDetails head_commit_details = 11; } message RemoveRepository { diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 7cc700ce60..b44cbb5c5e 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -46,6 +46,7 @@ pub use stories::*; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; +const MAX_SHORT_SHA_LENGTH: usize = 8; const BOOK_ONBOARDING: &str = "https://dub.sh/zed-c-onboarding"; @@ -513,8 +514,23 @@ impl TitleBar { pub fn render_project_branch(&self, cx: &mut Context) -> Option { let repository = self.project.read(cx).active_repository(cx)?; let workspace = self.workspace.upgrade()?; - let branch_name = repository.read(cx).branch.as_ref()?.name.clone(); - let branch_name = util::truncate_and_trailoff(&branch_name, MAX_BRANCH_NAME_LENGTH); + let branch_name = { + let repo = repository.read(cx); + repo.branch + .as_ref() + .map(|branch| branch.name.clone()) + .map(|name| util::truncate_and_trailoff(&name, MAX_BRANCH_NAME_LENGTH)) + .or_else(|| { + repo.head_commit.as_ref().map(|commit| { + commit + .sha + .chars() + .take(MAX_SHORT_SHA_LENGTH) + .collect::() + }) + }) + }?; + Some( Button::new("project_branch_trigger", branch_name) .color(Color::Muted)