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
// 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 super::super::*;

#[derive(Debug, Default, Clone, Copy)]
/// Check that submodules are not rewound to older revisions.
pub struct SubmoduleRewind;

impl SubmoduleRewind {
    /// Checks that submodules in the project always move forward.
    pub fn new() -> Self {
        SubmoduleRewind
    }
}

impl Check for SubmoduleRewind {
    fn name(&self) -> &str {
        "submodule-rewind"
    }

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

        for diff in &commit.diffs {
            // Ignore diffs which are not submodules on the new side.
            if diff.new_mode != "160000" {
                continue;
            }

            match diff.status {
                // Ignore submodules which have not been modified.
                StatusChange::Deleted |
                StatusChange::Added => continue,
                _ => (),
            }

            let submodule_ctx = SubmoduleContext::new(ctx, diff.name.as_ref());
            if submodule_ctx.is_none() {
                continue;
            }
            let submodule_ctx = submodule_ctx.unwrap();

            let merge_base = try!(submodule_ctx.context
                .git()
                .arg("merge-base")
                .arg(diff.old_blob.as_str())
                .arg(diff.new_blob.as_str())
                .output()
                .chain_err(|| "failed to construct merge-base command"));
            if !merge_base.status.success() {
                bail!(ErrorKind::Git(format!("failed to get the merge base for the `{}` \
                                              submodule: {}",
                                             diff.name,
                                             String::from_utf8_lossy(&merge_base.stderr))));
            }
            let base = String::from_utf8_lossy(&merge_base.stdout);

            if base.trim() == diff.new_blob.as_str() {
                result.add_error(format!("commit {} is not allowed since it moves the submodule \
                                          `{}` backwards from {} to {}.",
                                         commit.sha1_short,
                                         submodule_ctx.path,
                                         diff.old_blob,
                                         diff.new_blob));
            }
        }

        Ok(result)
    }
}

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

    static MOVE_TOPIC: &'static str = "2088079e35503be3be41dbdca55080ced95614e1";
    static REWIND_TOPIC: &'static str = "39c5d0d9dc7ee6abad72cd42c90d7c1af1be169c";

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

        conf.add_check(&check);

        let result = test_check_submodule("test_submodule_rewind_ok", MOVE_TOPIC, &conf);

        assert_eq!(result.warnings().len(), 0);
        assert_eq!(result.alerts().len(), 0);
        assert_eq!(result.errors().len(), 0);
        assert_eq!(result.allowed(), false);
        assert_eq!(result.pass(), true);
    }

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

        conf.add_check(&check);

        let result = test_check_submodule_base("test_submodule_rewind_rewind",
                                               REWIND_TOPIC,
                                               MOVE_TOPIC,
                                               &conf);

        assert_eq!(result.warnings().len(), 0);
        assert_eq!(result.alerts().len(), 0);
        assert_eq!(result.errors().len(), 1);
        assert_eq!(result.errors()[0],
                   "commit 39c5d0d is not allowed since it moves the submodule `submodule` \
                    backwards from 8a890d8c4b89560c70a059bbdd7bc59b92b5c92b to \
                    2a8baa8e23bb1de5eec202dd4a29adf47feb03b1.");
        assert_eq!(result.allowed(), false);
        assert_eq!(result.pass(), false);
    }
}