source control service blame integration

Summary: Expose Blame via source control service and integrate with scsc

Reviewed By: mitrandir77

Differential Revision: D18615736

fbshipit-source-id: 7534a3027eb3886fa153219e550ffa444521a93a
This commit is contained in:
Pavel Aslanov 2019-11-25 05:48:31 -08:00 committed by Facebook Github Bot
parent 3fd44a39f9
commit b57d8bcce5
5 changed files with 150 additions and 5 deletions

View File

@ -10,16 +10,18 @@ use std::fmt;
use std::future::Future;
use std::pin::Pin;
use blame::fetch_blame;
use bytes::Bytes;
use cloned::cloned;
use failure_ext::err_msg;
use failure_ext::{err_msg, Error};
use filestore::FetchKey;
use futures::Future as FutureLegacy;
use futures_preview::compat::{Future01CompatExt, Stream01CompatExt};
use futures_preview::future::{FutureExt, Shared};
use futures_util::{try_join, try_stream::TryStreamExt};
use manifest::{Entry, ManifestOps};
use mononoke_types::{
ChangesetId, ContentId, FileType, FileUnodeId, FsnodeId, MPath, ManifestUnodeId,
Blame, ChangesetId, ContentId, FileType, FileUnodeId, FsnodeId, MPath, ManifestUnodeId,
};
use xdiff;
@ -197,6 +199,20 @@ impl ChangesetPathContext {
};
Ok(entry)
}
pub async fn blame(&self) -> Result<(Bytes, Blame), MononokeError> {
let ctx = self.changeset.ctx().clone();
let repo = self.changeset.repo().blob_repo().clone();
let csid = self.changeset.id();
let path = self.mpath.as_ref().ok_or_else(|| {
MononokeError::InvalidRequest(format!("Blame is not available for directory: `/`"))
})?;
fetch_blame(ctx, repo, csid, path.clone())
.map_err(|error| MononokeError::from(Error::from(error)))
.compat()
.await
}
}
/// Renders the diff (in the git diff format) against some other path.

View File

@ -88,6 +88,7 @@ impl_into_thrift_error!(service::CommitInfoExn);
impl_into_thrift_error!(service::CommitCompareExn);
impl_into_thrift_error!(service::CommitIsAncestorOfExn);
impl_into_thrift_error!(service::CommitPathInfoExn);
impl_into_thrift_error!(service::CommitPathBlameExn);
impl_into_thrift_error!(service::TreeListExn);
impl_into_thrift_error!(service::FileExistsExn);
impl_into_thrift_error!(service::FileInfoExn);

View File

