Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
fetch: allow fetch of annotated tags
Merged fintohaps opened 2 years ago

If an ancestry check of an annotated tag was triggered then the fetch from that peer would fail. This is due to graph_ahead_behind only working for commit-ish objects.

To allow for annotated tags, and other object that peel to commits, the check now peels the two objects to commits before getting their Oids. The call to graph_ahead_behind can take these two Oids instead.

A test for annotated tags is included as part of the examples/git suite in the CLI tests.

4 files changed +154 -10 d1e2e3b6 87d1cb50
added radicle-cli/examples/git/git-tag.md
@@ -0,0 +1,70 @@
+
Alice creates an annotated tag and pushed to her `rad` remote:
+

+
``` ~alice
+
$ touch LICENSE
+
$ git add LICENSE
+
$ git commit -am "Add LICENSE"
+
[master 62d19fd] Add LICENSE
+
 1 file changed, 0 insertions(+), 0 deletions(-)
+
 create mode 100644 LICENSE
+
$ git tag v1.0 -a -m "Release v1.0"
+
```
+

+
``` ~alice (stderr)
+
$ git push rad v1.0 --tags
+
✓ Synced with 1 node(s)
+
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
 * [new tag]         v1.0 -> v1.0
+
```
+

+
Bob fetches the tag from Alice, by adding her as a remote:
+

+
``` ~bob
+
$ cd heartwood
+
$ rad remote add z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
+
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
+
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
+
✓ Remote alice added
+
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
+
```
+

+
Bob is able to fetch Alice's tag into his working copy by using the
+
`--tags` flag:
+

+
``` ~bob (stderr)
+
$ git fetch alice --tags
+
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
 * [new tag]         v1.0       -> v1.0
+
```
+

+
Alice forcefully creates a new version of the tag (let's say she made
+
a mistake):
+

+
``` ~alice
+
$ git commit --allow-empty -m "Release: v1.0"
+
[master 8260c04] Release: v1.0
+
$ git tag v1.0 -f -a -m "Release v1.0"
+
Updated tag 'v1.0' (was be18ed6)
+
```
+

+
``` ~alice (stderr)
+
$ git push rad v1.0 -f
+
✓ Synced with 1 node(s)
+
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
 + be18ed6...9dbdebc v1.0 -> v1.0 (forced update)
+
```
+

+
We ensure that Bob is still able to fetch from Alice and get the new
+
update of the tag:
+

+
``` ~bob
+
$ rad sync -f
+
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
+
✓ Fetched repository from 1 seed(s)
+
```
+

+
``` ~bob (stderr)
+
$ git fetch alice --tags -f
+
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
 t [tag update]      v1.0       -> v1.0
+
```
modified radicle-cli/tests/commands.rs
@@ -2263,6 +2263,51 @@ fn git_push_and_fetch() {
}

#[test]
+
fn git_tag() {
+
    let mut environment = Environment::new();
+
    let alice = environment.node(Config::test(Alias::new("alice")));
+
    let bob = environment.node(Config::test(Alias::new("bob")));
+
    let working = environment.tmp().join("working");
+

+
    fixtures::repository(working.join("alice"));
+

+
    test(
+
        "examples/rad-init.md",
+
        working.join("alice"),
+
        Some(&alice.home),
+
        [],
+
    )
+
    .unwrap();
+

+
    let alice = alice.spawn();
+
    let mut bob = bob.spawn();
+

+
    bob.connect(&alice).converge([&alice]);
+

+
    test(
+
        "examples/rad-clone.md",
+
        &working.join("bob"),
+
        Some(&bob.home),
+
        [],
+
    )
+
    .unwrap();
+
    formula(&environment.tmp(), "examples/git/git-tag.md")
+
        .unwrap()
+
        .home(
+
            "alice",
+
            working.join("alice"),
+
            [("RAD_HOME", alice.home.path().display())],
+
        )
+
        .home(
+
            "bob",
+
            working.join("bob"),
+
            [("RAD_HOME", bob.home.path().display())],
+
        )
+
        .run()
+
        .unwrap();
+
}
+

+
#[test]
fn rad_workflow() {
    let mut environment = Environment::new();
    let alice = environment.node(Config::test(Alias::new("alice")));
modified radicle-fetch/src/git/repository.rs
@@ -1,7 +1,7 @@
pub mod error;

use either::Either;
-
use radicle::git::{Namespaced, Oid, Qualified};
+
use radicle::git::{self, Namespaced, Oid, Qualified};
use radicle::storage::git::Repository;

use super::refs::{Applied, Policy, RefUpdate, Update};
@@ -38,10 +38,29 @@ pub fn contains(repo: &Repository, oid: Oid) -> Result<bool, error::Contains> {
        .map_err(error::Contains)
}

-
pub fn ancestry(repo: &Repository, old: Oid, new: Oid) -> Result<Ancestry, error::Ancestry> {
-
    if !contains(repo, old)? || !contains(repo, new)? {
-
        return Err(error::Ancestry::Missing { a: old, b: new });
+
/// Find the object identified by `oid` and peel it to its associated
+
/// commit `Oid`.
+
///
+
/// # Errors
+
///
+
/// - The object was not found
+
/// - The object does not peel to a commit
+
/// - Attempting to find the object fails
+
fn find_and_peel(repo: &Repository, oid: Oid) -> Result<Oid, error::Ancestry> {
+
    match repo.backend.find_object(*oid, None) {
+
        Ok(object) => Ok(object
+
            .peel(git::raw::ObjectType::Commit)
+
            .map_err(|err| error::Ancestry::Peel { oid, err })?
+
            .id()
+
            .into()),
+
        Err(e) if git::is_not_found_err(&e) => Err(error::Ancestry::Missing { oid }),
+
        Err(err) => Err(error::Ancestry::Object { oid, err }),
    }
+
}
+

+
pub fn ancestry(repo: &Repository, old: Oid, new: Oid) -> Result<Ancestry, error::Ancestry> {
+
    let old = find_and_peel(repo, old)?;
+
    let new = find_and_peel(repo, new)?;

    if old == new {
        return Ok(Ancestry::Equal);
@@ -49,7 +68,7 @@ pub fn ancestry(repo: &Repository, old: Oid, new: Oid) -> Result<Ancestry, error

    let (ahead, behind) = repo
        .backend
-
        .graph_ahead_behind(new.into(), old.into())
+
        .graph_ahead_behind(*new, *old)
        .map_err(|err| error::Ancestry::Check { old, new, err })?;

    if ahead > 0 && behind == 0 {
modified radicle-fetch/src/git/repository/error.rs
@@ -7,17 +7,27 @@ pub struct Contains(#[source] pub raw::Error);

#[derive(Debug, Error)]
pub enum Ancestry {
-
    #[error("missing one of {a} or {b} while checking ancestry")]
-
    Missing { a: Oid, b: Oid },
-
    #[error(transparent)]
-
    Contains(#[from] Contains),
-
    #[error("failed to check ancestry for {old} and {new}")]
+
    #[error("missing {oid} while checking ancestry")]
+
    Missing { oid: Oid },
+
    #[error("failed to check ancestry for {old} and {new}: {err}")]
    Check {
        old: Oid,
        new: Oid,
        #[source]
        err: raw::Error,
    },
+
    #[error("failed to peel object to commit {oid}: {err}")]
+
    Peel {
+
        oid: Oid,
+
        #[source]
+
        err: raw::Error,
+
    },
+
    #[error("failed to find object {oid}: {err}")]
+
    Object {
+
        oid: Oid,
+
        #[source]
+
        err: raw::Error,
+
    },
}

#[derive(Debug, Error)]