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:
parent
9950182ee0
commit
87d1cb50a0
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
@ -2317,6 +2317,51 @@ fn git_push_and_fetch() {
|
||||||
.unwrap();
|
.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]
|
#[test]
|
||||||
fn rad_workflow() {
|
fn rad_workflow() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use radicle::git::{Namespaced, Oid, Qualified};
|
use radicle::git::{self, Namespaced, Oid, Qualified};
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
use super::refs::{Applied, Policy, RefUpdate, Update};
|
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)
|
.map_err(error::Contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ancestry(repo: &Repository, old: Oid, new: Oid) -> Result<Ancestry, error::Ancestry> {
|
/// Find the object identified by `oid` and peel it to its associated
|
||||||
if !contains(repo, old)? || !contains(repo, new)? {
|
/// commit `Oid`.
|
||||||
return Err(error::Ancestry::Missing { a: old, b: new });
|
///
|
||||||
|
/// # 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 {
|
if old == new {
|
||||||
return Ok(Ancestry::Equal);
|
return Ok(Ancestry::Equal);
|
||||||
|
|
@ -49,7 +68,7 @@ pub fn ancestry(repo: &Repository, old: Oid, new: Oid) -> Result<Ancestry, error
|
||||||
|
|
||||||
let (ahead, behind) = repo
|
let (ahead, behind) = repo
|
||||||
.backend
|
.backend
|
||||||
.graph_ahead_behind(new.into(), old.into())
|
.graph_ahead_behind(*new, *old)
|
||||||
.map_err(|err| error::Ancestry::Check { old, new, err })?;
|
.map_err(|err| error::Ancestry::Check { old, new, err })?;
|
||||||
|
|
||||||
if ahead > 0 && behind == 0 {
|
if ahead > 0 && behind == 0 {
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,27 @@ pub struct Contains(#[source] pub raw::Error);
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Ancestry {
|
pub enum Ancestry {
|
||||||
#[error("missing one of {a} or {b} while checking ancestry")]
|
#[error("missing {oid} while checking ancestry")]
|
||||||
Missing { a: Oid, b: Oid },
|
Missing { oid: Oid },
|
||||||
#[error(transparent)]
|
#[error("failed to check ancestry for {old} and {new}: {err}")]
|
||||||
Contains(#[from] Contains),
|
|
||||||
#[error("failed to check ancestry for {old} and {new}")]
|
|
||||||
Check {
|
Check {
|
||||||
old: Oid,
|
old: Oid,
|
||||||
new: Oid,
|
new: Oid,
|
||||||
#[source]
|
#[source]
|
||||||
err: raw::Error,
|
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)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue