diff --git a/Cargo.lock b/Cargo.lock index c6430232f2c..c7936beebac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -761,7 +761,6 @@ dependencies = [ "but-gerrit", "but-github", "but-gitlab", - "but-graph", "but-hunk-assignment", "but-installer", "but-link", diff --git a/crates/but-workspace/src/ui/ref_info.rs b/crates/but-workspace/src/ui/ref_info.rs index be8d173c79d..96f928157a7 100644 --- a/crates/but-workspace/src/ui/ref_info.rs +++ b/crates/but-workspace/src/ui/ref_info.rs @@ -318,6 +318,14 @@ pub struct Segment { /// This means one will see the entire workspace, while knowing the focus is on one specific segment. /// *Note* that this segment can be listed in *multiple stacks* as it's reachable from multiple 'ahead' segments. pub is_entrypoint: bool, + /// The ID of a linked worktree associated with this segment, if any. + #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "export-ts", ts(type = "number[] | null"))] + #[cfg_attr( + feature = "export-schema", + schemars(schema_with = "but_schemars::bstring_bytes_opt") + )] + pub linked_worktree_id: Option, /// A derived value to help the UI decide which functions to make available. pub push_status: ui::PushStatus, /// This is always the `first()` commit in `commits` of the next stacksegment, or the first commit of @@ -351,6 +359,13 @@ impl Segment { }: crate::ref_info::Segment, names: &gix::remote::Names, ) -> anyhow::Result { + let linked_worktree_id = ref_info.as_ref().and_then(|ri| { + if let Some(but_graph::Worktree::LinkedId(id)) = &ri.worktree { + Some(id.clone()) + } else { + None + } + }); Ok(Segment { ref_name: ref_info.map(|ri| ri.ref_name.into()), remote_tracking_ref_name: remote_tracking_ref_name @@ -372,6 +387,7 @@ impl Segment { }), metadata, is_entrypoint, + linked_worktree_id, push_status, base, }) diff --git a/crates/but/Cargo.toml b/crates/but/Cargo.toml index 61db67fc526..49ea5ade202 100644 --- a/crates/but/Cargo.toml +++ b/crates/but/Cargo.toml @@ -62,7 +62,6 @@ but-forge.workspace = true but-forge-storage.workspace = true but-workspace = { workspace = true } but-api = { workspace = true, features = ["path-bytes"] } -but-graph.workspace = true but-llm.workspace = true but-link.workspace = true @@ -142,7 +141,6 @@ itertools.workspace = true self_cell.workspace = true [dev-dependencies] -but-graph.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } but-testsupport = { workspace = true, features = ["sandbox-but-api"] } snapbox = { workspace = true, features = ["term-svg"] } diff --git a/crates/but/src/command/eval_hook.rs b/crates/but/src/command/eval_hook.rs index e60bca7c69f..fa8e3d7b041 100644 --- a/crates/but/src/command/eval_hook.rs +++ b/crates/but/src/command/eval_hook.rs @@ -106,7 +106,7 @@ fn collect_stacks(ctx: &but_ctx::Context, repo: &gix::Repository) -> Vec Vec info, + Err(e) => { + tracing::debug!(?e, "eval-hook: failed to convert to UI types"); + return Vec::new(); + } + }; info.stacks .iter() .map(|stack| StackInfo { @@ -127,7 +134,7 @@ fn collect_stacks(ctx: &but_ctx::Context, repo: &gix::Repository) -> Vec String { .unwrap_or_default() } -pub(crate) fn gix_time_to_rfc3339(time: &gix::date::Time) -> String { - let seconds = time.seconds; - - DateTime::::from_timestamp(seconds, 0) - .map(|dt| dt.to_rfc3339()) - .unwrap_or_default() -} - /// Convert file assignments to JSON FileChange objects fn convert_file_assignments( assignments: &[super::assignment::FileAssignment], diff --git a/crates/but/src/command/legacy/status/mod.rs b/crates/but/src/command/legacy/status/mod.rs index 344590176f1..c825d443234 100644 --- a/crates/but/src/command/legacy/status/mod.rs +++ b/crates/but/src/command/legacy/status/mod.rs @@ -7,7 +7,7 @@ use but_core::{RepositoryExt, TreeStatus, ui}; use but_ctx::Context; use but_forge::ForgeReview; use but_oxidize::OidExt; -use but_workspace::{ref_info::LocalCommitRelation, ui::PushStatus}; +use but_workspace::ui::{CommitState, PushStatus}; use colored::{ColoredString, Colorize}; use gitbutler_branch_actions::upstream_integration::BranchStatus as UpstreamBranchStatus; use gitbutler_stack::StackId; @@ -15,7 +15,6 @@ use gix::date::time::CustomFormat; use serde::Serialize; use crate::{ - CLI_DATE, id::{SegmentWithId, ShortId, StackWithId, TreeChangeWithId}, tui::text::truncate_text, utils::{WriteWithUtils, time::format_relative_time_verbose}, @@ -105,17 +104,7 @@ pub(crate) async fn worktree( let mut guard = ctx.exclusive_worktree_access(); but_rules::process_rules(ctx, guard.write_permission()).ok(); // TODO: this is doing double work (hunk-dependencies can be reused) - // TODO: use this for JSON status information (regular status information - // already uses this) - let meta = ctx.meta()?; - but_workspace::head_info( - &*ctx.repo.get()?, - &meta, - but_workspace::ref_info::Options { - expensive_commit_info: true, - ..Default::default() - }, - )? + but_api::legacy::workspace::head_info(ctx)? }; let cache_config = if refresh_prs { @@ -646,26 +635,36 @@ pub fn print_group( first = false; if !segment.remote_commits.is_empty() { - let tracking_branch = segment + let tracking_branch_display = segment .inner .remote_tracking_ref_name .as_ref() - .and_then(|rtb| rtb.as_bstr().strip_prefix(b"refs/remotes/")) - .unwrap_or(b"unknown"); + .map(|rtb| format!("{}/{}", rtb.remote_name, rtb.display_name)) + .unwrap_or_else(|| "unknown".to_string()); writeln!(out, "┊┊")?; writeln!( out, "┊╭┄┄{}", - format!("(upstream: on {})", BStr::new(tracking_branch)).yellow() + format!("(upstream: on {tracking_branch_display})").yellow() )?; } for commit in &segment.remote_commits { let details = but_api::diff::commit_details(ctx, commit.commit_id(), ComputeLineStats::No)?; + let ui_commit = but_workspace::ui::Commit { + id: commit.inner.id, + parent_ids: Vec::new(), + message: commit.inner.message.clone(), + has_conflicts: false, + state: CommitState::LocalOnly, + created_at: commit.inner.created_at, + author: commit.inner.author.clone(), + gerrit_review_url: None, + }; print_commit( &repo, commit.short_id.clone(), - &commit.inner, + &ui_commit, CommitChanges::Remote(&details.diff_with_first_parent), CommitClassification::Upstream, false, @@ -684,22 +683,22 @@ pub fn print_group( commit.commit_id().to_string(), ) .unwrap_or_default(); - let classification = match commit.relation() { - LocalCommitRelation::LocalOnly => CommitClassification::LocalOnly, - LocalCommitRelation::LocalAndRemote(object_id) => { - if object_id == commit.commit_id() { + let classification = match commit.state() { + CommitState::LocalOnly => CommitClassification::LocalOnly, + CommitState::LocalAndRemote(object_id) => { + if *object_id == commit.commit_id() { CommitClassification::Pushed } else { CommitClassification::Modified } } - LocalCommitRelation::Integrated(_) => CommitClassification::Integrated, + CommitState::Integrated => CommitClassification::Integrated, }; print_commit( &repo, commit.short_id.clone(), - &commit.inner.inner, + &commit.inner, CommitChanges::Workspace(&commit.tree_changes_using_repo(&repo)?), classification, marked, @@ -786,7 +785,7 @@ enum CommitChanges<'a> { fn print_commit( repo: &gix::Repository, short_id: ShortId, - commit: &but_workspace::ref_info::Commit, + commit: &but_workspace::ui::Commit, commit_changes: CommitChanges, classification: CommitClassification, marked: bool, @@ -898,7 +897,7 @@ impl CliDisplay for but_core::TreeChange { fn display_cli_commit_details( repo: &gix::Repository, short_id: ShortId, - commit: &but_workspace::ref_info::Commit, + commit: &but_workspace::ui::Commit, has_changes: bool, verbose: bool, is_paged: bool, @@ -929,8 +928,7 @@ fn display_cli_commit_details( if verbose { // No message when verbose since it goes to the next line - let created_at = commit.author.time; - let formatted_time = created_at.format_or_unix(CLI_DATE); + let formatted_time = json::i128_to_rfc3339(commit.created_at); format!( "{}{} {} {}{}{}", start_id, diff --git a/crates/but/src/id/mod.rs b/crates/but/src/id/mod.rs index 98c17ea4065..00ae182ef88 100644 --- a/crates/but/src/id/mod.rs +++ b/crates/but/src/id/mod.rs @@ -13,7 +13,7 @@ use bstr::{BStr, BString, ByteSlice}; use but_core::{ChangeId, ref_metadata::StackId}; use but_ctx::Context; use but_hunk_assignment::HunkAssignment; -use but_workspace::{branch::Stack, ref_info::LocalCommitRelation}; +use but_workspace::ui::{self as ws_ui, CommitState, ref_info::Stack}; use nonempty::NonEmpty; use self_cell::self_cell; @@ -153,20 +153,20 @@ pub struct WorkspaceCommitWithId { /// The short ID. pub short_id: ShortId, /// The original workspace commit. - pub inner: but_workspace::ref_info::LocalCommit, + pub inner: ws_ui::Commit, } impl WorkspaceCommitWithId { /// The object ID of the commit. pub fn commit_id(&self) -> gix::ObjectId { - self.inner.inner.id + self.inner.id } /// The ID of the first parent if the commit has parents. pub fn first_parent_id(&self) -> Option { - self.inner.inner.parent_ids.first().cloned() + self.inner.parent_ids.first().cloned() } /// State in relation to its remote tracking branch. - pub fn relation(&self) -> LocalCommitRelation { - self.inner.relation + pub fn state(&self) -> &CommitState { + &self.inner.state } } /// Methods to calculate the short IDs of committed files. @@ -250,7 +250,7 @@ pub struct RemoteCommitWithId { /// The short ID. pub short_id: ShortId, /// The original remote commit. - pub inner: but_workspace::ref_info::Commit, + pub inner: ws_ui::UpstreamCommit, } impl RemoteCommitWithId { /// The object ID of the commit. @@ -290,7 +290,7 @@ pub struct SegmentWithId { pub is_auto_id: bool, /// The original segment except that `commits` and `commits_on_remote` are /// blank to save memory. - pub inner: but_workspace::ref_info::Segment, + pub inner: ws_ui::ref_info::Segment, /// The original `inner.commits` with additional information. pub workspace_commits: Vec, /// The original `inner.commits_on_remote` with additional information. @@ -304,19 +304,16 @@ impl SegmentWithId { /// Returns the branch name. pub fn branch_name(&self) -> Option<&BStr> { self.inner - .ref_info + .ref_name .as_ref() - .map(|ref_info| ref_info.ref_name.shorten()) + .map(|br| br.display_name.as_bytes().as_bstr()) } /// Returns the linked worktree ID. pub fn linked_worktree_id(&self) -> Option<&BStr> { - if let Some(ref_info) = &self.inner.ref_info - && let Some(but_graph::Worktree::LinkedId(id)) = &ref_info.worktree - { - Some(id.as_bstr()) - } else { - None - } + self.inner + .linked_worktree_id + .as_ref() + .map(|id| id.as_bstr()) } /// Returns the PR number. pub fn pr_number(&self) -> Option { @@ -566,7 +563,8 @@ impl IdMap { ..Default::default() }, )?; - Self::new(head_info.stacks, hunk_assignments) + let ui_ref_info = ws_ui::RefInfo::for_ui(head_info, &repo)?; + Self::new(ui_ref_info.stacks, hunk_assignments) } } diff --git a/crates/but/src/id/stacks_info.rs b/crates/but/src/id/stacks_info.rs index ec5b1848be3..4aee1480efe 100644 --- a/crates/but/src/id/stacks_info.rs +++ b/crates/but/src/id/stacks_info.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use bstr::BString; -use but_workspace::branch::Stack; +use but_workspace::ui::ref_info::Stack; use crate::id::{ RemoteCommitWithId, SegmentWithId, ShortId, StackWithId, UNASSIGNED, WorkspaceCommitWithId, diff --git a/crates/but/src/id/tests.rs b/crates/but/src/id/tests.rs index 0567797e7cb..70fccf4c4a0 100644 --- a/crates/but/src/id/tests.rs +++ b/crates/but/src/id/tests.rs @@ -2,7 +2,7 @@ use anyhow::bail; use but_core::ref_metadata::StackId; use but_hunk_assignment::HunkAssignment; use but_testsupport::{hex_to_id, hunk_header}; -use but_workspace::branch::Stack; +use but_workspace::ui::ref_info::Stack; use crate::{CliId, IdMap, id::id_usage::UintId}; @@ -1504,9 +1504,9 @@ mod util { use bstr::BString; use but_core::ref_metadata::StackId; use but_hunk_assignment::HunkAssignment; - use but_workspace::{ - branch::Stack, - ref_info::{Commit, LocalCommit, Segment}, + use but_workspace::ui::{ + self as ws_ui, + ref_info::{BranchReference, Segment, Stack}, }; use itertools::Itertools; @@ -1522,46 +1522,48 @@ mod util { base: Option, remote_commit_ids: [gix::ObjectId; N2], ) -> Segment { - fn commit(id: gix::ObjectId, parent_id: Option) -> Commit { - Commit { + fn commit(id: gix::ObjectId, parent_id: Option) -> ws_ui::Commit { + ws_ui::Commit { id, parent_ids: parent_id.into_iter().collect::>(), - tree_id: gix::index::hash::Kind::Sha1.empty_tree(), message: Default::default(), - author: Default::default(), - refs: Vec::new(), - flags: Default::default(), has_conflicts: false, - change_id: None, + state: ws_ui::CommitState::LocalOnly, + created_at: 0, + author: gix::actor::SignatureRef::default().into(), + gerrit_review_url: None, } } - let ref_info = Some(but_graph::RefInfo { - ref_name: gix::refs::FullName::try_from(format!("refs/heads/{shortened_branch_name}")) - .expect("could not generate ref name"), - worktree: None, + fn upstream_commit(id: gix::ObjectId) -> ws_ui::UpstreamCommit { + ws_ui::UpstreamCommit { + id, + message: Default::default(), + created_at: 0, + author: gix::actor::SignatureRef::default().into(), + } + } + + let ref_name = Some(BranchReference { + full_name_bytes: format!("refs/heads/{shortened_branch_name}").into(), + display_name: shortened_branch_name.to_string(), }); - let mut commits: Vec = Vec::new(); + let mut commits: Vec = Vec::new(); for (i, id) in local_commit_ids.iter().enumerate() { let parent_id = local_commit_ids.get(i + 1).or(base.as_ref()); - commits.push(LocalCommit { - inner: commit(*id, parent_id.cloned()), - relation: Default::default(), - }); - } - let mut commits_on_remote: Vec = Vec::new(); - for id in remote_commit_ids { - commits_on_remote.push(commit(id, None)) + commits.push(commit(*id, parent_id.cloned())); } + let commits_on_remote: Vec = + remote_commit_ids.into_iter().map(upstream_commit).collect(); Segment { - ref_info, - id: Default::default(), + ref_name, remote_tracking_ref_name: None, commits, commits_on_remote, commits_outside: None, metadata: None, is_entrypoint: false, + linked_worktree_id: None, push_status: but_workspace::ui::PushStatus::NothingToPush, base, } diff --git a/crates/but/tests/but/command/snapshots/status/two-worktrees/status-with-worktrees-verbose.stdout.term.svg b/crates/but/tests/but/command/snapshots/status/two-worktrees/status-with-worktrees-verbose.stdout.term.svg index c8e40b95405..f59fefbd39e 100644 --- a/crates/but/tests/but/command/snapshots/status/two-worktrees/status-with-worktrees-verbose.stdout.term.svg +++ b/crates/but/tests/but/command/snapshots/status/two-worktrees/status-with-worktrees-verbose.stdout.term.svg @@ -12,7 +12,7 @@ } .bold { font-weight: bold; } .italic { font-style: italic; } - .dimmed { opacity: 0.7; } + .dimmed { opacity: 0.4; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -35,13 +35,13 @@ ┊╭┄┄(upstream: on origin/A) - 197ddce author 2000-01-01 00:00:00 +0000 (no changes) + 197ddce author 2000-01-01T00:00:00+00:00 (no changes) ┊│ A-remote ┊- - ┊● 4c4624e author 2000-01-01 00:00:00 +0000 (no changes) + ┊● 4c4624e author 2000-01-01T00:00:00+00:00 (no changes) ┊│ A @@ -51,7 +51,7 @@ ┊╭┄h0 [B 📁 gitbutler/worktrees/B] - ┊● 3e01e28 author 2000-01-01 00:00:00 +0000 (no changes) + ┊● 3e01e28 author 2000-01-01T00:00:00+00:00 (no changes) ┊│ B diff --git a/crates/but/tests/but/snapshots/from-workspace/status01-verbose.stdout.term.svg b/crates/but/tests/but/snapshots/from-workspace/status01-verbose.stdout.term.svg index f459fe62ed6..fafc0f56a2c 100644 --- a/crates/but/tests/but/snapshots/from-workspace/status01-verbose.stdout.term.svg +++ b/crates/but/tests/but/snapshots/from-workspace/status01-verbose.stdout.term.svg @@ -11,7 +11,7 @@ } .bold { font-weight: bold; } .italic { font-style: italic; } - .dimmed { opacity: 0.7; } + .dimmed { opacity: 0.4; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -30,7 +30,7 @@ ┊╭┄g0 [A] - ┊● 9477ae7 author 2000-01-01 00:00:00 +0000 + ┊● 9477ae7 author 2000-01-01T00:00:00+00:00 ┊│ add A @@ -40,7 +40,7 @@ ┊╭┄h0 [B] - ┊● d3e2ba3 author 2000-01-01 00:00:00 +0000 + ┊● d3e2ba3 author 2000-01-01T00:00:00+00:00 ┊│ add B diff --git a/packages/core/src/generated/workspace/refInfo/index.ts b/packages/core/src/generated/workspace/refInfo/index.ts index e533cc7ecb5..287b7f3b2ef 100644 --- a/packages/core/src/generated/workspace/refInfo/index.ts +++ b/packages/core/src/generated/workspace/refInfo/index.ts @@ -133,6 +133,10 @@ export type Segment = { * *Note* that this segment can be listed in *multiple stacks* as it's reachable from multiple 'ahead' segments. */ isEntrypoint: boolean; + /** + * The ID of a linked worktree associated with this segment, if any. + */ + linkedWorktreeId: number[] | null; /** * A derived value to help the UI decide which functions to make available. */