Skip to content

Instantly share code, notes, and snippets.

@vladshablinsky
Last active December 27, 2016 11:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vladshablinsky/f85838450103acfce86ef53418e464c4 to your computer and use it in GitHub Desktop.
Save vladshablinsky/f85838450103acfce86ef53418e464c4 to your computer and use it in GitHub Desktop.
GSoC 2016 Homebrew results

Work on source modified time support. List of pull requests:

Add SubversionDownloadStrategy#source_modified_time

Override VCSDownloadStrategy#source_modified_time

download_strategy: use svn info --xml

download_strategy: cvs source_modified_time

Simplify Formula#outdated_versions logic

Update migration needed

Allow to store last commit hash of VCS. List of pull requests:

Implement VCSDownloadStrategy#last_commit

Implement HeadVersion. List of pull requests:

Implement HeadVersions

Fix HeadVersions

Test do fix for HEAD versions

Allow HEAD upgrades. List of pull requests:

Allow HEAD-upgrades

Fix update commit for non-HEAD kegs with head spec

Update upgrade/outdated documentation

Update --fetch-HEAD documentation

Add completions for --fetch-HEAD flag tab: fix Tab.for_formula versions

The major parts of the project

These PRs are the most important in the GSoC project:

  1. Last commits.
    This PR introduces functionality which allows to get last commit hash or revision for all the supported download strategies Homebrew has:
    Implement VCSDownloadStrategy#last_commit

  2. Head Versions
    This PR introduces HeadVersion class which is a subclass of Version, but to store HEAD versions. The difference between Version and HeadVersion is that HeadVersion also stores commit hash or revision of the formula and uses different algorithm of comparing HEAD versions.
    Implement HeadVersions

  3. Allow HEAD upgrades
    This PR is the result of the GSoC project. The outcome of it is that user can install HEAD version of some package and then whenever it's outdated a user don't need to uninstall the package and install it from scratch. Now it's possible to understand whether the HEAD version is outdated or not, and thus upgrade to the latest HEAD. Allow HEAD-upgrades

List of commits:

