forked from mirrors/jj
working_copy: let WorkingCopy and TreeState have the working copy path
I don't know why I didn't do it this way from the beginning.
This commit is contained in:
parent
14e7df995a
commit
4734eb6493
5 changed files with 79 additions and 50 deletions
|
@ -118,7 +118,11 @@ impl ReadonlyRepo {
|
|||
let store = StoreWrapper::new(store);
|
||||
|
||||
fs::create_dir(repo_path.join("working_copy")).unwrap();
|
||||
let working_copy = WorkingCopy::init(store.clone(), repo_path.join("working_copy"));
|
||||
let working_copy = WorkingCopy::init(
|
||||
store.clone(),
|
||||
wc_path.clone(),
|
||||
repo_path.join("working_copy"),
|
||||
);
|
||||
|
||||
fs::create_dir(repo_path.join("view")).unwrap();
|
||||
let signature = signature(user_settings);
|
||||
|
@ -161,7 +165,7 @@ impl ReadonlyRepo {
|
|||
|
||||
ReadonlyRepo::init_cycles(&mut repo, evolution);
|
||||
repo.working_copy_locked()
|
||||
.check_out(&repo, checkout_commit)
|
||||
.check_out(checkout_commit)
|
||||
.expect("failed to check out root commit");
|
||||
repo
|
||||
}
|
||||
|
@ -184,7 +188,11 @@ impl ReadonlyRepo {
|
|||
}
|
||||
let store = StoreWrapper::new(store);
|
||||
let repo_settings = user_settings.with_repo(&repo_path).unwrap();
|
||||
let working_copy = WorkingCopy::load(store.clone(), repo_path.join("working_copy"));
|
||||
let working_copy = WorkingCopy::load(
|
||||
store.clone(),
|
||||
wc_path.clone(),
|
||||
repo_path.join("working_copy"),
|
||||
);
|
||||
let view = ReadonlyView::load(store.clone(), repo_path.join("view"));
|
||||
let repo = ReadonlyRepo {
|
||||
repo_path,
|
||||
|
|
|
@ -72,7 +72,8 @@ impl FileState {
|
|||
|
||||
struct TreeState {
|
||||
store: Arc<StoreWrapper>,
|
||||
path: PathBuf,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
tree_id: TreeId,
|
||||
file_states: BTreeMap<FileRepoPath, FileState>,
|
||||
read_time: MillisSinceEpoch,
|
||||
|
@ -152,40 +153,53 @@ impl TreeState {
|
|||
&self.file_states
|
||||
}
|
||||
|
||||
pub fn init(store: Arc<StoreWrapper>, path: PathBuf) -> TreeState {
|
||||
let mut wc = TreeState::empty(store, path);
|
||||
pub fn init(
|
||||
store: Arc<StoreWrapper>,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
) -> TreeState {
|
||||
let mut wc = TreeState::empty(store, working_copy_path, state_path);
|
||||
wc.save();
|
||||
wc
|
||||
}
|
||||
|
||||
fn empty(store: Arc<StoreWrapper>, path: PathBuf) -> TreeState {
|
||||
fn empty(
|
||||
store: Arc<StoreWrapper>,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
) -> TreeState {
|
||||
let tree_id = store.empty_tree_id().clone();
|
||||
TreeState {
|
||||
store,
|
||||
path,
|
||||
working_copy_path,
|
||||
state_path,
|
||||
tree_id,
|
||||
file_states: BTreeMap::new(),
|
||||
read_time: MillisSinceEpoch(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(store: Arc<StoreWrapper>, path: PathBuf) -> TreeState {
|
||||
let maybe_file = File::open(path.join("tree_state"));
|
||||
pub fn load(
|
||||
store: Arc<StoreWrapper>,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
) -> TreeState {
|
||||
let maybe_file = File::open(state_path.join("tree_state"));
|
||||
let file = match maybe_file {
|
||||
Err(ref err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return TreeState::init(store, path);
|
||||
return TreeState::init(store, working_copy_path, state_path);
|
||||
}
|
||||
result => result.unwrap(),
|
||||
};
|
||||
|
||||
let mut wc = TreeState::empty(store, path);
|
||||
let mut wc = TreeState::empty(store, working_copy_path, state_path);
|
||||
wc.read(file);
|
||||
wc
|
||||
}
|
||||
|
||||
fn update_read_time(&mut self) {
|
||||
let own_file_state = self
|
||||
.file_state(&self.path.join("tree_state"))
|
||||
.file_state(&self.state_path.join("tree_state"))
|
||||
.unwrap_or_else(FileState::null);
|
||||
self.read_time = own_file_state.mtime;
|
||||
}
|
||||
|
@ -207,12 +221,14 @@ impl TreeState {
|
|||
.insert(file.to_internal_string(), file_state_to_proto(file_state));
|
||||
}
|
||||
|
||||
let mut temp_file = NamedTempFile::new_in(&self.path).unwrap();
|
||||
let mut temp_file = NamedTempFile::new_in(&self.state_path).unwrap();
|
||||
// update read time while we still have the file open for writes, so we know
|
||||
// there is no unknown data in it
|
||||
self.update_read_time();
|
||||
proto.write_to_writer(temp_file.as_file_mut()).unwrap();
|
||||
temp_file.persist(self.path.join("tree_state")).unwrap();
|
||||
temp_file
|
||||
.persist(self.state_path.join("tree_state"))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn file_state(&self, path: &PathBuf) -> Option<FileState> {
|
||||
|
@ -255,8 +271,8 @@ impl TreeState {
|
|||
// Look for changes to the working copy. If there are any changes, create
|
||||
// a new tree from it and return it, and also update the dirstate on disk.
|
||||
// TODO: respect ignores
|
||||
pub fn write_tree(&mut self, working_copy_path: PathBuf) -> &TreeId {
|
||||
let mut work = vec![(DirRepoPath::root(), working_copy_path)];
|
||||
pub fn write_tree(&mut self) -> &TreeId {
|
||||
let mut work = vec![(DirRepoPath::root(), self.working_copy_path.clone())];
|
||||
let mut tree_builder = self.store.tree_builder(self.tree_id.clone());
|
||||
let mut deleted_files: HashSet<&FileRepoPath> = self.file_states.keys().collect();
|
||||
let mut modified_files = BTreeMap::new();
|
||||
|
@ -484,7 +500,8 @@ impl TreeState {
|
|||
|
||||
pub struct WorkingCopy {
|
||||
store: Arc<StoreWrapper>,
|
||||
path: PathBuf,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
commit_id: RefCell<Option<CommitId>>,
|
||||
tree_state: RefCell<Option<TreeState>>,
|
||||
// cached commit
|
||||
|
@ -492,29 +509,39 @@ pub struct WorkingCopy {
|
|||
}
|
||||
|
||||
impl WorkingCopy {
|
||||
pub fn init(store: Arc<StoreWrapper>, path: PathBuf) -> WorkingCopy {
|
||||
pub fn init(
|
||||
store: Arc<StoreWrapper>,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
) -> WorkingCopy {
|
||||
// Leave the commit_id empty so a subsequent call to check out the root revision
|
||||
// will have an effect.
|
||||
let proto = protos::working_copy::Checkout::new();
|
||||
let mut file = OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(path.join("checkout"))
|
||||
.open(state_path.join("checkout"))
|
||||
.unwrap();
|
||||
proto.write_to_writer(&mut file).unwrap();
|
||||
WorkingCopy {
|
||||
store,
|
||||
path,
|
||||
working_copy_path,
|
||||
state_path,
|
||||
commit_id: RefCell::new(None),
|
||||
tree_state: RefCell::new(None),
|
||||
commit: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(store: Arc<StoreWrapper>, path: PathBuf) -> WorkingCopy {
|
||||
pub fn load(
|
||||
store: Arc<StoreWrapper>,
|
||||
working_copy_path: PathBuf,
|
||||
state_path: PathBuf,
|
||||
) -> WorkingCopy {
|
||||
WorkingCopy {
|
||||
store,
|
||||
path,
|
||||
working_copy_path,
|
||||
state_path,
|
||||
commit_id: RefCell::new(None),
|
||||
tree_state: RefCell::new(None),
|
||||
commit: RefCell::new(None),
|
||||
|
@ -522,13 +549,13 @@ impl WorkingCopy {
|
|||
}
|
||||
|
||||
fn write_proto(&self, proto: protos::working_copy::Checkout) {
|
||||
let mut temp_file = NamedTempFile::new_in(&self.path).unwrap();
|
||||
let mut temp_file = NamedTempFile::new_in(&self.state_path).unwrap();
|
||||
proto.write_to_writer(temp_file.as_file_mut()).unwrap();
|
||||
temp_file.persist(self.path.join("checkout")).unwrap();
|
||||
temp_file.persist(self.state_path.join("checkout")).unwrap();
|
||||
}
|
||||
|
||||
fn read_proto(&self) -> protos::working_copy::Checkout {
|
||||
let mut file = File::open(self.path.join("checkout")).unwrap();
|
||||
let mut file = File::open(self.state_path.join("checkout")).unwrap();
|
||||
protobuf::parse_from_reader(&mut file).unwrap()
|
||||
}
|
||||
|
||||
|
@ -565,8 +592,11 @@ impl WorkingCopy {
|
|||
|
||||
fn tree_state(&self) -> RefMut<Option<TreeState>> {
|
||||
if self.tree_state.borrow().is_none() {
|
||||
self.tree_state
|
||||
.replace(Some(TreeState::load(self.store.clone(), self.path.clone())));
|
||||
self.tree_state.replace(Some(TreeState::load(
|
||||
self.store.clone(),
|
||||
self.working_copy_path.clone(),
|
||||
self.state_path.clone(),
|
||||
)));
|
||||
}
|
||||
self.tree_state.borrow_mut()
|
||||
}
|
||||
|
@ -589,13 +619,9 @@ impl WorkingCopy {
|
|||
self.write_proto(proto);
|
||||
}
|
||||
|
||||
pub fn check_out(
|
||||
&self,
|
||||
repo: &ReadonlyRepo,
|
||||
commit: Commit,
|
||||
) -> Result<CheckoutStats, CheckoutError> {
|
||||
pub fn check_out(&self, commit: Commit) -> Result<CheckoutStats, CheckoutError> {
|
||||
assert!(commit.is_open());
|
||||
let lock_path = self.path.join("working_copy.lock");
|
||||
let lock_path = self.state_path.join("working_copy.lock");
|
||||
let _lock = FileLock::lock(lock_path);
|
||||
|
||||
// TODO: Write a "pending_checkout" file with the old and new TreeIds so we can
|
||||
|
@ -620,7 +646,7 @@ impl WorkingCopy {
|
|||
.tree_state()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.check_out(commit.tree().id().clone(), repo.working_copy_path())?;
|
||||
.check_out(commit.tree().id().clone(), &self.working_copy_path)?;
|
||||
|
||||
self.commit_id.replace(Some(commit.id().clone()));
|
||||
self.commit.replace(Some(commit));
|
||||
|
@ -631,7 +657,7 @@ impl WorkingCopy {
|
|||
}
|
||||
|
||||
pub fn commit(&self, settings: &UserSettings, repo: &mut ReadonlyRepo) -> Commit {
|
||||
let lock_path = self.path.join("working_copy.lock");
|
||||
let lock_path = self.state_path.join("working_copy.lock");
|
||||
let _lock = FileLock::lock(lock_path);
|
||||
|
||||
// Check if the current checkout has changed on disk after we read it. It's fine
|
||||
|
@ -643,12 +669,7 @@ impl WorkingCopy {
|
|||
.replace(Some(CommitId(current_proto.commit_id)));
|
||||
let current_commit = self.current_commit();
|
||||
|
||||
let new_tree_id = self
|
||||
.tree_state()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_tree(repo.working_copy_path().clone())
|
||||
.clone();
|
||||
let new_tree_id = self.tree_state().as_mut().unwrap().write_tree().clone();
|
||||
if &new_tree_id != current_commit.tree().id() {
|
||||
let mut tx = repo.start_transaction("commit working copy");
|
||||
let commit = CommitBuilder::for_rewrite_from(settings, repo.store(), ¤t_commit)
|
||||
|
|
|
@ -173,9 +173,9 @@ fn test_checkout_file_transitions(use_git: bool) {
|
|||
|
||||
let owned_wc = repo.working_copy().clone();
|
||||
let wc = owned_wc.lock().unwrap();
|
||||
wc.check_out(&repo, left_commit).unwrap();
|
||||
wc.check_out(left_commit).unwrap();
|
||||
wc.commit(&settings, Arc::get_mut(&mut repo).unwrap());
|
||||
wc.check_out(&repo, right_commit.clone()).unwrap();
|
||||
wc.check_out(right_commit.clone()).unwrap();
|
||||
|
||||
// Check that the working copy is clean.
|
||||
let after_commit = wc.commit(&settings, Arc::get_mut(&mut repo).unwrap());
|
||||
|
|
|
@ -44,18 +44,18 @@ fn test_concurrent_checkout(use_git: bool) {
|
|||
|
||||
// Check out commit1
|
||||
let wc1 = repo1.working_copy_locked();
|
||||
wc1.check_out(&repo1, commit1).unwrap();
|
||||
wc1.check_out(commit1).unwrap();
|
||||
|
||||
// Check out commit2 from another process (simulated by another repo instance)
|
||||
let repo2 = ReadonlyRepo::load(&settings, repo1.working_copy_path().clone());
|
||||
repo2
|
||||
.working_copy_locked()
|
||||
.check_out(&repo2, commit2.clone())
|
||||
.check_out(commit2.clone())
|
||||
.unwrap();
|
||||
|
||||
// Checking out another commit (via the first repo instance) should now fail.
|
||||
assert_eq!(
|
||||
wc1.check_out(&repo1, commit3),
|
||||
wc1.check_out(commit3),
|
||||
Err(CheckoutError::ConcurrentCheckout)
|
||||
);
|
||||
|
||||
|
@ -121,7 +121,7 @@ fn test_checkout_parallel(use_git: bool) {
|
|||
let commit = CommitBuilder::for_new_commit(&settings, store, tree.id().clone())
|
||||
.set_open(true)
|
||||
.write_to_transaction(&mut tx);
|
||||
repo.working_copy_locked().check_out(&repo, commit).unwrap();
|
||||
repo.working_copy_locked().check_out(commit).unwrap();
|
||||
tx.commit();
|
||||
|
||||
let mut threads = vec![];
|
||||
|
@ -136,7 +136,7 @@ fn test_checkout_parallel(use_git: bool) {
|
|||
let owned_wc = repo.working_copy().clone();
|
||||
let wc = owned_wc.lock().unwrap();
|
||||
let commit = repo.store().get_commit(&commit_id).unwrap();
|
||||
let stats = wc.check_out(&repo, commit).unwrap();
|
||||
let stats = wc.check_out(commit).unwrap();
|
||||
assert_eq!(stats.updated_files, 0);
|
||||
assert_eq!(stats.added_files, 1);
|
||||
assert_eq!(stats.removed_files, 1);
|
||||
|
|
|
@ -210,7 +210,7 @@ fn update_working_copy(
|
|||
ui.write("\n");
|
||||
// TODO: CheckoutError::ConcurrentCheckout should probably just result in a
|
||||
// warning for most commands (but be an error for the checkout command)
|
||||
let stats = wc.check_out(repo, new_commit.clone()).map_err(|err| {
|
||||
let stats = wc.check_out(new_commit.clone()).map_err(|err| {
|
||||
CommandError::InternalError(format!(
|
||||
"Failed to check out commit {}: {}",
|
||||
new_commit.id().hex(),
|
||||
|
|
Loading…
Reference in a new issue