From 87d1cb50a0cb59cc0428425d1afb0ccfd889b36e Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Wed, 21 Feb 2024 10:46:34 +0000 Subject: [PATCH] fetch: allow fetch of annotated tags 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 `Oid`s. The call to `graph_ahead_behind` can take these two `Oid`s instead. A test for annotated tags is included as part of the `examples/git` suite in the CLI tests. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-cli/examples/git/git-tag.md | 70 +++++++++++++++++++++++ radicle-cli/tests/commands.rs | 45 +++++++++++++++ radicle-fetch/src/git/repository.rs | 29 ++++++++-- radicle-fetch/src/git/repository/error.rs | 20 +++++-- 4 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 radicle-cli/examples/git/git-tag.md diff --git a/radicle-cli/examples/git/git-tag.md b/radicle-cli/examples/git/git-tag.md new file mode 100644 index 00000000..f9b4dd79 --- /dev/null +++ b/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 +``` diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index dd0af808..80bb85f3 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -2317,6 +2317,51 @@ fn git_push_and_fetch() { .unwrap(); } +#[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(); diff --git a/radicle-fetch/src/git/repository.rs b/radicle-fetch/src/git/repository.rs index a219a820..5d57ae22 100644 --- a/radicle-fetch/src/git/repository.rs +++ b/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 { .map_err(error::Contains) } -pub fn ancestry(repo: &Repository, old: Oid, new: Oid) -> Result { - 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 { + 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 { + 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 0 && behind == 0 { diff --git a/radicle-fetch/src/git/repository/error.rs b/radicle-fetch/src/git/repository/error.rs index 12525ce3..6c2d546c 100644 --- a/radicle-fetch/src/git/repository/error.rs +++ b/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)]