Skip to content

Commit 68c8950

Browse files
committed
Support ranges using displayed file order
Use the same display order as the status command when resolving a start..end file range. The previous implementation searched an unordered file list (and fell back to committed files), which could omit files that appear between the endpoints in the UI. This change adds get_all_files_in_display_order() to build the file list in the exact order shown by status (grouping assignments by file, iterating assigned files per stack, then unassigned files) and uses it to compute ranges. This ensures ranges include all files shown between the endpoints in the status view.
1 parent 529ea68 commit 68c8950

File tree

1 file changed

+64
-17
lines changed

1 file changed

+64
-17
lines changed

crates/but/src/rub/mod.rs

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -221,35 +221,82 @@ fn parse_range(ctx: &mut CommandContext, source: &str) -> anyhow::Result<Vec<Cli
221221
let start_id = &start_matches[0];
222222
let end_id = &end_matches[0];
223223

224-
// Get all files from status to find the range
225-
let all_files = crate::status::all_files(ctx)?;
224+
// Get all files in display order (same order as shown in status)
225+
let all_files_in_order = get_all_files_in_display_order(ctx)?;
226226

227-
// Find the positions of start and end in the file list
228-
let start_pos = all_files.iter().position(|id| id == start_id);
229-
let end_pos = all_files.iter().position(|id| id == end_id);
227+
// Find the positions of start and end in the ordered file list
228+
let start_pos = all_files_in_order.iter().position(|id| id == start_id);
229+
let end_pos = all_files_in_order.iter().position(|id| id == end_id);
230230

231231
if let (Some(start_idx), Some(end_idx)) = (start_pos, end_pos) {
232232
if start_idx <= end_idx {
233-
return Ok(all_files[start_idx..=end_idx].to_vec());
233+
return Ok(all_files_in_order[start_idx..=end_idx].to_vec());
234234
} else {
235-
return Ok(all_files[end_idx..=start_idx].to_vec());
235+
return Ok(all_files_in_order[end_idx..=start_idx].to_vec());
236236
}
237237
}
238238

239-
// If not found in files, try committed files
240-
let all_committed_files = crate::status::all_committed_files(ctx)?;
241-
let start_pos = all_committed_files.iter().position(|id| id == start_id);
242-
let end_pos = all_committed_files.iter().position(|id| id == end_id);
239+
Err(anyhow::anyhow!("Could not find range from '{}' to '{}' in the displayed file list", start_str, end_str))
240+
}
243241

244-
if let (Some(start_idx), Some(end_idx)) = (start_pos, end_pos) {
245-
if start_idx <= end_idx {
246-
return Ok(all_committed_files[start_idx..=end_idx].to_vec());
247-
} else {
248-
return Ok(all_committed_files[end_idx..=start_idx].to_vec());
242+
fn get_all_files_in_display_order(ctx: &mut CommandContext) -> anyhow::Result<Vec<CliId>> {
243+
use std::collections::BTreeMap;
244+
use bstr::BString;
245+
use but_hunk_assignment::HunkAssignment;
246+
247+
let project = gitbutler_project::Project::from_path(&ctx.project().path)?;
248+
let changes =
249+
but_core::diff::ui::worktree_changes_by_worktree_dir(project.path.clone())?.changes;
250+
let (assignments, _) =
251+
but_hunk_assignment::assignments_with_fallback(ctx, false, Some(changes.clone()), None)?;
252+
253+
// Group assignments by file, same as status display logic
254+
let mut by_file: BTreeMap<BString, Vec<HunkAssignment>> = BTreeMap::new();
255+
for assignment in &assignments {
256+
by_file
257+
.entry(assignment.path_bytes.clone())
258+
.or_default()
259+
.push(assignment.clone());
260+
}
261+
262+
let mut all_files = Vec::new();
263+
264+
// First, get files assigned to branches (they appear first in status display)
265+
let stacks = crate::log::stacks(ctx)?;
266+
for stack in stacks {
267+
for (_stack_id, details_result) in stack.id.map(|id| (stack.id, crate::log::stack_details(ctx, id))) {
268+
if let Ok(details) = details_result {
269+
for branch in &details.branch_details {
270+
for (path_bytes, assignments) in &by_file {
271+
for assignment in assignments {
272+
if let Some(stack_id) = assignment.stack_id {
273+
if stack.id == Some(stack_id) {
274+
let file_id = CliId::file_from_assignment(assignment);
275+
if !all_files.contains(&file_id) {
276+
all_files.push(file_id);
277+
}
278+
}
279+
}
280+
}
281+
}
282+
}
283+
}
284+
}
285+
}
286+
287+
// Then add unassigned files (they appear last in status display)
288+
for (path_bytes, assignments) in &by_file {
289+
for assignment in assignments {
290+
if assignment.stack_id.is_none() {
291+
let file_id = CliId::file_from_assignment(assignment);
292+
if !all_files.contains(&file_id) {
293+
all_files.push(file_id);
294+
}
295+
}
249296
}
250297
}
251298

252-
Err(anyhow::anyhow!("Could not find range from '{}' to '{}' in the same file list", start_str, end_str))
299+
Ok(all_files)
253300
}
254301

255302
fn parse_list(ctx: &mut CommandContext, source: &str) -> anyhow::Result<Vec<CliId>> {

0 commit comments

Comments
 (0)