mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-26 11:47:31 +00:00
mfc-candidates: Convert to Lua
d51c590023
added a Lua script to process the lists of candidate and
completed MFC commits to address sorting issues in the original shell
implementation.
Instead of having a mix of shell and Lua, just implement the entire
tool in Lua. This is more maintainable and gives a reasonable
improvement in performace.
Reviewed by: imp
Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D47416
This commit is contained in:
parent
12fc79619a
commit
48f3fcabea
@ -1,74 +0,0 @@
|
||||
#!/usr/libexec/flua
|
||||
|
||||
-- MFC candidate script utility - $0 from-file to-file
|
||||
--
|
||||
-- from-file specifies hashes that exist only in the "MFC from" branch and
|
||||
-- to-file specifies the original hashes of commits already merged to the
|
||||
-- "MFC to" branch.
|
||||
|
||||
-- SPDX-License-Identifier: BSD-2-Clause
|
||||
-- Copyright 2024 The FreeBSD Foundation
|
||||
|
||||
-- Read a file and return its content as a table
|
||||
local function read_file(filename)
|
||||
local file = assert(io.open(filename, "r"))
|
||||
local content = {}
|
||||
for line in file:lines() do
|
||||
table.insert(content, line)
|
||||
end
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
-- Remove hashes from 'set1' list that are present in 'set2' list
|
||||
local function set_difference(set1, set2)
|
||||
local set2_values = {}
|
||||
for _, value in ipairs(set2) do
|
||||
set2_values[value] = true
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for _, value in ipairs(set1) do
|
||||
if not set2_values[value] then
|
||||
table.insert(result, value)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Execute a command and print to stdout
|
||||
local function exec_command(command)
|
||||
local handle = io.popen(command)
|
||||
local output = handle:read("a")
|
||||
handle:close()
|
||||
io.write(output)
|
||||
end
|
||||
|
||||
-- Main function
|
||||
local function main()
|
||||
local from_file = arg[1]
|
||||
local to_file = arg[2]
|
||||
local exclude_file = arg[3]
|
||||
|
||||
if not from_file or not to_file then
|
||||
print("Usage: flua $0 from-file to-file")
|
||||
return
|
||||
end
|
||||
|
||||
local from_hashes = read_file(from_file)
|
||||
local to_hashes = read_file(to_file)
|
||||
|
||||
local result_hashes = set_difference(from_hashes, to_hashes)
|
||||
|
||||
if exclude_file then
|
||||
exclude_hashes = read_file(exclude_file)
|
||||
result_hashes = set_difference(result_hashes, exclude_hashes)
|
||||
end
|
||||
|
||||
-- Print the result
|
||||
for _, hash in ipairs(result_hashes) do
|
||||
exec_command("git show --pretty='%h %s' --no-patch " .. hash)
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
218
tools/tools/git/mfc-candidates.lua
Executable file
218
tools/tools/git/mfc-candidates.lua
Executable file
@ -0,0 +1,218 @@
|
||||
#!/usr/libexec/flua
|
||||
|
||||
-- SPDX-License-Identifier: BSD-2-Clause
|
||||
-- Copyright 2024 The FreeBSD Foundation
|
||||
|
||||
-- MFC candidate search utility. Identify hashes that exist only in the
|
||||
-- "MFC from" branch and do not have a corresponding "cherry picked from"
|
||||
-- commit in the "MFC to" branch.
|
||||
|
||||
-- Execute a command and return its output. A final newline is stripped,
|
||||
-- similar to sh.
|
||||
local function exec_command(command)
|
||||
local handle = assert(io.popen(command))
|
||||
local output = handle:read("a")
|
||||
handle:close()
|
||||
if output:sub(-1) == "\n" then
|
||||
return output:sub(1, -2)
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
-- Return a table of cherry-pick (MFC) candidates.
|
||||
local function read_from(from_branch, to_branch, author, dirspec)
|
||||
local command = "git rev-list --first-parent --reverse "
|
||||
command = command .. to_branch .. ".." .. from_branch
|
||||
if #author > 0 then
|
||||
command = command .. " --committer \\<" .. author .. "@"
|
||||
end
|
||||
if dirspec then
|
||||
command = command .. " " .. dirspec
|
||||
end
|
||||
if verbose > 1 then
|
||||
print("Obtaining MFC-from commits using command:")
|
||||
print(command)
|
||||
end
|
||||
local handle = assert(io.popen(command))
|
||||
local content = {}
|
||||
for line in handle:lines() do
|
||||
table.insert(content, line)
|
||||
end
|
||||
handle:close()
|
||||
return content
|
||||
end
|
||||
|
||||
-- Return a table of original hashes of changes that have already been
|
||||
-- cherry-picked (MFC'd).
|
||||
local function read_to(from_branch, to_branch, dirspec)
|
||||
local command = "git log " .. from_branch .. ".." .. to_branch
|
||||
command = command .. " --grep 'cherry picked from'"
|
||||
if dirspec then
|
||||
command = command .. " " .. dirspec
|
||||
end
|
||||
if verbose > 1 then
|
||||
print("Obtaining MFC-to commits using command:")
|
||||
print(command)
|
||||
end
|
||||
local handle = assert(io.popen(command))
|
||||
local content = {}
|
||||
for line in handle:lines() do
|
||||
local hash = line:match("%(cherry picked from commit ([0-9a-f]+)%)")
|
||||
if hash then
|
||||
table.insert(content, hash)
|
||||
end
|
||||
end
|
||||
handle:close()
|
||||
return content
|
||||
end
|
||||
|
||||
-- Read a commit exclude file and return its content as a table. Comments
|
||||
-- starting with # and text after a hash is ignored.
|
||||
local function read_exclude(filename)
|
||||
local file = assert(io.open(filename, "r"))
|
||||
local content = {}
|
||||
for line in file:lines() do
|
||||
local hash = line:match("^%x+")
|
||||
if hash then
|
||||
-- Hashes are 40 chars; if less, expand short hash.
|
||||
if #hash < 40 then
|
||||
hash = exec_command(
|
||||
"git show --pretty=%H --no-patch " .. hash)
|
||||
end
|
||||
table.insert(content, hash)
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
--- Remove hashes from 'set1' list that are present in 'set2' list
|
||||
local function set_difference(set1, set2)
|
||||
local set2_values = {}
|
||||
for _, value in ipairs(set2) do
|
||||
set2_values[value] = true
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for _, value in ipairs(set1) do
|
||||
if not set2_values[value] then
|
||||
table.insert(result, value)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Global state
|
||||
verbose = 0
|
||||
|
||||
local function params(from_branch, to_branch, author)
|
||||
print("from: " .. from_branch)
|
||||
print("to: " .. to_branch)
|
||||
if #author > 0 then
|
||||
print("author/committer: " .. author)
|
||||
else
|
||||
print("author/committer: <all>")
|
||||
end
|
||||
end
|
||||
|
||||
local function usage(from_branch, to_branch, author)
|
||||
local script_name = arg[0]:match("([^/]+)$")
|
||||
print(script_name .. " [-ah] [-f from_branch] [-t to_branch] [-u user] [-X exclude_file] [path ...]")
|
||||
print()
|
||||
params(from_branch, to_branch, author)
|
||||
end
|
||||
|
||||
-- Main function
|
||||
local function main()
|
||||
local from_branch = "freebsd/main"
|
||||
local to_branch = ""
|
||||
local author = os.getenv("USER") or ""
|
||||
local dirspec = nil
|
||||
|
||||
local url = exec_command("git remote get-url freebsd")
|
||||
local freebsd_repo = string.match(url, "[^/]+$")
|
||||
freebsd_repo = string.gsub(freebsd_repo, ".git$", "")
|
||||
if freebsd_repo == "ports" or freebsd_repo == "freebsd-ports" then
|
||||
local year = os.date("%Y")
|
||||
local month = os.date("%m")
|
||||
local qtr = math.ceil(month / 3)
|
||||
to_branch = "freebsd/" .. year .. "Q" .. qtr
|
||||
elseif freebsd_repo == "src" or freebsd_repo == "freebsd-src" then
|
||||
to_branch = "freebsd/stable/14"
|
||||
-- If pwd is a stable or release branch tree, default to it.
|
||||
local cur_branch = exec_command("git symbolic-ref --short HEAD")
|
||||
if string.match(cur_branch, "^stable/") then
|
||||
to_branch = cur_branch
|
||||
elseif string.match(cur_branch, "^releng/") then
|
||||
to_branch = cur_branch
|
||||
local major = string.match(cur_branch, "%d+")
|
||||
from_branch = "freebsd/stable/" .. major
|
||||
end
|
||||
else
|
||||
print("pwd is not under a ports or src repository.")
|
||||
return
|
||||
end
|
||||
|
||||
local do_help = false
|
||||
local exclude_file = nil
|
||||
local i = 1
|
||||
while i <= #arg and arg[i] do
|
||||
local opt = arg[i]
|
||||
if opt == "-a" then
|
||||
author = ""
|
||||
elseif opt == "-f" then
|
||||
from_branch = arg[i + 1]
|
||||
i = i + 1
|
||||
elseif opt == "-h" then
|
||||
do_help = true
|
||||
i = i + 1
|
||||
elseif opt == "-t" then
|
||||
to_branch = arg[i + 1]
|
||||
i = i + 1
|
||||
elseif opt == "-u" then
|
||||
author = arg[i + 1]
|
||||
i = i + 1
|
||||
elseif opt == "-v" then
|
||||
verbose = verbose + 1
|
||||
elseif opt == "-X" then
|
||||
exclude_file = arg[i + 1]
|
||||
print ("-X not working")
|
||||
i = i + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if do_help then
|
||||
usage(from_branch, to_branch, author)
|
||||
return
|
||||
end
|
||||
|
||||
if arg[i] then
|
||||
dirspec = arg[i]
|
||||
--print("dirspec = " .. dirspec)
|
||||
-- XXX handle multiple dirspecs?
|
||||
end
|
||||
|
||||
if verbose > 0 then
|
||||
params(from_branch, to_branch, author)
|
||||
end
|
||||
|
||||
local from_hashes = read_from(from_branch, to_branch, author, dirspec)
|
||||
local to_hashes = read_to(from_branch, to_branch, dirspec)
|
||||
|
||||
local result_hashes = set_difference(from_hashes, to_hashes)
|
||||
|
||||
if exclude_file then
|
||||
exclude_hashes = read_exclude(exclude_file)
|
||||
result_hashes = set_difference(result_hashes, exclude_hashes)
|
||||
end
|
||||
|
||||
-- Print the result
|
||||
for _, hash in ipairs(result_hashes) do
|
||||
print(exec_command("git show --pretty='%h %s' --no-patch " .. hash))
|
||||
end
|
||||
end
|
||||
|
||||
main()
|
@ -29,139 +29,5 @@
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
|
||||
from_branch=freebsd/main
|
||||
author="${USER}"
|
||||
|
||||
# Get the FreeBSD repository
|
||||
repo=$(basename "$(git remote get-url freebsd 2>/dev/null)" .git 2>/dev/null)
|
||||
|
||||
if [ "${repo}" = "ports" -o "${repo}" = "freebsd-ports" ]; then
|
||||
year=$(date '+%Y')
|
||||
month=$(date '+%m')
|
||||
qtr=$(((month-1) / 3 + 1))
|
||||
to_branch="freebsd/${year}Q${qtr}"
|
||||
elif [ "${repo}" = "src" -o "${repo}" = "freebsd-src" ]; then
|
||||
to_branch=freebsd/stable/14
|
||||
# If pwd is a stable or release branch tree, default to it.
|
||||
cur_branch=$(git symbolic-ref --short HEAD 2>/dev/null)
|
||||
case $cur_branch in
|
||||
stable/*)
|
||||
to_branch=$cur_branch
|
||||
;;
|
||||
releng/*)
|
||||
to_branch=$cur_branch
|
||||
major=${cur_branch#releng/}
|
||||
major=${major%.*}
|
||||
from_branch=freebsd/stable/$major
|
||||
esac
|
||||
else
|
||||
echo "pwd is not under a ports or src repository."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
params()
|
||||
{
|
||||
echo "from: $from_branch"
|
||||
echo "to: $to_branch"
|
||||
if [ -n "$author" ]; then
|
||||
echo "author/committer: $author"
|
||||
else
|
||||
echo "author/committer: <all>"
|
||||
fi
|
||||
}
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "usage: $(basename $0) [-ah] [-f from_branch] [-t to_branch] [-u user] [-X exclude_file] [path ...]"
|
||||
echo
|
||||
params
|
||||
exit 0
|
||||
}
|
||||
|
||||
while getopts "af:ht:u:vX:" opt; do
|
||||
case $opt in
|
||||
a)
|
||||
# All authors/committers
|
||||
author=
|
||||
;;
|
||||
f)
|
||||
from_branch=$OPTARG
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
t)
|
||||
to_branch=$OPTARG
|
||||
;;
|
||||
u)
|
||||
author=$OPTARG
|
||||
;;
|
||||
v)
|
||||
verbose=1
|
||||
;;
|
||||
X)
|
||||
if [ ! -r "$OPTARG" ]; then
|
||||
echo "Exclude file $OPTARG not readable" >&2
|
||||
exit 1
|
||||
fi
|
||||
exclude_file=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
if [ $verbose ]; then
|
||||
params
|
||||
echo
|
||||
fi
|
||||
|
||||
authorarg=
|
||||
if [ -n "$author" ]; then
|
||||
# Match user ID in the email portion of author or committer
|
||||
authorarg="--committer <${author}@"
|
||||
fi
|
||||
|
||||
# Commits in from_branch after branch point
|
||||
commits_from()
|
||||
{
|
||||
git rev-list --first-parent --reverse $authorarg $to_branch..$from_branch "$@"
|
||||
}
|
||||
|
||||
# "cherry picked from" hashes from commits in to_branch after branch point
|
||||
commits_to()
|
||||
{
|
||||
git log $from_branch..$to_branch --grep 'cherry picked from' "$@" |\
|
||||
sed -E -n 's/^[[:space:]]*\(cherry picked from commit ([0-9a-f]+)\)[[:space:]]*$/\1/p'
|
||||
}
|
||||
|
||||
# Turn a list of short hashes (and optional descriptions) into a list of full
|
||||
# hashes.
|
||||
canonicalize_hashes()
|
||||
{
|
||||
while read hash rest; do
|
||||
case "${hash}" in
|
||||
"#"*) continue ;;
|
||||
esac
|
||||
if ! git show --pretty=%H --no-patch $hash; then
|
||||
echo "error parsing hash list" >&2
|
||||
exit 1
|
||||
fi
|
||||
done | sort
|
||||
}
|
||||
|
||||
workdir=$(mktemp -d /tmp/find-mfc.XXXXXXXXXX)
|
||||
from_list=$workdir/commits-from
|
||||
to_list=$workdir/commits-to
|
||||
|
||||
if [ -n "$exclude_file" ]; then
|
||||
exclude_list=$workdir/commits-exclude
|
||||
canonicalize_hashes < $exclude_file > $exclude_list
|
||||
fi
|
||||
|
||||
commits_from "$@" > $from_list
|
||||
commits_to "$@" > $to_list
|
||||
|
||||
/usr/libexec/flua $(dirname $0)/candidatematch.lua \
|
||||
$from_list $to_list $exclude_list
|
||||
|
||||
rm -rf "$workdir"
|
||||
# Backwards compatibility wrapper
|
||||
/usr/libexec/flua $(dirname $0)/mfc-candidates.lua "$@"
|
||||
|
Loading…
Reference in New Issue
Block a user