Skip to content

Commit 075ca2d

Browse files
Merge pull request #12655 from gitbutlerapp/jt/stageprefix
stage, unstage: support path prefixes
2 parents 832230b + 9181808 commit 075ca2d

3 files changed

Lines changed: 50 additions & 6 deletions

File tree

crates/but/src/command/legacy/rub/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ pub(crate) fn handle_amend(
987987
}
988988

989989
/// Handler for `but stage <file_or_hunk> <branch>` - runs `but rub <file_or_hunk> <branch>`
990-
/// Validates that file_or_hunk is uncommitted and branch is a branch.
990+
/// Validates that file_or_hunk is uncommitted or a path prefix, and that branch is a branch.
991991
pub(crate) fn handle_stage(
992992
ctx: &mut Context,
993993
out: &mut OutputChannel,
@@ -998,10 +998,10 @@ pub(crate) fn handle_stage(
998998
let files = parse_sources_with_disambiguation(ctx, &id_map, file_or_hunk_str, out)?;
999999
let branch = resolve_single_id(ctx, &id_map, branch_str, "Branch", out)?;
10001000

1001-
// Validate that all files are uncommitted
1001+
// Validate that all files are uncommitted or a path prefix
10021002
for file in &files {
10031003
match file {
1004-
CliId::Uncommitted(_) => {
1004+
CliId::Uncommitted(_) | CliId::PathPrefix { .. } => {
10051005
// Valid type for stage
10061006
}
10071007
_ => {
@@ -1142,7 +1142,8 @@ pub(crate) fn handle_stage_tui(
11421142
}
11431143

11441144
/// Handler for `but unstage <file_or_hunk> [branch]` - runs `but rub <file_or_hunk> zz`
1145-
/// Validates that file_or_hunk is uncommitted. Optionally validates it's assigned to the specified branch.
1145+
/// Validates that file_or_hunk is uncommitted or a path prefix. Optionally
1146+
/// validates it's assigned to the specified branch.
11461147
pub(crate) fn handle_unstage(
11471148
ctx: &mut Context,
11481149
out: &mut OutputChannel,
@@ -1152,10 +1153,10 @@ pub(crate) fn handle_unstage(
11521153
let id_map = IdMap::new_from_context(ctx, None)?;
11531154
let files = parse_sources_with_disambiguation(ctx, &id_map, file_or_hunk_str, out)?;
11541155

1155-
// Validate that all files are uncommitted
1156+
// Validate that all files are uncommitted or a path prefix
11561157
for file in &files {
11571158
match file {
1158-
CliId::Uncommitted(_) => {
1159+
CliId::Uncommitted(_) | CliId::PathPrefix { .. } => {
11591160
// Valid type for unstage
11601161
}
11611162
_ => {

crates/but/src/id/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ impl<'a> Node<'a> for &'a StackWithId {
365365
id_map: &'a IdMap,
366366
_changes_in_commit_fn: &mut ChangesInCommitFn<'a>,
367367
) -> anyhow::Result<Vec<Box<dyn Node<'a> + 'a>>> {
368+
// Parse known suffixes.
369+
if element.ends_with('/') {
370+
return Ok(id_map.parse_uncommitted_path_prefix(self.id, element));
371+
}
368372
for uncommitted_file in id_map.uncommitted_files.values() {
369373
let hunk_assignment = uncommitted_file.hunk_assignments.first();
370374
// TODO once the set of allowed CLI IDs is determined and the

crates/but/tests/but/command/rub.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,23 @@ fn stage_command() -> anyhow::Result<()> {
568568
Ok(())
569569
}
570570

571+
#[test]
572+
fn stage_command_path_prefix() -> anyhow::Result<()> {
573+
let env = Sandbox::init_scenario_with_target_and_default_settings("two-stacks")?;
574+
575+
env.setup_metadata(&["A", "B"])?;
576+
env.file("path/a.txt", "text\n");
577+
env.but("stage path/ A")
578+
.assert()
579+
.success()
580+
.stderr_eq(snapbox::str![])
581+
.stdout_eq(snapbox::str![[r#"
582+
Staged hunk(s) → [A].
583+
584+
"#]]);
585+
Ok(())
586+
}
587+
571588
#[test]
572589
fn unstage_command() -> anyhow::Result<()> {
573590
let env = Sandbox::init_scenario_with_target_and_default_settings("two-stacks")?;
@@ -628,6 +645,28 @@ fn unstage_command() -> anyhow::Result<()> {
628645
Ok(())
629646
}
630647

648+
#[test]
649+
fn unstage_command_path_prefix() -> anyhow::Result<()> {
650+
let env = Sandbox::init_scenario_with_target_and_default_settings("two-stacks")?;
651+
652+
env.setup_metadata(&["A", "B"])?;
653+
env.file("path/a.txt", "text\n");
654+
655+
// First stage the file to A
656+
env.but("stage path/a.txt A").assert().success();
657+
658+
// Now unstage it, giving a path prefix
659+
env.but("unstage A@{stack}:path/ A")
660+
.assert()
661+
.success()
662+
.stderr_eq(snapbox::str![])
663+
.stdout_eq(snapbox::str![[r#"
664+
Unstaged hunk(s)
665+
666+
"#]]);
667+
Ok(())
668+
}
669+
631670
#[test]
632671
fn unstage_command_with_branch() -> anyhow::Result<()> {
633672
let env = Sandbox::init_scenario_with_target_and_default_settings("two-stacks")?;

0 commit comments

Comments
 (0)