Skip to content

Commit 9e5c9ac

Browse files
committed
feat: add --cleanup-backups flag for backup branch cleanup
Add `git chain rebase --cleanup-backups` to automatically delete backup branches (backup-<chain>/<branch>) after a successful rebase. Works with rebase, rebase --continue, and rebase --skip. Adds cleanup_backups parameter to rebase(), rebase_continue(), and rebase_skip() signatures, and the cleanup_backup_branches() helper that finds and deletes matching backup branches.
1 parent b546bdb commit 9e5c9ac

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

src/cli.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ where
183183
"skip_rebase",
184184
])
185185
.takes_value(false),
186+
)
187+
.arg(
188+
Arg::with_name("cleanup_backups")
189+
.long("cleanup-backups")
190+
.help("Delete backup branches after successful rebase")
191+
.conflicts_with_all(&["abort_rebase", "status_rebase"])
192+
.takes_value(false),
186193
);
187194

188195
let push_subcommand = SubCommand::with_name("push")

src/commands.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,14 @@ pub fn run(arg_matches: ArgMatches) -> Result<(), Error> {
258258
};
259259
}
260260
("rebase", Some(sub_matches)) => {
261+
let cleanup_backups = sub_matches.is_present("cleanup_backups");
262+
261263
if sub_matches.is_present("status_rebase") {
262264
git_chain.rebase_status()?;
263265
} else if sub_matches.is_present("continue_rebase") {
264-
git_chain.rebase_continue()?;
266+
git_chain.rebase_continue(cleanup_backups)?;
265267
} else if sub_matches.is_present("skip_rebase") {
266-
git_chain.rebase_skip()?;
268+
git_chain.rebase_skip(cleanup_backups)?;
267269
} else if sub_matches.is_present("abort_rebase") {
268270
git_chain.rebase_abort()?;
269271
} else {
@@ -291,6 +293,7 @@ pub fn run(arg_matches: ArgMatches) -> Result<(), Error> {
291293
step_rebase,
292294
ignore_root,
293295
squashed_merge_handling,
296+
cleanup_backups,
294297
)?;
295298
} else {
296299
eprintln!("Unable to rebase chain.");

src/git_chain/operations.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ impl GitChain {
2020
step_rebase: bool,
2121
ignore_root: bool,
2222
squashed_merge_handling: SquashedRebaseHandling,
23+
cleanup_backups: bool,
2324
) -> Result<(), Error> {
2425
// Check for existing chain rebase state (not for step mode)
2526
if !step_rebase && state_exists(&self.repo) {
@@ -333,6 +334,9 @@ impl GitChain {
333334
}
334335
let state = read_state(&self.repo)?;
335336
self.print_rebase_summary(&state, num_of_rebase_operations);
337+
if cleanup_backups {
338+
self.cleanup_backup_branches(chain_name, &state.branches);
339+
}
336340
let _ = delete_state(&self.repo);
337341
}
338342

@@ -399,7 +403,7 @@ impl GitChain {
399403
Ok(())
400404
}
401405

402-
pub fn rebase_continue(&self) -> Result<(), Error> {
406+
pub fn rebase_continue(&self, cleanup_backups: bool) -> Result<(), Error> {
403407
// 1. Verify state file exists
404408
if !state_exists(&self.repo) {
405409
return Err(Error::from_str(
@@ -688,6 +692,10 @@ impl GitChain {
688692

689693
// Print summary and clean up
690694
self.print_rebase_summary(&state, num_of_rebase_operations);
695+
let chain_name = state.chain_name.clone();
696+
if cleanup_backups {
697+
self.cleanup_backup_branches(&chain_name, &state.branches);
698+
}
691699
let _ = delete_state(&self.repo);
692700

693701
// Return to original branch
@@ -728,7 +736,7 @@ impl GitChain {
728736
Ok(())
729737
}
730738

731-
pub fn rebase_skip(&self) -> Result<(), Error> {
739+
pub fn rebase_skip(&self, cleanup_backups: bool) -> Result<(), Error> {
732740
// 1. Verify state file exists
733741
if !state_exists(&self.repo) {
734742
return Err(Error::from_str(
@@ -993,6 +1001,10 @@ impl GitChain {
9931001

9941002
// Print summary and clean up
9951003
self.print_rebase_summary(&state, num_of_rebase_operations);
1004+
let chain_name = state.chain_name.clone();
1005+
if cleanup_backups {
1006+
self.cleanup_backup_branches(&chain_name, &state.branches);
1007+
}
9961008
let _ = delete_state(&self.repo);
9971009

9981010
// Return to original branch
@@ -1110,6 +1122,53 @@ impl GitChain {
11101122
}
11111123
}
11121124

1125+
/// Delete backup branches for a chain after successful rebase.
1126+
fn cleanup_backup_branches(&self, chain_name: &str, branches: &[BranchState]) {
1127+
let mut cleaned = 0;
1128+
1129+
for branch in branches {
1130+
let backup_name = format!("backup-{}/{}", chain_name, branch.name);
1131+
// Check if backup branch exists
1132+
if self.git_local_branch_exists(&backup_name).unwrap_or(false) {
1133+
let output = Command::new("git")
1134+
.arg("branch")
1135+
.arg("-D")
1136+
.arg(&backup_name)
1137+
.output();
1138+
1139+
match output {
1140+
Ok(result) if result.status.success() => {
1141+
if cleaned == 0 {
1142+
println!();
1143+
println!("🧹 Cleaning up backup branches...");
1144+
}
1145+
println!(" Deleted {}", backup_name.bold());
1146+
cleaned += 1;
1147+
}
1148+
Ok(result) => {
1149+
let stderr = String::from_utf8_lossy(&result.stderr);
1150+
eprintln!(
1151+
" ⚠️ Failed to delete {}: {}",
1152+
backup_name.bold(),
1153+
stderr.trim()
1154+
);
1155+
}
1156+
Err(e) => {
1157+
eprintln!(" ⚠️ Failed to delete {}: {}", backup_name.bold(), e);
1158+
}
1159+
}
1160+
}
1161+
}
1162+
1163+
if cleaned > 0 {
1164+
println!(
1165+
" Cleaned up {} backup branch{}.",
1166+
cleaned,
1167+
if cleaned == 1 { "" } else { "es" }
1168+
);
1169+
}
1170+
}
1171+
11131172
pub fn rebase_abort(&self) -> Result<(), Error> {
11141173
// 1. Verify state file exists
11151174
if !state_exists(&self.repo) {

0 commit comments

Comments
 (0)