Skip to content

Commit d90e553

Browse files
committed
Support 'rub uncommit' for files in commits
Add plumbing to allow uncommitting files from commits via the `rub` subcommand so the UI's "uncommit" action (alt-click) can call into the CLI.
1 parent f9ab4d3 commit d90e553

File tree

5 files changed

+94
-12
lines changed

5 files changed

+94
-12
lines changed

crates/but/src/id/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ impl CliId {
8787
.into_iter()
8888
.filter(|id| id.matches_prefix(s))
8989
.for_each(|id| everything.push(id));
90+
crate::status::all_committed_files(ctx)?
91+
.into_iter()
92+
.filter(|id| id.matches_prefix(s))
93+
.for_each(|id| everything.push(id));
9094
crate::status::all_branches(ctx)?
9195
.into_iter()
9296
.filter(|id| id.matches_prefix(s))
@@ -117,6 +121,10 @@ impl CliId {
117121
.into_iter()
118122
.filter(|id| id.matches(s))
119123
.for_each(|id| everything.push(id));
124+
crate::status::all_committed_files(ctx)?
125+
.into_iter()
126+
.filter(|id| id.matches(s))
127+
.for_each(|id| everything.push(id));
120128
crate::status::all_branches(ctx)?
121129
.into_iter()
122130
.filter(|id| id.matches(s))

crates/but/src/main.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,25 +182,28 @@ where
182182
fn print_grouped_help() {
183183
use clap::CommandFactory;
184184
use std::collections::HashSet;
185-
185+
186186
let cmd = Args::command();
187187
let subcommands: Vec<_> = cmd.get_subcommands().collect();
188-
188+
189189
// Define command groupings and their order (excluding MISC)
190190
let groups = [
191191
("INSPECTION", vec!["log", "status"]),
192-
("STACK OPERATIONS", vec!["commit", "new", "describe", "branch"]),
192+
(
193+
"STACK OPERATIONS",
194+
vec!["commit", "rub", "new", "describe", "branch"],
195+
),
193196
("OPERATION HISTORY", vec!["oplog", "undo", "restore"]),
194197
];
195-
198+
196199
println!("A GitButler CLI tool");
197200
println!();
198201
println!("Usage: but [OPTIONS] <COMMAND>");
199202
println!();
200-
203+
201204
// Keep track of which commands we've already printed
202205
let mut printed_commands = HashSet::new();
203-
206+
204207
// Print grouped commands
205208
for (group_name, command_names) in &groups {
206209
println!("{}:", group_name);
@@ -213,13 +216,13 @@ fn print_grouped_help() {
213216
}
214217
println!();
215218
}
216-
219+
217220
// Collect any remaining commands not in the explicit groups
218221
let misc_commands: Vec<_> = subcommands
219222
.iter()
220223
.filter(|subcmd| !printed_commands.contains(subcmd.get_name()) && !subcmd.is_hide_set())
221224
.collect();
222-
225+
223226
// Print MISC section if there are any ungrouped commands
224227
if !misc_commands.is_empty() {
225228
println!("MISC:");
@@ -229,7 +232,7 @@ fn print_grouped_help() {
229232
}
230233
println!();
231234
}
232-
235+
233236
println!("Options:");
234237
println!(
235238
" -C, --current-dir <PATH> Run as if gitbutler-cli was started in PATH instead of the current working directory [default: .]"

crates/but/src/rub/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod assign;
1010
mod move_commit;
1111
mod squash;
1212
mod undo;
13+
mod uncommit;
1314

1415
use crate::id::CliId;
1516

@@ -46,9 +47,8 @@ pub(crate) fn handle(
4647
(CliId::CommittedFile { .. }, CliId::CommittedFile { .. }) => {
4748
bail!(makes_no_sense_error(&source, &target))
4849
}
49-
(CliId::CommittedFile { .. }, CliId::Unassigned) => {
50-
// Extract file from commit to unassigned area - for now, not implemented
51-
bail!("Extracting files from commits is not yet supported. Use git commands to extract file changes.")
50+
(CliId::CommittedFile { path, commit_oid }, CliId::Unassigned) => {
51+
uncommit::file_from_commit(ctx, path, commit_oid)
5252
}
5353
(CliId::CommittedFile { .. }, CliId::Branch { .. }) => {
5454
// Extract file from commit to branch - for now, not implemented

crates/but/src/rub/uncommit.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use anyhow::Result;
2+
use gitbutler_command_context::CommandContext;
3+
use colored::Colorize;
4+
5+
pub(crate) fn file_from_commit(
6+
_ctx: &CommandContext,
7+
file_path: &str,
8+
commit_oid: &gix::ObjectId
9+
) -> Result<()> {
10+
// For now, we'll show a message about what would happen
11+
// The actual implementation would need to:
12+
// 1. Extract the file changes from the commit
13+
// 2. Apply them to the working directory as uncommitted changes
14+
// 3. Remove the file changes from the commit (creating a new commit)
15+
16+
let commit_short = &commit_oid.to_string()[..7];
17+
println!(
18+
"Uncommitting {} from commit {}",
19+
file_path.white(),
20+
commit_short.blue()
21+
);
22+
23+
// TODO: Implement the actual uncommit logic
24+
// This would involve complex Git operations similar to what the GitButler UI does
25+
anyhow::bail!(
26+
"Uncommitting files from commits is not yet fully implemented. \
27+
Use the GitButler UI or git commands to extract file changes from commits."
28+
)
29+
}

crates/but/src/status/mod.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,48 @@ pub(crate) fn all_branches(ctx: &CommandContext) -> anyhow::Result<Vec<CliId>> {
103103
Ok(branches)
104104
}
105105

106+
pub(crate) fn all_committed_files(ctx: &mut CommandContext) -> anyhow::Result<Vec<CliId>> {
107+
let mut committed_files = Vec::new();
108+
109+
// Get stacks with detailed information
110+
let stack_entries = crate::log::stacks(ctx)?;
111+
let stacks: Vec<(
112+
Option<but_workspace::StackId>,
113+
but_workspace::ui::StackDetails,
114+
)> = stack_entries
115+
.iter()
116+
.filter_map(|s| {
117+
s.id.map(|id| (s.id, crate::log::stack_details(ctx, id)))
118+
.and_then(|(stack_id, result)| result.ok().map(|details| (stack_id, details)))
119+
})
120+
.collect();
121+
122+
// Iterate through all commits in all branches to get committed files
123+
for (_stack_id, stack) in &stacks {
124+
for branch in &stack.branch_details {
125+
// Process upstream commits
126+
for commit in &branch.upstream_commits {
127+
if let Ok(commit_files) = get_commit_files(ctx, commit.id) {
128+
for (file_path, _status) in commit_files {
129+
committed_files.push(CliId::committed_file(&file_path, commit.id));
130+
}
131+
}
132+
}
133+
134+
// Process local commits
135+
for commit in &branch.commits {
136+
if let Ok(commit_files) = get_commit_files(ctx, commit.id) {
137+
for (file_path, _status) in commit_files {
138+
committed_files.push(CliId::committed_file(&file_path, commit.id));
139+
}
140+
}
141+
}
142+
}
143+
}
144+
145+
Ok(committed_files)
146+
}
147+
106148
fn get_commit_files(ctx: &CommandContext, commit_id: gix::ObjectId) -> anyhow::Result<Vec<(String, String)>> {
107149
let repo = ctx.repo();
108150
let git2_oid = gix_to_git2_oid(commit_id);

0 commit comments

Comments
 (0)