lib: replace protobuf crate with prost

This commit is contained in:
Benjamin Saunders 2022-12-21 11:14:46 -08:00
parent 300f744e41
commit aaa175eca7
16 changed files with 702 additions and 287 deletions

View file

@ -42,6 +42,20 @@ jobs:
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
check-protos:
name: Check protos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b
- uses: dtolnay/rust-toolchain@e645b0cf01249a964ec099494d38d2da0f0b349f
with:
toolchain: stable
- run: sudo apt update && sudo apt-get -y install protobuf-compiler
- name: Generate Rust code from .proto files
run: cargo run -p gen-protos
- name: Check for uncommitted changes
run: git diff --exit-code
rustfmt: rustfmt:
name: Check formatting name: Check formatting
runs-on: ubuntu-latest runs-on: ubuntu-latest

111
Cargo.lock generated
View file

@ -545,6 +545,12 @@ dependencies = [
"instant", "instant",
] ]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]] [[package]]
name = "float-cmp" name = "float-cmp"
version = "0.9.0" version = "0.9.0"
@ -564,6 +570,13 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "gen-protos"
version = "0.1.0"
dependencies = [
"prost-build",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.6" version = "0.14.6"
@ -830,8 +843,7 @@ dependencies = [
"once_cell", "once_cell",
"pest", "pest",
"pest_derive", "pest_derive",
"protobuf", "prost",
"protobuf-codegen",
"rand", "rand",
"regex", "regex",
"serde_json", "serde_json",
@ -984,6 +996,12 @@ dependencies = [
"windows-sys 0.36.1", "windows-sys 0.36.1",
] ]
[[package]]
name = "multimap"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.1" version = "7.1.1"
@ -1180,6 +1198,16 @@ dependencies = [
"sha1", "sha1",
] ]
[[package]]
name = "petgraph"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.9" version = "0.2.9"
@ -1256,6 +1284,16 @@ dependencies = [
"termtree", "termtree",
] ]
[[package]]
name = "prettyplease"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773"
dependencies = [
"proc-macro2",
"syn",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -1290,55 +1328,58 @@ dependencies = [
] ]
[[package]] [[package]]
name = "protobuf" name = "prost"
version = "3.2.0" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592"
dependencies = [ dependencies = [
"bytes", "bytes",
"once_cell", "prost-derive",
"protobuf-support",
"thiserror",
] ]
[[package]] [[package]]
name = "protobuf-codegen" name = "prost-build"
version = "3.2.0" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd418ac3c91caa4032d37cb80ff0d44e2ebe637b2fb243b6234bf89cdac4901" checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6"
dependencies = [ dependencies = [
"anyhow", "bytes",
"once_cell", "heck",
"protobuf", "itertools",
"protobuf-parse", "lazy_static",
"regex",
"tempfile",
"thiserror",
]
[[package]]
name = "protobuf-parse"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d39b14605eaa1f6a340aec7f320b34064feb26c93aec35d6a9a2272a8ddfa49"
dependencies = [
"anyhow",
"indexmap",
"log", "log",
"protobuf", "multimap",
"protobuf-support", "petgraph",
"prettyplease",
"prost",
"prost-types",
"regex",
"syn",
"tempfile", "tempfile",
"thiserror",
"which", "which",
] ]
[[package]] [[package]]
name = "protobuf-support" name = "prost-derive"
version = "3.2.0" version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720"
dependencies = [ dependencies = [
"thiserror", "anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "prost-types"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091"
dependencies = [
"bytes",
"prost",
] ]
[[package]] [[package]]

View file

@ -31,7 +31,7 @@ name = "diff_bench"
harness = false harness = false
[workspace] [workspace]
members = ["lib", "lib/testutils"] members = ["lib", "lib/testutils", "lib/gen-protos"]
[dependencies] [dependencies]
chrono = { version = "0.4.23", default-features = false, features = ["std", "clock"] } chrono = { version = "0.4.23", default-features = false, features = ["std", "clock"] }

View file

@ -14,7 +14,6 @@ readme = "../README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
protobuf-codegen = "3.2.0"
version_check = "0.9.4" version_check = "0.9.4"
[dependencies] [dependencies]
@ -32,7 +31,6 @@ maplit = "1.0.2"
once_cell = "1.16.0" once_cell = "1.16.0"
pest = "2.5.1" pest = "2.5.1"
pest_derive = "2.5.1" pest_derive = "2.5.1"
protobuf = { version = "3.0.1", features = ["with-bytes"] }
regex = "1.7.0" regex = "1.7.0"
serde_json = "1.0.91" serde_json = "1.0.91"
tempfile = "3.3.0" tempfile = "3.3.0"
@ -42,6 +40,7 @@ uuid = { version = "1.2.2", features = ["v4"] }
whoami = "1.2.3" whoami = "1.2.3"
zstd = "0.12.1" zstd = "0.12.1"
tracing = "0.1.37" tracing = "0.1.37"
prost = "0.11.5"
[dev-dependencies] [dev-dependencies]
assert_matches = "1.5.0" assert_matches = "1.5.0"

View file

@ -12,24 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
fn main() { fn main() -> std::io::Result<()> {
let input = &[
"src/protos/op_store.proto",
"src/protos/store.proto",
"src/protos/working_copy.proto",
];
protobuf_codegen::Codegen::new()
.pure()
.inputs(input)
.include("src/protos")
.cargo_out_dir("protos")
.run_from_script();
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
for file in input {
println!("cargo:rerun-if-changed={file}");
}
if let Some(true) = version_check::supports_feature("map_first_last") { if let Some(true) = version_check::supports_feature("map_first_last") {
println!("cargo:rustc-cfg=feature=\"map_first_last\""); println!("cargo:rustc-cfg=feature=\"map_first_last\"");
} }
Ok(())
} }

View file

@ -0,0 +1,8 @@
[package]
name = "gen-protos"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
prost-build = "0.11.5"

View file

@ -0,0 +1,34 @@
// Copyright 2022 The Jujutsu Authors
//
// 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.
use std::io::Result;
use std::path::Path;
fn main() -> Result<()> {
let input = ["op_store.proto", "store.proto", "working_copy.proto"];
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let protos_dir = root.join("src").join("protos");
prost_build::Config::new()
.out_dir(&protos_dir)
.include_file("mod.rs")
.compile_protos(
&input
.into_iter()
.map(|x| protos_dir.join(x))
.collect::<Vec<_>>(),
&[protos_dir],
)
}

View file

@ -20,7 +20,7 @@ use std::sync::{Arc, Mutex};
use git2::Oid; use git2::Oid;
use itertools::Itertools; use itertools::Itertools;
use protobuf::Message; use prost::Message;
use uuid::Uuid; use uuid::Uuid;
use crate::backend::{ use crate::backend::{
@ -126,17 +126,18 @@ fn signature_to_git(signature: &Signature) -> git2::Signature {
} }
fn serialize_extras(commit: &Commit) -> Vec<u8> { fn serialize_extras(commit: &Commit) -> Vec<u8> {
let mut proto = crate::protos::store::Commit::new(); let mut proto = crate::protos::store::Commit {
proto.change_id = commit.change_id.to_bytes(); change_id: commit.change_id.to_bytes(),
..Default::default()
};
for predecessor in &commit.predecessors { for predecessor in &commit.predecessors {
proto.predecessors.push(predecessor.to_bytes()); proto.predecessors.push(predecessor.to_bytes());
} }
proto.write_to_bytes().unwrap() proto.encode_to_vec()
} }
fn deserialize_extras(commit: &mut Commit, bytes: &[u8]) { fn deserialize_extras(commit: &mut Commit, bytes: &[u8]) {
let mut cursor = Cursor::new(bytes); let proto = crate::protos::store::Commit::decode(bytes).unwrap();
let proto: crate::protos::store::Commit = Message::parse_from_reader(&mut cursor).unwrap();
commit.change_id = ChangeId::new(proto.change_id); commit.change_id = ChangeId::new(proto.change_id);
for predecessor in &proto.predecessors { for predecessor in &proto.predecessors {
commit.predecessors.push(CommitId::from_bytes(predecessor)); commit.predecessors.push(CommitId::from_bytes(predecessor));

View file

@ -19,7 +19,7 @@ use std::io::{ErrorKind, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use blake2::{Blake2b512, Digest}; use blake2::{Blake2b512, Digest};
use protobuf::{Message, MessageField}; use prost::Message;
use tempfile::{NamedTempFile, PersistError}; use tempfile::{NamedTempFile, PersistError};
use crate::backend::{ use crate::backend::{
@ -43,8 +43,8 @@ impl From<PersistError> for BackendError {
} }
} }
impl From<protobuf::Error> for BackendError { impl From<prost::DecodeError> for BackendError {
fn from(err: protobuf::Error) -> Self { fn from(err: prost::DecodeError) -> Self {
BackendError::Other(err.to_string()) BackendError::Other(err.to_string())
} }
} }
@ -183,17 +183,17 @@ impl Backend for LocalBackend {
fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> { fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
let path = self.tree_path(id); let path = self.tree_path(id);
let mut file = File::open(path).map_err(not_found_to_backend_error)?; let buf = fs::read(path).map_err(not_found_to_backend_error)?;
let proto: crate::protos::store::Tree = Message::parse_from_reader(&mut file)?; let proto = crate::protos::store::Tree::decode(&*buf)?;
Ok(tree_from_proto(&proto)) Ok(tree_from_proto(proto))
} }
fn write_tree(&self, _path: &RepoPath, tree: &Tree) -> BackendResult<TreeId> { fn write_tree(&self, _path: &RepoPath, tree: &Tree) -> BackendResult<TreeId> {
let temp_file = NamedTempFile::new_in(&self.path)?; let temp_file = NamedTempFile::new_in(&self.path)?;
let proto = tree_to_proto(tree); let proto = tree_to_proto(tree);
proto.write_to_writer(&mut temp_file.as_file())?; temp_file.as_file().write_all(&proto.encode_to_vec())?;
let id = TreeId::new(blake2b_hash(tree).to_vec()); let id = TreeId::new(blake2b_hash(tree).to_vec());
@ -203,17 +203,17 @@ impl Backend for LocalBackend {
fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> { fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
let path = self.conflict_path(id); let path = self.conflict_path(id);
let mut file = File::open(path).map_err(not_found_to_backend_error)?; let buf = fs::read(path).map_err(not_found_to_backend_error)?;
let proto: crate::protos::store::Conflict = Message::parse_from_reader(&mut file)?; let proto = crate::protos::store::Conflict::decode(&*buf)?;
Ok(conflict_from_proto(&proto)) Ok(conflict_from_proto(proto))
} }
fn write_conflict(&self, _path: &RepoPath, conflict: &Conflict) -> BackendResult<ConflictId> { fn write_conflict(&self, _path: &RepoPath, conflict: &Conflict) -> BackendResult<ConflictId> {
let temp_file = NamedTempFile::new_in(&self.path)?; let temp_file = NamedTempFile::new_in(&self.path)?;
let proto = conflict_to_proto(conflict); let proto = conflict_to_proto(conflict);
proto.write_to_writer(&mut temp_file.as_file())?; temp_file.as_file().write_all(&proto.encode_to_vec())?;
let id = ConflictId::new(blake2b_hash(conflict).to_vec()); let id = ConflictId::new(blake2b_hash(conflict).to_vec());
@ -227,17 +227,17 @@ impl Backend for LocalBackend {
} }
let path = self.commit_path(id); let path = self.commit_path(id);
let mut file = File::open(path).map_err(not_found_to_backend_error)?; let buf = fs::read(path).map_err(not_found_to_backend_error)?;
let proto: crate::protos::store::Commit = Message::parse_from_reader(&mut file)?; let proto = crate::protos::store::Commit::decode(&*buf)?;
Ok(commit_from_proto(&proto)) Ok(commit_from_proto(proto))
} }
fn write_commit(&self, commit: &Commit) -> BackendResult<CommitId> { fn write_commit(&self, commit: &Commit) -> BackendResult<CommitId> {
let temp_file = NamedTempFile::new_in(&self.path)?; let temp_file = NamedTempFile::new_in(&self.path)?;
let proto = commit_to_proto(commit); let proto = commit_to_proto(commit);
proto.write_to_writer(&mut temp_file.as_file())?; temp_file.as_file().write_all(&proto.encode_to_vec())?;
let id = CommitId::new(blake2b_hash(commit).to_vec()); let id = CommitId::new(blake2b_hash(commit).to_vec());
@ -247,7 +247,7 @@ impl Backend for LocalBackend {
} }
pub fn commit_to_proto(commit: &Commit) -> crate::protos::store::Commit { pub fn commit_to_proto(commit: &Commit) -> crate::protos::store::Commit {
let mut proto = crate::protos::store::Commit::new(); let mut proto = crate::protos::store::Commit::default();
for parent in &commit.parents { for parent in &commit.parents {
proto.parents.push(parent.to_bytes()); proto.parents.push(parent.to_bytes());
} }
@ -257,115 +257,116 @@ pub fn commit_to_proto(commit: &Commit) -> crate::protos::store::Commit {
proto.root_tree = commit.root_tree.to_bytes(); proto.root_tree = commit.root_tree.to_bytes();
proto.change_id = commit.change_id.to_bytes(); proto.change_id = commit.change_id.to_bytes();
proto.description = commit.description.clone(); proto.description = commit.description.clone();
proto.author = MessageField::some(signature_to_proto(&commit.author)); proto.author = Some(signature_to_proto(&commit.author));
proto.committer = MessageField::some(signature_to_proto(&commit.committer)); proto.committer = Some(signature_to_proto(&commit.committer));
proto proto
} }
fn commit_from_proto(proto: &crate::protos::store::Commit) -> Commit { fn commit_from_proto(proto: crate::protos::store::Commit) -> Commit {
let commit_id_from_proto = |parent: &Vec<u8>| CommitId::new(parent.clone()); let parents = proto.parents.into_iter().map(CommitId::new).collect();
let parents = proto.parents.iter().map(commit_id_from_proto).collect(); let predecessors = proto.predecessors.into_iter().map(CommitId::new).collect();
let predecessors = proto let root_tree = TreeId::new(proto.root_tree);
.predecessors let change_id = ChangeId::new(proto.change_id);
.iter()
.map(commit_id_from_proto)
.collect();
let root_tree = TreeId::new(proto.root_tree.to_vec());
let change_id = ChangeId::new(proto.change_id.to_vec());
Commit { Commit {
parents, parents,
predecessors, predecessors,
root_tree, root_tree,
change_id, change_id,
description: proto.description.clone(), description: proto.description,
author: signature_from_proto(&proto.author), author: signature_from_proto(proto.author.unwrap_or_default()),
committer: signature_from_proto(&proto.committer), committer: signature_from_proto(proto.committer.unwrap_or_default()),
} }
} }
fn tree_to_proto(tree: &Tree) -> crate::protos::store::Tree { fn tree_to_proto(tree: &Tree) -> crate::protos::store::Tree {
let mut proto = crate::protos::store::Tree::new(); let mut proto = crate::protos::store::Tree::default();
for entry in tree.entries() { for entry in tree.entries() {
let mut proto_entry = crate::protos::store::tree::Entry::new(); proto.entries.push(crate::protos::store::tree::Entry {
proto_entry.name = entry.name().string(); name: entry.name().string(),
proto_entry.value = MessageField::some(tree_value_to_proto(entry.value())); value: Some(tree_value_to_proto(entry.value())),
proto.entries.push(proto_entry); });
} }
proto proto
} }
fn tree_from_proto(proto: &crate::protos::store::Tree) -> Tree { fn tree_from_proto(proto: crate::protos::store::Tree) -> Tree {
let mut tree = Tree::default(); let mut tree = Tree::default();
for proto_entry in &proto.entries { for proto_entry in proto.entries {
let value = tree_value_from_proto(proto_entry.value.as_ref().unwrap()); let value = tree_value_from_proto(proto_entry.value.unwrap());
tree.set(RepoPathComponent::from(proto_entry.name.as_str()), value); tree.set(RepoPathComponent::from(proto_entry.name), value);
} }
tree tree
} }
fn tree_value_to_proto(value: &TreeValue) -> crate::protos::store::TreeValue { fn tree_value_to_proto(value: &TreeValue) -> crate::protos::store::TreeValue {
let mut proto = crate::protos::store::TreeValue::new(); let mut proto = crate::protos::store::TreeValue::default();
match value { match value {
TreeValue::File { id, executable } => { TreeValue::File { id, executable } => {
let mut file = crate::protos::store::tree_value::File::new(); proto.value = Some(crate::protos::store::tree_value::Value::File(
file.id = id.to_bytes(); crate::protos::store::tree_value::File {
file.executable = *executable; id: id.to_bytes(),
proto.set_file(file); executable: *executable,
},
));
} }
TreeValue::Symlink(id) => { TreeValue::Symlink(id) => {
proto.set_symlink_id(id.to_bytes()); proto.value = Some(crate::protos::store::tree_value::Value::SymlinkId(
id.to_bytes(),
));
} }
TreeValue::GitSubmodule(_id) => { TreeValue::GitSubmodule(_id) => {
panic!("cannot store git submodules"); panic!("cannot store git submodules");
} }
TreeValue::Tree(id) => { TreeValue::Tree(id) => {
proto.set_tree_id(id.to_bytes()); proto.value = Some(crate::protos::store::tree_value::Value::TreeId(
id.to_bytes(),
));
} }
TreeValue::Conflict(id) => { TreeValue::Conflict(id) => {
proto.set_conflict_id(id.to_bytes()); proto.value = Some(crate::protos::store::tree_value::Value::ConflictId(
id.to_bytes(),
));
} }
} }
proto proto
} }
fn tree_value_from_proto(proto: &crate::protos::store::TreeValue) -> TreeValue { fn tree_value_from_proto(proto: crate::protos::store::TreeValue) -> TreeValue {
match proto.value.as_ref().unwrap() { match proto.value.unwrap() {
crate::protos::store::tree_value::Value::TreeId(id) => { crate::protos::store::tree_value::Value::TreeId(id) => TreeValue::Tree(TreeId::new(id)),
TreeValue::Tree(TreeId::new(id.clone()))
}
crate::protos::store::tree_value::Value::File(crate::protos::store::tree_value::File { crate::protos::store::tree_value::Value::File(crate::protos::store::tree_value::File {
id, id,
executable, executable,
.. ..
}) => TreeValue::File { }) => TreeValue::File {
id: FileId::new(id.clone()), id: FileId::new(id),
executable: *executable, executable,
}, },
crate::protos::store::tree_value::Value::SymlinkId(id) => { crate::protos::store::tree_value::Value::SymlinkId(id) => {
TreeValue::Symlink(SymlinkId::new(id.clone())) TreeValue::Symlink(SymlinkId::new(id))
} }
crate::protos::store::tree_value::Value::ConflictId(id) => { crate::protos::store::tree_value::Value::ConflictId(id) => {
TreeValue::Conflict(ConflictId::new(id.clone())) TreeValue::Conflict(ConflictId::new(id))
} }
} }
} }
fn signature_to_proto(signature: &Signature) -> crate::protos::store::commit::Signature { fn signature_to_proto(signature: &Signature) -> crate::protos::store::commit::Signature {
let mut proto = crate::protos::store::commit::Signature::new(); crate::protos::store::commit::Signature {
proto.name = signature.name.clone(); name: signature.name.clone(),
proto.email = signature.email.clone(); email: signature.email.clone(),
let mut timestamp_proto = crate::protos::store::commit::Timestamp::new(); timestamp: Some(crate::protos::store::commit::Timestamp {
timestamp_proto.millis_since_epoch = signature.timestamp.timestamp.0; millis_since_epoch: signature.timestamp.timestamp.0,
timestamp_proto.tz_offset = signature.timestamp.tz_offset; tz_offset: signature.timestamp.tz_offset,
proto.timestamp = MessageField::some(timestamp_proto); }),
proto }
} }
fn signature_from_proto(proto: &crate::protos::store::commit::Signature) -> Signature { fn signature_from_proto(proto: crate::protos::store::commit::Signature) -> Signature {
let timestamp = &proto.timestamp; let timestamp = proto.timestamp.unwrap_or_default();
Signature { Signature {
name: proto.name.clone(), name: proto.name,
email: proto.email.clone(), email: proto.email,
timestamp: Timestamp { timestamp: Timestamp {
timestamp: MillisSinceEpoch(timestamp.millis_since_epoch), timestamp: MillisSinceEpoch(timestamp.millis_since_epoch),
tz_offset: timestamp.tz_offset, tz_offset: timestamp.tz_offset,
@ -374,7 +375,7 @@ fn signature_from_proto(proto: &crate::protos::store::commit::Signature) -> Sign
} }
fn conflict_to_proto(conflict: &Conflict) -> crate::protos::store::Conflict { fn conflict_to_proto(conflict: &Conflict) -> crate::protos::store::Conflict {
let mut proto = crate::protos::store::Conflict::new(); let mut proto = crate::protos::store::Conflict::default();
for part in &conflict.adds { for part in &conflict.adds {
proto.adds.push(conflict_part_to_proto(part)); proto.adds.push(conflict_part_to_proto(part));
} }
@ -384,25 +385,25 @@ fn conflict_to_proto(conflict: &Conflict) -> crate::protos::store::Conflict {
proto proto
} }
fn conflict_from_proto(proto: &crate::protos::store::Conflict) -> Conflict { fn conflict_from_proto(proto: crate::protos::store::Conflict) -> Conflict {
let mut conflict = Conflict::default(); let mut conflict = Conflict::default();
for part in &proto.removes { for part in proto.removes {
conflict.removes.push(conflict_part_from_proto(part)) conflict.removes.push(conflict_part_from_proto(part))
} }
for part in &proto.adds { for part in proto.adds {
conflict.adds.push(conflict_part_from_proto(part)) conflict.adds.push(conflict_part_from_proto(part))
} }
conflict conflict
} }
fn conflict_part_from_proto(proto: &crate::protos::store::conflict::Part) -> ConflictPart { fn conflict_part_from_proto(proto: crate::protos::store::conflict::Part) -> ConflictPart {
ConflictPart { ConflictPart {
value: tree_value_from_proto(proto.content.as_ref().unwrap()), value: tree_value_from_proto(proto.content.unwrap()),
} }
} }
fn conflict_part_to_proto(part: &ConflictPart) -> crate::protos::store::conflict::Part { fn conflict_part_to_proto(part: &ConflictPart) -> crate::protos::store::conflict::Part {
let mut proto = crate::protos::store::conflict::Part::new(); crate::protos::store::conflict::Part {
proto.content = MessageField::some(tree_value_to_proto(&part.value)); content: Some(tree_value_to_proto(&part.value)),
proto }
} }

View file

@ -15,12 +15,11 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::fs; use std::fs;
use std::fs::File; use std::io::{ErrorKind, Write};
use std::io::ErrorKind;
use std::path::PathBuf; use std::path::PathBuf;
use itertools::Itertools; use itertools::Itertools;
use protobuf::{Message, MessageField}; use prost::Message;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::backend::{CommitId, MillisSinceEpoch, Timestamp}; use crate::backend::{CommitId, MillisSinceEpoch, Timestamp};
@ -31,8 +30,8 @@ use crate::op_store::{
RefTarget, View, ViewId, WorkspaceId, RefTarget, View, ViewId, WorkspaceId,
}; };
impl From<protobuf::Error> for OpStoreError { impl From<prost::DecodeError> for OpStoreError {
fn from(err: protobuf::Error) -> Self { fn from(err: prost::DecodeError) -> Self {
OpStoreError::Other(err.to_string()) OpStoreError::Other(err.to_string())
} }
} }
@ -63,17 +62,17 @@ impl ProtoOpStore {
pub fn read_view(&self, id: &ViewId) -> OpStoreResult<View> { pub fn read_view(&self, id: &ViewId) -> OpStoreResult<View> {
let path = self.view_path(id); let path = self.view_path(id);
let mut file = File::open(path).map_err(not_found_to_store_error)?; let buf = fs::read(path)?;
let proto: crate::protos::op_store::View = Message::parse_from_reader(&mut file)?; let proto = crate::protos::op_store::View::decode(&*buf)?;
Ok(view_from_proto(&proto)) Ok(view_from_proto(proto))
} }
pub fn write_view(&self, view: &View) -> OpStoreResult<ViewId> { pub fn write_view(&self, view: &View) -> OpStoreResult<ViewId> {
let temp_file = NamedTempFile::new_in(&self.path)?; let temp_file = NamedTempFile::new_in(&self.path)?;
let proto = view_to_proto(view); let proto = view_to_proto(view);
proto.write_to_writer(&mut temp_file.as_file())?; temp_file.as_file().write_all(&proto.encode_to_vec())?;
let id = ViewId::new(blake2b_hash(view).to_vec()); let id = ViewId::new(blake2b_hash(view).to_vec());
@ -83,17 +82,17 @@ impl ProtoOpStore {
pub fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation> { pub fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation> {
let path = self.operation_path(id); let path = self.operation_path(id);
let mut file = File::open(path).map_err(not_found_to_store_error)?; let buf = fs::read(path).map_err(not_found_to_store_error)?;
let proto: crate::protos::op_store::Operation = Message::parse_from_reader(&mut file)?; let proto = crate::protos::op_store::Operation::decode(&*buf)?;
Ok(operation_from_proto(&proto)) Ok(operation_from_proto(proto))
} }
pub fn write_operation(&self, operation: &Operation) -> OpStoreResult<OperationId> { pub fn write_operation(&self, operation: &Operation) -> OpStoreResult<OperationId> {
let temp_file = NamedTempFile::new_in(&self.path)?; let temp_file = NamedTempFile::new_in(&self.path)?;
let proto = operation_to_proto(operation); let proto = operation_to_proto(operation);
proto.write_to_writer(&mut temp_file.as_file())?; temp_file.as_file().write_all(&proto.encode_to_vec())?;
let id = OperationId::new(blake2b_hash(operation).to_vec()); let id = OperationId::new(blake2b_hash(operation).to_vec());
@ -111,13 +110,13 @@ fn not_found_to_store_error(err: std::io::Error) -> OpStoreError {
} }
fn timestamp_to_proto(timestamp: &Timestamp) -> crate::protos::op_store::Timestamp { fn timestamp_to_proto(timestamp: &Timestamp) -> crate::protos::op_store::Timestamp {
let mut proto = crate::protos::op_store::Timestamp::new(); crate::protos::op_store::Timestamp {
proto.millis_since_epoch = timestamp.timestamp.0; millis_since_epoch: timestamp.timestamp.0,
proto.tz_offset = timestamp.tz_offset; tz_offset: timestamp.tz_offset,
proto }
} }
fn timestamp_from_proto(proto: &crate::protos::op_store::Timestamp) -> Timestamp { fn timestamp_from_proto(proto: crate::protos::op_store::Timestamp) -> Timestamp {
Timestamp { Timestamp {
timestamp: MillisSinceEpoch(proto.millis_since_epoch), timestamp: MillisSinceEpoch(proto.millis_since_epoch),
tz_offset: proto.tz_offset, tz_offset: proto.tz_offset,
@ -127,50 +126,47 @@ fn timestamp_from_proto(proto: &crate::protos::op_store::Timestamp) -> Timestamp
fn operation_metadata_to_proto( fn operation_metadata_to_proto(
metadata: &OperationMetadata, metadata: &OperationMetadata,
) -> crate::protos::op_store::OperationMetadata { ) -> crate::protos::op_store::OperationMetadata {
let mut proto = crate::protos::op_store::OperationMetadata::new(); crate::protos::op_store::OperationMetadata {
proto.start_time = MessageField::some(timestamp_to_proto(&metadata.start_time)); start_time: Some(timestamp_to_proto(&metadata.start_time)),
proto.end_time = MessageField::some(timestamp_to_proto(&metadata.end_time)); end_time: Some(timestamp_to_proto(&metadata.end_time)),
proto.description = metadata.description.clone(); description: metadata.description.clone(),
proto.hostname = metadata.hostname.clone(); hostname: metadata.hostname.clone(),
proto.username = metadata.username.clone(); username: metadata.username.clone(),
proto.tags = metadata.tags.clone(); tags: metadata.tags.clone(),
proto }
} }
fn operation_metadata_from_proto( fn operation_metadata_from_proto(
proto: &crate::protos::op_store::OperationMetadata, proto: crate::protos::op_store::OperationMetadata,
) -> OperationMetadata { ) -> OperationMetadata {
let start_time = timestamp_from_proto(&proto.start_time); let start_time = timestamp_from_proto(proto.start_time.unwrap_or_default());
let end_time = timestamp_from_proto(&proto.end_time); let end_time = timestamp_from_proto(proto.end_time.unwrap_or_default());
let description = proto.description.to_owned();
let hostname = proto.hostname.to_owned();
let username = proto.username.to_owned();
let tags = proto.tags.clone();
OperationMetadata { OperationMetadata {
start_time, start_time,
end_time, end_time,
description, description: proto.description,
hostname, hostname: proto.hostname,
username, username: proto.username,
tags, tags: proto.tags,
} }
} }
fn operation_to_proto(operation: &Operation) -> crate::protos::op_store::Operation { fn operation_to_proto(operation: &Operation) -> crate::protos::op_store::Operation {
let mut proto = crate::protos::op_store::Operation::new(); let mut proto = crate::protos::op_store::Operation {
proto.view_id = operation.view_id.as_bytes().to_vec(); view_id: operation.view_id.as_bytes().to_vec(),
metadata: Some(operation_metadata_to_proto(&operation.metadata)),
..Default::default()
};
for parent in &operation.parents { for parent in &operation.parents {
proto.parents.push(parent.to_bytes()); proto.parents.push(parent.to_bytes());
} }
proto.metadata = MessageField::some(operation_metadata_to_proto(&operation.metadata));
proto proto
} }
fn operation_from_proto(proto: &crate::protos::op_store::Operation) -> Operation { fn operation_from_proto(proto: crate::protos::op_store::Operation) -> Operation {
let operation_id_from_proto = |parent: &Vec<u8>| OperationId::new(parent.clone()); let parents = proto.parents.into_iter().map(OperationId::new).collect();
let parents = proto.parents.iter().map(operation_id_from_proto).collect(); let view_id = ViewId::new(proto.view_id);
let view_id = ViewId::new(proto.view_id.clone()); let metadata = operation_metadata_from_proto(proto.metadata.unwrap_or_default());
let metadata = operation_metadata_from_proto(&proto.metadata);
Operation { Operation {
view_id, view_id,
parents, parents,
@ -179,7 +175,7 @@ fn operation_from_proto(proto: &crate::protos::op_store::Operation) -> Operation
} }
fn view_to_proto(view: &View) -> crate::protos::op_store::View { fn view_to_proto(view: &View) -> crate::protos::op_store::View {
let mut proto = crate::protos::op_store::View::new(); let mut proto = crate::protos::op_store::View::default();
for (workspace_id, commit_id) in &view.wc_commit_ids { for (workspace_id, commit_id) in &view.wc_commit_ids {
proto proto
.wc_commit_ids .wc_commit_ids
@ -193,32 +189,38 @@ fn view_to_proto(view: &View) -> crate::protos::op_store::View {
} }
for (name, target) in &view.branches { for (name, target) in &view.branches {
let mut branch_proto = crate::protos::op_store::Branch::new(); let mut branch_proto = crate::protos::op_store::Branch {
name: name.clone(),
..Default::default()
};
branch_proto.name = name.clone(); branch_proto.name = name.clone();
if let Some(local_target) = &target.local_target { if let Some(local_target) = &target.local_target {
branch_proto.local_target = MessageField::some(ref_target_to_proto(local_target)); branch_proto.local_target = Some(ref_target_to_proto(local_target));
} }
for (remote_name, target) in &target.remote_targets { for (remote_name, target) in &target.remote_targets {
let mut remote_branch_proto = crate::protos::op_store::RemoteBranch::new(); branch_proto
remote_branch_proto.remote_name = remote_name.clone(); .remote_branches
remote_branch_proto.target = MessageField::some(ref_target_to_proto(target)); .push(crate::protos::op_store::RemoteBranch {
branch_proto.remote_branches.push(remote_branch_proto); remote_name: remote_name.clone(),
target: Some(ref_target_to_proto(target)),
});
} }
proto.branches.push(branch_proto); proto.branches.push(branch_proto);
} }
for (name, target) in &view.tags { for (name, target) in &view.tags {
let mut tag_proto = crate::protos::op_store::Tag::new(); proto.tags.push(crate::protos::op_store::Tag {
tag_proto.name = name.clone(); name: name.clone(),
tag_proto.target = MessageField::some(ref_target_to_proto(target)); target: Some(ref_target_to_proto(target)),
proto.tags.push(tag_proto); });
} }
for (git_ref_name, target) in &view.git_refs { for (git_ref_name, target) in &view.git_refs {
let mut git_ref_proto = crate::protos::op_store::GitRef::new(); proto.git_refs.push(crate::protos::op_store::GitRef {
git_ref_proto.name = git_ref_name.clone(); name: git_ref_name.clone(),
git_ref_proto.target = MessageField::some(ref_target_to_proto(target)); target: Some(ref_target_to_proto(target)),
proto.git_refs.push(git_ref_proto); ..Default::default()
});
} }
if let Some(git_head) = &view.git_head { if let Some(git_head) = &view.git_head {
@ -228,41 +230,34 @@ fn view_to_proto(view: &View) -> crate::protos::op_store::View {
proto proto
} }
fn view_from_proto(proto: &crate::protos::op_store::View) -> View { fn view_from_proto(proto: crate::protos::op_store::View) -> View {
let mut view = View::default(); let mut view = View::default();
// For compatibility with old repos before we had support for multiple working // For compatibility with old repos before we had support for multiple working
// copies // copies
#[allow(deprecated)]
if !proto.wc_commit_id.is_empty() { if !proto.wc_commit_id.is_empty() {
view.wc_commit_ids.insert( view.wc_commit_ids
WorkspaceId::default(), .insert(WorkspaceId::default(), CommitId::new(proto.wc_commit_id));
CommitId::new(proto.wc_commit_id.clone()),
);
} }
for (workspace_id, commit_id) in &proto.wc_commit_ids { for (workspace_id, commit_id) in proto.wc_commit_ids {
view.wc_commit_ids.insert( view.wc_commit_ids
WorkspaceId::new(workspace_id.clone()), .insert(WorkspaceId::new(workspace_id), CommitId::new(commit_id));
CommitId::new(commit_id.clone()),
);
} }
for head_id_bytes in &proto.head_ids { for head_id_bytes in proto.head_ids {
view.head_ids.insert(CommitId::from_bytes(head_id_bytes)); view.head_ids.insert(CommitId::new(head_id_bytes));
} }
for head_id_bytes in &proto.public_head_ids { for head_id_bytes in proto.public_head_ids {
view.public_head_ids view.public_head_ids.insert(CommitId::new(head_id_bytes));
.insert(CommitId::from_bytes(head_id_bytes));
} }
for branch_proto in &proto.branches { for branch_proto in proto.branches {
let local_target = branch_proto let local_target = branch_proto.local_target.map(ref_target_from_proto);
.local_target
.as_ref()
.map(ref_target_from_proto);
let mut remote_targets = BTreeMap::new(); let mut remote_targets = BTreeMap::new();
for remote_branch in &branch_proto.remote_branches { for remote_branch in branch_proto.remote_branches {
remote_targets.insert( remote_targets.insert(
remote_branch.remote_name.clone(), remote_branch.remote_name,
ref_target_from_proto(&remote_branch.target), ref_target_from_proto(remote_branch.target.unwrap_or_default()),
); );
} }
@ -275,69 +270,69 @@ fn view_from_proto(proto: &crate::protos::op_store::View) -> View {
); );
} }
for tag_proto in &proto.tags { for tag_proto in proto.tags {
view.tags.insert( view.tags.insert(
tag_proto.name.clone(), tag_proto.name,
ref_target_from_proto(&tag_proto.target), ref_target_from_proto(tag_proto.target.unwrap_or_default()),
); );
} }
for git_ref in &proto.git_refs { for git_ref in proto.git_refs {
if let Some(target) = git_ref.target.as_ref() { if let Some(target) = git_ref.target {
view.git_refs view.git_refs
.insert(git_ref.name.clone(), ref_target_from_proto(target)); .insert(git_ref.name, ref_target_from_proto(target));
} else { } else {
// Legacy format // Legacy format
view.git_refs.insert( view.git_refs.insert(
git_ref.name.clone(), git_ref.name,
RefTarget::Normal(CommitId::new(git_ref.commit_id.clone())), RefTarget::Normal(CommitId::new(git_ref.commit_id)),
); );
} }
} }
if !proto.git_head.is_empty() { if !proto.git_head.is_empty() {
view.git_head = Some(CommitId::new(proto.git_head.clone())); view.git_head = Some(CommitId::new(proto.git_head));
} }
view view
} }
fn ref_target_to_proto(value: &RefTarget) -> crate::protos::op_store::RefTarget { fn ref_target_to_proto(value: &RefTarget) -> crate::protos::op_store::RefTarget {
let mut proto = crate::protos::op_store::RefTarget::new(); let mut proto = crate::protos::op_store::RefTarget::default();
match value { match value {
RefTarget::Normal(id) => { RefTarget::Normal(id) => {
proto.set_commit_id(id.to_bytes()); proto.value = Some(crate::protos::op_store::ref_target::Value::CommitId(
id.to_bytes(),
));
} }
RefTarget::Conflict { removes, adds } => { RefTarget::Conflict { removes, adds } => {
let mut ref_conflict_proto = crate::protos::op_store::RefConflict::new(); let mut ref_conflict_proto = crate::protos::op_store::RefConflict::default();
for id in removes { for id in removes {
ref_conflict_proto.removes.push(id.to_bytes()); ref_conflict_proto.removes.push(id.to_bytes());
} }
for id in adds { for id in adds {
ref_conflict_proto.adds.push(id.to_bytes()); ref_conflict_proto.adds.push(id.to_bytes());
} }
proto.set_conflict(ref_conflict_proto); proto.value = Some(crate::protos::op_store::ref_target::Value::Conflict(
ref_conflict_proto,
));
} }
} }
proto proto
} }
fn ref_target_from_proto(proto: &crate::protos::op_store::RefTarget) -> RefTarget { fn ref_target_from_proto(proto: crate::protos::op_store::RefTarget) -> RefTarget {
match proto.value.as_ref().unwrap() { match proto.value.unwrap() {
crate::protos::op_store::ref_target::Value::CommitId(id) => { crate::protos::op_store::ref_target::Value::CommitId(id) => {
RefTarget::Normal(CommitId::from_bytes(id)) RefTarget::Normal(CommitId::new(id))
} }
crate::protos::op_store::ref_target::Value::Conflict(conflict) => { crate::protos::op_store::ref_target::Value::Conflict(conflict) => {
let removes = conflict let removes = conflict
.removes .removes
.iter() .into_iter()
.map(|id_bytes| CommitId::from_bytes(id_bytes)) .map(CommitId::new)
.collect_vec();
let adds = conflict
.adds
.iter()
.map(|id_bytes| CommitId::from_bytes(id_bytes))
.collect_vec(); .collect_vec();
let adds = conflict.adds.into_iter().map(CommitId::new).collect_vec();
RefTarget::Conflict { removes, adds } RefTarget::Conflict { removes, adds }
} }
} }

View file

@ -1,15 +1,9 @@
// Copyright 2020 The Jujutsu Authors pub mod op_store {
// include!("op_store.rs");
// Licensed under the Apache License, Version 2.0 (the "License"); }
// you may not use this file except in compliance with the License. pub mod store {
// You may obtain a copy of the License at include!("store.rs");
// }
// https://www.apache.org/licenses/LICENSE-2.0 pub mod working_copy {
// include!("working_copy.rs");
// 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.
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));

132
lib/src/protos/op_store.rs Normal file
View file

@ -0,0 +1,132 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RefConflict {
#[prost(bytes = "vec", repeated, tag = "1")]
pub removes: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", repeated, tag = "2")]
pub adds: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RefTarget {
#[prost(oneof = "ref_target::Value", tags = "1, 2")]
pub value: ::core::option::Option<ref_target::Value>,
}
/// Nested message and enum types in `RefTarget`.
pub mod ref_target {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Value {
#[prost(bytes, tag = "1")]
CommitId(::prost::alloc::vec::Vec<u8>),
#[prost(message, tag = "2")]
Conflict(super::RefConflict),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RemoteBranch {
#[prost(string, tag = "1")]
pub remote_name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub target: ::core::option::Option<RefTarget>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Branch {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
/// Unset if the branch has been deleted locally.
#[prost(message, optional, tag = "2")]
pub local_target: ::core::option::Option<RefTarget>,
/// TODO: How would we support renaming remotes while having undo work? If
/// the remote name is stored in config, it's going to become a mess if the
/// remote is renamed but the configs are left unchanged. Should each remote
/// be identified (here and in configs) by a UUID?
#[prost(message, repeated, tag = "3")]
pub remote_branches: ::prost::alloc::vec::Vec<RemoteBranch>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GitRef {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
/// This field is just for historical reasons (before we had the RefTarget
/// type). New GitRefs have (only) the target field.
/// TODO: Delete support for the old format.
#[prost(bytes = "vec", tag = "2")]
pub commit_id: ::prost::alloc::vec::Vec<u8>,
#[prost(message, optional, tag = "3")]
pub target: ::core::option::Option<RefTarget>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Tag {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub target: ::core::option::Option<RefTarget>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct View {
#[prost(bytes = "vec", repeated, tag = "1")]
pub head_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", repeated, tag = "4")]
pub public_head_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[deprecated]
#[prost(bytes = "vec", tag = "2")]
pub wc_commit_id: ::prost::alloc::vec::Vec<u8>,
#[prost(map = "string, bytes", tag = "8")]
pub wc_commit_ids: ::std::collections::HashMap<
::prost::alloc::string::String,
::prost::alloc::vec::Vec<u8>,
>,
#[prost(message, repeated, tag = "5")]
pub branches: ::prost::alloc::vec::Vec<Branch>,
#[prost(message, repeated, tag = "6")]
pub tags: ::prost::alloc::vec::Vec<Tag>,
/// Only a subset of the refs. For example, does not include refs/notes/.
#[prost(message, repeated, tag = "3")]
pub git_refs: ::prost::alloc::vec::Vec<GitRef>,
#[prost(bytes = "vec", tag = "7")]
pub git_head: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Operation {
#[prost(bytes = "vec", tag = "1")]
pub view_id: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", repeated, tag = "2")]
pub parents: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(message, optional, tag = "3")]
pub metadata: ::core::option::Option<OperationMetadata>,
}
/// TODO: Share with store.proto? Do we even need the timezone here?
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Timestamp {
#[prost(int64, tag = "1")]
pub millis_since_epoch: i64,
#[prost(int32, tag = "2")]
pub tz_offset: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct OperationMetadata {
#[prost(message, optional, tag = "1")]
pub start_time: ::core::option::Option<Timestamp>,
#[prost(message, optional, tag = "2")]
pub end_time: ::core::option::Option<Timestamp>,
#[prost(string, tag = "3")]
pub description: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub hostname: ::prost::alloc::string::String,
#[prost(string, tag = "5")]
pub username: ::prost::alloc::string::String,
#[prost(map = "string, string", tag = "6")]
pub tags: ::std::collections::HashMap<
::prost::alloc::string::String,
::prost::alloc::string::String,
>,
}

108
lib/src/protos/store.rs Normal file
View file

@ -0,0 +1,108 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TreeValue {
#[prost(oneof = "tree_value::Value", tags = "2, 3, 4, 5")]
pub value: ::core::option::Option<tree_value::Value>,
}
/// Nested message and enum types in `TreeValue`.
pub mod tree_value {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct File {
#[prost(bytes = "vec", tag = "1")]
pub id: ::prost::alloc::vec::Vec<u8>,
#[prost(bool, tag = "2")]
pub executable: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Value {
#[prost(message, tag = "2")]
File(File),
#[prost(bytes, tag = "3")]
SymlinkId(::prost::alloc::vec::Vec<u8>),
#[prost(bytes, tag = "4")]
TreeId(::prost::alloc::vec::Vec<u8>),
#[prost(bytes, tag = "5")]
ConflictId(::prost::alloc::vec::Vec<u8>),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Tree {
#[prost(message, repeated, tag = "1")]
pub entries: ::prost::alloc::vec::Vec<tree::Entry>,
}
/// Nested message and enum types in `Tree`.
pub mod tree {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Entry {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(message, optional, tag = "2")]
pub value: ::core::option::Option<super::TreeValue>,
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Commit {
#[prost(bytes = "vec", repeated, tag = "1")]
pub parents: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", repeated, tag = "2")]
pub predecessors: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", tag = "3")]
pub root_tree: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "4")]
pub change_id: ::prost::alloc::vec::Vec<u8>,
#[prost(string, tag = "5")]
pub description: ::prost::alloc::string::String,
#[prost(message, optional, tag = "6")]
pub author: ::core::option::Option<commit::Signature>,
#[prost(message, optional, tag = "7")]
pub committer: ::core::option::Option<commit::Signature>,
#[deprecated]
#[prost(bool, tag = "8")]
pub is_open: bool,
#[deprecated]
#[prost(bool, tag = "9")]
pub is_pruned: bool,
}
/// Nested message and enum types in `Commit`.
pub mod commit {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Timestamp {
#[prost(int64, tag = "1")]
pub millis_since_epoch: i64,
#[prost(int32, tag = "2")]
pub tz_offset: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Signature {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub email: ::prost::alloc::string::String,
#[prost(message, optional, tag = "3")]
pub timestamp: ::core::option::Option<Timestamp>,
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Conflict {
#[prost(message, repeated, tag = "1")]
pub removes: ::prost::alloc::vec::Vec<conflict::Part>,
#[prost(message, repeated, tag = "2")]
pub adds: ::prost::alloc::vec::Vec<conflict::Part>,
}
/// Nested message and enum types in `Conflict`.
pub mod conflict {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Part {
#[prost(message, optional, tag = "1")]
pub content: ::core::option::Option<super::TreeValue>,
}
}

View file

@ -0,0 +1,85 @@
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FileState {
#[prost(int64, tag = "1")]
pub mtime_millis_since_epoch: i64,
#[prost(uint64, tag = "2")]
pub size: u64,
#[prost(enumeration = "FileType", tag = "3")]
pub file_type: i32,
/// Set only if file_type is Conflict
#[prost(bytes = "vec", tag = "4")]
pub conflict_id: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SparsePatterns {
#[prost(string, repeated, tag = "1")]
pub prefixes: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TreeState {
#[prost(bytes = "vec", tag = "1")]
pub tree_id: ::prost::alloc::vec::Vec<u8>,
#[prost(map = "string, message", tag = "2")]
pub file_states: ::std::collections::HashMap<
::prost::alloc::string::String,
FileState,
>,
#[prost(message, optional, tag = "3")]
pub sparse_patterns: ::core::option::Option<SparsePatterns>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Checkout {
/// The operation at which the working copy was updated.
#[prost(bytes = "vec", tag = "2")]
pub operation_id: ::prost::alloc::vec::Vec<u8>,
/// An identifier for this workspace. It is used for looking up the current
/// checkout in the repo view. Currently a human-readable name.
/// TODO: Is it better to make this a UUID and a have map that to a name in
/// config? That way users can rename a workspace.
#[prost(string, tag = "3")]
pub workspace_id: ::prost::alloc::string::String,
/// The checked-out commit, which can be viewed as a cache of the working-copy
/// commit ID recorded in `operation_id`'s operation. No longer used.
/// TODO: Delete this mid 2022 or so
#[prost(bytes = "vec", tag = "1")]
pub commit_id: ::prost::alloc::vec::Vec<u8>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum FileType {
Normal = 0,
Symlink = 1,
Executable = 2,
Conflict = 3,
GitSubmodule = 4,
}
impl FileType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
FileType::Normal => "Normal",
FileType::Symlink => "Symlink",
FileType::Executable => "Executable",
FileType::Conflict => "Conflict",
FileType::GitSubmodule => "GitSubmodule",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"Normal" => Some(Self::Normal),
"Symlink" => Some(Self::Symlink),
"Executable" => Some(Self::Executable),
"Conflict" => Some(Self::Conflict),
"GitSubmodule" => Some(Self::GitSubmodule),
_ => None,
}
}
}

View file

@ -39,10 +39,14 @@ impl RepoPathComponent {
impl From<&str> for RepoPathComponent { impl From<&str> for RepoPathComponent {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
RepoPathComponent::from(value.to_owned())
}
}
impl From<String> for RepoPathComponent {
fn from(value: String) -> Self {
assert!(!value.contains('/')); assert!(!value.contains('/'));
RepoPathComponent { RepoPathComponent { value }
value: value.to_owned(),
}
} }
} }

View file

@ -27,7 +27,7 @@ use std::sync::Arc;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use protobuf::{EnumOrUnknown, Message, MessageField}; use prost::Message;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use thiserror::Error; use thiserror::Error;
@ -125,13 +125,13 @@ pub struct TreeState {
own_mtime: MillisSinceEpoch, own_mtime: MillisSinceEpoch,
} }
fn file_state_from_proto(proto: &crate::protos::working_copy::FileState) -> FileState { fn file_state_from_proto(proto: crate::protos::working_copy::FileState) -> FileState {
let file_type = match proto.file_type.enum_value_or_default() { let file_type = match proto.file_type() {
crate::protos::working_copy::FileType::Normal => FileType::Normal { executable: false }, crate::protos::working_copy::FileType::Normal => FileType::Normal { executable: false },
crate::protos::working_copy::FileType::Executable => FileType::Normal { executable: true }, crate::protos::working_copy::FileType::Executable => FileType::Normal { executable: true },
crate::protos::working_copy::FileType::Symlink => FileType::Symlink, crate::protos::working_copy::FileType::Symlink => FileType::Symlink,
crate::protos::working_copy::FileType::Conflict => { crate::protos::working_copy::FileType::Conflict => {
let id = ConflictId::new(proto.conflict_id.to_vec()); let id = ConflictId::new(proto.conflict_id);
FileType::Conflict { id } FileType::Conflict { id }
} }
crate::protos::working_copy::FileType::GitSubmodule => FileType::GitSubmodule, crate::protos::working_copy::FileType::GitSubmodule => FileType::GitSubmodule,
@ -144,7 +144,7 @@ fn file_state_from_proto(proto: &crate::protos::working_copy::FileState) -> File
} }
fn file_state_to_proto(file_state: &FileState) -> crate::protos::working_copy::FileState { fn file_state_to_proto(file_state: &FileState) -> crate::protos::working_copy::FileState {
let mut proto = crate::protos::working_copy::FileState::new(); let mut proto = crate::protos::working_copy::FileState::default();
let file_type = match &file_state.file_type { let file_type = match &file_state.file_type {
FileType::Normal { executable: false } => crate::protos::working_copy::FileType::Normal, FileType::Normal { executable: false } => crate::protos::working_copy::FileType::Normal,
FileType::Normal { executable: true } => crate::protos::working_copy::FileType::Executable, FileType::Normal { executable: true } => crate::protos::working_copy::FileType::Executable,
@ -155,7 +155,7 @@ fn file_state_to_proto(file_state: &FileState) -> crate::protos::working_copy::F
} }
FileType::GitSubmodule => crate::protos::working_copy::FileType::GitSubmodule, FileType::GitSubmodule => crate::protos::working_copy::FileType::GitSubmodule,
}; };
proto.file_type = EnumOrUnknown::new(file_type); proto.file_type = file_type as i32;
proto.mtime_millis_since_epoch = file_state.mtime.0; proto.mtime_millis_since_epoch = file_state.mtime.0;
proto.size = file_state.size; proto.size = file_state.size;
proto proto
@ -167,7 +167,7 @@ fn file_states_from_proto(
let mut file_states = BTreeMap::new(); let mut file_states = BTreeMap::new();
for (path_str, proto_file_state) in &proto.file_states { for (path_str, proto_file_state) in &proto.file_states {
let path = RepoPath::from_internal_string(path_str.as_str()); let path = RepoPath::from_internal_string(path_str.as_str());
file_states.insert(path, file_state_from_proto(proto_file_state)); file_states.insert(path, file_state_from_proto(proto_file_state.clone()));
} }
file_states file_states
} }
@ -401,32 +401,38 @@ impl TreeState {
fn read(&mut self, mut file: File) { fn read(&mut self, mut file: File) {
self.update_own_mtime(); self.update_own_mtime();
let proto: crate::protos::working_copy::TreeState = let mut buf = Vec::new();
Message::parse_from_reader(&mut file).unwrap(); file.read_to_end(&mut buf).unwrap();
let proto = crate::protos::working_copy::TreeState::decode(&*buf).unwrap();
self.tree_id = TreeId::new(proto.tree_id.clone()); self.tree_id = TreeId::new(proto.tree_id.clone());
self.file_states = file_states_from_proto(&proto); self.file_states = file_states_from_proto(&proto);
self.sparse_patterns = sparse_patterns_from_proto(&proto); self.sparse_patterns = sparse_patterns_from_proto(&proto);
} }
fn save(&mut self) { fn save(&mut self) {
let mut proto = crate::protos::working_copy::TreeState::new(); let mut proto = crate::protos::working_copy::TreeState {
proto.tree_id = self.tree_id.to_bytes(); tree_id: self.tree_id.to_bytes(),
..Default::default()
};
for (file, file_state) in &self.file_states { for (file, file_state) in &self.file_states {
proto.file_states.insert( proto.file_states.insert(
file.to_internal_file_string(), file.to_internal_file_string(),
file_state_to_proto(file_state), file_state_to_proto(file_state),
); );
} }
let mut sparse_patterns = crate::protos::working_copy::SparsePatterns::new(); let mut sparse_patterns = crate::protos::working_copy::SparsePatterns::default();
for path in &self.sparse_patterns { for path in &self.sparse_patterns {
sparse_patterns sparse_patterns
.prefixes .prefixes
.push(path.to_internal_file_string()); .push(path.to_internal_file_string());
} }
proto.sparse_patterns = MessageField::some(sparse_patterns); proto.sparse_patterns = Some(sparse_patterns);
let mut temp_file = NamedTempFile::new_in(&self.state_path).unwrap(); let mut temp_file = NamedTempFile::new_in(&self.state_path).unwrap();
proto.write_to_writer(temp_file.as_file_mut()).unwrap(); temp_file
.as_file_mut()
.write_all(&proto.encode_to_vec())
.unwrap();
// update own write time while we before we rename it, so we know // update own write time while we before we rename it, so we know
// there is no unknown data in it // there is no unknown data in it
self.update_own_mtime(); self.update_own_mtime();
@ -1012,15 +1018,17 @@ impl WorkingCopy {
operation_id: OperationId, operation_id: OperationId,
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
) -> WorkingCopy { ) -> WorkingCopy {
let mut proto = crate::protos::working_copy::Checkout::new(); let proto = crate::protos::working_copy::Checkout {
proto.operation_id = operation_id.to_bytes(); operation_id: operation_id.to_bytes(),
proto.workspace_id = workspace_id.as_str().to_string(); workspace_id: workspace_id.as_str().to_string(),
..Default::default()
};
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.create_new(true) .create_new(true)
.write(true) .write(true)
.open(state_path.join("checkout")) .open(state_path.join("checkout"))
.unwrap(); .unwrap();
proto.write_to_writer(&mut file).unwrap(); file.write_all(&proto.encode_to_vec()).unwrap();
WorkingCopy { WorkingCopy {
store, store,
working_copy_path, working_copy_path,
@ -1050,7 +1058,10 @@ impl WorkingCopy {
fn write_proto(&self, proto: crate::protos::working_copy::Checkout) { fn write_proto(&self, proto: crate::protos::working_copy::Checkout) {
let mut temp_file = NamedTempFile::new_in(&self.state_path).unwrap(); let mut temp_file = NamedTempFile::new_in(&self.state_path).unwrap();
proto.write_to_writer(temp_file.as_file_mut()).unwrap(); temp_file
.as_file_mut()
.write_all(&proto.encode_to_vec())
.unwrap();
// TODO: Retry if persisting fails (it will on Windows if the file happened to // TODO: Retry if persisting fails (it will on Windows if the file happened to
// be open for read). // be open for read).
temp_file.persist(self.state_path.join("checkout")).unwrap(); temp_file.persist(self.state_path.join("checkout")).unwrap();
@ -1058,9 +1069,8 @@ impl WorkingCopy {
fn checkout_state(&self) -> &CheckoutState { fn checkout_state(&self) -> &CheckoutState {
self.checkout_state.get_or_init(|| { self.checkout_state.get_or_init(|| {
let mut file = File::open(self.state_path.join("checkout")).unwrap(); let buf = fs::read(self.state_path.join("checkout")).unwrap();
let proto: crate::protos::working_copy::Checkout = let proto = crate::protos::working_copy::Checkout::decode(&*buf).unwrap();
Message::parse_from_reader(&mut file).unwrap();
CheckoutState { CheckoutState {
operation_id: OperationId::new(proto.operation_id), operation_id: OperationId::new(proto.operation_id),
workspace_id: if proto.workspace_id.is_empty() { workspace_id: if proto.workspace_id.is_empty() {
@ -1115,10 +1125,11 @@ impl WorkingCopy {
} }
fn save(&mut self) { fn save(&mut self) {
let mut proto = crate::protos::working_copy::Checkout::new(); self.write_proto(crate::protos::working_copy::Checkout {
proto.operation_id = self.operation_id().to_bytes(); operation_id: self.operation_id().to_bytes(),
proto.workspace_id = self.workspace_id().as_str().to_string(); workspace_id: self.workspace_id().as_str().to_string(),
self.write_proto(proto); ..Default::default()
});
} }
pub fn start_mutation(&mut self) -> LockedWorkingCopy { pub fn start_mutation(&mut self) -> LockedWorkingCopy {