Skip to content

Commit 4cdc339

Browse files
authored
Merge pull request #9450 from Byron/graph-segmentation
integration-checks (testing)
2 parents 75a74bc + 157844c commit 4cdc339

File tree

6 files changed

+389
-42
lines changed

6 files changed

+389
-42
lines changed

crates/but-workspace/src/changeset.rs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,13 @@ impl RefInfo {
148148
else {
149149
continue;
150150
};
151-
let identity_of_tip_to_base = Identifier::ChangesetId(id_for_tree_diff(
152-
repo,
153-
base_commit_id,
154-
topmost_unintegrated_commit.tree_id,
155-
)?);
151+
let Some(changeset_id) =
152+
id_for_tree_diff(repo, base_commit_id, topmost_unintegrated_commit.tree_id)?
153+
else {
154+
continue;
155+
};
156156

157+
let identity_of_tip_to_base = Identifier::ChangesetId(changeset_id);
157158
let Some(squashed_commit_id) = upstream_lut.get(&identity_of_tip_to_base).cloned()
158159
else {
159160
continue;
@@ -209,15 +210,17 @@ impl PushStatus {
209210
let first_commit = commits.first();
210211
let everything_integrated_locally =
211212
first_commit.is_some_and(|c| matches!(c.relation, LocalCommitRelation::Integrated(_)));
213+
let first_commit_is_local =
214+
first_commit.is_some_and(|c| matches!(c.relation, LocalCommitRelation::LocalOnly));
212215
if everything_integrated_locally {
213216
PushStatus::Integrated
214-
} else if commits
215-
.iter()
216-
.any(|c| matches!(c.relation, LocalCommitRelation::LocalAndRemote(id) if c.id != id))
217-
{
217+
} else if commits.iter().any(|c| {
218+
matches!(c.relation, LocalCommitRelation::LocalAndRemote(id) if c.id != id)
219+
|| (first_commit_is_local
220+
&& matches!(c.relation, LocalCommitRelation::Integrated(_)))
221+
}) {
218222
PushStatus::UnpushedCommitsRequiringForce
219-
} else if first_commit.is_some_and(|c| matches!(c.relation, LocalCommitRelation::LocalOnly))
220-
{
223+
} else if first_commit_is_local {
221224
if remote_has_commits {
222225
PushStatus::UnpushedCommitsRequiringForce
223226
} else {
@@ -236,11 +239,10 @@ fn changeset_identifier(
236239
let Some(commit) = commit else {
237240
return Ok(None);
238241
};
239-
Ok(Some(Identifier::ChangesetId(id_for_tree_diff(
240-
repo,
241-
commit.parent_ids.first().cloned(),
242-
commit.id,
243-
)?)))
242+
Ok(
243+
id_for_tree_diff(repo, commit.parent_ids.first().cloned(), commit.id)?
244+
.map(Identifier::ChangesetId),
245+
)
244246
}
245247

246248
fn lookup_similar<'a>(
@@ -276,6 +278,11 @@ fn create_similarity_lut(
276278
}
277279
match similarity_lut.entry(k) {
278280
Entry::Occupied(ambiguous) => {
281+
if matches!(ambiguous.key(), Identifier::ChangesetId(_)) {
282+
// the most expensive option should never be ambiguous (which can happen with merges),
283+
// so just keep the (typically top-most/first) commit with a changeset ID instead.
284+
return;
285+
}
279286
ambiguous_commits.insert(ambiguous.key().clone());
280287
ambiguous.remove();
281288
}
@@ -297,18 +304,12 @@ fn create_similarity_lut(
297304
commit.id,
298305
);
299306
if expensive {
300-
// Just like with rename-tracking, let's not use 'empty' commits as specific unique value.
301-
if commit.tree_id.is_empty_tree() {
307+
let Some(changeset_id) =
308+
id_for_tree_diff(repo, commit.parent_ids.first().cloned(), commit.id)?
309+
else {
302310
continue;
303-
}
304-
insert_or_expell_ambiguous(
305-
Identifier::ChangesetId(id_for_tree_diff(
306-
repo,
307-
commit.parent_ids.first().cloned(),
308-
commit.id,
309-
)?),
310-
commit.id,
311-
);
311+
};
312+
insert_or_expell_ambiguous(Identifier::ChangesetId(changeset_id), commit.id);
312313
}
313314
}
314315
}
@@ -322,10 +323,21 @@ fn id_for_tree_diff(
322323
repo: &gix::Repository,
323324
lhs: Option<gix::ObjectId>,
324325
rhs: gix::ObjectId,
325-
) -> anyhow::Result<ChangesetID> {
326+
) -> anyhow::Result<Option<ChangesetID>> {
326327
let lhs_tree = lhs.map(|id| id_to_tree(repo, id)).transpose()?;
327328
let rhs_tree = id_to_tree(repo, rhs)?;
328329

330+
let no_changes = lhs_tree
331+
.as_ref()
332+
.map_or(rhs_tree.id.is_empty_tree(), |lhs_tree| {
333+
lhs_tree.id == rhs_tree.id
334+
});
335+
if no_changes {
336+
return Ok(None);
337+
}
338+
// TODO(perf): use plumbing directly to avoid resource-cache overhead.
339+
// consider parallelization
340+
// really needs caching to be practical, in-memory might suffice for now.
329341
let changes = repo.diff_tree_to_tree(
330342
lhs_tree.as_ref(),
331343
&rhs_tree,
@@ -335,6 +347,9 @@ fn id_for_tree_diff(
335347
// but would cost time, making it useless.
336348
.track_rewrites(None),
337349
)?;
350+
if changes.is_empty() {
351+
return Ok(None);
352+
}
338353

339354
let mut hash = gix::hash::hasher(gix::hash::Kind::Sha1);
340355
hash.update(&[CURRENT_VERSION as u8]);
@@ -376,7 +391,7 @@ fn id_for_tree_diff(
376391
}
377392
}
378393
}
379-
Ok(hash.try_finalize()?)
394+
Ok(Some(hash.try_finalize()?))
380395
}
381396

382397
// TODO: use `peel_to_tree()` once special conflict markers aren't needed anymore.

crates/but-workspace/tests/fixtures/scenario/journey02.sh

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ We change the name of the first commit and also need the similarity to be detect
1616
git checkout -b A
1717
git branch soon-A-remote
1818
echo A-new >file && git add file && git commit -m A1
19-
commit A2
19+
echo A-new >other && git add other && git commit -m A2
2020
git checkout soon-A-remote
2121
echo A-new >file && git add file && git commit -m "A1 (same but different)"
2222
setup_remote_tracking soon-A-remote A "move"
@@ -25,6 +25,16 @@ We change the name of the first commit and also need the similarity to be detect
2525
create_workspace_commit_once A
2626
)
2727

28+
cp -R 01-one-rewritten-one-local-after-push 01.5-one-rewritten-one-local-after-push-merge
29+
(cd 01.5-one-rewritten-one-local-after-push-merge
30+
echo "On the remote, a rewritten/rebased commit we have locally is merged back into target." >.git/description
31+
git checkout -b soon-main-remote origin/main
32+
git merge --no-ff origin/A -m "merge origin/A"
33+
setup_remote_tracking soon-main-remote main "move"
34+
35+
git checkout gitbutler/workspace
36+
)
37+
2838
git init 02-diverged-remote
2939
(cd 02-diverged-remote
3040
echo "A setup that demands for a force-push
@@ -43,6 +53,18 @@ The tip of the local branch isn't in the ancestry of the remote anymore." >.git/
4353
create_workspace_commit_once A
4454
)
4555

56+
cp -R 02-diverged-remote 02.5-diverged-remote-merge
57+
(cd 02.5-diverged-remote-merge
58+
echo "A remote sharing a commit with a stack and its own commit gets merged.
59+
60+
We'd not want to see the remote unique commit anymore as it's also considered integrated." >.git/description
61+
git checkout main
62+
git merge --no-ff origin/A
63+
git rev-parse @ >.git/refs/remotes/origin/main
64+
65+
git checkout gitbutler/workspace
66+
)
67+
4668
git init 03-remote-one-behind
4769
(cd 03-remote-one-behind
4870
echo "A can be pushed as it has local, unpushed commits" >.git/description
@@ -55,6 +77,16 @@ git init 03-remote-one-behind
5577
create_workspace_commit_once A
5678
)
5779

80+
cp -R 03-remote-one-behind 03.5-remote-one-behind-merge-no-ff
81+
(cd 03.5-remote-one-behind-merge-no-ff
82+
echo "Remote origin/A is merged back (with forceful merge commit) while there are still local commits." >.git/description
83+
git checkout main
84+
git merge --no-ff origin/A
85+
git rev-parse @ >.git/refs/remotes/origin/main
86+
87+
git checkout gitbutler/workspace
88+
)
89+
5890
git init 04-remote-one-ahead-ff
5991
(cd 04-remote-one-ahead-ff
6092
echo "There are no unpushed local commits, the remote is one ahead (FF)" >.git/description
@@ -68,3 +100,13 @@ git init 04-remote-one-ahead-ff
68100
git checkout A
69101
create_workspace_commit_once A
70102
)
103+
104+
cp -R 04-remote-one-ahead-ff 04.5-remote-one-ahead-ff-merge
105+
(cd 04.5-remote-one-ahead-ff-merge
106+
echo "Remote origin/A is merged back (fast-forward), bringing all into the target branch" >.git/description
107+
git checkout main
108+
git merge origin/A
109+
git rev-parse @ >.git/refs/remotes/origin/main
110+
111+
git checkout gitbutler/workspace
112+
)

crates/but-workspace/tests/workspace/ref_info/with_workspace_commit/journey/exhaustive_with_squash_merges.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,8 @@ A new multi-segment stack is created without remote and squash merged locally.
806806
* fafd9d0 init
807807
");
808808

809+
// TODO: if the user now puts another dependent branch, it's breaking down in many ways.
810+
// We should be smarter about that and flesh out additional steps on top.
809811
add_stack_with_segments(&mut meta, 0, "S1", StackState::InWorkspace, &[]);
810812
let info = but_workspace::head_info(&repo, &*meta, standard_options());
811813
insta::assert_debug_snapshot!(info, @r#"

0 commit comments

Comments
 (0)