Skip to content

Commit 37eaed5

Browse files
committed
Ensure that head Git URLs always specify a branch
- There's a TODO on the "someday" list [1] to ensure that `head` Git URLs always specify a branch. - So I thought I'd automate this worry by adding an audit. - Since `resource` block URLs tend to be pinned to SHAs, if indeed they are Git URLs, this audit only applies to `head` URLs. [1]: https://github.com/orgs/Homebrew/projects/5?pane=issue&itemId=98789749
1 parent dbe68ef commit 37eaed5

File tree

10 files changed

+102
-35
lines changed

10 files changed

+102
-35
lines changed

Library/Homebrew/formula.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3797,12 +3797,12 @@ def stable(&block)
37973797
# If called as a method this provides just the {url} for the {SoftwareSpec}.
37983798
# If a block is provided you can also add {.depends_on} and {Patch}es just to the {.head} {SoftwareSpec}.
37993799
# The download strategies (e.g. `:using =>`) are the same as for {url}.
3800-
# `master` is the default branch for Git and doesn't need stating with a `branch:` parameter.
3800+
# Git repositories must always specify `branch:`.
38013801
#
38023802
# ### Example
38033803
#
38043804
# ```ruby
3805-
# head "https://we.prefer.https.over.git.example.com/.git"
3805+
# head "https://we.prefer.https.over.git.example.com/.git", branch: "main"
38063806
# ```
38073807
#
38083808
# ```ruby

Library/Homebrew/resource_auditor.rb

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,23 @@ def audit_urls
178178
end
179179

180180
def audit_head_branch
181-
return unless @online
182-
return unless @strict
183181
return if spec_name != :head
184-
return unless Utils::Git.remote_exists?(url)
185182
return if specs[:tag].present?
186183
return if specs[:revision].present?
184+
# Skip `resource` URLs as they use SHAs instead of branch specifiers.
185+
return if name != owner.name
186+
return unless url.end_with?(".git")
187+
188+
problem "Git `head` URL must specify a branch name" if specs[:branch].blank?
189+
190+
return unless @online
191+
return unless Utils::Git.remote_exists?(url)
187192

188-
branch = Utils.popen_read("git", "ls-remote", "--symref", url, "HEAD")
189-
.match(%r{ref: refs/heads/(.*?)\s+HEAD})&.to_a&.second
190-
return if branch.blank? || branch == specs[:branch]
193+
detected_branch = Utils.popen_read("git", "ls-remote", "--symref", url, "HEAD")
194+
.match(%r{ref: refs/heads/(.*?)\s+HEAD})&.to_a&.second
195+
return if detected_branch.blank? || detected_branch == specs[:branch]
191196

192-
problem "Specify the default branch as `branch: \"#{branch}\"`"
197+
problem "Detected a default branch \"#{detected_branch}\", not \"#{specs[:branch]}\""
193198
end
194199

195200
def problem(text)

Library/Homebrew/test/dev-cmd/bump_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
content = <<~RUBY
1212
desc "HEAD-only test formula"
1313
homepage "https://brew.sh"
14-
head "https://github.com/Homebrew/brew.git"
14+
head "https://github.com/Homebrew/brew.git", branch: "main"
1515
RUBY
1616
setup_test_formula("headonly", content)
1717

