Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cope with completed restack step #59

Merged
merged 1 commit into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use camino::Utf8PathBuf;
use clap::Args;
use clap::Parser;
use clap::Subcommand;
use reqwest::Method;

use crate::change_number::ChangeNumber;
use crate::commit_hash::CommitHash;
use crate::endpoint::Endpoint;
use crate::patchset::Patchset;

Expand Down Expand Up @@ -136,7 +138,7 @@ pub enum Restack {
/// Restack only the currently checked-out CL on its immediate ancestor.
This,
/// Continue an in-progress restack.
Continue,
Continue(RestackContinue),
/// Abort an in-progress restack.
Abort,
/// Push changes from a completed restack.
Expand All @@ -149,3 +151,19 @@ pub enum Restack {
git_rebase_todo: Utf8PathBuf,
},
}

#[derive(Debug, Clone, Args)]
pub struct RestackContinue {
/// If you ran `git rebase --continue` on your own and then checked something else out,
/// `git-gr` will not be able to determine the new commit hash for the in-progress restack
/// step. Use this flag to supply it manually.
#[arg(long)]
pub in_progress_commit: Option<CommitHash>,

/// If you ran `git rebase --continue` on your own and then checked something else out,
/// `git-gr` will not be able to determine the new commit hash for the in-progress restack
/// step. Use this flag to restart the in-progress restack step, abandoning any changes you
/// may have made.
#[arg(long)]
pub restart_in_progress: bool,
}
13 changes: 9 additions & 4 deletions src/gerrit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::change::Change;
use crate::change::TimestampFormat;
use crate::change_key::ChangeKey;
use crate::change_number::ChangeNumber;
use crate::cli::RestackContinue;
use crate::commit_hash::CommitHash;
use crate::current_exe::current_exe;
use crate::dependency_graph::DependencyGraph;
Expand Down Expand Up @@ -624,12 +625,16 @@ impl GerritGitRemote {
Ok(())
}

