2022-11-26 23:57:50 +00:00
|
|
|
// Copyright 2020 The Jujutsu Authors
|
2020-12-12 08:00:42 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2023-07-10 15:17:00 +00:00
|
|
|
#![allow(missing_docs)]
|
|
|
|
|
2023-07-15 15:54:50 +00:00
|
|
|
use std::collections::BTreeMap;
|
2020-12-12 08:00:42 +00:00
|
|
|
use std::fmt::Debug;
|
2023-03-30 20:11:18 +00:00
|
|
|
use std::fs;
|
|
|
|
use std::io::{ErrorKind, Write};
|
|
|
|
use std::path::{Path, PathBuf};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
2023-03-30 20:11:18 +00:00
|
|
|
use prost::Message;
|
2023-12-16 08:20:48 +00:00
|
|
|
use tempfile::NamedTempFile;
|
2023-07-26 17:59:27 +00:00
|
|
|
use thiserror::Error;
|
2022-11-02 16:51:25 +00:00
|
|
|
|
2023-03-30 20:11:18 +00:00
|
|
|
use crate::backend::{CommitId, MillisSinceEpoch, ObjectId, Timestamp};
|
|
|
|
use crate::content_hash::blake2b_hash;
|
|
|
|
use crate::file_util::persist_content_addressed_temp_file;
|
2023-08-06 16:21:35 +00:00
|
|
|
use crate::merge::Merge;
|
2023-03-30 20:11:18 +00:00
|
|
|
use crate::op_store::{
|
2023-10-05 15:19:41 +00:00
|
|
|
OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata, RefTarget,
|
2023-10-11 17:15:17 +00:00
|
|
|
RemoteRef, RemoteRefState, RemoteView, View, ViewId, WorkspaceId,
|
2023-03-30 20:11:18 +00:00
|
|
|
};
|
2023-09-10 02:07:31 +00:00
|
|
|
use crate::{git, op_store};
|
2022-11-02 16:51:25 +00:00
|
|
|
|
2023-07-26 17:59:27 +00:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
#[error("Failed to read {kind} with ID {id}: {err}")]
|
|
|
|
struct DecodeError {
|
|
|
|
kind: &'static str,
|
|
|
|
id: String,
|
|
|
|
#[source]
|
|
|
|
err: prost::DecodeError,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<DecodeError> for OpStoreError {
|
|
|
|
fn from(err: DecodeError) -> Self {
|
2023-07-26 17:27:43 +00:00
|
|
|
OpStoreError::Other(err.into())
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SimpleOpStore {
|
2023-03-30 20:11:18 +00:00
|
|
|
path: PathBuf,
|
2022-11-02 16:51:25 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
impl SimpleOpStore {
|
2023-10-14 13:09:33 +00:00
|
|
|
pub fn name() -> &'static str {
|
|
|
|
"simple_op_store"
|
|
|
|
}
|
|
|
|
|
2023-04-11 03:40:03 +00:00
|
|
|
/// Creates an empty OpStore, panics if it already exists
|
2022-12-14 19:10:42 +00:00
|
|
|
pub fn init(store_path: &Path) -> Self {
|
2023-03-30 20:11:18 +00:00
|
|
|
fs::create_dir(store_path.join("views")).unwrap();
|
|
|
|
fs::create_dir(store_path.join("operations")).unwrap();
|
|
|
|
SimpleOpStore {
|
|
|
|
path: store_path.to_owned(),
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2023-04-11 03:40:03 +00:00
|
|
|
/// Load an existing OpStore
|
2022-12-14 19:10:42 +00:00
|
|
|
pub fn load(store_path: &Path) -> Self {
|
2023-03-30 20:11:18 +00:00
|
|
|
SimpleOpStore {
|
|
|
|
path: store_path.to_path_buf(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn view_path(&self, id: &ViewId) -> PathBuf {
|
|
|
|
self.path.join("views").join(id.hex())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn operation_path(&self, id: &OperationId) -> PathBuf {
|
|
|
|
self.path.join("operations").join(id.hex())
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
2022-11-02 16:51:25 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
impl OpStore for SimpleOpStore {
|
2022-12-14 18:22:12 +00:00
|
|
|
fn name(&self) -> &str {
|
2023-10-14 13:09:33 +00:00
|
|
|
Self::name()
|
2022-12-14 18:22:12 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
fn read_view(&self, id: &ViewId) -> OpStoreResult<View> {
|
2023-03-30 20:11:18 +00:00
|
|
|
let path = self.view_path(id);
|
2023-11-11 09:52:39 +00:00
|
|
|
let buf = fs::read(path).map_err(|err| io_to_read_error(err, id))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
2023-07-26 17:59:27 +00:00
|
|
|
let proto = crate::protos::op_store::View::decode(&*buf).map_err(|err| DecodeError {
|
|
|
|
kind: "view",
|
|
|
|
id: id.hex(),
|
|
|
|
err,
|
|
|
|
})?;
|
2023-03-30 20:11:18 +00:00
|
|
|
Ok(view_from_proto(proto))
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn write_view(&self, view: &View) -> OpStoreResult<ViewId> {
|
2023-07-26 18:11:37 +00:00
|
|
|
let temp_file =
|
|
|
|
NamedTempFile::new_in(&self.path).map_err(|err| io_to_write_error(err, "view"))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
let proto = view_to_proto(view);
|
2023-07-26 18:11:37 +00:00
|
|
|
temp_file
|
|
|
|
.as_file()
|
|
|
|
.write_all(&proto.encode_to_vec())
|
|
|
|
.map_err(|err| io_to_write_error(err, "view"))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
let id = ViewId::new(blake2b_hash(view).to_vec());
|
|
|
|
|
2023-12-16 08:20:48 +00:00
|
|
|
persist_content_addressed_temp_file(temp_file, self.view_path(&id))
|
|
|
|
.map_err(|err| io_to_write_error(err, "view"))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
Ok(id)
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation> {
|
2023-03-30 20:11:18 +00:00
|
|
|
let path = self.operation_path(id);
|
2023-11-11 09:52:39 +00:00
|
|
|
let buf = fs::read(path).map_err(|err| io_to_read_error(err, id))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
2023-07-26 17:59:27 +00:00
|
|
|
let proto =
|
|
|
|
crate::protos::op_store::Operation::decode(&*buf).map_err(|err| DecodeError {
|
|
|
|
kind: "operation",
|
|
|
|
id: id.hex(),
|
|
|
|
err,
|
|
|
|
})?;
|
2023-03-30 20:11:18 +00:00
|
|
|
Ok(operation_from_proto(proto))
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn write_operation(&self, operation: &Operation) -> OpStoreResult<OperationId> {
|
2023-07-26 18:11:37 +00:00
|
|
|
let temp_file =
|
|
|
|
NamedTempFile::new_in(&self.path).map_err(|err| io_to_write_error(err, "operation"))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
let proto = operation_to_proto(operation);
|
2023-07-26 18:11:37 +00:00
|
|
|
temp_file
|
|
|
|
.as_file()
|
|
|
|
.write_all(&proto.encode_to_vec())
|
|
|
|
.map_err(|err| io_to_write_error(err, "operation"))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
let id = OperationId::new(blake2b_hash(operation).to_vec());
|
|
|
|
|
2023-12-16 08:20:48 +00:00
|
|
|
persist_content_addressed_temp_file(temp_file, self.operation_path(&id))
|
|
|
|
.map_err(|err| io_to_write_error(err, "operation"))?;
|
2023-03-30 20:11:18 +00:00
|
|
|
Ok(id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-26 18:11:37 +00:00
|
|
|
fn io_to_read_error(err: std::io::Error, id: &impl ObjectId) -> OpStoreError {
|
2023-03-30 20:11:18 +00:00
|
|
|
if err.kind() == ErrorKind::NotFound {
|
2023-11-11 09:59:06 +00:00
|
|
|
OpStoreError::ObjectNotFound {
|
|
|
|
object_type: id.object_type(),
|
|
|
|
hash: id.hex(),
|
|
|
|
source: Box::new(err),
|
|
|
|
}
|
2023-03-30 20:11:18 +00:00
|
|
|
} else {
|
2023-07-26 18:11:37 +00:00
|
|
|
OpStoreError::ReadObject {
|
|
|
|
object_type: id.object_type(),
|
|
|
|
hash: id.hex(),
|
|
|
|
source: Box::new(err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn io_to_write_error(err: std::io::Error, object_type: &'static str) -> OpStoreError {
|
|
|
|
OpStoreError::WriteObject {
|
|
|
|
object_type,
|
|
|
|
source: Box::new(err),
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn timestamp_to_proto(timestamp: &Timestamp) -> crate::protos::op_store::Timestamp {
|
|
|
|
crate::protos::op_store::Timestamp {
|
|
|
|
millis_since_epoch: timestamp.timestamp.0,
|
|
|
|
tz_offset: timestamp.tz_offset,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn timestamp_from_proto(proto: crate::protos::op_store::Timestamp) -> Timestamp {
|
|
|
|
Timestamp {
|
|
|
|
timestamp: MillisSinceEpoch(proto.millis_since_epoch),
|
|
|
|
tz_offset: proto.tz_offset,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn operation_metadata_to_proto(
|
|
|
|
metadata: &OperationMetadata,
|
|
|
|
) -> crate::protos::op_store::OperationMetadata {
|
|
|
|
crate::protos::op_store::OperationMetadata {
|
|
|
|
start_time: Some(timestamp_to_proto(&metadata.start_time)),
|
|
|
|
end_time: Some(timestamp_to_proto(&metadata.end_time)),
|
|
|
|
description: metadata.description.clone(),
|
|
|
|
hostname: metadata.hostname.clone(),
|
|
|
|
username: metadata.username.clone(),
|
|
|
|
tags: metadata.tags.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn operation_metadata_from_proto(
|
|
|
|
proto: crate::protos::op_store::OperationMetadata,
|
|
|
|
) -> OperationMetadata {
|
|
|
|
let start_time = timestamp_from_proto(proto.start_time.unwrap_or_default());
|
|
|
|
let end_time = timestamp_from_proto(proto.end_time.unwrap_or_default());
|
|
|
|
OperationMetadata {
|
|
|
|
start_time,
|
|
|
|
end_time,
|
|
|
|
description: proto.description,
|
|
|
|
hostname: proto.hostname,
|
|
|
|
username: proto.username,
|
|
|
|
tags: proto.tags,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn operation_to_proto(operation: &Operation) -> crate::protos::op_store::Operation {
|
|
|
|
let mut proto = crate::protos::op_store::Operation {
|
|
|
|
view_id: operation.view_id.as_bytes().to_vec(),
|
|
|
|
metadata: Some(operation_metadata_to_proto(&operation.metadata)),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
for parent in &operation.parents {
|
|
|
|
proto.parents.push(parent.to_bytes());
|
|
|
|
}
|
|
|
|
proto
|
|
|
|
}
|
|
|
|
|
|
|
|
fn operation_from_proto(proto: crate::protos::op_store::Operation) -> Operation {
|
|
|
|
let parents = proto.parents.into_iter().map(OperationId::new).collect();
|
|
|
|
let view_id = ViewId::new(proto.view_id);
|
|
|
|
let metadata = operation_metadata_from_proto(proto.metadata.unwrap_or_default());
|
|
|
|
Operation {
|
|
|
|
view_id,
|
|
|
|
parents,
|
|
|
|
metadata,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn view_to_proto(view: &View) -> crate::protos::op_store::View {
|
2023-09-25 11:05:24 +00:00
|
|
|
let mut proto = crate::protos::op_store::View {
|
|
|
|
// New/loaded view should have been migrated to the latest format
|
|
|
|
has_git_refs_migrated_to_remote: true,
|
|
|
|
..Default::default()
|
|
|
|
};
|
2023-03-30 20:11:18 +00:00
|
|
|
for (workspace_id, commit_id) in &view.wc_commit_ids {
|
|
|
|
proto
|
|
|
|
.wc_commit_ids
|
|
|
|
.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());
|
|
|
|
}
|
|
|
|
for head_id in &view.public_head_ids {
|
|
|
|
proto.public_head_ids.push(head_id.to_bytes());
|
|
|
|
}
|
|
|
|
|
2023-10-05 15:19:41 +00:00
|
|
|
proto.branches = branch_views_to_proto_legacy(&view.local_branches, &view.remote_views);
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
for (name, target) in &view.tags {
|
|
|
|
proto.tags.push(crate::protos::op_store::Tag {
|
|
|
|
name: name.clone(),
|
2023-07-12 14:41:38 +00:00
|
|
|
target: ref_target_to_proto(target),
|
2023-03-30 20:11:18 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (git_ref_name, target) in &view.git_refs {
|
|
|
|
proto.git_refs.push(crate::protos::op_store::GitRef {
|
|
|
|
name: git_ref_name.clone(),
|
2023-07-12 14:41:38 +00:00
|
|
|
target: ref_target_to_proto(target),
|
2023-03-30 20:11:18 +00:00
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-07-12 14:41:38 +00:00
|
|
|
proto.git_head = ref_target_to_proto(&view.git_head);
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
proto
|
|
|
|
}
|
|
|
|
|
|
|
|
fn view_from_proto(proto: crate::protos::op_store::View) -> View {
|
|
|
|
let mut view = View::default();
|
|
|
|
// For compatibility with old repos before we had support for multiple working
|
|
|
|
// copies
|
|
|
|
#[allow(deprecated)]
|
|
|
|
if !proto.wc_commit_id.is_empty() {
|
|
|
|
view.wc_commit_ids
|
|
|
|
.insert(WorkspaceId::default(), CommitId::new(proto.wc_commit_id));
|
|
|
|
}
|
|
|
|
for (workspace_id, commit_id) in proto.wc_commit_ids {
|
|
|
|
view.wc_commit_ids
|
|
|
|
.insert(WorkspaceId::new(workspace_id), CommitId::new(commit_id));
|
|
|
|
}
|
|
|
|
for head_id_bytes in proto.head_ids {
|
|
|
|
view.head_ids.insert(CommitId::new(head_id_bytes));
|
|
|
|
}
|
|
|
|
for head_id_bytes in proto.public_head_ids {
|
|
|
|
view.public_head_ids.insert(CommitId::new(head_id_bytes));
|
|
|
|
}
|
|
|
|
|
2023-10-05 15:19:41 +00:00
|
|
|
let (local_branches, remote_views) = branch_views_from_proto_legacy(proto.branches);
|
|
|
|
view.local_branches = local_branches;
|
|
|
|
view.remote_views = remote_views;
|
2023-03-30 20:11:18 +00:00
|
|
|
|
|
|
|
for tag_proto in proto.tags {
|
2023-07-12 14:41:38 +00:00
|
|
|
view.tags
|
|
|
|
.insert(tag_proto.name, ref_target_from_proto(tag_proto.target));
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for git_ref in proto.git_refs {
|
2023-07-12 13:11:50 +00:00
|
|
|
let target = if git_ref.target.is_some() {
|
|
|
|
ref_target_from_proto(git_ref.target)
|
2023-03-30 20:11:18 +00:00
|
|
|
} else {
|
|
|
|
// Legacy format
|
2023-07-12 13:11:50 +00:00
|
|
|
RefTarget::normal(CommitId::new(git_ref.commit_id))
|
|
|
|
};
|
2023-07-12 14:41:38 +00:00
|
|
|
view.git_refs.insert(git_ref.name, target);
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(deprecated)]
|
2023-07-12 13:11:50 +00:00
|
|
|
if proto.git_head.is_some() {
|
|
|
|
view.git_head = ref_target_from_proto(proto.git_head);
|
2023-03-30 20:11:18 +00:00
|
|
|
} else if !proto.git_head_legacy.is_empty() {
|
2023-07-11 13:14:59 +00:00
|
|
|
view.git_head = RefTarget::normal(CommitId::new(proto.git_head_legacy));
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
|
|
|
|
2023-09-25 11:05:24 +00:00
|
|
|
if !proto.has_git_refs_migrated_to_remote {
|
|
|
|
migrate_git_refs_to_remote(&mut view);
|
|
|
|
}
|
|
|
|
|
2023-03-30 20:11:18 +00:00
|
|
|
view
|
|
|
|
}
|
|
|
|
|
2023-09-10 02:07:31 +00:00
|
|
|
fn branch_views_to_proto_legacy(
|
|
|
|
local_branches: &BTreeMap<String, RefTarget>,
|
|
|
|
remote_views: &BTreeMap<String, RemoteView>,
|
|
|
|
) -> Vec<crate::protos::op_store::Branch> {
|
|
|
|
op_store::merge_join_branch_views(local_branches, remote_views)
|
|
|
|
.map(|(name, branch_target)| {
|
2023-10-12 09:54:52 +00:00
|
|
|
let local_target = ref_target_to_proto(branch_target.local_target);
|
2023-09-10 02:07:31 +00:00
|
|
|
let remote_branches = branch_target
|
2023-10-12 10:00:55 +00:00
|
|
|
.remote_refs
|
2023-10-12 09:54:52 +00:00
|
|
|
.iter()
|
2023-09-10 02:07:31 +00:00
|
|
|
.map(
|
2023-10-12 10:00:55 +00:00
|
|
|
|&(remote_name, remote_ref)| crate::protos::op_store::RemoteBranch {
|
2023-10-12 09:54:52 +00:00
|
|
|
remote_name: remote_name.to_owned(),
|
2023-10-12 10:00:55 +00:00
|
|
|
target: ref_target_to_proto(&remote_ref.target),
|
2023-10-12 16:32:18 +00:00
|
|
|
state: remote_ref_state_to_proto(remote_ref.state),
|
2023-09-10 02:07:31 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
.collect();
|
|
|
|
crate::protos::op_store::Branch {
|
|
|
|
name: name.to_owned(),
|
|
|
|
local_target,
|
|
|
|
remote_branches,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn branch_views_from_proto_legacy(
|
|
|
|
branches_legacy: Vec<crate::protos::op_store::Branch>,
|
|
|
|
) -> (BTreeMap<String, RefTarget>, BTreeMap<String, RemoteView>) {
|
|
|
|
let mut local_branches: BTreeMap<String, RefTarget> = BTreeMap::new();
|
|
|
|
let mut remote_views: BTreeMap<String, RemoteView> = BTreeMap::new();
|
|
|
|
for branch_proto in branches_legacy {
|
2023-10-11 17:15:17 +00:00
|
|
|
let local_target = ref_target_from_proto(branch_proto.local_target);
|
2023-09-10 02:07:31 +00:00
|
|
|
for remote_branch in branch_proto.remote_branches {
|
2023-10-12 16:32:18 +00:00
|
|
|
let state = remote_ref_state_from_proto(remote_branch.state).unwrap_or_else(|| {
|
|
|
|
// If local branch doesn't exist, we assume that the remote branch hasn't been
|
|
|
|
// merged because git.auto-local-branch was off. That's probably more common
|
|
|
|
// than deleted but yet-to-be-pushed local branch. Alternatively, we could read
|
|
|
|
// git.auto-local-branch setting here, but that wouldn't always work since the
|
|
|
|
// setting could be toggled after the branch got merged.
|
|
|
|
let is_git_tracking =
|
|
|
|
remote_branch.remote_name == git::REMOTE_NAME_FOR_LOCAL_GIT_REPO;
|
|
|
|
let default_state = if is_git_tracking || local_target.is_present() {
|
|
|
|
RemoteRefState::Tracking
|
|
|
|
} else {
|
|
|
|
RemoteRefState::New
|
|
|
|
};
|
|
|
|
tracing::trace!(
|
|
|
|
?branch_proto.name,
|
|
|
|
?remote_branch.remote_name,
|
|
|
|
?default_state,
|
|
|
|
"generated tracking state",
|
|
|
|
);
|
|
|
|
default_state
|
|
|
|
});
|
2023-09-10 02:07:31 +00:00
|
|
|
let remote_view = remote_views.entry(remote_branch.remote_name).or_default();
|
|
|
|
let remote_ref = RemoteRef {
|
|
|
|
target: ref_target_from_proto(remote_branch.target),
|
2023-10-11 17:15:17 +00:00
|
|
|
state,
|
2023-09-10 02:07:31 +00:00
|
|
|
};
|
|
|
|
remote_view
|
|
|
|
.branches
|
|
|
|
.insert(branch_proto.name.clone(), remote_ref);
|
|
|
|
}
|
|
|
|
if local_target.is_present() {
|
|
|
|
local_branches.insert(branch_proto.name, local_target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(local_branches, remote_views)
|
|
|
|
}
|
|
|
|
|
2023-09-27 09:01:03 +00:00
|
|
|
fn migrate_git_refs_to_remote(view: &mut View) {
|
|
|
|
if view.git_refs.is_empty() {
|
|
|
|
// Not a repo backed by Git?
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tracing::info!("migrating Git-tracking branches");
|
2023-10-05 15:19:41 +00:00
|
|
|
let mut git_view = RemoteView::default();
|
2023-09-25 11:05:24 +00:00
|
|
|
for (full_name, target) in &view.git_refs {
|
|
|
|
if let Some(name) = full_name.strip_prefix("refs/heads/") {
|
|
|
|
assert!(!name.is_empty());
|
2023-10-05 15:19:41 +00:00
|
|
|
let remote_ref = RemoteRef {
|
|
|
|
target: target.clone(),
|
2023-10-11 17:15:17 +00:00
|
|
|
// Git-tracking branches should never be untracked.
|
|
|
|
state: RemoteRefState::Tracking,
|
2023-10-05 15:19:41 +00:00
|
|
|
};
|
|
|
|
git_view.branches.insert(name.to_owned(), remote_ref);
|
2023-09-25 11:05:24 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-05 15:19:41 +00:00
|
|
|
view.remote_views
|
|
|
|
.insert(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO.to_owned(), git_view);
|
2023-09-27 09:01:03 +00:00
|
|
|
|
|
|
|
// jj < 0.9 might have imported refs from remote named "git"
|
|
|
|
let reserved_git_ref_prefix = format!("refs/remotes/{}/", git::REMOTE_NAME_FOR_LOCAL_GIT_REPO);
|
|
|
|
view.git_refs
|
|
|
|
.retain(|name, _| !name.starts_with(&reserved_git_ref_prefix));
|
|
|
|
}
|
|
|
|
|
2023-07-12 22:20:44 +00:00
|
|
|
fn ref_target_to_proto(value: &RefTarget) -> Option<crate::protos::op_store::RefTarget> {
|
2023-07-15 15:54:50 +00:00
|
|
|
let term_to_proto = |term: &Option<CommitId>| crate::protos::op_store::ref_conflict::Term {
|
|
|
|
value: term.as_ref().map(|id| id.to_bytes()),
|
|
|
|
};
|
2023-08-06 22:10:07 +00:00
|
|
|
let merge = value.as_merge();
|
2023-07-15 15:54:50 +00:00
|
|
|
let conflict_proto = crate::protos::op_store::RefConflict {
|
2023-10-17 18:19:53 +00:00
|
|
|
removes: merge.removes().map(term_to_proto).collect(),
|
|
|
|
adds: merge.adds().map(term_to_proto).collect(),
|
2023-07-15 15:54:50 +00:00
|
|
|
};
|
|
|
|
let proto = crate::protos::op_store::RefTarget {
|
|
|
|
value: Some(crate::protos::op_store::ref_target::Value::Conflict(
|
|
|
|
conflict_proto,
|
|
|
|
)),
|
|
|
|
};
|
|
|
|
Some(proto)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
#[cfg(test)]
|
|
|
|
fn ref_target_to_proto_legacy(value: &RefTarget) -> Option<crate::protos::op_store::RefTarget> {
|
2023-07-11 19:36:25 +00:00
|
|
|
if let Some(id) = value.as_normal() {
|
|
|
|
let proto = crate::protos::op_store::RefTarget {
|
|
|
|
value: Some(crate::protos::op_store::ref_target::Value::CommitId(
|
2023-03-30 20:11:18 +00:00
|
|
|
id.to_bytes(),
|
2023-07-11 19:36:25 +00:00
|
|
|
)),
|
|
|
|
};
|
|
|
|
Some(proto)
|
2023-07-19 12:31:49 +00:00
|
|
|
} else if value.has_conflict() {
|
2023-07-15 14:57:35 +00:00
|
|
|
let ref_conflict_proto = crate::protos::op_store::RefConflictLegacy {
|
2023-07-12 14:08:47 +00:00
|
|
|
removes: value.removed_ids().map(|id| id.to_bytes()).collect(),
|
|
|
|
adds: value.added_ids().map(|id| id.to_bytes()).collect(),
|
2023-07-11 19:36:25 +00:00
|
|
|
};
|
|
|
|
let proto = crate::protos::op_store::RefTarget {
|
2023-07-15 14:57:35 +00:00
|
|
|
value: Some(crate::protos::op_store::ref_target::Value::ConflictLegacy(
|
2023-03-30 20:11:18 +00:00
|
|
|
ref_conflict_proto,
|
2023-07-11 19:36:25 +00:00
|
|
|
)),
|
|
|
|
};
|
|
|
|
Some(proto)
|
|
|
|
} else {
|
|
|
|
assert!(value.is_absent());
|
|
|
|
None
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 22:20:44 +00:00
|
|
|
fn ref_target_from_proto(maybe_proto: Option<crate::protos::op_store::RefTarget>) -> RefTarget {
|
2023-07-15 15:54:50 +00:00
|
|
|
// TODO: Delete legacy format handling when we decide to drop support for views
|
|
|
|
// saved by jj <= 0.8.
|
2023-07-12 22:20:44 +00:00
|
|
|
let Some(proto) = maybe_proto else {
|
2023-07-15 14:57:35 +00:00
|
|
|
// Legacy absent id
|
2023-07-12 22:20:44 +00:00
|
|
|
return RefTarget::absent();
|
|
|
|
};
|
2023-03-30 20:11:18 +00:00
|
|
|
match proto.value.unwrap() {
|
2023-07-15 14:57:35 +00:00
|
|
|
#[allow(deprecated)]
|
2023-03-30 20:11:18 +00:00
|
|
|
crate::protos::op_store::ref_target::Value::CommitId(id) => {
|
2023-07-15 14:57:35 +00:00
|
|
|
// Legacy non-conflicting id
|
2023-07-12 13:11:50 +00:00
|
|
|
RefTarget::normal(CommitId::new(id))
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
2023-07-15 14:57:35 +00:00
|
|
|
#[allow(deprecated)]
|
|
|
|
crate::protos::op_store::ref_target::Value::ConflictLegacy(conflict) => {
|
|
|
|
// Legacy conflicting ids
|
2023-07-12 13:11:50 +00:00
|
|
|
let removes = conflict.removes.into_iter().map(CommitId::new);
|
|
|
|
let adds = conflict.adds.into_iter().map(CommitId::new);
|
|
|
|
RefTarget::from_legacy_form(removes, adds)
|
2023-03-30 20:11:18 +00:00
|
|
|
}
|
2023-07-15 14:57:35 +00:00
|
|
|
crate::protos::op_store::ref_target::Value::Conflict(conflict) => {
|
|
|
|
let term_from_proto =
|
|
|
|
|term: crate::protos::op_store::ref_conflict::Term| term.value.map(CommitId::new);
|
2023-11-05 02:59:56 +00:00
|
|
|
let removes = conflict.removes.into_iter().map(term_from_proto);
|
|
|
|
let adds = conflict.adds.into_iter().map(term_from_proto);
|
2023-11-05 02:48:06 +00:00
|
|
|
RefTarget::from_merge(Merge::from_removes_adds(removes, adds))
|
2023-07-15 14:57:35 +00:00
|
|
|
}
|
2021-07-31 00:47:30 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-02 16:51:25 +00:00
|
|
|
|
2023-10-12 16:32:18 +00:00
|
|
|
fn remote_ref_state_to_proto(state: RemoteRefState) -> Option<i32> {
|
|
|
|
let proto_state = match state {
|
|
|
|
RemoteRefState::New => crate::protos::op_store::RemoteRefState::New,
|
|
|
|
RemoteRefState::Tracking => crate::protos::op_store::RemoteRefState::Tracking,
|
|
|
|
};
|
|
|
|
Some(proto_state as i32)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remote_ref_state_from_proto(proto_value: Option<i32>) -> Option<RemoteRefState> {
|
|
|
|
let proto_state = proto_value.and_then(crate::protos::op_store::RemoteRefState::from_i32)?;
|
|
|
|
let state = match proto_state {
|
|
|
|
crate::protos::op_store::RemoteRefState::New => RemoteRefState::New,
|
|
|
|
crate::protos::op_store::RemoteRefState::Tracking => RemoteRefState::Tracking,
|
|
|
|
};
|
|
|
|
Some(state)
|
|
|
|
}
|
|
|
|
|
2022-11-02 16:51:25 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use insta::assert_snapshot;
|
2023-09-10 02:07:31 +00:00
|
|
|
use itertools::Itertools as _;
|
2022-11-02 16:51:25 +00:00
|
|
|
use maplit::{btreemap, hashmap, hashset};
|
|
|
|
|
|
|
|
use super::*;
|
2023-01-01 03:24:32 +00:00
|
|
|
use crate::backend::{CommitId, MillisSinceEpoch, ObjectId, Timestamp};
|
2022-12-02 17:52:17 +00:00
|
|
|
use crate::content_hash::blake2b_hash;
|
2023-10-05 15:19:41 +00:00
|
|
|
use crate::op_store::{OperationMetadata, RefTarget, WorkspaceId};
|
2022-11-02 16:51:25 +00:00
|
|
|
|
|
|
|
fn create_view() -> View {
|
2023-10-11 17:15:17 +00:00
|
|
|
let new_remote_ref = |target: &RefTarget| RemoteRef {
|
|
|
|
target: target.clone(),
|
|
|
|
state: RemoteRefState::New,
|
|
|
|
};
|
|
|
|
let tracking_remote_ref = |target: &RefTarget| RemoteRef {
|
2023-10-05 15:19:41 +00:00
|
|
|
target: target.clone(),
|
2023-10-11 17:15:17 +00:00
|
|
|
state: RemoteRefState::Tracking,
|
2023-10-05 15:19:41 +00:00
|
|
|
};
|
2022-11-02 16:51:25 +00:00
|
|
|
let head_id1 = CommitId::from_hex("aaa111");
|
|
|
|
let head_id2 = CommitId::from_hex("aaa222");
|
|
|
|
let public_head_id1 = CommitId::from_hex("bbb444");
|
|
|
|
let public_head_id2 = CommitId::from_hex("bbb555");
|
2023-07-11 13:14:59 +00:00
|
|
|
let branch_main_local_target = RefTarget::normal(CommitId::from_hex("ccc111"));
|
|
|
|
let branch_main_origin_target = RefTarget::normal(CommitId::from_hex("ccc222"));
|
|
|
|
let branch_deleted_origin_target = RefTarget::normal(CommitId::from_hex("ccc333"));
|
|
|
|
let tag_v1_target = RefTarget::normal(CommitId::from_hex("ddd111"));
|
|
|
|
let git_refs_main_target = RefTarget::normal(CommitId::from_hex("fff111"));
|
2023-07-11 15:22:21 +00:00
|
|
|
let git_refs_feature_target = RefTarget::from_legacy_form(
|
|
|
|
[CommitId::from_hex("fff111")],
|
|
|
|
[CommitId::from_hex("fff222"), CommitId::from_hex("fff333")],
|
|
|
|
);
|
2022-11-02 16:51:25 +00:00
|
|
|
let default_wc_commit_id = CommitId::from_hex("abc111");
|
|
|
|
let test_wc_commit_id = CommitId::from_hex("abc222");
|
|
|
|
View {
|
|
|
|
head_ids: hashset! {head_id1, head_id2},
|
|
|
|
public_head_ids: hashset! {public_head_id1, public_head_id2},
|
2023-10-05 15:19:41 +00:00
|
|
|
local_branches: btreemap! {
|
|
|
|
"main".to_string() => branch_main_local_target,
|
2022-11-02 16:51:25 +00:00
|
|
|
},
|
2023-07-15 15:54:50 +00:00
|
|
|
tags: btreemap! {
|
2023-07-12 14:41:38 +00:00
|
|
|
"v1.0".to_string() => tag_v1_target,
|
2023-07-15 15:54:50 +00:00
|
|
|
},
|
2023-10-05 15:19:41 +00:00
|
|
|
remote_views: btreemap! {
|
|
|
|
"origin".to_string() => RemoteView {
|
|
|
|
branches: btreemap! {
|
2023-10-11 17:15:17 +00:00
|
|
|
"main".to_string() => tracking_remote_ref(&branch_main_origin_target),
|
|
|
|
"deleted".to_string() => new_remote_ref(&branch_deleted_origin_target),
|
2023-10-05 15:19:41 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-07-15 15:54:50 +00:00
|
|
|
git_refs: btreemap! {
|
2023-07-12 14:41:38 +00:00
|
|
|
"refs/heads/main".to_string() => git_refs_main_target,
|
|
|
|
"refs/heads/feature".to_string() => git_refs_feature_target,
|
2023-07-15 15:54:50 +00:00
|
|
|
},
|
2023-07-11 13:14:59 +00:00
|
|
|
git_head: RefTarget::normal(CommitId::from_hex("fff111")),
|
2022-11-02 16:51:25 +00:00
|
|
|
wc_commit_ids: hashmap! {
|
|
|
|
WorkspaceId::default() => default_wc_commit_id,
|
|
|
|
WorkspaceId::new("test".to_string()) => test_wc_commit_id,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_operation() -> Operation {
|
|
|
|
Operation {
|
|
|
|
view_id: ViewId::from_hex("aaa111"),
|
|
|
|
parents: vec![
|
|
|
|
OperationId::from_hex("bbb111"),
|
|
|
|
OperationId::from_hex("bbb222"),
|
|
|
|
],
|
|
|
|
metadata: OperationMetadata {
|
|
|
|
start_time: Timestamp {
|
|
|
|
timestamp: MillisSinceEpoch(123456789),
|
|
|
|
tz_offset: 3600,
|
|
|
|
},
|
|
|
|
end_time: Timestamp {
|
|
|
|
timestamp: MillisSinceEpoch(123456800),
|
|
|
|
tz_offset: 3600,
|
|
|
|
},
|
|
|
|
description: "check out foo".to_string(),
|
|
|
|
hostname: "some.host.example.com".to_string(),
|
|
|
|
username: "someone".to_string(),
|
|
|
|
tags: hashmap! {
|
|
|
|
"key1".to_string() => "value1".to_string(),
|
|
|
|
"key2".to_string() => "value2".to_string(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hash_view() {
|
|
|
|
// Test exact output so we detect regressions in compatibility
|
|
|
|
assert_snapshot!(
|
2022-12-02 18:03:00 +00:00
|
|
|
ViewId::new(blake2b_hash(&create_view()).to_vec()).hex(),
|
2023-11-03 05:19:59 +00:00
|
|
|
@"d6d19f3edcb3b2fed6104801b7938e6be1147ab036e9fa81b7624fd5ff0149a6c221c3abb6fb7380c3f37e077a03b234313b44cbb6faca0b8c76f68f24ea7174"
|
2022-11-02 16:51:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hash_operation() {
|
|
|
|
// Test exact output so we detect regressions in compatibility
|
|
|
|
assert_snapshot!(
|
2022-12-02 18:03:00 +00:00
|
|
|
OperationId::new(blake2b_hash(&create_operation()).to_vec()).hex(),
|
2022-11-02 16:51:25 +00:00
|
|
|
@"3ec986c29ff8eb808ea8f6325d6307cea75ef02987536c8e4645406aba51afc8e229957a6e855170d77a66098c58912309323f5e0b32760caa2b59dc84d45fcf"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_read_write_view() {
|
|
|
|
let temp_dir = testutils::new_temp_dir();
|
2022-12-14 19:10:42 +00:00
|
|
|
let store = SimpleOpStore::init(temp_dir.path());
|
2022-11-02 16:51:25 +00:00
|
|
|
let view = create_view();
|
|
|
|
let view_id = store.write_view(&view).unwrap();
|
|
|
|
let read_view = store.read_view(&view_id).unwrap();
|
|
|
|
assert_eq!(read_view, view);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_read_write_operation() {
|
|
|
|
let temp_dir = testutils::new_temp_dir();
|
2022-12-14 19:10:42 +00:00
|
|
|
let store = SimpleOpStore::init(temp_dir.path());
|
2022-11-02 16:51:25 +00:00
|
|
|
let operation = create_operation();
|
|
|
|
let op_id = store.write_operation(&operation).unwrap();
|
|
|
|
let read_operation = store.read_operation(&op_id).unwrap();
|
|
|
|
assert_eq!(read_operation, operation);
|
|
|
|
}
|
2023-07-15 14:57:35 +00:00
|
|
|
|
2023-09-10 02:07:31 +00:00
|
|
|
#[test]
|
|
|
|
fn test_branch_views_legacy_roundtrip() {
|
2023-10-11 17:15:17 +00:00
|
|
|
let new_remote_ref = |target: &RefTarget| RemoteRef {
|
2023-09-10 02:07:31 +00:00
|
|
|
target: target.clone(),
|
2023-10-11 17:15:17 +00:00
|
|
|
state: RemoteRefState::New,
|
|
|
|
};
|
|
|
|
let tracking_remote_ref = |target: &RefTarget| RemoteRef {
|
|
|
|
target: target.clone(),
|
|
|
|
state: RemoteRefState::Tracking,
|
2023-09-10 02:07:31 +00:00
|
|
|
};
|
|
|
|
let local_branch1_target = RefTarget::normal(CommitId::from_hex("111111"));
|
|
|
|
let local_branch3_target = RefTarget::normal(CommitId::from_hex("222222"));
|
|
|
|
let git_branch1_target = RefTarget::normal(CommitId::from_hex("333333"));
|
|
|
|
let remote1_branch1_target = RefTarget::normal(CommitId::from_hex("444444"));
|
|
|
|
let remote2_branch2_target = RefTarget::normal(CommitId::from_hex("555555"));
|
|
|
|
let remote2_branch4_target = RefTarget::normal(CommitId::from_hex("666666"));
|
|
|
|
let local_branches = btreemap! {
|
|
|
|
"branch1".to_owned() => local_branch1_target.clone(),
|
|
|
|
"branch3".to_owned() => local_branch3_target.clone(),
|
|
|
|
};
|
|
|
|
let remote_views = btreemap! {
|
|
|
|
"git".to_owned() => RemoteView {
|
|
|
|
branches: btreemap! {
|
2023-10-11 17:15:17 +00:00
|
|
|
"branch1".to_owned() => tracking_remote_ref(&git_branch1_target),
|
2023-09-10 02:07:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"remote1".to_owned() => RemoteView {
|
|
|
|
branches: btreemap! {
|
2023-10-11 17:15:17 +00:00
|
|
|
"branch1".to_owned() => tracking_remote_ref(&remote1_branch1_target),
|
2023-09-10 02:07:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"remote2".to_owned() => RemoteView {
|
|
|
|
branches: btreemap! {
|
2023-10-12 16:32:18 +00:00
|
|
|
// "branch2" is non-tracking. "branch4" is tracking, but locally deleted.
|
2023-10-11 17:15:17 +00:00
|
|
|
"branch2".to_owned() => new_remote_ref(&remote2_branch2_target),
|
2023-10-12 16:32:18 +00:00
|
|
|
"branch4".to_owned() => tracking_remote_ref(&remote2_branch4_target),
|
2023-09-10 02:07:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let branches_legacy = branch_views_to_proto_legacy(&local_branches, &remote_views);
|
|
|
|
assert_eq!(
|
|
|
|
branches_legacy
|
|
|
|
.iter()
|
|
|
|
.map(|proto| &proto.name)
|
|
|
|
.sorted()
|
|
|
|
.collect_vec(),
|
|
|
|
vec!["branch1", "branch2", "branch3", "branch4"],
|
|
|
|
);
|
|
|
|
|
|
|
|
let (local_branches_reconstructed, remote_views_reconstructed) =
|
|
|
|
branch_views_from_proto_legacy(branches_legacy);
|
|
|
|
assert_eq!(local_branches_reconstructed, local_branches);
|
|
|
|
assert_eq!(remote_views_reconstructed, remote_views);
|
|
|
|
}
|
|
|
|
|
2023-09-27 09:01:03 +00:00
|
|
|
#[test]
|
|
|
|
fn test_migrate_git_refs_remote_named_git() {
|
|
|
|
let normal_ref_target = |id_hex: &str| RefTarget::normal(CommitId::from_hex(id_hex));
|
2023-10-11 17:15:17 +00:00
|
|
|
let normal_new_remote_ref = |id_hex: &str| RemoteRef {
|
2023-10-05 15:19:41 +00:00
|
|
|
target: normal_ref_target(id_hex),
|
2023-10-11 17:15:17 +00:00
|
|
|
state: RemoteRefState::New,
|
|
|
|
};
|
|
|
|
let normal_tracking_remote_ref = |id_hex: &str| RemoteRef {
|
|
|
|
target: normal_ref_target(id_hex),
|
|
|
|
state: RemoteRefState::Tracking,
|
2023-10-05 15:19:41 +00:00
|
|
|
};
|
2023-09-27 09:01:03 +00:00
|
|
|
let branch_to_proto =
|
|
|
|
|name: &str, local_ref_target, remote_branches| crate::protos::op_store::Branch {
|
|
|
|
name: name.to_owned(),
|
|
|
|
local_target: ref_target_to_proto(local_ref_target),
|
|
|
|
remote_branches,
|
|
|
|
};
|
|
|
|
let remote_branch_to_proto =
|
|
|
|
|remote_name: &str, ref_target| crate::protos::op_store::RemoteBranch {
|
|
|
|
remote_name: remote_name.to_owned(),
|
|
|
|
target: ref_target_to_proto(ref_target),
|
2023-10-12 16:32:18 +00:00
|
|
|
state: None, // to be generated based on local branch existence
|
2023-09-27 09:01:03 +00:00
|
|
|
};
|
|
|
|
let git_ref_to_proto = |name: &str, ref_target| crate::protos::op_store::GitRef {
|
|
|
|
name: name.to_owned(),
|
|
|
|
target: ref_target_to_proto(ref_target),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let proto = crate::protos::op_store::View {
|
2023-10-11 17:15:17 +00:00
|
|
|
branches: vec![
|
|
|
|
branch_to_proto(
|
|
|
|
"main",
|
|
|
|
&normal_ref_target("111111"),
|
|
|
|
vec![
|
|
|
|
remote_branch_to_proto("git", &normal_ref_target("222222")),
|
|
|
|
remote_branch_to_proto("gita", &normal_ref_target("333333")),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
branch_to_proto(
|
|
|
|
"untracked",
|
|
|
|
RefTarget::absent_ref(),
|
|
|
|
vec![remote_branch_to_proto("gita", &normal_ref_target("777777"))],
|
|
|
|
),
|
|
|
|
],
|
2023-09-27 09:01:03 +00:00
|
|
|
git_refs: vec![
|
|
|
|
git_ref_to_proto("refs/heads/main", &normal_ref_target("444444")),
|
|
|
|
git_ref_to_proto("refs/remotes/git/main", &normal_ref_target("555555")),
|
|
|
|
git_ref_to_proto("refs/remotes/gita/main", &normal_ref_target("666666")),
|
2023-10-11 17:15:17 +00:00
|
|
|
git_ref_to_proto("refs/remotes/gita/untracked", &normal_ref_target("888888")),
|
2023-09-27 09:01:03 +00:00
|
|
|
],
|
2023-09-25 11:05:24 +00:00
|
|
|
has_git_refs_migrated_to_remote: false,
|
2023-09-27 09:01:03 +00:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let view = view_from_proto(proto);
|
|
|
|
assert_eq!(
|
2023-10-05 15:19:41 +00:00
|
|
|
view.local_branches,
|
|
|
|
btreemap! {
|
|
|
|
"main".to_owned() => normal_ref_target("111111"),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
view.remote_views,
|
2023-09-27 09:01:03 +00:00
|
|
|
btreemap! {
|
2023-10-05 15:19:41 +00:00
|
|
|
"git".to_owned() => RemoteView {
|
|
|
|
branches: btreemap! {
|
2023-10-11 17:15:17 +00:00
|
|
|
"main".to_owned() => normal_tracking_remote_ref("444444"), // refs/heads/main
|
2023-10-05 15:19:41 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"gita".to_owned() => RemoteView {
|
|
|
|
branches: btreemap! {
|
2023-10-11 17:15:17 +00:00
|
|
|
"main".to_owned() => normal_tracking_remote_ref("333333"),
|
|
|
|
"untracked".to_owned() => normal_new_remote_ref("777777"),
|
2023-09-27 09:01:03 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
view.git_refs,
|
|
|
|
btreemap! {
|
|
|
|
"refs/heads/main".to_owned() => normal_ref_target("444444"),
|
|
|
|
"refs/remotes/gita/main".to_owned() => normal_ref_target("666666"),
|
2023-10-11 17:15:17 +00:00
|
|
|
"refs/remotes/gita/untracked".to_owned() => normal_ref_target("888888"),
|
2023-09-27 09:01:03 +00:00
|
|
|
},
|
|
|
|
);
|
2023-09-25 11:05:24 +00:00
|
|
|
|
|
|
|
// Once migrated, "git" remote branches shouldn't be populated again.
|
|
|
|
let mut proto = view_to_proto(&view);
|
|
|
|
assert!(proto.has_git_refs_migrated_to_remote);
|
|
|
|
proto.branches.clear();
|
|
|
|
let view = view_from_proto(proto);
|
2023-10-05 15:19:41 +00:00
|
|
|
assert!(!view.remote_views.contains_key("git"));
|
2023-09-27 09:01:03 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 15:54:50 +00:00
|
|
|
#[test]
|
|
|
|
fn test_ref_target_change_delete_order_roundtrip() {
|
2023-11-05 02:48:06 +00:00
|
|
|
let target = RefTarget::from_merge(Merge::from_removes_adds(
|
2023-07-15 15:54:50 +00:00
|
|
|
vec![Some(CommitId::from_hex("111111"))],
|
|
|
|
vec![Some(CommitId::from_hex("222222")), None],
|
|
|
|
));
|
|
|
|
let maybe_proto = ref_target_to_proto(&target);
|
|
|
|
assert_eq!(ref_target_from_proto(maybe_proto), target);
|
|
|
|
|
|
|
|
// If it were legacy format, order of None entry would be lost.
|
2023-11-05 02:48:06 +00:00
|
|
|
let target = RefTarget::from_merge(Merge::from_removes_adds(
|
2023-07-15 15:54:50 +00:00
|
|
|
vec![Some(CommitId::from_hex("111111"))],
|
|
|
|
vec![None, Some(CommitId::from_hex("222222"))],
|
|
|
|
));
|
|
|
|
let maybe_proto = ref_target_to_proto(&target);
|
|
|
|
assert_eq!(ref_target_from_proto(maybe_proto), target);
|
|
|
|
}
|
|
|
|
|
2023-07-15 14:57:35 +00:00
|
|
|
#[test]
|
|
|
|
fn test_ref_target_legacy_roundtrip() {
|
|
|
|
let target = RefTarget::absent();
|
2023-07-15 15:54:50 +00:00
|
|
|
let maybe_proto = ref_target_to_proto_legacy(&target);
|
2023-07-15 14:57:35 +00:00
|
|
|
assert_eq!(ref_target_from_proto(maybe_proto), target);
|
|
|
|
|
|
|
|
let target = RefTarget::normal(CommitId::from_hex("111111"));
|
2023-07-15 15:54:50 +00:00
|
|
|
let maybe_proto = ref_target_to_proto_legacy(&target);
|
2023-07-15 14:57:35 +00:00
|
|
|
assert_eq!(ref_target_from_proto(maybe_proto), target);
|
|
|
|
|
|
|
|
// N-way conflict
|
|
|
|
let target = RefTarget::from_legacy_form(
|
|
|
|
[CommitId::from_hex("111111"), CommitId::from_hex("222222")],
|
|
|
|
[
|
|
|
|
CommitId::from_hex("333333"),
|
|
|
|
CommitId::from_hex("444444"),
|
|
|
|
CommitId::from_hex("555555"),
|
|
|
|
],
|
|
|
|
);
|
2023-07-15 15:54:50 +00:00
|
|
|
let maybe_proto = ref_target_to_proto_legacy(&target);
|
2023-07-15 14:57:35 +00:00
|
|
|
assert_eq!(ref_target_from_proto(maybe_proto), target);
|
|
|
|
|
|
|
|
// Change-delete conflict
|
|
|
|
let target = RefTarget::from_legacy_form(
|
|
|
|
[CommitId::from_hex("111111")],
|
|
|
|
[CommitId::from_hex("222222")],
|
|
|
|
);
|
2023-07-15 15:54:50 +00:00
|
|
|
let maybe_proto = ref_target_to_proto_legacy(&target);
|
2023-07-15 14:57:35 +00:00
|
|
|
assert_eq!(ref_target_from_proto(maybe_proto), target);
|
|
|
|
}
|
2022-11-02 16:51:25 +00:00
|
|
|
}
|