extract list_session_files to storage

This commit is contained in:
Nikita Galaiko 2023-03-20 17:21:22 +01:00
parent 7e7e093684
commit e7adcdd017
No known key found for this signature in database
GPG Key ID: EBAB54E845BA519D
10 changed files with 189 additions and 150 deletions

View File

@ -674,6 +674,7 @@ fn init(app_handle: tauri::AppHandle) -> Result<()> {
&git_repository,
&repo.project,
&repo.deltas_storage,
&repo.sessions_storage,
) {
log::error!("{}: failed to reindex project: {:#}", project.id, err);
}

View File

@ -33,7 +33,11 @@ impl Repository {
Ok(Repository {
project: project.clone(),
git_repository,
deltas_storage: deltas::Store::new(git2::Repository::open(&project.path)?, project, sessions_storage.clone())?,
deltas_storage: deltas::Store::new(
git2::Repository::open(&project.path)?,
project,
sessions_storage.clone(),
)?,
sessions_storage,
})
}
@ -47,7 +51,7 @@ impl Repository {
session_id: &str,
files: Option<Vec<&str>>,
) -> Result<HashMap<String, String>> {
sessions::list_files(&self.git_repository, &self.project, session_id, files)
self.sessions_storage.list_files(session_id, files)
}
pub fn deltas(

View File

@ -97,6 +97,7 @@ impl Deltas {
repo: &git2::Repository,
project: &projects::Project,
deltas_storage: &deltas::Store,
session_storage: &sessions::Store,
) -> Result<()> {
let start = time::SystemTime::now();
@ -127,7 +128,9 @@ impl Deltas {
let session = sessions::Session::from_commit(repo, &commit).with_context(|| {
format!("Could not parse commit {} in project", oid.to_string())
})?;
if let Err(e) = self.index_session(repo, project, &session, deltas_storage) {
if let Err(e) =
self.index_session(project, &session, deltas_storage, session_storage)
{
log::error!(
"Could not index commit {} in {}: {:#}",
oid,
@ -146,19 +149,19 @@ impl Deltas {
pub fn index_session(
&mut self,
repo: &git2::Repository,
project: &projects::Project,
session: &sessions::Session,
deltas_storage: &deltas::Store,
session_storage: &sessions::Store,
) -> Result<()> {
log::info!("Indexing session {} in {}", session.id, project.path);
index_session(
&self.index,
&mut self.writer.lock().unwrap(),
session,
repo,
project,
deltas_storage,
session_storage,
)?;
self.meta_storage
.set(&project.id, &session.id, CURRENT_VERSION)?;
@ -196,17 +199,15 @@ fn index_session(
index: &tantivy::Index,
writer: &mut IndexWriter,
session: &sessions::Session,
repo: &git2::Repository,
project: &projects::Project,
deltas_storage: &deltas::Store,
session_storage: &sessions::Store,
) -> Result<()> {
let deltas = deltas_storage.list(&session.id, None)?;
if deltas.is_empty() {
return Ok(());
}
let files = sessions::list_files(
repo,
project,
let files = session_storage.list_files(
&session.id,
Some(deltas.keys().map(|k| k.as_str()).collect()),
)?;

View File

@ -37,7 +37,7 @@ fn test_filter_by_timestamp() {
let sessions_storage = sessions::Store::new(clone_repo(&repo), project.clone()).unwrap();
let deltas_storage =
deltas::Store::new(clone_repo(&repo), project.clone(), sessions_storage).unwrap();
deltas::Store::new(clone_repo(&repo), project.clone(), sessions_storage.clone()).unwrap();
let mut session = sessions::Session::from_head(&repo, &project).unwrap();
deltas_storage
.write(
@ -62,7 +62,12 @@ fn test_filter_by_timestamp() {
let mut searcher = super::Deltas::at(index_path.into()).unwrap();
let write_result = searcher.index_session(&repo, &project, &session, &deltas_storage);
let write_result = searcher.index_session(
&project,
&session,
&deltas_storage,
&sessions_storage,
);
assert!(write_result.is_ok());
let search_result_from = searcher.search(&super::SearchQuery {
@ -109,7 +114,7 @@ fn test_sorted_by_timestamp() {
let sessions_storage = sessions::Store::new(clone_repo(&repo), project.clone()).unwrap();
let deltas_storage =
deltas::Store::new(clone_repo(&repo), project.clone(), sessions_storage).unwrap();
deltas::Store::new(clone_repo(&repo), project.clone(), sessions_storage.clone()).unwrap();
let mut session = sessions::Session::from_head(&repo, &project).unwrap();
deltas_storage
.write(
@ -130,7 +135,12 @@ fn test_sorted_by_timestamp() {
let mut searcher = super::Deltas::at(index_path.into()).unwrap();
let write_result = searcher.index_session(&repo, &project, &session, &deltas_storage);
let write_result = searcher.index_session(
&project,
&session,
&deltas_storage,
&sessions_storage,
);
assert!(write_result.is_ok());
let search_result = searcher.search(&super::SearchQuery {
@ -155,7 +165,7 @@ fn test_simple() {
let sessions_storage = sessions::Store::new(clone_repo(&repo), project.clone()).unwrap();
let deltas_storage =
deltas::Store::new(clone_repo(&repo), project.clone(), sessions_storage).unwrap();
deltas::Store::new(clone_repo(&repo), project.clone(), sessions_storage.clone()).unwrap();
let mut session = sessions::Session::from_head(&repo, &project).unwrap();
deltas_storage
.write(
@ -176,7 +186,12 @@ fn test_simple() {
let mut searcher = super::Deltas::at(index_path.into()).unwrap();
let write_result = searcher.index_session(&repo, &project, &session, &deltas_storage);
let write_result = searcher.index_session(
&project,
&session,
&deltas_storage,
&sessions_storage,
);
assert!(write_result.is_ok());
let search_result1 = searcher.search(&super::SearchQuery {

View File

@ -2,7 +2,7 @@ mod activity;
mod sessions;
mod storage;
pub use sessions::{id_from_commit, list_files, Meta, Session};
pub use sessions::{id_from_commit, Meta, Session};
pub use storage::Store;
#[cfg(test)]

View File

@ -273,15 +273,6 @@ fn delete(project: &projects::Project) -> Result<()> {
Ok(())
}
fn is_current_session_id(project: &projects::Project, session_id: &str) -> Result<bool> {
let current_id_path = project.session_path().join("meta").join("id");
if !current_id_path.exists() {
return Ok(false);
}
let current_id = std::fs::read_to_string(current_id_path)?;
return Ok(current_id == session_id);
}
pub fn id_from_commit(repo: &git2::Repository, commit: &git2::Commit) -> Result<String> {
let tree = commit.tree().unwrap();
let session_id_path = Path::new("session/meta/id");
@ -304,88 +295,6 @@ fn read_as_string(repo: &git2::Repository, tree: &git2::Tree, path: &Path) -> Re
Ok(contents)
}
// return a map of file name -> file content for all files in the beginning of a session.
pub fn list_files(
repo: &git2::Repository,
project: &projects::Project,
session_id: &str,
paths: Option<Vec<&str>>,
) -> Result<HashMap<String, String>> {
let reference = repo.find_reference(&project.refname())?;
let commit = if is_current_session_id(project, session_id)? {
let head_commit = reference.peel_to_commit()?;
Some(head_commit)
} else {
let head_commit = reference.peel_to_commit()?;
let mut walker = repo.revwalk()?;
walker.push(head_commit.id())?;
walker.set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::REVERSE)?;
let mut session_commit = None;
let mut previous_session_commit = None;
for commit_id in walker {
let commit = repo.find_commit(commit_id?)?;
if id_from_commit(repo, &commit)? == session_id {
session_commit = Some(commit);
break;
}
previous_session_commit = Some(commit.clone());
}
match (previous_session_commit, session_commit) {
// if there is a previous session, we want to list the files from the previous session
(Some(previous_session_commit), Some(_)) => Some(previous_session_commit),
// if there is no previous session, we use the found session, because it's the first one.
(None, Some(session_commit)) => Some(session_commit),
_ => None,
}
};
if commit.is_none() {
return Err(anyhow!("session {} has no hash", session_id));
}
let commit = commit.unwrap();
let tree = commit.tree()?;
let mut files = HashMap::new();
tree.walk(git2::TreeWalkMode::PreOrder, |root, entry| {
if entry.name().is_none() {
return git2::TreeWalkResult::Ok;
}
let entry_path = Path::new(root).join(entry.name().unwrap());
if !entry_path.starts_with("wd") {
return git2::TreeWalkResult::Ok;
}
if "wd".eq(entry_path.to_str().unwrap()) {
return git2::TreeWalkResult::Ok;
}
if entry.kind() == Some(git2::ObjectType::Tree) {
return git2::TreeWalkResult::Ok;
}
let blob = entry.to_object(repo).and_then(|obj| obj.peel_to_blob());
let content = blob.map(|blob| blob.content().to_vec());
let relpath = entry_path.strip_prefix("wd").unwrap();
if let Some(paths) = paths.as_ref() {
if !paths.contains(&relpath.to_str().unwrap()) {
return git2::TreeWalkResult::Ok;
}
}
files.insert(
relpath.to_owned().to_str().unwrap().to_owned(),
String::from_utf8(content.unwrap_or_default()).unwrap_or_default(),
);
git2::TreeWalkResult::Ok
})?;
Ok(files)
}
fn flush(
repo: &git2::Repository,
user: &Option<users::User>,

View File

@ -267,6 +267,7 @@ fn test_list() {
fn test_list_files_from_first_presistent_session() {
let (repo, project) = test_project().unwrap();
let store = super::Store::new(clone_repo(&repo), project.clone()).unwrap();
let file_path = Path::new(&project.path).join("test.txt");
std::fs::write(file_path.clone(), "zero").unwrap();
@ -280,7 +281,7 @@ fn test_list_files_from_first_presistent_session() {
let file_path = Path::new(&project.path).join("test.txt");
std::fs::write(file_path.clone(), "one").unwrap();
let files = super::sessions::list_files(&repo, &project, &first.id, None);
let files = store.list_files(&first.id, None);
assert!(files.is_ok());
let files = files.unwrap();
assert_eq!(files.len(), 1);
@ -291,6 +292,7 @@ fn test_list_files_from_first_presistent_session() {
fn test_list_files_from_second_current_session() {
let (repo, project) = test_project().unwrap();
let store = super::Store::new(clone_repo(&repo), project.clone()).unwrap();
let file_path = Path::new(&project.path).join("test.txt");
std::fs::write(file_path.clone(), "zero").unwrap();
@ -308,7 +310,7 @@ fn test_list_files_from_second_current_session() {
assert!(second.is_ok());
let second = second.unwrap();
let files = super::sessions::list_files(&repo, &project, &second.id, None);
let files = store.list_files(&second.id, None);
assert!(files.is_ok());
let files = files.unwrap();
assert_eq!(files.len(), 1);
@ -318,6 +320,7 @@ fn test_list_files_from_second_current_session() {
#[test]
fn test_list_files_from_second_presistent_session() {
let (repo, project) = test_project().unwrap();
let store = super::Store::new(clone_repo(&repo), project.clone()).unwrap();
let file_path = Path::new(&project.path).join("test.txt");
std::fs::write(file_path.clone(), "zero").unwrap();
@ -340,7 +343,7 @@ fn test_list_files_from_second_presistent_session() {
std::fs::write(file_path.clone(), "two").unwrap();
let files = super::sessions::list_files(&repo, &project, &second.id, None);
let files = store.list_files(&second.id, None);
assert!(files.is_ok());
let files = files.unwrap();
assert_eq!(files.len(), 1);
@ -350,6 +353,7 @@ fn test_list_files_from_second_presistent_session() {
#[test]
fn test_flush_ensure_wd_structure() {
let (repo, project) = test_project().unwrap();
let store = super::Store::new(clone_repo(&repo), project.clone()).unwrap();
// create file inside a directory
let file_dir = Path::new(&project.path).join("dir1").join("dir2");
@ -412,7 +416,7 @@ fn test_flush_ensure_wd_structure() {
assert!(all_files_1.contains_key("wd/dir1/dir2"));
assert!(all_files_1.contains_key("wd/dir1/dir2/test.txt"));
let files = super::sessions::list_files(&repo, &project, &second.id, None);
let files = store.list_files(&second.id, None);
assert!(files.is_ok());
let files = files.unwrap();
assert_eq!(files.len(), 2);

View File

@ -1,34 +1,23 @@
use std::collections::HashMap;
use crate::{projects, sessions};
use anyhow::Result;
mod current;
mod persistent;
#[derive(Clone)]
pub struct Store {
project: projects::Project,
git_repository: git2::Repository,
current: current::Store,
persistent: persistent::Store,
}
impl Clone for Store {
fn clone(&self) -> Self {
Self {
project: self.project.clone(),
git_repository: git2::Repository::open(&self.project.path).unwrap(),
current: self.current.clone(),
persistent: self.persistent.clone(),
}
}
}
impl Store {
pub fn new(git_repository: git2::Repository, project: projects::Project) -> Result<Self> {
Ok(Self {
project: project.clone(),
git_repository,
current: current::Store::new(git2::Repository::open(&project.path)?, project.clone())?,
current: current::Store::new(git_repository, project.clone())?,
persistent: persistent::Store::new(
git2::Repository::open(&project.path)?,
project.clone(),
@ -36,6 +25,14 @@ impl Store {
})
}
pub fn list_files(
&self,
session_id: &str,
paths: Option<Vec<&str>>,
) -> Result<HashMap<String, String>> {
return self.persistent.list_files(session_id, paths);
}
// returns list of sessions in reverse chronological order
pub fn list(&self, earliest_timestamp_ms: Option<u128>) -> Result<Vec<sessions::Session>> {
let mut sessions = self.persistent.list(earliest_timestamp_ms)?;
@ -51,30 +48,9 @@ impl Store {
pub fn get_by_id(&self, session_id: &str) -> Result<Option<sessions::Session>> {
if is_current_session_id(&self.project, session_id)? {
return self.get_current();
return self.current.get();
}
let reference = self
.git_repository
.find_reference(&self.project.refname())?;
let head = self
.git_repository
.find_commit(reference.target().unwrap())?;
let mut walker = self.git_repository.revwalk()?;
walker.push(head.id())?;
walker.set_sorting(git2::Sort::TIME)?;
for commit_id in walker {
let commit = self.git_repository.find_commit(commit_id?)?;
if sessions::id_from_commit(&self.git_repository, &commit)? == session_id {
return Ok(Some(sessions::Session::from_commit(
&self.git_repository,
&commit,
)?));
}
}
Ok(None)
return self.persistent.get_by_id(session_id);
}
}

View File

@ -1,3 +1,5 @@
use std::{collections::HashMap, path::Path};
use crate::{projects, sessions};
use anyhow::{Context, Result};
@ -23,6 +25,124 @@ impl Store {
})
}
pub fn get_by_id(&self, session_id: &str) -> Result<Option<sessions::Session>> {
let reference = self
.git_repository
.find_reference(&self.project.refname())?;
let head = self
.git_repository
.find_commit(reference.target().unwrap())?;
let mut walker = self.git_repository.revwalk()?;
walker.push(head.id())?;
walker.set_sorting(git2::Sort::TIME)?;
for commit_id in walker {
let commit = self.git_repository.find_commit(commit_id?)?;
if sessions::id_from_commit(&self.git_repository, &commit)? == session_id {
return Ok(Some(sessions::Session::from_commit(
&self.git_repository,
&commit,
)?));
}
}
Ok(None)
}
pub fn list_files(
&self,
session_id: &str,
paths: Option<Vec<&str>>,
) -> Result<HashMap<String, String>> {
let files = self.list_files_from_disk(session_id)?;
match paths {
Some(paths) => {
let mut filtered_files = HashMap::new();
for path in paths {
if let Some(file) = files.get(path) {
filtered_files.insert(path.to_string(), file.to_string());
}
}
Ok(filtered_files)
}
None => Ok(files),
}
}
fn list_files_from_disk(&self, session_id: &str) -> Result<HashMap<String, String>> {
let reference = self
.git_repository
.find_reference(&self.project.refname())?;
let commit = if is_current_session_id(&self.project, session_id)? {
let head_commit = reference.peel_to_commit()?;
Some(head_commit)
} else {
let head_commit = reference.peel_to_commit()?;
let mut walker = self.git_repository.revwalk()?;
walker.push(head_commit.id())?;
walker.set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::REVERSE)?;
let mut session_commit = None;
let mut previous_session_commit = None;
for commit_id in walker {
let commit = self.git_repository.find_commit(commit_id?)?;
if sessions::id_from_commit(&self.git_repository, &commit)? == session_id {
session_commit = Some(commit);
break;
}
previous_session_commit = Some(commit.clone());
}
match (previous_session_commit, session_commit) {
// if there is a previous session, we want to list the files from the previous session
(Some(previous_session_commit), Some(_)) => Some(previous_session_commit),
// if there is no previous session, we use the found session, because it's the first one.
(None, Some(session_commit)) => Some(session_commit),
_ => None,
}
};
if commit.is_none() {
return Ok(HashMap::new());
}
let commit = commit.unwrap();
let tree = commit.tree()?;
let mut files = HashMap::new();
tree.walk(git2::TreeWalkMode::PreOrder, |root, entry| {
if entry.name().is_none() {
return git2::TreeWalkResult::Ok;
}
let entry_path = Path::new(root).join(entry.name().unwrap());
if !entry_path.starts_with("wd") {
return git2::TreeWalkResult::Ok;
}
if "wd".eq(entry_path.to_str().unwrap()) {
return git2::TreeWalkResult::Ok;
}
if entry.kind() == Some(git2::ObjectType::Tree) {
return git2::TreeWalkResult::Ok;
}
let blob = entry
.to_object(&self.git_repository)
.and_then(|obj| obj.peel_to_blob());
let content = blob.map(|blob| blob.content().to_vec());
let relpath = entry_path.strip_prefix("wd").unwrap();
files.insert(
relpath.to_owned().to_str().unwrap().to_owned(),
String::from_utf8(content.unwrap_or_default()).unwrap_or_default(),
);
git2::TreeWalkResult::Ok
})?;
Ok(files)
}
// returns list of sessions in reverse chronological order
// except for the first session. The first created session
// is special and used to bootstrap the gitbutler state inside a repo.
@ -68,3 +188,12 @@ impl Store {
Ok(sessions)
}
}
fn is_current_session_id(project: &projects::Project, session_id: &str) -> Result<bool> {
let current_id_path = project.session_path().join("meta").join("id");
if !current_id_path.exists() {
return Ok(false);
}
let current_id = std::fs::read_to_string(current_id_path)?;
return Ok(current_id == session_id);
}

View File

@ -69,7 +69,7 @@ impl<'a> SessionWatcher {
log::debug!("{}: unlocked", project.id);
self.deltas_searcher
.index_session(&repo, &project, &session, &deltas_storage)
.index_session(&project, &session, &deltas_storage, &sessions_storage)
.with_context(|| format!("failed to index session {}", session.id))?;
sender