From 501dd27eb249fa3b1546893ecaec033f1ce69fd4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 13:10:49 -0700 Subject: [PATCH 01/10] post_push.yml: Write the SSH key more securely Co-authored-by: Nobuyoshi Nakada --- .github/workflows/post_push.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 32d74f644e6bf0..317aad2e42eba1 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -13,8 +13,7 @@ jobs: - name: Sync git.ruby-lang.org run: | mkdir -p ~/.ssh - echo "$RUBY_GIT_SYNC_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 + (umask 066; printenv RUBY_GIT_SYNC_PRIVATE_KEY > ~/.ssh/id_ed25519) ssh-keyscan -t ed25519 git.ruby-lang.org >> ~/.ssh/known_hosts ssh -i ~/.ssh/id_ed25519 git-sync@git.ruby-lang.org "sudo -u git /home/git/git.ruby-lang.org/bin/update-ruby.sh $GITHUB_REF" env: From 7ec03e12b4fa1f8a16e35131688bb7fd17e7d097 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 14:03:11 -0700 Subject: [PATCH 02/10] post_push.yml: Migrate commit-email.rb to post_push (#14779) from post-receive.sh as of https://github.com/ruby/git.ruby-lang.org/commit/8d24ac65b5aeb44f7a3212410d6911be621223d4. --- .github/workflows/post_push.yml | 13 ++ tool/commit-mail.rb | 399 ++++++++++++++++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 tool/commit-mail.rb diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 317aad2e42eba1..cbbcb107e375b6 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -43,6 +43,19 @@ jobs: SLACK_WEBHOOK_URL_RUBY_JP: ${{ secrets.SLACK_WEBHOOK_URL_RUBY_JP }} if: ${{ github.ref == 'refs/heads/master' }} + - name: Notify commit to ruby-cvs + run: | + SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org sendmail" \ + ruby tool/commit-mail.rb . ruby-cvs@g.ruby-lang.org \ + "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$GITHUB_REF" \ + --viewer-uri "https://github.com/ruby/ruby/commit/" \ + --error-to cvs-admin@ruby-lang.org + env: + GITHUB_OLD_SHA: ${{ github.event.before }} + GITHUB_NEW_SHA: ${{ github.event.after }} + GITHUB_REF: ${{ github.ref }} + if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/ruby_') }} + - name: Auto-correct code styles run: | set -x diff --git a/tool/commit-mail.rb b/tool/commit-mail.rb new file mode 100644 index 00000000000000..29f096c0e2e2cc --- /dev/null +++ b/tool/commit-mail.rb @@ -0,0 +1,399 @@ +#!/usr/bin/env ruby + +require "optparse" +require "ostruct" +require "nkf" +require "shellwords" + +CommitEmailInfo = Struct.new( + :author, + :author_email, + :revision, + :entire_sha256, + :date, + :log, + :branch, + :diffs, + :added_files, :deleted_files, :updated_files, + :added_dirs, :deleted_dirs, :updated_dirs, +) + +class GitInfoBuilder + GitCommandFailure = Class.new(RuntimeError) + + def initialize(repo_path) + @repo_path = repo_path + end + + def build(oldrev, newrev, refname) + diffs = build_diffs(oldrev, newrev) + + info = CommitEmailInfo.new + info.author = git_show(newrev, format: '%an') + info.author_email = normalize_email(git_show(newrev, format: '%aE')) + info.revision = newrev[0...10] + info.entire_sha256 = newrev + info.date = Time.at(Integer(git_show(newrev, format: '%at'))) + info.log = git_show(newrev, format: '%B') + info.branch = git('rev-parse', '--symbolic', '--abbrev-ref', refname).strip + info.diffs = diffs + info.added_files = find_files(diffs, status: :added) + info.deleted_files = find_files(diffs, status: :deleted) + info.updated_files = find_files(diffs, status: :modified) + info.added_dirs = [] # git does not deal with directory + info.deleted_dirs = [] # git does not deal with directory + info.updated_dirs = [] # git does not deal with directory + info + end + + private + + # Force git-svn email address to @ruby-lang.org to avoid email bounce by invalid email address. + def normalize_email(email) + if email.match(/\A[^@]+@\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/) # git-svn + svn_user, _ = email.split('@', 2) + "#{svn_user}@ruby-lang.org" + else + email + end + end + + def find_files(diffs, status:) + files = [] + diffs.each do |path, values| + if values.keys.first == status + files << path + end + end + files + end + + # SVN version: + # { + # "filename" => { + # "[modified|added|deleted|copied|property_changed]" => { + # type: "[modified|added|deleted|copied|property_changed]", + # body: "diff body", # not implemented because not used + # added: Integer, + # deleted: Integer, + # } + # } + # } + def build_diffs(oldrev, newrev) + diffs = {} + + numstats = git('diff', '--numstat', oldrev, newrev).lines.map { |l| l.strip.split("\t", 3) } + git('diff', '--name-status', oldrev, newrev).each_line do |line| + status, path, _newpath = line.strip.split("\t", 3) + diff = build_diff(path, numstats) + + case status + when 'A' + diffs[path] = { added: { type: :added, **diff } } + when 'M' + diffs[path] = { modified: { type: :modified, **diff } } + when 'C' + diffs[path] = { copied: { type: :copied, **diff } } + when 'D' + diffs[path] = { deleted: { type: :deleted, **diff } } + when /\AR/ # R100 (which does not exist in git.ruby-lang.org's git 2.1.4) + # TODO: implement something + else + $stderr.puts "unexpected git diff status: #{status}" + end + end + + diffs + end + + def build_diff(path, numstats) + diff = { added: 0, deleted: 0 } # :body not implemented because not used + line = numstats.find { |(_added, _deleted, file, *)| file == path } + return diff if line.nil? + + added, deleted, _ = line + if added + diff[:added] = Integer(added) + end + if deleted + diff[:deleted] = Integer(deleted) + end + diff + end + + def git_show(revision, format:) + git('show', "--pretty=#{format}", '--no-patch', revision).strip + end + + def git(*args) + command = ['git', '-C', @repo_path, *args] + output = with_gitenv { IO.popen(command, external_encoding: 'UTF-8', &:read) } + unless $?.success? + raise GitCommandFailure, "failed to execute '#{command.join(' ')}':\n#{output}" + end + output + end + + def with_gitenv + orig = ENV.to_h.dup + begin + ENV.delete('GIT_DIR') + yield + ensure + ENV.replace(orig) + end + end +end + +CommitEmail = Module.new +class << CommitEmail + SENDMAIL = ENV.fetch('SENDMAIL', '/usr/sbin/sendmail') + private_constant :SENDMAIL + + def parse(args) + options = OpenStruct.new + options.error_to = nil + options.viewvc_uri = nil + + opts = OptionParser.new do |opts| + opts.separator('') + + opts.on('-e', '--error-to [TO]', + 'Add [TO] to to address when error is occurred') do |to| + options.error_to = to + end + + opts.on('--viewer-uri [URI]', + 'Use [URI] as URI of revision viewer') do |uri| + options.viewer_uri = uri + end + + opts.on_tail('--help', 'Show this message') do + puts opts + exit + end + end + + return opts.parse(args), options + end + + def main(repo_path, to, rest) + args, options = parse(rest) + + infos = args.each_slice(3).flat_map do |oldrev, newrev, refname| + revisions = IO.popen(['git', 'log', '--reverse', '--pretty=%H', "#{oldrev}^..#{newrev}"], &:read).lines.map(&:strip) + revisions[0..-2].zip(revisions[1..-1]).map do |old, new| + GitInfoBuilder.new(repo_path).build(old, new, refname) + end + end + + infos.each do |info| + next if info.branch.start_with?('notes/') + puts "#{info.branch}: #{info.revision} (#{info.author})" + + from = make_from(name: info.author, email: "noreply@ruby-lang.org") + sendmail(to, from, make_mail(to, from, info, viewer_uri: options.viewer_uri)) + end + end + + def sendmail(to, from, mail) + IO.popen([*SENDMAIL.shellsplit, to], 'w') do |f| + f.print(mail) + end + unless $?.success? + raise "Failed to run `#{SENDMAIL} #{to}` with: '#{mail}'" + end + end + + private + + def b_encode(str) + NKF.nkf('-WwM', str) + end + + def make_body(info, viewer_uri:) + body = '' + body << "#{info.author}\t#{format_time(info.date)}\n" + body << "\n" + body << " New Revision: #{info.revision}\n" + body << "\n" + body << " #{viewer_uri}#{info.revision}\n" + body << "\n" + body << " Log:\n" + body << info.log.lstrip.gsub(/^\t*/, ' ').rstrip + body << "\n\n" + body << added_dirs(info) + body << added_files(info) + body << deleted_dirs(info) + body << deleted_files(info) + body << modified_dirs(info) + body << modified_files(info) + [body.rstrip].pack('M') + end + + def format_time(time) + time.strftime('%Y-%m-%d %X %z (%a, %d %b %Y)') + end + + def changed_items(title, type, items) + rv = '' + unless items.empty? + rv << " #{title} #{type}:\n" + rv << items.collect {|item| " #{item}\n"}.join('') + end + rv + end + + def changed_files(title, files) + changed_items(title, 'files', files) + end + + def added_files(info) + changed_files('Added', info.added_files) + end + + def deleted_files(info) + changed_files('Removed', info.deleted_files) + end + + def modified_files(info) + changed_files('Modified', info.updated_files) + end + + def changed_dirs(title, files) + changed_items(title, 'directories', files) + end + + def added_dirs(info) + changed_dirs('Added', info.added_dirs) + end + + def deleted_dirs(info) + changed_dirs('Removed', info.deleted_dirs) + end + + def modified_dirs(info) + changed_dirs('Modified', info.updated_dirs) + end + + def changed_dirs_info(info, uri) + rev = info.revision + (info.added_dirs.collect do |dir| + " Added: #{dir}\n" + end + info.deleted_dirs.collect do |dir| + " Deleted: #{dir}\n" + end + info.updated_dirs.collect do |dir| + " Modified: #{dir}\n" + end).join("\n") + end + + def diff_info(info, uri) + info.diffs.collect do |key, values| + [ + key, + values.collect do |type, value| + case type + when :added + command = 'cat' + rev = "?revision=#{info.revision}&view=markup" + when :modified, :property_changed + command = 'diff' + prev_revision = (info.revision.is_a?(Integer) ? info.revision - 1 : "#{info.revision}^") + rev = "?r1=#{info.revision}&r2=#{prev_revision}&diff_format=u" + when :deleted, :copied + command = 'cat' + rev = '' + else + raise "unknown diff type: #{value[:type]}" + end + + link = [uri, key.sub(/ .+/, '') || ''].join('/') + rev + + desc = '' + + [desc, link] + end + ] + end + end + + def make_header(to, from, info) + headers = [] + headers << x_author(info) + headers << x_repository(info) + headers << x_revision(info) + headers << x_id(info) + headers << 'Mime-Version: 1.0' + headers << 'Content-Type: text/plain; charset=utf-8' + headers << 'Content-Transfer-Encoding: quoted-printable' + headers << "From: #{from}" + headers << "To: #{to}" + headers << "Subject: #{make_subject(info)}" + headers.find_all do |header| + /\A\s*\z/ !~ header + end.join("\n") + end + + def make_subject(info) + subject = '' + subject << "#{info.revision}" + subject << " (#{info.branch})" + subject << ': ' + subject << info.log.lstrip.lines.first.to_s.strip + b_encode(subject) + end + + # https://tools.ietf.org/html/rfc822#section-4.1 + # https://tools.ietf.org/html/rfc822#section-6.1 + # https://tools.ietf.org/html/rfc822#appendix-D + # https://tools.ietf.org/html/rfc2047 + def make_from(name:, email:) + if name.ascii_only? + escaped_name = name.gsub(/["\\\n]/) { |c| "\\#{c}" } + %Q["#{escaped_name}" <#{email}>] + else + escaped_name = "=?UTF-8?B?#{NKF.nkf('-WwMB', name)}?=" + %Q[#{escaped_name} <#{email}>] + end + end + + def x_author(info) + "X-SVN-Author: #{b_encode(info.author)}" + end + + def x_repository(info) + 'X-SVN-Repository: XXX' + end + + def x_id(info) + "X-SVN-Commit-Id: #{info.entire_sha256}" + end + + def x_revision(info) + "X-SVN-Revision: #{info.revision}" + end + + def make_mail(to, from, info, viewer_uri:) + "#{make_header(to, from, info)}\n#{make_body(info, viewer_uri: viewer_uri)}" + end +end + +repo_path, to, *rest = ARGV +begin + CommitEmail.main(repo_path, to, rest) +rescue StandardError => e + $stderr.puts "#{e.class}: #{e.message}" + $stderr.puts e.backtrace + + _, options = CommitEmail.parse(rest) + to = options.error_to + CommitEmail.sendmail(to, to, <<-MAIL) +From: #{to} +To: #{to} +Subject: Error + +#{$!.class}: #{$!.message} +#{$@.join("\n")} +MAIL + exit 1 +end From 5b8f47fa64735fb5ee8c0e82eae3a9704ab4efbd Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 14:06:01 -0700 Subject: [PATCH 03/10] post_push.yml: Specify the full path of sendmail --- .github/workflows/post_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index cbbcb107e375b6..91d4a090554974 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -45,7 +45,7 @@ jobs: - name: Notify commit to ruby-cvs run: | - SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org sendmail" \ + SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org /usr/sbin/sendmail" \ ruby tool/commit-mail.rb . ruby-cvs@g.ruby-lang.org \ "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$GITHUB_REF" \ --viewer-uri "https://github.com/ruby/ruby/commit/" \ From 86d97331da420069b257c5af8f32ead3a2474cc1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 6 Oct 2025 17:17:14 -0400 Subject: [PATCH 04/10] Add RUBY_FREE_AT_EXIT to MANDATORY_ENVS in test_process.rb We need to keep RUBY_FREE_AT_EXIT in these tests. --- test/ruby/test_process.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 221ff37c6b6946..7cea0b3a7027e7 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -280,7 +280,7 @@ def test_overwrite_ENV end; end - MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH] + MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT] case RbConfig::CONFIG['target_os'] when /linux/ MANDATORY_ENVS << 'LD_PRELOAD' From a48592a754299f3b764ac7311fa5feff8e7ada0a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 14:53:11 -0700 Subject: [PATCH 05/10] Let test-tool accept $(TESTS) like test-all does --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 15e314cd1bbe8a..58a8da14021fff 100644 --- a/common.mk +++ b/common.mk @@ -887,7 +887,7 @@ no-test-testframework: PHONY test-tool: $(TEST_RUNNABLE)-test-tool yes-test-tool: prog PHONY $(ACTIONS_GROUP) - $(gnumake_recursive)$(Q)$(exec) $(RUNRUBY) "$(TOOL_TESTSDIR)/runner.rb" --ruby="$(RUNRUBY)" $(TESTOPTS) + $(gnumake_recursive)$(Q)$(exec) $(RUNRUBY) "$(TOOL_TESTSDIR)/runner.rb" --ruby="$(RUNRUBY)" $(TESTOPTS) $(TESTS) $(ACTIONS_ENDGROUP) no-test-tool: PHONY From fc08d36a1521e5236cc10ef6bad9cb15693bac9d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 15:04:42 -0700 Subject: [PATCH 06/10] post_push.yml: Rename commit-mail.rb back to commit-email.rb. I didn't realize I dropped a letter when I moved it. It wasn't really intended, so I change it back. --- .github/workflows/post_push.yml | 2 +- tool/{commit-mail.rb => commit-email.rb} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tool/{commit-mail.rb => commit-email.rb} (100%) diff --git a/.github/workflows/post_push.yml b/.github/workflows/post_push.yml index 91d4a090554974..da109ed3bb1897 100644 --- a/.github/workflows/post_push.yml +++ b/.github/workflows/post_push.yml @@ -46,7 +46,7 @@ jobs: - name: Notify commit to ruby-cvs run: | SENDMAIL="ssh -i ${HOME}/.ssh/id_ed25519 git-sync@git.ruby-lang.org /usr/sbin/sendmail" \ - ruby tool/commit-mail.rb . ruby-cvs@g.ruby-lang.org \ + ruby tool/commit-email.rb . ruby-cvs@g.ruby-lang.org \ "$GITHUB_OLD_SHA" "$GITHUB_NEW_SHA" "$GITHUB_REF" \ --viewer-uri "https://github.com/ruby/ruby/commit/" \ --error-to cvs-admin@ruby-lang.org diff --git a/tool/commit-mail.rb b/tool/commit-email.rb similarity index 100% rename from tool/commit-mail.rb rename to tool/commit-email.rb From 0508786b73d572c5c2f56492823132c439ef2200 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 16:16:02 -0700 Subject: [PATCH 07/10] Migrate a test for commit-email.rb (#14784) from https://github.com/ruby/git.ruby-lang.org/commit/2c4628e489ed00732a5bcde3373d784307c54280. Also drop ostruct from dependencies. We could remove nkf if we use base64, but it's also a bundled gem, so it doesn't really help. --- tool/commit-email.rb | 7 ++-- tool/test/test_commit_mail.rb | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) mode change 100644 => 100755 tool/commit-email.rb create mode 100644 tool/test/test_commit_mail.rb diff --git a/tool/commit-email.rb b/tool/commit-email.rb old mode 100644 new mode 100755 index 29f096c0e2e2cc..d9f79f41c9982a --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby require "optparse" -require "ostruct" require "nkf" require "shellwords" @@ -145,15 +144,15 @@ def with_gitenv end end +CommitEmailOptions = Struct.new(:error_to, :viewer_uri) + CommitEmail = Module.new class << CommitEmail SENDMAIL = ENV.fetch('SENDMAIL', '/usr/sbin/sendmail') private_constant :SENDMAIL def parse(args) - options = OpenStruct.new - options.error_to = nil - options.viewvc_uri = nil + options = CommitEmailOptions.new opts = OptionParser.new do |opts| opts.separator('') diff --git a/tool/test/test_commit_mail.rb b/tool/test/test_commit_mail.rb new file mode 100644 index 00000000000000..4180a71d7bc729 --- /dev/null +++ b/tool/test/test_commit_mail.rb @@ -0,0 +1,63 @@ +require 'test/unit' +require 'shellwords' +require 'tmpdir' +require 'fileutils' +require 'open3' + +class TestCommitEmail < Test::Unit::TestCase + def setup + @ruby = Dir.mktmpdir + Dir.chdir(@ruby) do + git('init') + git('config', 'user.name', 'Jóhän Grübél') + git('config', 'user.email', 'johan@example.com') + git('commit', '--allow-empty', '-m', 'New repository initialized by cvs2svn.') + git('commit', '--allow-empty', '-m', 'Initial revision') + git('commit', '--allow-empty', '-m', 'version 1.0.0') + end + + @sendmail = File.join(Dir.mktmpdir, 'sendmail') + File.write(@sendmail, <<~SENDMAIL) + #!/usr/bin/env ruby + p ARGV + puts STDIN.read + SENDMAIL + FileUtils.chmod(0755, @sendmail) + + @commit_email = File.expand_path('../../tool/commit-email.rb', __dir__) + end + + # Just testing an exit status :p + # TODO: prepare something in test/fixtures/xxx and test output + def test_successful_run + unless EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', true).last.success? + omit "bundled gems are not available" + end + + Dir.chdir(@ruby) do + out, _, status = EnvUtil.invoke_ruby([ + { 'SENDMAIL' => @sendmail }.merge!(gem_env), + @commit_email, './', 'cvs-admin@ruby-lang.org', + git('rev-parse', 'HEAD^').chomp, git('rev-parse', 'HEAD').chomp, 'refs/heads/master', + '--viewer-uri', 'https://github.com/ruby/ruby/commit/', + '--error-to', 'cvs-admin@ruby-lang.org', + ], '', true) + assert_true(status.success?, out) + end + end + + private + + # Cancel the gem environments set by tool/test/init.rb + def gem_env + { 'GEM_PATH' => nil, 'GEM_HOME' => nil } + end + + def git(*cmd) + out, status = Open3.capture2('git', *cmd) + unless status.success? + raise "git #{cmd.shelljoin}\n#{out}" + end + out + end +end From afb21f34987e47932b92530f48a19992863b29b1 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 16:17:37 -0700 Subject: [PATCH 08/10] test_commit_email.rb: Stop printing LoadError on a require attempt --- tool/test/{test_commit_mail.rb => test_commit_email.rb} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename tool/test/{test_commit_mail.rb => test_commit_email.rb} (90%) diff --git a/tool/test/test_commit_mail.rb b/tool/test/test_commit_email.rb similarity index 90% rename from tool/test/test_commit_mail.rb rename to tool/test/test_commit_email.rb index 4180a71d7bc729..539b0b071cd42c 100644 --- a/tool/test/test_commit_mail.rb +++ b/tool/test/test_commit_email.rb @@ -30,8 +30,9 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - unless EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', true).last.success? - omit "bundled gems are not available" + _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) + unless status.success? + omit "bundled gems are not available: #{err}" end Dir.chdir(@ruby) do From dbb5972b340f24d9ff4f3996f57439d5a6b3454e Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 16:30:58 -0700 Subject: [PATCH 09/10] commit-email.rb: Use base64 instead of nkf which makes it more obvious what it's doing. --- tool/commit-email.rb | 7 ++++--- tool/test/test_commit_email.rb | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index d9f79f41c9982a..d5b17a09c23cb0 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby require "optparse" -require "nkf" +require "base64" require "shellwords" CommitEmailInfo = Struct.new( @@ -207,7 +207,8 @@ def sendmail(to, from, mail) private def b_encode(str) - NKF.nkf('-WwM', str) + base64_str = Base64.encode64(str.force_encoding('UTF-8')).strip # NKF.nkf('-WwMB', str) + "=?UTF-8?B?#{base64_str}?=" end def make_body(info, viewer_uri:) @@ -351,7 +352,7 @@ def make_from(name:, email:) escaped_name = name.gsub(/["\\\n]/) { |c| "\\#{c}" } %Q["#{escaped_name}" <#{email}>] else - escaped_name = "=?UTF-8?B?#{NKF.nkf('-WwMB', name)}?=" + escaped_name = b_encode(name) %Q[#{escaped_name} <#{email}>] end end diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 539b0b071cd42c..2d93f11c3e12cf 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -30,7 +30,7 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) + _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "base64"'], '', false, true) unless status.success? omit "bundled gems are not available: #{err}" end From 77b62a8292b14e1c1640376a413b9e7e4080b32d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 8 Oct 2025 17:03:17 -0700 Subject: [PATCH 10/10] Revert "commit-email.rb: Use base64 instead of nkf" This reverts commit dbb5972b340f24d9ff4f3996f57439d5a6b3454e. It didn't work, sorry. --- tool/commit-email.rb | 7 +++---- tool/test/test_commit_email.rb | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tool/commit-email.rb b/tool/commit-email.rb index d5b17a09c23cb0..d9f79f41c9982a 100755 --- a/tool/commit-email.rb +++ b/tool/commit-email.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby require "optparse" -require "base64" +require "nkf" require "shellwords" CommitEmailInfo = Struct.new( @@ -207,8 +207,7 @@ def sendmail(to, from, mail) private def b_encode(str) - base64_str = Base64.encode64(str.force_encoding('UTF-8')).strip # NKF.nkf('-WwMB', str) - "=?UTF-8?B?#{base64_str}?=" + NKF.nkf('-WwM', str) end def make_body(info, viewer_uri:) @@ -352,7 +351,7 @@ def make_from(name:, email:) escaped_name = name.gsub(/["\\\n]/) { |c| "\\#{c}" } %Q["#{escaped_name}" <#{email}>] else - escaped_name = b_encode(name) + escaped_name = "=?UTF-8?B?#{NKF.nkf('-WwMB', name)}?=" %Q[#{escaped_name} <#{email}>] end end diff --git a/tool/test/test_commit_email.rb b/tool/test/test_commit_email.rb index 2d93f11c3e12cf..539b0b071cd42c 100644 --- a/tool/test/test_commit_email.rb +++ b/tool/test/test_commit_email.rb @@ -30,7 +30,7 @@ def setup # Just testing an exit status :p # TODO: prepare something in test/fixtures/xxx and test output def test_successful_run - _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "base64"'], '', false, true) + _, err, status = EnvUtil.invoke_ruby([gem_env, '-e', 'require "nkf"'], '', false, true) unless status.success? omit "bundled gems are not available: #{err}" end