#!/usr/bin/awk # Scan a patch for correctness, according to XFS/iomap rules. # # Pass the commit id as the variable "commit_id" and the git log --oneline # output as "oneline" for report generation. function getval(key) { realkey = sprintf("%s: ", key) keylen = length(realkey) if (substr($0, 0, keylen) != realkey) return 0 return substr($0, keylen + 1) } function is_me(val, my_signature, dead_signature) { if (val == my_signature) return 1 if (dead_signature != 0 && val == dead_signature) return 1 return 0 } function commit_error(why) { if (!commit_errored) printf("%s\n", oneline) > "/dev/stderr" printf(" - %s\n", why) > "/dev/stderr" commit_errored = 1 } function commit_error_end(why) { if (commit_errored) printf("\n") > "/dev/stderr" } function prog_error(why) { printf("%s: %s\n", commit_id, why) > "/dev/stderr" } BEGIN { ret = 0 commit_errored = 0 if (length(commit_id) == 0) { printf("Commit id not supplied.") > "/dev/stderr" ret = 2 exit } if (length(oneline) == 0) { prog_error("Commit oneline not supplied.") ret = 2 exit } if (("git config user.name" | getline name) < 0) { prog_error("%s: Can't get git user.name.") ret = 2 exit } if (("git config user.email" | getline email) < 0) { prog_error("%s: Can't get git user.email.") ret = 2 exit } if (("git config core.abbrev" | getline abbrev_len) < 0) { prog_error("%s: Can't get git core.abbrev.") ret = 2 exit } if (abbrev_len <= 0) abbrev_len = 12 ("git config user.deadname" | getline dead_name) ("git config user.deademail" | getline dead_email) if (length(dead_name) > 0 && length(dead_email) == 0) { dead_email = email } if (length(dead_email) > 0 && length(dead_name) == 0) { dead_name = name } if (("git config --type bool user.devtree" | getline dev_tree) < 0) { dev_tree = "false" } dev_tree = (dev_tree == "true") my_signature = sprintf("%s <%s>", name, email) if (length(dead_email) > 0) dead_signature = sprintf("%s <%s>", dead_name, dead_email) else dead_signature = 0; you_are_author = 0 has_other_review = 0 has_self_review = 0 has_self_signoff = 0 has_committer_signoff = 0 bad_commit = 0 in_header = 1 in_body = 0 subject = 0 committer_signature = 0 committer_name = 0 author_signature = 0 author_name = 0 merge = 0 } { # Trim leading and trailing whitespace so that we can pick up tags that # are in the commit message. gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0) if (debug) printf(":%s:%s:%s:\n", in_header, $1, $0) if (in_header && $0 == "") { in_header = 0 in_body = 1 } else if (in_body && subject == 0) { # Pick up the subject line for error reporting subject = $0 if (debug) printf("subj:%s:%s:\n", $0, subject) } else if ((val = getval("Merge")) != 0) { # We don't process merge commits merge = 1 if (debug) printf("Merge:%s:%s:\n", $0, val); nextfile } else if ((val = getval("Author")) != 0) { # Pick up the author header author_signature = val author_name = gensub(/^[[:space:]]*(.+) <.*$/, "\\1", "g", author_signature); if (debug) printf("author:%s:%s:\n", val, author_signature); if (is_me(val, my_signature, dead_signature)) you_are_author = 1 } else if ((val = getval("Commit")) != 0) { # Pick up the committer header committer_signature = val; committer_name = gensub(/^[[:space:]]*(.+) <.*$/, "\\1", "g", committer_signature); if (debug) printf("committer:%s:%s:\n", val, committer_name); } else if ((val = getval("Signed-off-by")) != 0) { # Pick up signoffs sob_name = gensub(/^[[:space:]]*(.+) <.*$/, "\\1", "g", val); if (is_me(val, my_signature, dead_signature)) has_self_signoff = 1 if (val == author_signature || sob_name == author_name) has_author_signoff = 1 if (val == committer_signature || sob_name == committer_name) has_committer_signoff = 1 } else if ((val = getval("Reviewed-by")) != 0) { # Pick up reviews if (is_me(val, my_signature, dead_signature)) has_self_review = 1 else has_other_review = 1 } else if ((val = getval("Fixes")) != 0) { # Fixes tags must be of a certain length and exist in the git # history. split(val, cols) fixes_id = cols[1] if (debug) printf("fixes:%s:\n", fixes_id) if (length(fixes_id) < abbrev_len) { msg = sprintf("Fixes tag commit \"%s\" needs to be longer.", fixes_id) commit_error(msg) bad_commit = 1 } cmd = sprintf("git log -n 1 \"%s\" >/dev/null 2>&1", fixes_id) if (system(cmd) != 0) { msg = sprintf("Fixes tag commit \"%s\" does not exist.", fixes_id) commit_error(msg) bad_commit = 1 } } else if ($0 ~ /scanned for virus detection/) { commit_error("Email virus scanner garbage in commit message.") bad_commit = 1 } } END { if (ret != 0 || merge != 0) exit ret if (subject == 0) { commit_error("Patch subject not found.") ret = 1 } if (bad_commit) ret = 1 if (you_are_author) { # If you wrote the patch, you must have your own signoff and a # review by someone else (if this isn't your development tree). # You can't review your own patches. if (!has_self_signoff) { commit_error("Your patch needs your signoff.") ret = 1 } if (!has_other_review && !dev_tree) { commit_error("Your patch needs review by someone else.") ret = 1 } if (has_self_review) { commit_error("You cannot review your own patch.") ret = 1 } } else { # If you did not write the patch, it must have a signoff from # the author, a review by you, and a signoff by you unless the # author is the committer. if (!has_author_signoff) { commit_error("Patch needs to be signed off by the author.") ret = 1 } if (!has_self_review) { commit_error("Patch needs your review.") ret = 1 } if (!has_committer_signoff && !has_self_signoff) { commit_error("Patch needs your (or the committer's) signoff.") has_committer_signoff = 1 ret = 1 } } # All commits must have a signoff from the committer. if (!has_committer_signoff) { commit_error("This patch needs to be signed off by the committer.") ret = 1 } commit_error_end() exit ret }