Skip to content

Commit

Permalink
brew-pin: prevent selected formulae from upgrade.
Browse files Browse the repository at this point in the history
* Added `pin` et. al. to manpage.
* Added `brew pin` to `brew.1` * Added `brew unpin` to `brew.1`
* Added `brew list --pinned` to `brew.1`
* Added information about frozen formulae to `brew upgrade` in `brew.1`
* Added `pin` et.al. to completion scripts.
* Unpin formulae when uninstalling them
* Unpin and re-pin formulae when upgrading (avoids stale symlink)

References Homebrew#18386.
Closes Homebrew#18515.

Signed-off-by: Mike McQuaid <mike@mikemcquaid.com>
  • Loading branch information
urdh authored and MikeMcQuaid committed Mar 30, 2013
1 parent 480072c commit 1e76317
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
!/share/man/man1/brew.1
.DS_Store
/Library/LinkedKegs
/Library/PinnedKegs
/Library/Taps
/Library/Formula/.gitignore
8 changes: 7 additions & 1 deletion Library/Contributions/brew_bash_completion.sh
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,12 @@ _brew_list ()
return
elif __brewcomp_words_include "--verbose"; then
return
elif __brewcomp_words_include "--pinned"; then
return
elif __brewcomp_words_include "--versions"; then
return
else
__brewcomp "--unbrewed --verbose --versions"
__brewcomp "--unbrewed --verbose --pinned --versions"
return
fi
;;
Expand Down Expand Up @@ -400,11 +402,13 @@ _brew ()
options
outdated
prune
pin
search
tap
test
uninstall remove rm
unlink
unpin
untap
update
upgrade
Expand Down Expand Up @@ -435,9 +439,11 @@ _brew ()
missing) __brew_complete_formulae ;;
options) _brew_options ;;
outdated) _brew_outdated ;;
pin) _brew_complete_formulae ;;
search|-S) _brew_search ;;
tap) __brew_complete_taps ;;
uninstall|remove|rm) _brew_uninstall ;;
unpin) __brew_complete_formulae ;;
untap) __brew_complete_tapped ;;
update) _brew_update ;;
uses) _brew_uses ;;
Expand Down
3 changes: 2 additions & 1 deletion Library/Contributions/brew_fish_completion.fish
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function __fish_complete_brew_argument
return 0
end

if contains -- $cmd cleanup link ln missing rm remove test unlink uninstall upgrade
if contains -- $cmd cleanup link ln missing rm remove test unlink uninstall upgrade pin unpin
ls (brew --prefix)/Cellar
return 0
end
Expand Down Expand Up @@ -148,6 +148,7 @@ complete -c brew -s f -l force -n '__fish_complete_brew_command link ln' -d "Ove
complete -c brew -s n -l dry-run -n '__fish_complete_brew_command link ln' -d "Show which files would be deleted"

complete -c brew -l unbrewed -n '__fish_complete_brew_command list ls' -d "List files in Homebrew prefix not installed by Homebrew"
complete -c brew -l pinned -n '__fish_complete_brew_command list ls' -d "Show the version number for (specified) pinned formulae"
complete -c brew -l versions -n '__fish_complete_brew_command list ls' -d "Show the version number for specified formulae"

complete -c brew -n '__fish_complete_brew_command log' -u
Expand Down
5 changes: 4 additions & 1 deletion Library/Contributions/brew_zsh_completion.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ _1st_arguments=(
'log:git commit log for a formula'
'missing:check all installed formuale for missing dependencies.'
'outdated:list formulae for which a newer version is available'
'pin:pin specified formulae'
'prune:remove dead links'
'remove:remove a formula'
'search:search for a formula (/regex/ or string)'
'server:start a local web app that lets you browse formulae (requires Sinatra)'
'unlink:unlink a formula'
'unpin:unpin specified formulae'
'update:freshen up links'
'upgrade:upgrade outdated formulae'
'uses:show formulae which depend on a formula'
Expand Down Expand Up @@ -71,6 +73,7 @@ case "$words[1]" in
list|ls)
_arguments \
'(--unbrewed)--unbrewed[files in brew --prefix not controlled by brew]' \
'(--pinned)--pinned[list all versions of pinned formulae]' \
'(--versions)--versions[list all installed versions of a formula]' \
'1: :->forms' && return 0

