cli: update branches after rewriting commits

This makes it so (local) branches get updated when the commit they
point to gets rewritten. If the branch was conflicted, we just print a
warning and don't update the branch (though one could imagine
rewriting the conflict). We also just print a warning if the new
target is unclear because the commit was rewritten into multiple new
commits (divergent).

The updating doesn't work when the working copy commit gets rewritten
because the working copy changed on disk. That's because that's done
in a separate transaction inside `working_copy.rs`. That's similar to
how orphans of the working copy commit don't get automatically
evolved. I'll fix both problems soon.
This commit is contained in:
Martin von Zweigbergk 2021-08-11 09:19:20 -07:00
parent 8fe4433c9c
commit 3c1a9b4d10

View file

@ -16,7 +16,7 @@ extern crate chrono;
extern crate clap; extern crate clap;
extern crate config; extern crate config;
use std::collections::{HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt::Debug; use std::fmt::Debug;
use std::fs::OpenOptions; use std::fs::OpenOptions;
@ -198,6 +198,9 @@ struct RepoCommandHelper {
// Whether the checkout should be updated to an appropriate successor when the transaction // Whether the checkout should be updated to an appropriate successor when the transaction
// finishes. This should generally be true for commands that rewrite commits. // finishes. This should generally be true for commands that rewrite commits.
auto_update_checkout: bool, auto_update_checkout: bool,
// Whether branches should be updated to appropriate successors when the transaction
// finishes. This should generally be true for commands that rewrite commits.
auto_update_branches: bool,
} }
impl RepoCommandHelper { impl RepoCommandHelper {
@ -216,6 +219,7 @@ impl RepoCommandHelper {
working_copy_committed: false, working_copy_committed: false,
evolve_orphans: true, evolve_orphans: true,
auto_update_checkout: true, auto_update_checkout: true,
auto_update_branches: true,
}) })
} }
@ -229,6 +233,11 @@ impl RepoCommandHelper {
self self
} }
fn auto_update_branches(mut self, value: bool) -> Self {
self.auto_update_branches = value;
self
}
fn repo(&self) -> &Arc<ReadonlyRepo> { fn repo(&self) -> &Arc<ReadonlyRepo> {
&self.repo &self.repo
} }
@ -399,6 +408,9 @@ impl RepoCommandHelper {
if self.auto_update_checkout { if self.auto_update_checkout {
update_checkout_after_rewrite(ui, mut_repo)?; update_checkout_after_rewrite(ui, mut_repo)?;
} }
if self.auto_update_branches {
update_branches_after_rewrite(ui, mut_repo)?;
}
self.repo = tx.commit(); self.repo = tx.commit();
update_working_copy(ui, &self.repo, &self.repo.working_copy_locked()) update_working_copy(ui, &self.repo, &self.repo.working_copy_locked())
} }
@ -577,6 +589,55 @@ fn update_checkout_after_rewrite(ui: &mut Ui, mut_repo: &mut MutableRepo) -> io:
Ok(()) Ok(())
} }
fn update_branches_after_rewrite(ui: &mut Ui, mut_repo: &mut MutableRepo) -> io::Result<()> {
// TODO: Perhaps this method should be in MutableRepo.
let new_evolution = mut_repo.evolution();
let base_repo = mut_repo.base_repo();
let old_evolution = base_repo.evolution();
let mut updates = HashMap::new();
for (branch_name, branch_target) in mut_repo.view().branches() {
match &branch_target.local_target {
None => {
// nothing to do (a deleted branch doesn't need updating)
}
Some(RefTarget::Normal(current_target)) => {
if new_evolution.is_obsolete(current_target)
&& !old_evolution.is_obsolete(current_target)
{
let new_targets =
new_evolution.new_parent(mut_repo.as_repo_ref(), current_target);
if new_targets.len() == 1 {
updates.insert(
branch_name.clone(),
RefTarget::Normal(new_targets.iter().next().unwrap().clone()),
);
} else {
writeln!(
ui,
"Branch {}'s target was obsoleted, but the new target is unclear",
branch_name
)?;
}
}
}
Some(RefTarget::Conflict { adds, .. }) => {
for current_target in adds {
if new_evolution.is_obsolete(current_target)
&& !old_evolution.is_obsolete(current_target)
{
writeln!(ui, "Branch {}'s target was obsoleted, but not updating it since it's conflicted", branch_name )?;
}
}
}
}
}
for (branch_name, new_local_target) in updates {
mut_repo.set_local_branch(branch_name, new_local_target);
}
Ok(())
}
fn get_app<'a, 'b>() -> App<'a, 'b> { fn get_app<'a, 'b>() -> App<'a, 'b> {
let init_command = SubCommand::with_name("init") let init_command = SubCommand::with_name("init")
.about("Initialize a repo") .about("Initialize a repo")
@ -941,7 +1002,10 @@ fn cmd_checkout(
command: &CommandHelper, command: &CommandHelper,
sub_matches: &ArgMatches, sub_matches: &ArgMatches,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut repo_command = command.repo_helper(ui)?.auto_update_checkout(false); let mut repo_command = command
.repo_helper(ui)?
.auto_update_checkout(false)
.auto_update_branches(false);
let new_commit = repo_command.resolve_revision_arg(sub_matches)?; let new_commit = repo_command.resolve_revision_arg(sub_matches)?;
repo_command.commit_working_copy()?; repo_command.commit_working_copy()?;
let mut tx = let mut tx =
@ -2070,7 +2134,10 @@ fn cmd_branch(
command: &CommandHelper, command: &CommandHelper,
sub_matches: &ArgMatches, sub_matches: &ArgMatches,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut repo_command = command.repo_helper(ui)?; let mut repo_command = command
.repo_helper(ui)?
.auto_update_checkout(false)
.auto_update_branches(false);
let branch_name = sub_matches.value_of("name").unwrap(); let branch_name = sub_matches.value_of("name").unwrap();
if sub_matches.is_present("delete") { if sub_matches.is_present("delete") {
let mut tx = repo_command.start_transaction(&format!("delete branch {}", branch_name)); let mut tx = repo_command.start_transaction(&format!("delete branch {}", branch_name));