Library/Homebrew/test/formula_auditor_spec.rb

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class Foo < Formula
285285
formula_text = <<~RUBY
286286
class Cask < Formula
287287
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
288-
head "https://github.com/cask/cask.git"
288+
head "https://github.com/cask/cask.git", branch: "main"
289289
license "GPL-3.0-or-later"
290290
end
291291
RUBY
@@ -301,7 +301,7 @@ class Cask < Formula
301301
formula_text = <<~RUBY
302302
class Cask < Formula
303303
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
304-
head "https://github.com/cask/cask.git"
304+
head "https://github.com/cask/cask.git", branch: "main"
305305
license all_of: ["GPL-3.0-or-later", "MIT"]
306306
end
307307
RUBY
@@ -317,7 +317,7 @@ class Cask < Formula
317317
formula_text = <<~RUBY
318318
class Cask < Formula
319319
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
320-
head "https://github.com/cask/cask.git"
320+
head "https://github.com/cask/cask.git", branch: "main"
321321
license "GPL-3.0-or-later" => { with: "LLVM-exception" }
322322
end
323323
RUBY
@@ -332,7 +332,7 @@ class Cask < Formula
332332
formula_text = <<~RUBY
333333
class Cask < Formula
334334
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
335-
head "https://github.com/cask/cask.git"
335+
head "https://github.com/cask/cask.git", branch: "main"
336336
license "GPL-3.0-or-later" => { with: "zzz" }
337337
end
338338
RUBY
@@ -351,7 +351,7 @@ class Cask < Formula
351351
formula_text = <<~RUBY
352352
class Cask < Formula
353353
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
354-
head "https://github.com/cask/cask.git"
354+
head "https://github.com/cask/cask.git", branch: "main"
355355
license "GPL-3.0-or-later" => { with: "#{deprecated_spdx_exception}" }
356356
end
357357
RUBY
@@ -371,7 +371,7 @@ class Cask < Formula
371371
fa = formula_auditor "cask", <<~RUBY, spdx_license_data:, online: true, new_formula: true
372372
class Cask < Formula
373373
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
374-
head "https://github.com/cask/cask.git"
374+
head "https://github.com/cask/cask.git", branch: "main"
375375
license "GPL-3.0-or-later"
376376
end
377377
RUBY
@@ -385,7 +385,7 @@ class Cask < Formula
385385
fa = formula_auditor "cask", <<~RUBY, spdx_license_data:, online: true, new_formula: true
386386
class Cask < Formula
387387
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
388-
head "https://github.com/cask/cask.git"
388+
head "https://github.com/cask/cask.git", branch: "main"
389389
license any_of: ["GPL-3.0-or-later", "MIT"]
390390
end
391391
RUBY
@@ -399,7 +399,7 @@ class Cask < Formula
399399
formula_text = <<~RUBY
400400
class Cask < Formula
401401
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
402-
head "https://github.com/cask/cask.git"
402+
head "https://github.com/cask/cask.git", branch: "main"
403403
license "0BSD"
404404
end
405405
RUBY
@@ -416,7 +416,7 @@ class Cask < Formula
416416
formula_text = <<~RUBY
417417
class Cask < Formula
418418
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
419-
head "https://github.com/cask/cask.git"
419+
head "https://github.com/cask/cask.git", branch: "main"
420420
license "0BSD"
421421
end
422422
RUBY
@@ -433,7 +433,7 @@ class Cask < Formula
433433
formula_text = <<~RUBY
434434
class Cask < Formula
435435
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
436-
head "https://github.com/cask/cask.git"
436+
head "https://github.com/cask/cask.git", branch: "main"
437437
license #{license_any_mismatch}
438438
end
439439
RUBY
@@ -450,7 +450,7 @@ class Cask < Formula
450450
formula_text = <<~RUBY
451451
class Cask < Formula
452452
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
453-
head "https://github.com/cask/cask.git"
453+
head "https://github.com/cask/cask.git", branch: "main"
454454
license #{license_any}
455455
end
456456
RUBY
@@ -714,6 +714,63 @@ class Foo < Formula
714714
expect(fa.problems).to be_empty
715715
end
716716

