bookmarks_movement: refactor bookmark movement for pushrebase

Summary: Refactor control of movement of non-scratch bookmarks through pushrebase.

Reviewed By: krallin

Differential Revision: D22920694

fbshipit-source-id: 347777045b4995b69973118781511686cf34bdba
This commit is contained in:
Mark Thomas 2020-08-14 02:26:47 -07:00 committed by Facebook GitHub Bot
parent a16b88d1c5
commit c529e6a527
10 changed files with 191 additions and 111 deletions

View File

@ -13,10 +13,14 @@ bonsai_git_mapping = { path = "../../bonsai_git_mapping" }
bookmarks = { path = ".." }
bookmarks_types = { path = "../bookmarks_types" }
context = { path = "../../server/context" }
git_mapping_pushrebase_hook = { path = "../../bonsai_git_mapping/git_mapping_pushrebase_hook" }
globalrev_pushrebase_hook = { path = "../../bonsai_globalrev_mapping/globalrev_pushrebase_hook" }
metaconfig_types = { path = "../../metaconfig/types" }
mononoke_types = { path = "../../mononoke_types" }
pushrebase = { path = "../../pushrebase" }
reachabilityindex = { path = "../../reachabilityindex" }
scuba_ext = { path = "../../common/scuba_ext" }
futures_stats = { git = "https://github.com/facebookexperimental/rust-shed.git", branch = "master" }
anyhow = "1.0"
futures = { version = "0.3.5", features = ["async-await", "compat"] }
thiserror = "1.0"

View File

