Skip to content

Commit 56bd00a

Browse files
committed
Support for hunk-selections when committing and amending
1 parent ba9678f commit 56bd00a

File tree

8 files changed

+92
-29
lines changed

8 files changed

+92
-29
lines changed

crates/but-workspace/src/commit_engine/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ pub enum RejectionReason {
205205
/// This can happen if the target tree has an entry that isn't of the same type as the source worktree changes.
206206
UnsupportedTreeEntry,
207207
/// The DiffSpec points to an actual change, or a subset of that change using a file path and optionally hunks into that file.
208-
/// However, the actual change wasn't found or didn't match up in turn of hunks.
208+
/// However, at least one hunk was not fully contained..
209209
MissingDiffSpecAssociation,
210210
}
211211

crates/but-workspace/src/commit_engine/tree.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@ fn apply_worktree_changes<'repo>(
294294
for selected_hunk in &change_request.hunk_headers {
295295
if !worktree_hunks.contains(selected_hunk)
296296
|| has_zero_based_line_numbers(selected_hunk)
297+
|| !worktree_hunks.iter().any(|wth| {
298+
(wth.old_range() == selected_hunk.old_range()
299+
&& wth.new_range().contains(selected_hunk.new_range()))
300+
|| (wth.new_range() == selected_hunk.new_range()
301+
&& wth.old_range().contains(selected_hunk.old_range()))
302+
})
297303
{
298304
into_err_spec(possible_change, RejectionReason::MissingDiffSpecAssociation);
299305
// TODO: only skip this one hunk, but collect skipped hunks into a new err-spec.

crates/but-workspace/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//! - Git doesn't have a notion of such a branch.
2222
//! * **DiffSpec**
2323
//! - A type that identifies changes, either as whole file, or as hunks in the file.
24-
//! - It doesn't specify if the change is in a commit, or in the worktree, so that information must provided separately.
24+
//! - It doesn't specify if the change is in a commit, or in the worktree, so that information must be provided separately.
2525
2626
use anyhow::{Context, Result};
2727
use author::Author;

crates/but-workspace/tests/workspace/commit_engine/amend_commit.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ fn new_file_and_deletion_onto_merge_commit() -> anyhow::Result<()> {
8989
let (repo, _tmp) = writable_scenario("merge-with-two-branches-line-offset");
9090
// Rewrite the entire file, which is fine as we rewrite/amend the base-commit itself.
9191
write_sequence(&repo, "new-file", [(10, None)])?;
92-
std::fs::remove_file(repo.workdir().expect("non-bare").join("file"))?;
92+
std::fs::remove_file(repo.workdir_path("file").expect("non-bare"))?;
9393

9494
let outcome = commit_whole_files_and_all_hunks_from_workspace(
9595
&repo,
@@ -109,7 +109,7 @@ fn make_a_file_empty() -> anyhow::Result<()> {
109109

110110
let (repo, _tmp) = writable_scenario("merge-with-two-branches-line-offset");
111111
// Empty the file
112-
std::fs::write(repo.workdir().expect("non-bare").join("file"), "")?;
112+
std::fs::write(repo.workdir_path("file").expect("non-bare"), "")?;
113113
let outcome = commit_whole_files_and_all_hunks_from_workspace(
114114
&repo,
115115
Destination::AmendCommit(repo.rev_parse_single("merge")?.detach()),
@@ -129,7 +129,7 @@ fn new_file_and_deletion_onto_merge_commit_with_hunks() -> anyhow::Result<()> {
129129
let (repo, _tmp) = writable_scenario("merge-with-two-branches-line-offset");
130130
// Rewrite the entire file, which is fine as we rewrite/amend the base-commit itself.
131131
write_sequence(&repo, "new-file", [(10, None)])?;
132-
std::fs::remove_file(repo.workdir().expect("non-bare").join("file"))?;
132+
std::fs::remove_file(repo.workdir_path("file").expect("non-bare"))?;
133133

134134
let outcome = but_workspace::commit_engine::create_commit(
135135
&repo,

crates/but-workspace/tests/workspace/commit_engine/new_commit.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::utils::{
22
CONTEXT_LINES, commit_from_outcome, commit_whole_files_and_all_hunks_from_workspace,
3-
read_only_in_memory_scenario, to_change_specs_all_hunks,
3+
hunk_header, read_only_in_memory_scenario, to_change_specs_all_hunks,
44
to_change_specs_all_hunks_with_context_lines, to_change_specs_whole_file, visualize_tree,
55
writable_scenario, writable_scenario_with_ssh_key, write_sequence,
66
};
@@ -56,7 +56,7 @@ fn from_unborn_head() -> anyhow::Result<()> {
5656
"#);
5757

5858
std::fs::write(
59-
repo.workdir().expect("non-bare").join("new-untracked"),
59+
repo.workdir_path("new-untracked").expect("non-bare"),
6060
"new-content",
6161
)?;
6262
let outcome = commit_whole_files_and_all_hunks_from_workspace(
@@ -91,6 +91,67 @@ fn from_unborn_head() -> anyhow::Result<()> {
9191
Ok(())
9292
}
9393

94+
#[test]
95+
fn from_unborn_head_with_selection() -> anyhow::Result<()> {
96+
assure_stable_env();
97+
98+
let (repo, _tmp) = writable_scenario("unborn-untracked");
99+
let destination = Destination::NewCommit {
100+
parent_commit_id: None,
101+
message: "the commit with selection".into(),
102+
stack_segment: None,
103+
};
104+
let outcome = but_workspace::commit_engine::create_commit(
105+
&repo,
106+
destination,
107+
None,
108+
vec![DiffSpec {
109+
previous_path: None,
110+
path: "not-yet-tracked".into(),
111+
hunk_headers: vec![hunk_header("-1,0", "+1,1")],
112+
}],
113+
CONTEXT_LINES,
114+
)?;
115+
116+
let tree = visualize_tree(&repo, &outcome)?;
117+
insta::assert_snapshot!(tree, @r#"
118+
861d6e2
119+
└── not-yet-tracked:100644:d95f3ad "content\n"
120+
"#);
121+
122+
write_sequence(&repo, "also-untracked", [(1, 10)])?;
123+
let destination = Destination::NewCommit {
124+
parent_commit_id: outcome.new_commit,
125+
message: "the commit with sub-selection".into(),
126+
stack_segment: None,
127+
};
128+
let outcome = but_workspace::commit_engine::create_commit(
129+
&repo,
130+
destination,
131+
None,
132+
vec![DiffSpec {
133+
previous_path: None,
134+
path: "also-untracked".into(),
135+
// take the first 5 lines, instead of 10
136+
hunk_headers: vec![hunk_header("-1,0", "+4,3")],
137+
}],
138+
CONTEXT_LINES,
139+
)?;
140+
assert_eq!(
141+
outcome.rejected_specs.len(),
142+
0,
143+
"hunk-ranges can also be applied"
144+
);
145+
146+
let tree = visualize_tree(&repo, &outcome)?;
147+
// TODO: figure out why this is not using the selected range, and why it's lacking the file from the previous tree.
148+
insta::assert_snapshot!(tree, @r#"
149+
14bffe1
150+
└── also-untracked:100644:f00c965 "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"
151+
"#);
152+
Ok(())
153+
}
154+
94155
#[test]
95156
#[cfg(unix)]
96157
fn from_unborn_head_all_file_types() -> anyhow::Result<()> {
@@ -626,7 +687,7 @@ fn unborn_untracked_worktree_filters_are_applied_to_whole_files() -> anyhow::Res
626687
"#);
627688

628689
std::fs::write(
629-
repo.workdir().expect("non-bare").join("new-untracked"),
690+
repo.workdir_path("new-untracked").expect("non-bare"),
630691
"one\r\ntwo\r\n",
631692
)?;
632693
let outcome = commit_whole_files_and_all_hunks_from_workspace(

crates/but-workspace/tests/workspace/commit_engine/refs_update.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ mod utils {
11391139
rela_path: &str,
11401140
content: &str,
11411141
) -> std::io::Result<()> {
1142-
let work_dir = repo.workdir().expect("non-bare");
1143-
std::fs::write(work_dir.join(rela_path), content)
1142+
let work_path = repo.workdir_path(rela_path).expect("non-bare");
1143+
std::fs::write(work_path, content)
11441144
}
11451145
}

crates/but-workspace/tests/workspace/discard/hunk.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use crate::discard::hunk::util::{
2-
changed_file_in_worktree_with_hunks, hunk_header, previous_change_text,
3-
};
1+
use crate::discard::hunk::util::{changed_file_in_worktree_with_hunks, previous_change_text};
42
use crate::utils::{
5-
CONTEXT_LINES, read_only_in_memory_scenario, to_change_specs_all_hunks, visualize_index,
6-
writable_scenario,
3+
CONTEXT_LINES, hunk_header, read_only_in_memory_scenario, to_change_specs_all_hunks,
4+
visualize_index, writable_scenario,
75
};
86
use bstr::{BString, ByteSlice};
97
use but_core::UnifiedDiff;
@@ -829,7 +827,6 @@ mod util {
829827
use bstr::BString;
830828
use but_core::unified_diff::DiffHunk;
831829
use but_core::{TreeChange, UnifiedDiff};
832-
use but_workspace::commit_engine::HunkHeader;
833830
use gix::prelude::ObjectIdExt;
834831

835832
pub fn previous_change_text(
@@ -849,18 +846,6 @@ mod util {
849846
.into())
850847
}
851848

852-
/// Choose a slightly more obvious, yet easy to type syntax than a function with 4 parameters.
853-
pub fn hunk_header(old: &str, new: &str) -> HunkHeader {
854-
let ((old_start, old_lines), (new_start, new_lines)) =
855-
but_testsupport::hunk_header(old, new);
856-
HunkHeader {
857-
old_start,
858-
old_lines,
859-
new_start,
860-
new_lines,
861-
}
862-
}
863-
864849
pub fn changed_file_in_worktree_with_hunks(
865850
repo: &gix::Repository,
866851
filename: &str,

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bstr::ByteSlice;
22
use but_core::TreeStatus;
33
use but_testsupport::gix_testtools;
44
use but_testsupport::gix_testtools::{Creation, tempfile};
5-
use but_workspace::commit_engine::{Destination, DiffSpec};
5+
use but_workspace::commit_engine::{Destination, DiffSpec, HunkHeader};
66
use gix::prelude::ObjectIdExt;
77

88
pub const CONTEXT_LINES: u32 = 0;
@@ -278,3 +278,14 @@ pub fn write_local_config(repo: &gix::Repository) -> anyhow::Result<()> {
278278
)?;
279279
Ok(())
280280
}
281+
282+
/// Choose a slightly more obvious, yet easy to type syntax than a function with 4 parameters.
283+
pub fn hunk_header(old: &str, new: &str) -> HunkHeader {
284+
let ((old_start, old_lines), (new_start, new_lines)) = but_testsupport::hunk_header(old, new);
285+
HunkHeader {
286+
old_start,
287+
old_lines,
288+
new_start,
289+
new_lines,
290+
}
291+
}

0 commit comments

Comments
 (0)