admin: create a subcommand for a rebase

Summary:
This allows to create a changeset clone with p1 changed. Essentially, this is a
bonsai-only rebase without any safety checks. The safety of this operation
(i.e. the usability of the resulting changeset) is highly dependent on the
context the ultimate responsibility is on the person running this command. For
instance, this does not do *any conflict checks*. As such, this is really only
useful to rebase additions/deletions/modifications of unchanged files.

Reviewed By: StanislavGlebik

Differential Revision: D25947017

fbshipit-source-id: 4093c432e4f8ba2a647ee4b17c6840c33d13be81
This commit is contained in:
Kostia Balytskyi 2021-02-16 16:10:15 -08:00 committed by Facebook GitHub Bot
parent 9ee07c3643
commit dc2e1a850f
3 changed files with 124 additions and 0 deletions

View File

@ -48,6 +48,7 @@ mod hg_sync;
mod mutable_counters;
mod phases;
mod pushrebase;
mod rebase;
mod redaction;
mod rsync;
mod skiplist_subcommand;
@ -86,6 +87,7 @@ fn setup_app<'a, 'b>() -> MononokeClapApp<'a, 'b> {
.subcommand(subcommand_deleted_manifest::build_subcommand())
.subcommand(derived_data::build_subcommand())
.subcommand(rsync::build_subcommand())
.subcommand(rebase::build_subcommand())
.subcommand(pushrebase::build_subcommand())
.subcommand(subcommand_skeleton_manifests::build_subcommand())
}
@ -173,6 +175,9 @@ fn main(fb: FacebookInit) -> ExitCode {
(rsync::RSYNC, Some(sub_m)) => {
rsync::subcommand_rsync(fb, logger, &matches, sub_m).await
}
(rebase::REBASE, Some(sub_m)) => {
rebase::subcommand_rebase(fb, logger, &matches, sub_m).await
}
(pushrebase::PUSHREBASE, Some(sub_m)) => {
pushrebase::subcommand_pushrebase(fb, logger, &matches, sub_m).await
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
use anyhow::{anyhow, Error};
use blobstore::Loadable;
use clap::{App, Arg, ArgMatches, SubCommand};
use fbinit::FacebookInit;
use futures::{compat::Future01CompatExt, future::try_join};
use blobrepo::save_bonsai_changesets;
use cmdlib::{
args::{self, MononokeMatches},
helpers,
};
use context::CoreContext;
use mononoke_types::{BonsaiChangesetMut, ChangesetId};
use slog::Logger;
use crate::error::SubcommandError;
pub const ARG_DEST: &str = "dest";
pub const ARG_CSID: &str = "csid";
pub const REBASE: &str = "rebase";
const ARG_I_KNOW: &str = "i-know-what-i-am-doing";
pub fn build_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name(REBASE)
.about(
"produce a bonsai changeset clone with p1 changed to a given value. \
DOES NOT RUN ANY SAFETY CHECKS, DOES NOT CHECK FOR CONFLICTS!",
)
.arg(
Arg::with_name(ARG_CSID)
.long(ARG_CSID)
.takes_value(true)
.required(true)
.help("{hg|bonsai} changeset id or bookmark name"),
)
.arg(
Arg::with_name(ARG_DEST)
.long(ARG_DEST)
.takes_value(true)
.required(true)
.help("desired value of the p1"),
)
.arg(
Arg::with_name(ARG_I_KNOW)
.long(ARG_I_KNOW)
.takes_value(false)
.help("Acknowledges that you understnad that this is an unsafe command"),
)
}
fn copyfrom_fixup(bcs: &mut BonsaiChangesetMut, new_parent: ChangesetId) {
for maybe_file_change in bcs.file_changes.values_mut() {
if let Some((_, ref mut cs_id)) = maybe_file_change.as_mut().and_then(|c| c.copy_from_mut())
{
*cs_id = new_parent;
}
}
}
pub async fn subcommand_rebase<'a>(
fb: FacebookInit,
logger: Logger,
matches: &'a MononokeMatches<'_>,
sub_matches: &'a ArgMatches<'_>,
) -> Result<(), SubcommandError> {
if !sub_matches.is_present(ARG_I_KNOW) {
return Err(anyhow!("{} is required", ARG_I_KNOW).into());
}
args::init_cachelib(fb, &matches);
let ctx = CoreContext::new_with_logger(fb, logger.clone());
let repo = args::open_repo(fb, &logger, &matches).await?;
let cs_id = sub_matches
.value_of(ARG_CSID)
.ok_or_else(|| anyhow!("{} arg is not specified", ARG_CSID))?;
let dest = sub_matches
.value_of(ARG_DEST)
.ok_or_else(|| anyhow!("{} arg is not specified", ARG_DEST))?;
let (cs_id, dest) = try_join(
helpers::csid_resolve(ctx.clone(), repo.clone(), cs_id).compat(),
helpers::csid_resolve(ctx.clone(), repo.clone(), dest).compat(),
)
.await?;
let bcs = cs_id
.load(&ctx, repo.blobstore())
.await
.map_err(Error::from)?;
let mut rebased = bcs.into_mut();
if rebased.parents.is_empty() {
rebased.parents.push(dest);
} else {
rebased.parents[0] = dest;
}
copyfrom_fixup(&mut rebased, dest);
let rebased = rebased.freeze()?;
let rebased_cs_id = rebased.get_changeset_id();
save_bonsai_changesets(vec![rebased], ctx.clone(), repo).await?;
println!("{}", rebased_cs_id);
Ok(())
}

View File

@ -96,6 +96,10 @@ impl FileChange {
self.copy_from.as_ref()
}
pub fn copy_from_mut(&mut self) -> Option<&mut (MPath, ChangesetId)> {
self.copy_from.as_mut()
}
#[inline]
pub(crate) fn into_thrift_opt(fc_opt: Option<Self>) -> thrift::FileChangeOpt {
let fc_opt = fc_opt.map(Self::into_thrift);