backend: make backend aware of root commit

I had made the backends unaware of the virtual root commit because
they don't need to know about it, and we could avoid some duplicated
code by putting that in `Store` instead. However, as we saw in
b21a123bc8, the root commit being virtual has some user-visible
effects (they can't create a merge with the root and some other
commit). So I'm thinking that we may want to make the root commit an
actual commit, depending on which backend is used. Specificially, when
using the Git backend, we cannot record the root commit as an actual
parent since Git would fail when trying to look it up. Backends that
don't need compatibility can make the root commit an actual commit,
however.

This commit therefore makes the backends aware of the root commit. It
makes it remain a virtual commit in the Git backend, and makes it an
actual commit in the `LocalBackend`.

This commit breaks any existing repos using the `LocalBackend`, but
there shouldn't be any such repos other than for testing.
This commit is contained in:
Martin von Zweigbergk 2022-09-18 17:33:39 -07:00 committed by Martin von Zweigbergk
parent 76fd6a830d
commit fb8d087882
4 changed files with 57 additions and 41 deletions

View File

@ -374,6 +374,29 @@ impl Tree {
}
}
pub fn make_root_commit(empty_tree_id: TreeId) -> Commit {
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let signature = Signature {
name: String::new(),
email: String::new(),
timestamp,
};
let change_id = ChangeId::new(vec![0; 16]);
Commit {
parents: vec![],
predecessors: vec![],
root_tree: empty_tree_id,
change_id,
description: String::new(),
author: signature.clone(),
committer: signature,
is_open: false,
}
}
pub trait Backend: Send + Sync + Debug {
fn hash_length(&self) -> usize;
@ -387,6 +410,8 @@ pub trait Backend: Send + Sync + Debug {
fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId>;
fn root_commit_id(&self) -> &CommitId;
fn empty_tree_id(&self) -> &TreeId;
fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree>;

View File

@ -24,9 +24,9 @@ use protobuf::Message;
use uuid::Uuid;
use crate::backend::{
Backend, BackendError, BackendResult, ChangeId, Commit, CommitId, Conflict, ConflictId,
ConflictPart, FileId, MillisSinceEpoch, Signature, SymlinkId, Timestamp, Tree, TreeId,
TreeValue,
make_root_commit, Backend, BackendError, BackendResult, ChangeId, Commit, CommitId, Conflict,
ConflictId, ConflictPart, FileId, MillisSinceEpoch, Signature, SymlinkId, Timestamp, Tree,
TreeId, TreeValue,
};
use crate::repo_path::{RepoPath, RepoPathComponent};
use crate::stacked_table::{TableSegment, TableStore};
@ -47,15 +47,18 @@ impl From<git2::Error> for BackendError {
pub struct GitBackend {
repo: Mutex<git2::Repository>,
root_commit_id: CommitId,
empty_tree_id: TreeId,
extra_metadata_store: TableStore,
}
impl GitBackend {
fn new(repo: git2::Repository, extra_metadata_store: TableStore) -> Self {
let root_commit_id = CommitId::from_bytes(&[0; HASH_LENGTH]);
let empty_tree_id = TreeId::from_hex("4b825dc642cb6eb9a060e54bf8d69288fbee4904");
GitBackend {
repo: Mutex::new(repo),
root_commit_id,
empty_tree_id,
extra_metadata_store,
}
@ -208,6 +211,10 @@ impl Backend for GitBackend {
Ok(SymlinkId::new(oid.as_bytes().to_vec()))
}
fn root_commit_id(&self) -> &CommitId {
&self.root_commit_id
}
fn empty_tree_id(&self) -> &TreeId {
&self.empty_tree_id
}
@ -339,6 +346,10 @@ impl Backend for GitBackend {
return Err(BackendError::NotFound);
}
if *id == self.root_commit_id {
return Ok(make_root_commit(self.empty_tree_id.clone()));
}
let locked_repo = self.repo.lock().unwrap();
let git_commit_id = Oid::from_bytes(id.as_bytes())?;
let commit = locked_repo.find_commit(git_commit_id)?;

View File

@ -23,9 +23,9 @@ use protobuf::{Message, MessageField};
use tempfile::{NamedTempFile, PersistError};
use crate::backend::{
Backend, BackendError, BackendResult, ChangeId, Commit, CommitId, Conflict, ConflictId,
ConflictPart, FileId, MillisSinceEpoch, Signature, SymlinkId, Timestamp, Tree, TreeId,
TreeValue,
make_root_commit, Backend, BackendError, BackendResult, ChangeId, Commit, CommitId, Conflict,
ConflictId, ConflictPart, FileId, MillisSinceEpoch, Signature, SymlinkId, Timestamp, Tree,
TreeId, TreeValue,
};
use crate::file_util::persist_content_addressed_temp_file;
use crate::repo_path::{RepoPath, RepoPathComponent};
@ -51,6 +51,7 @@ impl From<protobuf::Error> for BackendError {
#[derive(Debug)]
pub struct LocalBackend {
path: PathBuf,
root_commit_id: CommitId,
empty_tree_id: TreeId,
}
@ -70,9 +71,11 @@ impl LocalBackend {
}
pub fn load(store_path: PathBuf) -> Self {
let root_commit_id = CommitId::from_bytes(&[0; 64]);
let empty_tree_id = TreeId::from_hex("786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce");
LocalBackend {
path: store_path,
root_commit_id,
empty_tree_id,
}
}
@ -165,6 +168,10 @@ impl Backend for LocalBackend {
Ok(id)
}
fn root_commit_id(&self) -> &CommitId {
&self.root_commit_id
}
fn empty_tree_id(&self) -> &TreeId {
&self.empty_tree_id
}
@ -216,6 +223,10 @@ impl Backend for LocalBackend {
}
fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
if *id == self.root_commit_id {
return Ok(make_root_commit(self.empty_tree_id.clone()));
}
let path = self.commit_path(id);
let mut file = File::open(path).map_err(not_found_to_backend_error)?;

View File

@ -18,8 +18,7 @@ use std::sync::{Arc, RwLock};
use crate::backend;
use crate::backend::{
Backend, BackendResult, ChangeId, CommitId, Conflict, ConflictId, FileId, MillisSinceEpoch,
Signature, SymlinkId, Timestamp, TreeId,
Backend, BackendResult, CommitId, Conflict, ConflictId, FileId, SymlinkId, TreeId,
};
use crate::commit::Commit;
use crate::repo_path::RepoPath;
@ -31,17 +30,14 @@ use crate::tree_builder::TreeBuilder;
#[derive(Debug)]
pub struct Store {
backend: Box<dyn Backend>,
root_commit_id: CommitId,
commit_cache: RwLock<HashMap<CommitId, Arc<backend::Commit>>>,
tree_cache: RwLock<HashMap<(RepoPath, TreeId), Arc<backend::Tree>>>,
}
impl Store {
pub fn new(backend: Box<dyn Backend>) -> Arc<Self> {
let root_commit_id = CommitId::new(vec![0; backend.hash_length()]);
Arc::new(Store {
backend,
root_commit_id,
commit_cache: Default::default(),
tree_cache: Default::default(),
})
@ -60,11 +56,11 @@ impl Store {
}
pub fn root_commit_id(&self) -> &CommitId {
&self.root_commit_id
self.backend.root_commit_id()
}
pub fn root_commit(self: &Arc<Self>) -> Commit {
self.get_commit(&self.root_commit_id).unwrap()
self.get_commit(self.backend.root_commit_id()).unwrap()
}
pub fn get_commit(self: &Arc<Self>, id: &CommitId) -> BackendResult<Commit> {
@ -72,29 +68,6 @@ impl Store {
Ok(Commit::new(self.clone(), id.clone(), data))
}
fn make_root_commit(&self) -> backend::Commit {
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let signature = Signature {
name: String::new(),
email: String::new(),
timestamp,
};
let change_id = ChangeId::new(vec![0; 16]);
backend::Commit {
parents: vec![],
predecessors: vec![],
root_tree: self.backend.empty_tree_id().clone(),
change_id,
description: String::new(),
author: signature.clone(),
committer: signature,
is_open: false,
}
}
fn get_backend_commit(&self, id: &CommitId) -> BackendResult<Arc<backend::Commit>> {
{
let read_locked_cached = self.commit_cache.read().unwrap();
@ -102,11 +75,7 @@ impl Store {
return Ok(data);
}
}
let commit = if id == self.root_commit_id() {
self.make_root_commit()
} else {
self.backend.read_commit(id)?
};
let commit = self.backend.read_commit(id)?;
let data = Arc::new(commit);
let mut write_locked_cache = self.commit_cache.write().unwrap();
write_locked_cache.insert(id.clone(), data.clone());