add a command to mononoke_admin to list bookmark moves log

Summary:
Add a functionality to show the log of a bookmark i.e. show previous positions of the bookmark. It should look like

mononoke_admin bookmarks log master
2d0e180d7fbec1fd9825cfb246e1fecde31d8c35 push March 18, 15:03
9740f4b7f09a8958c90dc66cbb2c79d1d7da0555 push March 17, 15:03
b05aafb29abb61f59f12fea13a907164e54ff683 manual move March 17, 15:03
...

Reviewed By: StanislavGlebik

Differential Revision: D14639245

fbshipit-source-id: 59d6a559a7ba9f9537735fa2e36fbd0f3f9db77c
This commit is contained in:
Igor Abramov 2019-04-01 09:57:10 -07:00 committed by Facebook Github Bot
parent ce09aa130d
commit 1bb5f676e4
5 changed files with 286 additions and 9 deletions

View File

@ -21,7 +21,7 @@ use crate::{BlobManifest, HgBlobChangeset};
use blob_changeset::{ChangesetMetadata, HgChangesetContent, RepoBlobstore};
use blobstore::Blobstore;
use bonsai_hg_mapping::{BonsaiHgMapping, BonsaiHgMappingEntry, BonsaiOrHgChangesetIds};
use bookmarks::{self, Bookmark, BookmarkPrefix, Bookmarks};
use bookmarks::{self, Bookmark, BookmarkPrefix, BookmarkUpdateReason, Bookmarks};
use bytes::Bytes;
use cacheblob::MemWritesBlobstore;
use changeset_fetcher::{ChangesetFetcher, SimpleChangesetFetcher};
@ -43,7 +43,7 @@ use mercurial_types::{
use mononoke_types::{
hash::Blake2, hash::Sha256, Blob, BlobstoreBytes, BlobstoreValue, BonsaiChangeset, ChangesetId,
ContentId, FileChange, FileContents, FileType, Generation, MPath, MPathElement, MononokeId,
RepositoryId,
RepositoryId, Timestamp,
};
use prefixblob::PrefixBlobstore;
use scuba_ext::{ScubaSampleBuilder, ScubaSampleBuilderExt};
@ -632,6 +632,17 @@ impl BlobRepo {
self.bookmarks.get(ctx, name, self.repoid)
}
pub fn list_bookmark_log_entries(
&self,
ctx: CoreContext,
name: Bookmark,
max_rec: u32,
) -> impl Stream<Item=(Option<ChangesetId>, BookmarkUpdateReason, Timestamp), Error=Error> {
self.bookmarks
.list_bookmark_log_entries(ctx.clone(), name, self.repoid, max_rec)
}
/// Heads maybe read from replica, so they may be out of date. Prefer to use this method
/// over `get_bookmarks` unless you need the most up-to-date bookmarks
pub fn get_bookmarks_maybe_stale(

View File

@ -149,6 +149,17 @@ queries! {
LIMIT 1"
}
read SelectBookmarkLogs(repo_id: RepositoryId, name: Bookmark, max_records: u32) -> (
Option<ChangesetId>, BookmarkUpdateReason, Timestamp
) {
"SELECT to_changeset_id, reason, timestamp
FROM bookmarks_update_log
WHERE repo_id = {repo_id}
AND name = {name}
ORDER BY id DESC
LIMIT {max_records}"
}
read SelectAll(repo_id: RepositoryId) -> (Bookmark, ChangesetId) {
"SELECT name, changeset_id
FROM bookmarks
@ -223,6 +234,19 @@ impl Bookmarks for SqlBookmarks {
.boxify()
}
fn list_bookmark_log_entries(
&self,
_ctx: CoreContext,
name: Bookmark,
repo_id: RepositoryId,
max_rec: u32,
) -> BoxStream<(Option<ChangesetId>, BookmarkUpdateReason, Timestamp), Error> {
SelectBookmarkLogs::query(&self.read_master_connection, &repo_id, &name, &max_rec)
.map(|rows| stream::iter_ok(rows))
.flatten_stream()
.boxify()
}
fn list_by_prefix(
&self,
_ctx: CoreContext,

View File

@ -1081,3 +1081,98 @@ fn test_read_log_entry_many_repos() {
.unwrap()
.is_none());
}
#[test]
fn test_list_bookmark_log_entries() {
let ctx = CoreContext::test_mock();
let bookmarks = SqlBookmarks::with_sqlite_in_memory().unwrap();
let name_1 = create_bookmark("book");
let mut txn = bookmarks.create_transaction(ctx.clone(), REPO_ZERO);
txn.force_set(
&name_1,
ONES_CSID,
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
},
)
.unwrap();
assert!(txn.commit().wait().is_ok());
let mut txn = bookmarks.create_transaction(ctx.clone(), REPO_ZERO);
txn.update(
&name_1,
TWOS_CSID,
ONES_CSID,
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
},
)
.unwrap();
txn.commit().wait().unwrap();
let mut txn = bookmarks.create_transaction(ctx.clone(), REPO_ZERO);
txn.update(
&name_1,
THREES_CSID,
TWOS_CSID,
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
},
)
.unwrap();
txn.commit().wait().unwrap();
let mut txn = bookmarks.create_transaction(ctx.clone(), REPO_ZERO);
txn.update(
&name_1,
FOURS_CSID,
THREES_CSID,
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
},
)
.unwrap();
txn.commit().wait().unwrap();
let mut txn = bookmarks.create_transaction(ctx.clone(), REPO_ZERO);
txn.update(
&name_1,
FIVES_CSID,
FOURS_CSID,
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
},
)
.unwrap();
txn.commit().wait().unwrap();
assert_eq!(
bookmarks
.list_bookmark_log_entries(ctx.clone(), name_1, REPO_ZERO, 3)
.map(|(cs, rs, _ts)| (cs, rs)) // dropping timestamps
.collect()
.wait()
.unwrap(),
vec![
(
Some(FIVES_CSID),
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
}
),
(
Some(FOURS_CSID),
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
}
),
(
Some(THREES_CSID),
BookmarkUpdateReason::TestMove {
bundle_replay_data: None,
}
),
]
);
}