Expand All @@ -81,7 +84,7 @@ case "$words[1]" in
install|home|homepage|log|info|abv|uses|cat|deps|edit|options)
_brew_all_formulae
_wanted formulae expl 'all formulae' compadd -a formulae ;;
remove|rm|uninstall|unlink|cleanup|link|ln)
remove|rm|uninstall|unlink|cleanup|link|ln|pin|unpin)
_brew_installed_formulae
_wanted installed_formulae expl 'installed formulae' compadd -a installed_formulae ;;
upgrade)
Expand Down
19 changes: 16 additions & 3 deletions Library/Contributions/manpages/brew.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Note that these flags should only appear after a command.
be linked or which would be deleted by `brew link --overwrite`, but will not
actually link or delete any files.

* `ls, list [--unbrewed] [--versions]` [<formulae>]:
* `ls, list [--unbrewed] [--versions] [--pinned]` [<formulae>]:
Without any arguments, list all installed formulae.

If <formulae> are given, list the installed files for <formulae>.
Expand All @@ -208,6 +208,10 @@ Note that these flags should only appear after a command.
If `--versions` is passed, show the version number for installed formulae,
or only the specified formulae if <formulae> are given.

If `--pinned` is passed, show the versions of pinned formulae, or only the
specified (pinned) formulae if <formulae> are given.
See also [`pin`][], [`unpin`][].

* `log [git-log-options]` <formula> ...:
Show the git log for the given formulae. Options that `git-log`(1)
recognizes can be passed before the formula list.
Expand All @@ -233,6 +237,10 @@ Note that these flags should only appear after a command.
If `--quiet` is passed, list only the names of outdated brews. Otherwise,
the versions are printed as well.

* `pin` <formulae>:
Pin the specified <formulae>, preventing them from being upgraded when
issuing the `brew upgrade` command without arguments. See also [`unpin`][].

* `prune`:
Remove dead symlinks from the Homebrew prefix. This is generally not
needed, but can be useful when doing DIY installations.
Expand Down Expand Up @@ -273,6 +281,10 @@ Note that these flags should only appear after a command.
Unsymlink <formula> from the Homebrew prefix. This can be useful for
temporarily disabling a formula: `brew unlink foo && commands && brew link foo`.

* `unpin` <formulae>:
Unpin <formulae>, allowing them to be upgraded by `brew upgrade`. See also
[`pin`][].

* `untap` <tap>:
Remove a tapped repository.

Expand All @@ -283,9 +295,10 @@ Note that these flags should only appear after a command.
If `--rebase` is specified then `git pull --rebase` is used.

* `upgrade` [<formulae>]:
Upgrade outdated brews.
Upgrade outdated, non-pinned brews.

If <formulae> are given, upgrade only the specified brews.
If <formulae> are given, upgrade only the specified brews (but do so even
if they are pinned; see [`pin`][], [`unpin`][]).

* `uses [--installed] [--recursive]` <formula>:
Show the formulae that specify <formula> as a dependency.
Expand Down
17 changes: 16 additions & 1 deletion Library/Homebrew/cmd/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ def list
# Things below use the CELLAR, which doesn't until the first formula is installed.
return unless HOMEBREW_CELLAR.exist?

if ARGV.include? '--versions'
if ARGV.include? '--pinned'
require 'formula'
list_pinned
elsif ARGV.include? '--versions'
list_versions
elsif ARGV.named.empty?
ENV['CLICOLOR'] = nil
Expand Down Expand Up @@ -48,6 +51,18 @@ def list_versions
puts "#{d.basename} #{versions*' '}"
end
end

def list_pinned
if ARGV.named.empty?
HOMEBREW_CELLAR.children.select{ |pn| pn.directory? }
else
ARGV.named.map{ |n| HOMEBREW_CELLAR+n }.select{ |pn| pn.exist? }
end.select do |d|
Formula.factory(d.basename.to_s).pinned?
end.each do |d|
puts "#{d.basename}"
end
end
end

class PrettyListing
Expand Down
16 changes: 16 additions & 0 deletions Library/Homebrew/cmd/pin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'formula'

module Homebrew extend self
def pin
if Process.uid.zero? and not File.stat(HOMEBREW_BREW_FILE).uid.zero?
abort "Cowardly refusing to `sudo pin'"
end
raise FormulaUnspecifiedError if ARGV.named.empty?
ARGV.formulae.each do |fmla|
f = Formula.factory(fmla.to_s)
onoe "Cannot pin uninstalled formula #{f.name}!" unless f.pinable?
opoo "Formula #{f.name} already pinned!" if f.pinable? and f.pinned?
f.pin if f.pinable? and not f.pinned?
end
end
end
2 changes: 2 additions & 0 deletions Library/Homebrew/cmd/uninstall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def uninstall
keg.lock do
puts "Uninstalling #{keg}..."
keg.unlink
Formula.factory(keg.fname).unpin
keg.uninstall
rm_opt_link keg.fname
end
Expand All @@ -28,6 +29,7 @@ def uninstall
if keg.directory?
keg = Keg.new(keg)
keg.unlink
Formula.factory(keg.fname).unpin
keg.rmtree
end
end
Expand Down
16 changes: 16 additions & 0 deletions Library/Homebrew/cmd/unpin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'formula'