@ -5,22 +5,27 @@
* GNU General Public License version 2.
*/
#![allow(unused)]
#![deny(warnings)]
use bookmarks_types::BookmarkName;
use context::CoreContext;
use metaconfig_types::{BookmarkAttrs, InfinitepushParams};
use mononoke_types::ChangesetId;
use pushrebase::PushrebaseError;
use thiserror::Error;
mod create;
mod delete;
mod git_mapping;
mod globalrev_mapping;
mod pushrebase_onto;
mod update;
pub use pushrebase::PushrebaseOutcome;
pub use crate::create::CreateBookmarkOp;
pub use crate::delete::DeleteBookmarkOp;
pub use crate::pushrebase_onto::PushrebaseOntoBookmarkOp;
pub use crate::update::{BookmarkUpdatePolicy, BookmarkUpdateTargets, UpdateBookmarkOp};
/// How authorization for the bookmark move should be determined.
@ -132,6 +137,9 @@ pub enum BookmarkMovementError {
#[error("Bookmark transaction failed")]
TransactionFailed,
#[error("Pushrebase failed")]
PushrebaseError(#[source] PushrebaseError),
#[error(transparent)]
Error(#[from] anyhow::Error),
}

View File

@ -0,0 +1,132 @@
/*
* 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 std::collections::HashSet;
use anyhow::anyhow;
use blobrepo::BlobRepo;
use bookmarks_types::BookmarkName;
use context::CoreContext;
use futures_stats::TimedFutureExt;
use git_mapping_pushrebase_hook::GitMappingPushrebaseHook;
use globalrev_pushrebase_hook::GlobalrevPushrebaseHook;
use metaconfig_types::{BookmarkAttrs, InfinitepushParams, PushrebaseParams};
use mononoke_types::BonsaiChangeset;
use scuba_ext::ScubaSampleBuilderExt;
use crate::{BookmarkKindRestrictions, BookmarkMoveAuthorization, BookmarkMovementError};
pub struct PushrebaseOntoBookmarkOp<'op> {
bookmark: &'op BookmarkName,
changesets: &'op HashSet<BonsaiChangeset>,
auth: BookmarkMoveAuthorization,
kind_restrictions: BookmarkKindRestrictions,
hg_replay: Option<&'op pushrebase::HgReplayData>,
}
#[must_use = "PushrebaseOntoBookmarkOp must be run to have an effect"]
impl<'op> PushrebaseOntoBookmarkOp<'op> {
pub fn new(
bookmark: &'op BookmarkName,
changesets: &'op HashSet<BonsaiChangeset>,
) -> PushrebaseOntoBookmarkOp<'op> {
PushrebaseOntoBookmarkOp {
bookmark,
changesets,
auth: BookmarkMoveAuthorization::Context,
kind_restrictions: BookmarkKindRestrictions::AnyKind,
hg_replay: None,
}
}
pub fn only_if_scratch(mut self) -> Self {
self.kind_restrictions = BookmarkKindRestrictions::OnlyScratch;
self
}
pub fn only_if_public(mut self) -> Self {
self.kind_restrictions = BookmarkKindRestrictions::OnlyPublic;
self
}
pub fn with_hg_replay_data(mut self, hg_replay: Option<&'op pushrebase::HgReplayData>) -> Self {
self.hg_replay = hg_replay;
self
}
pub async fn run(
self,
ctx: &'op CoreContext,
repo: &'op BlobRepo,
infinitepush_params: &'op InfinitepushParams,
pushrebase_params: &'op PushrebaseParams,
bookmark_attrs: &'op BookmarkAttrs,
) -> Result<pushrebase::PushrebaseOutcome, BookmarkMovementError> {
self.auth
.check_authorized(ctx, bookmark_attrs, self.bookmark)?;
if pushrebase_params.block_merges {
let any_merges = self.changesets.iter().any(BonsaiChangeset::is_merge);
if any_merges {
return Err(anyhow!(
"Pushrebase blocked because it contains a merge commit.\n\
If you need this for a specific use case please contact\n\
the Source Control team at https://fburl.com/27qnuyl2"
)
.into());
}
}
self.kind_restrictions
.check_kind(infinitepush_params, self.bookmark)?;
let mut pushrebase_hooks = Vec::new();
if pushrebase_params.assign_globalrevs {
let hook = GlobalrevPushrebaseHook::new(
repo.bonsai_globalrev_mapping().clone(),
repo.get_repoid(),
);
pushrebase_hooks.push(hook);
}
if pushrebase_params.populate_git_mapping {
let hook = GitMappingPushrebaseHook::new(repo.bonsai_git_mapping().clone());
pushrebase_hooks.push(hook);
}
let mut flags = pushrebase_params.flags.clone();
if let Some(rewritedates) = bookmark_attrs.should_rewrite_dates(self.bookmark) {
// Bookmark config overrides repo flags.rewritedates config
flags.rewritedates = rewritedates;
}
ctx.scuba().clone().log_with_msg("Pushrebase started", None);
let (stats, result) = pushrebase::do_pushrebase_bonsai(
ctx,
repo,
&flags,
self.bookmark,
self.changesets,
self.hg_replay,
pushrebase_hooks.as_slice(),
)
.timed()
.await;
let mut scuba_logger = ctx.scuba().clone();
scuba_logger.add_future_stats(&stats);
match &result {
Ok(outcome) => scuba_logger
.add("pushrebase_retry_num", outcome.retry_num.0)
.log_with_msg("Pushrebase finished", None),
Err(err) => scuba_logger.log_with_msg("Pushrebase failed", Some(format!("{:#?}", err))),
}
result.map_err(BookmarkMovementError::PushrebaseError)
}
}

View File

@ -10,24 +10,23 @@ use crate::{
PostResolveBookmarkOnlyPushRebase, PostResolveInfinitePush, PostResolvePush,
PostResolvePushRebase, PushrebaseBookmarkSpec,
};
use anyhow::{anyhow, format_err, Context, Error, Result};
use anyhow::{anyhow, Context, Error, Result};
use blobrepo::BlobRepo;
use blobrepo_hg::BlobRepoHg;
use bookmarks::{BookmarkName, BookmarkUpdateReason, BundleReplay};
use bookmarks_movement::{BookmarkUpdatePolicy, BookmarkUpdateTargets};
use bookmarks_movement::{BookmarkMovementError, BookmarkUpdatePolicy, BookmarkUpdateTargets};
use context::CoreContext;
use futures::{
compat::Future01CompatExt,
future::try_join,
stream::{FuturesUnordered, TryStreamExt},
};
use futures_stats::TimedFutureExt;
use git_mapping_pushrebase_hook::GitMappingPushrebaseHook;
use globalrev_pushrebase_hook::GlobalrevPushrebaseHook;
use mercurial_bundle_replay_data::BundleReplayData;
use metaconfig_types::{BookmarkAttrs, InfinitepushParams, PushParams, PushrebaseParams};
use mononoke_types::{BonsaiChangeset, ChangesetId, RawBundle2Id};
use pushrebase::PushrebaseHook;
use pushrebase::{PushrebaseError, PushrebaseHook};
use reachabilityindex::LeastCommonAncestorsHint;
use reverse_filler_queue::ReverseFillerQueue;
use scribe_commit_queue::{self, LogToScribe};
@ -438,7 +437,6 @@ async fn run_pushrebase(
) -> Result<UnbundlePushRebaseResponse, BundleResolverError> {
debug!(ctx.logger(), "unbundle processing: running pushrebase.");
let PostResolvePushRebase {
any_merges,
bookmark_push_part_id,
bookmark_spec,
maybe_hg_replay_data,
@ -462,7 +460,6 @@ async fn run_pushrebase(
repo,
&pushrebase_params,
&uploaded_bonsais,
any_merges,
&onto_bookmark,
&maybe_hg_replay_data,
bookmark_attrs,
@ -616,73 +613,29 @@ async fn normal_pushrebase(
repo: &BlobRepo,
pushrebase_params: &PushrebaseParams,
changesets: &HashSet<BonsaiChangeset>,
any_merges: bool,
bookmark: &BookmarkName,
maybe_hg_replay_data: &Option<pushrebase::HgReplayData>,
bookmark_attrs: &BookmarkAttrs,
infinitepush_params: &InfinitepushParams,
) -> Result<(ChangesetId, Vec<pushrebase::PushrebaseChangesetPair>), BundleResolverError> {
check_plain_bookmark_move_preconditions(
&ctx,
&bookmark,
"pushrebase",
&bookmark_attrs,
&infinitepush_params,
)?;
let block_merges = pushrebase_params.block_merges.clone();
if block_merges && any_merges {
return Err(format_err!(
"Pushrebase blocked because it contains a merge commit.\n\
If you need this for a specific use case please contact\n\
the Source Control team at https://fburl.com/27qnuyl2"
bookmarks_movement::PushrebaseOntoBookmarkOp::new(bookmark, changesets)
.only_if_public()
.with_hg_replay_data(maybe_hg_replay_data.as_ref())
.run(
ctx,
repo,
infinitepush_params,
pushrebase_params,
bookmark_attrs,
)
.into());
}
let hooks = get_pushrebase_hooks(&repo, &pushrebase_params);
let mut flags = pushrebase_params.flags.clone();
if let Some(rewritedates) = bookmark_attrs.should_rewrite_dates(bookmark) {
// Bookmark config overrides repo flags.rewritedates config
flags.rewritedates = rewritedates;
}
ctx.scuba().clone().log_with_msg("Pushrebase started", None);
let (stats, result) = pushrebase::do_pushrebase_bonsai(
&ctx,
&repo,
&flags,
bookmark,
&changesets,
maybe_hg_replay_data.as_ref(),
&hooks[..],
)
.timed()
.await;
let mut scuba_logger = ctx.scuba().clone();
scuba_logger.add_future_stats(&stats);
match result {
Ok(ref res) => {
scuba_logger
.add("pushrebase_retry_num", res.retry_num.0)
.log_with_msg("Pushrebase finished", None);
}
Err(ref err) => {
scuba_logger.log_with_msg("Pushrebase failed", Some(format!("{:#?}", err)));
}
}
result
.await
.map(|outcome| (outcome.head, outcome.rebased_changesets))
.map_err(|err| match err {
pushrebase::PushrebaseError::Conflicts(conflicts) => {
BookmarkMovementError::PushrebaseError(PushrebaseError::Conflicts(conflicts)) => {
BundleResolverError::PushrebaseConflicts(conflicts)
}
_ => BundleResolverError::Error(format_err!("pushrebase failed {:?}", err)),
_ => BundleResolverError::Error(err.into()),
})
.map(|res| (res.head, res.rebased_changesets))
}
async fn force_pushrebase(
@ -778,36 +731,6 @@ async fn force_pushrebase(
Ok((new_target, Vec::new()))
}
fn check_plain_bookmark_move_preconditions(
ctx: &CoreContext,
bookmark: &BookmarkName,
reason: &'static str,
bookmark_attrs: &BookmarkAttrs,
infinitepush_params: &InfinitepushParams,
) -> Result<()> {
let user = ctx.user_unix_name();
if !bookmark_attrs.is_allowed_user(user, bookmark) {
return Err(format_err!(
"[{}] This user `{:?}` is not allowed to move `{:?}`",
reason,
user,
bookmark
));
}
if let Some(ref namespace) = infinitepush_params.namespace {
if namespace.matches_bookmark(bookmark) {
return Err(format_err!(
"[{}] Only Infinitepush bookmarks are allowed to match pattern {}",
reason,
namespace.as_str(),
));
}
}
Ok(())
}
async fn log_commits_to_scribe(
ctx: &CoreContext,
repo: &BlobRepo,

View File

@ -267,7 +267,6 @@ impl PushRedirector {
// entry for the large repo will point to a blobstore key, which does not
// exist in that large repo.
let PostResolvePushRebase {
any_merges,
bookmark_push_part_id,
bookmark_spec,
maybe_hg_replay_data,
@ -315,7 +314,6 @@ impl PushRedirector {
});
let action = PostResolvePushRebase {
any_merges,
bookmark_push_part_id,
bookmark_spec,
maybe_hg_replay_data,

View File

@ -200,7 +200,6 @@ pub struct PostResolveInfinitePush {
/// Data, needed to perform post-resolve `PushRebase` action
#[derive(Clone)]
pub struct PostResolvePushRebase {
pub any_merges: bool,
pub bookmark_push_part_id: Option<PartId>,
pub bookmark_spec: PushrebaseBookmarkSpec<ChangesetId>,
pub maybe_hg_replay_data: Option<HgReplayData>,
@ -570,11 +569,6 @@ async fn resolve_pushrebase<'r>(
None => return Err(format_err!("onto is not specified").into()),
};
let changesets = &cg_push.changesets.clone();
let any_merges = changesets
.iter()
.any(|(_, revlog_cs)| revlog_cs.p1.is_some() && revlog_cs.p2.is_some());
let will_rebase = onto_bookmark != *DONOTREBASEBOOKMARK;
// Mutation information must not be present in public commits
// See T54101162, S186586
@ -643,7 +637,6 @@ async fn resolve_pushrebase<'r>(
});
Ok(PostResolveAction::PushRebase(PostResolvePushRebase {
any_merges,
bookmark_push_part_id,
bookmark_spec,
maybe_hg_replay_data,

View File

@ -148,13 +148,18 @@ pushrebase
searching for changes
remote: Command failed
remote: Error:
remote: [pushrebase] This user `Some("a")` is not allowed to move `BookmarkName { bookmark: "C" }`
remote: User 'a' is not permitted to move 'C'
remote:
remote: Root cause:
remote: [pushrebase] This user `Some("a")` is not allowed to move `BookmarkName { bookmark: "C" }`
remote: User 'a' is not permitted to move 'C'
remote:
remote: Debug context:
remote: "[pushrebase] This user `Some(\"a\")` is not allowed to move `BookmarkName { bookmark: \"C\" }`"
remote: PermissionDeniedUser {
remote: user: "a",
remote: bookmark: BookmarkName {
remote: bookmark: "C",
remote: },
remote: }
abort: stream ended unexpectedly (got 0 bytes, expected 4)
[255]
$ MOCK_USERNAME="c" hgmn push -r . --to C

View File

@ -86,6 +86,8 @@ Try to push merge commit
remote: the Source Control team at https://fburl.com/27qnuyl2
remote:
remote: Debug context:
remote: "Pushrebase blocked because it contains a merge commit.\nIf you need this for a specific use case please contact\nthe Source Control team at https://fburl.com/27qnuyl2"
remote: Error(
remote: "Pushrebase blocked because it contains a merge commit.\nIf you need this for a specific use case please contact\nthe Source Control team at https://fburl.com/27qnuyl2",
remote: )
abort: stream ended unexpectedly (got 0 bytes, expected 4)
[255]

View File

@ -37,13 +37,29 @@ Push another commit that conflicts
searching for changes
remote: Command failed
remote: Error:
remote: pushrebase failed Error(Conflict detected while inserting git mappings (tried inserting: [BonsaiGitMappingEntry { git_sha1: GitSha1(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b), bcs_id: ChangesetId(Blake2(3fa7acdeb82ac4f96a7bf1e7b5fa8f661c9921954a46164cbbfa828c0485595b)) }]))
remote: Pushrebase failed
remote:
remote: Root cause:
remote: pushrebase failed Error(Conflict detected while inserting git mappings (tried inserting: [BonsaiGitMappingEntry { git_sha1: GitSha1(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b), bcs_id: ChangesetId(Blake2(3fa7acdeb82ac4f96a7bf1e7b5fa8f661c9921954a46164cbbfa828c0485595b)) }]))
remote: Conflict detected while inserting git mappings (tried inserting: [BonsaiGitMappingEntry { git_sha1: GitSha1(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b), bcs_id: ChangesetId(Blake2(3fa7acdeb82ac4f96a7bf1e7b5fa8f661c9921954a46164cbbfa828c0485595b)) }])
remote:
remote: Caused by:
remote: Conflict detected while inserting git mappings (tried inserting: [BonsaiGitMappingEntry { git_sha1: GitSha1(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b), bcs_id: ChangesetId(Blake2(3fa7acdeb82ac4f96a7bf1e7b5fa8f661c9921954a46164cbbfa828c0485595b)) }])
remote:
remote: Debug context:
remote: "pushrebase failed Error(Conflict detected while inserting git mappings (tried inserting: [BonsaiGitMappingEntry { git_sha1: GitSha1(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b), bcs_id: ChangesetId(Blake2(3fa7acdeb82ac4f96a7bf1e7b5fa8f661c9921954a46164cbbfa828c0485595b)) }]))"
remote: PushrebaseError(
remote: Error(
remote: Conflict(
remote: [
remote: BonsaiGitMappingEntry {
remote: git_sha1: GitSha1(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b),
remote: bcs_id: ChangesetId(
remote: Blake2(3fa7acdeb82ac4f96a7bf1e7b5fa8f661c9921954a46164cbbfa828c0485595b),
remote: ),
remote: },
remote: ],
remote: ),
remote: ),
remote: )
abort: stream ended unexpectedly (got 0 bytes, expected 4)
[255]

View File

@ -307,7 +307,6 @@ async fn maybe_unbundle(
};
let PostResolvePushRebase {
any_merges: _,
bookmark_push_part_id: _,
bookmark_spec,
maybe_hg_replay_data: _,