# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with this
# work for additional information regarding copyright ownership.  The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.


require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helpers'))

RSpec.shared_examples 'local task' do
  it "should execute task for project in current directory" do
    define 'foobar'
    lambda { @task.invoke }.should run_task("foobar:#{@task.name}")
  end

  it "should not execute task for projects in other directory" do
    define 'foobar', :base_dir=>'elsewhere'
    lambda { task('build').invoke }.should_not run_task('foobar:build')
  end
end


describe 'build task' do
  it_should_behave_like 'local task'
  before(:each) { @task = task('build') }
end

describe 'clean task' do
  it_should_behave_like 'local task'
  before(:each) { @task = task('clean') }
end

describe 'package task' do
  it_should_behave_like 'local task'
  before(:each) { @task = task('package') }

  it 'should execute build task as prerequisite' do
    lambda { @task.invoke }.should run_task('build')
  end
end

describe 'install task' do
  it_should_behave_like 'local task'
  before(:each) { @task = task('install') }

  it 'should execute package task as prerequisite' do
    lambda { @task.invoke }.should run_task('package')
  end
end

describe 'uninstall task' do
  it_should_behave_like 'local task'
  before(:each) { @task = task('uninstall') }
end

describe 'upload task' do
  it_should_behave_like 'local task'
  before(:each) { @task = task('upload') }

  it 'should execute package task as prerequisite' do
    lambda { @task.invoke }.should run_task('package')
  end
end


describe Project, '#build' do
  it 'should return the project\'s build task' do
    define('foo').build.should eql(task('foo:build'))
  end

  it 'should enhance the project\'s build task' do
    task 'prereq'
    task 'action'
    define 'foo' do
      build 'prereq' do
        task('action').invoke
      end
    end
    lambda { project('foo').build.invoke }.should run_tasks('prereq', 'action')
  end

  it 'should execute build task for sub-project' do
    define 'foo' do
      define 'bar'
    end
    lambda { task('foo:build').invoke }.should run_task('foo:bar:build')
  end

  it 'should not execute build task of other projects' do
    define 'foo'
    define 'bar'
    lambda { task('foo:build').invoke }.should_not run_task('bar:build')
  end
end


describe Project, '#clean' do
  it 'should return the project\'s clean task' do
    define('foo').clean.should eql(task('foo:clean'))
  end

  it 'should enhance the project\'s clean task' do
    task 'prereq'
    task 'action'
    define 'foo' do
      clean 'prereq' do
        task('action').invoke
      end
    end
    lambda { project('foo').clean.invoke }.should run_tasks('prereq', 'action')
  end

  it 'should remove target directory' do
    define 'foo' do
      self.layout[:target] = 'targeted'
    end
    mkpath 'targeted'
    lambda { project('foo').clean.invoke }.should change { File.exist?('targeted') }.from(true).to(false)
  end

  it 'should remove reports directory' do
    define 'foo' do
      self.layout[:reports] = 'reported'
    end
    mkpath 'reported'
    lambda { project('foo').clean.invoke }.should change { File.exist?('reported') }.from(true).to(false)
  end

  it 'should execute clean task for sub-project' do
    define 'foo' do
      define 'bar'
    end
    lambda { task('foo:clean').invoke }.should run_task('foo:bar:clean')
  end

  it 'should not execute clean task of other projects' do
    define 'foo'
    define 'bar'
    lambda { task('foo:clean').invoke }.should_not run_task('bar:clean')
  end
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
      Git.should_receive(:`).with('git status').and_return <<-EOF
# On branch master
nothing to commit (working directory clean)
      EOF
      Git.uncommitted_files.should be_empty
    end

    it 'should reject a dirty repository, Git 1.4.2 or former' do
      Git.should_receive(:`).with('git status').and_return <<-EOF
