Received: by 2002:a05:6a10:9848:0:0:0:0 with SMTP id x8csp4471398pxf; Tue, 16 Mar 2021 14:34:10 -0700 (PDT) X-Google-Smtp-Source: ABdhPJys8G9FNkdMRdpV69D9aoyWKoqXponq3z5adq5mrN7AHQ2IWDNuKP49czfw6PoR5a6za3kR X-Received: by 2002:a17:906:b288:: with SMTP id q8mr31810072ejz.210.1615930450176; Tue, 16 Mar 2021 14:34:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1615930450; cv=none; d=google.com; s=arc-20160816; b=0dM4gMN3igG8Rj8iG88sVItJpj4clrtRypHta1TlWew6hXcg/tcSpMPdVeijQFhMV+ FL1POs5IkfhyKqgUA3AzKsRgQbzcU+X4s0kQCN8YHknWmCU8jzDA17dnbcg8vS23uYr2 FozKOWrsFuRuJ7saIbPzhx2mDB/D4tpuSRSXjwslvK4R4ltrvXV5q3J+DPGWPc/Wl5x0 iUgnuGumql/POx0U/kGUqVf0Dv2SdXvHC/KVBmwznTRm4jAPbSgUVc68dlw2pKwgkw9d YZBnDZX49525o+y+XjM9qmRm38s2cbsf/pqEYuvFtay81kPaRyFn0UUvHosFsHjr6VHd 9eaQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:from:subject:mime-version:message-id:date :dkim-signature; bh=9qIl2h84+tEpR/qPDqw7Ia65+Rx0tWJMRUJLpgSeqnM=; b=LKF41+6JmjniBMDvp0Awb3cJcnvxS/Hddk7sQ+yG7U3KkqMTB6TcXPSLgZWKMxJ/dw XzTfwJaZmWQbIYS3ptoF3x03vULP3J6oTA5/wiaO98NE8Dga3ykr5mot1WEGEwYfzC9j XvmxDjagcxUNJ9ZqEb14eJ+EqgAp7kr3fvcvBAorojIWsWYm4CC69rGoaOX4v41tmmmw P9r9CODvT6bVfmuYE1CxNF03N0CRV5+BQ8oGpfGXk3tmlz4PVBAFCe7F/sU59JePJ0pu qbtAQdq31trg46D7TZ0zcmU8Y06qlrD3//sn+RQ4YxnybPig37lka9ta7gTwLgGXuc0e I3NA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=VKidfvf+; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id f7si13989229edu.503.2021.03.16.14.33.46; Tue, 16 Mar 2021 14:34:10 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=VKidfvf+; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229681AbhCPVcU (ORCPT + 99 others); Tue, 16 Mar 2021 17:32:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58996 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229490AbhCPVbs (ORCPT ); Tue, 16 Mar 2021 17:31:48 -0400 Received: from mail-qv1-xf49.google.com (mail-qv1-xf49.google.com [IPv6:2607:f8b0:4864:20::f49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A0F41C06174A for ; Tue, 16 Mar 2021 14:31:47 -0700 (PDT) Received: by mail-qv1-xf49.google.com with SMTP id j3so26336515qvo.1 for ; Tue, 16 Mar 2021 14:31:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:message-id:mime-version:subject:from:to:cc; bh=9qIl2h84+tEpR/qPDqw7Ia65+Rx0tWJMRUJLpgSeqnM=; b=VKidfvf+MG8jEETiiE08axf+7iZQSbpLglqhROkebIZt0fikpDpV+kmPgHe73Lb49T F6Jt3upfNFYoCxTLLr1Z2K++BKVf9H7bLpTYOrOCm/U9fkU1VyayTnp28aq/OnASgofU Z6jsIV8CsPGH6Oo95QDA+JzG0J+v1sOJwO1sFf8/SskIpOSdb2U5uPvoBy0gKHGlRm9p jfsnW/ubAhcqY5QOmfnydoebGLjEyF8lN8awAq8SdqJwv7JQ+Q6N+dLLxyAfA/ov9cx0 CfTsAP240chH8GQBxFrb83QwL8m9p3jUl09lRYdM759F98i+SG29MZHWRvyLszMsA9pN F2Sg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:message-id:mime-version:subject:from:to:cc; bh=9qIl2h84+tEpR/qPDqw7Ia65+Rx0tWJMRUJLpgSeqnM=; b=sHZtGtS+yRhzZ8Dp5SSuZhK9JGUG/ZRWaDSXKQ2LTmetZ0Afa21D0NaszSY1sNcgGp oT2/TlI/U/bDzqQpfh6gZ2lfeddj0O+gyz4TZc1ViqviS9diLwkko3CH/DYhROkLY+GQ i/0+2cdBCODOZuTs8OT1RRT4W1lovzKLefr5P8jHW4RQamSNQYxFCzsdzyNlt2nkAGoV DPesuSbf7/NkNaN3/QGsJat1x/VTdvghPIMzXvjOfwv/QP1wJJ6gqUTipaXh5HuzK8V3 1hg0hqkoFuIISpuz129mDpemmAwI3Ncdboj2zfSMP7byO4jDLzCknO9crk9WdiqzuREg qS8w== X-Gm-Message-State: AOAM530VeqzqKtmkCgpZ7PiwuQ9lMSboubRAWUaKKaNI7Qtrjotu5YzI Sf6OBf95lpJ/MV+Wys/FfQjOZCMmYRiMRJw5ttQ= X-Received: from ndesaulniers1.mtv.corp.google.com ([2620:15c:211:202:b408:7c5f:edf4:6c69]) (user=ndesaulniers job=sendgmr) by 2002:a05:6214:2b06:: with SMTP id jx6mr1632764qvb.48.1615930306822; Tue, 16 Mar 2021 14:31:46 -0700 (PDT) Date: Tue, 16 Mar 2021 14:31:33 -0700 Message-Id: <20210316213136.1866983-1-ndesaulniers@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.31.0.rc2.261.g7f71774620-goog Subject: [PATCH] scripts: stable: add script to validate backports From: Nick Desaulniers To: Greg Kroah-Hartman , Sasha Levin Cc: Ard Biesheuvel , clang-built-linux@googlegroups.com, Nick Desaulniers , linux-kernel@vger.kernel.org, stable@vger.kernel.org Content-Type: text/plain; charset="UTF-8" Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org A common recurring mistake made when backporting patches to stable is forgetting to check for additional commits tagged with `Fixes:`. This script validates that local commits have a `commit upstream.` line in their commit message, and whether any additional `Fixes:` shas exist in the `master` branch but were not included. It can not know about fixes yet to be discovered, or fixes sent to the mailing list but not yet in mainline. To save time, it avoids checking all of `master`, stopping early once we've reached the commit time of the earliest backport. It takes 0.5s to validate 2 patches to linux-5.4.y when master is v5.12-rc3 and 5s to validate 27 patches to linux-4.19.y. It does not recheck dependencies of found fixes; the user is expected to run this script to a fixed point. It depnds on pygit2 python library for working with git, which can be installed via: $ pip3 install pygit2 It's expected to be run from a stable tree with commits applied. For example, consider 3cce9d44321e which is a fix for f77ac2e378be. Let's say I cherry picked f77ac2e378be into linux-5.4.y but forgot 3cce9d44321e (true story). If I ran: $ ./scripts/stable/check_backports.py Checking 1 local commits for additional Fixes: in master Please consider backporting 3cce9d44321e as a fix for f77ac2e378be So then I could cherry pick 3cce9d44321e as well: $ git cherry-pick -sx 3cce9d44321e $ ./scripts/stable/check_backports.py ... Exception: Missing 'commit upstream.' line Oops, let me fixup the commit message and retry. $ git commit --amend $ ./scripts/stable/check_backports.py Checking 2 local commits for additional Fixes: in master $ echo $? 0 This allows for client side validation by the backports author, and server side validation by the stable kernel maintainers. Signed-off-by: Nick Desaulniers --- MAINTAINERS | 1 + scripts/stable/check_backports.py | 92 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100755 scripts/stable/check_backports.py diff --git a/MAINTAINERS b/MAINTAINERS index aa84121c5611..a8639e9277c4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16960,6 +16960,7 @@ M: Sasha Levin L: stable@vger.kernel.org S: Supported F: Documentation/process/stable-kernel-rules.rst +F: scripts/stable/ STAGING - ATOMISP DRIVER M: Mauro Carvalho Chehab diff --git a/scripts/stable/check_backports.py b/scripts/stable/check_backports.py new file mode 100755 index 000000000000..529294e247ca --- /dev/null +++ b/scripts/stable/check_backports.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 Google, Inc. + +import os +import re +import sys + +import pygit2 as pg + + +def get_head_branch(repo): + # Walk the branches to find which is HEAD. + for branch_name in repo.branches: + branch = repo.branches[branch_name] + if branch.is_head(): + return branch + + +def get_local_commits(repo): + head_branch = get_head_branch(repo) + # Walk the HEAD ref until we hit the first commit from the upstream. + walker = repo.walk(repo.head.target) + upstream_branch = head_branch.upstream + upstream_commit, _ = repo.resolve_refish(upstream_branch.name) + walker.hide(upstream_commit.id) + commits = [commit for commit in walker] + if not len(commits): + raise Exception("No local commits") + return commits + + +def get_upstream_shas(commits): + upstream_shas = [] + prog = re.compile('commit ([0-9a-f]{40}) upstream.') + # For each line of each commit message, record the + # "commit upstream." line. + for commit in commits: + found_upstream_line = False + for line in commit.message.splitlines(): + result = prog.search(line) + if result: + upstream_shas.append(result.group(1)[:12]) + found_upstream_line = True + break + if not found_upstream_line: + raise Exception("Missing 'commit upstream.' line") + return upstream_shas + + +def get_oldest_commit_time(repo, shas): + commit_times = [repo.resolve_refish(sha)[0].commit_time for sha in shas] + return sorted(commit_times)[0] + + +def get_fixes_for(shas): + shas = set(shas) + prog = re.compile("Fixes: ([0-9a-f]{12,40})") + # Walk commits in the master branch. + master_commit, master_ref = repo.resolve_refish("master") + walker = repo.walk(master_ref.target) + oldest_commit_time = get_oldest_commit_time(repo, shas) + fixes = [] + for commit in walker: + # It's not possible for a Fixes: to be committed before a fixed tag, so + # don't iterate all of git history. + if commit.commit_time < oldest_commit_time: + break + for line in reversed(commit.message.splitlines()): + result = prog.search(line) + if not result: + continue + fixes_sha = result.group(1)[:12] + if fixes_sha in shas and commit.id.hex[:12] not in shas: + fixes.append((commit.id.hex[:12], fixes_sha)) + return fixes + + +def report(fixes): + if len(fixes): + for fix, broke in fixes: + print("Please consider backporting %s as a fix for %s" % (fix, broke)) + sys.exit(1) + + +if __name__ == "__main__": + repo = pg.Repository(os.getcwd()) + commits = get_local_commits(repo) + print("Checking %d local commits for additional Fixes: in master" % (len(commits))) + upstream_shas = get_upstream_shas(commits) + fixes = get_fixes_for(upstream_shas) + report(fixes) -- 2.31.0.rc2.261.g7f71774620-goog