1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
mod error {
    use crate::handshake::refs::parse;

    /// The error returned by [ls_refs()][crate::ls_refs()].
    #[derive(Debug, thiserror::Error)]
    #[allow(missing_docs)]
    pub enum Error {
        #[error(transparent)]
        Io(#[from] std::io::Error),
        #[error(transparent)]
        Transport(#[from] git_transport::client::Error),
        #[error(transparent)]
        Parse(#[from] parse::Error),
    }

    impl git_transport::IsSpuriousError for Error {
        fn is_spurious(&self) -> bool {
            match self {
                Error::Io(err) => err.is_spurious(),
                Error::Transport(err) => err.is_spurious(),
                _ => false,
            }
        }
    }
}
pub use error::Error;

/// What to do after preparing ls-refs in [ls_refs()][crate::ls_refs()].
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum Action {
    /// Continue by sending a 'ls-refs' command.
    Continue,
    /// Skip 'ls-refs' entirely.
    ///
    /// This is useful if the `ref-in-want` capability is taken advantage of. When fetching, one must must then send
    /// `want-ref`s during the negotiation phase.
    Skip,
}

pub(crate) mod function {
    use std::borrow::Cow;

    use bstr::BString;
    use git_features::progress::Progress;
    use git_transport::client::{Capabilities, Transport, TransportV2Ext};
    use maybe_async::maybe_async;

    use super::{Action, Error};
    use crate::{
        handshake::{refs::from_v2_refs, Ref},
        indicate_end_of_interaction, Command,
    };

    /// Invoke an ls-refs V2 command on `transport`, which requires a prior handshake that yielded
    /// server `capabilities`. `prepare_ls_refs(capabilities, arguments, features)` can be used to alter the _ls-refs_. `progress` is used to provide feedback.
    /// Note that `prepare_ls_refs()` is expected to add the `(agent, Some(name))` to the list of `features`.
    #[maybe_async]
    pub async fn ls_refs(
        mut transport: impl Transport,
        capabilities: &Capabilities,
        prepare_ls_refs: impl FnOnce(
            &Capabilities,
            &mut Vec<BString>,
            &mut Vec<(&str, Option<Cow<'static, str>>)>,
        ) -> std::io::Result<Action>,
        progress: &mut impl Progress,
    ) -> Result<Vec<Ref>, Error> {
        let ls_refs = Command::LsRefs;
        let mut ls_features = ls_refs.default_features(git_transport::Protocol::V2, capabilities);
        let mut ls_args = ls_refs.initial_arguments(&ls_features);
        if capabilities
            .capability("ls-refs")
            .and_then(|cap| cap.supports("unborn"))
            .unwrap_or_default()
        {
            ls_args.push("unborn".into());
        }
        let refs = match prepare_ls_refs(capabilities, &mut ls_args, &mut ls_features) {
            Ok(Action::Skip) => Vec::new(),
            Ok(Action::Continue) => {
                ls_refs.validate_argument_prefixes_or_panic(
                    git_transport::Protocol::V2,
                    capabilities,
                    &ls_args,
                    &ls_features,
                );

                progress.step();
                progress.set_name("list refs");
                let mut remote_refs = transport
                    .invoke(
                        ls_refs.as_str(),
                        ls_features.into_iter(),
                        if ls_args.is_empty() {
                            None
                        } else {
                            Some(ls_args.into_iter())
                        },
                    )
                    .await?;
                from_v2_refs(&mut remote_refs).await?
            }
            Err(err) => {
                indicate_end_of_interaction(transport).await?;
                return Err(err.into());
            }
        };
        Ok(refs)
    }
}