Skip to content

Commit f3a70a2

Browse files
committed
Finish branch_details_v3 implementation.
It uses `gitoxide` for most of the operations, and relies on RefMetaData.
1 parent 3bbcc3f commit f3a70a2

File tree

5 files changed

+261
-129
lines changed

5 files changed

+261
-129
lines changed
Lines changed: 150 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::head_info::function::workspace_data_of_default_workspace_branch;
2-
use crate::ui::{CommitState, PushStatus};
2+
use crate::ui::{CommitState, PushStatus, UpstreamCommit};
33
use crate::{state_handle, ui};
44
use anyhow::{Context, bail};
55
use but_core::RefMetadata;
66
use gitbutler_command_context::CommandContext;
77
use gitbutler_error::error::Code;
8-
use gitbutler_oxidize::OidExt;
8+
use gitbutler_oxidize::{ObjectIdExt, OidExt};
9+
use gix::reference::Category;
910
use gix::remote::Direction;
11+
use itertools::Itertools;
1012
use std::collections::HashSet;
1113
use std::path::Path;
1214

@@ -58,64 +60,20 @@ pub fn branch_details(
5860
bail!("Failed to find merge base");
5961
};
6062

61-
let mut revwalk = repository.revwalk()?;
62-
revwalk.push(branch_oid)?;
63-
revwalk.hide(default_target.sha)?;
64-
revwalk.simplify_first_parent()?;
65-
66-
let commits = revwalk
67-
.filter_map(|oid| repository.find_commit(oid.ok()?).ok())
68-
.collect::<Vec<_>>();
69-
70-
let upstream_commits = if let Some(upstream_oid) = upstream_oid {
71-
let mut revwalk = repository.revwalk()?;
72-
revwalk.push(upstream_oid)?;
73-
revwalk.hide(branch_oid)?;
74-
revwalk.hide(default_target.sha)?;
75-
revwalk.simplify_first_parent()?;
76-
revwalk
77-
.filter_map(|oid| repository.find_commit(oid.ok()?).ok())
78-
.collect::<Vec<_>>()
79-
} else {
80-
vec![]
81-
};
82-
8363
let mut authors = HashSet::new();
84-
85-
let commits = commits
86-
.into_iter()
87-
.map(|commit| {
88-
let author: ui::Author = commit.author().into();
89-
let commiter: ui::Author = commit.committer().into();
90-
authors.insert(author.clone());
91-
authors.insert(commiter);
92-
ui::Commit {
93-
id: commit.id().to_gix(),
94-
parent_ids: commit.parent_ids().map(|id| id.to_gix()).collect(),
95-
message: commit.message().unwrap_or_default().into(),
96-
has_conflicts: false,
97-
state: CommitState::LocalAndRemote(commit.id().to_gix()),
98-
created_at: u128::try_from(commit.time().seconds()).unwrap_or(0) * 1000,
99-
author,
100-
}
101-
})
102-
.collect::<Vec<_>>();
103-
104-
let upstream_commits = upstream_commits
105-
.into_iter()
106-
.map(|commit| {
107-
let author: ui::Author = commit.author().into();
108-
let commiter: ui::Author = commit.committer().into();
109-
authors.insert(author.clone());
110-
authors.insert(commiter);
111-
ui::UpstreamCommit {
112-
id: commit.id().to_gix(),
113-
message: commit.message().unwrap_or_default().into(),
114-
created_at: u128::try_from(commit.time().seconds()).unwrap_or(0) * 1000,
115-
author,
116-
}
64+
let commits = local_commits(repository, default_target.sha, branch_oid, &mut authors)?;
65+
let upstream_commits = upstream_oid
66+
.map(|upstream_oid| {
67+
upstream_commits(
68+
repository,
69+
upstream_oid,
70+
default_target.sha,
71+
branch_oid,
72+
&mut authors,
73+
)
11774
})
118-
.collect::<Vec<_>>();
75+
.transpose()?
76+
.unwrap_or_default();
11977

12078
Ok(ui::BranchDetails {
12179
name: branch_name.into(),
@@ -145,76 +103,169 @@ pub fn branch_details(
145103
/// This branch is assumed to not be in the workspace, but it will still be assumed to want to integrate with the workspace target reference if set.
146104
///
147105
/// ### Implementation
148-
#[allow(unused_variables)]
106+
///
107+
/// Note that the following fields aren't computed, but set to a bogus value - this would need to change once the UI makes use of them.
108+
/// They are set to values which should be clearly wrong.
109+
///
110+
/// - `push_status`
111+
/// - `is_conflicted`
149112
pub fn branch_details_v3(
150113
repo: &gix::Repository,
151114
name: &gix::refs::FullNameRef,
152115
meta: &impl RefMetadata,
153116
) -> anyhow::Result<ui::BranchDetails> {
154-
let integration_ref_name = workspace_data_of_default_workspace_branch(meta)?
117+
let integration_branch_name = workspace_data_of_default_workspace_branch(meta)?
155118
.context(
156119
"TODO: cannot run in non-workspace mode yet.\
157120
It would need a way to deal with limiting the commit traversal",
158121
)?
159122
.target_ref
160123
.context("TODO: a target to integrate with is currently needed for a workspace commit")?;
161-
let mut integration_ref = repo
162-
.find_reference(&integration_ref_name)
124+
let mut integration_branch = repo
125+
.find_reference(&integration_branch_name)
163126
.context("The branch to integrate with must be present")?;
164-
let integration_ref_target_id = integration_ref.peel_to_id_in_place()?;
127+
let integration_branch_id = integration_branch.peel_to_id_in_place()?;
165128

166129
let mut branch = repo.find_reference(name)?;
167-
let branch_target_id = branch.peel_to_id_in_place()?;
130+
let branch_id = branch.peel_to_id_in_place()?;
131+
132+
let cache = repo.commit_graph_if_enabled()?;
133+
let mut graph = repo.revision_graph(cache.as_ref());
168134

169135
let mut remote_tracking_branch = repo
170136
.branch_remote_tracking_ref_name(name, Direction::Fetch)
171137
.transpose()?
172138
.and_then(|remote_tracking_ref| repo.find_reference(remote_tracking_ref.as_ref()).ok());
173-
let remote_tracking_target_id = remote_tracking_branch
174-
.as_mut()
175-
.map(|remote_ref| remote_ref.peel_to_id_in_place())
176-
.transpose()?;
177-
let push_status = remote_tracking_target_id
178-
.map(|remote_target_id| {
179-
if remote_target_id == branch_target_id {
180-
PushStatus::NothingToPush
181-
} else {
182-
PushStatus::UnpushedCommits
183-
}
184-
})
185-
.unwrap_or(PushStatus::CompletelyUnpushed);
186139

187140
let meta = meta.branch(name)?;
188141
let meta: &but_core::ref_metadata::Branch = &meta;
189142

190143
let base_commit = {
191-
let cache = repo.commit_graph_if_enabled()?;
192-
let mut graph = repo.revision_graph(cache.as_ref());
193144
let merge_bases = repo.merge_bases_many_with_graph(
194-
branch_target_id,
195-
&[integration_ref_target_id.detach()],
145+
branch_id,
146+
&[integration_branch_id.detach()],
196147
&mut graph,
197148
)?;
198149
// TODO: have a test that shows why this must/should be last. Then maybe make it easy to do
199150
// the right thing whenever the mergebase with the integration branch is needed.
200-
merge_bases.last().map(|id| id.detach())
151+
merge_bases
152+
.last()
153+
.map(|id| id.detach())
154+
.with_context(|| format!("No merge-base found between {name} and the integration branch {integration_branch_name}", name = name.as_bstr()))?
155+
};
156+
157+
let mut authors = HashSet::new();
158+
let (commits, upstream_commits) = {
159+
let repo = git2::Repository::open(repo.path())?;
160+
161+
let commits = local_commits(
162+
&repo,
163+
integration_branch_id.to_git2(),
164+
branch_id.to_git2(),
165+
&mut authors,
166+
)?;
167+
168+
let upstream_commits = if let Some(remote_tracking_branch) = remote_tracking_branch.as_mut()
169+
{
170+
let remote_id = remote_tracking_branch.peel_to_id_in_place()?;
171+
upstream_commits(
172+
&repo,
173+
remote_id.to_git2(),
174+
integration_branch_id.to_git2(),
175+
branch_id.to_git2(),
176+
&mut authors,
177+
)?
178+
} else {
179+
Vec::new()
180+
};
181+
(commits, upstream_commits)
201182
};
202183

203-
todo!()
204-
// Ok(ui::BranchDetails {
205-
// name: name.as_bstr().into(),
206-
// remote_tracking_branch: remote_tracking_branch.map(|b| b.name().as_bstr().to_owned()),
207-
// description: meta.description.clone(),
208-
// pr_number: meta.review.pull_request,
209-
// review_id: meta.review.review_id.clone(),
210-
// base_commit: todo!(),
211-
// push_status,
212-
// last_updated_at: todo!(),
213-
// authors: todo!(),
214-
// is_conflicted: todo!(),
215-
// commits: todo!(),
216-
// upstream_commits: todo!(),
217-
// tip: branch_target_id.detach(),
218-
// is_remote_head: name.category() == Some(Category::RemoteBranch),
219-
// })
184+
Ok(ui::BranchDetails {
185+
name: name.as_bstr().into(),
186+
remote_tracking_branch: remote_tracking_branch.map(|b| b.name().as_bstr().to_owned()),
187+
description: meta.description.clone(),
188+
pr_number: meta.review.pull_request,
189+
review_id: meta.review.review_id.clone(),
190+
base_commit,
191+
last_updated_at: meta.ref_info.updated_at.map(|d| d.seconds as u128 * 1_000),
192+
authors: authors
193+
.into_iter()
194+
.sorted_by(|a, b| a.name.cmp(&b.name).then_with(|| a.email.cmp(&b.email)))
195+
.collect(),
196+
commits,
197+
upstream_commits,
198+
tip: branch_id.detach(),
199+
is_remote_head: name.category() == Some(Category::RemoteBranch),
200+
201+
// Should be unitialized, but this type is re-used in StackDetails where these are set so we can't indicate it.
202+
push_status: PushStatus::Integrated,
203+
is_conflicted: true,
204+
})
205+
}
206+
207+
/// Traverse all commits that are reachable from the first parent of `upstream_id`, but not in `integration_branch_id` nor in `branch_id`.
208+
/// While at it, collect the commiter and author of each commit into `authors`.
209+
fn upstream_commits(
210+
repository: &git2::Repository,
211+
upstream_id: git2::Oid,
212+
integration_branch_id: git2::Oid,
213+
branch_id: git2::Oid,
214+
authors: &mut HashSet<ui::Author>,
215+
) -> anyhow::Result<Vec<UpstreamCommit>> {
216+
let mut revwalk = repository.revwalk()?;
217+
revwalk.push(upstream_id)?;
218+
revwalk.hide(branch_id)?;
219+
revwalk.hide(integration_branch_id)?;
220+
revwalk.simplify_first_parent()?;
221+
Ok(revwalk
222+
.filter_map(Result::ok)
223+
.filter_map(|oid| repository.find_commit(oid).ok())
224+
.map(|commit| {
225+
let author: ui::Author = commit.author().into();
226+
let commiter: ui::Author = commit.committer().into();
227+
authors.insert(author.clone());
228+
authors.insert(commiter);
229+
ui::UpstreamCommit {
230+
id: commit.id().to_gix(),
231+
message: commit.message().unwrap_or_default().into(),
232+
created_at: u128::try_from(commit.time().seconds()).unwrap_or(0) * 1000,
233+
author,
234+
}
235+
})
236+
.collect())
237+
}
238+
239+
/// Traverse all commits that are reachable from the first parent of `branch_id`, but not in `integration_branch`, and store all
240+
/// commit authors and committers in `authors` while at it.
241+
fn local_commits(
242+
repository: &git2::Repository,
243+
integration_branch_id: git2::Oid,
244+
branch_id: git2::Oid,
245+
authors: &mut HashSet<ui::Author>,
246+
) -> anyhow::Result<Vec<ui::Commit>> {
247+
let mut revwalk = repository.revwalk()?;
248+
revwalk.push(branch_id)?;
249+
revwalk.hide(integration_branch_id)?;
250+
revwalk.simplify_first_parent()?;
251+
252+
Ok(revwalk
253+
.filter_map(Result::ok)
254+
.filter_map(|oid| repository.find_commit(oid).ok())
255+
.map(|commit| {
256+
let author: ui::Author = commit.author().into();
257+
let commiter: ui::Author = commit.committer().into();
258+
authors.insert(author.clone());
259+
authors.insert(commiter);
260+
ui::Commit {
261+
id: commit.id().to_gix(),
262+
parent_ids: commit.parent_ids().map(|id| id.to_gix()).collect(),
263+
message: commit.message().unwrap_or_default().into(),
264+
has_conflicts: false,
265+
state: CommitState::LocalAndRemote(commit.id().to_gix()),
266+
created_at: u128::try_from(commit.time().seconds()).unwrap_or(0) * 1000,
267+
author,
268+
}
269+
})
270+
.collect())
220271
}

crates/but-workspace/src/integrated.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
55
use anyhow::anyhow;
66
use anyhow::{Context, Result};
7-
use gitbutler_command_context::CommandContext;
87
use gitbutler_commit::commit_ext::CommitExt;
98
use gitbutler_oxidize::{GixRepositoryExt, git2_to_gix_object_id, gix_to_git2_oid};
109
use gitbutler_repo::{
@@ -24,22 +23,20 @@ pub(crate) struct IsCommitIntegrated<'repo, 'cache, 'graph> {
2423
}
2524

2625
impl<'repo, 'cache, 'graph> IsCommitIntegrated<'repo, 'cache, 'graph> {
26+
// TODO: use `gix_repo` for rev-walk once `hide()` is available.
2727
pub(crate) fn new(
28-
ctx: &'repo CommandContext,
28+
repo: &'repo git2::Repository,
2929
target: &Target,
3030
gix_repo: &'repo gix::Repository,
3131
graph: &'graph mut MergeBaseCommitGraph<'repo, 'cache>,
3232
) -> anyhow::Result<Self> {
33-
let remote_branch = ctx
34-
.repo()
33+
let remote_branch = repo
3534
.maybe_find_branch_by_refname(&target.branch.clone().into())?
3635
.ok_or(anyhow!("failed to get branch"))?;
3736
let remote_head = remote_branch.get().peel_to_commit()?;
38-
let upstream_tree_id = ctx.repo().find_commit(remote_head.id())?.tree_id();
37+
let upstream_tree_id = repo.find_commit(remote_head.id())?.tree_id();
3938

40-
let upstream_commits =
41-
ctx.repo()
42-
.log(remote_head.id(), LogUntil::Commit(target.sha), true)?;
39+
let upstream_commits = repo.log(remote_head.id(), LogUntil::Commit(target.sha), true)?;
4340
let upstream_change_ids = upstream_commits
4441
.iter()
4542
.filter_map(|commit| commit.change_id())

crates/but-workspace/src/stacks.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,15 @@ pub fn stack_details(
212212
) -> anyhow::Result<bool> {
213213
let upstream = branch.remote_reference(remote);
214214

215-
let reference = match ctx.repo().refname_to_id(&upstream) {
215+
let upstream_reference = match ctx.repo().refname_to_id(&upstream) {
216216
Ok(reference) => reference,
217217
Err(err) if err.code() == git2::ErrorCode::NotFound => return Ok(false),
218218
Err(other) => return Err(other).context("failed to find upstream reference"),
219219
};
220220

221221
let upstream_commit = ctx
222222
.repo()
223-
.find_commit(reference)
223+
.find_commit(upstream_reference)
224224
.context("failed to find upstream commit")?;
225225

226226
let branch_head = branch.head_oid(&ctx.gix_repo()?)?;
@@ -493,7 +493,7 @@ fn local_and_remote_commits(
493493
.context("failed to get default target")?;
494494
let cache = repo.commit_graph_if_enabled()?;
495495
let mut graph = repo.revision_graph(cache.as_ref());
496-
let mut check_commit = IsCommitIntegrated::new(ctx, &default_target, repo, &mut graph)?;
496+
let mut check_commit = IsCommitIntegrated::new(ctx.repo(), &default_target, repo, &mut graph)?;
497497

498498
let branch_commits = stack_branch.commits(ctx, stack)?;
499499
let mut local_and_remote: Vec<ui::Commit> = vec![];

0 commit comments

Comments
 (0)