@ -414,7 +414,7 @@ impl Blame {
Blame::new(ranges)
}
fn lines<'a>(&'a self) -> BlameLines<'a> {
pub fn lines<'a>(&'a self) -> BlameLines<'a> {
BlameLines::new(&self.ranges)
}
@ -509,7 +509,7 @@ fn blame_merge(csid: ChangesetId, blames: Vec<Blame>) -> Result<Blame, Error> {
/// Iterator over balme object as if it was just a list of lines with associated
/// changeset id and path. Implementation is not cloning anyting.
struct BlameLines<'a> {
pub struct BlameLines<'a> {
ranges: &'a Vec<BlameRange>,
ranges_index: usize,
index: u32,

View File

@ -7,9 +7,10 @@
*/
use std::cmp::min;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::convert::{TryFrom, TryInto};
use std::fmt::{Debug, Display};
use std::iter::FromIterator;
use std::ops::RangeBounds;
use std::sync::Arc;
@ -803,6 +804,7 @@ mod errors {
impl_into_thrift_error!(service::CommitCompareExn);
impl_into_thrift_error!(service::CommitIsAncestorOfExn);
impl_into_thrift_error!(service::CommitPathInfoExn);
impl_into_thrift_error!(service::CommitPathBlameExn);
impl_into_thrift_error!(service::TreeListExn);
impl_into_thrift_error!(service::FileExistsExn);
impl_into_thrift_error!(service::FileInfoExn);
@ -1246,6 +1248,80 @@ impl SourceControlService for SourceControlServiceImpl {
Ok(response)
}
async fn commit_path_blame(
&self,
commit_path: thrift::CommitPathSpecifier,
params: thrift::CommitPathBlameParams,
) -> Result<thrift::CommitPathBlameResponse, service::CommitPathBlameExn> {
let ctx = self.create_ctx(Some(&commit_path));
let (repo, changeset) = self.repo_changeset(ctx, &commit_path.commit).await?;
let path = changeset.path(&commit_path.path)?;
let (content, blame) = path.blame().await?;
let csids: Vec<_> = blame.ranges().iter().map(|range| range.csid).collect();
let identities = map_commit_identities(
&repo,
csids.clone(),
&BTreeSet::from_iter(Some(params.identity_scheme)),
)
.await?;
// author and date fields
let info: HashMap<_, _> = try_join_all(csids.into_iter().map(move |csid| {
let repo = repo.clone();
async move {
let changeset = repo
.changeset(ChangesetSpecifier::Bonsai(csid))
.await?
.ok_or_else(|| {
MononokeError::InvalidRequest(format!("failed to resolve commit: {}", csid))
})?;
let date = changeset.author_date().await?;
let date = thrift::DateTime {
timestamp: date.timestamp(),
tz: date.offset().local_minus_utc(),
};
let author = changeset.author().await?;
Ok::<_, MononokeError>((csid, (author, date)))
}
}))
.await?
.into_iter()
.collect();
let lines = String::from_utf8_lossy(content.as_ref())
.lines()
.zip(blame.lines())
.enumerate()
.map(
|(line, (contents, (csid, path)))| -> Result<_, thrift::RequestError> {
let commit = identities
.get(&csid)
.and_then(|ids| ids.get(&params.identity_scheme))
.ok_or_else(|| {
errors::commit_not_found(format!("failed to resolve commit: {}", csid))
})?;
let (author, date) = info.get(&csid).ok_or_else(|| {
errors::commit_not_found(format!("failed to resolve commit: {}", csid))
})?;
Ok(thrift::BlameVerboseLine {
line: (line + 1) as i32,
contents: contents.to_string(),
commit: commit.clone(),
path: path.to_string(),
author: author.clone(),
date: date.clone(),
})
},
)
.collect::<Result<Vec<_>, _>>()?;
let blame = thrift::BlameVerbose { lines };
Ok(thrift::CommitPathBlameResponse {
blame: thrift::Blame::blame_verbose(blame),
})
}
/// List the contents of a directory.
async fn tree_list(
&self,

View File

@ -140,6 +140,58 @@ diff
+e
+x
blame
$ scsc blame --repo repo -i "$COMMIT_C" --path b
323afe77a1b1e632e54e8d5a683ba2cc8511f299: b
c29e0e474e30ae40ed639fa6292797a7502bc590: c
323afe77a1b1e632e54e8d5a683ba2cc8511f299: d
323afe77a1b1e632e54e8d5a683ba2cc8511f299: e
323afe77a1b1e632e54e8d5a683ba2cc8511f299: f
$ scsc --json blame --repo repo -i "$COMMIT_C" --path b | jq -S .
[
{
"author": "test",
"commit": "323afe77a1b1e632e54e8d5a683ba2cc8511f299",
"contents": "b",
"datetime": "1970-01-01T00:00:00+00:00",
"line": 1,
"path": "b"
},
{
"author": "test",
"commit": "c29e0e474e30ae40ed639fa6292797a7502bc590",
"contents": "c",
"datetime": "1970-01-01T00:00:00+00:00",
"line": 2,
"path": "b"
},
{
"author": "test",
"commit": "323afe77a1b1e632e54e8d5a683ba2cc8511f299",
"contents": "d",
"datetime": "1970-01-01T00:00:00+00:00",
"line": 3,
"path": "b"
},
{
"author": "test",
"commit": "323afe77a1b1e632e54e8d5a683ba2cc8511f299",
"contents": "e",
"datetime": "1970-01-01T00:00:00+00:00",
"line": 4,
"path": "b"
},
{
"author": "test",
"commit": "323afe77a1b1e632e54e8d5a683ba2cc8511f299",
"contents": "f",
"datetime": "1970-01-01T00:00:00+00:00",
"line": 5,
"path": "b"
}
]
lookup using bookmarks
$ scsc lookup --repo repo -B BOOKMARK_B
323afe77a1b1e632e54e8d5a683ba2cc8511f299