git: Use RefString types for git::refs
Lean on rust's type system to improve safety. Signed-off-by: Slack Coder <slackcoder@server.ky>
This commit is contained in:
parent
48a5c75ae3
commit
f9ad3ac155
|
|
@ -815,6 +815,7 @@ dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
"git-ref-format",
|
||||||
"lexopt",
|
"lexopt",
|
||||||
"log",
|
"log",
|
||||||
"nakamoto-net",
|
"nakamoto-net",
|
||||||
|
|
@ -839,6 +840,7 @@ name = "radicle-tools"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"git-ref-format",
|
||||||
"radicle",
|
"radicle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,20 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1" }
|
anyhow = { version = "1" }
|
||||||
byteorder = { version = "1" }
|
|
||||||
bloomy = { version = "1.2" }
|
bloomy = { version = "1.2" }
|
||||||
|
byteorder = { version = "1" }
|
||||||
chrono = { version = "0.4.0" }
|
chrono = { version = "0.4.0" }
|
||||||
colored = { version = "1.9.0" }
|
colored = { version = "1.9.0" }
|
||||||
crossbeam-channel = { version = "0.5.6" }
|
crossbeam-channel = { version = "0.5.6" }
|
||||||
fastrand = { version = "1.8.0" }
|
fastrand = { version = "1.8.0" }
|
||||||
|
git-ref-format = { version = "0", features = ["serde", "macro"] }
|
||||||
lexopt = { version = "0.2.1" }
|
lexopt = { version = "0.2.1" }
|
||||||
log = { version = "0.4.17", features = ["std"] }
|
log = { version = "0.4.17", features = ["std"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = { version = "1", features = ["preserve_order"] }
|
|
||||||
nonempty = { version = "0.8.0", features = ["serialize"] }
|
|
||||||
nakamoto-net = { version = "0.3.0" }
|
nakamoto-net = { version = "0.3.0" }
|
||||||
nakamoto-net-poll = { version = "0.3.0" }
|
nakamoto-net-poll = { version = "0.3.0" }
|
||||||
|
nonempty = { version = "0.8.0", features = ["serialize"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = { version = "1", features = ["preserve_order"] }
|
||||||
tempfile = { version = "3.3.0" }
|
tempfile = { version = "3.3.0" }
|
||||||
thiserror = { version = "1" }
|
thiserror = { version = "1" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use crate::test::simulator;
|
||||||
use crate::test::simulator::{Peer as _, Simulation};
|
use crate::test::simulator::{Peer as _, Simulation};
|
||||||
use crate::test::storage::MockStorage;
|
use crate::test::storage::MockStorage;
|
||||||
use crate::LocalTime;
|
use crate::LocalTime;
|
||||||
use crate::{client, identity, rad, service, storage, test};
|
use crate::{client, git, identity, rad, service, test};
|
||||||
|
|
||||||
// NOTE
|
// NOTE
|
||||||
//
|
//
|
||||||
|
|
@ -397,7 +397,7 @@ fn test_push_and_pull() {
|
||||||
&repo,
|
&repo,
|
||||||
"alice",
|
"alice",
|
||||||
"alice's repo",
|
"alice's repo",
|
||||||
storage::BranchName::from("master"),
|
git::refname!("master"),
|
||||||
alice.signer(),
|
alice.signer(),
|
||||||
alice.storage(),
|
alice.storage(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1" }
|
anyhow = { version = "1" }
|
||||||
|
git-ref-format = { version = "0", features = ["serde", "macro"] }
|
||||||
|
|
||||||
[dependencies.radicle]
|
[dependencies.radicle]
|
||||||
version = "0"
|
version = "0"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use radicle::git;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let cwd = Path::new(".").canonicalize()?;
|
let cwd = Path::new(".").canonicalize()?;
|
||||||
let name = cwd.file_name().unwrap().to_string_lossy().to_string();
|
let name = cwd.file_name().unwrap().to_string_lossy().to_string();
|
||||||
|
|
@ -9,7 +11,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
&repo,
|
&repo,
|
||||||
&name,
|
&name,
|
||||||
"",
|
"",
|
||||||
radicle::git::BranchName::from("master"),
|
git::refname!("master"),
|
||||||
&profile.signer,
|
&profile.signer,
|
||||||
&profile.storage,
|
&profile.storage,
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub use ext::Error;
|
||||||
pub use ext::Oid;
|
pub use ext::Oid;
|
||||||
pub use git2 as raw;
|
pub use git2 as raw;
|
||||||
pub use git_ref_format as fmt;
|
pub use git_ref_format as fmt;
|
||||||
pub use git_ref_format::{refname, RefStr, RefString};
|
pub use git_ref_format::{refname, Component, Qualified, RefStr, RefString};
|
||||||
pub use git_url as url;
|
pub use git_url as url;
|
||||||
pub use git_url::Url;
|
pub use git_url::Url;
|
||||||
pub use radicle_git_ext as ext;
|
pub use radicle_git_ext as ext;
|
||||||
|
|
@ -41,6 +41,13 @@ pub enum ListRefsError {
|
||||||
InvalidRef(#[from] RefError),
|
InvalidRef(#[from] RefError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&RemoteId> for RefString {
|
||||||
|
fn from(id: &RemoteId) -> Self {
|
||||||
|
// PublicKey strings contain only legal characters.
|
||||||
|
RefString::try_from(id.to_string()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod refs {
|
pub mod refs {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
@ -50,31 +57,36 @@ pub mod refs {
|
||||||
pub mod storage {
|
pub mod storage {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn branch(remote: &RemoteId, branch: &str) -> String {
|
pub fn branch(remote: &RemoteId, branch: &RefStr) -> RefString {
|
||||||
format!("refs/remotes/{remote}/heads/{branch}")
|
refname!("refs/remotes")
|
||||||
|
.and::<RefString>(remote.into())
|
||||||
|
.and(refname!("heads"))
|
||||||
|
.and(branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the branch used to track project information.
|
/// Get the branch used to track project information.
|
||||||
pub fn id(remote: &RemoteId) -> String {
|
pub fn id(remote: &RemoteId) -> RefString {
|
||||||
branch(remote, &IDENTITY_BRANCH)
|
branch(remote, &IDENTITY_BRANCH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod workdir {
|
pub mod workdir {
|
||||||
pub fn branch(branch: &str) -> String {
|
use super::*;
|
||||||
format!("refs/heads/{branch}")
|
|
||||||
|
pub fn branch(branch: &RefStr) -> RefString {
|
||||||
|
refname!("refs/heads").join(branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn note(name: &str) -> String {
|
pub fn note(name: &RefStr) -> RefString {
|
||||||
format!("refs/notes/{name}")
|
refname!("refs/notes").join(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_branch(remote: &str, branch: &str) -> String {
|
pub fn remote_branch(remote: &RefStr, branch: &RefStr) -> RefString {
|
||||||
format!("refs/remotes/{remote}/{branch}")
|
refname!("refs/remotes").and(remote).and(branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tag(name: &str) -> String {
|
pub fn tag(name: &RefStr) -> RefString {
|
||||||
format!("refs/tags/{name}")
|
refname!("refs/tags").join(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl From<Delegate> for PublicKey {
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String, // TODO: Make optional.
|
pub description: String, // TODO: Make optional.
|
||||||
pub default_branch: String, // TODO: Make optional.
|
pub default_branch: git::RefString, // TODO: Make optional.
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::crypto::{Signer, Verified};
|
use crate::crypto::{Signer, Verified};
|
||||||
|
|
@ -13,7 +14,7 @@ use crate::storage::refs::SignedRefs;
|
||||||
use crate::storage::{BranchName, ReadRepository as _, RemoteId, WriteRepository as _};
|
use crate::storage::{BranchName, ReadRepository as _, RemoteId, WriteRepository as _};
|
||||||
use crate::{identity, storage};
|
use crate::{identity, storage};
|
||||||
|
|
||||||
pub const REMOTE_NAME: &str = "rad";
|
pub static REMOTE_NAME: Lazy<git::RefString> = Lazy::new(|| git::refname!("rad"));
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum InitError {
|
pub enum InitError {
|
||||||
|
|
@ -63,7 +64,7 @@ pub fn init<G: Signer, S: storage::WriteStorage>(
|
||||||
|
|
||||||
git::set_upstream(
|
git::set_upstream(
|
||||||
repo,
|
repo,
|
||||||
REMOTE_NAME,
|
&REMOTE_NAME,
|
||||||
&default_branch,
|
&default_branch,
|
||||||
&git::refs::storage::branch(pk, &default_branch),
|
&git::refs::storage::branch(pk, &default_branch),
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -71,7 +72,7 @@ pub fn init<G: Signer, S: storage::WriteStorage>(
|
||||||
// TODO: Note that you'll likely want to use `RemoteCallbacks` and set
|
// TODO: Note that you'll likely want to use `RemoteCallbacks` and set
|
||||||
// `push_update_reference` to test whether all the references were pushed
|
// `push_update_reference` to test whether all the references were pushed
|
||||||
// successfully.
|
// successfully.
|
||||||
git::configure_remote(repo, REMOTE_NAME, pk, &url)?.push::<&str>(
|
git::configure_remote(repo, &REMOTE_NAME, pk, &url)?.push::<&str>(
|
||||||
&[&format!(
|
&[&format!(
|
||||||
"{}:{}",
|
"{}:{}",
|
||||||
&git::refs::workdir::branch(&default_branch),
|
&git::refs::workdir::branch(&default_branch),
|
||||||
|
|
@ -276,24 +277,24 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
|
||||||
opts.no_reinit(true).description(&project.description);
|
opts.no_reinit(true).description(&project.description);
|
||||||
|
|
||||||
let repo = git2::Repository::init_opts(path, &opts)?;
|
let repo = git2::Repository::init_opts(path, &opts)?;
|
||||||
let default_branch = project.default_branch.as_str();
|
|
||||||
let url = storage.url(&proj);
|
let url = storage.url(&proj);
|
||||||
|
|
||||||
// Configure and fetch all refs from remote.
|
// Configure and fetch all refs from remote.
|
||||||
git::configure_remote(&repo, REMOTE_NAME, remote, &url)?.fetch::<&str>(&[], None, None)?;
|
git::configure_remote(&repo, &REMOTE_NAME, remote, &url)?.fetch::<&str>(&[], None, None)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Setup default branch.
|
// Setup default branch.
|
||||||
let remote_head_ref = git::refs::workdir::remote_branch(REMOTE_NAME, default_branch);
|
let remote_head_ref =
|
||||||
|
git::refs::workdir::remote_branch(&REMOTE_NAME, &project.default_branch);
|
||||||
let remote_head_commit = repo.find_reference(&remote_head_ref)?.peel_to_commit()?;
|
let remote_head_commit = repo.find_reference(&remote_head_ref)?.peel_to_commit()?;
|
||||||
let _ = repo.branch(default_branch, &remote_head_commit, true)?;
|
let _ = repo.branch(&project.default_branch, &remote_head_commit, true)?;
|
||||||
|
|
||||||
// Setup remote tracking for default branch.
|
// Setup remote tracking for default branch.
|
||||||
git::set_upstream(
|
git::set_upstream(
|
||||||
&repo,
|
&repo,
|
||||||
REMOTE_NAME,
|
&REMOTE_NAME,
|
||||||
default_branch,
|
&project.default_branch,
|
||||||
&git::refs::storage::branch(remote, default_branch),
|
&git::refs::storage::branch(remote, &project.default_branch),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,7 +303,7 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
|
||||||
|
|
||||||
/// Get the radicle ("rad") remote of a repository, and return the associated project id.
|
/// Get the radicle ("rad") remote of a repository, and return the associated project id.
|
||||||
pub fn remote(repo: &git2::Repository) -> Result<(git2::Remote<'_>, Id), git2::Error> {
|
pub fn remote(repo: &git2::Repository) -> Result<(git2::Remote<'_>, Id), git2::Error> {
|
||||||
let remote = repo.find_remote(REMOTE_NAME)?;
|
let remote = repo.find_remote(&REMOTE_NAME)?;
|
||||||
let url = remote.url_bytes();
|
let url = remote.url_bytes();
|
||||||
let url = git::Url::from_bytes(url).unwrap();
|
let url = git::Url::from_bytes(url).unwrap();
|
||||||
let path = url.path.to_string();
|
let path = url.path.to_string();
|
||||||
|
|
@ -335,7 +336,7 @@ mod tests {
|
||||||
&repo,
|
&repo,
|
||||||
"acme",
|
"acme",
|
||||||
"Acme's repo",
|
"Acme's repo",
|
||||||
BranchName::from("master"),
|
git::refname!("master"),
|
||||||
&signer,
|
&signer,
|
||||||
&storage,
|
&storage,
|
||||||
)
|
)
|
||||||
|
|
@ -353,7 +354,7 @@ mod tests {
|
||||||
assert_eq!(remotes[&public_key].refs, refs);
|
assert_eq!(remotes[&public_key].refs, refs);
|
||||||
assert_eq!(project.name, "acme");
|
assert_eq!(project.name, "acme");
|
||||||
assert_eq!(project.description, "Acme's repo");
|
assert_eq!(project.description, "Acme's repo");
|
||||||
assert_eq!(project.default_branch, BranchName::from("master"));
|
assert_eq!(project.default_branch, git::refname!("master"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
project.delegates.first(),
|
project.delegates.first(),
|
||||||
&Delegate {
|
&Delegate {
|
||||||
|
|
@ -378,7 +379,7 @@ mod tests {
|
||||||
&original,
|
&original,
|
||||||
"acme",
|
"acme",
|
||||||
"Acme's repo",
|
"Acme's repo",
|
||||||
BranchName::from("master"),
|
git::refname!("master"),
|
||||||
&alice,
|
&alice,
|
||||||
&storage,
|
&storage,
|
||||||
)
|
)
|
||||||
|
|
@ -408,7 +409,7 @@ mod tests {
|
||||||
&original,
|
&original,
|
||||||
"acme",
|
"acme",
|
||||||
"Acme's repo",
|
"Acme's repo",
|
||||||
BranchName::from("master"),
|
git::refname!("master"),
|
||||||
&signer,
|
&signer,
|
||||||
&storage,
|
&storage,
|
||||||
)
|
)
|
||||||
|
|
@ -430,14 +431,14 @@ mod tests {
|
||||||
.to_vec()
|
.to_vec()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
copy.find_remote(REMOTE_NAME)
|
copy.find_remote(&REMOTE_NAME)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.refspecs()
|
.refspecs()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r| r.bytes().to_vec())
|
.map(|r| r.bytes().to_vec())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
original
|
original
|
||||||
.find_remote(REMOTE_NAME)
|
.find_remote(&REMOTE_NAME)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.refspecs()
|
.refspecs()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use crate::storage::refs::Refs;
|
||||||
|
|
||||||
use self::refs::SignedRefs;
|
use self::refs::SignedRefs;
|
||||||
|
|
||||||
pub type BranchName = String;
|
pub type BranchName = git::RefString;
|
||||||
pub type Inventory = Vec<Id>;
|
pub type Inventory = Vec<Id>;
|
||||||
|
|
||||||
/// Storage error.
|
/// Storage error.
|
||||||
|
|
|
||||||
|
|
@ -810,7 +810,7 @@ mod tests {
|
||||||
&source,
|
&source,
|
||||||
"radicle",
|
"radicle",
|
||||||
"radicle",
|
"radicle",
|
||||||
BranchName::from("master"),
|
git::refname!("master"),
|
||||||
signer,
|
signer,
|
||||||
&storage,
|
&storage,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ impl Arbitrary for Doc<Unverified> {
|
||||||
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
|
||||||
let name = String::arbitrary(g);
|
let name = String::arbitrary(g);
|
||||||
let description = String::arbitrary(g);
|
let description = String::arbitrary(g);
|
||||||
let default_branch = String::arbitrary(g);
|
let default_branch = git::RefString::try_from(String::arbitrary(g)).unwrap();
|
||||||
let delegate = Delegate::arbitrary(g);
|
let delegate = Delegate::arbitrary(g);
|
||||||
|
|
||||||
Self::initial(name, description, default_branch, delegate)
|
Self::initial(name, description, default_branch, delegate)
|
||||||
|
|
@ -101,9 +101,11 @@ impl Arbitrary for Doc<Verified> {
|
||||||
let description = iter::repeat_with(|| rng.alphanumeric())
|
let description = iter::repeat_with(|| rng.alphanumeric())
|
||||||
.take(rng.usize(0..32))
|
.take(rng.usize(0..32))
|
||||||
.collect();
|
.collect();
|
||||||
let default_branch = iter::repeat_with(|| rng.alphanumeric())
|
let default_branch: git::RefString = iter::repeat_with(|| rng.alphanumeric())
|
||||||
.take(rng.usize(1..16))
|
.take(rng.usize(1..16))
|
||||||
.collect();
|
.collect::<String>()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
let delegates: NonEmpty<_> = iter::repeat_with(|| Delegate {
|
let delegates: NonEmpty<_> = iter::repeat_with(|| Delegate {
|
||||||
name: iter::repeat_with(|| rng.alphanumeric())
|
name: iter::repeat_with(|| rng.alphanumeric())
|
||||||
.take(rng.usize(1..16))
|
.take(rng.usize(1..16))
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::identity::Id;
|
||||||
use crate::rad;
|
use crate::rad;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::storage::refs::SignedRefs;
|
use crate::storage::refs::SignedRefs;
|
||||||
use crate::storage::{BranchName, WriteStorage};
|
use crate::storage::WriteStorage;
|
||||||
|
|
||||||
/// Create a new storage with a project.
|
/// Create a new storage with a project.
|
||||||
pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage, rad::InitError> {
|
pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage, rad::InitError> {
|
||||||
|
|
@ -23,7 +23,7 @@ pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage,
|
||||||
&repo,
|
&repo,
|
||||||
name,
|
name,
|
||||||
desc,
|
desc,
|
||||||
BranchName::from("master"),
|
git::refname!("master"),
|
||||||
&signer,
|
&signer,
|
||||||
&storage,
|
&storage,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -43,7 +43,7 @@ pub fn project<P: AsRef<Path>, S: WriteStorage, G: Signer>(
|
||||||
&repo,
|
&repo,
|
||||||
"acme",
|
"acme",
|
||||||
"Acme's repository",
|
"Acme's repository",
|
||||||
BranchName::from("master"),
|
git::refname!("master"),
|
||||||
signer,
|
signer,
|
||||||
storage,
|
storage,
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue