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 2009/03/02 21:12:01 UTC
svn commit: r749429 - in /buildr/trunk: CHANGELOG lib/buildr/core/build.rb
spec/core/build_spec.rb
Author: boisvert
Date: Mon Mar 2 20:12:00 2009
New Revision: 749429
URL: http://svn.apache.org/viewvc?rev=749429&view=rev
Log:
BUILDR-222 Support Git as a version control system
BUILDR-223 Release Task: customizable commit message
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=749429&r1=749428&r2=749429&view=diff
==============================================================================
--- buildr/trunk/CHANGELOG (original)
+++ buildr/trunk/CHANGELOG Mon Mar 2 20:12:00 2009
@@ -7,6 +7,8 @@
* Added: BUILDR-242: Include Scala-Tools Repository by Default.
* Added: BUILDR-93: Add specs for ScalaCheck integration
* Added: BUILDR-94: Add specs for Scala Specs integration
+* Added: BUILDR-222: Support Git as a version control system
+* Added BUILDR-223 Release Task: customizable commit message
* Added: Info message "Packaging filename.ext" now displayed for packaging tasks
* Change: require 'buildr/scala' is now official required to use Scala features
* Change: Introduced new options from Rake 0.8.3: -I (libdir), -R (rakelib),
Modified: buildr/trunk/lib/buildr/core/build.rb
URL: http://svn.apache.org/viewvc/buildr/trunk/lib/buildr/core/build.rb?rev=749429&r1=749428&r2=749429&view=diff
==============================================================================
--- buildr/trunk/lib/buildr/core/build.rb (original)
+++ buildr/trunk/lib/buildr/core/build.rb Mon Mar 2 20:12:00 2009
@@ -106,14 +106,123 @@
end
+ module Git
+ class << self
+ # :call-seq:
+ # execute(*args)
+ # execute('tag', 'FOO', :nofail)
+ #
+ # Executes the VCS command and returns the output. A :RuntimeError is thrown if the exit statu
+ # non-zero.
+ # This behavior might be changed by providing the symbol :nofail has the last argument.
+ def execute(*args)
+ cmd = 'git ' + args.map { |arg| arg[' '] ? %Q{"#{arg}"} : arg }.join(' ')
+ trace cmd
+ `#{cmd}`.tap {
+ fail "GIT command failed with status #{$?.exitstatus}" unless $?.exitstatus == 0
+ }
+ end
+ alias :git :execute
+
+ # Execute 'git status' and return the output lines in an array.
+ def uncommitted_files
+ s = status
+ if s =~ /^nothing to commit \(working directory clean\)$/
+ []
+ else
+ s.split "\n"
+ end
+ end
- class Svn
+ # Commit the given file with a message.
+ # The file has to be known to Git meaning that it has either to have been already committed in the past
+ # or freshly added to the index. Otherwise it will fail.
+ def commit(file, message)
+ git 'commit', '-m', message, file
+ end
+
+ # Update the remote refs using local refs
+ #
+ # By default, the "remote" destination of the push is the the remote repo linked to the current branch.
+ # The default remote branch is the current local branch.
+ def push(remote_repo = remote, remote_branch = current_branch)
+ git 'push', remote, current_branch
+ end
+
+ # Execute git status and return the output.
+ # This method won't fail if the exit status is non-zero.
+ def status
+ cmd = "git status"
+ trace cmd
+ `#{cmd}`
+ end
+ protected
+
+ # Return true if at least one remote repository is defined. False otherwise.
+ def remotes_defined?
+ !git('remote').empty?
+ end
+
+ # Return the name of the remote repository whose branch the current local branch tracks,
+ # or nil if none.
+ def remote(branch = current_branch)
+ match = git('config', '-l').split("\n").find {|l| l.match "^branch\.#{branch}\.remote=.*$"}
+ match.split('=').last if match
+ end
+
+ # Return true if the given local branch tracks a remote branch.
+ def has_remote?(branch = current_branch)
+ remote(branch) != nil
+ end
+
+ def has_no_remote?(branch = current_branch)
+ !has_remote? branch
+ end
+
+ # Return the name of the current branch
+ def current_branch
+ git('branch').split("\n").find {|l| l =~ /^\* .*$/}[2..-1]
+ end
+ end
+ end #of Git
+
+ module Svn
class << self
+ # :call-seq:
+ # execute(*args)
+ # execute('tag', 'FOO', :nofail)
+ #
+ # Executes the VCS command and returns the output. A :RuntimeError is thrown if the exit statu
+ # non-zero.
+ # This behavior might be changed by providing the symbol :nofail has the last argument.
+ def execute(*args)
+ cmd = 'svn ' + args.map { |arg| arg[' '] ? %Q{"#{arg}"} : arg }.join(' ')
+ trace cmd
+ `#{cmd}`.tap {
+ fail "SVN command failed with status #{$?.exitstatus}" unless $?.exitstatus == 0
+ }
+ end
+ alias :svn :execute
+
+ def tag(tag_name)
+ url = tag_url repo_url, tag_name
+ remove url, 'Removing old copy' rescue nil
+ copy Dir.pwd, url, "Release #{tag_name}"
+ end
+
+ # Status check reveals modified files, but also SVN externals which we can safely ignore.
+ def uncommitted_files
+ svn('status', '--ignore-externals').reject { |line| line =~ /^X\s/ }
+ end
+
def commit(file, message)
svn 'commit', '-m', message, file
end
+
+ protected
+
def copy(dir, url, message)
svn 'copy', dir, url, '-m', message
end
@@ -126,24 +235,24 @@
def remove(url, message)
svn 'remove', url, '-m', message
end
-
- # Status check reveals modified files, but also SVN externals which we can safely ignore.
- def uncommitted_files
- svn('status', '--ignore-externals').reject { |line| line =~ /^X\s/ }
- end
-
+
# :call-seq:
- # svn(*args)
+ # tag_url(svn_url, version) => tag_url
#
- # Executes SVN command and returns the output.
- def svn(*args)
- cmd = 'svn ' + args.map { |arg| arg[' '] ? %Q{"#{arg}"} : arg }.join(' ')
- trace cmd
- `#{cmd}`.tap { fail 'SVN command failed' unless $?.exitstatus == 0 }
+ # Returns the SVN url for the tag.
+ # Can tag from the trunk or from branches.
+ # Can handle the two standard repository layouts.
+ # - http://my.repo/foo/trunk => http://my.repo/foo/tags/1.0.0
+ # - http://my.repo/trunk/foo => http://my.repo/tags/foo/1.0.0
+ def tag_url(svn_url, tag)
+ trunk_or_branches = Regexp.union(%r{^(.*)/trunk(.*)$}, %r{^(.*)/branches(.*)/([^/]*)$})
+ match = trunk_or_branches.match(svn_url)
+ prefix = match[1] || match[3]
+ suffix = match[2] || match[4]
+ prefix + '/tags' + suffix + '/' + tag
end
end
- end
-
+ end # of Svn class
class Release
@@ -157,6 +266,27 @@
# Release.tag_name = lambda { |ver| "foo-#{ver}" }
attr_accessor :tag_name
+ # Use this to specify a different commit message to commit the buildfile with the next version in source control.
+ # You can set the commit message or a proc that will be called with the next version number,
+ # for example:
+ # Release.commit_message = lambda { |ver| "Changed version number to #{ver}" }
+ attr_accessor :commit_message
+
+ # :call-seq:
+ # add(MyReleaseClass)
+ #
+ # Add a Release implementation to the list of available Release classes.
+ def add(release)
+ @list ||= []
+ @list |= [release]
+ end
+ alias :<< :add
+
+ # The list of supported Release implementations
+ def list
+ @list ||= []
+ end
+
# :call-seq:
# make()
#
@@ -168,12 +298,12 @@
options << '--environment' << Buildr.environment unless Buildr.environment.to_s.empty?
buildr %w{clean upload}, options
end
- tag_release
- commit_new_snapshot
+ tag_release resolve_tag
+ update_version_to_next
end
-
+
# :call-seq:
- # extract_version() => this_version
+ # extract_version() => this_versin
#
# Extract the current version number from the buildfile.
# Raise an error if not found.
@@ -184,36 +314,7 @@
fail 'Looking for THIS_VERSION = "..." in your Buildfile, none found'
end
- # :call-seq:
- # tag_url(svn_url, version) => tag_url
- #
- # Returns the SVN url for the tag.
- # Can tag from the trunk or from branches.
- # Can handle the two standard repository layouts.
- # - http://my.repo/foo/trunk => http://my.repo/foo/tags/1.0.0
- # - http://my.repo/trunk/foo => http://my.repo/tags/foo/1.0.0
- def tag_url(svn_url, version)
- trunk_or_branches = Regexp.union(%r{^(.*)/trunk(.*)$}, %r{^(.*)/branches(.*)/([^/]*)$})
- match = trunk_or_branches.match(svn_url)
- prefix = match[1] || match[3]
- suffix = match[2] || match[4]
- tag = tag_name || version
- tag = tag.call(version) if Proc === tag
- prefix + '/tags' + suffix + '/' + tag
- end
-
- # :call-seq:
- # check()
- #
- # Check that we don't have any local changes in the working copy. Fails if it finds anything
- # in the working copy that is not checked into source control.
- def check
- fail "SVN URL must contain 'trunk' or 'branches/...'" unless Svn.repo_url =~ /(trunk)|(branches.*)$/
- fail "Uncommitted SVN files violate the First Principle Of Release!\n#{Svn.uncommitted_files}" unless Svn.uncommitted_files.empty?
- end
-
protected
-
# :call-seq:
# buildr(tasks, options)
#
@@ -270,35 +371,104 @@
buildfile.gsub(THIS_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{new_version}"}) }
end
- # :call-seq:
- # tag_release()
- #
- # Tags the current working copy with the release version number.
- def tag_release
+ # Return the name of the tag to tag the release with.
+ def resolve_tag
version = extract_version
- info "Tagging release #{version}"
- url = tag_url Svn.repo_url, version
- Svn.remove url, 'Removing old copy' rescue nil
- Svn.copy Dir.pwd, url, "Release #{version}"
+ tag = tag_name || version
+ tag = tag.call(version) if Proc === tag
+ tag
end
- # :call-seq:
- # commit_new_snapshot()
- #
- # Last, we commit what we currently have in the working copy with an upgraded version number.
- def commit_new_snapshot
+ # Move the version to next and save the updated buildfile
+ def update_buildfile
buildfile = change_version { |version| version[-1] = (version[-1].to_i + 1).to_s + '-SNAPSHOT' }
File.open(Buildr.application.buildfile.to_s, 'w') { |file| file.write buildfile }
- Svn.commit Buildr.application.buildfile.to_s, "Changed version number to #{extract_version}"
+ end
+
+ # Return the message to use to cimmit the buildfile with the next version
+ def message
+ version = extract_version
+ msg = commit_message || "Changed version number to #{version}"
+ msg = msg.call(version) if Proc === msg
+ msg
+ end
+
+ def update_version_to_next
+ update_buildfile
+ end
+ end
+ end
+
+ class GitRelease < Release
+ class << self
+ def applies_to?(directory = '.')
+ File.exist? File.join(directory, '.git/config')
+ end
+
+ # Fails if one of theses 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
+ fail "Uncommitted files violate the First Principle Of Release!\n"+Git.uncommitted_files.join("\n") unless Git.uncommitted_files.empty?
+ fail "You are releasing from a local branch that does not track a remote!" if Git.remotes_defined? && Git.has_no_remote?
+ end
+
+ # Add a tag reference in .git/refs/tags and push it to the remote if any.
+ # If a tag with the same name already exists it will get deleted (in both local and remote repositories).
+ def tag_release(tag)
+ info "Committing buildfile with version number #{extract_version}"
+ Git.commit File.basename(Buildr.application.buildfile.to_s), message
+ Git.push if Git.has_remote?
+ info "Tagging release #{tag}"
+ Git.git 'tag', '-d', tag rescue nil
+ Git.git 'push', Git.remote, ":refs/tags/#{tag}" rescue nil if Git.has_remote?
+ Git.git 'tag', '-a', tag, '-m', "[buildr] Cutting release #{tag}"
+ Git.git 'push', Git.remote, 'tag', tag if Git.has_remote?
+ end
+
+ def update_version_to_next
+ super
info "Current version is now #{extract_version}"
+ Git.commit File.basename(Buildr.application.buildfile.to_s), message
+ Git.push if Git.has_remote?
end
end
end
+ # add GitRelease to the list of Release implementations
+ Release.add GitRelease
+
+ class SvnRelease < Release
+ class << self
+ def applies_to?(directory = '.')
+ File.exist? File.join(directory, '.svn')
+ end
+
+ def check
+ fail "Uncommitted files violate the First Principle Of Release!\n"+Svn.uncommitted_files.join("\n") unless Svn.uncommitted_files.empty?
+ fail "SVN URL must contain 'trunk' or 'branches/...'" unless Svn.repo_url =~ /(trunk)|(branches.*)$/
+ end
+
+ def tag_release(tag)
+ info "Tagging release #{tag}"
+ Svn.tag tag
+ end
+ def update_version_to_next
+ super
+ info "Current version is now #{extract_version}"
+ Svn.commit Buildr.application.buildfile.to_s, message
+ end
+ end
+ end
+ # add SvnRelease to the list of Release implementations
+ Release.add SvnRelease
+
desc 'Make a release'
task 'release' do |task|
- Release.make
+ release = Release.list.detect { |impl| impl.applies_to? }
+ fail 'Unable to detect the Version Control System.' unless release
+ release.make
end
end
Modified: buildr/trunk/spec/core/build_spec.rb
URL: http://svn.apache.org/viewvc/buildr/trunk/spec/core/build_spec.rb?rev=749429&r1=749428&r2=749429&view=diff
==============================================================================
--- buildr/trunk/spec/core/build_spec.rb (original)
+++ buildr/trunk/spec/core/build_spec.rb Mon Mar 2 20:12:00 2009
@@ -200,224 +200,451 @@
end
end
+describe Buildr::Git do
+ describe '#uncommitted_files' do
+ it 'shoud return an empty array on a clean repository' do
+ cmd_output = <<EOF
+# On branch master
+nothing to commit (working directory clean)
+EOF
+ Git.stub!(:status).and_return(cmd_output)
+ Git.uncommitted_files.should be_empty
+ end
-describe Buildr::Release do
-
+ it 'should reject a dirty repository' do
+ cmd_output = <<EOF
+# On branch master
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# foo.temp
+EOF
+ Git.stub!(:status).and_return(cmd_output)
+ Git.uncommitted_files.should == cmd_output.split("\n")
+ end
+ end
+
+ describe '#remotes_defined?' do
+ it 'should return false if no remote repositories defined' do
+ Git.stub!(:git).with('remote').and_return('')
+ Git.send(:remotes_defined?).should be_false
+ end
+
+ it 'should return true if at least one remote repository is defined' do
+ Git.stub!(:git).with('remote').and_return("origin\n")
+ Git.send(:remotes_defined?).should be_true
+ end
+ end
+
+ describe '#remote' do
+ it 'shoud return the name of the corresponding remote' do
+ Git.stub!(:git).with('config', '-l').and_return(<<EOF)
+alias.oneline=log --pretty=oneline
+core.repositoryformatversion=0
+core.filemode=true
+core.bare=false
+core.logallrefupdates=true
+remote.origin.url=git://github.com/vic/buildr.git
+remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
+branch.master.remote=origin
+branch.master.merge=refs/heads/master
+EOF
+ Git.send(:remote, 'master').should == 'origin'
+ end
+
+ it 'should return nil if no remote for the given branch' do
+ Git.stub!(:git).with('config', '-l').and_return(<<EOF)
+remote.origin.url=git://github.com/vic/buildr.git
+remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
+EOF
+ Git.send(:remote, 'master').should == nil
+ end
+ end
+
+ describe '#current_branch' do
+ it 'should return the current branch' do
+ Git.stub!(: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 Buildr::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.send(: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.send(: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.send(: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.send(: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.stub!(:svn).and_return(<<EOF)
+Path: .
+URL: http://my.repo.org/foo/trunk
+Repository Root: http://my.repo.org
+Repository UUID: 12345678-9abc-def0-1234-56789abcdef0
+Revision: 112
+Node Kind: directory
+Schedule: normal
+Last Changed Author: Lacton
+Last Changed Rev: 110
+Last Changed Date: 2008-08-19 12:00:00 +0200 (Tue, 19 Aug 2008)
+EOF
+ Svn.send(:repo_url).should == 'http://my.repo.org/foo/trunk'
+ end
+ end
+
+
+ end
+
+end # of Buildr::Svn
+
+describe 'a release process', :shared=>true 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)
- Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk')
- Svn.stub!(:uncommitted_files).and_return('')
- Svn.stub!(:remove)
- Svn.stub!(:copy)
- Svn.stub!(:commit)
+ @release.stub!(:buildr)
+ @release.stub!(:check)
end
it 'should tag a release with the release version' do
- Svn.should_receive(:copy).with(Dir.pwd, 'http://my.repo.org/foo/tags/1.0.0', 'Release 1.0.0').and_return {
- file('buildfile').should contain('VERSION_NUMBER = "1.0.0"')
+ @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').and_return {
+ file('buildfile').should contain('VERSION_NUMBER = "1.0.0"')
}
- Release.make
+ @release.make
end
it 'should update the buildfile with the next version number' do
- Release.make
+ @release.stub!(:tag_release)
+ @release.make
file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"')
end
it 'should commit the updated buildfile' do
- Svn.should_receive(:commit).with(File.expand_path('buildfile'), 'Changed version number to 1.0.1-SNAPSHOT').and_return {
- file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"')
- }
- Release.make
+ @release.stub!(:tag_release)
+ @release.make
+ file('buildfile').should contain('VERSION_NUMBER = "1.0.1-SNAPSHOT"')
end
end
-
- describe '#check' do
+ describe '#resolve_tag' do
before do
- Svn.stub!(:uncommitted_files).and_return('')
+ @release.stub!(:extract_version).and_return('1.0.0')
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
+ it 'should return tag specified by tag_name' do
+ @release.tag_name = 'first'
+ @release.send(:resolve_tag).should == 'first'
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/...'")
+
+ 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
- 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('M foo.rb')
- lambda { Release.check }.should raise_error(RuntimeError,
- "Uncommitted SVN files violate the First Principle Of Release!\n" +
- "M foo.rb")
+ 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'
+ @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'
+ @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'
+ @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'
+ @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')
+ lambda { @release.extract_version }.should raise_error('Looking for THIS_VERSION = "..." in your Buildfile, none found')
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
- Release.tag_url('http://my.repo.org/foo/trunk', '1.0.0').should == 'http://my.repo.org/foo/tags/1.0.0'
+ 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 accept to tag foo/branches/1.0' do
- Release.tag_url('http://my.repo.org/foo/branches/1.0', '1.0.1').should == 'http://my.repo.org/foo/tags/1.0.1'
+ 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 accept to tag trunk/foo' do
- Release.tag_url('http://my.repo.org/trunk/foo', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0'
+ 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
- it 'should accept to tag branches/foo/1.0' do
- Release.tag_url('http://my.repo.org/branches/foo/1.0', '1.0.0').should == 'http://my.repo.org/tags/foo/1.0.0'
+
+ describe '#update_version_to_next' do
+ before do
+ write 'buildfile', 'THIS_VERSION = "1.0.0"'
end
-
- it 'should use tag specified by tag_name' do
- Release.tag_name = 'first'
- Release.tag_url('http://my.repo.org/foo/trunk', '1.0.0').should == 'http://my.repo.org/foo/tags/first'
+
+ it 'should update the buildfile with a new version number' do
+ @release.send :update_version_to_next
+ file('buildfile').should contain('THIS_VERSION = "1.0.1-SNAPSHOT"')
end
-
- it 'should use tag returned by tag_name if tag_name is a proc' do
- Release.tag_name = lambda { |version| "buildr-#{version}" }
- Release.tag_url('http://my.repo.org/foo/trunk', '1.0.0').should == 'http://my.repo.org/foo/tags/buildr-1.0.0'
+
+ 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
- after { Release.tag_name = nil }
- 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
- describe '#with_release_candidate_version' do
- before do
- Buildr.application.stub!(:buildfile).and_return(file('buildfile'))
- write 'buildfile', "THIS_VERSION = '1.1.0-SNAPSHOT'"
+ 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.1-SNAPSHOT')
end
+ end
+
+end # of Release
- 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
+
+
+describe Buildr::GitRelease do
+ before do
+ @release = nil
+ end
+ describe '#applies_to?' do
+ it 'should reject a non-git repo' do
+ GitRelease.applies_to?.should be_false
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
+ 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 '#release_check' do
+ it 'shoud accept a clean repository' do
+ cmd_output = <<EOF
+# On branch master
+nothing to commit (working directory clean)
+EOF
+ Git.stub!(:status).and_return(cmd_output)
+ Git.stub!(:remotes_defined?).and_return(false)
+ lambda{ GitRelease.check }.should_not raise_error
+ end
+
+ it 'should reject a dirty repository' do
+ cmd_output = <<EOF
+# On branch master
+# Untracked files:
+# (use "git add <file>..." to include in what will be committed)
+#
+# foo.temp
+EOF
+ Git.stub!(:uncommitted_files).and_return(cmd_output.split("\n"))
+ lambda { GitRelease.check }.should raise_error(RuntimeError)
+ end
+
+
+ it 'should pass if no remote repositories are defined' do
+ Git.stub!(:uncommitted_files).and_return([])
+ Git.stub!(:remotes_defined?).and_return(false)
+
+ lambda{ GitRelease.check }.should_not raise_error
+ end
+
+ it 'should fail if the current branch does not track a remote repo but at least one remote repo is defined' do
+ Git.stub!(:uncommitted_files).and_return([])
+ Git.stub!(:remotes_defined?).and_return(true)
+ Git.stub!(:has_no_remote?).and_return(true)
+
+ lambda{ GitRelease.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
- write 'buildfile', "THIS_VERSION = '1.0.1'"
- Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk')
- Svn.stub!(:copy)
- Svn.stub!(:remove)
+ GitRelease.stub!(:extract_version).and_return('1.0.1')
+ 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')
+ Git.stub!(:git).with('branch').and_return("* master\n")
+ GitRelease.stub!(:resolve_tag).and_return('TEST_TAG')
end
- it 'should tag the working copy' do
- Svn.should_receive(:copy).with(Dir.pwd, 'http://my.repo.org/foo/tags/1.0.1', 'Release 1.0.1')
- Release.send :tag_release
+ 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')
+
+ GitRelease.tag_release 'TEST_TAG'
end
- it 'should remove the tag if it already exists' do
- Svn.should_receive(:remove).with('http://my.repo.org/foo/tags/1.0.1', 'Removing old copy')
- Release.send :tag_release
+ 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")
+
+ GitRelease.tag_release 'TEST_TAG'
end
- it 'should accept that the tag does not exist' do
- Svn.stub!(:remove).and_raise(RuntimeError)
- Release.send :tag_release
+ 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')
+
+ GitRelease.tag_release 'TEST_TAG'
end
- it 'should inform the user' do
- lambda { Release.send :tag_release }.should show_info('Tagging release 1.0.1')
+ it 'should NOT push the tag if no remote is tracked' do
+ Git.stub!(:remote)
+ Git.should_not_receive(:git).with('push', 'origin', 'tag', 'TEST_TAG')
+
+ GitRelease.tag_release 'TEST_TAG'
end
end
+end
- describe '#commit_new_snapshot' do
+describe Buildr::SvnRelease do
+ 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
- write 'buildfile', 'THIS_VERSION = "1.0.0"'
- Svn.stub!(:commit)
+ Svn.stub!(:uncommitted_files).and_return('')
end
- it 'should update the buildfile with a new version number' do
- Release.send :commit_new_snapshot
- file('buildfile').should contain('THIS_VERSION = "1.0.1-SNAPSHOT"')
+ it 'should accept to release from the trunk' do
+ Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/trunk')
+ lambda { SvnRelease.check }.should_not raise_error
end
- it 'should commit the new buildfile on the trunk' do
- Svn.should_receive(:commit).with(File.expand_path('buildfile'), 'Changed version number to 1.0.1-SNAPSHOT')
- Release.send :commit_new_snapshot
+ it 'should accept to release from a branch' do
+ Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/branches/1.0')
+ lambda { SvnRelease.check }.should_not raise_error
end
- it 'should inform the user of the new version' do
- lambda { Release.send :commit_new_snapshot }.should show_info('Current version is now 1.0.1-SNAPSHOT')
+ it 'should reject releasing from a tag' do
+ Svn.stub!(:repo_url).and_return('http://my.repo.org/foo/tags/1.0.0')
+ lambda { SvnRelease.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 { SvnRelease.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(['M foo.rb'])
+ lambda { SvnRelease.check }.should raise_error(RuntimeError,
+ "Uncommitted files violate the First Principle Of Release!\n" +
+ "M foo.rb")
end
end
-
+
end
+describe Buildr::SvnRelease do
+ before do
+ @release = SvnRelease
+ Svn.stub!(:execute)
+ Svn.stub!(:tag)
+ Svn.stub!(:commit)
+ Svn.stub!(:uncommitted_files)
+ end
+ it_should_behave_like 'a release process'
+end
-describe Buildr::Svn, '#repo_url' do
- it 'should extract the SVN URL from svn info' do
- Svn.stub!(:svn, 'info').and_return(<<EOF)
-Path: .
-URL: http://my.repo.org/foo/trunk
-Repository Root: http://my.repo.org
-Repository UUID: 12345678-9abc-def0-1234-56789abcdef0
-Revision: 112
-Node Kind: directory
-Schedule: normal
-Last Changed Author: Lacton
-Last Changed Rev: 110
-Last Changed Date: 2008-08-19 12:00:00 +0200 (Tue, 19 Aug 2008)
-EOF
- Svn.repo_url.should == 'http://my.repo.org/foo/trunk'
+describe Buildr::GitRelease do
+ before do
+ @release = GitRelease
+ Git.stub!(:git)
+ Git.stub!(:commit)
+ Git.stub!(:remote)
+ Git.stub!(:has_remote?)
end
-end
\ No newline at end of file
+ it_should_behave_like 'a release process'
+end