cli: Get `rad-checkout` working properly
This commit is contained in:
parent
1f06f5340f
commit
bb135b64b3
|
|
@ -0,0 +1,77 @@
|
|||
With the `rad checkout` command, you can create a new working copy from an
|
||||
existing project.
|
||||
|
||||
```
|
||||
$ rad checkout rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
|
||||
Initializing local checkout for 🌱 rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji (heartwood)
|
||||
|
||||
ok Performing checkout...
|
||||
ok Setting up remotes...
|
||||
|
||||
🌱 Project checkout successful under ./heartwood
|
||||
|
||||
```
|
||||
|
||||
Let's have a look at what the command did. Navigate to the working copy:
|
||||
|
||||
```
|
||||
$ cd heartwood
|
||||
```
|
||||
|
||||
Check the README:
|
||||
```
|
||||
$ cat README
|
||||
Hello World!
|
||||
```
|
||||
|
||||
Check the repository status:
|
||||
|
||||
```
|
||||
$ git status
|
||||
On branch master
|
||||
Your branch is up to date with 'rad/master'.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
```
|
||||
|
||||
Check the remote configuration:
|
||||
|
||||
```
|
||||
$ git remote --verbose
|
||||
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
|
||||
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||
```
|
||||
|
||||
List the branches:
|
||||
|
||||
```
|
||||
$ git branch --all
|
||||
* master
|
||||
remotes/rad/master
|
||||
```
|
||||
|
||||
List the references:
|
||||
|
||||
```
|
||||
$ git show-ref
|
||||
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
|
||||
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/remotes/rad/master
|
||||
```
|
||||
|
||||
View the repository configuration:
|
||||
|
||||
```
|
||||
$ cat .git/config
|
||||
[core]
|
||||
bare = false
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
logallrefupdates = true
|
||||
[remote "rad"]
|
||||
url = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
fetch = +refs/heads/*:refs/remotes/rad/*
|
||||
[branch "master"]
|
||||
remote = rad
|
||||
merge = refs/heads/master
|
||||
```
|
||||
|
|
@ -27,5 +27,5 @@ Projects can be listed with the `ls` command:
|
|||
|
||||
```
|
||||
$ rad ls
|
||||
heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji cdf76ce Radicle Heartwood Protocol & Stack
|
||||
heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji f2de534 Radicle Heartwood Protocol & Stack
|
||||
```
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ missing from the project.
|
|||
|
||||
```
|
||||
$ git checkout -b flux-capacitor-power
|
||||
$ touch README.md
|
||||
$ touch REQUIREMENTS
|
||||
```
|
||||
|
||||
Here the instructions are added to the project's README for 1.21 gigawatts and
|
||||
commit the changes to git.
|
||||
|
||||
```
|
||||
$ git add README.md
|
||||
$ git add REQUIREMENTS
|
||||
$ git commit -v -m "Define power requirements"
|
||||
[flux-capacitor-power 9dad201] Define power requirements
|
||||
[flux-capacitor-power 3e674d1] Define power requirements
|
||||
1 file changed, 0 insertions(+), 0 deletions(-)
|
||||
create mode 100644 README.md
|
||||
create mode 100644 REQUIREMENTS
|
||||
```
|
||||
|
||||
Once the code is ready, we open (or create) a patch with our changes for the project.
|
||||
|
|
@ -32,10 +32,10 @@ $ rad patch open --message "define power requirements" --no-confirm
|
|||
ok Pushing HEAD to storage...
|
||||
ok Analyzing remotes...
|
||||
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master (cdf76ce) <- z6MknSL…StBU8Vi/flux-capacitor-power (9dad201)
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master (f2de534) <- z6MknSL…StBU8Vi/flux-capacitor-power (3e674d1)
|
||||
1 commit(s) ahead, 0 commit(s) behind
|
||||
|
||||
9dad201 Define power requirements
|
||||
3e674d1 Define power requirements
|
||||
|
||||
|
||||
╭─ define power requirements ───────
|
||||
|
|
@ -45,7 +45,7 @@ No description provided.
|
|||
╰───────────────────────────────────
|
||||
|
||||
|
||||
ok Patch b9bb418c6f504ee91e54c555bdc8fc37b4d9b28b created 🌱
|
||||
ok Patch c3bd983b5cf9643f7e98a2e6ee216d6794bac16b created 🌱
|
||||
```
|
||||
|
||||
It will now be listed as one of the project's open patches.
|
||||
|
|
@ -55,16 +55,16 @@ $ rad patch
|
|||
|
||||
- YOU PROPOSED -
|
||||
|
||||
define power requirements b9bb418c6f5 R0 9dad201 (flux-capacitor-power) ahead 1, behind 0
|
||||
define power requirements c3bd983b5cf R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0
|
||||
└─ * opened by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [..]
|
||||
|
||||
- OTHERS PROPOSED -
|
||||
|
||||
Nothing to show.
|
||||
|
||||
$ rad patch show b9bb418c6f504ee91e54c555bdc8fc37b4d9b28b
|
||||
$ rad patch show c3bd983b5cf9643f7e98a2e6ee216d6794bac16b
|
||||
|
||||
patch b9bb418c6f504ee91e54c555bdc8fc37b4d9b28b
|
||||
patch c3bd983b5cf9643f7e98a2e6ee216d6794bac16b
|
||||
|
||||
╭─ define power requirements ───────
|
||||
|
||||
|
|
@ -72,13 +72,13 @@ No description provided.
|
|||
|
||||
╰───────────────────────────────────
|
||||
|
||||
commit 9dad201e2cb3306dd708b4a9ebe811b293e27196
|
||||
commit 3e674d1a1df90807e934f9ae5da2591dd6848a33
|
||||
Author: radicle <radicle@localhost>
|
||||
Date: Thu Dec 15 17:28:04 2022 +0000
|
||||
|
||||
Define power requirements
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
diff --git a/REQUIREMENTS b/REQUIREMENTS
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
|
||||
|
|
|
|||
|
|
@ -132,10 +132,13 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
|
|||
|
||||
/// Setup a remote and tracking branch for each given remote.
|
||||
pub fn setup_remotes(setup: project::SetupRemote, remotes: &[NodeId]) -> anyhow::Result<()> {
|
||||
let mut spinner = term::spinner("Setting up remotes...");
|
||||
for remote_id in remotes {
|
||||
spinner.message(format!("Setting up remote {remote_id}.."));
|
||||
|
||||
if let Some((remote, branch)) = setup.run(*remote_id)? {
|
||||
let remote = remote.name().unwrap(); // Only valid UTF-8 is used.
|
||||
//
|
||||
|
||||
term::success!("Remote {} set", term::format::highlight(remote));
|
||||
term::success!(
|
||||
"Remote-tracking branch {} created for {}",
|
||||
|
|
@ -144,6 +147,7 @@ pub fn setup_remotes(setup: project::SetupRemote, remotes: &[NodeId]) -> anyhow:
|
|||
);
|
||||
}
|
||||
}
|
||||
spinner.finish();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,9 +73,12 @@ fn test<'a>(
|
|||
.env("GIT_COMMITTER_DATE", "1671125284")
|
||||
.env("GIT_COMMITTER_EMAIL", "radicle@localhost")
|
||||
.env("GIT_COMMITTER_NAME", "radicle")
|
||||
.env("GIT_CONFIG", "/dev/null")
|
||||
.env("GIT_CONFIG_GLOBAL", "/dev/null")
|
||||
.env("GIT_CONFIG_NOSYSTEM", "1")
|
||||
.env("RAD_HOME", home.to_string_lossy())
|
||||
.env("RAD_PASSPHRASE", "radicle")
|
||||
.env("TZ", "Etc/GMT")
|
||||
.env("TZ", "UTC")
|
||||
.env(radicle_cob::git::RAD_COMMIT_TIME, "1671125284")
|
||||
.envs(envs)
|
||||
.cwd(cwd)
|
||||
|
|
@ -130,6 +133,33 @@ fn rad_init() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_checkout() {
|
||||
let mut environment = Environment::new();
|
||||
let profile = environment.profile("alice");
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let copy = tempfile::tempdir().unwrap();
|
||||
|
||||
// Setup a test repository.
|
||||
fixtures::repository(working.path());
|
||||
|
||||
test(
|
||||
"examples/rad-init.md",
|
||||
working.path(),
|
||||
Some(&profile.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test(
|
||||
"examples/rad-checkout.md",
|
||||
copy.path(),
|
||||
Some(&profile.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_delegate() {
|
||||
let mut environment = Environment::new();
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io, mem};
|
||||
use std::{env, fs, io, mem};
|
||||
|
||||
use snapbox::cmd::Command;
|
||||
use snapbox::cmd::{Command, OutputAssert};
|
||||
use snapbox::{Assert, Substitutions};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -136,25 +136,54 @@ impl TestFormula {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<bool, io::Error> {
|
||||
pub fn run(&mut self) -> Result<bool, io::Error> {
|
||||
let assert = Assert::new().substitutions(self.subs.clone());
|
||||
|
||||
for test in &self.tests {
|
||||
for assertion in &test.assertions {
|
||||
let program = if assertion.program == "rad" {
|
||||
snapbox::cmd::cargo_bin("rad")
|
||||
} else if assertion.program == "cd" {
|
||||
let path: PathBuf = assertion.args.first().unwrap().into();
|
||||
let path = self.cwd.join(path);
|
||||
|
||||
// TODO: Add support for `..` and `/`
|
||||
// TODO: Error if more than one args are given.
|
||||
|
||||
if !path.exists() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("cd: '{}' does not exist", path.display()),
|
||||
));
|
||||
}
|
||||
self.cwd = path;
|
||||
|
||||
continue;
|
||||
} else {
|
||||
PathBuf::from(&assertion.program)
|
||||
};
|
||||
|
||||
Command::new(program)
|
||||
let result = Command::new(program.clone())
|
||||
.env_clear()
|
||||
.envs(env::vars().filter(|(k, _)| k == "PATH"))
|
||||
.envs(self.env.clone())
|
||||
.current_dir(&self.cwd)
|
||||
.args(&assertion.args)
|
||||
.with_assert(assert.clone())
|
||||
.assert()
|
||||
.stdout_matches(&assertion.expected)
|
||||
.success();
|
||||
.output();
|
||||
|
||||
match result {
|
||||
Ok(output) => {
|
||||
let assert = OutputAssert::new(output).with_assert(assert.clone());
|
||||
assert.stdout_matches(&assertion.expected).success();
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(io::Error::new(
|
||||
err.kind(),
|
||||
format!("{err}: `{}`", program.display()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
|
|
|
|||
|
|
@ -255,10 +255,9 @@ pub fn commit<'a>(
|
|||
target: &RefStr,
|
||||
message: &str,
|
||||
sig: &git2::Signature,
|
||||
tree: &git2::Tree,
|
||||
) -> Result<git2::Commit<'a>, git2::Error> {
|
||||
let tree_id = repo.index()?.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let oid = repo.commit(Some(target.as_str()), sig, sig, message, &tree, &[parent])?;
|
||||
let oid = repo.commit(Some(target.as_str()), sig, sig, message, tree, &[parent])?;
|
||||
let commit = repo.find_commit(oid)?;
|
||||
|
||||
Ok(commit)
|
||||
|
|
@ -308,7 +307,16 @@ pub fn configure_remote<'r>(
|
|||
|
||||
/// Fetch from the given `remote`.
|
||||
pub fn fetch(repo: &git2::Repository, remote: &str) -> Result<(), git2::Error> {
|
||||
repo.find_remote(remote)?.fetch::<&str>(&[], None, None)
|
||||
repo.find_remote(remote)?.fetch::<&str>(
|
||||
&[],
|
||||
Some(
|
||||
git2::FetchOptions::new()
|
||||
.update_fetchhead(false)
|
||||
.prune(git2::FetchPrune::On)
|
||||
.download_tags(git2::AutotagOption::None),
|
||||
),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Push `refspecs` to the given `remote` using the provided `namespace`.
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
|
|||
let mut opts = git2::RepositoryInitOptions::new();
|
||||
opts.no_reinit(true).description(project.description());
|
||||
|
||||
let repo = git2::Repository::init_opts(path.as_ref().join(project.name()), &opts)?;
|
||||
let repo = git2::Repository::init_opts(path.as_ref(), &opts)?;
|
||||
let url = git::Url::from(proj).with_namespace(*remote);
|
||||
|
||||
// Configure and fetch all refs from remote.
|
||||
|
|
@ -298,15 +298,18 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
|
|||
git::refs::workdir::remote_branch(&REMOTE_NAME, project.default_branch());
|
||||
|
||||
let remote_head_commit = repo.find_reference(&remote_head_ref)?.peel_to_commit()?;
|
||||
let _ = repo.branch(project.default_branch(), &remote_head_commit, true)?;
|
||||
let branch = repo
|
||||
.branch(project.default_branch(), &remote_head_commit, true)?
|
||||
.into_reference();
|
||||
let branch_ref = branch
|
||||
.name()
|
||||
.expect("checkout: default branch name is valid UTF-8");
|
||||
|
||||
repo.set_head(branch_ref)?;
|
||||
repo.checkout_head(None)?;
|
||||
|
||||
// Setup remote tracking for default branch.
|
||||
git::set_upstream(
|
||||
&repo,
|
||||
&REMOTE_NAME,
|
||||
project.default_branch(),
|
||||
&git::refs::workdir::branch(project.default_branch()),
|
||||
)?;
|
||||
git::set_upstream(&repo, &REMOTE_NAME, project.default_branch(), branch_ref)?;
|
||||
}
|
||||
|
||||
Ok(repo)
|
||||
|
|
|
|||
|
|
@ -844,12 +844,16 @@ mod tests {
|
|||
let alice_proj_storage = alice.repository(proj_id).unwrap();
|
||||
let alice_head = proj_repo.find_commit(alice_head).unwrap();
|
||||
let alice_sig = git2::Signature::now("Alice", "alice@radicle.xyz").unwrap();
|
||||
let alice_tree = proj_repo
|
||||
.find_tree(proj_repo.index().unwrap().write_tree().unwrap())
|
||||
.unwrap();
|
||||
let alice_head = git::commit(
|
||||
&proj_repo,
|
||||
&alice_head,
|
||||
&refname,
|
||||
"Making changes",
|
||||
&alice_sig,
|
||||
&alice_tree,
|
||||
)
|
||||
.unwrap()
|
||||
.id();
|
||||
|
|
@ -911,6 +915,8 @@ mod tests {
|
|||
let backend = &project.backend;
|
||||
let sig = git2::Signature::now(&alice.to_string(), "anonymous@radicle.xyz").unwrap();
|
||||
let head = git::initial_commit(backend, &sig).unwrap();
|
||||
let tree =
|
||||
git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), backend).unwrap();
|
||||
|
||||
git::commit(
|
||||
backend,
|
||||
|
|
@ -918,6 +924,7 @@ mod tests {
|
|||
&git::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
|
||||
"Second commit",
|
||||
&sig,
|
||||
&tree,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -62,17 +62,25 @@ pub fn repository<P: AsRef<Path>>(path: P) -> (git2::Repository, git2::Oid) {
|
|||
)
|
||||
.unwrap();
|
||||
let head = git::initial_commit(&repo, &sig).unwrap();
|
||||
let oid = git::commit(
|
||||
&repo,
|
||||
&head,
|
||||
git::refname!("refs/heads/master").as_refstr(),
|
||||
"Second commit",
|
||||
&sig,
|
||||
)
|
||||
.unwrap()
|
||||
.id();
|
||||
let tree = git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
|
||||
let oid = {
|
||||
let commit = git::commit(
|
||||
&repo,
|
||||
&head,
|
||||
git::refname!("refs/heads/master").as_refstr(),
|
||||
"Second commit",
|
||||
&sig,
|
||||
&tree,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
commit.id()
|
||||
};
|
||||
repo.set_head("refs/heads/master").unwrap();
|
||||
repo.checkout_head(None).unwrap();
|
||||
|
||||
// Look, I don't really understand why we have to do this, but we do.
|
||||
drop(tree);
|
||||
drop(head);
|
||||
|
||||
(repo, oid)
|
||||
|
|
@ -99,18 +107,22 @@ pub mod gen {
|
|||
let repo = git2::Repository::init(path).unwrap();
|
||||
let sig = git2::Signature::now(string(6).as_str(), email().as_str()).unwrap();
|
||||
let head = git::initial_commit(&repo, &sig).unwrap();
|
||||
let tree =
|
||||
git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
|
||||
let oid = git::commit(
|
||||
&repo,
|
||||
&head,
|
||||
git::refname!("refs/heads/master").as_refstr(),
|
||||
string(16).as_str(),
|
||||
&sig,
|
||||
&tree,
|
||||
)
|
||||
.unwrap()
|
||||
.id();
|
||||
|
||||
// Look, I don't really understand why we have to do this, but we do.
|
||||
drop(head);
|
||||
drop(tree);
|
||||
|
||||
(repo, oid)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue