Skip to content

Commit 18f80c0

Browse files
committed
Workspace merges can now deal with conflicting commits.
1 parent 88db615 commit 18f80c0

File tree

4 files changed

+102
-11
lines changed

4 files changed

+102
-11
lines changed

crates/but-testsupport/src/lib.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! Utilities for testing.
22
#![deny(missing_docs)]
33

4-
use std::{collections::HashMap, path::Path};
5-
64
use gix::{
75
Repository,
86
bstr::{BStr, ByteSlice},
97
config::tree::Key,
108
};
119
pub use gix_testtools;
10+
use std::io::Write;
11+
use std::{collections::HashMap, path::Path};
1212

1313
mod in_memory_meta;
1414
pub use in_memory_meta::{InMemoryRefMetadata, InMemoryRefMetadataHandle, StackState};
@@ -31,14 +31,41 @@ pub fn hunk_header(old: &str, new: &str) -> ((u32, u32), (u32, u32)) {
3131
(parse_header(old), parse_header(new))
3232
}
3333

34-
/// While `gix` can't (or can't conveniently) do everything, let's make using `git` easier.
34+
/// While `gix` can't (or can't conveniently) do everything, let's make using `git` easier,
35+
/// by producing a command that is anchored to the `gix` repository.
36+
/// Call [`run()`](CommandExt::run) when done configuring its arguments.
3537
pub fn git(repo: &gix::Repository) -> std::process::Command {
3638
let mut cmd = std::process::Command::new(gix::path::env::exe_invocation());
3739
cmd.current_dir(repo.workdir().expect("non-bare"));
3840
isolate_env_std_cmd(&mut cmd);
3941
cmd
4042
}
4143

44+
/// Run the given `script` in bash, with the `cwd` set to the `repo` worktree.
45+
/// Panic if the script fails.
46+
pub fn invoke_bash(script: &str, repo: &gix::Repository) {
47+
let mut cmd = std::process::Command::new("bash");
48+
cmd.current_dir(repo.workdir().expect("non-bare"));
49+
isolate_env_std_cmd(&mut cmd);
50+
cmd.stdin(std::process::Stdio::piped())
51+
.stdout(std::process::Stdio::piped())
52+
.stderr(std::process::Stdio::piped());
53+
let mut child = cmd.spawn().expect("bash can be spawned");
54+
child
55+
.stdin
56+
.as_mut()
57+
.unwrap()
58+
.write_all(script.as_bytes())
59+
.expect("failed to write to stdin");
60+
let out = child.wait_with_output().expect("can wait for output");
61+
assert!(
62+
out.status.success(),
63+
"{cmd:?} failed: {}\n\n{}",
64+
out.stdout.as_bstr(),
65+
out.stderr.as_bstr()
66+
);
67+
}
68+
4269
/// Open a repository at `path` suitable for testing which means that:
4370
///
4471
/// * author and committer are configured, as well as a stable time.

crates/but-workspace/src/commit.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,8 @@ pub mod merge {
365365
}
366366

367367
fn peel_to_tree(commit: gix::Id) -> anyhow::Result<gix::ObjectId> {
368-
Ok(commit.object()?.peel_to_tree()?.id)
368+
let commit = but_core::Commit::from_id(commit)?;
369+
Ok(commit.tree_id_or_auto_resolution()?.detach())
369370
}
370371
}
371372

crates/but-workspace/tests/fixtures/scenario/with-conflict.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
set -eu -o pipefail
44

55
git init
6-
# A repository with a normal and an artificial conflicting commit
6+
echo "A repository with a normal and an artificial conflicting commit" >.git/description
7+
78
echo content >file && git add . && git commit -m "init"
89
git tag normal
910

crates/but-workspace/tests/workspace/commit.rs

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
mod from_new_merge_with_metadata {
2+
use crate::ref_info::with_workspace_commit::utils::{
3+
named_read_only_in_memory_scenario, named_writable_scenario_with_description_and_graph,
4+
};
25
use bstr::ByteSlice;
3-
use but_graph::init::Options;
6+
use but_graph::init::{Options, Overlay};
47
use but_testsupport::{visualize_commit_graph_all, visualize_tree};
58
use but_workspace::WorkspaceCommit;
69
use gix::prelude::ObjectIdExt;
7-
8-
use crate::ref_info::with_workspace_commit::utils::named_read_only_in_memory_scenario;
10+
use gix::refs::Target;
911

1012
#[test]
1113
fn without_conflict_journey() -> anyhow::Result<()> {
@@ -240,9 +242,69 @@ mod from_new_merge_with_metadata {
240242

241243
#[test]
242244
fn with_conflict_commits() -> anyhow::Result<()> {
243-
let (repo, mut meta) = named_read_only_in_memory_scenario("with-conflict", "")?;
244-
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r"");
245-
// but_testsupport::git(&repo)
245+
let (_tmp, mut graph, repo, mut meta, _description) =
246+
named_writable_scenario_with_description_and_graph("with-conflict", |_| {})?;
247+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r"
248+
* 8450331 (HEAD -> main, tag: conflicted) GitButler WIP Commit
249+
* a047f81 (tag: normal) init
250+
");
251+
but_testsupport::invoke_bash(
252+
r#"
253+
git branch tip-conflicted
254+
git reset --hard @~1
255+
git checkout -b unrelated
256+
touch unrelated-file && git add unrelated-file && git commit -m "unrelated"
257+
"#,
258+
&repo,
259+
);
260+
insta::assert_snapshot!(visualize_commit_graph_all(&repo)?, @r"
261+
* 8450331 (tag: conflicted, tip-conflicted) GitButler WIP Commit
262+
| * 8ab1c4d (HEAD -> unrelated) unrelated
263+
|/
264+
* a047f81 (tag: normal, main) init
265+
");
266+
267+
let stacks = ["tip-conflicted", "unrelated"];
268+
add_stacks(&mut meta, stacks);
269+
270+
graph = graph.redo_traversal_with_overlay(
271+
&repo,
272+
&meta,
273+
Overlay::default().with_references_if_new([
274+
repo.find_reference("unrelated")?.inner,
275+
// The workspace ref is needed so the workspace and its stacks are iterated as well.
276+
// Algorithms which work with simulation also have to be mindful about this.
277+
gix::refs::Reference {
278+
name: "refs/heads/gitbutler/workspace".try_into()?,
279+
target: Target::Object(repo.rev_parse_single("main")?.detach()),
280+
peeled: None,
281+
},
282+
]),
283+
)?;
284+
285+
let out =
286+
WorkspaceCommit::from_new_merge_with_metadata(&to_stacks(stacks), &graph, &repo, None)?;
287+
insta::assert_debug_snapshot!(out, @r#"
288+
Outcome {
289+
workspace_commit_id: Sha1(ed5a3012c6a4798404f5b8586588d0ede0664683),
290+
stacks: [
291+
Stack { tip: 8450331, name: "tip-conflicted" },
292+
Stack { tip: 8ab1c4d, name: "unrelated" },
293+
],
294+
missing_stacks: [],
295+
conflicting_stacks: [],
296+
}
297+
"#);
298+
299+
// There it auto-resolves the commit to not merge the actual tree structure.
300+
insta::assert_snapshot!(visualize_tree(
301+
out.workspace_commit_id.attach(&repo).object()?.into_commit().tree_id()?
302+
), @r#"
303+
8882acc
304+
├── file:100644:e69de29 ""
305+
└── unrelated-file:100644:e69de29 ""
306+
"#);
307+
246308
Ok(())
247309
}
248310

0 commit comments

Comments
 (0)