pub fn restack(&mut self, branch: &str) -> miette::Result<()> {
restack(self, branch)
pub fn restack(
&mut self,
branch: &str,
options: Option<RestackContinue>,
) -> miette::Result<()> {
restack(self, branch, options)
}

pub fn restack_continue(&mut self) -> miette::Result<()> {
self.restack("HEAD")
pub fn restack_continue(&mut self, options: RestackContinue) -> miette::Result<()> {
self.restack("HEAD", Some(options))
}

pub fn restack_push(&self) -> miette::Result<()> {
Expand Down
7 changes: 7 additions & 0 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ impl Git {
Ok(())
}

/// Determine if a rebase is currently in progress.
pub fn rebase_in_progress(&self) -> miette::Result<bool> {
let git_dir = self.get_git_dir()?;
let rebase_dir = git_dir.join("rebase-merge");
Ok(rebase_dir.exists())
}

pub fn fetch(&self, remote: &str) -> miette::Result<()> {
self.command()
.args(["fetch", remote])
Expand Down
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fn main() -> miette::Result<()> {
let todo = create_todo(&mut gerrit, branch_str)?;
todo.write(&git)?;
gerrit.push(branch.clone(), target)?;
gerrit.restack(branch_str)?;
gerrit.restack(branch_str, None)?;
} else {
gerrit.push(branch, target)?;
}
Expand Down Expand Up @@ -121,11 +121,11 @@ fn main() -> miette::Result<()> {
let mut gerrit = git.gerrit(None)?;
match command {
None => {
gerrit.restack("HEAD")?;
gerrit.restack("HEAD", None)?;
}
Some(command) => match command {
cli::Restack::Continue => {
gerrit.restack_continue()?;
cli::Restack::Continue(restack_continue) => {
gerrit.restack_continue(restack_continue)?;
}
cli::Restack::Abort => {
gerrit.restack_abort()?;
Expand Down
110 changes: 96 additions & 14 deletions src/restack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::VecDeque;
use std::fmt::Display;
use std::io::BufReader;
use std::io::BufWriter;
use std::ops::Deref;

use camino::Utf8PathBuf;
use command_error::CommandExt;
Expand All @@ -15,6 +16,7 @@ use miette::IntoDiagnostic;

use crate::change_number::ChangeNumber;
use crate::change_status::ChangeStatus;
use crate::cli::RestackContinue;
use crate::commit_hash::CommitHash;
use crate::dependency_graph::DependencyGraph;
use crate::gerrit::GerritGitRemote;
Expand All @@ -32,7 +34,7 @@ pub struct RestackTodo {
/// Map from change numbers to updated commit hashes.
pub refs: BTreeMap<ChangeNumber, RefUpdate>,
/// Restack step in progress, if any.
in_progress: Option<Step>,
in_progress: Option<InProgress>,
}

impl RestackTodo {
Expand Down Expand Up @@ -155,15 +157,39 @@ impl Display for RefUpdate {
}
}

pub fn restack(gerrit: &mut GerritGitRemote, branch: &str) -> miette::Result<()> {
pub fn restack(
gerrit: &mut GerritGitRemote,
branch: &str,
options: Option<RestackContinue>,
) -> miette::Result<()> {
let git = gerrit.git();
let mut fetched = false;
let mut todo = get_or_create_todo(gerrit, branch)?;

match &todo.in_progress {
Some(step) => {
if let Some(step) = todo.in_progress.take() {
if options
.as_ref()
.map(|options| options.restart_in_progress)
.unwrap_or(false)
{
tracing::info!("Retrying restacking {step}");
todo.steps.push_front(step.inner);
} else if let Some(commit) =
options.and_then(|mut options| options.in_progress_commit.take())
{
tracing::info!(
"Using `--in-progress-commit`; restacking {step} produced commit {commit}"
);
todo.refs.insert(
step.change,
RefUpdate {
old: step.old_head,
new: commit,
},
);
todo.write(&git)?;
} else if git.rebase_in_progress()? {
tracing::info!("Continuing to restack {step}");
let old_head = git.rev_parse("REBASE_HEAD")?;
match git
.command()
.args(["rebase", "--continue"])
Expand All @@ -176,7 +202,7 @@ pub fn restack(gerrit: &mut GerritGitRemote, branch: &str) -> miette::Result<()>
todo.refs.insert(
step.change,
RefUpdate {
old: old_head,
old: step.old_head,
new: git.get_head()?,
},
);
Expand All @@ -186,8 +212,33 @@ pub fn restack(gerrit: &mut GerritGitRemote, branch: &str) -> miette::Result<()>
return error;
}
}
} else {
let head = git.get_head()?;
let change_id = git.change_id(&head)?;
let expect_change = gerrit.get_change(step.change)?;

tracing::warn!(
"Please use `git gr restack continue` instead of `git rebase --continue`"
);
if change_id == expect_change.id {
// OK, the user just did `git rebase --continue` on their own.
todo.refs.insert(
step.change,
RefUpdate {
old: step.old_head,
new: head,
},
);
todo.write(&git)?;
} else {
// The user did `git rebase --continue` on their own and then did
// something else...
return Err(miette!(
"Cannot find commit for change {}; use `git gr restack continue --in-progress-commit` or `--restart-in-progress` to continue",
expect_change.number.pretty(gerrit)?
));
}
}
None => {}
}

if todo.refs.is_empty() {
Expand All @@ -210,15 +261,22 @@ pub fn restack(gerrit: &mut GerritGitRemote, branch: &str) -> miette::Result<()>
}

while let Some(step) = todo.steps.pop_front() {
let old_head = gerrit.fetch_cl(gerrit.get_change(step.change)?.patchset())?;
let in_progress = InProgress {
inner: step,
old_head,
};

let step_result = todo
.perform_step(&step, gerrit, &mut fetched)
.wrap_err_with(|| format!("Failed to restack {step}"));
.perform_step(&in_progress, gerrit, &mut fetched)
.wrap_err_with(|| format!("Failed to restack {}", in_progress));

match step_result {
Ok(()) => {
todo.write(&git)?;
}
error @ Err(_) => {
todo.in_progress = Some(step);
todo.in_progress = Some(in_progress);
todo.write(&git)?;
return error.wrap_err(CONTINUE_MESSAGE);
}
Expand Down Expand Up @@ -271,10 +329,12 @@ pub fn restack_abort(git: &Git) -> miette::Result<()> {
if todo_path.exists() {
fs::remove_file(todo_path).into_diagnostic()?;
}
git.command()
.args(["rebase", "--abort"])
.status_checked()
.into_diagnostic()?;
if git.rebase_in_progress()? {
git.command()
.args(["rebase", "--abort"])
.status_checked()
.into_diagnostic()?;
}
Ok(())
}

Expand Down Expand Up @@ -384,3 +444,25 @@ pub fn create_todo(gerrit: &mut GerritGitRemote, branch: &str) -> miette::Result

Ok(todo)
}

#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
struct InProgress {
/// The step in progress.
inner: Step,
/// The HEAD commit of the change before restacking.
old_head: CommitHash,
}

impl Deref for InProgress {
type Target = Step;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl Display for InProgress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}
Loading