use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::Infallible;
use std::str::FromStr;
use crate::pdb::{PdbError, PdbErrorKind};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SourceServerError {
#[error("missing VCS name in srcsrv stream")]
MissingSourceServerVcs,
}
#[derive(Debug, Clone)]
pub struct SourceServerInfo {
pub path: String,
pub revision: Option<String>,
}
#[derive(Debug, Clone)]
enum SourceServerVcs {
Perforce,
Unknown(String),
}
impl SourceServerVcs {
pub fn name(&self) -> &str {
match self {
SourceServerVcs::Perforce => "Perforce",
SourceServerVcs::Unknown(s) => s,
}
}
}
impl FromStr for SourceServerVcs {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("perforce") {
Ok(Self::Perforce)
} else {
Ok(Self::Unknown(s.to_owned()))
}
}
}
pub struct SourceServerMappings<'s> {
vcs: SourceServerVcs,
cache: RefCell<HashMap<String, Option<SourceServerInfo>>>,
stream: srcsrv::SrcSrvStream<'s>,
}
impl<'s> SourceServerMappings<'s> {
pub fn parse(srcsrv_stream: &'s [u8]) -> Result<Self, PdbError> {
let stream = srcsrv::SrcSrvStream::parse(srcsrv_stream)
.map_err(|e| PdbError::new(PdbErrorKind::BadObject, e))?;
let vcs = stream
.version_control_description()
.ok_or(PdbError::new(
PdbErrorKind::BadObject,
SourceServerError::MissingSourceServerVcs,
))?
.parse()
.unwrap_or_else(|e| match e {});
Ok(Self {
vcs,
cache: Default::default(),
stream,
})
}
fn compute_info(
vcs: &SourceServerVcs,
stream: &srcsrv::SrcSrvStream,
path: &str,
) -> Option<SourceServerInfo> {
let Ok(Some((_method, var_map))) = stream.source_and_raw_var_values_for_path(path, "")
else {
return None;
};
match vcs {
SourceServerVcs::Perforce => {
let depot_path = var_map.get("var3")?;
let changelist = var_map.get("var4");
let depot = depot_path.trim_start_matches("//");
let revision = changelist.filter(|cl| !cl.is_empty());
Some(SourceServerInfo {
path: depot.to_owned(),
revision: revision.map(|s| s.to_owned()),
})
}
SourceServerVcs::Unknown(_) => None,
}
}
pub fn get_info(&self, path: &str) -> Option<SourceServerInfo> {
self.cache
.borrow_mut()
.entry(path.to_owned())
.or_insert_with(|| Self::compute_info(&self.vcs, &self.stream, path))
.clone()
}
pub fn vcs_name(&self) -> &str {
self.vcs.name()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_perforce_mapping() {
let stream = br#"SRCSRV: ini ------------------------------------------------
VERSION=1
VERCTRL=Perforce
SRCSRV: variables ------------------------------------------
SRCSRVTRG=%targ%\%var2%\%fnbksl%(%var3%)
SRCSRVCMD=
SRCSRV: source files ---------------------------------------
c:\projects\breakpad-tools\deps\breakpad\src\client\windows\crash_generation\crash_generation_client.cc*P4_SERVER*depot/breakpad/src/client/windows/crash_generation/crash_generation_client.cc*12345
c:\projects\breakpad-tools\deps\breakpad\src\common\scoped_ptr.h*P4_SERVER*depot/breakpad/src/common/scoped_ptr.h*12346
c:\projects\breakpad-tools\deps\breakpad\src\common\windows\string_utils-inl.h*P4_SERVER*depot/breakpad/src/common/windows/string_utils-inl.h*12347
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.13.26128\include\system_error*P4_SERVER*depot/msvc/2017/include/system_error*67890
SRCSRV: end ------------------------------------------------"#;
let mappings = SourceServerMappings::parse(stream).unwrap();
let info = mappings.get_info(r"c:\projects\breakpad-tools\deps\breakpad\src\client\windows\crash_generation\crash_generation_client.cc").unwrap();
assert_eq!(
info.path,
"depot/breakpad/src/client/windows/crash_generation/crash_generation_client.cc"
);
assert_eq!(info.revision.as_deref(), Some("12345"));
}
}