Integrate release recovery, semantic review, coverage and Windows packaging
This commit is contained in:
@@ -20,6 +20,50 @@ pub const LIMITS: ProjectLimits = ProjectLimits {
|
||||
|
||||
const DRAFT_HISTORY_BYTES: usize = 32 * 1024 * 1024;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RecoveryWorkspace {
|
||||
entry: String,
|
||||
active: String,
|
||||
files: BTreeMap<String, (String, String)>,
|
||||
reachable: BTreeSet<String>,
|
||||
applied: bool,
|
||||
new_path: String,
|
||||
}
|
||||
|
||||
impl RecoveryWorkspace {
|
||||
/// Check the same bounds before replacing a checkpoint and before restoring it.
|
||||
pub(crate) fn validate(&self) -> Result<(), String> {
|
||||
if self.files.len() > LIMITS.max_files
|
||||
|| !self.files.contains_key(&self.entry)
|
||||
|| !self.files.contains_key(&self.active)
|
||||
|| !self.reachable.contains(&self.entry)
|
||||
|| self.reachable.len() > LIMITS.max_include_edges + 1
|
||||
{
|
||||
return Err("Recovery has an invalid file count, entry or active file.".into());
|
||||
}
|
||||
if self.files.values().fold(0usize, |bytes, (text, base)| {
|
||||
bytes.saturating_add(text.len()).saturating_add(base.len())
|
||||
}) > LIMITS.model.max_source_bytes.saturating_mul(2)
|
||||
{
|
||||
return Err("Recovery source buffers exceed the size limit.".into());
|
||||
}
|
||||
for path in self.files.keys().chain(self.reachable.iter()) {
|
||||
// A recovery buffer is never a filesystem path to open implicitly.
|
||||
if path.starts_with('/')
|
||||
|| path.contains('\\')
|
||||
|| path.contains(':')
|
||||
|| path
|
||||
.split('/')
|
||||
.any(|part| part == ".." || part == "." || part.is_empty())
|
||||
{
|
||||
return Err("Recovery contains an unsafe source path.".into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Buffer {
|
||||
pub text: String,
|
||||
baseline: String,
|
||||
@@ -57,6 +101,41 @@ pub struct Workspace {
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn recovery_snapshot(&self) -> RecoveryWorkspace {
|
||||
RecoveryWorkspace {
|
||||
entry: self.entry.clone(),
|
||||
active: self.active.clone(),
|
||||
files: self
|
||||
.files
|
||||
.iter()
|
||||
.map(|(path, buffer)| {
|
||||
(path.clone(), (buffer.text.clone(), buffer.baseline.clone()))
|
||||
})
|
||||
.collect(),
|
||||
reachable: self.reachable.clone(),
|
||||
applied: self.applied,
|
||||
new_path: self.new_path.clone(),
|
||||
}
|
||||
}
|
||||
pub fn from_recovery(saved: RecoveryWorkspace) -> Result<Self, String> {
|
||||
saved.validate()?;
|
||||
Ok(Self {
|
||||
entry: saved.entry,
|
||||
active: saved.active,
|
||||
files: saved
|
||||
.files
|
||||
.into_iter()
|
||||
.map(|(path, (text, baseline))| {
|
||||
let mut buffer = Buffer::new(text);
|
||||
buffer.baseline = baseline;
|
||||
(path, buffer)
|
||||
})
|
||||
.collect(),
|
||||
reachable: saved.reachable,
|
||||
applied: saved.applied,
|
||||
new_path: saved.new_path,
|
||||
})
|
||||
}
|
||||
pub fn new(bundle: &SourceBundle, applied: bool) -> Self {
|
||||
Self {
|
||||
entry: bundle.entry.clone(),
|
||||
|
||||
Reference in New Issue
Block a user