# On branch master
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   lib/buildr.rb
#       modified:   spec/buildr_spec.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       error.log
      EOF
      Git.uncommitted_files.should include('lib/buildr.rb', 'error.log')
    end

    it 'should reject a dirty repository, Git 1.4.3 or higher' do
      Git.should_receive(:`).with('git status').and_return <<-EOF
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#\tmodified:   lib/buildr.rb
#\tmodified:   spec/buildr_spec.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#\terror.log
no changes added to commit (use "git add" and/or "git commit -a")
      EOF
      Git.uncommitted_files.should include('lib/buildr.rb', 'error.log')
    end
  end

  describe '#remote' do
    it 'should return the name of the corresponding remote' do
      Git.should_receive(:git).with('config', '--get', 'branch.master.remote').and_return "origin\n"
      Git.should_receive(:git).with('remote').and_return "upstream\norigin\n"
      Git.send(:remote, 'master').should == 'origin'
    end

    it 'should return nil if no remote for the given branch' do
      Git.should_receive(:git).with('config', '--get', 'branch.master.remote').and_return "\n"
      Git.should_not_receive(:git).with('remote')
      Git.send(:remote, 'master').should be_nil
    end
  end

  describe '#current_branch' do
    it 'should return the current branch' do
      Git.should_receive(:git).with('branch').and_return("  master\n* a-clever-idea\n  ze-great-idea")
      Git.send(:current_branch).should == 'a-clever-idea'
    end
  end

end # of Git


describe Svn do
  describe '#tag' do
    it 'should remove any existing tag with the same name' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/trunk')
      Svn.stub(:copy)
      Svn.should_receive(:remove).with('http://my.repo.org/foo/tags/1.0.0', 'Removing old copy')

      Svn.tag '1.0.0'
    end

    it 'should do an svn copy with the release version' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/trunk')
      Svn.stub(:remove)
      Svn.should_receive(:copy).with(Dir.pwd, 'http://my.repo.org/foo/tags/1.0.0', 'Release 1.0.0')

      Svn.tag '1.0.0'
    end
  end

  # Reference: http://svnbook.red-bean.com/en/1.4/svn.reposadmin.planning.html#svn.reposadmin.projects.chooselayout
  describe '#tag_url' do
    it 'should accept to tag foo/trunk' do
      Svn.tag_url('http://my.repo.org/foo/trunk', '1.0.0').should == 'http://my.repo.org/foo/tags/1.0.0'
    end

    it 'should accept to tag foo/branches/1.0' do
      Svn.tag_url('http://my.repo.org/foo/branches/1.0', '1.0.1').should == 'http://my.repo.org/foo/tags/1.0.1'
    end

    it 'should accept to tag trunk/foo' do
      Svn.tag_url('http://my.repo.org/trunk/foo', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0'
    end

    it 'should accept to tag branches/foo/1.0' do
      Svn.tag_url('http://my.repo.org/branches/foo/1.0', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0'
    end

    describe '#repo_url' do
      it 'should extract the SVN URL from svn info' do
        Svn.should_receive(:svn).and_return <<-XML
<?xml version="1.0"?>
<info>
<entry
   kind="dir"
   path="."
   revision="724987">
<url>http://my.repo.org/foo/trunk</url>
<repository>
<root>http://my.repo.org</root>
<uuid>13f79535-47bb-0310-9956-ffa450edef68</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="724955">
<author>boisvert</author>
<date>2008-12-10T01:53:51.240936Z</date>
</commit>
</entry>
</info>
        XML
        Svn.repo_url.should == 'http://my.repo.org/foo/trunk'
      end
    end

  end

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)
    end

    it 'should return SvnRelease if project uses SVN' do
      write '.svn/xml'
      Release.find.should be_instance_of(SvnRelease)
    end

    # TravisCI seems to place the tmp directory
    # sub-ordinate to git repository so this will not work
    unless ENV['TRAVIS_BUILD_ID']
      it 'should return nil if no known release process' do
        Dir.chdir(Dir.tmpdir) do
          Release.find.should be_nil
        end
      end
    end

    after :each do
      Release.instance_exec { @release = nil }
    end
  end
end


RSpec.shared_examples 'a release process' do

  describe '#make' do
    before do
      write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
      # Prevent a real call to a spawned buildr process.
      @release.stub(:buildr)
      @release.stub(:check)
      @release.should_receive(:sh).with('buildr', '--buildfile', File.expand_path('buildfile.next'),
                                          '--environment', 'development', 'clean', 'upload', 'DEBUG=no')
    end

    it 'should tag a release with the release version' do
      @release.stub(:update_version_to_next)
      @release.should_receive(:tag_release).with('1.0.0')
      @release.make
    end

    it 'should not alter the buildfile before tagging' do
      @release.stub(:update_version_to_next)
      @release.should_receive(:tag_release).with('1.0.0')
      @release.make
      file('buildfile').should contain('VERSION_NUMBER = "1.0.0"')
    end

    it 'should update the buildfile with the next version number' do
      @release.stub(:tag_release)
      @release.make
      file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"')
    end

    it 'should keep leading zeros in the next version number' do
      write 'buildfile', "VERSION_NUMBER = '1.0.001-SNAPSHOT'"
      @release.stub(:tag_release)
      @release.make
      file('buildfile').should contain('VERSION_NUMBER = "1.0.002-SNAPSHOT"')
    end

    it 'should commit the updated buildfile' do
      @release.stub(:tag_release)
      @release.make
      file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"')
    end

    it 'should not consider "-rc" as "-SNAPSHOT"' do
      write 'buildfile', "VERSION_NUMBER = '1.0.0-rc1'"
      @release.stub(:tag_release)
      @release.make
      file('buildfile').should contain('VERSION_NUMBER = "1.0.0-rc1"')
    end

    it 'should only commit the updated buildfile if the version changed' do
      write 'buildfile', "VERSION_NUMBER = '1.0.0-rc1'"
      @release.should_not_receive(:update_version_to_next)
      @release.stub(:tag_release)
      @release.make
    end
  end

  describe '#resolve_next_version' do

    it 'should increment the version number if SNAPSHOT' do
      @release.send(:resolve_next_version, "1.0.0-SNAPSHOT").should == '1.0.1-SNAPSHOT'
    end

    it 'should NOT increment the version number if no SNAPSHOT' do
      @release.send(:resolve_next_version, "1.0.0").should == '1.0.0'
    end

    it 'should return the version specified by NEXT_VERSION env var' do
      ENV['NEXT_VERSION'] = "version_from_env"
      @release.send(:resolve_next_version, "1.0.0").should == 'version_from_env'
    end

    it 'should return the version specified by next_version' do
      Release.next_version = "ze_next_version"
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_next_version'
    end

    it 'should return the version specified by next_version if next_version is a proc' do
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == '1.0.0++'
    end

    it "should return the version specified by 'NEXT_VERSION' env var even if next_version is non nil" do
      ENV['NEXT_VERSION'] = "ze_version_from_env"
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env'
    end

    it "should return the version specified by 'next_version' env var even if next_version is non nil" do
      ENV['NEXT_VERSION'] = nil
      ENV['next_version'] = "ze_version_from_env_lowercase"
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env_lowercase'
    end
    after {
      Release.next_version = nil
      ENV['NEXT_VERSION'] = nil
      ENV['next_version'] = nil
    }
  end

  describe '#resolve_next_version' do

    it 'should increment the version number if SNAPSHOT' do
      @release.send(:resolve_next_version, "1.0.0-SNAPSHOT").should == '1.0.1-SNAPSHOT'
    end

    it 'should NOT increment the version number if no SNAPSHOT' do
      @release.send(:resolve_next_version, "1.0.0").should == '1.0.0'
    end

    it 'should return the version specified by NEXT_VERSION env var' do
      ENV['NEXT_VERSION'] = "version_from_env"
      @release.send(:resolve_next_version, "1.0.0").should == 'version_from_env'
    end

    it 'should return the version specified by next_version' do
      Release.next_version = "ze_next_version"
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_next_version'
    end

    it 'should return the version specified by next_version if next_version is a proc' do
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == '1.0.0++'
    end

    it "should return the version specified by 'NEXT_VERSION' env var even if next_version is non nil" do
      ENV['NEXT_VERSION'] = "ze_version_from_env"
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env'
    end

    it "should return the version specified by 'next_version' env var even if next_version is non nil" do
      ENV['NEXT_VERSION'] = nil
      ENV['next_version'] = "ze_version_from_env_lowercase"
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env_lowercase'
    end
    after {
      Release.next_version = nil
      ENV['NEXT_VERSION'] = nil
      ENV['next_version'] = nil
    }
  end

  describe '#resolve_next_version' do

    it 'should increment the version number if SNAPSHOT' do
      @release.send(:resolve_next_version, "1.0.0-SNAPSHOT").should == '1.0.1-SNAPSHOT'
    end

    it 'should NOT increment the version number if no SNAPSHOT' do
      @release.send(:resolve_next_version, "1.0.0").should == '1.0.0'
    end

    it 'should return the version specified by NEXT_VERSION env var' do
      ENV['NEXT_VERSION'] = "version_from_env"
      @release.send(:resolve_next_version, "1.0.0").should == 'version_from_env'
    end

    it 'should return the version specified by next_version' do
      Release.next_version = "ze_next_version"
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_next_version'
    end

    it 'should return the version specified by next_version if next_version is a proc' do
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == '1.0.0++'
    end

    it "should return the version specified by 'NEXT_VERSION' env var even if next_version is non nil" do
      ENV['NEXT_VERSION'] = "ze_version_from_env"
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env'
    end

    it "should return the version specified by 'next_version' env var even if next_version is non nil" do
      ENV['NEXT_VERSION'] = nil
      ENV['next_version'] = "ze_version_from_env_lowercase"
      Release.next_version = lambda {|version| "#{version}++"}
      @release.send(:resolve_next_version, "1.0.0").should == 'ze_version_from_env_lowercase'
    end
    after {
      Release.next_version = nil
      ENV['NEXT_VERSION'] = nil
      ENV['next_version'] = nil
    }
  end

  describe '#resolve_tag' do
    before do
      @release.stub(:extract_version).and_return('1.0.0')
    end

    it 'should return tag specified by tag_name' do
      Release.tag_name  = 'first'
      @release.send(:resolve_tag).should == 'first'
    end

    it 'should use tag returned by tag_name if tag_name is a proc' do
      Release.tag_name  = lambda { |version| "buildr-#{version}" }
      @release.send(:resolve_tag).should == 'buildr-1.0.0'
    end
    after { Release.tag_name = nil }
  end

  describe '#tag_release' do
    it 'should inform the user' do
      @release.stub(:extract_version).and_return('1.0.0')
      lambda { @release.tag_release('1.0.0') }.should show_info('Tagging release 1.0.0')
    end
  end

  describe '#extract_version' do
    it 'should extract VERSION_NUMBER with single quotes' do
      write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
      @release.extract_version.should == '1.0.0-SNAPSHOT'
    end

    it 'should extract VERSION_NUMBER with double quotes' do
      write 'buildfile', %{VERSION_NUMBER = "1.0.1-SNAPSHOT"}
      @release.extract_version.should == '1.0.1-SNAPSHOT'
    end

    it 'should extract VERSION_NUMBER without any spaces' do
      write 'buildfile', "VERSION_NUMBER='1.0.2-SNAPSHOT'"
      @release.extract_version.should == '1.0.2-SNAPSHOT'
    end

    it 'should extract THIS_VERSION as an alternative to VERSION_NUMBER' do
      write 'buildfile', "THIS_VERSION = '1.0.3-SNAPSHOT'"
      @release.extract_version.should == '1.0.3-SNAPSHOT'
    end

    it 'should complain if no current version number' do
      write 'buildfile', 'define foo'
      lambda { @release.extract_version }.should raise_error('Looking for THIS_VERSION = "..." in your Buildfile, none found')
    end

    it 'should use version.rb instead of buildfile, if present' do
      write 'version.rb', "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
      @release.extract_version.should == '1.0.0-SNAPSHOT'
    end

    it 'should complain if there is a version.rb file, but it contains no version number' do
      write 'version.rb', "#SOMETHING SOMETHING"
      lambda { @release.extract_version }.should raise_error('Looking for THIS_VERSION = "..." in your Buildfile, none found')
    end
  end

  describe '#with_release_candidate_version' do
    before do
      Buildr.application.stub(:buildfile).and_return(file('buildfile'))
      write 'buildfile', "THIS_VERSION = '1.1.0-SNAPSHOT'"
    end

    it 'should yield the name of the release candidate buildfile' do
      @release.send :with_release_candidate_version do |new_filename|
        File.read(new_filename).should == %{THIS_VERSION = "1.1.0"}
      end
    end

    it 'should yield a name different from the original buildfile' do
      @release.send :with_release_candidate_version do |new_filename|
        new_filename.should_not point_to_path('buildfile')
      end
    end
  end

  describe '#update_version_to_next' do
    before do
      write 'buildfile', "VERSION_NUMBER = '1.0.5-SNAPSHOT'"
      @release.send(:this_version=, "1.0.5-SNAPSHOT")
    end

    it 'should update the buildfile with a new version number' do
      @release.send :update_version_to_next
      `cp buildfile /tmp/out`
      file('buildfile').should contain('VERSION_NUMBER = "1.0.6-SNAPSHOT"')
    end

    it 'should commit the new buildfile on the trunk' do
      @release.should_receive(:message).and_return('Changed version number to 1.0.1-SNAPSHOT')
      @release.update_version_to_next
    end

    it 'should use the commit message specified by commit_message' do
      Release.commit_message  = 'Here is my custom message'
      @release.should_receive(:message).and_return('Here is my custom message')
      @release.update_version_to_next
    end

    it 'should use the commit message returned by commit_message if commit_message is a proc' do
      Release.commit_message  = lambda { |new_version|
        new_version.should == '1.0.1-SNAPSHOT'
        "increment version number to #{new_version}"
      }
      @release.should_receive(:message).and_return('increment version number to 1.0.1-SNAPSHOT')
      @release.update_version_to_next
    end

    it 'should inform the user of the new version' do
      lambda { @release.update_version_to_next }.should show_info('Current version is now 1.0.6-SNAPSHOT')
    end
    after { Release.commit_message = nil }
  end


  describe '#check' do
    before { @release.send(:this_version=, "1.0.0-SNAPSHOT") }
    it 'should fail if THIS_VERSION equals the next_version' do
      @release.stub(:resolve_next_version).and_return('1.0.0-SNAPSHOT')
      lambda { @release.check }.should raise_error("The next version can't be equal to the current version 1.0.0-SNAPSHOT.\nUpdate THIS_VERSION/VERSION_NUMBER, specify Release.next_version or use NEXT_VERSION env var")
    end
  end
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'

  before do
    write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
    @release = GitRelease.new
    Git.stub(:git)
    Git.stub(:current_branch).and_return('master')
  end

  describe '#applies_to?' do

    # TravisCI seems to place the tmp directory
    # sub-ordinate to git repository so this will not work
    unless ENV['TRAVIS_BUILD_ID']
      it 'should reject a non-git repo' do
        Dir.chdir(Dir.tmpdir) do
          GitRelease.applies_to?.should be_false
        end
      end
    end

    it 'should accept a git repo' do
      FileUtils.mkdir '.git'
      FileUtils.touch File.join('.git', 'config')
      GitRelease.applies_to?.should be_true
    end
  end

  describe '#check' do
    before do
      @release = GitRelease.new
      @release.send(:this_version=, '1.0.0-SNAPSHOT')
    end

    it 'should accept a clean repository' do
      Git.should_receive(:`).with('git status').and_return <<-EOF
