git-checks 3.5.2

Checks to run against a topic in git to enforce coding standards.
Documentation
// 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.

use crates::regex::Regex;

use impl_prelude::*;

#[derive(Debug, Default, Clone, Copy)]
/// A check which denies paths which look like merge conflict resolution paths.
///
/// Sometimes after a merge, the files written to assist in resolving the conflict will be added
/// accidentally.
pub struct RejectConflictPaths;

lazy_static! {
    static ref CONFLICT_FILE_PATH: Regex =
        Regex::new("^(?P<base>.*)\
                    (?P<kind>_(BACKUP|BASE|LOCAL|REMOTE))\
                    (?P<pid>_[0-9]+)\
                    (?P<ext>(\\..*)?)$").unwrap();
}
const ORIG_SUFFIX: &str = ".orig";

impl RejectConflictPaths {
    /// Create a new check to reject merge conflict resolution paths.
    pub fn new() -> Self {
        Self {}
    }

    fn check_conflict_path_name(ctx: &CheckGitContext, path: &str) -> Result<bool> {
        if let Some(file_path) = CONFLICT_FILE_PATH.captures(path) {
            let base = file_path.name("base")
                .expect("the conflict file path regex should have a 'base' group");
            let ext = file_path.name("ext")
                .expect("the conflict file path regex should have a 'ext' group");

            let basepath = format!("{}{}", base.as_str(), ext.as_str());
            Self::check_for_path(ctx, &basepath)
        } else {
            Ok(false)
        }
    }

    fn check_orig_path_name(ctx: &CheckGitContext, path: &str) -> Result<bool> {
        if path.ends_with(ORIG_SUFFIX) {
            let basepath = path.trim_right_matches(ORIG_SUFFIX);
            Self::check_for_path(ctx, basepath)
        } else {
            Ok(false)
        }
    }

    fn check_for_path(ctx: &CheckGitContext, path: &str) -> Result<bool> {
        let cat_file = ctx.git()
            .arg("cat-file")
            .arg("-e")
            .arg(format!(":{}", path))
            .output()
            .chain_err(|| "failed to contruct cat-file command")?;
        Ok(cat_file.status.success())
    }
}

impl ContentCheck for RejectConflictPaths {
    fn name(&self) -> &str {
        "reject-conflict-paths"
    }

