cli: Get `rad-checkout` working properly

This commit is contained in:
Alexis Sellier 2023-01-24 12:51:41 +01:00
parent 1f06f5340f
commit bb135b64b3
No known key found for this signature in database
10 changed files with 213 additions and 43 deletions

View File

@ -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
```

View File

@ -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
```

View File

@ -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) <- z6MknSLStBU8Vi/flux-capacitor-power (9dad201)
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master (f2de534) <- z6MknSLStBU8Vi/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

View File

@ -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(())
}

View File

@ -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();

View File

@ -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)

View File

@ -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`.

View File

@ -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)

View File

@ -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();

View File

@ -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)
}