# On branch master
nothing to commit (working directory clean)
      EOF
      Git.should_receive(:remote).and_return('master')
      lambda { @release.check }.should_not raise_error
    end

    it 'should reject a dirty repository' do
      Git.should_receive(:`).with('git status').and_return <<-EOF
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       foo.temp
EOF
      lambda { @release.check }.should raise_error(RuntimeError, /uncommitted files/i)
    end

    it 'should reject a repository not tracking remote branch' do
      Git.should_receive(:uncommitted_files).and_return([])
      Git.should_receive(:remote).and_return(nil)
      lambda{ @release.check }.should raise_error(RuntimeError,
        "You are releasing from a local branch that does not track a remote!")
    end
  end

  describe '#tag_release' do
    before do
      @release = GitRelease.new
      @release.stub(:extract_version).and_return('1.0.1')
      @release.stub(:resolve_tag).and_return('TEST_TAG')
      Git.stub(:git).with('tag', '-a', 'TEST_TAG', '-m', '[buildr] Cutting release TEST_TAG')
      Git.stub(:git).with('push', 'origin', 'tag', 'TEST_TAG')
      Git.stub(:commit)
      Git.stub(:push)
      Git.stub(:remote).and_return('origin')
    end

    it 'should delete any existing tag with the same name' do
      Git.should_receive(:git).with('tag', '-d', 'TEST_TAG')
      Git.should_receive(:git).with('push', 'origin', ':refs/tags/TEST_TAG')
      @release.tag_release 'TEST_TAG'
    end

    it 'should commit the buildfile before tagging' do
      Git.should_receive(:commit).with(File.basename(Buildr.application.buildfile.to_s), "Changed version number to 1.0.1")
      @release.tag_release 'TEST_TAG'
    end

    it 'should push the tag if a remote is tracked' do
      Git.should_receive(:git).with('tag', '-d', 'TEST_TAG')
      Git.should_receive(:git).with('push', 'origin', ':refs/tags/TEST_TAG')
      Git.should_receive(:git).with('tag', '-a', 'TEST_TAG', '-m', '[buildr] Cutting release TEST_TAG')
      Git.should_receive(:git).with('push', 'origin', 'tag',  'TEST_TAG')
      @release.tag_release 'TEST_TAG'
    end

    it 'should NOT push the tag if no remote is tracked' do
      Git.stub(:remote).and_return(nil)
      Git.should_not_receive(:git).with('push', 'origin', 'tag',  'TEST_TAG')
      @release.tag_release 'TEST_TAG'
    end
  end
end


describe SvnRelease do
  it_should_behave_like 'a release process'

  before do
    write 'buildfile', "VERSION_NUMBER = '1.0.0-SNAPSHOT'"
    @release = SvnRelease.new
    Svn.stub(:svn)
    Svn.stub(:repo_url).and_return('http://my.repo.org/foo/trunk')
    Svn.stub(:tag)
  end

  describe '#applies_to?' do
    it 'should reject a non-git repo' do
      SvnRelease.applies_to?.should be_false
    end

    it 'should accept a git repo' do
      FileUtils.touch '.svn'
      SvnRelease.applies_to?.should be_true
    end
  end

  describe '#check' do
    before do
      Svn.stub(:uncommitted_files).and_return([])
      @release = SvnRelease.new
      @release.send(:this_version=, "1.0.0-SNAPSHOT")
    end

    it 'should accept to release from the trunk' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/trunk')
      lambda { @release.check }.should_not raise_error
    end

    it 'should accept to release from a branch' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/branches/1.0')
      lambda { @release.check }.should_not raise_error
    end

    it 'should reject releasing from a tag' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/tags/1.0.0')
      lambda { @release.check }.should raise_error(RuntimeError, "SVN URL must contain 'trunk' or 'branches/...'")
    end

    it 'should reject a non standard repository layout' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/bar')
      lambda { @release.check }.should raise_error(RuntimeError, "SVN URL must contain 'trunk' or 'branches/...'")
    end

    it 'should reject an uncommitted file' do
      Svn.stub(:repo_url).and_return('http://my.repo.org/foo/trunk')
      Svn.stub(:uncommitted_files).and_return(['foo.rb'])
      lambda { @release.check }.should raise_error(RuntimeError,
        "Uncommitted files violate the First Principle Of Release!\n" +
        "foo.rb")
    end
  end
end