717+
it "requires `branch:` to be specified for Git head URLs" do
718+
fa = formula_auditor "foo", <<~RUBY
719+
class Foo < Formula
720+
url "https://brew.sh/foo-1.0.tgz"
721+
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
722+
head "https://github.com/example/foo.git"
723+
end
724+
RUBY
725+
726+
fa.audit_specs
727+
expect(fa.problems.first[:message]).to match("Git `head` URL must specify a branch name")
728+
end
729+
730+
it "suggests a detected default branch for Git head URLs" do
731+
fa = formula_auditor "foo", <<~RUBY, online: true
732+
class Foo < Formula
733+
url "https://brew.sh/foo-1.0.tgz"
734+
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
735+
head "https://github.com/Homebrew/homebrew-core.git", branch: "master"
736+
end
737+
RUBY
738+
739+
fa.audit_specs
740+
# This is `.last` because the first problem is the unreachable stable URL.
741+
expect(fa.problems.last[:message]).to match('Detected a default branch "main", not "master"')
742+
end
743+
744+
it "ignores `branch:` for non-Git head URLs" do
745+
fa = formula_auditor "foo", <<~RUBY
746+
class Foo < Formula
747+
url "https://brew.sh/foo-1.0.tgz"
748+
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
749+
head "https://brew.sh/foo.tgz", branch: "develop"
750+
end
751+
RUBY
752+
753+
fa.audit_specs
754+
expect(fa.problems).not_to match("Git `head` URL must specify a branch name")
755+
end
756+
757+
it "ignores `branch:` for `resource` URLs" do
758+
fa = formula_auditor "foo", <<~RUBY
759+
class Foo < Formula
760+
url "https://brew.sh/foo-1.0.tgz"
761+
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
762+
763+
resource "bar" do
764+
url "https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/bar.rb"
765+
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
766+
end
767+
end
768+
RUBY
769+
770+
fa.audit_specs
771+
expect(fa.problems).to be_empty
772+
end
773+
717774
it "allows versions with no throttle rate" do
718775
fa = formula_auditor "bar", <<~RUBY, core_tap: true
719776
class Bar < Formula
@@ -770,7 +827,7 @@ class Foo < Formula
770827
class Bar < Formula
771828
url "https://brew.sh/foo-1.0.tgz"
772829
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
773-
head "https://brew.sh/foo.git"
830+
head "https://brew.sh/foo.git", branch: "develop"
774831
end
775832
RUBY
776833

@@ -783,7 +840,7 @@ class Bar < Formula
783840
class BarAT1 < Formula
784841
url "https://brew.sh/foo-1.0.tgz"
785842
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
786-
head "https://brew.sh/foo.git"
843+
head "https://brew.sh/foo.git", branch: "develop"
787844
end
788845
RUBY
789846

@@ -796,7 +853,7 @@ class BarAT1 < Formula
796853
class Foo < Formula
797854
url "https://brew.sh/foo-1.0.tgz"
798855
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
799-
head "https://brew.sh/foo.git"
856+
head "https://brew.sh/foo.git", branch: "develop"
800857
end
801858
RUBY
802859

Library/Homebrew/test/livecheck/livecheck_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
desc "Test formula"
1818
homepage "https://brew.sh"
1919
url "https://brew.sh/test-0.0.1.tgz"
20-
head "https://github.com/Homebrew/brew.git"
20+
head "https://github.com/Homebrew/brew.git", branch: "main"
2121

2222
livecheck do
2323
url "https://formulae.brew.sh/api/formula/ruby.json"
@@ -252,7 +252,7 @@
252252
desc "Test formula with a duplicate URL"
253253
homepage "https://github.com/Homebrew/brew.git"
254254
url "https://brew.sh/test-0.0.1.tgz"
255-
head "https://github.com/Homebrew/brew.git"
255+
head "https://github.com/Homebrew/brew.git", branch: "main"
256256
end
257257
end
258258

Library/Homebrew/test/livecheck/skip_conditions_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
desc "Test formula"
1313
homepage "https://brew.sh"
1414
url "https://brew.sh/test-0.0.1.tgz"
15-
head "https://github.com/Homebrew/brew.git"
15+
head "https://github.com/Homebrew/brew.git", branch: "main"
1616

1717
livecheck do
1818
url "https://formulae.brew.sh/api/formula/ruby.json"
@@ -34,7 +34,7 @@
3434
head_only: formula("test_head_only") do
3535
desc "HEAD-only test formula"
3636
homepage "https://brew.sh"
37-
head "https://github.com/Homebrew/brew.git"
37+
head "https://github.com/Homebrew/brew.git", branch: "main"
3838
end,
3939
gist: formula("test_gist") do
4040
desc "Gist test formula"

Library/Homebrew/test/livecheck_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
formula do
99
homepage "https://brew.sh"
1010
url "https://brew.sh/test-0.0.1.tgz"
11-
head "https://github.com/Homebrew/brew.git"
11+
head "https://github.com/Homebrew/brew.git", branch: "main"
1212
end
1313
end
1414
let(:livecheck_f) { described_class.new(f.class) }

