bad_commits.rs 6.46 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2016 Kitware, Inc.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

9
use crates::git_checks_core::impl_prelude::*;
10

11
#[derive(Debug)]
12 13
/// Check for commits which should not be in the history.
pub struct BadCommits {
14
    /// The set of bad commits to deny.
15
    bad_commits: Vec<CommitId>,
16 17 18
}

impl BadCommits {
19
    /// Create a new check which checks for and denies branches with the given bad commits.
20 21 22 23
    pub fn new<I>(bad_commits: I) -> Self
        where I: IntoIterator,
              I::Item: ToString,
    {
24
        Self {
25 26 27
            bad_commits: bad_commits.into_iter()
                .map(|s| CommitId::new(s.to_string()))
                .collect(),
28 29 30 31
        }
    }
}

32
impl Check for BadCommits {
33 34 35 36
    fn name(&self) -> &str {
        "bad-commits"
    }

Ben Boeckel's avatar
Ben Boeckel committed
37
    fn check(&self, _: &CheckGitContext, commit: &Commit) -> Result<CheckResult> {
38
        let mut result = CheckResult::new();
39

40
        if self.bad_commits.contains(&commit.sha1) {
41
            result.add_error(format!("commit {} is a known-bad commit that was removed from the \
42
                                      server.",
43 44
                                     commit.sha1))
                .add_alert(format!("commit {} was pushed to the server.", commit.sha1),
45
                           true);
46 47
        }

48
        Ok(result)
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
impl TopicCheck for BadCommits {
    fn name(&self) -> &str {
        "bad-commits-topic"
    }

    fn check(&self, ctx: &CheckGitContext, topic: &Topic) -> Result<CheckResult> {
        let rev_list = ctx.git()
            .arg("rev-list")
            .arg("--reverse")
            .arg("--topo-order")
            .arg(&topic.sha1.as_str())
            .arg(&format!("^{}", topic.base))
            .output()
            .chain_err(|| "failed to construct rev-list command")?;
        if !rev_list.status.success() {
            bail!(ErrorKind::Git(format!("failed to list all topic refs: {}",
                                         String::from_utf8_lossy(&rev_list.stderr))));
        }

        let refs = String::from_utf8_lossy(&rev_list.stdout);

        Ok(refs.lines()
           .map(CommitId::new)
           .fold(CheckResult::new(), |mut result, commit| {
                if self.bad_commits.contains(&commit) {
                    result.add_error(format!("commit {} is a known-bad commit that was removed from the \
                                              server.",
                                             commit))
                        .add_alert(format!("commit {} was pushed to the server.", commit),
                                   true);
                }

                result
           }))
    }
}

89 90
#[cfg(test)]
mod tests {
91 92
    use BadCommits;
    use test::*;
93

94 95 96
    const GOOD_COMMIT: &str = "7b0c51ed98a23a32718ed7014d6d4a813423f1bd";
    const BAD_COMMIT: &str = "029a00428913ee915ce5ee7250c023abfbc2aca3";
    const BAD_TOPIC: &str = "3d535904b40868dcba6465cf2c3ce4358501880a";
97

98 99
    #[test]
    fn test_bad_commits_good_commit() {
100
        let check = BadCommits::new(&[
Ben Boeckel's avatar
Ben Boeckel committed
101 102
            BAD_COMMIT,
        ]);
103
        run_check_ok("test_bad_commits_good_commit", GOOD_COMMIT, check);
104 105
    }

106 107
    #[test]
    fn test_bad_commits_no_bad_commit() {
108
        let check = BadCommits::new(&[
Ben Boeckel's avatar
Ben Boeckel committed
109 110 111
            // This commit should never exist.
            "0000000000000000000000000000000000000000",
        ]);
112
        run_check_ok("test_bad_commits_no_bad_commit", BAD_TOPIC, check);
113 114 115 116
    }

    #[test]
    fn test_bad_commits_already_in_history() {
117
        let check = BadCommits::new(&[
Ben Boeckel's avatar
Ben Boeckel committed
118 119 120
            // This commit is in the shared history.
            FILLER_COMMIT,
        ]);
121
        run_check_ok("test_bad_commits_already_in_history", BAD_TOPIC, check);
122 123 124 125
    }

    #[test]
    fn test_bad_commits_not_already_in_history() {
126
        let check = BadCommits::new(&[
Ben Boeckel's avatar
Ben Boeckel committed
127 128 129
            // This commit is on the branch being brought in.
            BAD_COMMIT,
        ]);
130
        let result = run_check("test_bad_commits_not_already_in_history", BAD_TOPIC, check);
131 132 133 134

        assert_eq!(result.warnings().len(), 0);
        assert_eq!(result.alerts().len(), 1);
        assert_eq!(result.alerts()[0],
135
                   "commit 029a00428913ee915ce5ee7250c023abfbc2aca3 was pushed to the server.");
136 137
        assert_eq!(result.errors().len(), 1);
        assert_eq!(result.errors()[0],
138 139
                   "commit 029a00428913ee915ce5ee7250c023abfbc2aca3 is a known-bad commit that \
                    was removed from the server.");
140
        assert_eq!(result.temporary(), false);
141 142 143
        assert_eq!(result.allowed(), false);
        assert_eq!(result.pass(), false);
    }
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

    #[test]
    fn test_bad_commits_topic_good_commit() {
        let check = BadCommits::new(&[
            BAD_COMMIT,
        ]);
        run_topic_check_ok("test_bad_commits_topic_good_commit", GOOD_COMMIT, check);
    }

    #[test]
    fn test_bad_commits_topic_no_bad_commit() {
        let check = BadCommits::new(&[
            // This commit should never exist.
            "0000000000000000000000000000000000000000",
        ]);
        run_topic_check_ok("test_bad_commits_topic_no_bad_commit", BAD_TOPIC, check);
    }

    #[test]
    fn test_bad_commits_topic_already_in_history() {
        let check = BadCommits::new(&[
            // This commit is in the shared history.
            FILLER_COMMIT,
        ]);
        run_topic_check_ok("test_bad_commits_topic_already_in_history", BAD_TOPIC, check);
    }

    #[test]
    fn test_bad_commits_topic_not_already_in_history() {
        let check = BadCommits::new(&[
            // This commit is on the topic being brought in.
            BAD_COMMIT,
        ]);
        let result = run_topic_check("test_bad_commits_topic_not_already_in_history", BAD_TOPIC, check);

        assert_eq!(result.warnings().len(), 0);
        assert_eq!(result.alerts().len(), 1);
        assert_eq!(result.alerts()[0],
                   "commit 029a00428913ee915ce5ee7250c023abfbc2aca3 was pushed to the server.");
        assert_eq!(result.errors().len(), 1);
        assert_eq!(result.errors()[0],
                   "commit 029a00428913ee915ce5ee7250c023abfbc2aca3 is a known-bad commit that \
                    was removed from the server.");
        assert_eq!(result.temporary(), false);
        assert_eq!(result.allowed(), false);
        assert_eq!(result.pass(), false);
    }
191
}