cli/clone: Add flag `--bare`

This commit is contained in:
Lorenz Leutgeb 2025-09-12 10:41:10 +02:00 committed by Fintan Halpenny
parent 766e281d39
commit 86ea33b0ca
4 changed files with 136 additions and 5 deletions

View File

@ -13,6 +13,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## New Features ## New Features
- `rad clone` now supports the flag `--bare` which works analoguously to
`git clone --bare`.
## Fixed Bugs
## 1.4.0
## Release Highlights
## Deprecations
## New Features
- `rad cob log` now supports the arguments `--from` and `--to` which can be used - `rad cob log` now supports the arguments `--from` and `--to` which can be used
to range over particular operations on a COB. to range over particular operations on a COB.

View File

@ -0,0 +1,81 @@
To create a local bare copy of a repository on the radicle network, we use the
`clone` command, followed by the identifier or *RID* of the repository:
```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed --bare
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from the network, found [..] potential seed(s).
✓ Target met: [..] seed(s)
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
We can now have a look at the new directory that was created from the cloned
repository:
```
$ cd heartwood
$ ls
FETCH_HEAD
HEAD
config
description
hooks
info
objects
refs
```
As expected, some `git` commands fail:
``` (stderr) (fail)
$ git status
fatal: this operation must be run in a work tree
```
Let's check that the remote tracking branch was setup correctly:
```
$ git branch --remotes
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
rad/master
```
The first branch is ours, and the second points to the repository delegate.
We can also take a look at the remotes:
```
$ git remote -v
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji (fetch)
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push)
```
Let's check the last commit!
```
$ git log -n 1
commit f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
Author: anonymous <anonymous@radicle.xyz>
Date: Mon Jan 1 14:39:16 2018 +0000
Second commit
```
Cloned repositories show up in `rad ls`:
```
$ rad ls --seeded
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```

View File

@ -46,6 +46,7 @@ Usage
Options Options
--bare Make a bare repository
--scope <scope> Follow scope: `followed` or `all` (default: all) --scope <scope> Follow scope: `followed` or `all` (default: all)
-s, --seed <nid> Clone from this seed (may be specified multiple times) -s, --seed <nid> Clone from this seed (may be specified multiple times)
--timeout <secs> Timeout for fetching repository (default: 9) --timeout <secs> Timeout for fetching repository (default: 9)
@ -64,6 +65,7 @@ pub struct Options {
scope: Scope, scope: Scope,
/// Sync settings. /// Sync settings.
sync: SyncSettings, sync: SyncSettings,
bare: bool,
} }
impl Args for Options { impl Args for Options {
@ -75,6 +77,7 @@ impl Args for Options {
let mut scope = Scope::All; let mut scope = Scope::All;
let mut sync = SyncSettings::default(); let mut sync = SyncSettings::default();
let mut directory = None; let mut directory = None;
let mut bare = false;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -99,6 +102,9 @@ impl Args for Options {
// We keep this flag here for consistency though it doesn't have any effect, // We keep this flag here for consistency though it doesn't have any effect,
// since the command is fully non-interactive. // since the command is fully non-interactive.
} }
Long("bare") => {
bare = true;
}
Long("help") | Short('h') => { Long("help") | Short('h') => {
return Err(Error::Help.into()); return Err(Error::Help.into());
} }
@ -125,6 +131,7 @@ impl Args for Options {
directory, directory,
scope, scope,
sync, sync,
bare,
}, },
vec![], vec![],
)) ))
@ -153,6 +160,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
options.sync.with_profile(&profile), options.sync.with_profile(&profile),
&mut node, &mut node,
&profile, &profile,
options.bare,
)? )?
.print_or_success() .print_or_success()
.ok_or_else(|| anyhow::anyhow!("failed to clone {}", options.id))?; .ok_or_else(|| anyhow::anyhow!("failed to clone {}", options.id))?;
@ -163,7 +171,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
.filter(|id| id != profile.id()) .filter(|id| id != profile.id())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let default_branch = proj.default_branch().clone(); let default_branch = proj.default_branch().clone();
let path = working.workdir().unwrap(); // SAFETY: The working copy is not bare. let path = if !options.bare {
working.workdir().unwrap()
} else {
working.path()
};
// Configure repository and setup tracking for repository delegates. // Configure repository and setup tracking for repository delegates.
radicle::git::configure_repository(&working)?; radicle::git::configure_repository(&working)?;
@ -229,6 +241,7 @@ struct Checkout {
repository: storage::git::Repository, repository: storage::git::Repository,
doc: Doc, doc: Doc,
project: Project, project: Project,
bare: bool,
} }
impl Checkout { impl Checkout {
@ -236,6 +249,7 @@ impl Checkout {
repository: storage::git::Repository, repository: storage::git::Repository,
profile: &Profile, profile: &Profile,
directory: Option<PathBuf>, directory: Option<PathBuf>,
bare: bool,
) -> Result<Self, CheckoutFailure> { ) -> Result<Self, CheckoutFailure> {
let rid = repository.rid(); let rid = repository.rid();
let doc = repository let doc = repository
@ -257,6 +271,7 @@ impl Checkout {
repository, repository,
doc: doc.doc, doc: doc.doc,
project: proj, project: proj,
bare,
}) })
} }
@ -274,7 +289,7 @@ impl Checkout {
"Creating checkout in ./{}..", "Creating checkout in ./{}..",
term::format::tertiary(destination.display()) term::format::tertiary(destination.display())
)); ));
match rad::checkout(self.id, &self.remote, self.path, storage, false) { match rad::checkout(self.id, &self.remote, self.path, storage, self.bare) {
Err(err) => { Err(err) => {
spinner.message(format!( spinner.message(format!(
"Failed to checkout in ./{}", "Failed to checkout in ./{}",
@ -303,6 +318,7 @@ fn clone(
settings: SyncSettings, settings: SyncSettings,
node: &mut Node, node: &mut Node,
profile: &Profile, profile: &Profile,
bare: bool,
) -> Result<CloneResult, CloneError> { ) -> Result<CloneResult, CloneError> {
// Seed repository. // Seed repository.
if node.seed(id, scope)? { if node.seed(id, scope)? {
@ -322,7 +338,7 @@ fn clone(
node::sync::FetcherResult::TargetReached(_) => { node::sync::FetcherResult::TargetReached(_) => {
profile.storage.repository(id).map_or_else( profile.storage.repository(id).map_or_else(
|err| Ok(CloneResult::RepositoryMissing { rid: id, err }), |err| Ok(CloneResult::RepositoryMissing { rid: id, err }),
|repository| Ok(perform_checkout(repository, profile, directory)?), |repository| Ok(perform_checkout(repository, profile, directory, bare)?),
) )
} }
node::sync::FetcherResult::TargetError(failure) => { node::sync::FetcherResult::TargetError(failure) => {
@ -330,7 +346,7 @@ fn clone(
} }
} }
} }
Ok(repository) => Ok(perform_checkout(repository, profile, directory)?), Ok(repository) => Ok(perform_checkout(repository, profile, directory, bare)?),
} }
} }
@ -338,8 +354,9 @@ fn perform_checkout(
repository: storage::git::Repository, repository: storage::git::Repository,
profile: &Profile, profile: &Profile,
directory: Option<PathBuf>, directory: Option<PathBuf>,
bare: bool,
) -> Result<CloneResult, rad::CheckoutError> { ) -> Result<CloneResult, rad::CheckoutError> {
Checkout::new(repository, profile, directory).map_or_else( Checkout::new(repository, profile, directory, bare).map_or_else(
|failure| Ok(CloneResult::Failure(failure)), |failure| Ok(CloneResult::Failure(failure)),
|checkout| checkout.run(&profile.storage), |checkout| checkout.run(&profile.storage),
) )

View File

@ -1155,6 +1155,26 @@ fn rad_clone() {
test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap(); test("examples/rad-clone.md", working, Some(&bob.home), []).unwrap();
} }
#[test]
fn rad_clone_bare() {
let mut environment = Environment::new();
let mut alice = environment.node("alice");
let bob = environment.node("bob");
let working = environment.tempdir().join("working");
// Setup a test project.
let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
let mut alice = alice.spawn();
let mut bob = bob.spawn();
// Prevent Alice from fetching Bob's fork, as we're not testing that and it may cause errors.
alice.handle.seed(acme, Scope::Followed).unwrap();
bob.connect(&alice).converge([&alice]);
test("examples/rad-clone-bare.md", working, Some(&bob.home), []).unwrap();
}
#[test] #[test]
fn rad_clone_directory() { fn rad_clone_directory() {
let mut environment = Environment::new(); let mut environment = Environment::new();