You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by bo...@apache.org on 2012/07/19 21:25:38 UTC

svn commit: r1363492 - in /buildr/trunk: CHANGELOG lib/buildr/core/build.rb spec/core/build_spec.rb

Author: boisvert
Date: Thu Jul 19 19:25:38 2012
New Revision: 1363492

URL: http://svn.apache.org/viewvc?rev=1363492&view=rev
Log:
BUILDR-645 Support Mercurial as a version control system (Tan Quach)

Modified:
    buildr/trunk/CHANGELOG
    buildr/trunk/lib/buildr/core/build.rb
    buildr/trunk/spec/core/build_spec.rb

Modified: buildr/trunk/CHANGELOG
URL: http://svn.apache.org/viewvc/buildr/trunk/CHANGELOG?rev=1363492&r1=1363491&r2=1363492&view=diff
==============================================================================
--- buildr/trunk/CHANGELOG (original)
+++ buildr/trunk/CHANGELOG Thu Jul 19 19:25:38 2012
@@ -1,4 +1,5 @@
 1.4.8 (Pending)
+* Added:  BUILDR-645 Support Mercurial as a version control system (Tan Quach)
 * Fixed:  BUILDR-646 TGZ files do not keep their permissions when extracted
           via Buildr::Unzip#extract
 * Added:  Add add_exploded_ear_artifact and add_exploded_ejb_artifact to the idea project extension.

Modified: buildr/trunk/lib/buildr/core/build.rb
URL: http://svn.apache.org/viewvc/buildr/trunk/lib/buildr/core/build.rb?rev=1363492&r1=1363491&r2=1363492&view=diff
==============================================================================
--- buildr/trunk/lib/buildr/core/build.rb (original)
+++ buildr/trunk/lib/buildr/core/build.rb Thu Jul 19 19:25:38 2012
@@ -100,6 +100,58 @@ module Buildr
 
   end
 
+  module Hg #:nodoc:
+    module_function
+
+    # :call-seq:
+    #   hg(*args)
+    #
+    # Executes a Mercurial (hg) command passing through the args and returns the output. 
+    # Throws exception if the exit status is not zero. For example:
+    #   hg 'commit'
+    #   hg 'update', 'default'
+    def hg(*args)
+      cmd = "hg #{args.shift} #{args.map { |arg| arg.inspect }.join(' ')}"
+      output = `#{cmd}`
+      fail "Mercurial command \"#{cmd}\" failed with status #{$?.exitstatus}\n#{output}" unless $?.exitstatus == 0
+      return output
+    end
+
+    # Return a list of uncommitted / untracked files as reported by hg status
+    # The codes used to show the status of files are:
+    #   M = modified
+    #   A = added
+    #   R = removed
+    #   C = clean
+    #   ! = missing (deleted by non-hg command, but still tracked)
+    #   ? = not tracked
+    #   I = ignored
+    #     = origin of the previous file listed as A (added)
+    def uncommitted_files
+      `hg status`.scan(/^(A|M|R|!|\?) (\S.*)$/).map{ |match| match.last.split.last }
+    end
+
+    # Commit the given file with a message. The file should already be added to the Mercurial index.
+    def commit(file, message)
+      hg 'commit', '-m', message, file
+    end
+
+    # Update the remote branch with the local commits
+    # This will push the current remote destination and current branch.
+    def push
+      hg 'push'
+    end
+
+    # Return the name of the current local branch or nil if none.
+    def current_branch
+      hg('branch').to_s.strip
+    end
+
+    # Return the aliases (if any) of any remote repositories which the current local branch tracks
+    def remote
+      hg('paths').scan(/^(?:default|default-push)\s+=\s+(\S.*)/).map{ |match| match.last }
+    end
+  end
 
   module Git  #:nodoc:
     module_function
@@ -415,6 +467,54 @@ module Buildr
   end
 
 
+  class HgRelease < Release
+    class << self
+      def applies_to?
+        if File.exist? '.hg/requires'
+          true
+        else
+          curr_pwd = Dir.pwd
+          Dir.chdir('..') do
+            return false if curr_pwd == Dir.pwd # Means going up one level is not possible.
+            applies_to?
+          end
+        end
+      end
+    end
+
+    # Fails if one of these 2 conditions are not met:
+    #   1. The reository is not 'clean'; no content staged or unstaged
+    #   2. The repository is only a local repository and has no remote refs
+    def check
+      super
+      info "Working in branch '#{Hg.current_branch}'"
+      uncommitted = Hg.uncommitted_files
+      fail "Uncommitted files violate the First Principle Of Release!\n#{uncommitted.join("\n")}" unless uncommitted.empty?
+      fail "You are releasing from a local branch that does not track a remote!" if Hg.remote.empty?
+    end
+
+    # Tag this release in Mercurial
+    def tag_release(tag)
+      unless this_version == extract_version
+        info "Committing buildfile with version number #{extract_version}"
+        Hg.commit File.basename(Buildr.application.buildfile.to_s), message
+        Hg.push if Hg.remote
+      end
+      info "Tagging release #{tag}"
+      Hg.hg 'tag', tag, '-m', "[buildr] Cutting release #{tag}"
+      Hg.push if Hg.remote
+    end
+
+    # Update buildfile with next version number
+    def update_version_to_next
+      super
+      info "Current version is now #{extract_version}"
+      Hg.commit File.basename(Buildr.application.buildfile.to_s), message
+      Hg.push if Hg.remote
+    end     
+  end
+
+
   class GitRelease < Release
     class << self
       def applies_to?
