swh_graph/stdlib/
root_directory.rs

1// Copyright (C) 2024  The Software Heritage developers
2// See the AUTHORS file at the top-level directory of this distribution
3// License: GNU General Public License version 3, or any later version
4// See top-level LICENSE file for more information
5
6use anyhow::{bail, ensure, Result};
7
8use crate::graph::*;
9use crate::properties;
10use crate::NodeType;
11
12/// Given a node id pointing to a revision or release, returns the node id of
13/// the associated topmost ("root") directory.
14///
15/// If the release points to a revision, this function recurses once through
16/// that revision.
17pub fn find_root_dir<G>(graph: &G, node: NodeId) -> Result<Option<NodeId>>
18where
19    G: SwhForwardGraph + SwhGraphWithProperties,
20    <G as SwhGraphWithProperties>::Maps: properties::Maps,
21{
22    match graph.properties().node_type(node) {
23        NodeType::Release => find_root_dir_from_rel(graph, node),
24        NodeType::Revision => find_root_dir_from_rev(graph, node),
25        ty => bail!("Expected node type release or revision, but got {ty} instead."),
26    }
27}
28
29fn find_root_dir_from_rel<G>(graph: &G, rel_id: NodeId) -> Result<Option<NodeId>>
30where
31    G: SwhForwardGraph + SwhGraphWithProperties,
32    <G as SwhGraphWithProperties>::Maps: properties::Maps,
33{
34    let props = graph.properties();
35    let rel_swhid = props.swhid(rel_id);
36
37    let mut root_dir = None;
38    let mut root_rev = None;
39    for succ in graph.successors(rel_id) {
40        let node_type = props.node_type(succ);
41        match node_type {
42            NodeType::Directory => {
43                ensure!(
44                    root_dir.is_none(),
45                    "{rel_swhid} has more than one directory successor",
46                );
47                root_dir = Some(succ);
48            }
49            NodeType::Revision => {
50                ensure!(
51                    root_rev.is_none(),
52                    "{rel_swhid} has more than one revision successor",
53                );
54                root_rev = Some(succ);
55            }
56            _ => (),
57        }
58    }
59
60    match (root_dir, root_rev) {
61        (Some(_), Some(_)) => {
62            bail!("{rel_swhid} has both a directory and a revision as successors",)
63        }
64        (None, Some(root_rev)) => {
65            let mut root_dir = None;
66            for succ in graph.successors(root_rev) {
67                if graph.properties().node_type(succ) == NodeType::Directory {
68                    let rev_swhid = graph.properties().swhid(succ);
69                    ensure!(
70                        root_dir.is_none(),
71                        "{rel_swhid} (via {rev_swhid}) has more than one directory successor",
72                    );
73                    root_dir = Some(succ);
74                }
75            }
76            Ok(root_dir)
77        }
78        (Some(root_dir), None) => Ok(Some(root_dir)),
79        (None, None) => Ok(None),
80    }
81}
82
83fn find_root_dir_from_rev<G>(graph: &G, rev_id: NodeId) -> Result<Option<NodeId>>
84where
85    G: SwhForwardGraph + SwhGraphWithProperties,
86    <G as SwhGraphWithProperties>::Maps: properties::Maps,
87{
88    let mut root_dir = None;
89    let props = graph.properties();
90    for succ in graph.successors(rev_id) {
91        let node_type = props.node_type(succ);
92        if node_type == NodeType::Directory {
93            let rev_swhid = props.swhid(succ);
94            ensure!(
95                root_dir.is_none(),
96                "{rev_swhid} has more than one directory successor",
97            );
98            root_dir = Some(succ);
99        }
100    }
101
102    Ok(root_dir)
103}