use crates::regex::Regex;
use impl_prelude::*;
#[derive(Debug, Default, Clone, Copy)]
pub struct LfsPointer;
impl LfsPointer {
pub fn new() -> Self {
Self {}
}
fn check_line(prev_key: Option<String>, key: &str, value: &str, content: &Content, name: &str)
-> CheckResult {
let mut result = CheckResult::new();
if let Some(old_key) = prev_key {
if old_key == "version" {
} else if key < old_key.as_str() {
result.add_error(format!("{}unsorted key `{}` found in LFS pointer `{}`.",
commit_prefix_str(content, "not allowed;"),
key,
name));
}
} else if key == "version" {
if value != GIT_LFS_SPEC_URL {
result.add_warning(format!("{}unexpected git-lfs version string in \
`{}`: `{}`.",
commit_prefix_str(content, "contains an"),
name,
value));
}
} else {
result.add_error(format!("{}the first key in LFS pointer `{}` must be \
`version`; found `{}`.",
commit_prefix_str(content, "not allowed;"),
name,
key));
}
match key {
"oid" => {
let mut split = value.splitn(2, ':');
let algo = split.next();
let digest = split.next();
if Some("sha256") != algo {
if digest.is_some() {
result.add_warning(format!("{}unrecognized hash algorithm `{}` in LFS \
pointer `{}`.",
commit_prefix_str(content, "contains an"),
algo.unwrap_or("<unknown>"),
name));
} else {
result.add_error(format!("{}missing hash algorithm in LFS pointer `{}`.",
commit_prefix_str(content, "not allowed;"),
name));
}
}
},
"size" => {
if value.parse::<u64>().is_err() {
result.add_error(format!("{}the first key in LFS pointer `{}` must be \
`version`; found `{}`.",
commit_prefix_str(content, "not allowed;"),
name,
key));
}
},
_ => (),
}
result
}
}
const GIT_LFS_SPEC_URL: &str = "https://git-lfs.github.com/spec/v1";
lazy_static! {
static ref LFS_LINE_RE: Regex =
Regex::new("^(?P<key>[a-z0-9.-]*) \
(?P<value>[^\r\n]+)$").unwrap();
}
impl ContentCheck for LfsPointer {
fn name(&self) -> &str {
"lfs-pointer"
}
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,
}
let filter_attr = ctx.check_attr("filter", diff.name.as_path())?;
if let AttributeState::Value(filter_name) = filter_attr {
if filter_name != "lfs" {
continue;
}
} else {
continue;
}
let cat_file = ctx.git()
.arg("cat-file")
.arg("blob")
.arg(diff.new_blob.as_str())
.output()
.chain_err(|| "failed to contruct cat-file command")?;
let lfs_pointer = if let Ok(lfs_pointer) = String::from_utf8(cat_file.stdout) {
lfs_pointer
} else {
result.add_error(format!("{}invalid utf-8 sequence in an LFS pointer added in \
`{}`.",
commit_prefix_str(content, "not allowed;"),
diff.name));
continue;
};
if lfs_pointer.is_empty() {
continue;
}
let (lfs_res, _, has_oid, has_size) = lfs_pointer.lines()
.enumerate()
.fold((CheckResult::new(), None, false, false), |data, (count, line)| {
let (mut lfs_res, prev_key, has_oid, has_size) = data;
if let Some(lfs_line) = LFS_LINE_RE.captures(line) {
let key = lfs_line.name("key")
.expect("the LFS regex should have a 'key' group");
let value = lfs_line.name("value")
.expect("the LFS regex should have a 'value' group");
if Some(key.as_str()) == prev_key.as_ref().map(String::as_str) {
lfs_res.add_error(format!("{}duplicate key `{}` in an LFS pointer \
added in `{}`.",
commit_prefix_str(content, "not allowed;"),
key.as_str(),
diff.name));
}
let line_res = Self::check_line(prev_key, key.as_str(), value.as_str(),
content, diff.name.as_str());
let new_has_oid = has_oid || key.as_str() == "oid";
let new_has_size = has_size || key.as_str() == "size";
(lfs_res.combine(line_res),
Some(key.as_str().to_string()),
new_has_oid,
new_has_size)
} else {
lfs_res.add_error(format!("{}invalid line in an LFS pointer \
added in `{}` on line {}.",
commit_prefix_str(content, "not allowed;"),
diff.name,
count + 1));
(lfs_res, prev_key, has_oid, has_size)
}
});
result = result.combine(lfs_res);
if !has_oid {
result.add_error(format!("{}an LFS pointer is missing the `oid` key in `{}`.",
commit_prefix_str(content, "not allowed;"),
diff.name));
}
if !has_size {
result.add_error(format!("{}an LFS pointer is missing the `size` key in `{}`.",
commit_prefix_str(content, "not allowed;"),
diff.name));
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use checks::LfsPointer;
use checks::test::*;
const LFS_INVALID_UTF8: &str = "c413d8954d903557de00be9d96b4daa264bd4b22";
const LFS_INVALID_LINE_FORMAT: &str = "709c2cd38863717231453bb4945897a0ebef3f80";
const LFS_MISSING_VERSION: &str = "0ec2ab0e229fdcbff18a8bfd55774e4f71b04bbb";
const LFS_MISPLACED_VERSION: &str = "c86f7b78fd6ca471d97f3861addf3a35c25f3504";
const LFS_INVALID_VERSION: &str = "f799c407f7678ba38f87720fb4217ece6c50f7e5";
const LFS_DUPLICATE_KEY: &str = "4d0ead1cac518874d1f7cd005569238b7b9bc7c3";
const LFS_MISSING_SIZE: &str = "e6f342338093141e365b1bb1596290c155198c60";
const LFS_MISSING_OID: &str = "ceeabe41a44a4469b6644017af725b0127d92302";
const LFS_UNRECOGNIZED_HASH: &str = "1aa2873adebe911caf594c376efc5c522f8d4c3a";
const LFS_MISSING_HASH: &str = "109806f98fd036cba88d07f94df162aad5086d0b";
const LFS_UNSORTED_KEYS: &str = "aaeedde2f188005c24e090bc44378b17770e3b94";
const LFS_EMPTY_POINTER: &str = "186cb934ce4b20f85ee6bfb834ab5652121e6436";
const LFS_VALID_POINTER: &str = "e088802e2abc6b7c2bb99e194bcc074a9cf9076c";
const LFS_INVALID_UTF8_FIXED: &str = "5997c958735f9d4eab0640fa54b4c094bd74e71b";
const LFS_INVALID_LINE_FORMAT_FIXED: &str = "501a70c9ff37a7ce9e7c853a737b93e3be6d73d1";
const LFS_MISSING_VERSION_FIXED: &str = "225108803b9a8679c48272dc83765ce5a6a281e1";
const LFS_MISPLACED_VERSION_FIXED: &str = "7f2c99eab3e1cb58e41bdfe0ae56dfa5c6529a26";
const LFS_INVALID_VERSION_FIXED: &str = "ce3f57bc200ebe52cce3b10580ce6adc76e830bd";
const LFS_DUPLICATE_KEY_FIXED: &str = "59dafc938b6fbf913624afd16b3732c79f88e4b5";
const LFS_MISSING_SIZE_FIXED: &str = "9f8196108b33fc91884be01b63dd82bb484b41be";
const LFS_MISSING_OID_FIXED: &str = "b88474b22be8e87d2276fb9e54bf30147638d9b2";
const LFS_UNRECOGNIZED_HASH_FIXED: &str = "68aaf7516f3f22705a87ad19799bd03a9c5ae999";
const LFS_MISSING_HASH_FIXED: &str = "31d34a4fb619de4e4877e426255bd206b4f579b9";
const LFS_UNSORTED_KEYS_FIXED: &str = "829033be4f407b6c9e472778364fbabb76431c2b";
#[test]
fn test_lfs_invalid_utf8() {
let check = LfsPointer;
let result = run_check("test_lfs_invalid_utf8", LFS_INVALID_UTF8, check);
test_result_errors(result, &[
"commit c413d8954d903557de00be9d96b4daa264bd4b22 not allowed; invalid utf-8 sequence \
in an LFS pointer added in `invalid-utf8.lfs`.",
]);
}
#[test]
fn test_lfs_invalid_utf8_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_invalid_utf8_topic", LFS_INVALID_UTF8, check);
test_result_errors(result, &[
"invalid utf-8 sequence in an LFS pointer added in `invalid-utf8.lfs`.",
]);
}
#[test]
fn test_lfs_invalid_utf8_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_invalid_utf8_topic_fixed", LFS_INVALID_UTF8_FIXED, check)
}
#[test]
fn test_lfs_invalid_line_format() {
let check = LfsPointer;
let result = run_check("test_lfs_invalid_line_format", LFS_INVALID_LINE_FORMAT, check);
test_result_errors(result, &[
"commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
pointer added in `invalid-line-format.lfs` on line 2.",
"commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
pointer added in `invalid-line-format.lfs` on line 3.",
"commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
pointer added in `invalid-line-format.lfs` on line 4.",
"commit 709c2cd38863717231453bb4945897a0ebef3f80 not allowed; invalid line in an LFS \
pointer added in `invalid-line-format.lfs` on line 8.",
]);
}
#[test]
fn test_lfs_invalid_line_format_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_invalid_line_format_topic", LFS_INVALID_LINE_FORMAT, check);
test_result_errors(result, &[
"invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 2.",
"invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 3.",
"invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 4.",
"invalid line in an LFS pointer added in `invalid-line-format.lfs` on line 8.",
]);
}
#[test]
fn test_lfs_invalid_line_format_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_invalid_line_format_topic_fixed", LFS_INVALID_LINE_FORMAT_FIXED, check)
}
#[test]
fn test_lfs_missing_version() {
let check = LfsPointer;
let result = run_check("test_lfs_missing_version", LFS_MISSING_VERSION, check);
test_result_errors(result, &[
"commit 0ec2ab0e229fdcbff18a8bfd55774e4f71b04bbb not allowed; the first key in LFS \
pointer `missing-version.lfs` must be `version`; found `oid`.",
]);
}
#[test]
fn test_lfs_missing_version_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_missing_version_topic", LFS_MISSING_VERSION, check);
test_result_errors(result, &[
"the first key in LFS pointer `missing-version.lfs` must be `version`; found `oid`.",
]);
}
#[test]
fn test_lfs_missing_version_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_missing_version_topic_fixed", LFS_MISSING_VERSION_FIXED, check)
}
#[test]
fn test_lfs_misplaced_version() {
let check = LfsPointer;
let result = run_check("test_lfs_misplaced_version", LFS_MISPLACED_VERSION, check);
test_result_errors(result, &[
"commit c86f7b78fd6ca471d97f3861addf3a35c25f3504 not allowed; the first key in LFS \
pointer `misplaced-version.lfs` must be `version`; found `oid`.",
]);
}
#[test]
fn test_lfs_misplaced_version_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_misplaced_version_topic", LFS_MISPLACED_VERSION, check);
test_result_errors(result, &[
"the first key in LFS pointer `misplaced-version.lfs` must be `version`; found `oid`.",
]);
}
#[test]
fn test_lfs_misplaced_version_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_misplaced_version_topic_fixed", LFS_MISPLACED_VERSION_FIXED, check)
}
#[test]
fn test_lfs_invalid_version() {
let check = LfsPointer;
let result = run_check("test_lfs_invalid_version", LFS_INVALID_VERSION, check);
test_result_warnings(result, &[
"commit f799c407f7678ba38f87720fb4217ece6c50f7e5 contains an unexpected git-lfs \
version string in `invalid-version.lfs`: `https://hawser.github.com/spec/v1`.",
]);
}
#[test]
fn test_lfs_invalid_version_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_invalid_version_topic", LFS_INVALID_VERSION, check);
test_result_warnings(result, &[
"unexpected git-lfs version string in `invalid-version.lfs`: \
`https://hawser.github.com/spec/v1`.",
]);
}
#[test]
fn test_lfs_invalid_version_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_invalid_version_topic_fixed", LFS_INVALID_VERSION_FIXED, check)
}
#[test]
fn test_lfs_duplicate_key() {
let check = LfsPointer;
let result = run_check("test_lfs_duplicate_key", LFS_DUPLICATE_KEY, check);
test_result_errors(result, &[
"commit 4d0ead1cac518874d1f7cd005569238b7b9bc7c3 not allowed; duplicate key \
`duplicate` in an LFS pointer added in `duplicate-key.lfs`.",
]);
}
#[test]
fn test_lfs_duplicate_key_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_duplicate_key_topic", LFS_DUPLICATE_KEY, check);
test_result_errors(result, &[
"duplicate key `duplicate` in an LFS pointer added in `duplicate-key.lfs`.",
]);
}
#[test]
fn test_lfs_duplicate_key_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_duplicate_key_topic_fixed", LFS_DUPLICATE_KEY_FIXED, check)
}
#[test]
fn test_lfs_missing_size() {
let check = LfsPointer;
let result = run_check("test_lfs_missing_size", LFS_MISSING_SIZE, check);
test_result_errors(result, &[
"commit e6f342338093141e365b1bb1596290c155198c60 not allowed; an LFS pointer is \
missing the `size` key in `missing-size.lfs`.",
]);
}
#[test]
fn test_lfs_missing_size_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_missing_size_topic", LFS_MISSING_SIZE, check);
test_result_errors(result, &[
"an LFS pointer is missing the `size` key in `missing-size.lfs`.",
]);
}
#[test]
fn test_lfs_missing_size_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_missing_size_topic_fixed", LFS_MISSING_SIZE_FIXED, check)
}
#[test]
fn test_lfs_missing_oid() {
let check = LfsPointer;
let result = run_check("test_lfs_missing_oid", LFS_MISSING_OID, check);
test_result_errors(result, &[
"commit ceeabe41a44a4469b6644017af725b0127d92302 not allowed; an LFS pointer is \
missing the `oid` key in `missing-oid.lfs`.",
]);
}
#[test]
fn test_lfs_missing_oid_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_missing_oid_topic", LFS_MISSING_OID, check);
test_result_errors(result, &[
"an LFS pointer is missing the `oid` key in `missing-oid.lfs`.",
]);
}
#[test]
fn test_lfs_missing_oid_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_missing_oid_topic_fixed", LFS_MISSING_OID_FIXED, check)
}
#[test]
fn test_lfs_unrecognized_hash() {
let check = LfsPointer;
let result = run_check("test_lfs_unrecognized_hash", LFS_UNRECOGNIZED_HASH, check);
test_result_warnings(result, &[
"commit 1aa2873adebe911caf594c376efc5c522f8d4c3a contains an unrecognized hash \
algorithm `md256` in LFS pointer `unrecognized-hash.lfs`.",
]);
}
#[test]
fn test_lfs_unrecognized_hash_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_unrecognized_hash_topic", LFS_UNRECOGNIZED_HASH, check);
test_result_warnings(result, &[
"unrecognized hash algorithm `md256` in LFS pointer `unrecognized-hash.lfs`.",
]);
}
#[test]
fn test_lfs_unrecognized_hash_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_unrecognized_hash_topic_fixed", LFS_UNRECOGNIZED_HASH_FIXED, check)
}
#[test]
fn test_lfs_missing_hash() {
let check = LfsPointer;
let result = run_check("test_lfs_missing_hash", LFS_MISSING_HASH, check);
test_result_errors(result, &[
"commit 109806f98fd036cba88d07f94df162aad5086d0b not allowed; missing hash algorithm \
in LFS pointer `missing-hash.lfs`.",
]);
}
#[test]
fn test_lfs_missing_hash_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_missing_hash_topic", LFS_MISSING_HASH, check);
test_result_errors(result, &[
"missing hash algorithm in LFS pointer `missing-hash.lfs`.",
]);
}
#[test]
fn test_lfs_missing_hash_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_missing_hash_topic_fixed", LFS_MISSING_HASH_FIXED, check)
}
#[test]
fn test_lfs_unsorted_keys() {
let check = LfsPointer;
let result = run_check("test_lfs_unsorted_keys", LFS_UNSORTED_KEYS, check);
test_result_errors(result, &[
"commit aaeedde2f188005c24e090bc44378b17770e3b94 not allowed; unsorted key `oid` \
found in LFS pointer `unsorted-keys.lfs`.",
]);
}
#[test]
fn test_lfs_unsorted_keys_topic() {
let check = LfsPointer;
let result = run_topic_check("test_lfs_unsorted_keys_topic", LFS_UNSORTED_KEYS, check);
test_result_errors(result, &[
"unsorted key `oid` found in LFS pointer `unsorted-keys.lfs`.",
]);
}
#[test]
fn test_lfs_unsorted_keys_topic_fixed() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_unsorted_keys_topic_fixed", LFS_UNSORTED_KEYS_FIXED, check)
}
#[test]
fn test_lfs_empty_pointer() {
let check = LfsPointer;
run_check_ok("test_lfs_empty_pointer", LFS_EMPTY_POINTER, check);
}
#[test]
fn test_lfs_empty_pointer_topic() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_empty_pointer_topic", LFS_EMPTY_POINTER, check)
}
#[test]
fn test_lfs_valid_pointer() {
let check = LfsPointer;
run_check_ok("test_lfs_valid_pointer", LFS_VALID_POINTER, check);
}
#[test]
fn test_lfs_valid_pointer_topic() {
let check = LfsPointer;
run_topic_check_ok("test_lfs_valid_pointer_topic", LFS_VALID_POINTER, check)
}
}