mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 14:58:03 +03:00
workingcopy: get rid of treestate abstraction
Summary: These traits were previously used to abstract the treetate in tests, but the treestate is easy enough to work with. I think it's much simpler overall to use the concrete TreeState in code and tests (more tests to follow). Reviewed By: zzl0 Differential Revision: D43501754 fbshipit-source-id: 6b106d0d814afbaba14b58e5f2f2fd23c519eeb6
This commit is contained in:
parent
153d42fe7e
commit
b02faa5be3
@ -10,7 +10,6 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use parking_lot::Mutex;
|
|
||||||
use pathmatcher::Matcher;
|
use pathmatcher::Matcher;
|
||||||
use repolock::RepoLocker;
|
use repolock::RepoLocker;
|
||||||
use treestate::dirstate;
|
use treestate::dirstate;
|
||||||
@ -26,147 +25,110 @@ use watchman_client::prelude::*;
|
|||||||
|
|
||||||
use crate::util::walk_treestate;
|
use crate::util::walk_treestate;
|
||||||
|
|
||||||
pub trait WatchmanTreeStateWrite {
|
pub fn mark_needs_check(ts: &mut TreeState, path: &RepoPathBuf) -> Result<bool> {
|
||||||
fn mark_needs_check(&mut self, path: &RepoPathBuf) -> Result<bool>;
|
let state = ts.get(path)?;
|
||||||
|
let filestate = match state {
|
||||||
fn clear_needs_check(&mut self, path: &RepoPathBuf) -> Result<bool>;
|
Some(filestate) => {
|
||||||
|
|
||||||
fn set_clock(&mut self, clock: Clock) -> Result<()>;
|
|
||||||
|
|
||||||
fn flush(self, locker: &RepoLocker) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait WatchmanTreeStateRead {
|
|
||||||
fn list_needs_check(
|
|
||||||
&mut self,
|
|
||||||
matcher: Arc<dyn Matcher + Send + Sync + 'static>,
|
|
||||||
) -> Result<(Vec<RepoPathBuf>, Vec<ParseError>)>;
|
|
||||||
|
|
||||||
fn get_clock(&self) -> Result<Option<Clock>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct WatchmanTreeState<'a> {
|
|
||||||
pub treestate: Arc<Mutex<TreeState>>,
|
|
||||||
pub root: &'a Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WatchmanTreeStateWrite for WatchmanTreeState<'_> {
|
|
||||||
fn mark_needs_check(&mut self, path: &RepoPathBuf) -> Result<bool> {
|
|
||||||
let mut treestate = self.treestate.lock();
|
|
||||||
|
|
||||||
let state = treestate.get(path)?;
|
|
||||||
let filestate = match state {
|
|
||||||
Some(filestate) => {
|
|
||||||
let filestate = filestate.clone();
|
|
||||||
if filestate.state.intersects(StateFlags::NEED_CHECK) {
|
|
||||||
// It's already marked need_check, so return early so we don't mutate the
|
|
||||||
// treestate.
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
FileStateV2 {
|
|
||||||
state: filestate.state | StateFlags::NEED_CHECK,
|
|
||||||
..filestate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The file is currently untracked
|
|
||||||
None => FileStateV2 {
|
|
||||||
state: StateFlags::NEED_CHECK,
|
|
||||||
mode: 0o666,
|
|
||||||
size: -1,
|
|
||||||
mtime: -1,
|
|
||||||
copied: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
treestate.insert(path, &filestate)?;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_needs_check(&mut self, path: &RepoPathBuf) -> Result<bool> {
|
|
||||||
let mut treestate = self.treestate.lock();
|
|
||||||
|
|
||||||
let state = treestate.get(path)?;
|
|
||||||
if let Some(filestate) = state {
|
|
||||||
let filestate = filestate.clone();
|
let filestate = filestate.clone();
|
||||||
if !filestate.state.intersects(StateFlags::NEED_CHECK) {
|
if filestate.state.intersects(StateFlags::NEED_CHECK) {
|
||||||
// It's already clear.
|
// It's already marked need_check, so return early so we don't mutate the
|
||||||
|
// treestate.
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
let filestate = FileStateV2 {
|
FileStateV2 {
|
||||||
state: filestate.state & !StateFlags::NEED_CHECK,
|
state: filestate.state | StateFlags::NEED_CHECK,
|
||||||
..filestate
|
..filestate
|
||||||
};
|
}
|
||||||
treestate.insert(path, &filestate)?;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
}
|
||||||
Ok(false)
|
// The file is currently untracked
|
||||||
}
|
None => FileStateV2 {
|
||||||
|
state: StateFlags::NEED_CHECK,
|
||||||
|
mode: 0o666,
|
||||||
|
size: -1,
|
||||||
|
mtime: -1,
|
||||||
|
copied: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ts.insert(path, &filestate)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
fn set_clock(&mut self, clock: Clock) -> Result<()> {
|
pub fn clear_needs_check(ts: &mut TreeState, path: &RepoPathBuf) -> Result<bool> {
|
||||||
let mut treestate = self.treestate.lock();
|
let state = ts.get(path)?;
|
||||||
|
if let Some(filestate) = state {
|
||||||
let clock_string = match clock {
|
let filestate = filestate.clone();
|
||||||
Clock::Spec(ClockSpec::StringClock(string)) => Ok(string),
|
if !filestate.state.intersects(StateFlags::NEED_CHECK) {
|
||||||
clock => Err(anyhow!(
|
// It's already clear.
|
||||||
"Watchman implementation only handles opaque string type. Got the following clock instead: {:?}",
|
return Ok(false);
|
||||||
clock
|
|
||||||
)),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let mut metadata_buf = treestate.get_metadata();
|
|
||||||
let mut metadata = Metadata::deserialize(&mut metadata_buf)?;
|
|
||||||
metadata.0.insert("clock".to_string(), clock_string);
|
|
||||||
let mut metadata_buf = vec![];
|
|
||||||
metadata.serialize(&mut metadata_buf)?;
|
|
||||||
treestate.set_metadata(&metadata_buf);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(self, locker: &RepoLocker) -> Result<()> {
|
|
||||||
match dirstate::flush(&self.root, &mut self.treestate.lock(), locker) {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
// If the dirstate was changed before we flushed, that's ok. Let the other write win
|
|
||||||
// since writes during status are just optimizations.
|
|
||||||
Err(e) => match e.downcast_ref::<ErrorKind>() {
|
|
||||||
Some(e) if *e == ErrorKind::TreestateOutOfDate => Ok(()),
|
|
||||||
_ => Err(e),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
let filestate = FileStateV2 {
|
||||||
|
state: filestate.state & !StateFlags::NEED_CHECK,
|
||||||
|
..filestate
|
||||||
|
};
|
||||||
|
ts.insert(path, &filestate)?;
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_clock(ts: &mut TreeState, clock: Clock) -> Result<()> {
|
||||||
|
let clock_string = match clock {
|
||||||
|
Clock::Spec(ClockSpec::StringClock(string)) => Ok(string),
|
||||||
|
clock => Err(anyhow!(
|
||||||
|
"Watchman implementation only handles opaque string type. Got the following clock instead: {:?}",
|
||||||
|
clock
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let mut metadata_buf = ts.get_metadata();
|
||||||
|
let mut metadata = Metadata::deserialize(&mut metadata_buf)?;
|
||||||
|
metadata.0.insert("clock".to_string(), clock_string);
|
||||||
|
let mut metadata_buf = vec![];
|
||||||
|
metadata.serialize(&mut metadata_buf)?;
|
||||||
|
ts.set_metadata(&metadata_buf);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(root: &Path, ts: &mut TreeState, locker: &RepoLocker) -> Result<()> {
|
||||||
|
match dirstate::flush(root, ts, locker) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
// If the dirstate was changed before we flushed, that's ok. Let the other write win
|
||||||
|
// since writes during status are just optimizations.
|
||||||
|
Err(e) => match e.downcast_ref::<ErrorKind>() {
|
||||||
|
Some(e) if *e == ErrorKind::TreestateOutOfDate => Ok(()),
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WatchmanTreeStateRead for WatchmanTreeState<'_> {
|
pub fn list_needs_check(
|
||||||
fn list_needs_check(
|
ts: &mut TreeState,
|
||||||
&mut self,
|
matcher: Arc<dyn Matcher + Send + Sync + 'static>,
|
||||||
matcher: Arc<dyn Matcher + Send + Sync + 'static>,
|
) -> Result<(Vec<RepoPathBuf>, Vec<ParseError>)> {
|
||||||
) -> Result<(Vec<RepoPathBuf>, Vec<ParseError>)> {
|
let mut needs_check = Vec::new();
|
||||||
let mut needs_check = Vec::new();
|
|
||||||
|
|
||||||
let parse_errs = walk_treestate(
|
let parse_errs = walk_treestate(
|
||||||
&mut self.treestate.lock(),
|
ts,
|
||||||
matcher,
|
matcher,
|
||||||
StateFlags::NEED_CHECK,
|
StateFlags::NEED_CHECK,
|
||||||
StateFlags::empty(),
|
StateFlags::empty(),
|
||||||
|path, _state| {
|
|path, _state| {
|
||||||
needs_check.push(path);
|
needs_check.push(path);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok((needs_check, parse_errs))
|
Ok((needs_check, parse_errs))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_clock(&self) -> Result<Option<Clock>> {
|
pub fn get_clock(ts: &mut TreeState) -> Result<Option<Clock>> {
|
||||||
let treestate = self.treestate.lock();
|
let mut metadata_buf = ts.get_metadata();
|
||||||
|
let metadata = Metadata::deserialize(&mut metadata_buf)?;
|
||||||
let mut metadata_buf = treestate.get_metadata();
|
Ok(metadata
|
||||||
let metadata = Metadata::deserialize(&mut metadata_buf)?;
|
.0
|
||||||
Ok(metadata
|
.get(&"clock".to_string())
|
||||||
.0
|
.map(|clock| Clock::Spec(ClockSpec::StringClock(clock.clone()))))
|
||||||
.get(&"clock".to_string())
|
|
||||||
.map(|clock| Clock::Spec(ClockSpec::StringClock(clock.clone()))))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -199,12 +161,7 @@ mod tests {
|
|||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut wm_ts = WatchmanTreeState {
|
let (needs_check, _) = list_needs_check(&mut ts, matcher)?;
|
||||||
treestate: Arc::new(Mutex::new(ts)),
|
|
||||||
root: "/dev/null".as_ref(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (needs_check, _) = wm_ts.list_needs_check(matcher)?;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
needs_check,
|
needs_check,
|
||||||
vec![RepoPathBuf::from_string("include_me".to_string())?],
|
vec![RepoPathBuf::from_string("include_me".to_string())?],
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -29,8 +30,10 @@ use types::RepoPathBuf;
|
|||||||
use vfs::VFS;
|
use vfs::VFS;
|
||||||
use watchman_client::prelude::*;
|
use watchman_client::prelude::*;
|
||||||
|
|
||||||
use super::treestate::WatchmanTreeState;
|
use super::treestate::clear_needs_check;
|
||||||
use super::treestate::WatchmanTreeStateWrite;
|
use super::treestate::flush;
|
||||||
|
use super::treestate::mark_needs_check;
|
||||||
|
use super::treestate::set_clock;
|
||||||
use crate::filechangedetector::ArcReadFileContents;
|
use crate::filechangedetector::ArcReadFileContents;
|
||||||
use crate::filechangedetector::FileChangeDetector;
|
use crate::filechangedetector::FileChangeDetector;
|
||||||
use crate::filechangedetector::FileChangeDetectorTrait;
|
use crate::filechangedetector::FileChangeDetectorTrait;
|
||||||
@ -38,7 +41,8 @@ use crate::filechangedetector::FileChangeResult;
|
|||||||
use crate::filechangedetector::ResolvedFileChangeResult;
|
use crate::filechangedetector::ResolvedFileChangeResult;
|
||||||
use crate::filesystem::PendingChangeResult;
|
use crate::filesystem::PendingChangeResult;
|
||||||
use crate::filesystem::PendingChanges;
|
use crate::filesystem::PendingChanges;
|
||||||
use crate::watchmanfs::treestate::WatchmanTreeStateRead;
|
use crate::watchmanfs::treestate::get_clock;
|
||||||
|
use crate::watchmanfs::treestate::list_needs_check;
|
||||||
use crate::workingcopy::WorkingCopy;
|
use crate::workingcopy::WorkingCopy;
|
||||||
|
|
||||||
type ArcReadTreeManifest = Arc<dyn ReadTreeManifest + Send + Sync>;
|
type ArcReadTreeManifest = Arc<dyn ReadTreeManifest + Send + Sync>;
|
||||||
@ -124,12 +128,9 @@ impl PendingChanges for WatchmanFileSystem {
|
|||||||
config: &dyn Config,
|
config: &dyn Config,
|
||||||
io: &IO,
|
io: &IO,
|
||||||
) -> Result<Box<dyn Iterator<Item = Result<PendingChangeResult>>>> {
|
) -> Result<Box<dyn Iterator<Item = Result<PendingChangeResult>>>> {
|
||||||
let mut watchman_ts = WatchmanTreeState {
|
let ts = &mut *self.treestate.lock();
|
||||||
treestate: self.treestate.clone(),
|
|
||||||
root: self.vfs.root(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let prev_clock = watchman_ts.get_clock()?;
|
let prev_clock = get_clock(ts)?;
|
||||||
|
|
||||||
let result = async_runtime::block_on(self.query_result(WatchmanConfig {
|
let result = async_runtime::block_on(self.query_result(WatchmanConfig {
|
||||||
clock: prev_clock.clone(),
|
clock: prev_clock.clone(),
|
||||||
@ -160,8 +161,7 @@ impl PendingChanges for WatchmanFileSystem {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |f| f.len() > file_change_threshold);
|
.map_or(false, |f| f.len() > file_change_threshold);
|
||||||
|
|
||||||
let manifests =
|
let manifests = WorkingCopy::current_manifests(ts, &self.tree_resolver)?;
|
||||||
WorkingCopy::current_manifests(&self.treestate.lock(), &self.tree_resolver)?;
|
|
||||||
|
|
||||||
let file_change_detector = FileChangeDetector::new(
|
let file_change_detector = FileChangeDetector::new(
|
||||||
self.vfs.clone(),
|
self.vfs.clone(),
|
||||||
@ -170,7 +170,7 @@ impl PendingChanges for WatchmanFileSystem {
|
|||||||
self.store.clone(),
|
self.store.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (ts_needs_check, ts_errors) = watchman_ts.list_needs_check(matcher)?;
|
let (ts_needs_check, ts_errors) = list_needs_check(ts, matcher)?;
|
||||||
|
|
||||||
let mut wm_errors: Vec<ParseError> = Vec::new();
|
let mut wm_errors: Vec<ParseError> = Vec::new();
|
||||||
let wm_needs_check: Vec<RepoPathBuf> = result
|
let wm_needs_check: Vec<RepoPathBuf> = result
|
||||||
@ -190,7 +190,7 @@ impl PendingChanges for WatchmanFileSystem {
|
|||||||
|
|
||||||
let mut pending_changes = detect_changes(
|
let mut pending_changes = detect_changes(
|
||||||
file_change_detector,
|
file_change_detector,
|
||||||
&mut self.treestate.lock(),
|
ts,
|
||||||
ts_needs_check,
|
ts_needs_check,
|
||||||
wm_needs_check,
|
wm_needs_check,
|
||||||
result.clock,
|
result.clock,
|
||||||
@ -205,7 +205,7 @@ impl PendingChanges for WatchmanFileSystem {
|
|||||||
.map(|e| Err(anyhow!(e))),
|
.map(|e| Err(anyhow!(e))),
|
||||||
);
|
);
|
||||||
|
|
||||||
pending_changes.persist(watchman_ts, should_update_clock, &self.locker)?;
|
pending_changes.persist(self.vfs.root(), ts, should_update_clock, &self.locker)?;
|
||||||
|
|
||||||
Ok(Box::new(pending_changes.into_iter()))
|
Ok(Box::new(pending_changes.into_iter()))
|
||||||
}
|
}
|
||||||
@ -317,13 +317,14 @@ impl WatchmanPendingChanges {
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn persist(
|
pub fn persist(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut treestate: impl WatchmanTreeStateWrite,
|
root: &Path,
|
||||||
|
ts: &mut TreeState,
|
||||||
should_update_clock: bool,
|
should_update_clock: bool,
|
||||||
locker: &RepoLocker,
|
locker: &RepoLocker,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut wrote = false;
|
let mut wrote = false;
|
||||||
for path in self.needs_clear.iter() {
|
for path in self.needs_clear.iter() {
|
||||||
match treestate.clear_needs_check(path) {
|
match clear_needs_check(ts, path) {
|
||||||
Ok(v) => wrote |= v,
|
Ok(v) => wrote |= v,
|
||||||
Err(e) =>
|
Err(e) =>
|
||||||
// We can still build a valid result if we fail to clear the
|
// We can still build a valid result if we fail to clear the
|
||||||
@ -336,16 +337,16 @@ impl WatchmanPendingChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for path in self.needs_mark.iter() {
|
for path in self.needs_mark.iter() {
|
||||||
wrote |= treestate.mark_needs_check(path)?;
|
wrote |= mark_needs_check(ts, path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the treestate is already dirty, we're going to write it anyway, so let's go ahead and
|
// If the treestate is already dirty, we're going to write it anyway, so let's go ahead and
|
||||||
// update the clock while we're at it.
|
// update the clock while we're at it.
|
||||||
if should_update_clock || wrote {
|
if should_update_clock || wrote {
|
||||||
treestate.set_clock(self.clock.clone())?;
|
set_clock(ts, self.clock.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
treestate.flush(locker)
|
flush(root, ts, locker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user