View File

@ -171,6 +171,15 @@ pub trait Bookmarks: Send + Sync + 'static {
repoid: RepositoryId,
) -> BoxFuture<Option<BookmarkUpdateLogEntry>, Error>;
/// Read the log entry for specific bookmark with specified to changeset id.
fn list_bookmark_log_entries(
&self,
_ctx: CoreContext,
name: Bookmark,
repo_id: RepositoryId,
max_rec: u32,
) -> BoxStream<(Option<ChangesetId>, BookmarkUpdateReason, Timestamp), Error>;
/// Count the number of BookmarkUpdateLog entries wiht id greater than the give value
fn count_further_bookmark_log_entries(
&self,

View File

@ -5,18 +5,22 @@
// GNU General Public License version 2 or any later version.
use clap::{App, Arg, ArgMatches, SubCommand};
use cloned::cloned;
use context::CoreContext;
use failure_ext::Error;
use futures::{future, Future};
use futures::{future, Future, Stream};
use futures_ext::{try_boxfuture, BoxFuture, FutureExt};
use mercurial_types::HgChangesetId;
use mononoke_types::{DateTime, Timestamp};
use serde_json::{json, to_string_pretty};
use slog::Logger;
use blobrepo::BlobRepo;
use bookmarks::{Bookmark, BookmarkUpdateReason};
use context::CoreContext;
const SET_CMD: &'static str = "set";
const GET_CMD: &'static str = "get";
const LOG_CMD: &'static str = "log";
pub fn prepare_command<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
let set = SubCommand::with_name(SET_CMD)
@ -47,20 +51,48 @@ pub fn prepare_command<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
.help("What changeset type to return, either bonsai or hg. Defaults to hg."),
);
let log = SubCommand::with_name(LOG_CMD)
.about("gets the log of changesets for a specific bookmark")
.args_from_usage(
r#"
<BOOKMARK_NAME> 'bookmark to target'
--json 'if provided json will be returned'
"#,
)
.arg(
Arg::with_name("changeset-type")
.long("changeset-type")
.short("cs")
.takes_value(true)
.possible_values(&["bonsai", "hg"])
.required(false)
.help("What changeset type to return, either bonsai or hg. Defaults to hg."),
)
.arg(
Arg::with_name("limit")
.long("limit")
.short("l")
.takes_value(true)
.required(false)
.help("Imposes the limit on number of log records in output."),
);
app.about("set of commands to manipulate bookmarks")
.subcommand(set)
.subcommand(get)
.subcommand(log)
}
pub fn handle_command<'a>(
ctx: CoreContext,
repo: BoxFuture<BlobRepo, Error>,
matches: &ArgMatches<'a>,
logger: Logger,
_logger: Logger,
) -> BoxFuture<(), Error> {
match matches.subcommand() {
(GET_CMD, Some(sub_m)) => handle_get(sub_m, ctx, logger, repo),
(SET_CMD, Some(sub_m)) => handle_set(sub_m, ctx, logger, repo),
(GET_CMD, Some(sub_m)) => handle_get(sub_m, ctx, repo),
(SET_CMD, Some(sub_m)) => handle_set(sub_m, ctx, repo),
(LOG_CMD, Some(sub_m)) => handle_log(sub_m, ctx, repo),
_ => {
println!("{}", matches.usage());
::std::process::exit(1);
@ -83,7 +115,6 @@ fn format_output(json_flag: bool, changeset_id: String, changeset_type: &str) ->
fn handle_get<'a>(
args: &ArgMatches<'a>,
ctx: CoreContext,
_logger: Logger,
repo: BoxFuture<BlobRepo, Error>,
) -> BoxFuture<(), Error> {
let bookmark_name = args.value_of("BOOKMARK_NAME").unwrap().to_string();
@ -119,10 +150,117 @@ fn handle_get<'a>(
}
}
fn format_log_output(
json_flag: bool,
changeset_id: String,
reason: BookmarkUpdateReason,
timestamp: Timestamp,
changeset_type: &str,
) -> String {
let reason_str = reason.to_string();
if json_flag {
let answer = json!({
"changeset_type": changeset_type,
"changeset_id": changeset_id,
"reason": reason_str,
"timestamp_sec": timestamp.timestamp_seconds()
});
to_string_pretty(&answer).unwrap()
} else {
let dt: DateTime = timestamp.into();
let dts = dt.as_chrono().format("%b %e %T %Y");
format!(
"({}) {} {} {}",
changeset_type.to_uppercase(),
changeset_id,
reason,
dts
)
}
}
fn list_hg_bookmark_log_entries(
repo: BlobRepo,
ctx: CoreContext,
name: Bookmark,
max_rec: u32,
) -> impl Stream<
Item = BoxFuture<(Option<HgChangesetId>, BookmarkUpdateReason, Timestamp), Error>,
Error = Error,
> {
repo.list_bookmark_log_entries(ctx.clone(), name, max_rec)
.map({
cloned!(ctx, repo);
move |(cs_id, rs, ts)| match cs_id {
Some(x) => repo
.get_hg_from_bonsai_changeset(ctx.clone(), x)
.map(move |cs| (Some(cs), rs, ts))
.boxify(),
None => future::ok((None, rs, ts)).boxify(),
}
})
}
fn handle_log<'a>(
args: &ArgMatches<'a>,
ctx: CoreContext,
repo: BoxFuture<BlobRepo, Error>,
) -> BoxFuture<(), Error> {
let bookmark_name = args.value_of("BOOKMARK_NAME").unwrap().to_string();
let bookmark = Bookmark::new(bookmark_name).unwrap();
let changeset_type = args.value_of("changeset-type").unwrap_or("hg");
let json_flag = args.is_present("json");
let output_limit_as_string = args.value_of("limit").unwrap_or("25");
let max_rec = match output_limit_as_string.parse::<u32>() {
Ok(n) => n,
Err(e) => panic!(
"Bad limit value supplied: \"{}\" - {}",
output_limit_as_string, e
),
};
match changeset_type {
"hg" => repo
.and_then(move |repo| {
list_hg_bookmark_log_entries(repo, ctx, bookmark, max_rec)
.buffered(100)
.map(move |rows| {
let (cs_id, reason, timestamp) = rows;
let cs_id_str = match cs_id {
None => String::new(),
Some(x) => x.to_string(),
};
let output =
format_log_output(json_flag, cs_id_str, reason, timestamp, "hg");
println!("{}", output);
})
.for_each(|_x| Ok(()))
})
.boxify(),
"bonsai" => repo
.and_then(move |repo| {
repo.list_bookmark_log_entries(ctx, bookmark, max_rec)
.map(move |rows| {
let (cs_id, reason, timestamp) = rows;
let cs_id_str = match cs_id {
None => String::new(),
Some(x) => x.to_string(),
};
let output =
format_log_output(json_flag, cs_id_str, reason, timestamp, "bonsai");
println!("{}", output);
})
.for_each(|_| Ok(()))
})
.boxify(),
_ => panic!("Unknown changeset-type supplied"),
}
}
fn handle_set<'a>(
args: &ArgMatches<'a>,
ctx: CoreContext,
_logger: Logger,
repo: BoxFuture<BlobRepo, Error>,
) -> BoxFuture<(), Error> {
let bookmark_name = args.value_of("BOOKMARK_NAME").unwrap().to_string();