Skip to content

Commit ee80b70

Browse files
committed
Let but-workspace use it's own ui::Segment and subtypes
That way we can craft the `but_graph::Segment` so that it makes sense for the graph and doesn't have to compromise on types.
1 parent 8826993 commit ee80b70

File tree

11 files changed

+321
-282
lines changed

11 files changed

+321
-282
lines changed

crates/but-graph/src/api.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl Graph {
1313
/// Insert `segment` to the graph so that it's not connected to any other segment, and return its index.
1414
pub fn insert_root(&mut self, segment: Segment) -> SegmentIndex {
1515
let index = self.inner.add_node(segment);
16-
self.inner[index].id = index.index();
16+
self.inner[index].id = index;
1717
if self.entrypoint.is_none() {
1818
self.entrypoint = Some((index, None))
1919
}
@@ -41,7 +41,7 @@ impl Graph {
4141
dst_commit_id: impl Into<Option<gix::ObjectId>>,
4242
) -> SegmentIndex {
4343
let dst = self.inner.add_node(dst);
44-
self.inner[dst].id = dst.index();
44+
self.inner[dst].id = dst;
4545
self.connect_segments_with_ids(
4646
src,
4747
src_commit,
@@ -196,17 +196,15 @@ impl Graph {
196196
}
197197

198198
/// Produce a string that concisely represents `commit`, adding `extra` information as needed.
199-
pub fn commit_debug_string<'a>(
199+
pub fn commit_debug_string(
200200
commit: &crate::Commit,
201-
extra: impl Into<Option<&'a str>>,
202201
has_conflicts: bool,
203202
is_entrypoint: bool,
204203
show_message: bool,
205204
is_early_end: bool,
206205
) -> String {
207-
let extra = extra.into();
208206
format!(
209-
"{ep}{end}{kind}{conflict}{hex}{extra}{flags}{msg}{refs}",
207+
"{ep}{end}{kind}{conflict}{hex}{flags}{msg}{refs}",
210208
ep = if is_entrypoint { "👉" } else { "" },
211209
end = if is_early_end { "✂️" } else { "" },
212210
kind = if commit.flags.contains(CommitFlags::NotInRemote) {
@@ -215,11 +213,6 @@ impl Graph {
215213
"🟣"
216214
},
217215
conflict = if has_conflicts { "💥" } else { "" },
218-
extra = if let Some(extra) = extra {
219-
format!(" [{extra}]")
220-
} else {
221-
"".into()
222-
},
223216
flags = if !commit.flags.is_empty() {
224217
format!(" ({})", commit.flags.debug_string())
225218
} else {
@@ -370,8 +363,7 @@ impl Graph {
370363
.enumerate()
371364
.map(|(cidx, c)| {
372365
Self::commit_debug_string(
373-
&c.inner,
374-
None,
366+
c,
375367
c.has_conflicts,
376368
!show_segment_entrypoint && Some((sidx, Some(cidx))) == entrypoint,
377369
false,

crates/but-graph/src/init/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ impl Graph {
367367

368368
let refs_at_commit_before_removal = refs_by_id.remove(&id).unwrap_or_default();
369369
segment.commits.push(
370-
info.into_local_commit(
370+
info.into_commit(
371371
repo,
372372
segment
373373
.commits

crates/but-graph/src/init/utils.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::init::walk::TopoWalk;
22
use crate::init::{EdgeOwned, Instruction, PetGraph, QueueItem, remotes};
33
use crate::{
4-
Commit, CommitFlags, CommitIndex, Edge, Graph, LocalCommit, Segment, SegmentIndex,
5-
SegmentMetadata, is_workspace_ref_name,
4+
Commit, CommitFlags, CommitIndex, Edge, Graph, Segment, SegmentIndex, SegmentMetadata,
5+
is_workspace_ref_name,
66
};
77
use anyhow::{Context, bail};
88
use bstr::BString;
@@ -308,15 +308,15 @@ pub struct TraverseInfo {
308308
}
309309

310310
impl TraverseInfo {
311-
pub fn into_local_commit(
311+
pub fn into_commit(
312312
self,
313313
repo: &gix::Repository,
314314
flags: CommitFlags,
315315
refs: Vec<gix::refs::FullName>,
316-
) -> anyhow::Result<LocalCommit> {
316+
) -> anyhow::Result<Commit> {
317317
let commit = but_core::Commit::from_id(self.id.attach(repo))?;
318318
let has_conflicts = commit.is_conflicted();
319-
let commit = match self.commit {
319+
Ok(match self.commit {
320320
Some(commit) => Commit {
321321
refs,
322322
flags,
@@ -329,13 +329,8 @@ impl TraverseInfo {
329329
author: commit.author.clone(),
330330
flags,
331331
refs,
332+
has_conflicts,
332333
},
333-
};
334-
335-
Ok(LocalCommit {
336-
inner: commit,
337-
relation: Default::default(),
338-
has_conflicts,
339334
})
340335
}
341336
}
@@ -394,6 +389,8 @@ pub fn find(
394389
author: author.context("Every valid commit must have an author signature")?,
395390
refs: Vec::new(),
396391
flags: CommitFlags::empty(),
392+
// TODO: we probably should set this
393+
has_conflicts: false,
397394
})
398395
}
399396
};

crates/but-graph/src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
#![deny(missing_docs, rust_2018_idioms)]
44

55
mod segment;
6-
pub use segment::{
7-
Commit, CommitFlags, LocalCommit, LocalCommitRelation, RemoteCommit, Segment, SegmentMetadata,
8-
};
6+
pub use segment::{Commit, CommitFlags, Segment, SegmentMetadata};
97

108
/// Edges to other segments are the index into the list of local commits of the parent segment.
119
/// That way we can tell where a segment branches off, despite the graph only connecting segments, and not commits.
@@ -33,7 +31,7 @@ pub struct EntryPoint<'graph> {
3331
/// The segment that served starting point for the traversal into this graph.
3432
pub segment: &'graph Segment,
3533
/// If present, the commit that started the traversal in the `segment`.
36-
pub commit: Option<&'graph LocalCommit>,
34+
pub commit: Option<&'graph Commit>,
3735
}
3836

3937
/// This structure is used as data associated with each edge and is mainly for collecting

crates/but-graph/src/segment.rs

Lines changed: 16 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use crate::CommitIndex;
1+
use crate::{CommitIndex, SegmentIndex};
22
use bitflags::bitflags;
33
use gix::bstr::BString;
4-
use std::ops::{Deref, DerefMut};
54

65
/// A commit with must useful information extracted from the Git commit itself.
76
///
@@ -21,12 +20,21 @@ pub struct Commit {
2120
pub refs: Vec<gix::refs::FullName>,
2221
/// Additional properties to help classify this commit.
2322
pub flags: CommitFlags,
24-
// TODO: bring has_conflict: bool here, then remove `RemoteCommit` type.
23+
/// Whether the commit is in a conflicted state, a GitButler concept.
24+
/// GitButler will perform rebasing/reordering etc. without interruptions and flag commits as conflicted if needed.
25+
/// Conflicts are resolved via the Edit Mode mechanism.
26+
///
27+
/// Note that even though GitButler won't push branches with conflicts, the user can still push such branches at will.
28+
pub has_conflicts: bool,
2529
}
2630

2731
impl Commit {
2832
/// Read the object of the `commit_id` and extract relevant values, while setting `flags` as well.
29-
pub fn new_from_id(commit_id: gix::Id<'_>, flags: CommitFlags) -> anyhow::Result<Self> {
33+
pub fn new_from_id(
34+
commit_id: gix::Id<'_>,
35+
flags: CommitFlags,
36+
has_conflicts: bool,
37+
) -> anyhow::Result<Self> {
3038
let commit = commit_id.object()?.into_commit();
3139
// Decode efficiently, no need to own this.
3240
let commit = commit.decode()?;
@@ -37,6 +45,7 @@ impl Commit {
3745
author: commit.author.to_owned()?,
3846
refs: Vec::new(),
3947
flags,
48+
has_conflicts,
4049
})
4150
}
4251
}
@@ -66,6 +75,7 @@ impl From<but_core::Commit<'_>> for Commit {
6675
author: value.inner.author,
6776
refs: Vec::new(),
6877
flags: CommitFlags::empty(),
78+
has_conflicts: false,
6979
}
7080
}
7181
}
@@ -109,152 +119,6 @@ impl CommitFlags {
109119
}
110120
}
111121

112-
/// A commit that is reachable through the *local tracking branch*, with additional, computed information.
113-
#[derive(Clone, Eq, PartialEq)]
114-
pub struct LocalCommit {
115-
/// The simple commit.
116-
pub inner: Commit,
117-
/// Provide additional information on how this commit relates to other points of reference, like its remote branch,
118-
/// or the target branch to integrate with.
119-
pub relation: LocalCommitRelation,
120-
/// Whether the commit is in a conflicted state, a GitButler concept.
121-
/// GitButler will perform rebasing/reordering etc. without interruptions and flag commits as conflicted if needed.
122-
/// Conflicts are resolved via the Edit Mode mechanism.
123-
///
124-
/// Note that even though GitButler won't push branches with conflicts, the user can still push such branches at will.
125-
pub has_conflicts: bool,
126-
}
127-
128-
impl std::fmt::Debug for LocalCommit {
129-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130-
let refs = self
131-
.refs
132-
.iter()
133-
.map(|rn| format!("►{}", rn.shorten()))
134-
.collect::<Vec<_>>()
135-
.join(", ");
136-
write!(
137-
f,
138-
"LocalCommit({conflict}{hash}, {msg:?}, {relation}{refs})",
139-
conflict = if self.has_conflicts { "💥" } else { "" },
140-
hash = self.id.to_hex_with_len(7),
141-
msg = self.message,
142-
relation = self.relation.display(self.id),
143-
refs = if refs.is_empty() {
144-
"".to_string()
145-
} else {
146-
format!(", {refs}")
147-
}
148-
)
149-
}
150-
}
151-
152-
impl LocalCommit {
153-
/// Create a new branch-commit, along with default values for the non-commit fields.
154-
// TODO: remove this function once ref_info code doesn't need it anymore (i.e. mapping is implemented).
155-
pub fn new_from_id(value: gix::Id<'_>, flags: CommitFlags) -> anyhow::Result<Self> {
156-
Ok(LocalCommit {
157-
inner: Commit::new_from_id(value, flags)?,
158-
relation: LocalCommitRelation::LocalOnly,
159-
has_conflicts: false,
160-
})
161-
}
162-
}
163-
164-
/// The state of the [local commit](LocalCommit) in relation to its remote tracking branch or its integration branch.
165-
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
166-
pub enum LocalCommitRelation {
167-
/// The commit is only local
168-
#[default]
169-
LocalOnly,
170-
/// The commit is also present in the remote tracking branch.
171-
///
172-
/// This is the case if:
173-
/// - The commit has been pushed to the remote
174-
/// - The commit has been copied from a remote commit (when applying a remote branch)
175-
///
176-
/// This variant carries the remote commit id.
177-
/// The `remote_commit_id` may be the same as the `id` or it may be different if the local commit has been rebased
178-
/// or updated in another way.
179-
LocalAndRemote(gix::ObjectId),
180-
/// The commit is considered integrated.
181-
/// This should happen when the commit or the contents of this commit is already part of the base.
182-
Integrated,
183-
}
184-
185-
impl LocalCommitRelation {
186-
/// Convert this relation into something displaying, mainly for debugging.
187-
pub fn display(&self, id: gix::ObjectId) -> &'static str {
188-
match self {
189-
LocalCommitRelation::LocalOnly => "local",
190-
LocalCommitRelation::LocalAndRemote(remote_id) => {
191-
if *remote_id == id {
192-
"local/remote(identity)"
193-
} else {
194-
"local/remote(similarity)"
195-
}
196-
}
197-
LocalCommitRelation::Integrated => "integrated",
198-
}
199-
}
200-
}
201-
202-
impl Deref for LocalCommit {
203-
type Target = Commit;
204-
205-
fn deref(&self) -> &Self::Target {
206-
&self.inner
207-
}
208-
}
209-
210-
impl DerefMut for LocalCommit {
211-
fn deref_mut(&mut self) -> &mut Self::Target {
212-
&mut self.inner
213-
}
214-
}
215-
216-
/// A commit that is reachable only through the *remote tracking branch*, with additional, computed information.
217-
///
218-
/// TODO: Remote commits can also be integrated, without the local branch being all caught up. Currently we can't represent that.
219-
#[derive(Clone, Eq, PartialEq)]
220-
pub struct RemoteCommit {
221-
/// The simple commit.
222-
pub inner: Commit,
223-
/// Whether the commit is in a conflicted state, a GitButler concept.
224-
/// GitButler will perform rebasing/reordering etc. without interruptions and flag commits as conflicted if needed.
225-
/// Conflicts are resolved via the Edit Mode mechanism.
226-
///
227-
/// Note that even though GitButler won't push branches with conflicts, the user can still push such branches at will.
228-
/// For remote commits, this only happens if someone manually pushed them.
229-
pub has_conflicts: bool,
230-
}
231-
232-
impl std::fmt::Debug for RemoteCommit {
233-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234-
write!(
235-
f,
236-
"RemoteCommit({conflict}{hash}, {msg:?}",
237-
conflict = if self.has_conflicts { "💥" } else { "" },
238-
hash = self.id.to_hex_with_len(7),
239-
msg = self.message,
240-
)
241-
}
242-
}
243-
244-
impl Deref for RemoteCommit {
245-
type Target = Commit;
246-
247-
fn deref(&self) -> &Self::Target {
248-
&self.inner
249-
}
250-
}
251-
252-
impl DerefMut for RemoteCommit {
253-
fn deref_mut(&mut self) -> &mut Self::Target {
254-
&mut self.inner
255-
}
256-
}
257-
258122
/// A segment of a commit graph, representing a set of commits exclusively.
259123
#[derive(Default, Clone, Eq, PartialEq)]
260124
pub struct Segment {
@@ -269,24 +133,15 @@ pub struct Segment {
269133
pub ref_name: Option<gix::refs::FullName>,
270134
/// An ID which can uniquely identify this segment among all segments within the graph that owned it.
271135
/// Note that it's not suitable to permanently identify the segment, so should not be persisted.
272-
pub id: usize,
136+
pub id: SegmentIndex,
273137
/// The name of the remote tracking branch of this segment, if present, i.e. `refs/remotes/origin/main`.
274138
/// Its presence means that a remote is configured and that the stack content
275139
pub remote_tracking_ref_name: Option<gix::refs::FullName>,
276140
/// The portion of commits that can be reached from the tip of the *branch* downwards, so that they are unique
277141
/// for that stack segment and not included in any other stack or stack segment.
278142
///
279143
/// The list could be empty for when this is a dedicated empty segment as insertion position of commits.
280-
pub commits: Vec<LocalCommit>,
281-
/// Commits that are reachable from the remote-tracking branch associated with this branch,
282-
/// but are not reachable from this branch or duplicated by a commit in it.
283-
/// Note that commits that are also similar to commits in `commits` are pruned, and not present here.
284-
///
285-
/// Note that remote commits along with their remote tracking branch should always retain a shared history
286-
/// with the local tracking branch. If these diverge, we can represent this in data, but currently there is
287-
/// no derived value to make this visible explicitly.
288-
// TODO: remove this in favor of having a UI-only variant of the segment that contains these.
289-
pub commits_unique_in_remote_tracking_branch: Vec<RemoteCommit>,
144+
pub commits: Vec<Commit>,
290145
/// Read-only metadata with additional information, or `None` if nothing was present.
291146
pub metadata: Option<SegmentMetadata>,
292147
}
@@ -347,7 +202,6 @@ impl std::fmt::Debug for Segment {
347202
ref_name,
348203
id,
349204
commits,
350-
commits_unique_in_remote_tracking_branch,
351205
remote_tracking_ref_name,
352206
metadata,
353207
} = self;
@@ -368,10 +222,6 @@ impl std::fmt::Debug for Segment {
368222
},
369223
)
370224
.field("commits", &commits)
371-
.field(
372-
"commits_unique_in_remote_tracking_branch",
373-
&commits_unique_in_remote_tracking_branch,
374-
)
375225
.field(
376226
"metadata",
377227
match metadata {

0 commit comments

Comments
 (0)