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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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.

extern crate git_workarea;
use self::git_workarea::CommitId;

use super::super::*;

use std::process::Command;

#[derive(Debug, Default, Clone, Copy)]
/// A check which checks for valid identities.
///
/// This check uses the `host` external binary to check the validity of domain names used in email
/// addresses.
pub struct ValidName;

fn check_name(name: &str) -> bool {
    name.find(' ').is_some()
}

fn check_email(email: &str) -> bool {
    let domain_part = email.splitn(2, '@')
        .skip(1)
        .next();

    if let Some(domain) = domain_part {
        let dig = Command::new("host")
            .arg("-t").arg("MX")
            .arg(format!("{}.", domain)) // Search for the absolute domain.
            .output();
        let dig_output = match dig {
            Ok(dig_output) => dig_output,
            Err(err) => {
                error!(target: "git-checks",
                       "failed to construct host command: {:?}",
                       err);

                return false;
            },
        };

        if !dig_output.status.success() {
            warn!(target: "git-checks",
                  "failed to look up MX record for domain {}: {}",
                  domain,
                  // The `host` tool always outputs to stdout
                  String::from_utf8_lossy(&dig_output.stdout));

            false
        } else {
            true
        }
    } else {
        false
    }
}

fn check_identity(what: &str, who: &str, identity: &Identity) -> CheckResult {
    let mut result = CheckResult::new();

    if !check_name(&identity.name) {
        result.add_error(format!("The {} name (`{}`) for {} has no space in it. \
                                  A full name is required for contribution. Please set the \
                                  `user.name` Git configuration value.",
                                 who,
                                 identity.name,
                                 what));
    }

    if !check_email(&identity.email) {
        result.add_error(format!("The {} email (`{}`) for {} has an unknown domain. Please set \
                                  the `user.email` Git configuration value.",
                                 who,
                                 identity.email,
                                 what));
    }

    result
}

impl ValidName {
    /// Create a new check to check identities.
    pub fn new() -> Self {
        ValidName {}
    }
}

impl Check for ValidName {
    fn name(&self) -> &str {
        "valid-name"
    }

    fn check(&self, _: &CheckGitContext, commit: &Commit) -> Result<CheckResult> {
        let what = format!("commit {}", commit.sha1_short);
        let author_res = check_identity(&what, "author", &commit.author);
        let commiter_res = check_identity(&what, "committer", &commit.committer);

        Ok(author_res.combine(commiter_res))
    }
}

impl BranchCheck for ValidName {
    fn name(&self) -> &str {
        "valid-name"
    }

    fn check(&self, ctx: &CheckGitContext, _: &CommitId) -> Result<CheckResult> {
        Ok(check_identity("the topic", "owner", ctx.topic_owner()))
    }
}

#[cfg(test)]
mod tests {
    use super::ValidName;
    use super::super::test::*;

    static BAD_TOPIC: &'static str = "dcd8895d299031d607481b4936478f8de4cc28ae";

    #[test]
    fn test_valid_name() {
        let check = ValidName::new();
        let mut conf = GitCheckConfiguration::new();

        conf.add_check(&check);

        let result = test_check("test_valid_name", BAD_TOPIC, &conf);

        assert_eq!(result.warnings().len(), 0);
        assert_eq!(result.alerts().len(), 0);
        assert_eq!(result.errors().len(), 6);
        assert_eq!(result.errors()[0],
                   "The committer email (`bademail@baddomain.invalid`) for commit dcd8895 has an \
                   unknown domain. Please set the `user.email` Git configuration value.");
        assert_eq!(result.errors()[1],
                   "The author email (`bademail@baddomain.invalid`) for commit 9002239 has an \
                   unknown domain. Please set the `user.email` Git configuration value.");
        assert_eq!(result.errors()[2],
                   "The committer email (`bademail`) for commit da71ae0 has an unknown domain. \
                    Please set the `user.email` Git configuration value.");
        assert_eq!(result.errors()[3],
                   "The committer name (`Mononym`) for commit 1debf17 has no space in it. A full \
                    name is required for contribution. Please set the `user.name` Git \
                    configuration value.");
        assert_eq!(result.errors()[4],
                   "The author email (`bademail`) for commit 9de4928 has an unknown domain. \
                    Please set the `user.email` Git configuration value.");
        assert_eq!(result.errors()[5],
                   "The author name (`Mononym`) for commit edac4e5 has no space in it. A full \
                    name is required for contribution. Please set the `user.name` Git \
                    configuration value.");
        assert_eq!(result.allowed(), false);
        assert_eq!(result.pass(), false);
    }
}