Skip to content

Commit 18b79a4

Browse files
committed
feat: add stg branch --reset command to reset the stack without losing patches
* Introduce alternative to `stg repair` for more manual control over stack metadata repair process. * Run `stg branch --reset` to force mark all patches in the stack as unapplied. * Manually reconcile the stack state by running `stg push --merged` or `stg uncommit`.
1 parent f88c7f0 commit 18b79a4

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

src/cmd/branch/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod describe;
1010
mod list;
1111
mod protect;
1212
mod rename;
13+
mod reset;
1314
mod unprotect;
1415

1516
use anyhow::Result;
@@ -59,6 +60,7 @@ fn make() -> clap::Command {
5960
"{--delete,-D} [--force] [branch]",
6061
"--cleanup [--force] [branch]",
6162
"{--describe,-d} <description> [branch]",
63+
"--reset [branch]",
6264
],
6365
))
6466
.subcommand(self::list::command())
@@ -70,6 +72,7 @@ fn make() -> clap::Command {
7072
.subcommand(self::delete::command())
7173
.subcommand(self::cleanup::command())
7274
.subcommand(self::describe::command())
75+
.subcommand(self::reset::command())
7376
.arg(
7477
clap::Arg::new("merge")
7578
.long("merge")
@@ -98,6 +101,7 @@ fn run(matches: &clap::ArgMatches) -> Result<()> {
98101
"--delete" => self::delete::dispatch(&repo, submatches),
99102
"--cleanup" => self::cleanup::dispatch(&repo, submatches),
100103
"--describe" => self::describe::dispatch(&repo, submatches),
104+
"--reset" => self::reset::dispatch(&repo, submatches),
101105
s => panic!("unhandled branch subcommand {s}"),
102106
}
103107
} else if let Some(target_branch_loc) = matches.get_one::<BranchLocator>("branch-any") {

src/cmd/branch/reset.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
3+
//! `stg branch --reset` implementation.
4+
5+
use std::rc::Rc;
6+
7+
use anyhow::{anyhow, Result};
8+
9+
use crate::{
10+
color::get_color_stdout,
11+
ext::RepositoryExtended,
12+
print_info_message,
13+
stack::{InitializationPolicy, Stack, StackAccess, StackState, StackStateAccess},
14+
};
15+
16+
pub(super) fn command() -> clap::Command {
17+
clap::Command::new("--reset")
18+
.about("Reset the stack and mark all patches as unapplied")
19+
.long_about(
20+
"Reset the stack head to the current git HEAD and mark all patches as \
21+
unapplied.\n\
22+
\n\
23+
This command is useful when the branch has diverged from the stack state \
24+
and you want to reset the stack without losing patches. After running this \
25+
command, you can reconcile the state by hand either by iteratively running \
26+
`stg push --merged` or by scrapping the patches and starting anew with \
27+
`stg uncommit`.",
28+
)
29+
.arg(
30+
clap::Arg::new("branch-any")
31+
.help("Branch to reset (defaults to current branch)")
32+
.value_name("branch")
33+
.value_parser(clap::value_parser!(crate::branchloc::BranchLocator)),
34+
)
35+
}
36+
37+
pub(super) fn dispatch(repo: &gix::Repository, matches: &clap::ArgMatches) -> Result<()> {
38+
let stack = if let Some(branch_loc) = matches.get_one::<crate::branchloc::BranchLocator>("branch-any") {
39+
let branch = branch_loc.resolve(repo)?;
40+
Stack::from_branch(repo, branch, InitializationPolicy::RequireInitialized)?
41+
} else {
42+
Stack::current(repo, InitializationPolicy::RequireInitialized)?
43+
};
44+
45+
let config = repo.config_snapshot();
46+
if stack.is_protected(&config) {
47+
return Err(anyhow!(
48+
"this branch is protected; modification is not permitted."
49+
));
50+
}
51+
52+
if stack.get_branch_head().id == stack.head().id {
53+
print_info_message(
54+
matches,
55+
"git head already matching stack state, doing nothing",
56+
);
57+
return Ok(());
58+
}
59+
60+
stack
61+
.setup_transaction()
62+
.use_index_and_worktree(false)
63+
.with_output_stream(get_color_stdout(matches))
64+
.transact(|trans| {
65+
let commit = trans.stack().get_branch_head().to_owned();
66+
67+
let stack = trans.stack();
68+
let repo = stack.repo;
69+
let stack_state_commit = repo
70+
.find_reference(stack.get_stack_refname())?
71+
.peel_to_commit()
72+
.map(Rc::new)?;
73+
74+
let new_stack_state = StackState::from_commit(trans.stack().repo, &stack_state_commit)?
75+
.reset_branch_state(commit, stack_state_commit);
76+
77+
trans.reset_to_state(new_stack_state)
78+
})
79+
.execute("branch-reset")?;
80+
81+
Ok(())
82+
}

0 commit comments

Comments
 (0)