mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-24 22:00:38 +08:00
meta: use long crate names
To help with clarity and potential publishing of the crates, the full `radicle-` prefix is used for the crate names. Note that it wasn't chosen got `git-ref-format` and `git-trailers` since they are truly general libraries (bar the feature flag for link specific reference names). Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
+4
-4
@@ -1,13 +1,13 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"git-ext",
|
||||
"git-ref-format",
|
||||
"git-trailers",
|
||||
"git-types",
|
||||
"link-git",
|
||||
"macros",
|
||||
"radicle-git-ext",
|
||||
"radicle-git-types",
|
||||
"radicle-macros",
|
||||
"radicle-std-ext",
|
||||
# TODO: port gitd-lib over
|
||||
# "cli/gitd-lib",
|
||||
"std-ext",
|
||||
"test",
|
||||
]
|
||||
|
||||
@@ -38,4 +38,4 @@ features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.radicle-std-ext]
|
||||
path = "../std-ext"
|
||||
path = "../radicle-std-ext"
|
||||
@@ -27,13 +27,13 @@ version = "0.13"
|
||||
features = ["std", "derive"]
|
||||
|
||||
[dependencies.radicle-git-ext]
|
||||
path = "../git-ext"
|
||||
path = "../radicle-git-ext"
|
||||
|
||||
[dependencies.radicle-macros]
|
||||
path = "../macros"
|
||||
path = "../radicle-macros"
|
||||
|
||||
[dependencies.radicle-std-ext]
|
||||
path = "../std-ext"
|
||||
path = "../radicle-std-ext"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
@@ -17,4 +17,4 @@ quote = "1"
|
||||
syn = "1"
|
||||
|
||||
[dependencies.radicle-git-ext]
|
||||
path = "../git-ext"
|
||||
path = "../radicle-git-ext"
|
||||
+1
-1
@@ -12,7 +12,7 @@ test = true
|
||||
doc = false
|
||||
|
||||
[dev-dependencies.git-ext-test]
|
||||
path = "../git-ext/t"
|
||||
path = "../radicle-git-ext/t"
|
||||
features = ["test"]
|
||||
|
||||
[dev-dependencies.git-ref-format-test]
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
[package]
|
||||
name = "it-helpers"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
publish = false
|
||||
|
||||
description = "Integration test helpers"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
test = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
futures = "0.3"
|
||||
once_cell = "1.10"
|
||||
tempfile = "3.3"
|
||||
tokio = "1.13"
|
||||
tracing = "0.1"
|
||||
either = "1.6"
|
||||
|
||||
[dependencies.git2]
|
||||
version = "0.13.24"
|
||||
default-features = false
|
||||
features = ["vendored-libgit2"]
|
||||
|
||||
#
|
||||
# workspace dependencies
|
||||
#
|
||||
|
||||
[dependencies.git-ref-format]
|
||||
path = "../../git-ref-format"
|
||||
|
||||
[dependencies.librad]
|
||||
path = "../../librad"
|
||||
|
||||
[dependencies.link-async]
|
||||
path = "../../link-async"
|
||||
|
||||
[dependencies.lnk-clib]
|
||||
path = "../../cli/lnk-clib"
|
||||
|
||||
[dependencies.radicle-git-ext]
|
||||
path = "../../git-ext"
|
||||
|
||||
[dependencies.test-helpers]
|
||||
path = "../test-helpers"
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
|
||||
//
|
||||
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
|
||||
// Linking Exception. For full terms see the included LICENSE file.
|
||||
|
||||
use git_ref_format::Qualified;
|
||||
|
||||
#[tracing::instrument(skip(repo))]
|
||||
pub fn create_commit(
|
||||
repo: &git2::Repository,
|
||||
on_branch: Qualified,
|
||||
) -> Result<git2::Oid, git2::Error> {
|
||||
let empty_tree = {
|
||||
let mut index = repo.index()?;
|
||||
let oid = index.write_tree()?;
|
||||
repo.find_tree(oid).unwrap()
|
||||
};
|
||||
let author = git2::Signature::now("The Animal", "animal@muppets.com").unwrap();
|
||||
repo.commit(
|
||||
Some(on_branch.as_str()),
|
||||
&author,
|
||||
&author,
|
||||
"Initial commit",
|
||||
&empty_tree,
|
||||
&[],
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
pub mod fixed;
|
||||
pub mod git;
|
||||
pub mod layout;
|
||||
pub mod ssh;
|
||||
pub mod testnet;
|
||||
pub mod tmp;
|
||||
pub mod working_copy;
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright © 2021 The Radicle Link Contributors
|
||||
//
|
||||
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
|
||||
// Linking Exception. For full terms see the included LICENSE file.
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use lnk_clib::keys::ssh::SshAuthSock;
|
||||
use test_helpers::tempdir::WithTmpDir;
|
||||
|
||||
pub type TmpSshSock = WithTmpDir<SshAuthSock>;
|
||||
|
||||
pub fn ssh_auth_sock() -> TmpSshSock {
|
||||
WithTmpDir::new(|path| -> anyhow::Result<SshAuthSock> {
|
||||
let sock = path.join("tmp.sock");
|
||||
Ok(SshAuthSock::Uds(sock))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Run a computation with a forked `ssh-agent` on a temporary file handle.
|
||||
///
|
||||
/// Once the computation is finished, the `ssh-agent` is killed by getting its
|
||||
/// PID and running the equivalent of `SSH_AGENT_PID=<pid> ssh-agent -k`.
|
||||
/// This is a best effort of resource cleanup, but has no guarantees if the
|
||||
/// parsing of the PID or the killin of the agent fail.
|
||||
pub fn with_ssh_agent<F, T>(callback: F) -> anyhow::Result<T>
|
||||
where
|
||||
F: FnOnce(SshAuthSock) -> anyhow::Result<T>,
|
||||
{
|
||||
let sock = ssh_auth_sock();
|
||||
let path = match &*sock {
|
||||
SshAuthSock::Uds(path) => path,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let agent = Command::new("ssh-agent").arg("-a").arg(path).output()?;
|
||||
anyhow::ensure!(agent.status.success(), agent.status);
|
||||
let pid = agent_pid(&agent.stdout)?;
|
||||
let res = callback((*sock).clone());
|
||||
kill_agent_pid(pid)?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Kill the ssh-agent running on the given PID.
|
||||
fn kill_agent_pid(pid: &str) -> anyhow::Result<()> {
|
||||
debug!(pid = %pid, "killing ssh-agent");
|
||||
let status = Command::new("ssh-agent")
|
||||
.env("SSH_AGENT_PID", pid)
|
||||
.args(["-k"])
|
||||
.stdout(Stdio::null())
|
||||
.status()?;
|
||||
debug!(status = %status, "status of killing agent");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the PID of the launched ssh-agent.
|
||||
///
|
||||
/// It gets the PID by stripping the output from the command using the text
|
||||
/// `"echo Agent pid "`.
|
||||
fn agent_pid(out: &[u8]) -> anyhow::Result<&str> {
|
||||
const PREFIX: &str = "SSH_AGENT_PID=";
|
||||
const SEP: u8 = b';';
|
||||
let pid = out
|
||||
.split(|b| b == &SEP)
|
||||
.find_map(|bs| std::str::from_utf8(bs).ok()?.trim().strip_prefix(PREFIX))
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find SSH_AGENT_PID"))?;
|
||||
Ok(pid)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright © 2021 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPLv3-or-later
|
||||
|
||||
use std::io;
|
||||
|
||||
use librad::{git::storage::Storage, paths::Paths, SecretKey};
|
||||
use test_helpers::tempdir::WithTmpDir;
|
||||
|
||||
pub type TmpPaths = WithTmpDir<Paths>;
|
||||
|
||||
pub fn paths() -> TmpPaths {
|
||||
WithTmpDir::new(|path| -> Result<_, io::Error> {
|
||||
let paths = Paths::from_root(path)?;
|
||||
Ok::<_, io::Error>(paths)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
type TmpRepo = WithTmpDir<git2::Repository>;
|
||||
|
||||
pub fn repo() -> anyhow::Result<TmpRepo> {
|
||||
Ok(WithTmpDir::new(|path| {
|
||||
let setup = || {
|
||||
let repo = git2::Repository::init(path)?;
|
||||
|
||||
// We need to set user info to _something_, but that doesn't have to
|
||||
// be valid, as we're using a shared repo with many keys
|
||||
let mut config = repo.config()?;
|
||||
config.set_str("user.name", "shared")?;
|
||||
config.set_str("user.email", "not.relevant@for.testing")?;
|
||||
Ok(repo)
|
||||
};
|
||||
setup().map_err(|e: git2::Error| io::Error::new(io::ErrorKind::Other, e))
|
||||
})?)
|
||||
}
|
||||
|
||||
pub type TmpStorage = WithTmpDir<Storage>;
|
||||
|
||||
pub fn storage(signer: SecretKey) -> TmpStorage {
|
||||
WithTmpDir::new(|path| -> Result<_, io::Error> {
|
||||
let paths = Paths::from_root(path)?;
|
||||
let storage =
|
||||
Storage::open(&paths, signer).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
Ok::<_, io::Error>(storage)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
use git_ref_format::{lit, name, refspec, Qualified, RefStr, RefString};
|
||||
|
||||
use librad::{
|
||||
git::{
|
||||
local::url::LocalUrl,
|
||||
types::{
|
||||
remote::{LocalFetchspec, LocalPushspec},
|
||||
Fetchspec,
|
||||
Force,
|
||||
Refspec,
|
||||
Remote,
|
||||
},
|
||||
},
|
||||
git_ext as ext,
|
||||
net::{peer::Peer, protocol::RequestPullGuard},
|
||||
refspec_pattern,
|
||||
PeerId,
|
||||
Signer,
|
||||
};
|
||||
|
||||
use crate::fixed::TestProject;
|
||||
|
||||
/// A remote in the working copy
|
||||
pub enum WorkingRemote {
|
||||
/// A remote representing a remote peer, named `PeerId::encode_id`
|
||||
Peer(PeerId),
|
||||
/// A remote representing the local peer, named "rad"
|
||||
Rad,
|
||||
}
|
||||
|
||||
impl From<PeerId> for WorkingRemote {
|
||||
fn from(p: PeerId) -> Self {
|
||||
WorkingRemote::Peer(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkingRemote {
|
||||
fn fetchspec(&self) -> Fetchspec {
|
||||
match self {
|
||||
Self::Peer(peer_id) => {
|
||||
let name = RefString::try_from(format!("{}", peer_id)).expect("peer is refstring");
|
||||
let dst = RefString::from(Qualified::from(lit::refs_remotes(name.clone())))
|
||||
.with_pattern(refspec::STAR);
|
||||
let src = RefString::from(Qualified::from(lit::refs_remotes(name)))
|
||||
.and(name::HEADS)
|
||||
.with_pattern(refspec::STAR);
|
||||
let refspec = Refspec {
|
||||
src,
|
||||
dst,
|
||||
force: Force::True,
|
||||
};
|
||||
refspec.into_fetchspec()
|
||||
},
|
||||
Self::Rad => {
|
||||
let name = RefString::try_from("rad").unwrap();
|
||||
let src =
|
||||
RefString::from_iter([name::REFS, name::HEADS]).with_pattern(refspec::STAR);
|
||||
Refspec {
|
||||
src,
|
||||
dst: RefString::from(Qualified::from(lit::refs_remotes(name)))
|
||||
.with_pattern(refspec::STAR),
|
||||
force: Force::True,
|
||||
}
|
||||
.into_fetchspec()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn remote_ref(&self, branch: &RefStr) -> RefString {
|
||||
let name = match self {
|
||||
Self::Rad => name::RAD.to_owned(),
|
||||
Self::Peer(peer_id) => {
|
||||
RefString::try_from(peer_id.to_string()).expect("peer id is refstring")
|
||||
},
|
||||
};
|
||||
RefString::from(Qualified::from(lit::refs_remotes(name))).join(branch)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `WorkingCopy` for test driving interactions with the monorepo where one
|
||||
/// needs to update the tree of a project.
|
||||
///
|
||||
/// Remotes are named after the peer ID, except in the case of the remote
|
||||
/// representing the local Peer ID - which is called "rad".
|
||||
pub struct WorkingCopy<'a, S, G> {
|
||||
repo: git2::Repository,
|
||||
_repo_path: tempfile::TempDir,
|
||||
peer: &'a Peer<S, G>,
|
||||
project: &'a TestProject,
|
||||
}
|
||||
|
||||
impl<'a, S, G> WorkingCopy<'a, S, G>
|
||||
where
|
||||
S: Signer + Clone,
|
||||
G: RequestPullGuard,
|
||||
{
|
||||
/// Create a new working copy. This initializes a git repository and then
|
||||
/// fetches the state of the local peer into `refs/remotes/rad/*`.
|
||||
pub fn new(
|
||||
project: &'a TestProject,
|
||||
peer: &'a Peer<S, G>,
|
||||
) -> Result<WorkingCopy<'a, S, G>, anyhow::Error> {
|
||||
let repo_path = tempfile::tempdir()?;
|
||||
let repo = git2::Repository::init(repo_path.as_ref())?;
|
||||
|
||||
let mut copy = WorkingCopy {
|
||||
peer,
|
||||
project,
|
||||
repo,
|
||||
_repo_path: repo_path,
|
||||
};
|
||||
copy.fetch(WorkingRemote::Rad)?;
|
||||
Ok(copy)
|
||||
}
|
||||
|
||||
/// Fetch changes from the monorepo into the working copy. The fetchspec
|
||||
/// used depends on the peer ID.
|
||||
///
|
||||
/// * If `from` is `WorkingRemote::Peer` then `refs/remotes/<peer
|
||||
/// ID>/refs/*:refs/remotes/<peer ID>/heads/*`
|
||||
/// * If `from` is `WorkingRemote::Rad` then
|
||||
/// `refs/heads/*:refs/remotes/rad/*`
|
||||
///
|
||||
/// I.e. changes from remote peers end up in a remote called
|
||||
/// `PeerId::encode_id` whilst changes from the local peer end up in a
|
||||
/// remote called "rad".
|
||||
pub fn fetch(&mut self, from: WorkingRemote) -> Result<(), anyhow::Error> {
|
||||
let fetchspec = from.fetchspec();
|
||||
let url = LocalUrl::from(self.project.project.urn());
|
||||
let mut remote = Remote::rad_remote(url, fetchspec);
|
||||
let _ = remote.fetch(self.peer.clone(), &self.repo, LocalFetchspec::Configured)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Push changes from `refs/heads/*` to the local peer
|
||||
pub fn push(&mut self) -> Result<(), anyhow::Error> {
|
||||
let url = LocalUrl::from(self.project.project.urn());
|
||||
let name = RefString::try_from("rad").unwrap();
|
||||
let fetchspec = Refspec {
|
||||
src: RefString::from_iter([name::REFS, name::HEADS]).with_pattern(refspec::STAR),
|
||||
dst: RefString::from(Qualified::from(lit::refs_remotes(name)))
|
||||
.with_pattern(refspec::STAR),
|
||||
force: Force::True,
|
||||
}
|
||||
.into_fetchspec();
|
||||
let mut remote = Remote::rad_remote(url, fetchspec);
|
||||
let _ = remote.push(
|
||||
self.peer.clone(),
|
||||
&self.repo,
|
||||
LocalPushspec::Matching {
|
||||
pattern: refspec_pattern!("refs/heads/*"),
|
||||
force: Force::True,
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new commit on top of whichever commit is the head of
|
||||
/// `on_branch`. If the branch does not exist this will create it.
|
||||
pub fn commit(
|
||||
&mut self,
|
||||
message: &str,
|
||||
on_branch: Qualified,
|
||||
) -> Result<git2::Oid, anyhow::Error> {
|
||||
let branch_name = on_branch.non_empty_components().2;
|
||||
let parent = match self.repo.find_branch(&branch_name, git2::BranchType::Local) {
|
||||
Ok(b) => b.get().target().and_then(|o| self.repo.find_commit(o).ok()),
|
||||
Err(e) if ext::error::is_not_found_err(&e) => None,
|
||||
Err(e) => return Err(anyhow::Error::from(e)),
|
||||
};
|
||||
let empty_tree = {
|
||||
let mut index = self.repo.index()?;
|
||||
let oid = index.write_tree()?;
|
||||
self.repo.find_tree(oid).unwrap()
|
||||
};
|
||||
let author = git2::Signature::now("The Animal", "animal@muppets.com").unwrap();
|
||||
let parents = match &parent {
|
||||
Some(p) => vec![p],
|
||||
None => Vec::new(),
|
||||
};
|
||||
self.repo
|
||||
.commit(
|
||||
Some(&on_branch),
|
||||
&author,
|
||||
&author,
|
||||
message,
|
||||
&empty_tree,
|
||||
&parents,
|
||||
)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
pub fn commit_and_push(
|
||||
&mut self,
|
||||
message: &str,
|
||||
on_branch: Qualified,
|
||||
) -> Result<git2::Oid, anyhow::Error> {
|
||||
let id = self.commit(message, on_branch)?;
|
||||
self.push()?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Create a branch at `refs/heads/<branch>` which tracks the given remote.
|
||||
/// The remote branch name depends on `from`.
|
||||
///
|
||||
/// * If `from` is `WorkingCopy::Rad` then `refs/remotes/rad/<branch>`
|
||||
/// * If `from` is `WorkingCopy::Peer(peer_id)` then `refs/remotes/<peer
|
||||
/// id>/<branch>`
|
||||
pub fn create_remote_tracking_branch(
|
||||
&self,
|
||||
from: WorkingRemote,
|
||||
branch: &RefStr,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let target = self
|
||||
.repo
|
||||
.find_reference(from.remote_ref(branch).as_str())?
|
||||
.target()
|
||||
.ok_or_else(|| anyhow::anyhow!("remote ref is not a direct reference"))?;
|
||||
let commit = self.repo.find_commit(target)?;
|
||||
self.repo.branch(branch.as_str(), &commit, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fast forward the local branch `refs/heads/<branch>` to whatever is
|
||||
/// pointed to by `refs/remotes/<remote>/<branch>`
|
||||
///
|
||||
/// * If `from` is `WorkingRemote::Peer(peer_id)` then `remote` is
|
||||
/// `peer_id.encode_id()`
|
||||
/// * If `from` is `WorkingRemote::Rad` then `remote` is `"rad"`
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * If the local branch does not exist
|
||||
/// * If the remote branch does not exist
|
||||
/// * If either of the branches does not point at a commit
|
||||
/// * If the remote branch is not a descendant of the local branch
|
||||
pub fn fast_forward_to(&self, from: WorkingRemote, branch: &RefStr) -> anyhow::Result<()> {
|
||||
let remote_ref = from.remote_ref(branch);
|
||||
let remote_target = self
|
||||
.repo
|
||||
.find_reference(&remote_ref)?
|
||||
.target()
|
||||
.ok_or_else(|| anyhow::anyhow!("remote ref had no target"))?;
|
||||
let local_ref = RefString::from(Qualified::from(lit::refs_heads(branch)));
|
||||
let local_target = self
|
||||
.repo
|
||||
.find_reference(&local_ref)?
|
||||
.target()
|
||||
.ok_or_else(|| anyhow::anyhow!("local ref had no target"))?;
|
||||
if !self.repo.graph_descendant_of(remote_target, local_target)? {
|
||||
anyhow::bail!("remote ref was not a descendant of local ref");
|
||||
} else {
|
||||
self.repo
|
||||
.reference(&local_ref, remote_target, true, "fast forward")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new commit which merges `refs/heads/<branch>` and
|
||||
/// `refs/remotes/<remote>/<branch>`
|
||||
///
|
||||
/// this will create a new commit with two parents, one for the remote
|
||||
/// branch and one for the local branch
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * If the remote branch does not exist
|
||||
/// * If the local branch does not exist
|
||||
/// * If either of the references does not point to a commit
|
||||
pub fn merge_remote(&self, remote: PeerId, branch: &RefStr) -> anyhow::Result<git2::Oid> {
|
||||
let peer_branch = WorkingRemote::Peer(remote).remote_ref(branch);
|
||||
let peer_commit = self
|
||||
.repo
|
||||
.find_reference(peer_branch.as_ref())?
|
||||
.peel_to_commit()?;
|
||||
let local_branch = Qualified::from(lit::refs_heads(branch));
|
||||
let local_commit = self
|
||||
.repo
|
||||
.find_reference(local_branch.as_ref())?
|
||||
.peel_to_commit()?;
|
||||
|
||||
let message = format!("merge {} into {}", peer_branch, local_branch);
|
||||
let empty_tree = {
|
||||
let mut index = self.repo.index()?;
|
||||
let oid = index.write_tree()?;
|
||||
self.repo.find_tree(oid).unwrap()
|
||||
};
|
||||
let author = git2::Signature::now("The Animal", "animal@muppets.com").unwrap();
|
||||
let parents = vec![&peer_commit, &local_commit];
|
||||
self.repo
|
||||
.commit(
|
||||
Some(&local_branch),
|
||||
&author,
|
||||
&author,
|
||||
&message,
|
||||
&empty_tree,
|
||||
&parents,
|
||||
)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user