Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions crates/but-workspace/src/ui/ref_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BString>,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Byron the but CLI uses this linked_worktree_id for some reason. It's not quite clear why it's necessary but i couldn't shed it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but status actually uses the linked_worktree_id to show if a reference is checked out in a linked worktree, but it's display only:

let workspace = segment
.linked_worktree_id()
.and_then(|id| {
let ws = repo.worktree_proxy_by_id(id.as_bstr())?;
let base = ws.base().ok()?;
let git_dir = gix::path::realpath(repo.git_dir()).ok();
let base = git_dir
.and_then(|git_dir| base.strip_prefix(git_dir).ok())
.unwrap_or_else(|| &base);
format!(" 📁 {base}", base = base.display()).into()
})
.unwrap_or_default();

The rebase engine would use this information to update linked worktrees when it changed what a reference points to (right now it's not doing that).

/// 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
Expand Down Expand Up @@ -351,6 +359,13 @@ impl Segment {
}: crate::ref_info::Segment,
names: &gix::remote::Names,
) -> anyhow::Result<Self> {
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
Expand All @@ -372,6 +387,7 @@ impl Segment {
}),
metadata,
is_entrypoint,
linked_worktree_id,
push_status,
base,
})
Expand Down
2 changes: 0 additions & 2 deletions crates/but/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"] }
Expand Down
11 changes: 9 additions & 2 deletions crates/but/src/command/eval_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn collect_stacks(ctx: &but_ctx::Context, repo: &gix::Repository) -> Vec<StackIn
let Ok(meta) = ctx.meta() else {
return Vec::new();
};
let info = match but_workspace::head_info(
let raw_info = match but_workspace::head_info(
repo,
&meta,
but_workspace::ref_info::Options {
Expand All @@ -120,14 +120,21 @@ fn collect_stacks(ctx: &but_ctx::Context, repo: &gix::Repository) -> Vec<StackIn
return Vec::new();
}
};
let info = match but_workspace::ui::RefInfo::for_ui(raw_info, repo) {
Ok(info) => info,
Err(e) => {
tracing::debug!(?e, "eval-hook: failed to convert to UI types");
return Vec::new();
}
};
info.stacks
.iter()
.map(|stack| StackInfo {
branches: stack
.segments
.iter()
.filter_map(|seg| {
let name = seg.ref_info.as_ref()?.ref_name.shorten().to_string();
let name = seg.ref_name.as_ref()?.display_name.clone();
Some(BranchInfo {
name,
commit_count: seg.commits.len(),
Expand Down
18 changes: 4 additions & 14 deletions crates/but/src/command/legacy/status/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,18 +355,16 @@ impl Commit {
None
};

let commit = &commit.inner.inner;
let commit = &commit.inner;
Ok(Commit {
cli_id,
commit_id: commit.id.to_string(),
created_at: gix_time_to_rfc3339(&commit.author.time),
created_at: i128_to_rfc3339(commit.created_at),
message: commit.message.to_string(),
author_name: commit.author.name.to_string(),
author_email: commit.author.email.to_string(),
conflicted: Some(commit.has_conflicts),
// TODO: populate but_workspace::ref_info::LocalCommit with the
// Gerrit URL
review_id: None,
review_id: commit.gerrit_review_url.clone(),
changes,
})
}
Expand All @@ -379,7 +377,7 @@ impl Commit {
Commit {
cli_id,
commit_id: commit.id.to_string(),
created_at: gix_time_to_rfc3339(&commit.author.time),
created_at: i128_to_rfc3339(commit.created_at),
message: commit.message.to_string(),
author_name: commit.author.name.to_string(),
author_email: commit.author.email.to_string(),
Expand Down Expand Up @@ -442,14 +440,6 @@ pub(crate) fn i128_to_rfc3339(ts_millis: i128) -> String {
.unwrap_or_default()
}

pub(crate) fn gix_time_to_rfc3339(time: &gix::date::Time) -> String {
let seconds = time.seconds;

DateTime::<Utc>::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],
Expand Down
54 changes: 26 additions & 28 deletions crates/but/src/command/legacy/status/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ 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;
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},
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
34 changes: 16 additions & 18 deletions crates/but/src/id/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<gix::ObjectId> {
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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<WorkspaceCommitWithId>,
/// The original `inner.commits_on_remote` with additional information.
Expand All @@ -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<usize> {
Expand Down Expand Up @@ -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)
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/but/src/id/stacks_info.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading
Loading