diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a58e39b5f0f8..ec7a8ccae982 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,7 +13,12 @@ By opening a pull request you confirm that, unless explicitly stated otherwise, - are all your own work, and you have the right to contribute them. - are contributed solely under the terms and conditions of the Apache License 2.0 (see section 5 of the license for more information). -Please make sure (eg. `git log`) that all commits have a valid name and email address for you in the Author field. +### LLMs, Commit messages and PR description: + + - Please make sure (eg. `git log`) that all commits have a valid name and email address for you in the Author field. + - LLM assisted commits should be attributed with an `Assisted-by: MODEL_NAME MODEL_VERSION` line appended to the commit message. + - Please mention coding assistance in the PR description too (eg. by adding the same `Assisted-by` line from above) + - Please describe the changes in your own words - we'd like to know you understand the changes being made! If you're a first time contributor, see the Contributing guidelines for more information. diff --git a/.github/scripts/CommitHeaderChecker.java b/.github/scripts/CommitHeaderChecker.java index ae0e95e3ca12..74ca2ba21dc5 100644 --- a/.github/scripts/CommitHeaderChecker.java +++ b/.github/scripts/CommitHeaderChecker.java @@ -28,9 +28,10 @@ import static java.util.stream.Gatherers.fold; import static java.util.stream.Gatherers.scan; -import static java.util.stream.Gatherers.windowSliding; -record Commit(int index, String from, String date, String subject, String blank) {} +static final Pattern robots = Pattern.compile("(gpt|claude|gemini|llama|mistral|command|grok|qwen|deepseek)"); + +record Commit(int index, String from, String date, String subject, List msg) {} record Result(int total, boolean green) {} // checks commit headers for valid author, email and commit msg formatting @@ -66,14 +67,32 @@ void main(String[] args) throws IOException, InterruptedException { .followRedirects(Redirect.NORMAL).build()) { result = client.send(request, BodyHandlers.ofLines()).body() - // 5 line window, From/Date/Subject and extra line for blank line / overflow check - // "From" can be two lines if the name is very long - .gather(windowSliding(5)) + // gather commit header + message as List of lines + .gather( + Gatherer., List>ofSequential( + ArrayList::new, + (window, line, downstream) -> { + if (line.startsWith("From: ")) { // window start + window.add(line); + } else if (!window.isEmpty()) { + if (line.equals("---")) { // window end (separator after message) + downstream.push(List.copyOf(window)); + window.clear(); + } else { + window.add(line); + } + } + return true; + } + ) + ) .filter(w -> isCommitHeader(w)) + // map to indexed commits .gather(scan( - () -> new Commit(-1, "", "", "", ""), + () -> new Commit(-1, "", "", "", List.of()), (c, w) -> createCommit(c.index+1, w))) .peek(System.out::println) + // check commits and store as result .gather(fold( () -> new Result(0, true), (r, c) -> new Result(r.total+1, r.green & checkCommit(c)))) @@ -90,7 +109,7 @@ void main(String[] args) throws IOException, InterruptedException { // Subject: [PATCH] Mail Validator private static boolean isCommitHeader(List lines) { int i = 0; - return lines.size() == 5 + return lines.size() >= 4 && lines.get(i++).startsWith("From: ") // "From" can be two lines in some cases &&(lines.get(i++).startsWith("Date: ") || lines.get(i++).startsWith("Date: ")) && lines.get(i++).startsWith("Subject: "); @@ -99,14 +118,15 @@ private static boolean isCommitHeader(List lines) { private static Commit createCommit(int index, List lines) { int i = 0; return lines.get(1).startsWith("Date: ") // "From" can be two lines in some cases - ? new Commit(index, lines.get(i++), lines.get(i++), lines.get(i++), lines.get(i++)) - : new Commit(index, lines.get(i++) + lines.get(i++), lines.get(i++), lines.get(i++), lines.get(i++)); + ? new Commit(index, lines.get(i++), lines.get(i++), lines.get(i++), lines.subList(i, lines.size())) + : new Commit(index, lines.get(i++) + lines.get(i++), lines.get(i++), lines.get(i++), lines.subList(i, lines.size())); } boolean checkCommit(Commit c) { return checkNameAndEmail(c.index, c.from) & checkSubject(c.index, c.subject) - & checkBlankLineAfterSubject(c.index, c.blank); + & checkBlankLineAfterSubject(c.index, c.msg) + & checkHumanCoAuthors(c.index, c.msg); } boolean checkNameAndEmail(int i, String from) { @@ -152,7 +172,7 @@ boolean checkSubject(int i, String subject) { } // there should be a blank line after the subject line, some subjects can overflow though. -boolean checkBlankLineAfterSubject(int i, String blank) { +boolean checkBlankLineAfterSubject(int i, List msg) { // disabled since this would produce too many warnings due to overflowing subject lines // if (!blank.isBlank()) { // log("::warning::blank line after subject recommended in commit " + i + " (is subject over 50 char limit?)"); @@ -161,6 +181,17 @@ boolean checkBlankLineAfterSubject(int i, String blank) { return true; } +boolean checkHumanCoAuthors(int i, List msg) { + for (String line : msg) { + String lower = line.toLowerCase(Locale.ROOT); + if ((lower.startsWith("co-authored-by:") || lower.startsWith("generated-by:")) && robots.matcher(lower).find()) { + log("::error::please use 'Assisted-by: MODEL_NAME MODEL_VERSION' in commit " + i); + return false; + } + } + return true; +} + void log(String msg) { System.out.println(msg); }