66
77require "abstract_command"
88require "executables_db"
9+ require "utils/github"
910
1011module Homebrew
1112 module DevCmd
@@ -14,143 +15,98 @@ class WhichUpdate < AbstractCommand
1415 description <<~EOS
1516 Database update for `brew which-formula`.
1617 EOS
17- switch "--stats" ,
18- description : "Print statistics about the database contents (number of commands and formulae, " \
19- "list of missing formulae)."
20- switch "--commit" ,
21- description : "Commit the changes using `git`."
22- switch "--update-existing" ,
23- description : "Update database entries with outdated formula versions."
24- switch "--install-missing" ,
25- description : "Install and update formulae that are missing from the database and don't have bottles."
26- switch "--eval-all" ,
27- description : "Evaluate all installed taps, rather than just the core tap."
28- flag "--max-downloads=" ,
29- description : "Specify a maximum number of formulae to download and update."
18+ flag "--bottle-json-dir=" ,
19+ description : "Use generated bottle JSON files in the given directory to update formula entries."
20+ flag "--removed-formulae-file=" ,
21+ description : "Remove database entries for formulae listed in the given file."
22+ flag "--pull-request=" ,
23+ description : "Update entries for formula changes in the given pull request number."
24+ flag "--repository=" ,
25+ depends_on : "--pull-request" ,
26+ description : "GitHub repository for `--pull-request` (default: `$GITHUB_REPOSITORY`)."
3027 flag "--summary-file=" ,
3128 description : "Output a summary of the changes to a file."
32- conflicts "--stats" , "--commit"
33- conflicts "--stats" , "--install-missing"
34- conflicts "--stats" , "--update-existing"
35- conflicts "--stats" , "--max-downloads"
3629 named_args :database , number : 1
3730 end
3831
3932 sig { override . void }
4033 def run
41- if args . stats?
42- stats source : args . named . fetch ( 0 )
43- else
44- update_and_save! source : args . named . fetch ( 0 ) ,
45- commit : args . commit? ,
46- update_existing : args . update_existing? ,
47- install_missing : args . install_missing? ,
48- max_downloads : args . max_downloads &.to_i ,
49- eval_all : args . eval_all? ,
50- summary_file : args . summary_file
34+ updated = update_and_save! source : args . named . fetch ( 0 ) ,
35+ bottle_json_dir : args . bottle_json_dir ,
36+ removed_formulae_file : args . removed_formulae_file ,
37+ pull_request : args . pull_request ,
38+ repository : args . repository ,
39+ summary_file : args . summary_file
40+
41+ if ( github_output = ENV [ "GITHUB_OUTPUT" ] . presence )
42+ File . open ( github_output , "a" ) { |file | file . puts "updated=#{ updated } " }
5143 end
5244 end
5345
54- sig { params ( source : String ) . void }
55- def stats ( source :)
56- opoo "The DB file doesn't exist." unless File . exist? source
57- db = ExecutablesDB . new source
58-
59- formulae = db . formula_names
60- core = Formula . core_names
61-
62- cmds_count = db . exes . values . reduce ( 0 ) { |s , exs | s + exs . binaries . size }
63-
64- core_percentage = ( ( formulae & core ) . size * 1000 / core . size . to_f ) . round / 10.0
65-
66- missing = ( core - formulae ) . reject { |f | Formula [ f ] . disabled? }
67- puts <<~EOS
68- #{ formulae . size } formulae
69- #{ cmds_count } commands
70- #{ core_percentage } % (missing: #{ missing * " " } )
71- EOS
72-
73- unknown = formulae - Formula . full_names
74- puts "\n Unknown formulae: #{ unknown * ", " } ." if unknown . any?
75- nil
76- end
77-
7846 sig {
7947 params (
80- source : String ,
81- commit : T ::Boolean ,
82- update_existing : T ::Boolean ,
83- install_missing : T ::Boolean ,
84- max_downloads : T . nilable ( Integer ) ,
85- eval_all : T ::Boolean ,
86- summary_file : T . nilable ( String ) ,
87- ) . void
48+ source : String ,
49+ bottle_json_dir : T . nilable ( String ) ,
50+ removed_formulae_file : T . nilable ( String ) ,
51+ pull_request : T . nilable ( String ) ,
52+ repository : T . nilable ( String ) ,
53+ summary_file : T . nilable ( String ) ,
54+ ) . returns ( T ::Boolean )
8855 }
89- def update_and_save! ( source :, commit : false , update_existing : false , install_missing : false ,
90- max_downloads : nil , eval_all : false , summary_file : nil )
56+ def update_and_save! ( source :, bottle_json_dir : nil , removed_formulae_file : nil , pull_request : nil ,
57+ repository : nil , summary_file : nil )
58+ source_path = Pathname ( source )
59+ original_database = source_path . exist? ? source_path . read : nil
9160 db = ExecutablesDB . new source
92- db . update! ( update_existing :, install_missing :,
93- max_downloads :, eval_all :)
94- db . save!
9561
96- if summary_file
97- msg = summary_file_message ( db . changes )
98- File . open ( summary_file , "a" ) do |file |
99- file . puts ( msg )
100- end
62+ removed_formulae = if removed_formulae_file . blank? || !File . file? ( removed_formulae_file )
63+ [ ]
64+ else
65+ File . readlines ( removed_formulae_file , chomp : true ) . filter_map { |line | line . strip . presence }
10166 end
10267
103- return if !commit || !db . changed?
104-
105- msg = git_commit_message ( db . changes )
106- safe_system "git" , "-C" , db . root . to_s , "commit" , "-m" , msg , source
107- end
108-
109- sig { params ( els : T ::Array [ String ] , verb : String ) . returns ( String ) }
110- def english_list ( els , verb )
111- msg = +""
112- msg << els . slice ( 0 , 3 ) &.join ( ", " )
113- msg << " and #{ els . length - 3 } more" if msg . length < 40 && els . length > 3
114- "#{ verb . capitalize } #{ msg } "
115- end
68+ if pull_request
69+ repository = repository . presence || ENV [ "GITHUB_REPOSITORY" ] . presence
70+ if repository . blank?
71+ raise UsageError ,
72+ "`--repository` or `$GITHUB_REPOSITORY` is required with `--pull-request`."
73+ end
11674
117- sig { params ( changes : ExecutablesDB ::Changes ) . returns ( String ) }
118- def git_commit_message ( changes )
119- msg = [ ]
120- ExecutablesDB ::Changes ::TYPES . each do |action |
121- names = changes . send ( action )
122- next if names . empty?
75+ owner , repo = repository . split ( "/" , 2 )
76+ if owner . blank? || repo . blank? || repo . include? ( "/" )
77+ raise UsageError , "`--repository` must be in the form `owner/repo`."
78+ end
12379
124- action = "bump version for" if action == :version_bump
125- msg << english_list ( names . to_a . sort , action . to_s )
126- break
80+ GitHub ::API . paginate_rest ( GitHub . url_to ( "repos" , owner , repo , "pulls" , pull_request , "files" ) ) do |files |
81+ T . cast ( files , T ::Array [ T ::Hash [ String , T . untyped ] ] ) . each do |file |
82+ filename = file [ "filename" ] . to_s
83+ next if !filename . start_with? ( "Formula/" ) || !filename . end_with? ( ".rb" )
84+
85+ case file [ "status" ] . to_s
86+ when "removed"
87+ removed_formulae << File . basename ( filename , ".rb" )
88+ when "renamed"
89+ removed_formulae << File . basename ( file [ "previous_filename" ] . to_s , ".rb" )
90+ end
91+ end
92+ end
12793 end
12894
129- msg . join
130- end
95+ db . update! ( bottle_json_dir :, removed_formulae :)
96+ db . save!
97+ updated = original_database != source_path . read
13198
132- sig { params ( changes : ExecutablesDB ::Changes ) . returns ( String ) }
133- def summary_file_message ( changes )
134- msg = [ ]
135- ExecutablesDB ::Changes ::TYPES . each do |action |
136- names = changes . send ( action )
137- next if names . empty?
99+ if summary_file
100+ File . open ( summary_file , "a" ) do |file |
101+ file . puts <<~EOS
102+ ## Database Update Summary
138103
139- action_heading = action . to_s . split ( "_" ) . map ( &:capitalize ) . join ( " " )
140- msg << "### #{ action_heading } "
141- msg << ""
142- names . to_a . sort . each do |name |
143- msg << "- [`#{ name } `](https://formulae.brew.sh/formula/#{ name } )"
104+ #{ updated ? "Updated command-not-found database." : "No changes" }
105+ EOS
144106 end
145107 end
146108
147- msg << "No changes" if msg . empty?
148-
149- <<~MESSAGE
150- ## Database Update Summary
151-
152- #{ msg . join ( "\n " ) }
153- MESSAGE
109+ updated
154110 end
155111 end
156112 end
0 commit comments