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 <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2024-02-21 10:46:34 +00:00 committed by cloudhead
parent 9950182ee0
commit 87d1cb50a0
No known key found for this signature in database
4 changed files with 154 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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