Skip to content

Commit

Permalink
feat: local changes set
Browse files Browse the repository at this point in the history
  • Loading branch information
dpc committed Apr 20, 2024
1 parent cce1b9f commit f98bdd9
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 5 deletions.
5 changes: 5 additions & 0 deletions cli/src/commands/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ Please use `jj new 'all:x|y'` instead of `jj new --allow-large-revsets x y`.",
)?;
}
}

tx.base_repo()
.store()
.set_local_change(new_commit.change_id(), true)?;

num_rebased += tx.mut_repo().rebase_descendants(command.settings())?;
if args.no_edit {
if let Some(mut formatter) = ui.status_formatter() {
Expand Down
4 changes: 4 additions & 0 deletions lib/src/default_index/revset_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,10 @@ fn build_predicate_fn(
let commit = store.get_commit(&entry.commit_id()).unwrap();
commit.has_conflict().unwrap()
}),
RevsetFilterPredicate::Local => box_pure_predicate_fn(move |index, pos| {
let entry = index.entry_by_pos(pos);
store.is_local_change(&entry.change_id()).unwrap()
}),
}
}

Expand Down
8 changes: 7 additions & 1 deletion lib/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ impl ReadonlyRepo {
let backend = backend_initializer(user_settings, &store_path)?;
let backend_path = store_path.join("type");
fs::write(&backend_path, backend.name()).context(&backend_path)?;
let store = Store::new(backend, signer, user_settings.use_tree_conflict_format());
let store = Store::new(
repo_path.clone(),
backend,
signer,
user_settings.use_tree_conflict_format(),
);
let repo_settings = user_settings.with_repo(&repo_path).unwrap();

let op_store_path = repo_path.join("op_store");
Expand Down Expand Up @@ -638,6 +643,7 @@ impl RepoLoader {
store_factories: &StoreFactories,
) -> Result<Self, StoreLoadError> {
let store = Store::new(
repo_path.to_owned(),
store_factories.load_backend(user_settings, &repo_path.join("store"))?,
Signer::from_settings(user_settings)?,
user_settings.use_tree_conflict_format(),
Expand Down
6 changes: 6 additions & 0 deletions lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ pub enum RevsetFilterPredicate {
File(FilesetExpression),
/// Commits with conflicts
HasConflict,
/// Local change
Local,
}

#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -1359,6 +1361,10 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
let expression = parse_expression_rule(arg.into_inner(), state)?;
Ok(Rc::new(RevsetExpression::Present(expression)))
});
map.insert("local", |name, arguments_pair, _state| {
expect_no_arguments(name, arguments_pair)?;
Ok(RevsetExpression::filter(RevsetFilterPredicate::Local))
});
map
});

Expand Down
86 changes: 82 additions & 4 deletions lib/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,24 @@
#![allow(missing_docs)]

use std::any::Any;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Formatter};
use std::io::Read;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use std::time::SystemTime;

use pollster::FutureExt;

use crate::backend::{
self, Backend, BackendResult, ChangeId, CommitId, ConflictId, FileId, MergedTreeId, SigningFn,
SymlinkId, TreeId,
self, Backend, BackendError, BackendResult, ChangeId, CommitId, ConflictId, FileId,
MergedTreeId, SigningFn, SymlinkId, TreeId,
};
use crate::commit::Commit;
use crate::index::Index;
use crate::merge::{Merge, MergedTreeValue};
use crate::merged_tree::MergedTree;
use crate::object_id::ObjectId;
use crate::repo_path::{RepoPath, RepoPathBuf};
use crate::signing::Signer;
use crate::tree::Tree;
Expand All @@ -39,10 +41,14 @@ use crate::tree_builder::TreeBuilder;
/// Wraps the low-level backend and makes it return more convenient types. Also
/// adds caching.
pub struct Store {
repo_path: PathBuf,
backend: Box<dyn Backend>,
signer: Signer,
commit_cache: RwLock<HashMap<CommitId, Arc<backend::Commit>>>,
tree_cache: RwLock<HashMap<(RepoPathBuf, TreeId), Arc<backend::Tree>>>,
/// In memory copy of all local change ids, lazy loaded (`None` - not loaded
/// yet)
local_changes_cache: RwLock<Option<HashSet<ChangeId>>>,
use_tree_conflict_format: bool,
}

Expand All @@ -56,15 +62,18 @@ impl Debug for Store {

impl Store {
pub fn new(
repo_path: PathBuf,
backend: Box<dyn Backend>,
signer: Signer,
use_tree_conflict_format: bool,
) -> Arc<Self> {
Arc::new(Store {
repo_path,
backend,
signer,
commit_cache: Default::default(),
tree_cache: Default::default(),
local_changes_cache: Default::default(),
use_tree_conflict_format,
})
}
Expand Down Expand Up @@ -118,6 +127,75 @@ impl Store {
self.get_commit_async(id).block_on()
}

pub fn is_local_change(self: &Arc<Self>, id: &ChangeId) -> BackendResult<bool> {
if let Some(cached) = self.local_changes_cache.read().unwrap().as_ref() {
return Ok(cached.contains(id));
}

let mut write = self.local_changes_cache.write().unwrap();
*write = Some(self.load_local_changes()?);
Ok(write.as_ref().unwrap().contains(id))
}

pub fn set_local_change(self: &Arc<Self>, id: &ChangeId, local: bool) -> BackendResult<()> {
let mut write = self.local_changes_cache.write().unwrap();
if write.is_none() {
*write = Some(self.load_local_changes()?);
}
if local {
write.as_mut().unwrap().insert(id.clone());
} else {
write.as_mut().unwrap().remove(id);
}

self.store_local_changes(write.as_ref().unwrap())?;

Ok(())
}

fn load_local_changes(self: &Arc<Self>) -> BackendResult<HashSet<ChangeId>> {
let path = self.repo_path.join("local_changes.json");

if !path.exists() {
return Ok(Default::default());
}

let locals: HashSet<String> = serde_json::from_reader(
&std::fs::File::open(&path).map_err(|e| BackendError::Other(Box::new(e)))?,
)
.map_err(|e| BackendError::Other(Box::new(e)))?;

locals
.into_iter()
.map(|s| ChangeId::try_from_hex(&s).map_err(|e| BackendError::Other(Box::new(e))))
.collect()
}

// TODO: move this somewhere else, doesn't need to be on a Backend.
// In fact maybe loading and checking can be moved out as well...
fn store_local_changes(
self: &Arc<Self>,
local_changes: &HashSet<ChangeId>,
) -> BackendResult<()> {
let path = self.repo_path.join("local_changes.json");

let local_changes: HashSet<String> = local_changes
.iter()
.map(|change_id| change_id.hex())
.collect();
// TODO: write to a tmp file, fsync, rename, fsync
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.map_err(|e| BackendError::Other(Box::new(e)))?;
serde_json::to_writer(&mut file, &local_changes)
.map_err(|e| BackendError::Other(Box::new(e)))?;
file.flush().map_err(|e| BackendError::Other(Box::new(e)))?;
Ok(())
}

pub async fn get_commit_async(self: &Arc<Self>, id: &CommitId) -> BackendResult<Commit> {
let data = self.get_backend_commit(id).await?;
Ok(Commit::new(self.clone(), id.clone(), data))
Expand Down

0 comments on commit f98bdd9

Please sign in to comment.