mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
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:
parent
ce09aa130d
commit
1bb5f676e4
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user