swh-graph-stdlib 13.0.0

Library of algorithms and data structures for swh-graph
Documentation
// Copyright (C) 2026  The Software Heritage developers
// See the AUTHORS file at the top-level directory of this distribution
// License: GNU General Public License version 3, or any later version
// See top-level LICENSE file for more information

use anyhow::Result;
use swh_graph::graph_builder::GraphBuilder;
use swh_graph::labels::{Visit, VisitStatus};
use swh_graph::swhid;
use swh_graph::views::Transposed;
use swh_graph_stdlib::{peel, peel_once, peel_until};

#[test]
fn test_peel_rev_to_dir() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000000))?
        .done();
    let dir1 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000001))?
        .done();
    let dir2 = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000002))?
        .done();
    builder.arc(rev, dir1);
    builder.arc(dir1, dir2);
    let graph = builder.done()?;

    assert_eq!(peel_once(&graph, rev)?, Some(dir1));
    assert_eq!(peel_until(&graph, rev, "dir".parse().unwrap())?, Some(dir1));
    assert_eq!(peel(&graph, rev)?, Some(dir1));

    Ok(())
}

#[test]
fn test_peel_rel_to_rev() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rel = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000000))?
        .done();
    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))?
        .done();
    let rev2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000002))?
        .done();
    builder.arc(rel, rev1);
    builder.arc(rev1, rev2);
    let graph = builder.done()?;

    assert_eq!(peel_once(&graph, rel,)?, Some(rev1));
    assert_eq!(peel_until(&graph, rel, "rev".parse().unwrap())?, Some(rev1));
    assert_eq!(peel(&graph, rel)?, Some(rev1));

    Ok(())
}

#[test]
fn test_peel_rel_chain_to_dir() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rel0 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000000))?
        .done();
    let rel1 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000001))?
        .done();
    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000002))?
        .done();
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000003))?
        .done();
    builder.arc(rel0, rel1);
    builder.arc(rel1, rev);
    builder.arc(rev, dir);
    let graph = builder.done()?;

    // Peel once
    assert_eq!(peel_once(&graph, rel0)?, Some(rel1));

    // Peel with specific target
    assert_eq!(peel_until(&graph, rel0, "dir".parse().unwrap())?, Some(dir));
    assert_eq!(peel_until(&graph, rel0, "rev".parse().unwrap())?, Some(rev));

    // Peel recursively until type change
    assert_eq!(peel(&graph, rel0)?, Some(rev));

    Ok(())
}

#[test]
fn test_peel_snp_to_rev() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let snp = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000000))?
        .done();
    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))?
        .done();
    let rev2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000002))?
        .done();
    builder.arc(snp, rev1);
    builder.arc(rev1, rev2);
    builder.snp_arc(snp, rev1, "refs/heads/main");
    let graph = builder.done()?;

    assert_eq!(peel_once(&graph, snp)?, Some(rev1));
    assert_eq!(peel_until(&graph, snp, "rev".parse().unwrap())?, Some(rev1));
    assert_eq!(peel(&graph, snp)?, Some(rev1));

    Ok(())
}

#[test]
fn test_peel_ori_to_snp() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let ori = builder
        .node(swhid!(swh:1:ori:0000000000000000000000000000000000000000))?
        .done();
    let snp = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000001))?
        .done();
    let visit = Visit::new(VisitStatus::Full, 1719568024).unwrap();
    builder.ori_arc(ori, snp, visit.status(), visit.timestamp());
    let graph = builder.done()?;

    assert_eq!(peel_once(&graph, ori)?, Some(snp));
    assert_eq!(peel_until(&graph, ori, "snp".parse().unwrap())?, Some(snp));
    assert_eq!(peel(&graph, ori)?, Some(snp));

    Ok(())
}

#[test]
fn test_peel_self() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000000))?
        .done();
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000001))?
        .done();
    builder.arc(rev, dir);
    let graph = builder.done()?;

    // Peeling a node to its own type returns itself
    assert_eq!(peel_until(&graph, rev, "rev".parse().unwrap())?, Some(rev));

    Ok(())
}

#[test]
fn test_dont_peel_dir_or_cnt() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let cnt = builder
        .node(swhid!(swh:1:cnt:0000000000000000000000000000000000000000))?
        .done();
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000001))?
        .done();
    builder.arc(dir, cnt);
    let graph = builder.done()?;

    // Content cannot be peeled
    assert!(peel_once(&graph, cnt).is_err());
    assert!(peel_until(&graph, cnt, "dir".parse().unwrap()).is_err());
    assert!(peel(&graph, cnt).is_err());

    // Directory cannot be peeled either
    assert!(peel_once(&graph, dir).is_err());
    assert!(peel(&graph, dir).is_err());

    Ok(())
}

#[test]
fn test_peel_get_stuck() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rel = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000000))?
        .done();
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000001))?
        .done();
    builder.arc(rel, dir);
    let graph = builder.done()?;

    // Trying to peel a release to a revision, but it points to a directory
    assert!(peel_until(&graph, rel, "rev".parse().unwrap()).is_err());

    Ok(())
}

#[test]
fn test_peel_no_successor() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rev0 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000000))?
        .done();
    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))?
        .done();
    // rev0 -> rev1 (parent rev, no directory successor)
    builder.arc(rev0, rev1);
    builder.arc(rev1, rev0);
    let graph = builder.done()?;

    // Revision with no directory successor
    assert_eq!(peel_once(&graph, rev0)?, None);
    assert_eq!(peel_until(&graph, rev0, "dir".parse().unwrap())?, None);
    assert_eq!(peel(&graph, rev0)?, None);

    Ok(())
}

#[test]
fn test_cannot_peel_on_transposed() -> Result<()> {
    let mut builder = GraphBuilder::default();
    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000000))?
        .done();
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000001))?
        .done();
    builder.arc(rev, dir);
    let graph = builder.done()?;
    let graph = Transposed(&graph);

    assert!(peel_until(&graph, rev, "dir".parse().unwrap()).is_err());

    Ok(())
}