mirror of
https://github.com/facebook/sapling.git
synced 2025-01-07 14:10:42 +03:00
Short hashes lookup: implement suggestions the same way as in Mercurial.
Summary: Suggestions come in the error message as it is currently implemented in Mercurial code. Format of suggestions also stays the same. We give the hash, time, author and the title. All suggestions are ordered (most recent go first). We don't show them if there are two many. Reviewed By: krallin Differential Revision: D19732053 fbshipit-source-id: b94154cbc5a4f440a0053fc3fac2bca2ae0b7119
This commit is contained in:
parent
af2f50d644
commit
8228f84a60
@ -1315,7 +1315,7 @@ impl BlobRepo {
|
||||
})
|
||||
.traced(
|
||||
&ctx.trace(),
|
||||
"generate_hg_chengeset",
|
||||
"generate_hg_changeset",
|
||||
trace_args! {"changeset" => bcs_id.to_hex().to_string()},
|
||||
)
|
||||
.timed(move |stats, _| {
|
||||
|
@ -19,6 +19,7 @@ use failure_ext::FutureFailureErrorExt;
|
||||
use futures::future::{Either, Future, IntoFuture};
|
||||
use futures_ext::{BoxFuture, FutureExt};
|
||||
use mononoke_types::DateTime;
|
||||
use std::fmt::{self, Display};
|
||||
use std::{collections::BTreeMap, io::Write};
|
||||
|
||||
const STEP_PARENTS_METADATA_KEY: &str = "stepparents";
|
||||
@ -312,3 +313,24 @@ impl Loadable for HgChangesetId {
|
||||
.boxify()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HgBlobChangeset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let comments = self.comments();
|
||||
let title_end = comments
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &c)| c == b'\n')
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(comments.len());
|
||||
|
||||
write!(
|
||||
f,
|
||||
"changeset: {}\nauthor: {}\ndate: {}\nsummary: {}\n",
|
||||
self.changesetid,
|
||||
String::from_utf8_lossy(&self.user()),
|
||||
self.time().as_chrono().to_rfc2822(),
|
||||
String::from_utf8_lossy(&self.comments()[0..title_end])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -921,63 +921,129 @@ impl HgCommands for RepoClient {
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
fn check_bookmark_exists(
|
||||
// Generate positive response including HgChangesetId as hex.
|
||||
fn generate_changeset_resp_buf(csid: HgChangesetId) -> HgCommandRes<Bytes> {
|
||||
Ok(generate_resp_buf(true, csid.to_hex().as_bytes()))
|
||||
.into_future()
|
||||
.boxify()
|
||||
}
|
||||
|
||||
// Generate error response with the message including suggestions (commits info).
|
||||
// Suggestions are ordered by commit time (most recent first).
|
||||
fn generate_suggestions_resp_buf(
|
||||
ctx: CoreContext,
|
||||
repo: BlobRepo,
|
||||
bookmark: BookmarkName,
|
||||
suggestion_cids: Vec<HgChangesetId>,
|
||||
) -> HgCommandRes<Bytes> {
|
||||
repo.get_bookmark(ctx, &bookmark)
|
||||
.map(move |csid| match csid {
|
||||
Some(csid) => generate_resp_buf(true, csid.to_hex().as_bytes()),
|
||||
None => generate_resp_buf(false, format!("{} not found", bookmark).as_bytes()),
|
||||
let futs = suggestion_cids
|
||||
.into_iter()
|
||||
.map(|hg_csid| {
|
||||
hg_csid
|
||||
.load(ctx.clone(), repo.blobstore())
|
||||
.from_err()
|
||||
.map(move |cs| (cs.to_string().into_bytes(), cs.time().clone()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
future::join_all(futs)
|
||||
.map(|mut info_plus_date| {
|
||||
info_plus_date.sort_by_key(|&(_, time)| time);
|
||||
let mut infos = info_plus_date
|
||||
.into_iter()
|
||||
.map(|(info, _)| info)
|
||||
.collect::<Vec<_>>();
|
||||
infos.push(b"ambiguous identifier\nsuggestions are:\n".to_vec());
|
||||
infos.reverse();
|
||||
generate_resp_buf(false, &infos.join(&[b'\n'][..]))
|
||||
})
|
||||
.boxify()
|
||||
}
|
||||
|
||||
// If the string is a valid full changeset hash, parse it.
|
||||
// If the string is a valid prefix of changeset hash, resolve it to a changeset.
|
||||
// TODO(liubovd): support ambiguity and suggestions
|
||||
// Controls how many suggestions to fetch in case of ambiguous outcome of prefix lookup.
|
||||
const MAX_NUMBER_OF_SUGGESTIONS_TO_FETCH: usize = 10;
|
||||
|
||||
// Resolves changeset or set of suggestions from the key (full hex hash or a prefix) if exist.
|
||||
// Note: `get_many_hg_by_prefix` works for the full hex hashes well but
|
||||
// `changeset_exists` has better caching and is preferable for the full length hex hashes.
|
||||
let node_fut = match HgChangesetId::from_str(&key) {
|
||||
Ok(node) => ok(Some(node)).boxify(),
|
||||
Ok(csid) => repo
|
||||
.changeset_exists(ctx.clone(), csid)
|
||||
.map(move |exists| {
|
||||
if exists {
|
||||
HgChangesetIdsResolvedFromPrefix::Single(csid)
|
||||
} else {
|
||||
HgChangesetIdsResolvedFromPrefix::NoMatch
|
||||
}
|
||||
})
|
||||
.boxify(),
|
||||
Err(_) => match HgChangesetIdPrefix::from_str(&key) {
|
||||
Ok(cs_prefix) => repo
|
||||
.get_bonsai_hg_mapping()
|
||||
.get_many_hg_by_prefix(ctx.clone(), repo.get_repoid(), cs_prefix, 1)
|
||||
.map(move |resolved_cids| match resolved_cids {
|
||||
HgChangesetIdsResolvedFromPrefix::Single(cs) => Some(cs),
|
||||
_ => None,
|
||||
})
|
||||
.get_many_hg_by_prefix(
|
||||
ctx.clone(),
|
||||
repo.get_repoid(),
|
||||
cs_prefix,
|
||||
MAX_NUMBER_OF_SUGGESTIONS_TO_FETCH,
|
||||
)
|
||||
.boxify(),
|
||||
Err(_) => ok(None).boxify(),
|
||||
Err(_) => ok(HgChangesetIdsResolvedFromPrefix::NoMatch).boxify(),
|
||||
},
|
||||
};
|
||||
|
||||
let bookmark = BookmarkName::new(&key).ok();
|
||||
// The lookup order:
|
||||
// If there is an exact commit match, return that even if the key is the prefix of the hash.
|
||||
// If there is a bookmark match, return that.
|
||||
// If there are suggestions, show them. This happens in case of ambiguous outcome of prefix lookup.
|
||||
// Otherwise, show an error.
|
||||
|
||||
let bookmark = BookmarkName::new(&key).ok();
|
||||
let lookup_fut = node_fut
|
||||
.and_then(move |node| match (node, bookmark) {
|
||||
(Some(node), Some(bookmark)) => {
|
||||
let csid = node;
|
||||
repo.changeset_exists(ctx.clone(), csid)
|
||||
.and_then({
|
||||
cloned!(ctx);
|
||||
move |exists| {
|
||||
if exists {
|
||||
Ok(generate_resp_buf(true, node.to_hex().as_bytes()))
|
||||
.into_future()
|
||||
.boxify()
|
||||
} else {
|
||||
check_bookmark_exists(ctx, repo, bookmark)
|
||||
}
|
||||
.and_then(move |resolved_cids| {
|
||||
use HgChangesetIdsResolvedFromPrefix::*;
|
||||
|
||||
// Describing the priority relative to bookmark presence for the key.
|
||||
enum LookupOutcome {
|
||||
HighPriority(HgCommandRes<Bytes>),
|
||||
LowPriority(HgCommandRes<Bytes>),
|
||||
};
|
||||
|
||||
let outcome = match resolved_cids {
|
||||
Single(csid) => LookupOutcome::HighPriority(generate_changeset_resp_buf(csid)),
|
||||
Multiple(suggestion_cids) => LookupOutcome::LowPriority(
|
||||
generate_suggestions_resp_buf(ctx.clone(), repo.clone(), suggestion_cids),
|
||||
),
|
||||
TooMany(_) => LookupOutcome::LowPriority(
|
||||
Ok(generate_resp_buf(
|
||||
false,
|
||||
format!("ambiguous identifier '{}'", key).as_bytes(),
|
||||
))
|
||||
.into_future()
|
||||
.boxify(),
|
||||
),
|
||||
NoMatch => LookupOutcome::LowPriority(
|
||||
Ok(generate_resp_buf(
|
||||
false,
|
||||
format!("{} not found", key).as_bytes(),
|
||||
))
|
||||
.into_future()
|
||||
.boxify(),
|
||||
),
|
||||
};
|
||||
|
||||
match (outcome, bookmark) {
|
||||
(LookupOutcome::HighPriority(res), _) => res,
|
||||
(LookupOutcome::LowPriority(res), Some(bookmark)) => repo
|
||||
.get_bookmark(ctx.clone(), &bookmark)
|
||||
.and_then(move |maybe_csid| {
|
||||
if let Some(csid) = maybe_csid {
|
||||
generate_changeset_resp_buf(csid)
|
||||
} else {
|
||||
res
|
||||
}
|
||||
})
|
||||
.boxify()
|
||||
.boxify(),
|
||||
(LookupOutcome::LowPriority(res), None) => res,
|
||||
}
|
||||
(None, Some(bookmark)) => check_bookmark_exists(ctx.clone(), repo, bookmark),
|
||||
// Failed to parse as a hash or bookmark.
|
||||
_ => Ok(generate_resp_buf(false, "invalid input".as_bytes()))
|
||||
.into_future()
|
||||
.boxify(),
|
||||
})
|
||||
.boxify();
|
||||
|
||||
|
@ -153,6 +153,7 @@ Do infinitepush (aka commit cloud) push, to a bookmark
|
||||
remote: Unknown bookmark: scratch/123. Use --create to create one.
|
||||
abort: stream ended unexpectedly (got 0 bytes, expected 4)
|
||||
[255]
|
||||
|
||||
$ hgmn push ssh://user@dummy/repo -r . --to "scratch/123" --create
|
||||
pushing to ssh://user@dummy/repo
|
||||
searching for changes
|
||||
@ -180,6 +181,7 @@ Do infinitepush (aka commit cloud) push, to a bookmark
|
||||
remote: Non fastforward bookmark move from * to * (try --force?) (glob)
|
||||
abort: stream ended unexpectedly (got 0 bytes, expected 4)
|
||||
[255]
|
||||
|
||||
$ hgmn push ssh://user@dummy/repo -r 3903775176ed --to "scratch/123" --force
|
||||
pushing to ssh://user@dummy/repo
|
||||
searching for changes
|
||||
@ -604,6 +606,23 @@ More sophisticated test for phases
|
||||
abort: 'listkeyspatterns' command is not supported for the server ssh://user@dummy/repo
|
||||
[255]
|
||||
|
||||
$ hgmn pull -r b # test ambiguous prefix
|
||||
pulling from ssh://user@dummy/repo
|
||||
abort: ambiguous identifier
|
||||
suggestions are:
|
||||
|
||||
changeset: bf677f20a49dc5ac94946f3d91ad181f8a6fdbab
|
||||
author: test
|
||||
date: Thu, 01 Jan 1970 00:00:00 +0000
|
||||
summary: zzz
|
||||
|
||||
changeset: b9f080ea95005f3513a22aa15f1f74d7371ce5d4
|
||||
author: test
|
||||
date: Thu, 01 Jan 1970 00:00:00 +0000
|
||||
summary: zzzzz
|
||||
!
|
||||
[255]
|
||||
|
||||
$ hgmn pull -r 5e59ac0f4dd0 -r bf677f20a49d -r 7d67c7248d48 -r b9f080ea9500 -q
|
||||
|
||||
$ tglogpnr -r "::b9f080ea9500 - ::default/master_bookmark"
|
||||
|
Loading…
Reference in New Issue
Block a user