    fn check(&self, ctx: &CheckGitContext, content: &Content) -> Result<CheckResult> {
        let mut result = CheckResult::new();

        for diff in content.diffs() {
            match diff.status {
                StatusChange::Added |
                StatusChange::Modified(_) => (),
                _ => continue,
            }

            if Self::check_conflict_path_name(ctx, diff.name.as_str())? {
                result.add_error(format!("{}it appears as though `{}` is a merge conflict \
                                          resolution file and cannot be added.",
                                         commit_prefix_str(content, "not allowed;"),
                                         diff.name));
            }

            if Self::check_orig_path_name(ctx, diff.name.as_str())? {
                result.add_error(format!("{}it appears as though `{}` is a merge conflict \
                                          backup file and cannot be added.",
                                         commit_prefix_str(content, "not allowed;"),
                                         diff.name));
            }
        }

        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    use checks::RejectConflictPaths;
    use checks::test::*;

    const MERGE_CONFLICT_NO_BASE: &str = "52710df4a433731545ef99440edeb431b3160fc6";
    const MERGE_CONFLICT_ORIG_NO_BASE: &str = "672a6a32b045bec6f93b9b1b5252611f40437a07";

    const MERGE_CONFLICT_NO_EXT: &str = "f31b2e2cb75083d174097db7054cbc9e5836bad7";
    const MERGE_CONFLICT_WITH_EXT: &str = "59f02bdb6c404c8f7cfb32700645c149148c089b";
    const MERGE_CONFLICT_TWO_EXT: &str = "e9421eadfcac3c67a090444ef2ac859e86a8a2e0";
    const MERGE_CONFLICT_ORIG_EXT: &str = "4abcf323b81c757e668ce1936f475b085d6852e8";

    const MERGE_CONFLICT_NO_EXT_FIXED: &str = "63c923b5be729f672eddcd4b1e979a7d3d22606f";
    const MERGE_CONFLICT_WITH_EXT_FIXED: &str = "8fcdd0d920ef47d0294229fdad4b3abd3ebdc43b";
    const MERGE_CONFLICT_TWO_EXT_FIXED: &str = "0bf291f1f8a320abfb63776c56e1d1497231e24e";
    const MERGE_CONFLICT_ORIG_EXT_FIXED: &str = "e1fd80490cd7f6556120aad6309d8fb95818b6ef";

    #[test]
    fn test_reject_conflict_paths_no_base() {
        let check = RejectConflictPaths;
        run_check_ok("test_reject_conflict_paths_no_base", MERGE_CONFLICT_NO_BASE, check);
    }

    #[test]
    fn test_reject_conflict_paths_no_base_topic() {
        let check = RejectConflictPaths;
        run_topic_check_ok("test_reject_conflict_paths_no_base_topic",
                           MERGE_CONFLICT_NO_BASE,
                           check);
    }

    #[test]
    fn test_reject_conflict_paths_orig_no_base() {
        let check = RejectConflictPaths;
        run_check_ok("test_reject_conflict_paths_orig_no_base", MERGE_CONFLICT_ORIG_NO_BASE, check);
    }

    #[test]
    fn test_reject_conflict_paths_orig_no_base_topic() {
        let check = RejectConflictPaths;
        run_topic_check_ok("test_reject_conflict_paths_orig_no_base_topic",
                           MERGE_CONFLICT_ORIG_NO_BASE,
                           check);
    }

    #[test]
    fn test_reject_conflict_paths_no_ext() {
        let check = RejectConflictPaths;
        let result = run_check("test_reject_conflict_paths_no_ext", MERGE_CONFLICT_NO_EXT, check);
        test_result_errors(result, &[
            "commit f31b2e2cb75083d174097db7054cbc9e5836bad7 not allowed; it appears as though \
             `no_ext_BACKUP_12345` is a merge conflict resolution file and cannot be added.",
            "commit f31b2e2cb75083d174097db7054cbc9e5836bad7 not allowed; it appears as though \
             `no_ext_BASE_12345` is a merge conflict resolution file and cannot be added.",
            "commit f31b2e2cb75083d174097db7054cbc9e5836bad7 not allowed; it appears as though \
             `no_ext_LOCAL_12345` is a merge conflict resolution file and cannot be added.",
            "commit f31b2e2cb75083d174097db7054cbc9e5836bad7 not allowed; it appears as though \
             `no_ext_REMOTE_12345` is a merge conflict resolution file and cannot be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_no_ext_topic() {
        let check = RejectConflictPaths;
        let result = run_topic_check("test_reject_conflict_paths_no_ext_topic", MERGE_CONFLICT_NO_EXT, check);
        test_result_errors(result, &[
            "it appears as though `no_ext_BACKUP_12345` is a merge conflict resolution file and \
             cannot be added.",
            "it appears as though `no_ext_BASE_12345` is a merge conflict resolution file and \
             cannot be added.",
            "it appears as though `no_ext_LOCAL_12345` is a merge conflict resolution file and \
             cannot be added.",
            "it appears as though `no_ext_REMOTE_12345` is a merge conflict resolution file and \
             cannot be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_no_ext_topic_fixed() {
        let check = RejectConflictPaths;
        run_topic_check_ok("test_reject_conflict_paths_no_ext_topic_fixed",
                           MERGE_CONFLICT_NO_EXT_FIXED,
                           check);
    }

    #[test]
    fn test_reject_conflict_paths_with_ext() {
        let check = RejectConflictPaths;
        let result = run_check("test_reject_conflict_paths_with_ext", MERGE_CONFLICT_WITH_EXT, check);
        test_result_errors(result, &[
            "commit 59f02bdb6c404c8f7cfb32700645c149148c089b not allowed; it appears as though \
             `conflict_with_BACKUP_12345.ext` is a merge conflict resolution file and cannot be \
             added.",
            "commit 59f02bdb6c404c8f7cfb32700645c149148c089b not allowed; it appears as though \
             `conflict_with_BASE_12345.ext` is a merge conflict resolution file and cannot be \
             added.",
            "commit 59f02bdb6c404c8f7cfb32700645c149148c089b not allowed; it appears as though \
             `conflict_with_LOCAL_12345.ext` is a merge conflict resolution file and cannot be \
             added.",
            "commit 59f02bdb6c404c8f7cfb32700645c149148c089b not allowed; it appears as though \
             `conflict_with_REMOTE_12345.ext` is a merge conflict resolution file and cannot be \
             added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_with_ext_topic() {
        let check = RejectConflictPaths;
        let result = run_topic_check("test_reject_conflict_paths_with_ext_topic", MERGE_CONFLICT_WITH_EXT, check);
        test_result_errors(result, &[
            "it appears as though `conflict_with_BACKUP_12345.ext` is a merge conflict resolution \
             file and cannot be added.",
            "it appears as though `conflict_with_BASE_12345.ext` is a merge conflict resolution \
             file and cannot be added.",
            "it appears as though `conflict_with_LOCAL_12345.ext` is a merge conflict resolution \
             file and cannot be added.",
            "it appears as though `conflict_with_REMOTE_12345.ext` is a merge conflict resolution \
             file and cannot be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_with_ext_topic_fixed() {
        let check = RejectConflictPaths;
        run_topic_check_ok("test_reject_conflict_paths_with_ext_topic_fixed",
                           MERGE_CONFLICT_WITH_EXT_FIXED,
                           check);
    }

    #[test]
    fn test_reject_conflict_paths_two_ext() {
        let check = RejectConflictPaths;
        let result = run_check("test_reject_conflict_paths_two_ext", MERGE_CONFLICT_TWO_EXT, check);
        test_result_errors(result, &[
            "commit e9421eadfcac3c67a090444ef2ac859e86a8a2e0 not allowed; it appears as though \
             `conflict_with.two_BACKUP_12345.ext` is a merge conflict resolution file and cannot \
             be added.",
            "commit e9421eadfcac3c67a090444ef2ac859e86a8a2e0 not allowed; it appears as though \
             `conflict_with.two_BASE_12345.ext` is a merge conflict resolution file and cannot be \
             added.",
            "commit e9421eadfcac3c67a090444ef2ac859e86a8a2e0 not allowed; it appears as though \
             `conflict_with.two_LOCAL_12345.ext` is a merge conflict resolution file and cannot be \
             added.",
            "commit e9421eadfcac3c67a090444ef2ac859e86a8a2e0 not allowed; it appears as though \
             `conflict_with.two_REMOTE_12345.ext` is a merge conflict resolution file and cannot \
             be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_two_ext_topic() {
        let check = RejectConflictPaths;
        let result = run_topic_check("test_reject_conflict_paths_two_ext_topic", MERGE_CONFLICT_TWO_EXT, check);
        test_result_errors(result, &[
            "it appears as though `conflict_with.two_BACKUP_12345.ext` is a merge conflict \
             resolution file and cannot be added.",
            "it appears as though `conflict_with.two_BASE_12345.ext` is a merge conflict \
             resolution file and cannot be added.",
            "it appears as though `conflict_with.two_LOCAL_12345.ext` is a merge conflict \
             resolution file and cannot be added.",
            "it appears as though `conflict_with.two_REMOTE_12345.ext` is a merge conflict \
             resolution file and cannot be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_two_ext_topic_fixed() {
        let check = RejectConflictPaths;
        run_topic_check_ok("test_reject_conflict_paths_two_ext_topic_fixed",
                           MERGE_CONFLICT_TWO_EXT_FIXED,
                           check);
    }

    #[test]
    fn test_reject_conflict_paths_orig_ext() {
        let check = RejectConflictPaths;
        let result = run_check("test_reject_conflict_paths_orig_ext", MERGE_CONFLICT_ORIG_EXT, check);
        test_result_errors(result, &[
            "commit 4abcf323b81c757e668ce1936f475b085d6852e8 not allowed; it appears as though \
             `orig_file.ext.orig` is a merge conflict backup file and cannot be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_orig_ext_topic() {
        let check = RejectConflictPaths;
        let result = run_topic_check("test_reject_conflict_paths_orig_ext_topic", MERGE_CONFLICT_ORIG_EXT, check);
        test_result_errors(result, &[
            "it appears as though `orig_file.ext.orig` is a merge conflict backup file and cannot \
             be added.",
        ]);
    }

    #[test]
    fn test_reject_conflict_paths_orig_ext_topic_fixed() {
        let check = RejectConflictPaths;
        run_topic_check_ok("test_reject_conflict_paths_orig_ext_topic_fixed",
                           MERGE_CONFLICT_ORIG_EXT_FIXED,
                           check);
    }
}