@@ -430,7 +530,7 @@ module Buildr
       end
     end
 
-    # Fails if one of theses 2 conditions are not met:
+    # Fails if one of these 2 conditions are not met:
     #    1. the repository is clean: no content staged or unstaged
     #    2. some remote repositories are defined but the current branch does not track any
     def check
@@ -491,6 +591,7 @@ module Buildr
     end
   end
 
+  Release.add HgRelease
   Release.add SvnRelease
   Release.add GitRelease
 

Modified: buildr/trunk/spec/core/build_spec.rb
URL: http://svn.apache.org/viewvc/buildr/trunk/spec/core/build_spec.rb?rev=1363492&r1=1363491&r2=1363492&view=diff
==============================================================================
--- buildr/trunk/spec/core/build_spec.rb (original)
+++ buildr/trunk/spec/core/build_spec.rb Thu Jul 19 19:25:38 2012
@@ -200,6 +200,66 @@ describe Project, '#reports' do
 end
 
 
+describe Hg do
+  describe '#current_branch' do
+    it 'should return the correct branch' do
+      Hg.should_receive(:hg).with('branch').and_return("default\n")
+      Hg.send(:current_branch).should == 'default'
+    end
+  end
+
+  describe '#uncommitted_files' do
+    it 'should return an array of modified files' do
+      Hg.should_receive(:`).with('hg status').and_return <<-EOF
+M abc.txt
+M xyz.txt
+R hello
+R removed
+! conflict
+A README
+? ignore.txt
+      EOF
+      Hg.uncommitted_files.should include('abc.txt', 'xyz.txt', 'hello', 'README', 'conflict', 'ignore.txt')
+    end
+  end
+
+  describe '#uncommitted_files' do
+    it 'should return an empty array on a clean repository' do
+      Hg.should_receive(:`).with('hg status').and_return "\n"
+      Hg.uncommitted_files.should be_empty
+    end   
+  end
+
+  describe '#remote' do
+    it 'should return the aliases of the default remote repositories' do
+      Hg.should_receive(:hg).with('paths').and_return <<-EOF
+default = https://hg.apache.org/repo/my-repo
+    EOF
+    Hg.send(:remote).should include('https://hg.apache.org/repo/my-repo')
+    end
+
+    it 'should return the aliases of the default push remote repositories' do
+      Hg.should_receive(:hg).with('paths').and_return <<-EOF
+default-push = https://hg.apache.org/repo/my-repo
+    EOF
+    Hg.send(:remote).should include('https://hg.apache.org/repo/my-repo')
+    end
+
+    it 'should return empty array when no remote repositories found' do
+      Hg.should_receive(:hg).with('paths').and_return "\n"
+      Hg.send(:remote).should be_empty
+    end
+
+    it 'should return empty array when no default-push remote repository found' do
+      Hg.should_receive(:hg).with('paths').and_return <<-EOF
+blah = https://bitbucket.org/sample-repo
+      EOF
+      Hg.send(:remote).should be_empty
+    end
+  end
+end # end of Hg
+
+
 describe Git do
   describe '#uncommitted_files' do
     it 'should return an empty array on a clean repository' do
@@ -346,6 +406,11 @@ end # of Buildr::Svn
 
 describe Release do
   describe 'find' do
+    it 'should return HgRelease if project uses Hg' do
+      write '.hg/requires'
+      Release.find.should be_instance_of(HgRelease)
+    end
+
     it 'should return GitRelease if project uses Git' do
       write '.git/config'
       Release.find.should be_instance_of(GitRelease)
@@ -679,6 +744,58 @@ shared_examples_for 'a release process' 
 end
 
 
+describe HgRelease do
+  it_should_behave_like 'a release process'
+
+  before do
+    write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
+    @release = HgRelease.new
+    Hg.stub!(:hg)
+    Hg.stub!(:remote).and_return('https://bitbucket.org/sample-repo')
+    Hg.stub!(:current_branch).and_return('default')
+  end
+
+  describe '#applies_to?' do
+    it 'should reject a non-hg repo' do
+      Dir.chdir(Dir.tmpdir) do
+        HgRelease.applies_to?.should be_false
+      end
+    end
+
+    it 'should accept a hg repo' do
+      FileUtils.mkdir '.hg'
+      FileUtils.touch File.join('.hg', 'requires')
+      HgRelease.applies_to?.should be_true
+    end
+  end
+
+  describe '#check' do
+    before do
+      @release = HgRelease.new
+      @release.send(:this_version=, '1.0.0-SNAPSHOT')
+    end
+
+    it 'should accept a clean repo' do
+      Hg.should_receive(:uncommitted_files).and_return([])
+      Hg.should_receive(:remote).and_return(["http://bitbucket.org/sample-repo"])
+      lambda { @release.check }.should_not raise_error
+    end
+
+    it 'should reject a dirty repo' do
+      Hg.should_receive(:uncommitted_files).and_return(['dirty_file.txt'])
+      lambda { @release.check }.should raise_error(RuntimeError, /uncommitted files/i)
+    end
+
+    it 'should reject a local branch not tracking a remote repo' do
+      Hg.should_receive(:uncommitted_files).and_return([])
+      Hg.should_receive(:remote).and_return([])
+      lambda{ @release.check }.should raise_error(RuntimeError,
+        "You are releasing from a local branch that does not track a remote!")
+    end
+  end
+end
+
+
 describe GitRelease do
   it_should_behave_like 'a release process'