Skip to main content

git_checks/
reject_symlinks.rs

1// Copyright Kitware, Inc.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use derive_builder::Builder;
10use git_checks_core::impl_prelude::*;
11
12/// Rejects the addition of symlinks to a repository.
13#[derive(Builder, Debug, Default, Clone, Copy)]
14#[non_exhaustive]
15#[builder(field(private))]
16pub struct RejectSymlinks {}
17
18impl RejectSymlinks {
19    /// Create a new builder.
20    pub fn builder() -> RejectSymlinksBuilder {
21        Default::default()
22    }
23}
24
25impl ContentCheck for RejectSymlinks {
26    fn name(&self) -> &str {
27        "reject-symlinks"
28    }
29
30    fn check(
31        &self,
32        _: &CheckGitContext,
33        content: &dyn Content,
34    ) -> Result<CheckResult, Box<dyn Error>> {
35        let mut result = CheckResult::new();
36
37        for diff in content.diffs() {
38            match diff.status {
39                StatusChange::Added | StatusChange::Modified(_) => (),
40                _ => continue,
41            }
42
43            if diff.new_mode == "120000" {
44                result.add_error(format!(
45                    "{}adds a symlink at `{}` which is not allowed.",
46                    commit_prefix(content),
47                    diff.name,
48                ));
49            }
50        }
51
52        Ok(result)
53    }
54}
55
56#[cfg(feature = "config")]
57pub(crate) mod config {
58    use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck, TopicCheckConfig};
59    use serde::Deserialize;
60    #[cfg(test)]
61    use serde_json::json;
62
63    use crate::RejectSymlinks;
64
65    /// Configuration for the `RejectSymlinks` check.
66    ///
67    /// No configuration available.
68    ///
69    /// This check is registered as a commit check with the name `"reject_symlinks"` and a topic
70    /// check with the name `"reject_symlinks/topic"`.
71    #[derive(Deserialize, Debug)]
72    pub struct RejectSymlinksConfig {}
73
74    impl IntoCheck for RejectSymlinksConfig {
75        type Check = RejectSymlinks;
76
77        fn into_check(self) -> Self::Check {
78            Default::default()
79        }
80    }
81
82    register_checks! {
83        RejectSymlinksConfig {
84            "reject_symlinks" => CommitCheckConfig,
85            "reject_symlinks/topic" => TopicCheckConfig,
86        },
87    }
88
89    #[test]
90    fn test_reject_symlinks_config_empty() {
91        let json = json!({});
92        let check: RejectSymlinksConfig = serde_json::from_value(json).unwrap();
93
94        let _ = check.into_check();
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use git_checks_core::{Check, TopicCheck};
101
102    use crate::test::*;
103    use crate::RejectSymlinks;
104
105    const BAD_TOPIC: &str = "00ffdf352196c16a453970de022a8b4343610ccf";
106    const DELETE_TOPIC: &str = "59ebc7302ce05ec21e396e508be99f8dc2924c2d";
107    const FIX_TOPIC: &str = "d93ffc2e8b782ba8dce2278dd86fda0df80f454b";
108
109    #[test]
110    fn test_reject_symlinks_builder_default() {
111        assert!(RejectSymlinks::builder().build().is_ok());
112    }
113
114    #[test]
115    fn test_reject_symlinks_name_commit() {
116        let check = RejectSymlinks::default();
117        assert_eq!(Check::name(&check), "reject-symlinks");
118    }
119
120    #[test]
121    fn test_reject_symlinks_name_topic() {
122        let check = RejectSymlinks::default();
123        assert_eq!(TopicCheck::name(&check), "reject-symlinks");
124    }
125
126    #[test]
127    fn test_reject_symlinks() {
128        let check = RejectSymlinks::default();
129        let result = run_check("test_reject_symlinks", BAD_TOPIC, check);
130        test_result_errors(result, &[
131            "commit 00ffdf352196c16a453970de022a8b4343610ccf adds a symlink at `absolute-link` \
132             which is not allowed.",
133            "commit 00ffdf352196c16a453970de022a8b4343610ccf adds a symlink at `broken-link` \
134             which is not allowed.",
135            "commit 00ffdf352196c16a453970de022a8b4343610ccf adds a symlink at `inside-link` \
136             which is not allowed.",
137            "commit 00ffdf352196c16a453970de022a8b4343610ccf adds a symlink at `outside-link` \
138             which is not allowed.",
139        ]);
140    }
141
142    #[test]
143    fn test_reject_symlinks_topic() {
144        let check = RejectSymlinks::default();
145        let result = run_topic_check("test_reject_symlinks_topic", BAD_TOPIC, check);
146        test_result_errors(
147            result,
148            &[
149                "adds a symlink at `absolute-link` which is not allowed.",
150                "adds a symlink at `broken-link` which is not allowed.",
151                "adds a symlink at `inside-link` which is not allowed.",
152                "adds a symlink at `outside-link` which is not allowed.",
153            ],
154        );
155    }
156
157    #[test]
158    fn test_reject_symlinks_topic_fixed() {
159        let check = RejectSymlinks::default();
160        run_topic_check_ok("test_reject_symlinks_topic_fixed", FIX_TOPIC, check);
161    }
162
163    #[test]
164    fn test_reject_symlinks_delete_file() {
165        let check = RejectSymlinks::default();
166        let conf = make_check_conf(&check);
167
168        let result = test_check_base(
169            "test_reject_symlinks_delete_file",
170            DELETE_TOPIC,
171            BAD_TOPIC,
172            &conf,
173        );
174        test_result_ok(result);
175    }
176
177    #[test]
178    fn test_reject_symlinks_delete_file_topic() {
179        let check = RejectSymlinks::default();
180        run_topic_check_ok(
181            "test_reject_symlinks_delete_file_topic",
182            DELETE_TOPIC,
183            check,
184        );
185    }
186}