diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43430ad40..2dcd6be02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,20 @@ jobs: env: 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: name: Check formatting runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index fcd0c70d2..776eaca94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -545,6 +545,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "float-cmp" version = "0.9.0" @@ -564,6 +570,13 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "gen-protos" +version = "0.1.0" +dependencies = [ + "prost-build", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -830,8 +843,7 @@ dependencies = [ "once_cell", "pest", "pest_derive", - "protobuf", - "protobuf-codegen", + "prost", "rand", "regex", "serde_json", @@ -984,6 +996,12 @@ dependencies = [ "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]] name = "nom" version = "7.1.1" @@ -1180,6 +1198,16 @@ dependencies = [ "sha1", ] +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1256,6 +1284,16 @@ dependencies = [ "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]] name = "proc-macro-error" version = "1.0.4" @@ -1290,55 +1328,58 @@ dependencies = [ ] [[package]] -name = "protobuf" -version = "3.2.0" +name = "prost" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ "bytes", - "once_cell", - "protobuf-support", - "thiserror", + "prost-derive", ] [[package]] -name = "protobuf-codegen" -version = "3.2.0" +name = "prost-build" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd418ac3c91caa4032d37cb80ff0d44e2ebe637b2fb243b6234bf89cdac4901" +checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" dependencies = [ - "anyhow", - "once_cell", - "protobuf", - "protobuf-parse", - "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", + "bytes", + "heck", + "itertools", + "lazy_static", "log", - "protobuf", - "protobuf-support", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", "tempfile", - "thiserror", "which", ] [[package]] -name = "protobuf-support" -version = "3.2.0" +name = "prost-derive" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" 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]] diff --git a/Cargo.toml b/Cargo.toml index 3895a87ce..1bceb9ba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ name = "diff_bench" harness = false [workspace] -members = ["lib", "lib/testutils"] +members = ["lib", "lib/testutils", "lib/gen-protos"] [dependencies] chrono = { version = "0.4.23", default-features = false, features = ["std", "clock"] } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 1419c2b00..6266712d8 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -14,7 +14,6 @@ readme = "../README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -protobuf-codegen = "3.2.0" version_check = "0.9.4" [dependencies] @@ -32,7 +31,6 @@ maplit = "1.0.2" once_cell = "1.16.0" pest = "2.5.1" pest_derive = "2.5.1" -protobuf = { version = "3.0.1", features = ["with-bytes"] } regex = "1.7.0" serde_json = "1.0.91" tempfile = "3.3.0" @@ -42,6 +40,7 @@ uuid = { version = "1.2.2", features = ["v4"] } whoami = "1.2.3" zstd = "0.12.1" tracing = "0.1.37" +prost = "0.11.5" [dev-dependencies] assert_matches = "1.5.0" diff --git a/lib/build.rs b/lib/build.rs index e19aaa80e..c7a3e25e0 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -12,24 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -fn main() { - 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(); +fn main() -> std::io::Result<()> { 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") { println!("cargo:rustc-cfg=feature=\"map_first_last\""); } + + Ok(()) } diff --git a/lib/gen-protos/Cargo.toml b/lib/gen-protos/Cargo.toml new file mode 100644 index 000000000..209d9711a --- /dev/null +++ b/lib/gen-protos/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "gen-protos" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +prost-build = "0.11.5" diff --git a/lib/gen-protos/src/main.rs b/lib/gen-protos/src/main.rs new file mode 100644 index 000000000..593da9175 --- /dev/null +++ b/lib/gen-protos/src/main.rs @@ -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::>(), + &[protos_dir], + ) +} diff --git a/lib/src/git_backend.rs b/lib/src/git_backend.rs index 2749b0cf0..57c4d4a42 100644 --- a/lib/src/git_backend.rs +++ b/lib/src/git_backend.rs @@ -20,7 +20,7 @@ use std::sync::{Arc, Mutex}; use git2::Oid; use itertools::Itertools; -use protobuf::Message; +use prost::Message; use uuid::Uuid; use crate::backend::{ @@ -126,17 +126,18 @@ fn signature_to_git(signature: &Signature) -> git2::Signature { } fn serialize_extras(commit: &Commit) -> Vec { - let mut proto = crate::protos::store::Commit::new(); - proto.change_id = commit.change_id.to_bytes(); + let mut proto = crate::protos::store::Commit { + change_id: commit.change_id.to_bytes(), + ..Default::default() + }; for predecessor in &commit.predecessors { proto.predecessors.push(predecessor.to_bytes()); } - proto.write_to_bytes().unwrap() + proto.encode_to_vec() } fn deserialize_extras(commit: &mut Commit, bytes: &[u8]) { - let mut cursor = Cursor::new(bytes); - let proto: crate::protos::store::Commit = Message::parse_from_reader(&mut cursor).unwrap(); + let proto = crate::protos::store::Commit::decode(bytes).unwrap(); commit.change_id = ChangeId::new(proto.change_id); for predecessor in &proto.predecessors { commit.predecessors.push(CommitId::from_bytes(predecessor)); diff --git a/lib/src/local_backend.rs b/lib/src/local_backend.rs index 44f350077..9c0cf7dc1 100644 --- a/lib/src/local_backend.rs +++ b/lib/src/local_backend.rs @@ -19,7 +19,7 @@ use std::io::{ErrorKind, Read, Write}; use std::path::{Path, PathBuf}; use blake2::{Blake2b512, Digest}; -use protobuf::{Message, MessageField}; +use prost::Message; use tempfile::{NamedTempFile, PersistError}; use crate::backend::{ @@ -43,8 +43,8 @@ impl From for BackendError { } } -impl From for BackendError { - fn from(err: protobuf::Error) -> Self { +impl From for BackendError { + fn from(err: prost::DecodeError) -> Self { BackendError::Other(err.to_string()) } } @@ -183,17 +183,17 @@ impl Backend for LocalBackend { fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult { 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)?; - Ok(tree_from_proto(&proto)) + let proto = crate::protos::store::Tree::decode(&*buf)?; + Ok(tree_from_proto(proto)) } fn write_tree(&self, _path: &RepoPath, tree: &Tree) -> BackendResult { let temp_file = NamedTempFile::new_in(&self.path)?; 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()); @@ -203,17 +203,17 @@ impl Backend for LocalBackend { fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult { 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)?; - Ok(conflict_from_proto(&proto)) + let proto = crate::protos::store::Conflict::decode(&*buf)?; + Ok(conflict_from_proto(proto)) } fn write_conflict(&self, _path: &RepoPath, conflict: &Conflict) -> BackendResult { let temp_file = NamedTempFile::new_in(&self.path)?; 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()); @@ -227,17 +227,17 @@ impl Backend for LocalBackend { } 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)?; - Ok(commit_from_proto(&proto)) + let proto = crate::protos::store::Commit::decode(&*buf)?; + Ok(commit_from_proto(proto)) } fn write_commit(&self, commit: &Commit) -> BackendResult { let temp_file = NamedTempFile::new_in(&self.path)?; 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()); @@ -247,7 +247,7 @@ impl Backend for LocalBackend { } 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 { 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.change_id = commit.change_id.to_bytes(); proto.description = commit.description.clone(); - proto.author = MessageField::some(signature_to_proto(&commit.author)); - proto.committer = MessageField::some(signature_to_proto(&commit.committer)); + proto.author = Some(signature_to_proto(&commit.author)); + proto.committer = Some(signature_to_proto(&commit.committer)); proto } -fn commit_from_proto(proto: &crate::protos::store::Commit) -> Commit { - let commit_id_from_proto = |parent: &Vec| CommitId::new(parent.clone()); - let parents = proto.parents.iter().map(commit_id_from_proto).collect(); - let predecessors = proto - .predecessors - .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()); +fn commit_from_proto(proto: crate::protos::store::Commit) -> Commit { + let parents = proto.parents.into_iter().map(CommitId::new).collect(); + let predecessors = proto.predecessors.into_iter().map(CommitId::new).collect(); + let root_tree = TreeId::new(proto.root_tree); + let change_id = ChangeId::new(proto.change_id); Commit { parents, predecessors, root_tree, change_id, - description: proto.description.clone(), - author: signature_from_proto(&proto.author), - committer: signature_from_proto(&proto.committer), + description: proto.description, + author: signature_from_proto(proto.author.unwrap_or_default()), + committer: signature_from_proto(proto.committer.unwrap_or_default()), } } 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() { - let mut proto_entry = crate::protos::store::tree::Entry::new(); - proto_entry.name = entry.name().string(); - proto_entry.value = MessageField::some(tree_value_to_proto(entry.value())); - proto.entries.push(proto_entry); + proto.entries.push(crate::protos::store::tree::Entry { + name: entry.name().string(), + value: Some(tree_value_to_proto(entry.value())), + }); } 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(); - for proto_entry in &proto.entries { - let value = tree_value_from_proto(proto_entry.value.as_ref().unwrap()); - tree.set(RepoPathComponent::from(proto_entry.name.as_str()), value); + for proto_entry in proto.entries { + let value = tree_value_from_proto(proto_entry.value.unwrap()); + tree.set(RepoPathComponent::from(proto_entry.name), value); } tree } 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 { TreeValue::File { id, executable } => { - let mut file = crate::protos::store::tree_value::File::new(); - file.id = id.to_bytes(); - file.executable = *executable; - proto.set_file(file); + proto.value = Some(crate::protos::store::tree_value::Value::File( + crate::protos::store::tree_value::File { + id: id.to_bytes(), + executable: *executable, + }, + )); } 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) => { panic!("cannot store git submodules"); } 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) => { - proto.set_conflict_id(id.to_bytes()); + proto.value = Some(crate::protos::store::tree_value::Value::ConflictId( + id.to_bytes(), + )); } } proto } -fn tree_value_from_proto(proto: &crate::protos::store::TreeValue) -> TreeValue { - match proto.value.as_ref().unwrap() { - crate::protos::store::tree_value::Value::TreeId(id) => { - TreeValue::Tree(TreeId::new(id.clone())) - } +fn tree_value_from_proto(proto: crate::protos::store::TreeValue) -> TreeValue { + match proto.value.unwrap() { + crate::protos::store::tree_value::Value::TreeId(id) => TreeValue::Tree(TreeId::new(id)), crate::protos::store::tree_value::Value::File(crate::protos::store::tree_value::File { id, executable, .. }) => TreeValue::File { - id: FileId::new(id.clone()), - executable: *executable, + id: FileId::new(id), + executable, }, 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) => { - TreeValue::Conflict(ConflictId::new(id.clone())) + TreeValue::Conflict(ConflictId::new(id)) } } } fn signature_to_proto(signature: &Signature) -> crate::protos::store::commit::Signature { - let mut proto = crate::protos::store::commit::Signature::new(); - proto.name = signature.name.clone(); - proto.email = signature.email.clone(); - let mut timestamp_proto = crate::protos::store::commit::Timestamp::new(); - timestamp_proto.millis_since_epoch = signature.timestamp.timestamp.0; - timestamp_proto.tz_offset = signature.timestamp.tz_offset; - proto.timestamp = MessageField::some(timestamp_proto); - proto + crate::protos::store::commit::Signature { + name: signature.name.clone(), + email: signature.email.clone(), + timestamp: Some(crate::protos::store::commit::Timestamp { + millis_since_epoch: signature.timestamp.timestamp.0, + tz_offset: signature.timestamp.tz_offset, + }), + } } -fn signature_from_proto(proto: &crate::protos::store::commit::Signature) -> Signature { - let timestamp = &proto.timestamp; +fn signature_from_proto(proto: crate::protos::store::commit::Signature) -> Signature { + let timestamp = proto.timestamp.unwrap_or_default(); Signature { - name: proto.name.clone(), - email: proto.email.clone(), + name: proto.name, + email: proto.email, timestamp: Timestamp { timestamp: MillisSinceEpoch(timestamp.millis_since_epoch), 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 { - let mut proto = crate::protos::store::Conflict::new(); + let mut proto = crate::protos::store::Conflict::default(); for part in &conflict.adds { proto.adds.push(conflict_part_to_proto(part)); } @@ -384,25 +385,25 @@ fn conflict_to_proto(conflict: &Conflict) -> crate::protos::store::Conflict { 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(); - for part in &proto.removes { + for part in proto.removes { 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 } -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 { - 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 { - let mut proto = crate::protos::store::conflict::Part::new(); - proto.content = MessageField::some(tree_value_to_proto(&part.value)); - proto + crate::protos::store::conflict::Part { + content: Some(tree_value_to_proto(&part.value)), + } } diff --git a/lib/src/proto_op_store.rs b/lib/src/proto_op_store.rs index 6b16a83b1..05bf5b25a 100644 --- a/lib/src/proto_op_store.rs +++ b/lib/src/proto_op_store.rs @@ -15,12 +15,11 @@ use std::collections::BTreeMap; use std::fmt::Debug; use std::fs; -use std::fs::File; -use std::io::ErrorKind; +use std::io::{ErrorKind, Write}; use std::path::PathBuf; use itertools::Itertools; -use protobuf::{Message, MessageField}; +use prost::Message; use tempfile::NamedTempFile; use crate::backend::{CommitId, MillisSinceEpoch, Timestamp}; @@ -31,8 +30,8 @@ use crate::op_store::{ RefTarget, View, ViewId, WorkspaceId, }; -impl From for OpStoreError { - fn from(err: protobuf::Error) -> Self { +impl From for OpStoreError { + fn from(err: prost::DecodeError) -> Self { OpStoreError::Other(err.to_string()) } } @@ -63,17 +62,17 @@ impl ProtoOpStore { pub fn read_view(&self, id: &ViewId) -> OpStoreResult { 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)?; - Ok(view_from_proto(&proto)) + let proto = crate::protos::op_store::View::decode(&*buf)?; + Ok(view_from_proto(proto)) } pub fn write_view(&self, view: &View) -> OpStoreResult { let temp_file = NamedTempFile::new_in(&self.path)?; 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()); @@ -83,17 +82,17 @@ impl ProtoOpStore { pub fn read_operation(&self, id: &OperationId) -> OpStoreResult { 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)?; - Ok(operation_from_proto(&proto)) + let proto = crate::protos::op_store::Operation::decode(&*buf)?; + Ok(operation_from_proto(proto)) } pub fn write_operation(&self, operation: &Operation) -> OpStoreResult { let temp_file = NamedTempFile::new_in(&self.path)?; 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()); @@ -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 { - let mut proto = crate::protos::op_store::Timestamp::new(); - proto.millis_since_epoch = timestamp.timestamp.0; - proto.tz_offset = timestamp.tz_offset; - proto + 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 { +fn timestamp_from_proto(proto: crate::protos::op_store::Timestamp) -> Timestamp { Timestamp { timestamp: MillisSinceEpoch(proto.millis_since_epoch), 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( metadata: &OperationMetadata, ) -> crate::protos::op_store::OperationMetadata { - let mut proto = crate::protos::op_store::OperationMetadata::new(); - proto.start_time = MessageField::some(timestamp_to_proto(&metadata.start_time)); - proto.end_time = MessageField::some(timestamp_to_proto(&metadata.end_time)); - proto.description = metadata.description.clone(); - proto.hostname = metadata.hostname.clone(); - proto.username = metadata.username.clone(); - proto.tags = metadata.tags.clone(); - proto + 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, + proto: crate::protos::op_store::OperationMetadata, ) -> OperationMetadata { - let start_time = timestamp_from_proto(&proto.start_time); - let end_time = timestamp_from_proto(&proto.end_time); - let description = proto.description.to_owned(); - let hostname = proto.hostname.to_owned(); - let username = proto.username.to_owned(); - let tags = proto.tags.clone(); + 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, - hostname, - username, - tags, + 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::new(); - proto.view_id = operation.view_id.as_bytes().to_vec(); + 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.metadata = MessageField::some(operation_metadata_to_proto(&operation.metadata)); proto } -fn operation_from_proto(proto: &crate::protos::op_store::Operation) -> Operation { - let operation_id_from_proto = |parent: &Vec| OperationId::new(parent.clone()); - let parents = proto.parents.iter().map(operation_id_from_proto).collect(); - let view_id = ViewId::new(proto.view_id.clone()); - let metadata = operation_metadata_from_proto(&proto.metadata); +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, @@ -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 { - 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 { proto .wc_commit_ids @@ -193,32 +189,38 @@ fn view_to_proto(view: &View) -> crate::protos::op_store::View { } 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(); 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 { - let mut remote_branch_proto = crate::protos::op_store::RemoteBranch::new(); - remote_branch_proto.remote_name = remote_name.clone(); - remote_branch_proto.target = MessageField::some(ref_target_to_proto(target)); - branch_proto.remote_branches.push(remote_branch_proto); + branch_proto + .remote_branches + .push(crate::protos::op_store::RemoteBranch { + remote_name: remote_name.clone(), + target: Some(ref_target_to_proto(target)), + }); } proto.branches.push(branch_proto); } for (name, target) in &view.tags { - let mut tag_proto = crate::protos::op_store::Tag::new(); - tag_proto.name = name.clone(); - tag_proto.target = MessageField::some(ref_target_to_proto(target)); - proto.tags.push(tag_proto); + proto.tags.push(crate::protos::op_store::Tag { + name: name.clone(), + target: Some(ref_target_to_proto(target)), + }); } for (git_ref_name, target) in &view.git_refs { - let mut git_ref_proto = crate::protos::op_store::GitRef::new(); - git_ref_proto.name = git_ref_name.clone(); - git_ref_proto.target = MessageField::some(ref_target_to_proto(target)); - proto.git_refs.push(git_ref_proto); + proto.git_refs.push(crate::protos::op_store::GitRef { + name: git_ref_name.clone(), + target: Some(ref_target_to_proto(target)), + ..Default::default() + }); } if let Some(git_head) = &view.git_head { @@ -228,41 +230,34 @@ fn view_to_proto(view: &View) -> crate::protos::op_store::View { 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(); // 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.clone()), - ); + 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.clone()), - CommitId::new(commit_id.clone()), - ); + 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::from_bytes(head_id_bytes)); + 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::from_bytes(head_id_bytes)); + for head_id_bytes in proto.public_head_ids { + view.public_head_ids.insert(CommitId::new(head_id_bytes)); } - for branch_proto in &proto.branches { - let local_target = branch_proto - .local_target - .as_ref() - .map(ref_target_from_proto); + for branch_proto in proto.branches { + let local_target = branch_proto.local_target.map(ref_target_from_proto); 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_branch.remote_name.clone(), - ref_target_from_proto(&remote_branch.target), + remote_branch.remote_name, + 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( - tag_proto.name.clone(), - ref_target_from_proto(&tag_proto.target), + tag_proto.name, + ref_target_from_proto(tag_proto.target.unwrap_or_default()), ); } - for git_ref in &proto.git_refs { - if let Some(target) = git_ref.target.as_ref() { + for git_ref in proto.git_refs { + if let Some(target) = git_ref.target { view.git_refs - .insert(git_ref.name.clone(), ref_target_from_proto(target)); + .insert(git_ref.name, ref_target_from_proto(target)); } else { // Legacy format view.git_refs.insert( - git_ref.name.clone(), - RefTarget::Normal(CommitId::new(git_ref.commit_id.clone())), + git_ref.name, + RefTarget::Normal(CommitId::new(git_ref.commit_id)), ); } } 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 } 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 { 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 } => { - 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 { ref_conflict_proto.removes.push(id.to_bytes()); } for id in adds { 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 } -fn ref_target_from_proto(proto: &crate::protos::op_store::RefTarget) -> RefTarget { - match proto.value.as_ref().unwrap() { +fn ref_target_from_proto(proto: crate::protos::op_store::RefTarget) -> RefTarget { + match proto.value.unwrap() { 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) => { let removes = conflict .removes - .iter() - .map(|id_bytes| CommitId::from_bytes(id_bytes)) - .collect_vec(); - let adds = conflict - .adds - .iter() - .map(|id_bytes| CommitId::from_bytes(id_bytes)) + .into_iter() + .map(CommitId::new) .collect_vec(); + let adds = conflict.adds.into_iter().map(CommitId::new).collect_vec(); RefTarget::Conflict { removes, adds } } } diff --git a/lib/src/protos/mod.rs b/lib/src/protos/mod.rs index 2a73c7b6d..53aeaae31 100644 --- a/lib/src/protos/mod.rs +++ b/lib/src/protos/mod.rs @@ -1,15 +1,9 @@ -// Copyright 2020 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. - -include!(concat!(env!("OUT_DIR"), "/protos/mod.rs")); +pub mod op_store { + include!("op_store.rs"); +} +pub mod store { + include!("store.rs"); +} +pub mod working_copy { + include!("working_copy.rs"); +} diff --git a/lib/src/protos/op_store.rs b/lib/src/protos/op_store.rs new file mode 100644 index 000000000..3ed9b1ae6 --- /dev/null +++ b/lib/src/protos/op_store.rs @@ -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>, + #[prost(bytes = "vec", repeated, tag = "2")] + pub adds: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, +} +#[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, +} +/// 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), + #[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, +} +#[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, + /// 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, +} +#[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, + #[prost(message, optional, tag = "3")] + pub target: ::core::option::Option, +} +#[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, +} +#[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>, + #[prost(bytes = "vec", repeated, tag = "4")] + pub public_head_ids: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[deprecated] + #[prost(bytes = "vec", tag = "2")] + pub wc_commit_id: ::prost::alloc::vec::Vec, + #[prost(map = "string, bytes", tag = "8")] + pub wc_commit_ids: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::vec::Vec, + >, + #[prost(message, repeated, tag = "5")] + pub branches: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "6")] + pub tags: ::prost::alloc::vec::Vec, + /// 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, + #[prost(bytes = "vec", tag = "7")] + pub git_head: ::prost::alloc::vec::Vec, +} +#[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, + #[prost(bytes = "vec", repeated, tag = "2")] + pub parents: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(message, optional, tag = "3")] + pub metadata: ::core::option::Option, +} +/// 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, + #[prost(message, optional, tag = "2")] + pub end_time: ::core::option::Option, + #[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, + >, +} diff --git a/lib/src/protos/store.rs b/lib/src/protos/store.rs new file mode 100644 index 000000000..04898b86e --- /dev/null +++ b/lib/src/protos/store.rs @@ -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, +} +/// 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, + #[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), + #[prost(bytes, tag = "4")] + TreeId(::prost::alloc::vec::Vec), + #[prost(bytes, tag = "5")] + ConflictId(::prost::alloc::vec::Vec), + } +} +#[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, +} +/// 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, + } +} +#[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>, + #[prost(bytes = "vec", repeated, tag = "2")] + pub predecessors: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(bytes = "vec", tag = "3")] + pub root_tree: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "4")] + pub change_id: ::prost::alloc::vec::Vec, + #[prost(string, tag = "5")] + pub description: ::prost::alloc::string::String, + #[prost(message, optional, tag = "6")] + pub author: ::core::option::Option, + #[prost(message, optional, tag = "7")] + pub committer: ::core::option::Option, + #[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, + } +} +#[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, + #[prost(message, repeated, tag = "2")] + pub adds: ::prost::alloc::vec::Vec, +} +/// 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, + } +} diff --git a/lib/src/protos/working_copy.rs b/lib/src/protos/working_copy.rs new file mode 100644 index 000000000..b0d5535c5 --- /dev/null +++ b/lib/src/protos/working_copy.rs @@ -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, +} +#[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, + #[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, +} +#[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, + /// 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, +} +#[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 { + match value { + "Normal" => Some(Self::Normal), + "Symlink" => Some(Self::Symlink), + "Executable" => Some(Self::Executable), + "Conflict" => Some(Self::Conflict), + "GitSubmodule" => Some(Self::GitSubmodule), + _ => None, + } + } +} diff --git a/lib/src/repo_path.rs b/lib/src/repo_path.rs index d32bd822e..1b92a63a6 100644 --- a/lib/src/repo_path.rs +++ b/lib/src/repo_path.rs @@ -39,10 +39,14 @@ impl RepoPathComponent { impl From<&str> for RepoPathComponent { fn from(value: &str) -> Self { + RepoPathComponent::from(value.to_owned()) + } +} + +impl From for RepoPathComponent { + fn from(value: String) -> Self { assert!(!value.contains('/')); - RepoPathComponent { - value: value.to_owned(), - } + RepoPathComponent { value } } } diff --git a/lib/src/working_copy.rs b/lib/src/working_copy.rs index 4e57e2462..5d273e424 100644 --- a/lib/src/working_copy.rs +++ b/lib/src/working_copy.rs @@ -27,7 +27,7 @@ use std::sync::Arc; use std::time::UNIX_EPOCH; use once_cell::unsync::OnceCell; -use protobuf::{EnumOrUnknown, Message, MessageField}; +use prost::Message; use tempfile::NamedTempFile; use thiserror::Error; @@ -125,13 +125,13 @@ pub struct TreeState { own_mtime: MillisSinceEpoch, } -fn file_state_from_proto(proto: &crate::protos::working_copy::FileState) -> FileState { - let file_type = match proto.file_type.enum_value_or_default() { +fn file_state_from_proto(proto: crate::protos::working_copy::FileState) -> FileState { + let file_type = match proto.file_type() { 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::Symlink => FileType::Symlink, 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 } } 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 { - 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 { FileType::Normal { executable: false } => crate::protos::working_copy::FileType::Normal, 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, }; - proto.file_type = EnumOrUnknown::new(file_type); + proto.file_type = file_type as i32; proto.mtime_millis_since_epoch = file_state.mtime.0; proto.size = file_state.size; proto @@ -167,7 +167,7 @@ fn file_states_from_proto( let mut file_states = BTreeMap::new(); for (path_str, proto_file_state) in &proto.file_states { 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 } @@ -401,32 +401,38 @@ impl TreeState { fn read(&mut self, mut file: File) { self.update_own_mtime(); - let proto: crate::protos::working_copy::TreeState = - Message::parse_from_reader(&mut file).unwrap(); + let mut buf = Vec::new(); + 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.file_states = file_states_from_proto(&proto); self.sparse_patterns = sparse_patterns_from_proto(&proto); } fn save(&mut self) { - let mut proto = crate::protos::working_copy::TreeState::new(); - proto.tree_id = self.tree_id.to_bytes(); + let mut proto = crate::protos::working_copy::TreeState { + tree_id: self.tree_id.to_bytes(), + ..Default::default() + }; for (file, file_state) in &self.file_states { proto.file_states.insert( file.to_internal_file_string(), 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 { sparse_patterns .prefixes .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(); - 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 // there is no unknown data in it self.update_own_mtime(); @@ -1012,15 +1018,17 @@ impl WorkingCopy { operation_id: OperationId, workspace_id: WorkspaceId, ) -> WorkingCopy { - let mut proto = crate::protos::working_copy::Checkout::new(); - proto.operation_id = operation_id.to_bytes(); - proto.workspace_id = workspace_id.as_str().to_string(); + let proto = crate::protos::working_copy::Checkout { + operation_id: operation_id.to_bytes(), + workspace_id: workspace_id.as_str().to_string(), + ..Default::default() + }; let mut file = OpenOptions::new() .create_new(true) .write(true) .open(state_path.join("checkout")) .unwrap(); - proto.write_to_writer(&mut file).unwrap(); + file.write_all(&proto.encode_to_vec()).unwrap(); WorkingCopy { store, working_copy_path, @@ -1050,7 +1058,10 @@ impl WorkingCopy { fn write_proto(&self, proto: crate::protos::working_copy::Checkout) { 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 // be open for read). temp_file.persist(self.state_path.join("checkout")).unwrap(); @@ -1058,9 +1069,8 @@ impl WorkingCopy { fn checkout_state(&self) -> &CheckoutState { self.checkout_state.get_or_init(|| { - let mut file = File::open(self.state_path.join("checkout")).unwrap(); - let proto: crate::protos::working_copy::Checkout = - Message::parse_from_reader(&mut file).unwrap(); + let buf = fs::read(self.state_path.join("checkout")).unwrap(); + let proto = crate::protos::working_copy::Checkout::decode(&*buf).unwrap(); CheckoutState { operation_id: OperationId::new(proto.operation_id), workspace_id: if proto.workspace_id.is_empty() { @@ -1115,10 +1125,11 @@ impl WorkingCopy { } fn save(&mut self) { - let mut proto = crate::protos::working_copy::Checkout::new(); - proto.operation_id = self.operation_id().to_bytes(); - proto.workspace_id = self.workspace_id().as_str().to_string(); - self.write_proto(proto); + self.write_proto(crate::protos::working_copy::Checkout { + operation_id: self.operation_id().to_bytes(), + workspace_id: self.workspace_id().as_str().to_string(), + ..Default::default() + }); } pub fn start_mutation(&mut self) -> LockedWorkingCopy {