b40b072ed8486f91f3951bdd19bea3f04af3e75b: tab: fix Tab.for_formula versions (#687)

7fa9c0e9778d50f88ff7cf8ac18c7f198fd9c9fd: Update --fetch-HEAD documentation

b7dc6ce7ee0fe580b382bec9859fa1b6129e7930: Update zsh-completion for --fetch-HEAD

0e0342e18a66f35e11a97364a078af118bac688e: Add --fetch-HEAD completions to bash

b8ce1fe1b2abfc6b38a6c6451ef8074f2a83a8eb: Update upgrade/outdated documentation (#650)

072e5df4ed3afb0ca6a7bbd8e869d9ff8e5f8d73: Cache outdated_versions for Formula

9754dbada809260ccb8e61fedfdc8e20c9c93317: Update upgrade/outdated methods for head versions

04cb161ddb29ab26314684edc214650cdb192046: test_formula: add outdated_versions tests

001bef0604534adeb5f85d77e00a20e8a1542b7a: formula: detect outdated HEAD in outdated_versions

a59bdc4a2a429bbe5fb812bd4ca98cd437a319be: formula: don't return outdated head in installed_prefix

1b88c2912b9e0fb9b03580da3707ec36e2d0c888: formula: add new HEAD methods

00f37d67787ff5e06e754680d23ea015699ea6f7: Apply and add new download strategy tests

5ddee3502e7c8dd67b1fec4de2f72df6fd16cc94: download_strategy: use short hash for mercurial

09d21ad2586427b91734943a22e286255923bbcf: download_strategy: allow to suppress output

1693ddbdcbaa9f92355e46af2b28ae5d05afb516: Introduce GitHubGitDownloadStrategy

1114219384baf0948cabcce8752a4537896f7704: Add tests for Tab versions

42bc623a277c4379255fc86ee59be77cf9c63392: tab: allow to store versions

63c563f97074bdfb2ef8bf5388b216d137087c3c: Fix update commit for non-HEAD kegs with head spec (#644)

092d4712a1d00bc08eb0515d8e61f7859c7a2de0: Update commit when resolving the formula (#536)

242508fca4d2b167cef3c355722f3471594d7b4b: software_spec: use version dups for resources (#534)

4b2c4ef25835a87b5bac4fb26eae91dbfed93863: Update and test eligible_kegs_for_cleanup

3fb5d70a729472a7d7f2a5d0d7b84248921fb583: Unify Version.create usage

454003c4c1ea43f4fd84db96017636fc4c50b318: test_formula: test new HEAD methods

458f9a008cc5316de9ec18ebae3b0f3990583540: Apply tests to new HEAD format

00cdd5f481d409b1874b95c7f149c717ffb0259f: Add HeadVersion tests

2e916110e4c561b3e4175da099fc795e85ddb822: Use HeadVersion for install/reinstall

8a968a0b60dbc78f9f48be76762c9f050fa6416d: resource: detect HEAD versions

9ac58366048f7919a168ea2aebd7103b20b7f095: pkg_version: allow HeadVersion and HEAD revisions

80489dcb499a727c7613284aea4e744a690f12dc: version: introduce HeadVersion

45b3bfd11ac1d9d12d0e885576702eab2acc60cb: download_strategy: use short hash for git last_commit

0b2cc5c20db30f5d0046091f8c2318752f7b0659: test_download_strategies: add git tests

2f5f352baa95ce9cc6b4e0007ee2fc028ffc2a1a: VCSDownloadStrategy: add last_commit method

fbac41d95bc7d9500eed195b46aba2a95ed89b18: test_formula: improve test_migration_needed

4aedeea96d4c9d9c20bc822d520e453ac8964c56: formula: simplify migration_needed?

da06e813c2b8925499484ff8be7772f6aa6ae9e3: cmd/install: use migration_needed?

0d3b5f6849e236272d6a1b83a1869845608b3d10: test_formula: add migration_needed test

9c15174e3cfa9a1f832b6532ef7480ef2b16a44e: formula: simplify outdated_versions logic

d47df68cbd03fb621825d12a531f91938571ec04: test_formula: add outdated_versions tests

5703ebf49667e21f81564dd29be3e68f1df9e7c4: download_strategy: cvs source_modified_time (#268)

90d3317d7dc9d809d7fc8da15e824161a0ce3008: download_strategy: use svn info --xml (#174)

6f1116c8e159bbeb718b0dd1e7bf5e8f6408f012: download_strategy: fossil source_modified_time

f79edbc560b2a7b0cf6455dc2093081bfcde40c2: download_strategy: bazaar source_modified_time

155960d991a8f12b1176a55f0da8de1cb6f1ee24: download_strategy: mercurial source_modified_time

3ff1aa9fa3cb467a8fff912e780822a788303cff: download_strategy: add svn source_modified_time (#156)

All the commits made to Homebrew/brew

https://github.com/homebrew/brew/commits/master?author=vladshablinsky

List of commits and diffs:

commit b40b072ed8486f91f3951bdd19bea3f04af3e75b
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    tab: fix Tab.for_formula versions (#687)

diff --git a/Library/Homebrew/tab.rb b/Library/Homebrew/tab.rb
index 276fcfa..d5a95e0 100644
--- a/Library/Homebrew/tab.rb
+++ b/Library/Homebrew/tab.rb
@@ -138,6 +138,11 @@ class Tab < OpenStruct
         "path" => f.path.to_s,
         "tap" => f.tap ? f.tap.name : f.tap,
         "spec" => f.active_spec_sym.to_s,
+        "versions" => {
+          "stable" => f.stable ? f.stable.version.to_s : nil,
+          "devel" => f.devel ? f.devel.version.to_s : nil,
+          "head" => f.head ? f.head.version.to_s : nil,
+        }
       }
     end
 

commit 7fa9c0e9778d50f88ff7cf8ac18c7f198fd9c9fd
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Update --fetch-HEAD documentation

diff --git a/Library/Homebrew/cmd/outdated.rb b/Library/Homebrew/cmd/outdated.rb
index b0092a8..e421b24 100644
--- a/Library/Homebrew/cmd/outdated.rb
+++ b/Library/Homebrew/cmd/outdated.rb
@@ -12,9 +12,10 @@
 #:    If `--json=`<version> is passed, the output will be in JSON format. The only
 #:    valid version is `v1`.
 #:
-#:    If `--fetch-HEAD` is passed, fetch upstream repository to detect that HEAD
-#:    formula is outdated. Otherwise HEAD-installation is considered outdated if
-#:    new stable or devel version is bumped after that installation.
+#:    If `--fetch-HEAD` is passed, fetch the upstream repository to detect if
+#:    the HEAD installation of the formula is outdated. Otherwise, the
+#:    repository's HEAD will be checked for updates when a new stable or devel
+#:    version has been released.
 
 require "formula"
 require "keg"
diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb
index 2306e90..d0fa180 100644
--- a/Library/Homebrew/cmd/upgrade.rb
+++ b/Library/Homebrew/cmd/upgrade.rb
@@ -5,9 +5,10 @@
 #:
 #:    If `--cleanup` is specified then remove previously installed <formula> version(s).
 #:
-#:    If `--fetch-HEAD` is passed, fetch upstream repository to detect that HEAD
-#:    formula is outdated. Otherwise HEAD-installation is considered outdated if
-#:    new stable or devel version is bumped after that installation.
+#:    If `--fetch-HEAD` is passed, fetch the upstream repository to detect if
+#:    the HEAD installation of the formula is outdated. Otherwise, the
+#:    repository's HEAD will be checked for updates when a new stable or devel
+#:    version has been released.
 #:
 #:    If <formulae> are given, upgrade only the specified brews (but do so even
 #:    if they are pinned; see `pin`, `unpin`).
diff --git a/share/doc/homebrew/brew.1.html b/share/doc/homebrew/brew.1.html
index b9b6bf3..e25222c 100644
--- a/share/doc/homebrew/brew.1.html
+++ b/share/doc/homebrew/brew.1.html
@@ -307,9 +307,10 @@ precedence over <code>--verbose</code>).</p>
 <p>If <code>--json=</code><var>version</var> is passed, the output will be in JSON format. The only
 valid version is <code>v1</code>.</p>
 
-<p>If <code>--fetch-HEAD</code> is passed, fetch upstream repository to detect that HEAD
-formula is outdated. Otherwise HEAD-installation is considered outdated if
-new stable or devel version is bumped after that installation.</p></dd>
+<p>If <code>--fetch-HEAD</code> is passed, fetch the upstream repository to detect if
+the HEAD installation of the formula is outdated. Otherwise, the
+repository's HEAD will be checked for updates when a new stable or devel
+version has been released.</p></dd>
 <dt><code>pin</code> <var>formulae</var></dt><dd><p>Pin the specified <var>formulae</var>, preventing them from being upgraded when
 issuing the <code>brew upgrade</code> command. See also <code>unpin</code>.</p></dd>
 <dt><code>prune</code> [<code>--dry-run</code>]</dt><dd><p>Remove dead symlinks from the Homebrew prefix. This is generally not
@@ -446,9 +447,10 @@ source. This is useful for creating patches for the software.</p></dd>
 
 <p>If <code>--cleanup</code> is specified then remove previously installed <var>formula</var> version(s).</p>
 
-<p>If <code>--fetch-HEAD</code> is passed, fetch upstream repository to detect that HEAD
-formula is outdated. Otherwise HEAD-installation is considered outdated if
-new stable or devel version is bumped after that installation.</p>
+<p>If <code>--fetch-HEAD</code> is passed, fetch the upstream repository to detect if
+the HEAD installation of the formula is outdated. Otherwise, the
+repository's HEAD will be checked for updates when a new stable or devel
+version has been released.</p>
 
 <p>If <var>formulae</var> are given, upgrade only the specified brews (but do so even
 if they are pinned; see <code>pin</code>, <code>unpin</code>).</p></dd>
diff --git a/share/man/man1/brew.1 b/share/man/man1/brew.1
index f3c5f38..2b6a163 100644
--- a/share/man/man1/brew.1
+++ b/share/man/man1/brew.1
@@ -408,7 +408,7 @@ If \fB\-\-verbose\fR is passed, display detailed version information\.
 If \fB\-\-json=\fR\fIversion\fR is passed, the output will be in JSON format\. The only valid version is \fBv1\fR\.
 .
 .IP
-If \fB\-\-fetch\-HEAD\fR is passed, fetch upstream repository to detect that HEAD formula is outdated\. Otherwise HEAD\-installation is considered outdated if new stable or devel version is bumped after that installation\.
+If \fB\-\-fetch\-HEAD\fR is passed, fetch the upstream repository to detect if the HEAD installation of the formula is outdated\. Otherwise, the repository\'s HEAD will be checked for updates when a new stable or devel version has been released\.
 .
 .TP
 \fBpin\fR \fIformulae\fR
@@ -610,7 +610,7 @@ Options for the \fBinstall\fR command are also valid here\.
 If \fB\-\-cleanup\fR is specified then remove previously installed \fIformula\fR version(s)\.
 .
 .IP
-If \fB\-\-fetch\-HEAD\fR is passed, fetch upstream repository to detect that HEAD formula is outdated\. Otherwise HEAD\-installation is considered outdated if new stable or devel version is bumped after that installation\.
+If \fB\-\-fetch\-HEAD\fR is passed, fetch the upstream repository to detect if the HEAD installation of the formula is outdated\. Otherwise, the repository\'s HEAD will be checked for updates when a new stable or devel version has been released\.
 .
 .IP
 If \fIformulae\fR are given, upgrade only the specified brews (but do so even if they are pinned; see \fBpin\fR, \fBunpin\fR)\.

commit b7dc6ce7ee0fe580b382bec9859fa1b6129e7930
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Update zsh-completion for --fetch-HEAD

diff --git a/share/zsh/site-functions/_brew b/share/zsh/site-functions/_brew
index 57b07d1..f4f5ac8 100644
--- a/share/zsh/site-functions/_brew
+++ b/share/zsh/site-functions/_brew
@@ -139,6 +139,7 @@ case "$words[1]" in
   upgrade)
     _arguments \
       '(--cleanup)--cleanup[remove previously installed formula version(s)]' \
+      '(--fetch-HEAD)--fetch-HEAD[detect outdated installation by fetching the repo]' \
       '1: :->forms' && return 0
 
     if [[ "$state" == forms ]]; then

commit 0e0342e18a66f35e11a97364a078af118bac688e
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Add --fetch-HEAD completions to bash

diff --git a/etc/bash_completion.d/brew b/etc/bash_completion.d/brew
index 40a1174..67c4c6a 100644
--- a/etc/bash_completion.d/brew
+++ b/etc/bash_completion.d/brew
@@ -343,7 +343,7 @@ _brew_outdated() {
   local cur="${COMP_WORDS[COMP_CWORD]}"
   case "$cur" in
     --*)
-      __brewcomp "--quiet --json=v1"
+      __brewcomp "--quiet --json=v1 --fetch-HEAD"
       return
       ;;
   esac
@@ -520,6 +520,7 @@ _brew_upgrade() {
         --cleanup
         --debug
         --verbose
+        --fetch-HEAD
         "
       return
       ;;

commit b8ce1fe1b2abfc6b38a6c6451ef8074f2a83a8eb
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    Update upgrade/outdated documentation (#650)

diff --git a/Library/Homebrew/cmd/outdated.rb b/Library/Homebrew/cmd/outdated.rb
index 7e93644..b0092a8 100644
--- a/Library/Homebrew/cmd/outdated.rb
+++ b/Library/Homebrew/cmd/outdated.rb
@@ -1,4 +1,4 @@
-#:  * `outdated` [`--quiet`|`--verbose`|`--json=v1`]:
+#:  * `outdated` [`--quiet`|`--verbose`|`--json=v1`] [`--fetch-HEAD`]:
 #:    Show formulae that have an updated version available.
 #:
 #:    By default, version information is displayed in interactive shells, and
@@ -11,6 +11,10 @@
 #:
 #:    If `--json=`<version> is passed, the output will be in JSON format. The only
 #:    valid version is `v1`.
+#:
+#:    If `--fetch-HEAD` is passed, fetch upstream repository to detect that HEAD
+#:    formula is outdated. Otherwise HEAD-installation is considered outdated if
+#:    new stable or devel version is bumped after that installation.
 
 require "formula"
 require "keg"
diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb
index 1933c05..2306e90 100644
--- a/Library/Homebrew/cmd/upgrade.rb
+++ b/Library/Homebrew/cmd/upgrade.rb
@@ -1,10 +1,14 @@
-#:  * `upgrade` [<install-options>] [`--cleanup`] [<formulae>]:
+#:  * `upgrade` [<install-options>] [`--cleanup`] [`--fetch-HEAD`] [<formulae>]:
 #:    Upgrade outdated, unpinned brews.
 #:
 #:    Options for the `install` command are also valid here.
 #:
 #:    If `--cleanup` is specified then remove previously installed <formula> version(s).
 #:
+#:    If `--fetch-HEAD` is passed, fetch upstream repository to detect that HEAD
+#:    formula is outdated. Otherwise HEAD-installation is considered outdated if
+#:    new stable or devel version is bumped after that installation.
+#:
 #:    If <formulae> are given, upgrade only the specified brews (but do so even
 #:    if they are pinned; see `pin`, `unpin`).
 
diff --git a/share/doc/homebrew/brew.1.html b/share/doc/homebrew/brew.1.html
index ad1b1f3..b9b6bf3 100644
--- a/share/doc/homebrew/brew.1.html
+++ b/share/doc/homebrew/brew.1.html
@@ -294,7 +294,7 @@ spaces.</p>
 <p>If <code>--all</code> is passed, show options for all formulae.</p>
 
 <p>If <code>--installed</code> is passed, show options for all installed formulae.</p></dd>
-<dt><code>outdated</code> [<code>--quiet</code>|<code>--verbose</code>|<code>--json=v1</code>]</dt><dd><p>Show formulae that have an updated version available.</p>
+<dt><code>outdated</code> [<code>--quiet</code>|<code>--verbose</code>|<code>--json=v1</code>] [<code>--fetch-HEAD</code>]</dt><dd><p>Show formulae that have an updated version available.</p>
 
 <p>By default, version information is displayed in interactive shells, and
 suppressed otherwise.</p>
@@ -305,7 +305,11 @@ precedence over <code>--verbose</code>).</p>
 <p>If <code>--verbose</code> is passed, display detailed version information.</p>
 
 <p>If <code>--json=</code><var>version</var> is passed, the output will be in JSON format. The only
-valid version is <code>v1</code>.</p></dd>
+valid version is <code>v1</code>.</p>
+
+<p>If <code>--fetch-HEAD</code> is passed, fetch upstream repository to detect that HEAD
+formula is outdated. Otherwise HEAD-installation is considered outdated if
+new stable or devel version is bumped after that installation.</p></dd>
 <dt><code>pin</code> <var>formulae</var></dt><dd><p>Pin the specified <var>formulae</var>, preventing them from being upgraded when
 issuing the <code>brew upgrade</code> command. See also <code>unpin</code>.</p></dd>
 <dt><code>prune</code> [<code>--dry-run</code>]</dt><dd><p>Remove dead symlinks from the Homebrew prefix. This is generally not
@@ -436,12 +440,16 @@ source. This is useful for creating patches for the software.</p></dd>
 
 <p>If <code>--merge</code> is specified then <code>git merge</code> is used to include updates
   (rather than <code>git rebase</code>).</p></dd>
-<dt><code>upgrade</code> [<var>install-options</var>] [<code>--cleanup</code>] [<var>formulae</var>]</dt><dd><p>Upgrade outdated, unpinned brews.</p>
+<dt><code>upgrade</code> [<var>install-options</var>] [<code>--cleanup</code>] [<code>--fetch-HEAD</code>] [<var>formulae</var>]</dt><dd><p>Upgrade outdated, unpinned brews.</p>
 
 <p>Options for the <code>install</code> command are also valid here.</p>
 
 <p>If <code>--cleanup</code> is specified then remove previously installed <var>formula</var> version(s).</p>
 
+<p>If <code>--fetch-HEAD</code> is passed, fetch upstream repository to detect that HEAD
+formula is outdated. Otherwise HEAD-installation is considered outdated if
+new stable or devel version is bumped after that installation.</p>
+
 <p>If <var>formulae</var> are given, upgrade only the specified brews (but do so even
 if they are pinned; see <code>pin</code>, <code>unpin</code>).</p></dd>
 <dt><code>uses</code> [<code>--installed</code>] [<code>--recursive</code>] [<code>--include-build</code>] [<code>--include-optional</code>] [<code>--skip-recommended</code>] [<code>--devel</code>|<code>--HEAD</code>] <var>formulae</var></dt><dd><p>Show the formulae that specify <var>formulae</var> as a dependency. When given
diff --git a/share/man/man1/brew.1 b/share/man/man1/brew.1
index 8777111..f3c5f38 100644
--- a/share/man/man1/brew.1
+++ b/share/man/man1/brew.1
@@ -392,7 +392,7 @@ If \fB\-\-all\fR is passed, show options for all formulae\.
 If \fB\-\-installed\fR is passed, show options for all installed formulae\.
 .
 .TP
-\fBoutdated\fR [\fB\-\-quiet\fR|\fB\-\-verbose\fR|\fB\-\-json=v1\fR]
+\fBoutdated\fR [\fB\-\-quiet\fR|\fB\-\-verbose\fR|\fB\-\-json=v1\fR] [\fB\-\-fetch\-HEAD\fR]
 Show formulae that have an updated version available\.
 .
 .IP
@@ -407,6 +407,9 @@ If \fB\-\-verbose\fR is passed, display detailed version information\.
 .IP
 If \fB\-\-json=\fR\fIversion\fR is passed, the output will be in JSON format\. The only valid version is \fBv1\fR\.
 .
+.IP
+If \fB\-\-fetch\-HEAD\fR is passed, fetch upstream repository to detect that HEAD formula is outdated\. Otherwise HEAD\-installation is considered outdated if new stable or devel version is bumped after that installation\.
+.
 .TP
 \fBpin\fR \fIformulae\fR
 Pin the specified \fIformulae\fR, preventing them from being upgraded when issuing the \fBbrew upgrade\fR command\. See also \fBunpin\fR\.
@@ -597,7 +600,7 @@ Fetch the newest version of Homebrew and all formulae from GitHub using \fBgit\f
 If \fB\-\-merge\fR is specified then \fBgit merge\fR is used to include updates (rather than \fBgit rebase\fR)\.
 .
 .TP
-\fBupgrade\fR [\fIinstall\-options\fR] [\fB\-\-cleanup\fR] [\fIformulae\fR]
+\fBupgrade\fR [\fIinstall\-options\fR] [\fB\-\-cleanup\fR] [\fB\-\-fetch\-HEAD\fR] [\fIformulae\fR]
 Upgrade outdated, unpinned brews\.
 .
 .IP
@@ -607,6 +610,9 @@ Options for the \fBinstall\fR command are also valid here\.
 If \fB\-\-cleanup\fR is specified then remove previously installed \fIformula\fR version(s)\.
 .
 .IP
+If \fB\-\-fetch\-HEAD\fR is passed, fetch upstream repository to detect that HEAD formula is outdated\. Otherwise HEAD\-installation is considered outdated if new stable or devel version is bumped after that installation\.
+.
+.IP
 If \fIformulae\fR are given, upgrade only the specified brews (but do so even if they are pinned; see \fBpin\fR, \fBunpin\fR)\.
 .
 .TP

commit 072e5df4ed3afb0ca6a7bbd8e869d9ff8e5f8d73
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Cache outdated_versions for Formula

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index d7d2f16..3730b13 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -1001,24 +1001,28 @@ class Formula
 
   # @private
   def outdated_versions(options = {})
-    @outdated_versions ||= begin
-      all_versions = []
-
+    @outdated_versions ||= Hash.new do |cache, key|
       raise Migrator::MigrationNeededError.new(self) if migration_needed?
+      cache[key] = _outdated_versions(key)
+    end
+    @outdated_versions[options]
+  end
 
-      installed_kegs.each do |keg|
-        version = keg.version
-        all_versions << version
+  def _outdated_versions(options = {})
+    all_versions = []
 
-        return [] if pkg_version <= version && !version.head?
-      end
+    installed_kegs.each do |keg|
+      version = keg.version
+      all_versions << version
 
-      head_version = latest_head_version
-      if head_version
-        head_version_outdated?(head_version, options) ? all_versions.sort! : []
-      else
-        all_versions.sort!
-      end
+      return [] if pkg_version <= version && !version.head?
+    end
+
+    head_version = latest_head_version
+    if head_version && !head_version_outdated?(head_version, options)
+      []
+    else
+      all_versions.sort
     end
   end
 
diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 3f39384..af98c35 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -583,6 +583,7 @@ class OutdatedVersionsTests < Homebrew::TestCase
 
   def reset_outdated_versions
     f.instance_variable_set(:@outdated_versions, nil)
+    f.instance_variable_set(:@outdated_versions_head_fetched, nil)
   end
 
   def test_greater_different_tap_installed

commit 9754dbada809260ccb8e61fedfdc8e20c9c93317
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Update upgrade/outdated methods for head versions

diff --git a/Library/Homebrew/cmd/outdated.rb b/Library/Homebrew/cmd/outdated.rb
index a318b65..7e93644 100644
--- a/Library/Homebrew/cmd/outdated.rb
+++ b/Library/Homebrew/cmd/outdated.rb
@@ -32,10 +32,19 @@ module Homebrew
 
   def print_outdated(formulae)
     verbose = ($stdout.tty? || ARGV.verbose?) && !ARGV.flag?("--quiet")
+    fetch_head = ARGV.fetch_head?
 
-    formulae.select(&:outdated?).each do |f|
+    outdated_formulae = formulae.select { |f| f.outdated?(:fetch_head => fetch_head) }
+
+    outdated_formulae.each do |f|
       if verbose
-        puts "#{f.full_name} (#{f.outdated_versions*", "} < #{f.pkg_version})"
+        outdated_versions = f.outdated_versions(:fetch_head => fetch_head)
+        current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
+          "latest HEAD"
+        else
+          f.pkg_version.to_s
+        end
+        puts "#{f.full_name} (#{outdated_versions.join(", ")}) < #{current_version}"
       else
         puts f.full_name
       end
@@ -44,11 +53,20 @@ module Homebrew
 
   def print_outdated_json(formulae)
     json = []
-    outdated = formulae.select(&:outdated?).each do |f|
+    fetch_head = ARGV.fetch_head?
+    outdated_formulae = formulae.select { |f| f.outdated?(:fetch_head => fetch_head) }
+
+    outdated = outdated_formulae.each do |f|
+      outdated_versions = f.outdated_versions(:fetch_head => fetch_head)
+      current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
+        "HEAD"
+      else
+        f.pkg_version.to_s
+      end
 
       json << { :name => f.full_name,
-                :installed_versions => f.outdated_versions.collect(&:to_s),
-                :current_version => f.pkg_version.to_s }
+                :installed_versions => outdated_versions.collect(&:to_s),
+                :current_version => current_version }
     end
     puts Utils::JSON.dump(json)
 
diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb
index c96d2b1..1933c05 100644
--- a/Library/Homebrew/cmd/upgrade.rb
+++ b/Library/Homebrew/cmd/upgrade.rb
@@ -19,10 +19,15 @@ module Homebrew
     Homebrew.perform_preinstall_checks
 
     if ARGV.named.empty?
-      outdated = Formula.installed.select(&:outdated?)
+      outdated = Formula.installed.select do |f|
+        f.outdated?(:fetch_head => ARGV.fetch_head?)
+      end
+
       exit 0 if outdated.empty?
     else
-      outdated = ARGV.resolved_formulae.select(&:outdated?)
+      outdated = ARGV.resolved_formulae.select do |f|
+        f.outdated?(:fetch_head => ARGV.fetch_head?)
+      end
 
       (ARGV.resolved_formulae - outdated).each do |f|
         versions = f.installed_kegs.map { |keg| keg.version }
diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb
index 4a49795..adceee2 100644
--- a/Library/Homebrew/extend/ARGV.rb
+++ b/Library/Homebrew/extend/ARGV.rb
@@ -223,6 +223,10 @@ module HomebrewArgvExtension
     include? "--force-bottle"
   end
 
+  def fetch_head?
+    include? "--fetch-HEAD"
+  end
+
   # eg. `foo -ns -i --bar` has three switches, n, s and i
   def switch?(char)
     return false if char.length > 1

commit 04cb161ddb29ab26314684edc214650cdb192046
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    test_formula: add outdated_versions tests

diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 2c4935d..3f39384 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -134,6 +134,47 @@ class FormulaTests < Homebrew::TestCase
     f.rack.rmtree
   end
 
+  def test_installed_prefix_outdated_stable_head_installed
+    f = formula do
+      url "foo"
+      version "1.9"
+      head "foo"
+    end
+
+    head_prefix = HOMEBREW_CELLAR/"#{f.name}/HEAD"
+    head_prefix.mkpath
+    tab = Tab.empty
+    tab.tabfile = head_prefix.join("INSTALL_RECEIPT.json")
+    tab.source["versions"] = { "stable" => "1.0" }
+    tab.write
+
+    assert_equal HOMEBREW_CELLAR/"#{f.name}/#{f.version}", f.installed_prefix
+  ensure
+    f.rack.rmtree
+  end
+
+  def test_installed_prefix_outdated_devel_head_installed
+    f = formula do
+      url "foo"
+      version "1.9"
+      devel do
+        url "foo"
+        version "2.1"
+      end
+    end
+
+    head_prefix = HOMEBREW_CELLAR/"#{f.name}/HEAD"
+    head_prefix.mkpath
+    tab = Tab.empty
+    tab.tabfile = head_prefix.join("INSTALL_RECEIPT.json")
+    tab.source["versions"] = { "stable" => "1.9", "devel" => "2.0" }
+    tab.write
+
+    assert_equal HOMEBREW_CELLAR/"#{f.name}/#{f.version}", f.installed_prefix
+  ensure
+    f.rack.rmtree
+  end
+
   def test_installed_prefix_head
     f = formula("test", Pathname.new(__FILE__).expand_path, :head) do
       head "foo"
@@ -526,60 +567,67 @@ class OutdatedVersionsTests < Homebrew::TestCase
   end
 
   def teardown
-    @f.rack.rmtree
+    @f.rack.rmtree if @f.rack.exist?
   end
 
-  def setup_tab_for_prefix(prefix, tap_string = nil)
+  def setup_tab_for_prefix(prefix, options = {})
     prefix.mkpath
     tab = Tab.empty
     tab.tabfile = prefix.join("INSTALL_RECEIPT.json")
-    tab.source["tap"] = tap_string if tap_string
-    tab.write
+    tab.source["tap"] = options[:tap] if options[:tap]
+    tab.source["versions"] = options[:versions] if options[:versions]
+    tab.source_modified_time = options[:source_modified_time].to_i
+    tab.write unless options[:no_write]
     tab
   end
 
+  def reset_outdated_versions
+    f.instance_variable_set(:@outdated_versions, nil)
+  end
+
   def test_greater_different_tap_installed
-    setup_tab_for_prefix(greater_prefix, "user/repo")
+    setup_tab_for_prefix(greater_prefix, :tap => "user/repo")
     assert_predicate f.outdated_versions, :empty?
   end
 
   def test_greater_same_tap_installed
     f.instance_variable_set(:@tap, CoreTap.instance)
-    setup_tab_for_prefix(greater_prefix, "homebrew/core")
+    setup_tab_for_prefix(greater_prefix, :tap => "homebrew/core")
     assert_predicate f.outdated_versions, :empty?
   end
 
   def test_outdated_different_tap_installed
-    setup_tab_for_prefix(outdated_prefix, "user/repo")
+    setup_tab_for_prefix(outdated_prefix, :tap => "user/repo")
     refute_predicate f.outdated_versions, :empty?
   end
 
   def test_outdated_same_tap_installed
     f.instance_variable_set(:@tap, CoreTap.instance)
-    setup_tab_for_prefix(outdated_prefix, "homebrew/core")
+    setup_tab_for_prefix(outdated_prefix, :tap => "homebrew/core")
     refute_predicate f.outdated_versions, :empty?
   end
 
   def test_same_head_installed
     f.instance_variable_set(:@tap, CoreTap.instance)
-    setup_tab_for_prefix(head_prefix, "homebrew/core")
+    setup_tab_for_prefix(head_prefix, :tap => "homebrew/core")
     assert_predicate f.outdated_versions, :empty?
   end
 
   def test_different_head_installed
     f.instance_variable_set(:@tap, CoreTap.instance)
-    setup_tab_for_prefix(head_prefix, "user/repo")
+    setup_tab_for_prefix(head_prefix, :tap => "user/repo")
     assert_predicate f.outdated_versions, :empty?
   end
 
   def test_mixed_taps_greater_version_installed
     f.instance_variable_set(:@tap, CoreTap.instance)
-    setup_tab_for_prefix(outdated_prefix, "homebrew/core")
-    setup_tab_for_prefix(greater_prefix, "user/repo")
+    setup_tab_for_prefix(outdated_prefix, :tap => "homebrew/core")
+    setup_tab_for_prefix(greater_prefix, :tap => "user/repo")
 
     assert_predicate f.outdated_versions, :empty?
 
-    setup_tab_for_prefix(greater_prefix, "homebrew/core")
+    setup_tab_for_prefix(greater_prefix, :tap => "homebrew/core")
+    reset_outdated_versions
 
     assert_predicate f.outdated_versions, :empty?
   end
@@ -590,23 +638,97 @@ class OutdatedVersionsTests < Homebrew::TestCase
     extra_outdated_prefix = HOMEBREW_CELLAR/"#{f.name}/1.0"
 
     setup_tab_for_prefix(outdated_prefix)
-    setup_tab_for_prefix(extra_outdated_prefix, "homebrew/core")
+    setup_tab_for_prefix(extra_outdated_prefix, :tap => "homebrew/core")
+    reset_outdated_versions
 
     refute_predicate f.outdated_versions, :empty?
 
-    setup_tab_for_prefix(outdated_prefix, "user/repo")
+    setup_tab_for_prefix(outdated_prefix, :tap => "user/repo")
+    reset_outdated_versions
 
     refute_predicate f.outdated_versions, :empty?
   end
 
   def test_same_version_tap_installed
     f.instance_variable_set(:@tap, CoreTap.instance)
-    setup_tab_for_prefix(same_prefix, "homebrew/core")
+    setup_tab_for_prefix(same_prefix, :tap => "homebrew/core")
+
+    assert_predicate f.outdated_versions, :empty?
+
+    setup_tab_for_prefix(same_prefix, :tap => "user/repo")
+    reset_outdated_versions
 
     assert_predicate f.outdated_versions, :empty?
+  end
+
+  def test_outdated_installed_head_less_than_stable
+    tab = setup_tab_for_prefix(head_prefix, :versions => { "stable" => "1.0" })
+    refute_predicate f.outdated_versions, :empty?
 
-    setup_tab_for_prefix(same_prefix, "user/repo")
+    # Tab.for_keg(head_prefix) will be fetched from CACHE but we write it anyway
+    tab.source["versions"] = { "stable" => f.version.to_s }
+    tab.write
+    reset_outdated_versions
 
     assert_predicate f.outdated_versions, :empty?
   end
+
+  def test_outdated_fetch_head
+    outdated_stable_prefix = HOMEBREW_CELLAR.join("testball/1.0")
+    head_prefix_a = HOMEBREW_CELLAR.join("testball/HEAD")
+    head_prefix_b = HOMEBREW_CELLAR.join("testball/HEAD-aaaaaaa_1")
+    head_prefix_c = HOMEBREW_CELLAR.join("testball/HEAD-5658946")
+
+    setup_tab_for_prefix(outdated_stable_prefix)
+    tab_a = setup_tab_for_prefix(head_prefix_a, :versions => { "stable" => "1.0" })
+    setup_tab_for_prefix(head_prefix_b)
+
+    initial_env = ENV.to_hash
+    testball_repo = HOMEBREW_PREFIX.join("testball_repo")
+    testball_repo.mkdir
+
+    @f = formula("testball") do
+      url "foo"
+      version "2.10"
+      head "file://#{testball_repo}", :using => :git
+    end
+
+    %w[AUTHOR COMMITTER].each do |role|
+      ENV["GIT_#{role}_NAME"] = "brew tests"
+      ENV["GIT_#{role}_EMAIL"] = "brew-tests@localhost"
+      ENV["GIT_#{role}_DATE"] = "Thu May 21 00:04:11 2009 +0100"
+    end
+
+    testball_repo.cd do
+      FileUtils.touch "LICENSE"
+      shutup do
+        system "git", "init"
+        system "git", "add", "--all"
+        system "git", "commit", "-m", "Initial commit"
+      end
+    end
+
+    refute_predicate f.outdated_versions(:fetch_head => true), :empty?
+
+    tab_a.source["versions"] = { "stable" => f.version.to_s }
+    tab_a.write
+    reset_outdated_versions
+    refute_predicate f.outdated_versions(:fetch_head => true), :empty?
+
+    head_prefix_a.rmtree
+    reset_outdated_versions
+    refute_predicate f.outdated_versions(:fetch_head => true), :empty?
+
+    setup_tab_for_prefix(head_prefix_c, :source_modified_time => 1)
+    reset_outdated_versions
+    assert_predicate f.outdated_versions(:fetch_head => true), :empty?
+  ensure
+    ENV.replace(initial_env)
+    testball_repo.rmtree if testball_repo.exist?
+    outdated_stable_prefix.rmtree if outdated_stable_prefix.exist?
+    head_prefix_b.rmtree if head_prefix.exist?
+    head_prefix_c.rmtree if head_prefix_c.exist?
+    FileUtils.rm_rf HOMEBREW_CACHE/"testball--git"
+    FileUtils.rm_rf HOMEBREW_CELLAR/"testball"
+  end
 end

commit 001bef0604534adeb5f85d77e00a20e8a1542b7a
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    formula: detect outdated HEAD in outdated_versions

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index d3a6087..d7d2f16 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -1000,7 +1000,7 @@ class Formula
   end
 
   # @private
-  def outdated_versions
+  def outdated_versions(options = {})
     @outdated_versions ||= begin
       all_versions = []
 
@@ -1009,16 +1009,22 @@ class Formula
       installed_kegs.each do |keg|
         version = keg.version
         all_versions << version
-        return [] if pkg_version <= version
+
+        return [] if pkg_version <= version && !version.head?
       end
 
-      all_versions.sort!
+      head_version = latest_head_version
+      if head_version
+        head_version_outdated?(head_version, options) ? all_versions.sort! : []
+      else
+        all_versions.sort!
+      end
     end
   end
 
   # @private
-  def outdated?
-    !outdated_versions.empty?
+  def outdated?(options = {})
+    !outdated_versions(options).empty?
   rescue Migrator::MigrationNeededError
     true
   end

commit a59bdc4a2a429bbe5fb812bd4ca98cd437a319be
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    formula: don't return outdated head in installed_prefix

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index d0476ec..d3a6087 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -449,8 +449,8 @@ class Formula
   # and then {#stable}'s {#prefix}
   # @private
   def installed_prefix
-    if head && (head_prefix = latest_head_prefix) && head_prefix.directory?
-      head_prefix
+    if head && (head_version = latest_head_version) && !head_version_outdated?(head_version)
+      latest_head_prefix
     elsif devel && (devel_prefix = prefix(PkgVersion.new(devel.version, revision))).directory?
       devel_prefix
     elsif stable && (stable_prefix = prefix(PkgVersion.new(stable.version, revision))).directory?

commit 1b88c2912b9e0fb9b03580da3707ec36e2d0c888
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    formula: add new HEAD methods

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index a941658..d0476ec 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -413,16 +413,36 @@ class Formula
     Pathname.new("#{HOMEBREW_LIBRARY}/LinkedKegs/#{name}")
   end
 
-  def latest_head_prefix
+  def latest_head_version
     head_versions = installed_prefixes.map do |pn|
       pn_pkgversion = PkgVersion.parse(pn.basename.to_s)
       pn_pkgversion if pn_pkgversion.head?
     end.compact
 
-    latest_head_version = head_versions.max_by do |pn_pkgversion|
+    head_versions.max_by do |pn_pkgversion|
       [Tab.for_keg(prefix(pn_pkgversion)).source_modified_time, pn_pkgversion.revision]
     end
-    prefix(latest_head_version) if latest_head_version
+  end
+
+  def latest_head_prefix
+    head_version = latest_head_version
+    prefix(head_version) if head_version
+  end
+
+  def head_version_outdated?(version, options={})
+    tab = Tab.for_keg(prefix(version))
+
+    return true if stable && tab.stable_version && tab.stable_version < stable.version
+    return true if devel && tab.devel_version && tab.devel_version < devel.version
+
+    if options[:fetch_head]
+      return false unless head && head.downloader.is_a?(VCSDownloadStrategy)
+      downloader = head.downloader
+      downloader.shutup! unless ARGV.verbose?
+      downloader.commit_outdated?(version.version.commit)
+    else
+      false
+    end
   end
 
   # The latest prefix for this formula. Checks for {#head}, then {#devel}

commit 00f37d67787ff5e06e754680d23ea015699ea6f7
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Apply and add new download strategy tests

diff --git a/Library/Homebrew/test/test_download_strategies.rb b/Library/Homebrew/test/test_download_strategies.rb
index 9a53d48..3f371d4 100644
--- a/Library/Homebrew/test/test_download_strategies.rb
+++ b/Library/Homebrew/test/test_download_strategies.rb
@@ -69,7 +69,6 @@ class GitDownloadStrategyTests < Homebrew::TestCase
     @strategy = GitDownloadStrategy.new("baz", resource)
     @cached_location = @strategy.cached_location
     mkpath @cached_location
-    touch @cached_location/"README"
   end
 
   def teardown
@@ -84,30 +83,39 @@ class GitDownloadStrategyTests < Homebrew::TestCase
     end
   end
 
-  def inside_repo_using_git_env
+  def using_git_env
     initial_env = ENV.to_hash
     %w[AUTHOR COMMITTER].each do |role|
       ENV["GIT_#{role}_NAME"] = "brew tests"
       ENV["GIT_#{role}_EMAIL"] = "brew-tests@localhost"
       ENV["GIT_#{role}_DATE"] = "Thu May 21 00:04:11 2009 +0100"
     end
-    @cached_location.cd do
-      yield
-    end
+    yield
   ensure
     ENV.replace(initial_env)
   end
 
   def setup_git_repo
-    inside_repo_using_git_env do
-      shutup do
-        system "git", "init"
-        system "git", "remote", "add", "origin", "https://github.com/Homebrew/homebrew-foo"
+    using_git_env do
+      @cached_location.cd do
+        shutup do
+          system "git", "init"
+          system "git", "remote", "add", "origin", "https://github.com/Homebrew/homebrew-foo"
+        end
+        touch "README"
+        git_commit_all
       end
-      git_commit_all
     end
   end
 
+  def test_github_git_download_strategy_user_repo
+    resource = ResourceDouble.new("https://github.com/homebrew/brew.git")
+    strategy = GitHubGitDownloadStrategy.new("brew", resource)
+
+    assert_equal strategy.instance_variable_get(:@user), "homebrew"
+    assert_equal strategy.instance_variable_get(:@repo), "brew"
+  end
+
   def test_source_modified_time
     setup_git_repo
     assert_equal 1242860651, @strategy.source_modified_time.to_i
@@ -115,12 +123,41 @@ class GitDownloadStrategyTests < Homebrew::TestCase
 
   def test_last_commit
     setup_git_repo
-    inside_repo_using_git_env do
-      touch "LICENSE"
-      git_commit_all
+    using_git_env do
+      @cached_location.cd do
+        touch "LICENSE"
+        git_commit_all
+      end
     end
     assert_equal "c50c79b", @strategy.last_commit
   end
+
+  def test_fetch_last_commit
+    remote_repo = HOMEBREW_PREFIX.join("remote_repo")
+    remote_repo.mkdir
+
+    resource = ResourceDouble.new("file://#{remote_repo}")
+    resource.instance_variable_set(:@version, Version.create("HEAD"))
+    @strategy = GitDownloadStrategy.new("baz", resource)
+
+    using_git_env do
+      remote_repo.cd do
+        shutup do
+          system "git", "init"
+          system "git", "remote", "add", "origin", "https://github.com/Homebrew/homebrew-foo"
+        end
+        touch "README"
+        git_commit_all
+        touch "LICENSE"
+        git_commit_all
+      end
+    end
+
+    @strategy.shutup!
+    assert_equal "c50c79b", @strategy.fetch_last_commit
+  ensure
+    remote_repo.rmtree if remote_repo.directory?
+  end
 end
 
 class DownloadStrategyDetectorTests < Homebrew::TestCase
@@ -133,6 +170,11 @@ class DownloadStrategyDetectorTests < Homebrew::TestCase
     assert_equal GitDownloadStrategy, @d
   end
 
+  def test_detect_github_git_download_strategy
+    @d = DownloadStrategyDetector.detect("https://github.com/homebrew/brew.git")
+    assert_equal GitHubGitDownloadStrategy, @d
+  end
+
   def test_default_to_curl_strategy
     @d = DownloadStrategyDetector.detect(Object.new)
     assert_equal CurlDownloadStrategy, @d

commit 5ddee3502e7c8dd67b1fec4de2f72df6fd16cc94
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    download_strategy: use short hash for mercurial

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 4d9e80f..044030e 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -913,7 +913,7 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
   end
 
   def last_commit
-    Utils.popen_read("hg", "parent", "--template", "{node}", "-R", cached_location.to_s)
+    Utils.popen_read("hg", "parent", "--template", "{node|short}", "-R", cached_location.to_s)
   end
 
   private

commit 09d21ad2586427b91734943a22e286255923bbcf
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    download_strategy: allow to suppress output

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 0c38e8a..4d9e80f 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -6,6 +6,7 @@ class AbstractDownloadStrategy
   include FileUtils
 
   attr_reader :meta, :name, :version, :resource
+  attr_reader :shutup
 
   def initialize(name, resource)
     @name = name
@@ -19,6 +20,19 @@ class AbstractDownloadStrategy
   def fetch
   end
 
+  # Supress output
+  def shutup!
+    @shutup = true
+  end
+
+  def puts(*args)
+    super(*args) unless shutup
+  end
+
+  def ohai(*args)
+    super(*args) unless shutup
+  end
+
   # Unpack {#cached_location} into the current working directory, and possibly
   # chdir into the newly-unpacked directory.
   # Unlike {Resource#stage}, this does not take a block.
@@ -59,6 +73,14 @@ class AbstractDownloadStrategy
     args
   end
 
+  def safe_system(*args)
+    if @shutup
+      quiet_system(*args) || raise(ErrorDuringExecution.new(args.shift, *args))
+    else
+      super(*args)
+    end
+  end
+
   def quiet_safe_system(*args)
     safe_system(*expand_safe_system_args(args))
   end

commit 1693ddbdcbaa9f92355e46af2b28ae5d05afb516
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Introduce GitHubGitDownloadStrategy

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 3016b88..0c38e8a 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -147,6 +147,16 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
     end
   end
 
+  def fetch_last_commit
+    fetch
+    last_commit
+  end
+
+  def commit_outdated?(commit)
+    @last_commit ||= fetch_last_commit
+    commit != @last_commit
+  end
+
   def cached_location
     @clone
   end
@@ -756,6 +766,45 @@ class GitDownloadStrategy < VCSDownloadStrategy
   end
 end
 
+class GitHubGitDownloadStrategy < GitDownloadStrategy
+  def initialize(name, resource)
+    super
+    if @url =~ %r{^https?://github\.com/([^/]+)/([^/]+)\.git$}
+      @user = $1
+      @repo = $2
+    end
+  end
+
+  def github_last_commit
+    return if ENV["HOMEBREW_NO_GITHUB_API"]
+
+    output, _, status = curl_output "-H", "Accept: application/vnd.github.v3.sha", \
+      "-I", "https://api.github.com/repos/#{@user}/#{@repo}/commits/#{@ref}"
+
+    commit = output[/^ETag: \"(\h+)\"/, 1] if status.success?
+    version.update_commit(commit) if commit
+    commit
+  end
+
+  def multiple_short_commits_exist?(commit)
+    return if ENV["HOMEBREW_NO_GITHUB_API"]
+    output, _, status = curl_output "-H", "Accept: application/vnd.github.v3.sha", \
+      "-I", "https://api.github.com/repos/#{@user}/#{@repo}/commits/#{commit}"
+
+    !(status.success? && output && output[/^Status: (200)/, 1] == "200")
+  end
+
+  def commit_outdated?(commit)
+    @last_commit ||= github_last_commit
+    if !@last_commit
+      super
+    else
+      return true unless @last_commit.start_with?(commit)
+      multiple_short_commits_exist?(commit)
+    end
+  end
+end
+
 class CVSDownloadStrategy < VCSDownloadStrategy
   def initialize(name, resource)
     super
@@ -957,6 +1006,8 @@ class DownloadStrategyDetector
 
   def self.detect_from_url(url)
     case url
+    when %r{^https?://github\.com/[^/]+/[^/]+\.git$}
+      GitHubGitDownloadStrategy
     when %r{^https?://.+\.git$}, %r{^git://}
       GitDownloadStrategy
     when %r{^https?://www\.apache\.org/dyn/closer\.cgi}, %r{^https?://www\.apache\.org/dyn/closer\.lua}

commit 1114219384baf0948cabcce8752a4537896f7704
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Add tests for Tab versions

diff --git a/Library/Homebrew/test/fixtures/receipt.json b/Library/Homebrew/test/fixtures/receipt.json
index d021dda..4a82978 100644
--- a/Library/Homebrew/test/fixtures/receipt.json
+++ b/Library/Homebrew/test/fixtures/receipt.json
@@ -16,6 +16,11 @@
   "source": {
       "path": "/usr/local/Library/Taps/hombrew/homebrew-core/Formula/foo.rb",
       "tap": "homebrew/core",
-      "spec": "stable"
+      "spec": "stable",
+      "versions": {
+        "stable": "2.14",
+        "devel": "2.15",
+        "head": "HEAD-0000000"
+      }
   }
 }
diff --git a/Library/Homebrew/test/test_tab.rb b/Library/Homebrew/test/test_tab.rb
index 2f22786..8a261a4 100644
--- a/Library/Homebrew/test/test_tab.rb
+++ b/Library/Homebrew/test/test_tab.rb
@@ -20,6 +20,11 @@ class TabTests < Homebrew::TestCase
                      "tap" => "homebrew/core",
                      "path" => nil,
                      "spec" => "stable",
+                     "versions" => {
+                       "stable" => "0.10",
+                       "devel" => "0.14",
+                       "head" => "HEAD-1111111",
+                     }
                    })
   end
 
@@ -35,6 +40,9 @@ class TabTests < Homebrew::TestCase
     assert_nil tab.tap
     assert_nil tab.time
     assert_nil tab.HEAD
+    assert_nil tab.stable_version
+    assert_nil tab.devel_version
+    assert_nil tab.head_version
     assert_equal DevelopmentTools.default_compiler, tab.cxxstdlib.compiler
     assert_nil tab.cxxstdlib.type
   end
@@ -105,6 +113,9 @@ class TabTests < Homebrew::TestCase
     assert_equal TEST_SHA1, tab.HEAD
     assert_equal :clang, tab.cxxstdlib.compiler
     assert_equal :libcxx, tab.cxxstdlib.type
+    assert_equal "2.14", tab.stable_version.to_s
+    assert_equal "2.15", tab.devel_version.to_s
+    assert_equal "HEAD-0000000", tab.head_version.to_s
   end
 
   def test_to_json
@@ -119,6 +130,9 @@ class TabTests < Homebrew::TestCase
     assert_equal @tab.HEAD, tab.HEAD
     assert_equal @tab.compiler, tab.compiler
     assert_equal @tab.stdlib, tab.stdlib
+    assert_equal @tab.stable_version, tab.stable_version
+    assert_equal @tab.devel_version, tab.devel_version
+    assert_equal @tab.head_version, tab.head_version
   end
 
   def test_remap_deprecated_options

commit 42bc623a277c4379255fc86ee59be77cf9c63392
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    tab: allow to store versions

diff --git a/Library/Homebrew/tab.rb b/Library/Homebrew/tab.rb
index 22489c4..276fcfa 100644
--- a/Library/Homebrew/tab.rb
+++ b/Library/Homebrew/tab.rb
@@ -32,7 +32,12 @@ class Tab < OpenStruct
       "source" => {
         "path" => formula.path.to_s,
         "tap" => formula.tap ? formula.tap.name : nil,
-        "spec" => formula.active_spec_sym.to_s
+        "spec" => formula.active_spec_sym.to_s,
+        "versions" => {
+          "stable" => formula.stable ? formula.stable.version.to_s : nil,
+          "devel" => formula.devel ? formula.devel.version.to_s : nil,
+          "head" => formula.head ? formula.head.version.to_s : nil,
+        }
       }
     }
 
@@ -68,6 +73,14 @@ class Tab < OpenStruct
       end
     end
 
+    if attributes["source"]["versions"].nil?
+      attributes["source"]["versions"] = {
+        "stable" => nil,
+        "devel" => nil,
+        "head" => nil,
+      }
+    end
+
     new(attributes)
   end
 
@@ -145,7 +158,12 @@ class Tab < OpenStruct
       "source" => {
         "path" => nil,
         "tap" => nil,
-        "spec" => "stable"
+        "spec" => "stable",
+        "versions" => {
+          "stable" => nil,
+          "devel" => nil,
+          "head" => nil,
+        }
       }
     }
 
@@ -232,6 +250,22 @@ class Tab < OpenStruct
     source["spec"].to_sym
   end
 
+  def versions
+    source["versions"]
+  end
+
+  def stable_version
+    Version.create(versions["stable"]) if versions["stable"]
+  end
+
+  def devel_version
+    Version.create(versions["devel"]) if versions["devel"]
+  end
+
+  def head_version
+    Version.create(versions["head"]) if versions["head"]
+  end
+
   def source_modified_time
     Time.at(super)
   end

commit 63c563f97074bdfb2ef8bf5388b216d137087c3c
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    Fix update commit for non-HEAD kegs with head spec (#644)

diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb
index 2da6141..4a49795 100644
--- a/Library/Homebrew/extend/ARGV.rb
+++ b/Library/Homebrew/extend/ARGV.rb
@@ -34,7 +34,7 @@ module HomebrewArgvExtension
           f.build = tab
           if f.head? && tab.tabfile
             k = Keg.new(tab.tabfile.parent)
-            f.version.update_commit(k.version.version.commit)
+            f.version.update_commit(k.version.version.commit) if k.version.head?
           end
         end
         f
diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb
index 20f115f..f7a4be8 100644
--- a/Library/Homebrew/formulary.rb
+++ b/Library/Homebrew/formulary.rb
@@ -246,7 +246,7 @@ class Formulary
       end
     end
     f.build = tab
-    f.version.update_commit(keg.version.version.commit) if f.head?
+    f.version.update_commit(keg.version.version.commit) if f.head? && keg.version.head?
     f
   end
 

commit 092d4712a1d00bc08eb0515d8e61f7859c7a2de0
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    Update commit when resolving the formula (#536)

diff --git a/Library/Homebrew/extend/ARGV.rb b/Library/Homebrew/extend/ARGV.rb
index beee470..2da6141 100644
--- a/Library/Homebrew/extend/ARGV.rb
+++ b/Library/Homebrew/extend/ARGV.rb
@@ -32,6 +32,10 @@ module HomebrewArgvExtension
           resolved_spec = spec(nil) || tab.spec
           f.set_active_spec(resolved_spec) if f.send(resolved_spec)
           f.build = tab
+          if f.head? && tab.tabfile
+            k = Keg.new(tab.tabfile.parent)
+            f.version.update_commit(k.version.version.commit)
+          end
         end
         f
       else
diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb
index 4254437..20f115f 100644
--- a/Library/Homebrew/formulary.rb
+++ b/Library/Homebrew/formulary.rb
@@ -246,6 +246,7 @@ class Formulary
       end
     end
     f.build = tab
+    f.version.update_commit(keg.version.version.commit) if f.head?
     f
   end
 
diff --git a/Library/Homebrew/postinstall.rb b/Library/Homebrew/postinstall.rb
index 14e5781..0b6d8f6 100644
--- a/Library/Homebrew/postinstall.rb
+++ b/Library/Homebrew/postinstall.rb
@@ -11,7 +11,7 @@ begin
 
   trap("INT", old_trap)
 
-  formula = ARGV.formulae.first
+  formula = ARGV.resolved_formulae.first
   formula.extend(Debrew::Formula) if ARGV.debug?
   formula.run_post_install
 rescue Exception => e
diff --git a/Library/Homebrew/test.rb b/Library/Homebrew/test.rb
index 796ce09..ffffa18 100644
--- a/Library/Homebrew/test.rb
+++ b/Library/Homebrew/test.rb
@@ -19,7 +19,7 @@ begin
 
   trap("INT", old_trap)
 
-  formula = ARGV.formulae.first
+  formula = ARGV.resolved_formulae.first
   formula.extend(Homebrew::Assertions)
   formula.extend(Debrew::Formula) if ARGV.debug?
 

commit 242508fca4d2b167cef3c355722f3471594d7b4b
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    software_spec: use version dups for resources (#534)

diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb
index 47bd1bd..909819c 100644
--- a/Library/Homebrew/software_spec.rb
+++ b/Library/Homebrew/software_spec.rb
@@ -53,7 +53,7 @@ class SoftwareSpec
     @resource.owner = self
     resources.each_value do |r|
       r.owner     = self
-      r.version ||= version
+      r.version ||= (version.head? ? Version.create("HEAD") : version.dup)
     end
     patches.each { |p| p.owner = self }
   end

commit 4b2c4ef25835a87b5bac4fb26eae91dbfed93863
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Update and test eligible_kegs_for_cleanup

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index cb27e94..5e8232d 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -1521,7 +1521,12 @@ class Formula
   def eligible_kegs_for_cleanup
     eligible_for_cleanup = []
     if installed?
-      eligible_kegs = installed_kegs.select { |k| pkg_version > k.version }
+      eligible_kegs = if head? && (head_prefix = latest_head_prefix)
+        installed_kegs - [Keg.new(head_prefix)]
+      else
+        installed_kegs.select { |k| pkg_version > k.version }
+      end
+
       if eligible_kegs.any?
         eligible_kegs.each do |keg|
           if keg.linked?
diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index c5175e2..d586481 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -451,6 +451,30 @@ class FormulaTests < Homebrew::TestCase
     f3.rack.rmtree
   end
 
+  def test_eligible_kegs_for_cleanup_head_installed
+    f = formula do
+      version "0.1"
+      head "foo"
+    end
+
+    stable_prefix = f.installed_prefix
+    stable_prefix.mkpath
+
+    [["000000_1", 1], ["111111", 2], ["111111_1", 2]].each do |pkg_version_suffix, stamp|
+      prefix = f.prefix("HEAD-#{pkg_version_suffix}")
+      prefix.mkpath
+      tab = Tab.empty
+      tab.tabfile = prefix.join("INSTALL_RECEIPT.json")
+      tab.source_modified_time = stamp
+      tab.write
+    end
+
+    eligible_kegs = f.installed_kegs - [Keg.new(f.prefix("HEAD-111111_1"))]
+    assert_equal eligible_kegs, f.eligible_kegs_for_cleanup
+  ensure
+    f.rack.rmtree
+  end
+
   def test_pour_bottle
     f_false = formula("foo") do
       url "foo-1.0"

commit 3fb5d70a729472a7d7f2a5d0d7b84248921fb583
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Unify Version.create usage

diff --git a/Library/Homebrew/cmd/audit.rb b/Library/Homebrew/cmd/audit.rb
index 18ebbec..5171ec7 100644
--- a/Library/Homebrew/cmd/audit.rb
+++ b/Library/Homebrew/cmd/audit.rb
@@ -606,7 +606,7 @@ class FormulaAuditor
     case stable && stable.url
     when %r{download\.gnome\.org/sources}, %r{ftp\.gnome\.org/pub/GNOME/sources}i
       version = Version.parse(stable.url)
-      if version >= Version.new("1.0")
+      if version >= Version.create("1.0")
         minor_version = version.to_s.split(".", 3)[1].to_i
         if minor_version.odd?
           problem "#{stable.version} is a development release"
diff --git a/Library/Homebrew/cmd/create.rb b/Library/Homebrew/cmd/create.rb
index eb3bec4..ac93681 100644
--- a/Library/Homebrew/cmd/create.rb
+++ b/Library/Homebrew/cmd/create.rb
@@ -116,7 +116,7 @@ class FormulaCreator
     end
     update_path
     if @version
-      @version = Version.new(@version)
+      @version = Version.create(@version)
     else
       @version = Pathname.new(url).version
     end
diff --git a/Library/Homebrew/cmd/pull.rb b/Library/Homebrew/cmd/pull.rb
index 3b3c689..f93ad09 100644
--- a/Library/Homebrew/cmd/pull.rb
+++ b/Library/Homebrew/cmd/pull.rb
@@ -441,7 +441,7 @@ module Homebrew
 
     def version(spec_type)
       version_str = info["versions"][spec_type.to_s]
-      version_str && Version.new(version_str)
+      version_str && Version.create(version_str)
     end
 
     def pkg_version(spec_type = :stable)
diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb
index d691ad7..4954cde 100644
--- a/Library/Homebrew/diagnostic.rb
+++ b/Library/Homebrew/diagnostic.rb
@@ -761,7 +761,7 @@ module Homebrew
       def check_git_version
         # https://help.github.com/articles/https-cloning-errors
         return unless Utils.git_available?
-        return unless Version.new(Utils.git_version) < Version.new("1.7.10")
+        return unless Version.create(Utils.git_version) < Version.create("1.7.10")
 
         git = Formula["git"]
         git_upgrade_cmd = git.any_version_installed? ? "upgrade" : "install"
diff --git a/Library/Homebrew/extend/ENV/shared.rb b/Library/Homebrew/extend/ENV/shared.rb
index 2debbae..62aa311 100644
--- a/Library/Homebrew/extend/ENV/shared.rb
+++ b/Library/Homebrew/extend/ENV/shared.rb
@@ -325,6 +325,6 @@ module SharedEnvExtension
 
   def gcc_with_cxx11_support?(cc)
     version = cc[/^gcc-(\d+(?:\.\d+)?)$/, 1]
-    version && Version.new(version) >= Version.new("4.8")
+    version && Version.create(version) >= Version.create("4.8")
   end
 end
diff --git a/Library/Homebrew/extend/os/mac/diagnostic.rb b/Library/Homebrew/extend/os/mac/diagnostic.rb
index 6f64d1f..b3c9b03 100644
--- a/Library/Homebrew/extend/os/mac/diagnostic.rb
+++ b/Library/Homebrew/extend/os/mac/diagnostic.rb
@@ -287,8 +287,8 @@ module Homebrew
         return unless MacOS::XQuartz.version
         return if MacOS::XQuartz.provided_by_apple?
 
-        installed_version = Version.new(MacOS::XQuartz.version)
-        latest_version = Version.new(MacOS::XQuartz.latest_version)
+        installed_version = Version.create(MacOS::XQuartz.version)
+        latest_version = Version.create(MacOS::XQuartz.latest_version)
         return if installed_version >= latest_version
 
         <<-EOS.undent
diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index aa66bbb..cb27e94 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -1326,7 +1326,7 @@ class Formula
       }
     end
 
-    hsh["installed"] = hsh["installed"].sort_by { |i| Version.new(i["version"]) }
+    hsh["installed"] = hsh["installed"].sort_by { |i| Version.create(i["version"]) }
 
     hsh
   end
diff --git a/Library/Homebrew/language/python.rb b/Library/Homebrew/language/python.rb
index 646e2ac..3c84e53 100644
--- a/Library/Homebrew/language/python.rb
+++ b/Library/Homebrew/language/python.rb
@@ -5,7 +5,7 @@ module Language
     def self.major_minor_version(python)
       version = /\d\.\d/.match `#{python} --version 2>&1`
       return unless version
-      Version.new(version.to_s)
+      Version.create(version.to_s)
     end
 
     def self.homebrew_site_packages(version = "2.7")
diff --git a/Library/Homebrew/requirements/emacs_requirement.rb b/Library/Homebrew/requirements/emacs_requirement.rb
index 4f7c36a..591b7ba 100644
--- a/Library/Homebrew/requirements/emacs_requirement.rb
+++ b/Library/Homebrew/requirements/emacs_requirement.rb
@@ -11,7 +11,7 @@ class EmacsRequirement < Requirement
     next false unless which "emacs"
     next true unless @version
     emacs_version = Utils.popen_read("emacs", "--batch", "--eval", "(princ emacs-version)")
-    Version.new(emacs_version) >= Version.new(@version)
+    Version.create(emacs_version) >= Version.create(@version)
   end
 
   env do
diff --git a/Library/Homebrew/requirements/perl_requirement.rb b/Library/Homebrew/requirements/perl_requirement.rb
index 79d5e8e..0aac216 100644
--- a/Library/Homebrew/requirements/perl_requirement.rb
+++ b/Library/Homebrew/requirements/perl_requirement.rb
@@ -12,7 +12,7 @@ class PerlRequirement < Requirement
     which_all("perl").detect do |perl|
       perl_version = Utils.popen_read(perl, "--version")[/\(v(\d+\.\d+)(?:\.\d+)?\)/, 1]
       next unless perl_version
-      Version.new(perl_version.to_s) >= Version.new(@version)
+      Version.create(perl_version.to_s) >= Version.create(@version)
     end
   end
 
diff --git a/Library/Homebrew/requirements/python_requirement.rb b/Library/Homebrew/requirements/python_requirement.rb
index 4faf78d..2800f52 100644
--- a/Library/Homebrew/requirements/python_requirement.rb
+++ b/Library/Homebrew/requirements/python_requirement.rb
@@ -11,16 +11,16 @@ class PythonRequirement < Requirement
     version = python_short_version
     next unless version
     # Always use Python 2.7 for consistency on older versions of OSX.
-    version == Version.new("2.7")
+    version == Version.create("2.7")
   end
 
   env do
     short_version = python_short_version
 
-    if !system_python? && short_version == Version.new("2.7")
+    if !system_python? && short_version == Version.create("2.7")
       ENV.prepend_path "PATH", which_python.dirname
     # Homebrew Python should take precedence over older Pythons in the PATH
-    elsif short_version != Version.new("2.7")
+    elsif short_version != Version.create("2.7")
       ENV.prepend_path "PATH", Formula["python"].opt_bin
     end
 
diff --git a/Library/Homebrew/requirements/ruby_requirement.rb b/Library/Homebrew/requirements/ruby_requirement.rb
index 87db397..c445bd4 100644
--- a/Library/Homebrew/requirements/ruby_requirement.rb
+++ b/Library/Homebrew/requirements/ruby_requirement.rb
@@ -12,7 +12,7 @@ class RubyRequirement < Requirement
     which_all("ruby").detect do |ruby|
       version = /\d\.\d/.match Utils.popen_read(ruby, "--version")
       next unless version
-      Version.new(version.to_s) >= Version.new(@version)
+      Version.create(version.to_s) >= Version.create(@version)
     end
   end
 
diff --git a/Library/Homebrew/requirements/x11_requirement.rb b/Library/Homebrew/requirements/x11_requirement.rb
index ce921cf..d91f9d3 100644
--- a/Library/Homebrew/requirements/x11_requirement.rb
+++ b/Library/Homebrew/requirements/x11_requirement.rb
@@ -13,17 +13,17 @@ class X11Requirement < Requirement
   def initialize(name = "x11", tags = [])
     @name = name
     if /(\d\.)+\d/ === tags.first
-      @min_version = Version.new(tags.shift)
+      @min_version = Version.create(tags.shift)
       @min_version_string = " #{@min_version}"
     else
-      @min_version = Version.new("0.0.0")
+      @min_version = Version.create("0.0.0")
       @min_version_string = ""
     end
     super(tags)
   end
 
   satisfy :build_env => false do
-    MacOS::XQuartz.installed? && min_version <= Version.new(MacOS::XQuartz.version)
+    MacOS::XQuartz.installed? && min_version <= Version.create(MacOS::XQuartz.version)
   end
 
   def message
diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb
index c7e9197..47bd1bd 100644
--- a/Library/Homebrew/software_spec.rb
+++ b/Library/Homebrew/software_spec.rb
@@ -204,7 +204,7 @@ end
 class HeadSoftwareSpec < SoftwareSpec
   def initialize
     super
-    @resource.version = HeadVersion.new("HEAD")
+    @resource.version = Version.create("HEAD")
   end
 
   def verify_download_integrity(_fn)
diff --git a/Library/Homebrew/test/test_pkg_version.rb b/Library/Homebrew/test/test_pkg_version.rb
index ac0a9f2..ff7896f 100644
--- a/Library/Homebrew/test/test_pkg_version.rb
+++ b/Library/Homebrew/test/test_pkg_version.rb
@@ -7,12 +7,12 @@ class PkgVersionTests < Homebrew::TestCase
   end
 
   def test_parse
-    assert_equal PkgVersion.new(Version.new("1.0"), 1), PkgVersion.parse("1.0_1")
-    assert_equal PkgVersion.new(Version.new("1.0"), 1), PkgVersion.parse("1.0_1")
-    assert_equal PkgVersion.new(Version.new("1.0"), 0), PkgVersion.parse("1.0")
-    assert_equal PkgVersion.new(Version.new("1.0"), 0), PkgVersion.parse("1.0_0")
-    assert_equal PkgVersion.new(Version.new("2.1.4"), 0), PkgVersion.parse("2.1.4_0")
-    assert_equal PkgVersion.new(Version.new("1.0.1e"), 1), PkgVersion.parse("1.0.1e_1")
+    assert_equal PkgVersion.new(Version.create("1.0"), 1), PkgVersion.parse("1.0_1")
+    assert_equal PkgVersion.new(Version.create("1.0"), 1), PkgVersion.parse("1.0_1")
+    assert_equal PkgVersion.new(Version.create("1.0"), 0), PkgVersion.parse("1.0")
+    assert_equal PkgVersion.new(Version.create("1.0"), 0), PkgVersion.parse("1.0_0")
+    assert_equal PkgVersion.new(Version.create("2.1.4"), 0), PkgVersion.parse("2.1.4_0")
+    assert_equal PkgVersion.new(Version.create("1.0.1e"), 1), PkgVersion.parse("1.0.1e_1")
   end
 
   def test_comparison
@@ -24,26 +24,26 @@ class PkgVersionTests < Homebrew::TestCase
     assert_operator v("HEAD"), :>, v("1.0")
     assert_operator v("1.0"), :<, v("HEAD")
 
-    v = PkgVersion.new(Version.new("1.0"), 0)
+    v = PkgVersion.new(Version.create("1.0"), 0)
     assert_nil v <=> Object.new
     assert_raises(ArgumentError) { v > Object.new }
-    assert_raises(ArgumentError) { v > Version.new("1.0") }
+    assert_raises(ArgumentError) { v > Version.create("1.0") }
   end
 
   def test_to_s
-    assert_equal "1.0", PkgVersion.new(Version.new("1.0"), 0).to_s
-    assert_equal "1.0_1", PkgVersion.new(Version.new("1.0"), 1).to_s
-    assert_equal "1.0", PkgVersion.new(Version.new("1.0"), 0).to_s
-    assert_equal "1.0", PkgVersion.new(Version.new("1.0"), 0).to_s
+    assert_equal "1.0", PkgVersion.new(Version.create("1.0"), 0).to_s
+    assert_equal "1.0_1", PkgVersion.new(Version.create("1.0"), 1).to_s
+    assert_equal "1.0", PkgVersion.new(Version.create("1.0"), 0).to_s
+    assert_equal "1.0", PkgVersion.new(Version.create("1.0"), 0).to_s
     assert_equal "HEAD_1", PkgVersion.new(Version.create("HEAD"), 1).to_s
     assert_equal "HEAD-ffffff_1", PkgVersion.new(Version.create("HEAD-ffffff"), 1).to_s
   end
 
   def test_hash
-    p1 = PkgVersion.new(Version.new("1.0"), 1)
-    p2 = PkgVersion.new(Version.new("1.0"), 1)
-    p3 = PkgVersion.new(Version.new("1.1"), 1)
-    p4 = PkgVersion.new(Version.new("1.0"), 0)
+    p1 = PkgVersion.new(Version.create("1.0"), 1)
+    p2 = PkgVersion.new(Version.create("1.0"), 1)
+    p3 = PkgVersion.new(Version.create("1.1"), 1)
+    p4 = PkgVersion.new(Version.create("1.0"), 0)
     assert_equal p1.hash, p2.hash
     refute_equal p1.hash, p3.hash
     refute_equal p1.hash, p4.hash
diff --git a/Library/Homebrew/test/test_version_subclasses.rb b/Library/Homebrew/test/test_version_subclasses.rb
index b2f226e..5b38268 100644
--- a/Library/Homebrew/test/test_version_subclasses.rb
+++ b/Library/Homebrew/test/test_version_subclasses.rb
@@ -34,10 +34,10 @@ class MacOSVersionTests < Homebrew::TestCase
   end
 
   def test_compare_with_version
-    assert_operator @v, :>, Version.new("10.6")
-    assert_operator @v, :==, Version.new("10.7")
-    assert_operator @v, :===, Version.new("10.7")
-    assert_operator @v, :<, Version.new("10.8")
+    assert_operator @v, :>, Version.create("10.6")
+    assert_operator @v, :==, Version.create("10.7")
+    assert_operator @v, :===, Version.create("10.7")
+    assert_operator @v, :<, Version.create("10.8")
   end
 
   def test_from_symbol
diff --git a/Library/Homebrew/test/test_versions.rb b/Library/Homebrew/test/test_versions.rb
index bdcc16f..a7cb1dc 100644
--- a/Library/Homebrew/test/test_versions.rb
+++ b/Library/Homebrew/test/test_versions.rb
@@ -4,17 +4,17 @@ require "version"
 class VersionTests < Homebrew::TestCase
   def test_accepts_objects_responding_to_to_str
     value = stub(:to_str => "0.1")
-    assert_equal "0.1", Version.new(value).to_s
+    assert_equal "0.1", Version.create(value).to_s
   end
 
   def test_raises_for_non_string_objects
-    assert_raises(TypeError) { Version.new(1.1) }
-    assert_raises(TypeError) { Version.new(1) }
-    assert_raises(TypeError) { Version.new(:symbol) }
+    assert_raises(TypeError) { Version.create(1.1) }
+    assert_raises(TypeError) { Version.create(1) }
+    assert_raises(TypeError) { Version.create(:symbol) }
   end
 
   def test_detected_from_url?
-    refute Version.new("1.0").detected_from_url?
+    refute Version.create("1.0").detected_from_url?
     assert Version::FromURL.new("1.0").detected_from_url?
   end
 end
@@ -462,20 +462,20 @@ class HeadVersionTests < Homebrew::TestCase
   end
 
   def test_commit_assigned
-    v = HeadVersion.new("HEAD-abcdef")
+    v = Version.create("HEAD-abcdef")
     assert_equal "abcdef", v.commit
     assert_equal "HEAD-abcdef", v.to_str
   end
 
   def test_no_commit
-    v = HeadVersion.new("HEAD")
+    v = Version.create("HEAD")
     assert_nil v.commit
     assert_equal "HEAD", v.to_str
   end
 
   def test_update_commit
-    v1 = HeadVersion.new("HEAD-abcdef")
-    v2 = HeadVersion.new("HEAD")
+    v1 = Version.create("HEAD-abcdef")
+    v2 = Version.create("HEAD")
 
     v1.update_commit("ffffff")
     assert_equal "ffffff", v1.commit
diff --git a/Library/Homebrew/test/testing_env.rb b/Library/Homebrew/test/testing_env.rb
index 35aa00b..887cb2d 100644
--- a/Library/Homebrew/test/testing_env.rb
+++ b/Library/Homebrew/test/testing_env.rb
@@ -27,7 +27,7 @@ module Homebrew
     end
 
     def assert_version_equal(expected, actual)
-      assert_equal Version.new(expected), actual
+      assert_equal Version.create(expected), actual
     end
 
     def assert_version_detected(expected, url, specs={})

commit 454003c4c1ea43f4fd84db96017636fc4c50b318
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    test_formula: test new HEAD methods

diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 12d6eae..c5175e2 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -158,6 +158,29 @@ class FormulaTests < Homebrew::TestCase
     assert_equal prefix, f.installed_prefix
   end
 
+  def test_latest_head_prefix
+    f = Testball.new
+
+    stamps_with_revisions = [[111111, 1], [222222, 1], [222222, 2], [222222, 0]]
+
+    stamps_with_revisions.each do |stamp, revision|
+      version = "HEAD-#{stamp}"
+      version += "_#{revision}" if revision > 0
+      prefix = f.rack.join(version)
+      prefix.mkpath
+
+      tab = Tab.empty
+      tab.tabfile = prefix.join("INSTALL_RECEIPT.json")
+      tab.source_modified_time = stamp
+      tab.write
+    end
+
+    prefix = HOMEBREW_CELLAR/"#{f.name}/HEAD-222222_2"
+    assert_equal prefix, f.latest_head_prefix
+  ensure
+    f.rack.rmtree
+  end
+
   def test_equality
     x = Testball.new
     y = Testball.new
@@ -282,6 +305,38 @@ class FormulaTests < Homebrew::TestCase
     assert_equal PkgVersion.parse("HEAD_1"), f.pkg_version
   end
 
+  def test_update_head_version
+    initial_env = ENV.to_hash
+
+    f = formula do
+      head "foo", :using => :git
+    end
+
+    cached_location = f.head.downloader.cached_location
+    cached_location.mkpath
+
+    %w[AUTHOR COMMITTER].each do |role|
+      ENV["GIT_#{role}_NAME"] = "brew tests"
+      ENV["GIT_#{role}_EMAIL"] = "brew-tests@localhost"
+      ENV["GIT_#{role}_DATE"] = "Thu May 21 00:04:11 2009 +0100"
+    end
+
+    cached_location.cd do
+      FileUtils.touch "LICENSE"
+      shutup do
+        system "git", "init"
+        system "git", "add", "--all"
+        system "git", "commit", "-m", "Initial commit"
+      end
+    end
+
+    f.update_head_version
+    assert_equal Version.create("HEAD-5658946"), f.head.version
+  ensure
+    ENV.replace(initial_env)
+    cached_location.rmtree
+  end
+
   def test_legacy_options
     f = formula do
       url "foo-1.0"

commit 458f9a008cc5316de9ec18ebae3b0f3990583540
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Apply tests to new HEAD format

diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 024d5c2..12d6eae 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -272,14 +272,14 @@ class FormulaTests < Homebrew::TestCase
     assert_equal PkgVersion.parse("1.0_1"), f.pkg_version
   end
 
-  def test_head_ignores_revisions
+  def test_head_uses_revisions
     f = formula("test", Pathname.new(__FILE__).expand_path, :head) do
       url "foo-1.0.bar"
       revision 1
       head "foo"
     end
 
-    assert_equal PkgVersion.parse("HEAD"), f.pkg_version
+    assert_equal PkgVersion.parse("HEAD_1"), f.pkg_version
   end
 
   def test_legacy_options
diff --git a/Library/Homebrew/test/test_pkg_version.rb b/Library/Homebrew/test/test_pkg_version.rb
index 06a57b7..ac0a9f2 100644
--- a/Library/Homebrew/test/test_pkg_version.rb
+++ b/Library/Homebrew/test/test_pkg_version.rb
@@ -35,7 +35,8 @@ class PkgVersionTests < Homebrew::TestCase
     assert_equal "1.0_1", PkgVersion.new(Version.new("1.0"), 1).to_s
     assert_equal "1.0", PkgVersion.new(Version.new("1.0"), 0).to_s
     assert_equal "1.0", PkgVersion.new(Version.new("1.0"), 0).to_s
-    assert_equal "HEAD", PkgVersion.new(Version.new("HEAD"), 1).to_s
+    assert_equal "HEAD_1", PkgVersion.new(Version.create("HEAD"), 1).to_s
+    assert_equal "HEAD-ffffff_1", PkgVersion.new(Version.create("HEAD-ffffff"), 1).to_s
   end
 
   def test_hash

commit 00cdd5f481d409b1874b95c7f149c717ffb0259f
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Add HeadVersion tests

diff --git a/Library/Homebrew/test/test_versions.rb b/Library/Homebrew/test/test_versions.rb
index 873180d..bdcc16f 100644
--- a/Library/Homebrew/test/test_versions.rb
+++ b/Library/Homebrew/test/test_versions.rb
@@ -55,7 +55,11 @@ class VersionComparisonTests < Homebrew::TestCase
 
   def test_HEAD
     assert_operator version("HEAD"), :>, version("1.2.3")
+    assert_operator version("HEAD-abcdef"), :>, version("1.2.3")
     assert_operator version("1.2.3"), :<, version("HEAD")
+    assert_operator version("1.2.3"), :<, version("HEAD-fedcba")
+    assert_operator version("HEAD-abcdef"), :==, version("HEAD-fedcba")
+    assert_operator version("HEAD"), :==, version("HEAD-fedcba")
   end
 
   def test_comparing_alpha_versions
@@ -156,6 +160,12 @@ class VersionParsingTests < Homebrew::TestCase
     assert_version_nil "foo"
   end
 
+  def test_create
+    v = Version.create("1.20")
+    refute_predicate v, :head?
+    assert_equal "1.20", v.to_str
+  end
+
   def test_version_all_dots
     assert_version_detected "1.14", "http://example.com/foo.bar.la.1.14.zip"
   end
@@ -441,3 +451,38 @@ class VersionParsingTests < Homebrew::TestCase
       "http://github.com/foo/bar.git", {:tag => "v1.2.3"}
   end
 end
+
+class HeadVersionTests < Homebrew::TestCase
+  def test_create_head
+    v1 = Version.create("HEAD-abcdef")
+    v2 = Version.create("HEAD")
+
+    assert_predicate v1, :head?
+    assert_predicate v2, :head?
+  end
+
+  def test_commit_assigned
+    v = HeadVersion.new("HEAD-abcdef")
+    assert_equal "abcdef", v.commit
+    assert_equal "HEAD-abcdef", v.to_str
+  end
+
+  def test_no_commit
+    v = HeadVersion.new("HEAD")
+    assert_nil v.commit
+    assert_equal "HEAD", v.to_str
+  end
+
+  def test_update_commit
+    v1 = HeadVersion.new("HEAD-abcdef")
+    v2 = HeadVersion.new("HEAD")
+
+    v1.update_commit("ffffff")
+    assert_equal "ffffff", v1.commit
+    assert_equal "HEAD-ffffff", v1.to_str
+
+    v2.update_commit("ffffff")
+    assert_equal "ffffff", v2.commit
+    assert_equal "HEAD-ffffff", v2.to_str
+  end
+end
diff --git a/Library/Homebrew/test/testing_env.rb b/Library/Homebrew/test/testing_env.rb
index 214aa97..35aa00b 100644
--- a/Library/Homebrew/test/testing_env.rb
+++ b/Library/Homebrew/test/testing_env.rb
@@ -23,7 +23,7 @@ end
 module Homebrew
   module VersionAssertions
     def version(v)
-      Version.new(v)
+      Version.create(v)
     end
 
     def assert_version_equal(expected, actual)

commit 2e916110e4c561b3e4175da099fc795e85ddb822
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    Use HeadVersion for install/reinstall

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 061e551..3016b88 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -135,6 +135,8 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
       clone_repo
     end
 
+    version.update_commit(last_commit) if head?
+
     if @ref_type == :tag && @revision && current_revision
       unless current_revision == @revision
         raise <<-EOS.undent
diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index 4cb5845..aa66bbb 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -287,6 +287,14 @@ class Formula
     active_spec.version
   end
 
+  def update_head_version
+    return unless head?
+    return unless head.downloader.is_a?(VCSDownloadStrategy)
+    return unless head.downloader.cached_location.exist?
+
+    head.version.update_commit(head.downloader.last_commit)
+  end
+
   # The {PkgVersion} for this formula with {version} and {#revision} information.
   def pkg_version
     PkgVersion.new(version, revision)
@@ -405,11 +413,23 @@ class Formula
     Pathname.new("#{HOMEBREW_LIBRARY}/LinkedKegs/#{name}")
   end
 
+  def latest_head_prefix
+    head_versions = installed_prefixes.map do |pn|
+      pn_pkgversion = PkgVersion.parse(pn.basename.to_s)
+      pn_pkgversion if pn_pkgversion.head?
+    end.compact
+
+    latest_head_version = head_versions.max_by do |pn_pkgversion|
+      [Tab.for_keg(prefix(pn_pkgversion)).source_modified_time, pn_pkgversion.revision]
+    end
+    prefix(latest_head_version) if latest_head_version
+  end
+
   # The latest prefix for this formula. Checks for {#head}, then {#devel}
   # and then {#stable}'s {#prefix}
   # @private
   def installed_prefix
-    if head && (head_prefix = prefix(PkgVersion.new(head.version, revision))).directory?
+    if head && (head_prefix = latest_head_prefix) && head_prefix.directory?
       head_prefix
     elsif devel && (devel_prefix = prefix(PkgVersion.new(devel.version, revision))).directory?
       devel_prefix
diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb
index 375a529..4ba8be3 100644
--- a/Library/Homebrew/formula_installer.rb
+++ b/Library/Homebrew/formula_installer.rb
@@ -586,6 +586,8 @@ class FormulaInstaller
       end
     end
 
+    formula.update_head_version
+
     if !formula.prefix.directory? || Keg.new(formula.prefix).empty_installation?
       raise "Empty installation"
     end
@@ -593,6 +595,7 @@ class FormulaInstaller
   rescue Exception
     ignore_interrupts do
       # any exceptions must leave us with nothing installed
+      formula.update_head_version
       formula.prefix.rmtree if formula.prefix.directory?
       formula.rack.rmdir_if_possible
     end
diff --git a/Library/Homebrew/pkg_version.rb b/Library/Homebrew/pkg_version.rb
index 9e065c8..4bf701f 100644
--- a/Library/Homebrew/pkg_version.rb
+++ b/Library/Homebrew/pkg_version.rb
@@ -5,6 +5,8 @@ class PkgVersion
 
   RX = /\A(.+?)(?:_(\d+))?\z/
 
+  attr_reader :version, :revision
+
   def self.parse(path)
     _, version, revision = *path.match(RX)
     version = Version.create(version)
@@ -38,8 +40,4 @@ class PkgVersion
   def hash
     version.hash ^ revision.hash
   end
-
-  protected
-
-  attr_reader :version, :revision
 end
diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb
index 5288577..c7e9197 100644
--- a/Library/Homebrew/software_spec.rb
+++ b/Library/Homebrew/software_spec.rb
@@ -29,6 +29,7 @@ class SoftwareSpec
   def_delegators :@resource, :cached_download, :clear_cache
   def_delegators :@resource, :checksum, :mirrors, :specs, :using
   def_delegators :@resource, :version, :mirror, *Checksum::TYPES
+  def_delegators :@resource, :downloader
 
   def initialize
     @resource = Resource.new
@@ -203,7 +204,7 @@ end
 class HeadSoftwareSpec < SoftwareSpec
   def initialize
     super
-    @resource.version = Version.new("HEAD")
+    @resource.version = HeadVersion.new("HEAD")
   end
 
   def verify_download_integrity(_fn)

commit 8a968a0b60dbc78f9f48be76762c9f050fa6416d
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    resource: detect HEAD versions

diff --git a/Library/Homebrew/resource.rb b/Library/Homebrew/resource.rb
index a11c295..fe18f14 100644
--- a/Library/Homebrew/resource.rb
+++ b/Library/Homebrew/resource.rb
@@ -159,7 +159,7 @@ class Resource
 
     case val
     when nil     then Version.detect(url, specs)
-    when String  then Version.new(val)
+    when String  then Version.create(val)
     when Version then val
     else
       raise TypeError, "version '#{val.inspect}' should be a string"

commit 9ac58366048f7919a168ea2aebd7103b20b7f095
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    pkg_version: allow HeadVersion and HEAD revisions

diff --git a/Library/Homebrew/pkg_version.rb b/Library/Homebrew/pkg_version.rb
index 6207227..9e065c8 100644
--- a/Library/Homebrew/pkg_version.rb
+++ b/Library/Homebrew/pkg_version.rb
@@ -7,13 +7,13 @@ class PkgVersion
 
   def self.parse(path)
     _, version, revision = *path.match(RX)
-    version = Version.new(version)
+    version = Version.create(version)
     new(version, revision.to_i)
   end
 
   def initialize(version, revision)
     @version = version
-    @revision = version.head? ? 0 : revision
+    @revision = revision
   end
 
   def head?

commit 80489dcb499a727c7613284aea4e744a690f12dc
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    version: introduce HeadVersion

diff --git a/Library/Homebrew/version.rb b/Library/Homebrew/version.rb
index 02f442f..002bd53 100644
--- a/Library/Homebrew/version.rb
+++ b/Library/Homebrew/version.rb
@@ -179,6 +179,18 @@ class Version
     end
   end
 
+  def self.create(val)
+    unless val.respond_to?(:to_str)
+      raise TypeError, "Version value must be a string; got a #{val.class} (#{val})"
+    end
+
+    if val.to_str.start_with?("HEAD")
+      HeadVersion.new(val)
+    else
+      Version.new(val)
+    end
+  end
+
   def initialize(val)
     if val.respond_to?(:to_str)
       @version = val.to_str
@@ -192,7 +204,7 @@ class Version
   end
 
   def head?
-    version == "HEAD"
+    false
   end
 
   def <=>(other)
@@ -200,6 +212,7 @@ class Version
     return 0 if version == other.version
     return 1 if head? && !other.head?
     return -1 if !head? && other.head?
+    return 0 if head? && other.head?
 
     ltokens = tokens
     rtokens = other.tokens
@@ -379,3 +392,25 @@ class Version
     return m.captures.first unless m.nil?
   end
 end
+
+class HeadVersion < Version
+  attr_reader :commit
+
+  def initialize(val)
+    super
+    @commit = @version[/^HEAD-(.+)$/, 1]
+  end
+
+  def update_commit(commit)
+    @commit = commit
+    @version = if commit
+      "HEAD-#{commit}"
+    else
+      "HEAD"
+    end
+  end
+
+  def head?
+    true
+  end
+end

commit 45b3bfd11ac1d9d12d0e885576702eab2acc60cb
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    download_strategy: use short hash for git last_commit

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index f690155..cb94fa3 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -592,7 +592,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
   end
 
   def last_commit
-    Utils.popen_read("git", "--git-dir", git_dir ,"rev-parse", "HEAD").chomp
+    Utils.popen_read("git", "--git-dir", git_dir ,"rev-parse", "--short", "HEAD").chomp
   end
 
   private

commit 0b2cc5c20db30f5d0046091f8c2318752f7b0659
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    test_download_strategies: add git tests

diff --git a/Library/Homebrew/test/test_download_strategies.rb b/Library/Homebrew/test/test_download_strategies.rb
index 24f38ad..9a53d48 100644
--- a/Library/Homebrew/test/test_download_strategies.rb
+++ b/Library/Homebrew/test/test_download_strategies.rb
@@ -60,6 +60,69 @@ class VCSDownloadStrategyTests < Homebrew::TestCase
   end
 end
 
+class GitDownloadStrategyTests < Homebrew::TestCase
+  include FileUtils
+
+  def setup
+    resource = ResourceDouble.new("https://github.com/homebrew/foo")
+    @commit_id = 1
+    @strategy = GitDownloadStrategy.new("baz", resource)
+    @cached_location = @strategy.cached_location
+    mkpath @cached_location
+    touch @cached_location/"README"
+  end
+
+  def teardown
+    rmtree @cached_location
+  end
+
+  def git_commit_all
+    shutup do
+      system "git", "add", "--all"
+      system "git", "commit", "-m", "commit number #{@commit_id}"
+      @commit_id += 1
+    end
+  end
+
+  def inside_repo_using_git_env
+    initial_env = ENV.to_hash
+    %w[AUTHOR COMMITTER].each do |role|
+      ENV["GIT_#{role}_NAME"] = "brew tests"
+      ENV["GIT_#{role}_EMAIL"] = "brew-tests@localhost"
+      ENV["GIT_#{role}_DATE"] = "Thu May 21 00:04:11 2009 +0100"
+    end
+    @cached_location.cd do
+      yield
+    end
+  ensure
+    ENV.replace(initial_env)
+  end
+
+  def setup_git_repo
+    inside_repo_using_git_env do
+      shutup do
+        system "git", "init"
+        system "git", "remote", "add", "origin", "https://github.com/Homebrew/homebrew-foo"
+      end
+      git_commit_all
+    end
+  end
+
+  def test_source_modified_time
+    setup_git_repo
+    assert_equal 1242860651, @strategy.source_modified_time.to_i
+  end
+
+  def test_last_commit
+    setup_git_repo
+    inside_repo_using_git_env do
+      touch "LICENSE"
+      git_commit_all
+    end
+    assert_equal "c50c79b", @strategy.last_commit
+  end
+end
+
 class DownloadStrategyDetectorTests < Homebrew::TestCase
   def setup
     @d = DownloadStrategyDetector.new

commit 2f5f352baa95ce9cc6b4e0007ee2fc028ffc2a1a
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    VCSDownloadStrategy: add last_commit method

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 4ce3e34..f690155 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -153,6 +153,12 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
     version.head?
   end
 
+  # Return last commit's unique identifier for the repository.
+  # Return most recent modified timestamp unless overridden.
+  def last_commit
+    source_modified_time.to_i.to_s
+  end
+
   private
 
   def cache_tag
@@ -501,6 +507,10 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
     Time.parse REXML::XPath.first(xml, "//date/text()").to_s
   end
 
+  def last_commit
+    Utils.popen_read("svn", "info", "--show-item", "revision", cached_location.to_s).strip
+  end
+
   private
 
   def repo_url
@@ -581,6 +591,10 @@ class GitDownloadStrategy < VCSDownloadStrategy
     Time.parse Utils.popen_read("git", "--git-dir", git_dir, "show", "-s", "--format=%cD")
   end
 
+  def last_commit
+    Utils.popen_read("git", "--git-dir", git_dir ,"rev-parse", "HEAD").chomp
+  end
+
   private
 
   def cache_tag
@@ -818,6 +832,10 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
     Time.parse Utils.popen_read("hg", "tip", "--template", "{date|isodate}", "-R", cached_location.to_s)
   end
 
+  def last_commit
+    Utils.popen_read("hg", "parent", "--template", "{node}", "-R", cached_location.to_s)
+  end
+
   private
 
   def cache_tag
@@ -854,6 +872,10 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
     Time.parse Utils.popen_read("bzr", "log", "-l", "1", "--timezone=utc", cached_location.to_s)[/^timestamp: (.+)$/, 1]
   end
 
+  def last_commit
+    Utils.popen_read("bzr", "revno", cached_location.to_s).chomp
+  end
+
   private
 
   def cache_tag
@@ -891,6 +913,10 @@ class FossilDownloadStrategy < VCSDownloadStrategy
     Time.parse Utils.popen_read("fossil", "info", "tip", "-R", cached_location.to_s)[/^uuid: +\h+ (.+)$/, 1]
   end
 
+  def last_commit
+    Utils.popen_read("fossil", "info", "tip", "-R", cached_location.to_s)[/^uuid: +(\h+) .+$/, 1]
+  end
+
   private
 
   def cache_tag

commit fbac41d95bc7d9500eed195b46aba2a95ed89b18
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    test_formula: improve test_migration_needed

diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 588ba20..024d5c2 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -46,6 +46,7 @@ class FormulaTests < Homebrew::TestCase
     f.instance_variable_set(:@tap, CoreTap.instance)
 
     oldname_prefix = HOMEBREW_CELLAR/"oldname/2.20"
+    newname_prefix = HOMEBREW_CELLAR/"newname/2.10"
     oldname_prefix.mkpath
     oldname_tab = Tab.empty
     oldname_tab.tabfile = oldname_prefix.join("INSTALL_RECEIPT.json")
@@ -58,8 +59,13 @@ class FormulaTests < Homebrew::TestCase
     oldname_tab.write
 
     assert_predicate f, :migration_needed?
+
+    newname_prefix.mkpath
+
+    refute_predicate f, :migration_needed?
   ensure
     oldname_prefix.parent.rmtree
+    newname_prefix.parent.rmtree
   end
 
   def test_installed?

commit 4aedeea96d4c9d9c20bc822d520e453ac8964c56
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    formula: simplify migration_needed?

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index cb58824..25797c8 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -957,8 +957,14 @@ class Formula
   end
 
   def migration_needed?
-    oldname && !rack.exist? && (dir = HOMEBREW_CELLAR/oldname).directory? &&
-      !dir.subdirs.empty? && tap == Tab.for_keg(dir.subdirs.first).tap
+    return false unless oldname
+    return false if rack.exist?
+
+    old_rack = HOMEBREW_CELLAR/oldname
+    return false unless old_rack.directory?
+    return false if old_rack.subdirs.empty?
+
+    tap == Tab.for_keg(old_rack.subdirs.first).tap
   end
 
   # @private

commit da06e813c2b8925499484ff8be7772f6aa6ae9e3
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    cmd/install: use migration_needed?

diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb
index 60141c4..c07143e 100644
--- a/Library/Homebrew/cmd/install.rb
+++ b/Library/Homebrew/cmd/install.rb
@@ -132,8 +132,7 @@ module Homebrew
           msg = "#{f.full_name}-#{f.installed_version} already installed"
           msg << ", it's just not linked" unless f.linked_keg.symlink? || f.keg_only?
           opoo msg
-        elsif f.oldname && (dir = HOMEBREW_CELLAR/f.oldname).directory? && !dir.subdirs.empty? \
-            && f.tap == Tab.for_keg(dir.subdirs.first).tap && !ARGV.force?
+        elsif f.migration_needed? && !ARGV.force?
           # Check if the formula we try to install is the same as installed
           # but not migrated one. If --force passed then install anyway.
           opoo "#{f.oldname} already installed, it's just not migrated"

commit 0d3b5f6849e236272d6a1b83a1869845608b3d10
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    test_formula: add migration_needed test

diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index ebbd308..588ba20 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -40,6 +40,28 @@ class FormulaTests < Homebrew::TestCase
     f.rack.rmtree
   end
 
+  def test_migration_needed
+    f = Testball.new("newname")
+    f.instance_variable_set(:@oldname, "oldname")
+    f.instance_variable_set(:@tap, CoreTap.instance)
+
+    oldname_prefix = HOMEBREW_CELLAR/"oldname/2.20"
+    oldname_prefix.mkpath
+    oldname_tab = Tab.empty
+    oldname_tab.tabfile = oldname_prefix.join("INSTALL_RECEIPT.json")
+    oldname_tab.write
+
+    refute_predicate f, :migration_needed?
+
+    oldname_tab.tabfile.unlink
+    oldname_tab.source["tap"] = "homebrew/core"
+    oldname_tab.write
+
+    assert_predicate f, :migration_needed?
+  ensure
+    oldname_prefix.parent.rmtree
+  end
+
   def test_installed?
     f = Testball.new
     f.stubs(:installed_prefix).returns(stub(:directory? => false))

commit 9c15174e3cfa9a1f832b6532ef7480ef2b16a44e
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    formula: simplify outdated_versions logic

diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index ebd2376..cb58824 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -956,33 +956,25 @@ class Formula
     @oldname_lock.unlock unless @oldname_lock.nil?
   end
 
+  def migration_needed?
+    oldname && !rack.exist? && (dir = HOMEBREW_CELLAR/oldname).directory? &&
+      !dir.subdirs.empty? && tap == Tab.for_keg(dir.subdirs.first).tap
+  end
+
   # @private
   def outdated_versions
     @outdated_versions ||= begin
       all_versions = []
-      older_or_same_tap_versions = []
 
-      if oldname && !rack.exist? && (dir = HOMEBREW_CELLAR/oldname).directory? &&
-        !dir.subdirs.empty? && tap == Tab.for_keg(dir.subdirs.first).tap
-        raise Migrator::MigrationNeededError.new(self)
-      end
+      raise Migrator::MigrationNeededError.new(self) if migration_needed?
 
       installed_kegs.each do |keg|
         version = keg.version
         all_versions << version
-        older_version = pkg_version <= version
-
-        tab_tap = Tab.for_keg(keg).tap
-        if tab_tap.nil? || tab_tap == tap || older_version
-          older_or_same_tap_versions << version
-        end
+        return [] if pkg_version <= version
       end
 
-      if older_or_same_tap_versions.all? { |v| pkg_version > v }
-        all_versions.sort!
-      else
-        []
-      end
+      all_versions.sort!
     end
   end
 

commit d47df68cbd03fb621825d12a531f91938571ec04
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    test_formula: add outdated_versions tests

diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb
index 7f015f6..ebbd308 100644
--- a/Library/Homebrew/test/test_formula.rb
+++ b/Library/Homebrew/test/test_formula.rb
@@ -406,3 +406,101 @@ class FormulaTests < Homebrew::TestCase
     assert f_true.pour_bottle?
   end
 end
+
+class OutdatedVersionsTests < Homebrew::TestCase
+  attr_reader :outdated_prefix, :same_prefix, :greater_prefix, :head_prefix
+  attr_reader :f
+
+  def setup
+    @f = formula { url "foo"; version "1.20" }
+    @outdated_prefix = HOMEBREW_CELLAR/"#{f.name}/1.11"
+    @same_prefix = HOMEBREW_CELLAR/"#{f.name}/1.20"
+    @greater_prefix = HOMEBREW_CELLAR/"#{f.name}/1.21"
+    @head_prefix = HOMEBREW_CELLAR/"#{f.name}/HEAD"
+  end
+
+  def teardown
+    @f.rack.rmtree
+  end
+
+  def setup_tab_for_prefix(prefix, tap_string=nil)
+    prefix.mkpath
+    tab = Tab.empty
+    tab.tabfile = prefix.join("INSTALL_RECEIPT.json")
+    tab.source["tap"] = tap_string if tap_string
+    tab.write
+    tab
+  end
+
+  def test_greater_different_tap_installed
+    setup_tab_for_prefix(greater_prefix, "user/repo")
+    assert_predicate f.outdated_versions, :empty?
+  end
+
+  def test_greater_same_tap_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+    setup_tab_for_prefix(greater_prefix, "homebrew/core")
+    assert_predicate f.outdated_versions, :empty?
+  end
+
+  def test_outdated_different_tap_installed
+    setup_tab_for_prefix(outdated_prefix, "user/repo")
+    refute_predicate f.outdated_versions, :empty?
+  end
+
+  def test_outdated_same_tap_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+    setup_tab_for_prefix(outdated_prefix, "homebrew/core")
+    refute_predicate f.outdated_versions, :empty?
+  end
+
+  def test_same_head_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+    setup_tab_for_prefix(head_prefix, "homebrew/core")
+    assert_predicate f.outdated_versions, :empty?
+  end
+
+  def test_different_head_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+    setup_tab_for_prefix(head_prefix, "user/repo")
+    assert_predicate f.outdated_versions, :empty?
+  end
+
+  def test_mixed_taps_greater_version_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+    setup_tab_for_prefix(outdated_prefix, "homebrew/core")
+    setup_tab_for_prefix(greater_prefix, "user/repo")
+
+    assert_predicate f.outdated_versions, :empty?
+
+    setup_tab_for_prefix(greater_prefix, "homebrew/core")
+
+    assert_predicate f.outdated_versions, :empty?
+  end
+
+  def test_mixed_taps_outdated_version_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+
+    extra_outdated_prefix = HOMEBREW_CELLAR/"#{f.name}/1.0"
+
+    setup_tab_for_prefix(outdated_prefix)
+    setup_tab_for_prefix(extra_outdated_prefix, "homebrew/core")
+
+    refute_predicate f.outdated_versions, :empty?
+
+    setup_tab_for_prefix(outdated_prefix, "user/repo")
+
+    refute_predicate f.outdated_versions, :empty?
+  end
+
+  def test_same_version_tap_installed
+    f.instance_variable_set(:@tap, CoreTap.instance)
+    setup_tab_for_prefix(same_prefix, "homebrew/core")
+
+    assert_predicate f.outdated_versions, :empty?
+
+    setup_tab_for_prefix(same_prefix, "user/repo")
+
+    assert_predicate f.outdated_versions, :empty?
+  end
+end

commit 5703ebf49667e21f81564dd29be3e68f1df9e7c4
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    download_strategy: cvs source_modified_time (#268)

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 0de9bf8..1ac1dbc 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -713,6 +713,21 @@ class CVSDownloadStrategy < VCSDownloadStrategy
     end
   end
 
+  def source_modified_time
+    # Look for the file timestamps under {#cached_location} because
+    # newly-unpacked directory can have timestamps of the moment of copying.
+    # Filter CVS's files because the timestamp for each of them is the moment
+    # of clone.
+    max_mtime = Time.at(0)
+    cached_location.find do |f|
+      Find.prune if f.directory? && f.basename.to_s == "CVS"
+      next unless f.file?
+      mtime = f.mtime
+      max_mtime = mtime if mtime > max_mtime
+    end
+    max_mtime
+  end
+
   def stage
     cp_r File.join(cached_location, "."), Dir.pwd
   end

commit 90d3317d7dc9d809d7fc8da15e824161a0ce3008
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    download_strategy: use svn info --xml (#174)

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index a1e0ca5..0de9bf8 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -1,4 +1,5 @@
 require "utils/json"
+require "rexml/document"
 
 class AbstractDownloadStrategy
   include FileUtils
@@ -495,7 +496,8 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
   end
 
   def source_modified_time
-    Time.parse Utils.popen_read("svn", "info", cached_location.to_s).strip[/^Last Changed Date: (.+)$/, 1]
+    xml = REXML::Document.new(Utils.popen_read("svn", "info", "--xml", cached_location.to_s))
+    Time.parse REXML::XPath.first(xml, "//date/text()").to_s
   end
 
   private

commit 6f1116c8e159bbeb718b0dd1e7bf5e8f6408f012
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    download_strategy: fossil source_modified_time

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 1384131..a1e0ca5 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -838,6 +838,10 @@ class FossilDownloadStrategy < VCSDownloadStrategy
     safe_system(*args)
   end
 
+  def source_modified_time
+    Time.parse Utils.popen_read("fossil", "info", "tip", "-R", cached_location.to_s)[/^uuid: +\h+ (.+)$/, 1]
+  end
+
   private
 
   def cache_tag

commit f79edbc560b2a7b0cf6455dc2093081bfcde40c2
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    download_strategy: bazaar source_modified_time

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index 71f47a2..1384131 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -801,6 +801,10 @@ class BazaarDownloadStrategy < VCSDownloadStrategy
     rm_r ".bzr"
   end
 
+  def source_modified_time
+    Time.parse Utils.popen_read("bzr", "log", "-l", "1", "--timezone=utc", cached_location.to_s)[/^timestamp: (.+)$/, 1]
+  end
+
   private
 
   def cache_tag

commit 155960d991a8f12b1176a55f0da8de1cb6f1ee24
Author: Vlad Shablinsky <vladshablinsky@gmail.com>

    download_strategy: mercurial source_modified_time

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index ef750cd..71f47a2 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -765,6 +765,10 @@ class MercurialDownloadStrategy < VCSDownloadStrategy
     end
   end
 
+  def source_modified_time
+    Time.parse Utils.popen_read("hg", "tip", "--template", "{date|isodate}", "-R", cached_location.to_s)
+  end
+
   private
 
   def cache_tag

commit 3ff1aa9fa3cb467a8fff912e780822a788303cff
Author: Uladzislau Shablinski <vladshablinsky@gmail.com>

    download_strategy: add svn source_modified_time (#156)

diff --git a/Library/Homebrew/download_strategy.rb b/Library/Homebrew/download_strategy.rb
index c3124fe..ef750cd 100644
--- a/Library/Homebrew/download_strategy.rb
+++ b/Library/Homebrew/download_strategy.rb
@@ -494,6 +494,10 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
     quiet_safe_system "svn", "export", "--force", cached_location, Dir.pwd
   end
 
+  def source_modified_time
+    Time.parse Utils.popen_read("svn", "info", cached_location.to_s).strip[/^Last Changed Date: (.+)$/, 1]
+  end
+
   private
 
   def repo_url
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment