summaryrefslogtreecommitdiffstats
path: root/vendor/gix/src/remote/url/rewrite.rs
blob: ae0eee4267d96870e8bb18a2675ea0f70a8fb115 (plain)
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
use gix_features::threading::OwnShared;

use crate::{
    bstr::{BStr, BString, ByteVec},
    config,
    remote::Direction,
};

#[derive(Debug, Clone)]
struct Replace {
    find: BString,
    with: OwnShared<BString>,
}

#[derive(Default, Debug, Clone)]
pub(crate) struct Rewrite {
    url_rewrite: Vec<Replace>,
    push_url_rewrite: Vec<Replace>,
}

/// Init
impl Rewrite {
    pub fn from_config(
        config: &gix_config::File<'static>,
        mut filter: fn(&gix_config::file::Metadata) -> bool,
    ) -> Rewrite {
        config
            .sections_by_name_and_filter("url", &mut filter)
            .map(|sections| {
                let mut url_rewrite = Vec::new();
                let mut push_url_rewrite = Vec::new();
                for section in sections {
                    let replace = match section.header().subsection_name() {
                        Some(base) => OwnShared::new(base.to_owned()),
                        None => continue,
                    };

                    for instead_of in section.values(config::tree::Url::INSTEAD_OF.name) {
                        url_rewrite.push(Replace {
                            with: OwnShared::clone(&replace),
                            find: instead_of.into_owned(),
                        });
                    }
                    for instead_of in section.values(config::tree::Url::PUSH_INSTEAD_OF.name) {
                        push_url_rewrite.push(Replace {
                            with: OwnShared::clone(&replace),
                            find: instead_of.into_owned(),
                        });
                    }
                }
                Rewrite {
                    url_rewrite,
                    push_url_rewrite,
                }
            })
            .unwrap_or_default()
    }
}

/// Access
impl Rewrite {
    fn replacements_for(&self, direction: Direction) -> &[Replace] {
        match direction {
            Direction::Fetch => &self.url_rewrite,
            Direction::Push => &self.push_url_rewrite,
        }
    }

    pub fn longest(&self, url: &gix_url::Url, direction: Direction) -> Option<BString> {
        if self.replacements_for(direction).is_empty() {
            None
        } else {
            let mut url = url.to_bstring();
            self.rewrite_url_in_place(&mut url, direction).then_some(url)
        }
    }

    /// Rewrite the given `url` of `direction` and return `true` if a replacement happened.
    ///
    /// Note that the result must still be checked for validity, it might not be a valid URL as we do a syntax-unaware replacement.
    pub fn rewrite_url_in_place(&self, url: &mut BString, direction: Direction) -> bool {
        self.replacements_for(direction)
            .iter()
            .fold(None::<(usize, &BStr)>, |mut acc, replace| {
                if url.starts_with(replace.find.as_ref()) {
                    let (bytes_matched, prev_rewrite_with) =
                        acc.get_or_insert((replace.find.len(), replace.with.as_slice().into()));
                    if *bytes_matched < replace.find.len() {
                        *bytes_matched = replace.find.len();
                        *prev_rewrite_with = replace.with.as_slice().into();
                    }
                };
                acc
            })
            .map(|(bytes_matched, replace_with)| {
                url.replace_range(..bytes_matched, replace_with);
            })
            .is_some()
    }
}