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