--- /dev/null
+5f923ff1a6a8a9ff6f06dc49c8e0e2ceee111567
+945aa0e42bc08879a9c979cbffb9b3f50e4945f3
+8e52a59808e9ca1b8e535725eb76b83e409745eb
+22b2390c66e2f9a5f3bcb2f1727c830348a6e61d
+7a7491a23eacaae41c07568d833e668ec1d351cf
+598ab9ca10d35d6990f9af3a4d58f265acd8b121
+8e0882d17a38cb9d309df705e76a8e88529f30a9
+46367e0a5c9a58087d59f19966b23ee980bdbb24
+67e16d37e9c83fea9f67d144eeac27a83d52c949
+73acd543cb1f88af880445de1e1a7238dd46c9de
+b499d4f65a7c1ce45500dcbf1c5c63d85241b330
+2e5d50ee43096ff5422c88f835ccceb1728def06
+3aa9fbf4fab2f21c0a6f0dba510bfe8266578f2f
+7058988fd65d719b69b658a74b268d4a2f1909c5
+4239c27f3867b558ae2e26950d5153d496b02d8f
+91f2ade57bb72e9bb4a44da44e5dc69adb3c7584
+93592c69179a8d5ccf1f09c3f26e91f194772b8e
--- /dev/null
+;;; cherry.el --- Cherry pick commits from upstream master -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library defines the command `cherry-pick-new-commits' that
+;; iterates over new commits in the upstream master branch, and
+;; suggests cherry-picking them one after the other.
+
+;;; Code:
+
+(defvar cherry-upstream-remote "upstream")
+
+(defvar cherry-upstream-branch "master")
+
+(defvar cherry-skip-list-file
+ (expand-file-name "admin/cherry-skip-list" source-directory))
+
+(defun cherry-merge-base ()
+ (car
+ (string-lines
+ (shell-command-to-string
+ (concat "git -C " source-directory " merge-base "
+ cherry-upstream-remote "/" cherry-upstream-branch
+ " HEAD"))
+ t)))
+
+(defun cherry-picked-p (commit merge-base)
+ (car
+ (string-lines
+ (shell-command-to-string
+ (concat "git log -1 --pretty=%H --grep='(cherry picked from commit "
+ commit
+ ")' "
+ merge-base "..HEAD"))
+ t)))
+
+(defun cherry-skipped-p (commit)
+ (= 0 (call-process "grep" cherry-skip-list-file nil nil
+ "-q" "-E" (concat "^" commit))))
+
+(defun cherry-upstream-commits ()
+ (string-lines
+ (shell-command-to-string
+ (concat "git -C " source-directory " cherry HEAD upstream/master | grep -vE '^-' | cut -f 2 -d ' '"))
+ t))
+
+(defun cherry-pick-new-commits ()
+ "Pick or skip new commits in the upstream branch."
+ (interactive)
+ (call-process "git" nil nil nil
+ "-C" source-directory "fetch" cherry-upstream-remote)
+ (let* ((merge-base (cherry-merge-base))
+ (new-commits
+ (seq-remove
+ (lambda (commit)
+ (or (cherry-picked-p commit merge-base)
+ (cherry-skipped-p commit)))
+ (cherry-upstream-commits))))
+ (if new-commits
+ (dolist (commit new-commits)
+ (with-current-buffer (get-buffer-create "*cherry*")
+ (delete-region (point-min) (point-max))
+ (fundamental-mode))
+ (call-process "git" nil "*cherry*" t "-C" source-directory
+ "format-patch" "-1" commit "--stdout")
+ (pop-to-buffer "*cherry*")
+ (diff-mode)
+ (goto-char (point-min))
+ (let ((choice (read-multiple-choice
+ "Pick?"
+ '((?p "pick")
+ (?s "skip")
+ (?q "quit")
+ ;; (?a "amend")
+ ))))
+ (pcase (car choice)
+ (?s
+ (message "Skipping...")
+ (shell-command (concat "echo " commit
+ (read-string "Reason: " "")
+ ">>" cherry-skip-list-file))
+ (message "Skipped."))
+ (?p
+ (message "Picking...")
+ (if (= 0 (call-process "git" nil nil nil
+ "-C" source-directory
+ "cherry-pick" "-x" commit))
+ (message "Picked.")
+ (user-error "Cherry picking failed")))
+ (?q (bury-buffer "*cherry*")
+ (user-error "Quit cherry picking")))))
+ (message "No new commits."))))
+
+(provide 'cherry)
+;;; cherry.el ends here