ok/jj
1
0
Fork 0
forked from mirrors/jj

op_store: add support for tracking multiple workspaces (#13)

This patch teaches the `View` object to keep track of the checkout in
each workspace. It serializes that information into the `OpStore`. For
compatibility with existing repos, the existing field for a single
workspace's checkout is interpreted as being for the workspace called
"default".

This is just an early step towards support for multiple
workspaces. Remaining things to do:

 * Record the workspace ID somewhere in `.jj/` (maybe in
   `.jj/working_copy/`)

 * Update existing code to use the workspace ID instead of assuming
   it's always "default" as we do after this patch

 * Add a way of indicating in `.jj/` that the repo lives elsewhere and
   make it possible to load a repo from such workspaces

 * Add a command for creating additional workspaces

 * Show each workspace's checkout in log output
This commit is contained in:
Martin von Zweigbergk 2021-11-17 13:06:02 -08:00
parent ca055d91d9
commit 0098edd3d2
5 changed files with 72 additions and 31 deletions

View file

@ -59,7 +59,8 @@ message Tag {
message View {
repeated bytes head_ids = 1;
repeated bytes public_head_ids = 4;
bytes checkout = 2;
bytes checkout = 2 [deprecated = true];
map<string, bytes> checkouts = 8;
repeated Branch branches = 5;
repeated Tag tags = 6;
// Only a subset of the refs. For example, does not include refs/notes/.

View file

@ -17,6 +17,29 @@ use std::fmt::{Debug, Error, Formatter};
use crate::backend::{CommitId, Timestamp};
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct WorkspaceId(String);
impl Debug for WorkspaceId {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.debug_tuple("WorkspaceId").field(&self.0).finish()
}
}
impl WorkspaceId {
pub fn new(value: String) -> Self {
Self(value)
}
pub fn default() -> Self {
Self("default".to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct ViewId(Vec<u8>);
@ -133,7 +156,7 @@ pub struct BranchTarget {
/// Represents the way the repo looks at a given time, just like how a Tree
/// object represents how the file system looks at a given time.
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Debug, Default)]
pub struct View {
/// All head commits
pub head_ids: HashSet<CommitId>,
@ -146,25 +169,10 @@ pub struct View {
// TODO: Support multiple Git worktrees?
// TODO: Do we want to store the current branch name too?
pub git_head: Option<CommitId>,
// The commit that *should be* checked out in the (default) working copy. Note that the
// working copy (.jj/working_copy/) has the source of truth about which commit *is* checked out
// (to be precise: the commit to which we most recently completed a checkout to).
// TODO: Allow multiple working copies
pub checkout: CommitId,
}
impl View {
pub fn new(checkout: CommitId) -> Self {
Self {
head_ids: HashSet::new(),
public_head_ids: HashSet::new(),
branches: BTreeMap::new(),
tags: BTreeMap::new(),
git_refs: BTreeMap::new(),
git_head: None,
checkout,
}
}
// The commit that *should be* checked out in the workspace. Note that the working copy
// (.jj/working_copy/) has the source of truth about which commit *is* checked out (to be
// precise: the commit to which we most recently completed a checkout to).
pub checkouts: HashMap<WorkspaceId, CommitId>,
}
/// Represents an operation (transaction) on the repo view, just like how a

View file

@ -31,7 +31,7 @@ use crate::dag_walk::topo_order_reverse;
use crate::index::{IndexRef, MutableIndex, ReadonlyIndex};
use crate::index_store::IndexStore;
use crate::op_heads_store::OpHeadsStore;
use crate::op_store::{BranchTarget, OpStore, OperationId, RefTarget};
use crate::op_store::{BranchTarget, OpStore, OperationId, RefTarget, WorkspaceId};
use crate::operation::Operation;
use crate::rewrite::DescendantRebaser;
use crate::settings::{RepoSettings, UserSettings};
@ -177,10 +177,14 @@ impl ReadonlyRepo {
is_open: true,
};
let checkout_commit = store.write_commit(checkout_commit);
let workspace_id = WorkspaceId::default();
let op_store: Arc<dyn OpStore> = Arc::new(SimpleOpStore::init(repo_path.join("op_store")));
let mut root_view = op_store::View::new(checkout_commit.id().clone());
let mut root_view = op_store::View::default();
root_view
.checkouts
.insert(workspace_id, checkout_commit.id().clone());
root_view.head_ids.insert(checkout_commit.id().clone());
root_view
.public_head_ids

View file

@ -28,7 +28,7 @@ use crate::backend::{CommitId, MillisSinceEpoch, Timestamp};
use crate::file_util::persist_content_addressed_temp_file;
use crate::op_store::{
BranchTarget, OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata,
RefTarget, View, ViewId,
RefTarget, View, ViewId, WorkspaceId,
};
impl From<std::io::Error> for OpStoreError {
@ -200,7 +200,11 @@ fn operation_from_proto(proto: &crate::protos::op_store::Operation) -> Operation
fn view_to_proto(view: &View) -> crate::protos::op_store::View {
let mut proto = crate::protos::op_store::View::new();
proto.checkout = view.checkout.to_bytes();
for (workspace_id, commit_id) in &view.checkouts {
proto
.checkouts
.insert(workspace_id.as_str().to_string(), commit_id.to_bytes());
}
for head_id in &view.head_ids {
proto.head_ids.push(head_id.to_bytes());
}
@ -245,7 +249,21 @@ fn view_to_proto(view: &View) -> crate::protos::op_store::View {
}
fn view_from_proto(proto: &crate::protos::op_store::View) -> View {
let mut view = View::new(CommitId::new(proto.checkout.clone()));
let mut view = View::default();
// For compatibility with old repos before we had support for multiple working
// copies
if !proto.checkout.is_empty() {
view.checkouts.insert(
WorkspaceId::default(),
CommitId::new(proto.checkout.clone()),
);
}
for (workspace_id, commit_id) in &proto.checkouts {
view.checkouts.insert(
WorkspaceId::new(workspace_id.clone()),
CommitId::new(commit_id.clone()),
);
}
for head_id_bytes in proto.head_ids.iter() {
view.head_ids.insert(CommitId::from_bytes(head_id_bytes));
}
@ -370,7 +388,8 @@ mod tests {
removes: vec![CommitId::from_hex("fff111")],
adds: vec![CommitId::from_hex("fff222"), CommitId::from_hex("fff333")],
};
let checkout_id = CommitId::from_hex("abc111");
let default_checkout_id = CommitId::from_hex("abc111");
let test_checkout_id = CommitId::from_hex("abc222");
let view = View {
head_ids: hashset! {head_id1, head_id2},
public_head_ids: hashset! {public_head_id1, public_head_id2},
@ -396,7 +415,10 @@ mod tests {
"refs/heads/feature".to_string() => git_refs_feature_target
},
git_head: Some(CommitId::from_hex("fff111")),
checkout: checkout_id,
checkouts: hashmap! {
WorkspaceId::default() => default_checkout_id,
WorkspaceId::new("test".to_string()) => test_checkout_id,
},
};
let view_id = store.write_view(&view).unwrap();
let read_view = store.read_view(&view_id).unwrap();

View file

@ -17,7 +17,7 @@ use std::collections::{BTreeMap, HashSet};
use crate::backend::CommitId;
use crate::index::IndexRef;
use crate::op_store;
use crate::op_store::{BranchTarget, RefTarget};
use crate::op_store::{BranchTarget, RefTarget, WorkspaceId};
use crate::refs::merge_ref_targets;
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
@ -40,8 +40,13 @@ impl View {
}
}
// TODO: Delete this function
pub fn checkout(&self) -> &CommitId {
&self.data.checkout
self.get_checkout(&WorkspaceId::default()).unwrap()
}
pub fn get_checkout(&self, workspace_id: &WorkspaceId) -> Option<&CommitId> {
self.data.checkouts.get(workspace_id)
}
pub fn heads(&self) -> &HashSet<CommitId> {
@ -68,8 +73,9 @@ impl View {
self.data.git_head.clone()
}
// TODO: Pass in workspace id here
pub fn set_checkout(&mut self, id: CommitId) {
self.data.checkout = id;
self.data.checkouts.insert(WorkspaceId::default(), id);
}
pub fn add_head(&mut self, head_id: &CommitId) {