diff --git a/tools/tools/git/mfc-candidates.lua b/tools/tools/git/mfc-candidates.lua index b29f1ad4d121..d2865a892971 100755 --- a/tools/tools/git/mfc-candidates.lua +++ b/tools/tools/git/mfc-candidates.lua @@ -1,217 +1,217 @@ #!/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) + "git rev-parse " .. 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: ") 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] 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()