module Homebrew extend self
def unpin
if Process.uid.zero? and not File.stat(HOMEBREW_BREW_FILE).uid.zero?
abort "Cowardly refusing to `sudo unpin'"
end
raise FormulaUnspecifiedError if ARGV.named.empty?
ARGV.formulae.each do |fmla|
f = Formula.factory(fmla.to_s)
onoe "Cannot unpin uninstalled formula #{f.name}!" unless f.pinable?
opoo "Formula #{f.name} already unpinned!" if f.pinable? and not f.pinned?
f.unpin if f.pinable? and f.pinned?
end
end
end
20 changes: 19 additions & 1 deletion Library/Homebrew/cmd/upgrade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ def upgrade

if ARGV.named.empty?
require 'cmd/outdated'
upgrade_pinned = false
outdated = Homebrew.outdated_brews
else
upgrade_pinned = true
outdated = ARGV.formulae.select do |f|
if f.installed?
onoe "#{f}-#{f.installed_version} already installed"
Expand All @@ -32,10 +34,19 @@ def upgrade
exit 1 if outdated.empty?
end

if outdated.length > 1
unless upgrade_pinned
pinned = outdated.select { |f| f.pinned? }
outdated -= pinned
end

if outdated.length > 0
oh1 "Upgrading #{outdated.length} outdated package#{outdated.length.plural_s}, with result:"
puts outdated.map{ |f| "#{f.name} #{f.version}" } * ", "
end
if not upgrade_pinned and pinned.length > 0
oh1 "Not upgrading #{pinned.length} pinned package#{outdated.length.plural_s}:"
puts pinned.map{ |f| "#{f.name} #{f.version}" } * ", "
end

outdated.each do |f|
upgrade_formula f
Expand All @@ -60,6 +71,13 @@ def upgrade_formula f
installer.install
installer.caveats
installer.finish

# If the formula was pinned, and we were force-upgrading it, unpin and
# pin it again to get a symlink pointing to the correct keg.
if f.pinned?
f.unpin
f.pin
end
rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to upgrade f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on.
Expand Down
19 changes: 19 additions & 0 deletions Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'dependency_collector'
require 'formula_support'
require 'formula_lock'
require 'formula_pin'
require 'hardware'
require 'bottles'
require 'patches'
Expand Down Expand Up @@ -68,6 +69,8 @@ def initialize name='__UNKNOWN__', path=nil
# make sure to strip "--" from the start of options
self.class.build.add opt[/--(.+)$/, 1], desc
end

@pin = FormulaPin.new(self)
end

def url; @active_spec.url; end
Expand All @@ -80,6 +83,22 @@ def installed?
installed_prefix.children.length > 0 rescue false
end

def pinable?
@pin.pinable?
end

def pinned?
@pin.pinned?
end

def pin
@pin.pin
end

def unpin
@pin.unpin
end

def linked_keg
HOMEBREW_REPOSITORY/'Library/LinkedKegs'/@name
end
Expand Down
36 changes: 36 additions & 0 deletions Library/Homebrew/formula_pin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'formula'
require 'fileutils'

class FormulaPin
HOMEBREW_PINNED = HOMEBREW_LIBRARY/'PinnedKegs'

def initialize(formula)
@formula = formula
@name = formula.name
HOMEBREW_PINNED.mkdir unless HOMEBREW_PINNED.exist?
@path = HOMEBREW_PINNED/@name
end

def pin_at(version)
version_path = @formula.installed_prefix.parent.join(version)
FileUtils.ln_s version_path, @path unless pinned? or not version_path.exist?
end

def pin
versions = @formula.installed_prefix.parent.children.map { |item| item.basename.to_s }
version = versions.map { |item| Version.new(item) }.sort[0].to_s
pin_at(version)
end

def unpin
FileUtils.rm @path if pinned?
end

def pinned?
@path.exist?
end

def pinable?
@formula.installed_prefix.parent.children.length > 0
end
end
Loading

0 comments on commit 1e76317

Please sign in to comment.