Library/Homebrew/test/rubocops/components_redundancy_spec.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Foo < Formula
2525
it "reports an offense if both `head` and `head do` are present" do
2626
expect_offense(<<~RUBY)
2727
class Foo < Formula
28-
head "https://brew.sh/foo.git"
28+
head "https://brew.sh/foo.git", branch: "develop"
2929
head do
3030
^^^^^^^ FormulaAudit/ComponentsRedundancy: `head` and `head do` should not be simultaneously present
3131
# stuff
@@ -50,7 +50,7 @@ class Foo < Formula
5050
it "reports no offenses if `stable do` is present with a `head` method" do
5151
expect_no_offenses(<<~RUBY)
5252
class Foo < Formula
53-
head "https://brew.sh/foo.git"
53+
head "https://brew.sh/foo.git", branch: "develop"
5454
5555
stable do
5656
# stuff
@@ -92,7 +92,7 @@ class Foo < Formula
9292
it "reports no offenses if `stable do` is present with `url` and `depends_on`" do
9393
expect_no_offenses(<<~RUBY)
9494
class Foo < Formula
95-
head "https://brew.sh/foo.git"
95+
head "https://brew.sh/foo.git", branch: "trunk"
9696
9797
stable do
9898
url "https://brew.sh/foo-1.0.tgz"
@@ -109,6 +109,7 @@ class Foo < Formula
109109
110110
head do
111111
url "https://brew.sh/foo.git"
112+
branch "develop"
112113
depends_on "bar"
113114
end
114115
end

Library/Homebrew/test/utils/ast/formula_ast_spec.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ class Foo < Formula
193193
194194
head do
195195
url "https://brew.sh/foo.git"
196+
branch "develop"
196197
end
197198
end
198199
RUBY
@@ -205,6 +206,7 @@ class Foo < Formula
205206
206207
head do
207208
url "https://brew.sh/foo.git"
209+
branch "develop"
208210
end
209211
end
210212
RUBY
@@ -355,7 +357,7 @@ class Foo < Formula
355357
described_class.new <<~RUBY.chomp
356358
class Foo < Formula
357359
url "https://brew.sh/foo-1.0.tar.gz"
358-
head "https://brew.sh/foo.git"
360+
head "https://brew.sh/foo.git", branch: "develop"
359361
end
360362
RUBY
361363
end
@@ -364,7 +366,7 @@ class Foo < Formula
364366
<<~RUBY.chomp
365367
class Foo < Formula
366368
url "https://brew.sh/foo-1.0.tar.gz"
367-
head "https://brew.sh/foo.git"
369+
head "https://brew.sh/foo.git", branch: "develop"
368370
369371
bottle do
370372
sha256 "f7b1fc772c79c20fddf621ccc791090bc1085fcef4da6cca03399424c66e06ca" => :sierra
@@ -387,6 +389,7 @@ class Foo < Formula
387389
388390
head do
389391
url "https://brew.sh/foo.git"
392+
branch "develop"
390393
end
391394
end
392395
RUBY
@@ -403,6 +406,7 @@ class Foo < Formula
403406
404407
head do
405408
url "https://brew.sh/foo.git"
409+
branch "develop"
406410
end
407411
end
408412
RUBY

docs/Formula-Cookbook.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ end
841841

842842
Formulae can specify an alternate download for the upstream project's development/cutting-edge source (e.g. `master`/`main`/`trunk`) using [`head`](https://rubydoc.brew.sh/Formula#head-class_method), which can be activated by passing `--HEAD` when installing. Specifying it is done in the same manner as [`url`](https://rubydoc.brew.sh/Formula#url-class_method), with added conventions for fetching from version control repositories:
843843

844-
* Git repositories need `branch:` specified to fetch a branch other than "master". If the repository is very large, specify `only_path` to [limit the checkout to one path](Cask-Cookbook.md#git-urls).
844+
* Git repositories **must always** specify `branch:`. If the repository is very large, specify `only_path` to [limit the checkout to one path](Cask-Cookbook.md#git-urls).
845845

846846
```sh
847847
head "https://github.com/some/package.git", branch: "main"

0 commit comments

Comments
 (0)