cli: Add way to init from existing repositories
This adds an option to `rad init` to initialize a working copy with
an existing radicle repository in storage:
rad init --existing <rid>
This sets up the remote(s) and configures the repo for you.
This commit is contained in:
parent
74336f961a
commit
ee1c867800
|
|
@ -0,0 +1,40 @@
|
|||
Let's clone a regular repository via plain Git:
|
||||
```
|
||||
$ git clone $URL heartwood
|
||||
$ cd heartwood
|
||||
$ git rev-parse HEAD
|
||||
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
|
||||
```
|
||||
|
||||
We can see it's not a Radicle working copy:
|
||||
``` (fail)
|
||||
$ rad .
|
||||
✗ Error: Current directory is not a Radicle repository
|
||||
```
|
||||
|
||||
Let's pick an existing repository:
|
||||
```
|
||||
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
```
|
||||
|
||||
And initialize this working copy as that existing repository:
|
||||
```
|
||||
$ rad init --existing rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
✓ Initialized existing repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji in [..]/heartwood/..
|
||||
```
|
||||
|
||||
We can confirm that the working copy is initialized:
|
||||
```
|
||||
$ rad .
|
||||
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
$ git remote show rad
|
||||
* remote rad
|
||||
Fetch URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
Push URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
HEAD branch: (unknown)
|
||||
Remote branch:
|
||||
master new (next fetch will store in remotes/rad)
|
||||
Local ref configured for 'git push':
|
||||
master pushes to master (up to date)
|
||||
```
|
||||
|
|
@ -18,6 +18,7 @@ use radicle::node::events::UploadPack;
|
|||
use radicle::node::policy::Scope;
|
||||
use radicle::node::{Event, Handle, NodeId, DEFAULT_SUBSCRIBE_TIMEOUT};
|
||||
use radicle::prelude::Doc;
|
||||
use radicle::storage::ReadStorage as _;
|
||||
use radicle::{profile, Node};
|
||||
|
||||
use crate::commands;
|
||||
|
|
@ -43,6 +44,7 @@ Options
|
|||
--scope <scope> Repository follow scope: `followed` or `all` (default: all)
|
||||
--private Set repository visibility to *private*
|
||||
--public Set repository visibility to *public*
|
||||
--existing <rid> Setup repository as an existing Radicle repository
|
||||
-u, --set-upstream Setup the upstream of the default branch
|
||||
--setup-signing Setup the radicle key as a signing key for this repository
|
||||
--no-confirm Don't ask for confirmation during setup
|
||||
|
|
@ -60,6 +62,7 @@ pub struct Options {
|
|||
pub branch: Option<String>,
|
||||
pub interactive: Interactive,
|
||||
pub visibility: Option<Visibility>,
|
||||
pub existing: Option<RepoId>,
|
||||
pub setup_signing: bool,
|
||||
pub scope: Scope,
|
||||
pub set_upstream: bool,
|
||||
|
|
@ -81,6 +84,7 @@ impl Args for Options {
|
|||
let mut set_upstream = false;
|
||||
let mut setup_signing = false;
|
||||
let mut scope = Scope::All;
|
||||
let mut existing = None;
|
||||
let mut seed = true;
|
||||
let mut verbose = false;
|
||||
let mut visibility = None;
|
||||
|
|
@ -150,6 +154,12 @@ impl Args for Options {
|
|||
Long("public") => {
|
||||
visibility = Some(Visibility::Public);
|
||||
}
|
||||
Long("existing") if existing.is_none() => {
|
||||
let val = parser.value()?;
|
||||
let rid = term::args::rid(&val)?;
|
||||
|
||||
existing = Some(rid);
|
||||
}
|
||||
Long("verbose") | Short('v') => {
|
||||
verbose = true;
|
||||
}
|
||||
|
|
@ -170,6 +180,7 @@ impl Args for Options {
|
|||
description,
|
||||
branch,
|
||||
scope,
|
||||
existing,
|
||||
interactive,
|
||||
set_upstream,
|
||||
setup_signing,
|
||||
|
|
@ -184,29 +195,38 @@ impl Args for Options {
|
|||
|
||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
let profile = ctx.profile()?;
|
||||
|
||||
init(options, &profile)
|
||||
}
|
||||
|
||||
pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let path = options.path.unwrap_or_else(|| cwd.clone());
|
||||
let path = path.as_path().canonicalize()?;
|
||||
let interactive = options.interactive;
|
||||
let repo = match git::Repository::open(&path) {
|
||||
let cwd = env::current_dir()?;
|
||||
let path = options.path.as_deref().unwrap_or(cwd.as_path());
|
||||
let repo = match git::Repository::open(path) {
|
||||
Ok(r) => r,
|
||||
Err(e) if radicle::git::ext::is_not_found_err(&e) => {
|
||||
anyhow::bail!("a Git repository was not found at the given path")
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
if let Ok((remote, _)) = git::rad_remote(&repo) {
|
||||
if let Some(remote) = remote.url() {
|
||||
bail!("repository is already initialized with remote {remote}");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rid) = options.existing {
|
||||
init_existing(repo, rid, options, &profile)
|
||||
} else {
|
||||
init(repo, options, &profile)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
repo: git::Repository,
|
||||
options: Options,
|
||||
profile: &profile::Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = repo
|
||||
.workdir()
|
||||
.unwrap_or_else(|| repo.path())
|
||||
.canonicalize()?;
|
||||
let interactive = options.interactive;
|
||||
let head: String = repo
|
||||
.head()
|
||||
.ok()
|
||||
|
|
@ -319,7 +339,7 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
|
|||
term::format::dim("(RID)"),
|
||||
term::format::highlight(rid.urn())
|
||||
);
|
||||
let directory = if path == cwd {
|
||||
let directory = if path == env::current_dir()? {
|
||||
"this directory".to_owned()
|
||||
} else {
|
||||
term::format::tertiary(path.display()).to_string()
|
||||
|
|
@ -350,6 +370,48 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_existing(
|
||||
working: git::Repository,
|
||||
rid: RepoId,
|
||||
options: Options,
|
||||
profile: &profile::Profile,
|
||||
) -> anyhow::Result<()> {
|
||||
let stored = profile.storage.repository(rid)?;
|
||||
let project = stored.project()?;
|
||||
let url = radicle::git::Url::from(rid);
|
||||
|
||||
radicle::git::configure_repository(&working)?;
|
||||
radicle::git::configure_remote(
|
||||
&working,
|
||||
&radicle::rad::REMOTE_NAME,
|
||||
&url,
|
||||
&url.clone().with_namespace(profile.public_key),
|
||||
)?;
|
||||
|
||||
if options.set_upstream {
|
||||
// Setup eg. `master` -> `rad/master`
|
||||
radicle::git::set_upstream(
|
||||
&working,
|
||||
&*radicle::rad::REMOTE_NAME,
|
||||
project.default_branch(),
|
||||
radicle::git::refs::workdir::branch(project.default_branch()),
|
||||
)?;
|
||||
}
|
||||
|
||||
term::success!(
|
||||
"Initialized existing repository {} in {}..",
|
||||
term::format::tertiary(rid),
|
||||
term::format::dim(
|
||||
working
|
||||
.workdir()
|
||||
.unwrap_or_else(|| working.path())
|
||||
.display()
|
||||
),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SyncResult<T> {
|
||||
NodeStopped,
|
||||
|
|
|
|||
|
|
@ -204,6 +204,28 @@ fn rad_init() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_init_existing() {
|
||||
let mut environment = Environment::new();
|
||||
let mut profile = environment.node(config::node("alice"));
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let rid = profile.project("heartwood", "Radicle Heartwood Protocol & Stack");
|
||||
|
||||
test(
|
||||
"examples/rad-init-existing.md",
|
||||
working.path(),
|
||||
Some(&profile.home),
|
||||
[(
|
||||
"URL",
|
||||
git::url::File::new(profile.storage.path())
|
||||
.rid(rid)
|
||||
.to_string()
|
||||
.as_str(),
|
||||
)],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_init_no_seed() {
|
||||
let mut environment = Environment::new();
|
||||
|
|
|
|||
|
|
@ -697,6 +697,8 @@ pub mod process {
|
|||
pub mod url {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::prelude::RepoId;
|
||||
|
||||
/// A Git URL using the `file://` scheme.
|
||||
pub struct File {
|
||||
pub path: PathBuf,
|
||||
|
|
@ -707,6 +709,12 @@ pub mod url {
|
|||
pub fn new(path: impl Into<PathBuf>) -> Self {
|
||||
Self { path: path.into() }
|
||||
}
|
||||
|
||||
/// Return a URL with the given RID set.
|
||||
pub fn rid(mut self, rid: RepoId) -> Self {
|
||||
self.path.push(rid.canonical());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for File {
|
||||
|
|
|
|||
Loading…
Reference in New Issue