diff --git a/lib/protos/op_store.proto b/lib/protos/op_store.proto index 4e4956b71..1a7d2c2a6 100644 --- a/lib/protos/op_store.proto +++ b/lib/protos/op_store.proto @@ -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 checkouts = 8; repeated Branch branches = 5; repeated Tag tags = 6; // Only a subset of the refs. For example, does not include refs/notes/. diff --git a/lib/src/op_store.rs b/lib/src/op_store.rs index a446cc571..134c9bd56 100644 --- a/lib/src/op_store.rs +++ b/lib/src/op_store.rs @@ -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); @@ -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, @@ -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, - // 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, } /// Represents an operation (transaction) on the repo view, just like how a diff --git a/lib/src/repo.rs b/lib/src/repo.rs index 6f1cd3707..6b1580b29 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -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 = 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 diff --git a/lib/src/simple_op_store.rs b/lib/src/simple_op_store.rs index 5cb54f593..c1e3d8c26 100644 --- a/lib/src/simple_op_store.rs +++ b/lib/src/simple_op_store.rs @@ -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 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(); diff --git a/lib/src/view.rs b/lib/src/view.rs index e37fab5e3..3d4169aa0 100644 --- a/lib/src/view.rs +++ b/lib/src/view.rs @@ -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 { @@ -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) {