Skip to main content

gix/remote/url/
rewrite.rs

1use gix_features::threading::OwnShared;
2
3use crate::{
4    bstr::{BStr, BString, ByteVec},
5    config,
6    remote::Direction,
7};
8
9#[derive(Debug, Clone)]
10struct Replace {
11    find: BString,
12    with: OwnShared<BString>,
13}
14
15#[derive(Default, Debug, Clone)]
16pub(crate) struct Rewrite {
17    url_rewrite: Vec<Replace>,
18    push_url_rewrite: Vec<Replace>,
19}
20
21/// Init
22impl Rewrite {
23    pub fn from_config(
24        config: &gix_config::File<'static>,
25        mut filter: fn(&gix_config::file::Metadata) -> bool,
26    ) -> Rewrite {
27        config
28            .sections_by_name_and_filter("url", &mut filter)
29            .map(|sections| {
30                let mut url_rewrite = Vec::new();
31                let mut push_url_rewrite = Vec::new();
32                for section in sections {
33                    let replace = match section.header().subsection_name() {
34                        Some(base) => OwnShared::new(base.to_owned()),
35                        None => continue,
36                    };
37
38                    for instead_of in section.values(config::tree::Url::INSTEAD_OF.name) {
39                        url_rewrite.push(Replace {
40                            with: OwnShared::clone(&replace),
41                            find: instead_of.into_owned(),
42                        });
43                    }
44                    for instead_of in section.values(config::tree::Url::PUSH_INSTEAD_OF.name) {
45                        push_url_rewrite.push(Replace {
46                            with: OwnShared::clone(&replace),
47                            find: instead_of.into_owned(),
48                        });
49                    }
50                }
51                Rewrite {
52                    url_rewrite,
53                    push_url_rewrite,
54                }
55            })
56            .unwrap_or_default()
57    }
58}
59
60/// Access
61impl Rewrite {
62    fn replacements_for(&self, direction: Direction) -> &[Replace] {
63        match direction {
64            Direction::Fetch => &self.url_rewrite,
65            Direction::Push => &self.push_url_rewrite,
66        }
67    }
68
69    pub fn longest(&self, url: &gix_url::Url, direction: Direction) -> Option<BString> {
70        if self.replacements_for(direction).is_empty() {
71            None
72        } else {
73            let mut url = url.to_bstring();
74            self.rewrite_url_in_place(&mut url, direction).then_some(url)
75        }
76    }
77
78    /// Rewrite the given `url` of `direction` and return `true` if a replacement happened.
79    ///
80    /// Note that the result must still be checked for validity, it might not be a valid URL as we do a syntax-unaware replacement.
81    pub fn rewrite_url_in_place(&self, url: &mut BString, direction: Direction) -> bool {
82        self.replacements_for(direction)
83            .iter()
84            .fold(None::<(usize, &BStr)>, |mut acc, replace| {
85                if url.starts_with(replace.find.as_ref()) {
86                    let (bytes_matched, prev_rewrite_with) =
87                        acc.get_or_insert((replace.find.len(), replace.with.as_slice().into()));
88                    if *bytes_matched < replace.find.len() {
89                        *bytes_matched = replace.find.len();
90                        *prev_rewrite_with = replace.with.as_slice().into();
91                    }
92                }
93                acc
94            })
95            .map(|(bytes_matched, replace_with)| {
96                url.replace_range(..bytes_matched, replace_with);
97            })
98            .is_some()
99    }
100}